From c54f7c447a0da0dace4f5740aee7888410e34e19 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E7=9F=B3=E5=A4=B4?= <3301352378@qq.com> Date: Wed, 8 Jul 2026 16:08:10 +0800 Subject: [PATCH] docs: add N12R internal sandbox handoff workflow --- docs/go-mcp-minimal-plan.md | 139 +++-- docs/go-mcp-runbook.md | 35 +- docs/handoff-2026-07-06.md | 67 ++- docs/internal-sandbox-live-capture-plan.md | 217 +++++++ docs/internal-sandbox-operator-runbook.md | 387 +++++++++++++ docs/n12r-final-handoff-checklist.md | 219 +++++++ ...12r-return-package-validation-checklist.md | 223 ++++++++ docs/offline-evidence-intake-plan.md | 32 +- scripts/test-validate-n12r-return-package.ps1 | 179 ++++++ scripts/validate-n12r-return-package.ps1 | 537 ++++++++++++++++++ 10 files changed, 1986 insertions(+), 49 deletions(-) create mode 100644 docs/internal-sandbox-live-capture-plan.md create mode 100644 docs/internal-sandbox-operator-runbook.md create mode 100644 docs/n12r-final-handoff-checklist.md create mode 100644 docs/n12r-return-package-validation-checklist.md create mode 100644 scripts/test-validate-n12r-return-package.ps1 create mode 100644 scripts/validate-n12r-return-package.ps1 diff --git a/docs/go-mcp-minimal-plan.md b/docs/go-mcp-minimal-plan.md index 1242f9f..b23a6da 100644 --- a/docs/go-mcp-minimal-plan.md +++ b/docs/go-mcp-minimal-plan.md @@ -26,9 +26,15 @@ N0 Baseline cleanup and direction lock -> N10 Operator runbook and process scripts -> N11 Commit/review checkpoint -> N12-pre Offline evidence intake - -> N12 Real logged-in UIA capture gate + -> N12R Internal-sandbox live UIA capture route + -> N12R-review Selector design review gate + +Blocked/non-active: + -> N12-current Current-environment real logged-in UIA capture gate ``` +**Reality update 2026-07-08:** iSphere is internal-network only. The current repository environment is an outer-network coordination/analysis environment. Therefore `N12-current` is not the active route unless network placement changes. Live login and live UIA capture must happen in an internal-network test sandbox through `N12R`. + Hard rule: **no node may start until the previous node's acceptance conditions are met.** --- @@ -753,7 +759,7 @@ Pass if: - Unrelated generated files are staged. - Worktree contains ignored build outputs outside `runs/`. -**Next node:** N12-pre if the current environment cannot log in; otherwise N12 may start only when the real logged-in current-environment preconditions below are met. +**Next node:** N12-pre for offline evidence intake, followed by N12R for live capture in an internal-network test sandbox. `N12-current` remains blocked because the current outer-network environment cannot log in to internal iSphere. --- @@ -830,24 +836,101 @@ Pass if: - Package is used to design automatic login, sending, file transfer, injection, hook, memory reading, or endpoint-security bypass. - Any reader treats the offline package as a replacement for true N12. -**Next node:** N12 remains blocked until the current environment can manually log in and perform a live current-environment UIA capture. +**Next node:** N12R internal-sandbox live capture route if live UI evidence is needed. `N12-current` remains blocked in the outer-network environment. --- -## N12: Real logged-in UIA capture gate +## N12R: Internal-sandbox live capture route -**Purpose:** Capture real iSphere UIA evidence after the user provides/login context. +**Purpose:** Capture live iSphere UIA evidence from a separate internal-network test sandbox where iSphere can be manually opened and logged in. **Preconditions:** - N11 accepted. -- Optional N12-pre offline evidence, if used, has been validated only as pre-gate context and not as N12 pass evidence. -- User manually opens and logs in to iSphere. -- iSphere main window is visible. +- Optional N12-pre offline evidence, if used, has been validated only as pre-gate context and not as live capture evidence. +- An internal-network test sandbox can manually open and log in to iSphere / IMPlatformClient using the normal approved process. +- The internal sandbox has this repository or a trusted copy of the built helper/MCP artifacts. +- The operator follows `docs/internal-sandbox-operator-runbook.md`. **Actions:** -Use Go MCP tools: +- In the internal sandbox, run only the first-phase read-only operations: + +```text +win_helper_version +win_helper_self_check +win_helper_scan_windows +win_helper_dump_uia +``` + +or the equivalent helper ops: + +```text +version +self_check +scan_windows +dump_uia +``` + +- Save the returned package under: + +```text +runs/internal-sandbox-live-capture// +``` + +- Follow the package contract in `docs/internal-sandbox-live-capture-plan.md`. + +**Expected outputs:** + +- `metadata/source-notes.txt` stating internal-network test sandbox, manual login, main window visible, and forbidden actions not performed. +- `helper/version.json`. +- `helper/self-check.json`. +- `windows/scan-windows.json`. +- `windows/selected-window.json`. +- `uia/dump-uia-main.json`. +- `uia/dump-uia-main-redacted.json`. +- `notes/operator-notes.txt`. + +**Acceptance conditions:** + +Pass if: + +- The package satisfies `docs/n12r-return-package-validation-checklist.md`. +- `scan-windows.json` includes an iSphere / IMPlatformClient candidate window. +- `dump-uia-main.json` contains a UI Automation root tree for the selected window. +- The capture notes state the user manually logged in and no forbidden action was performed. +- The redacted dump exists before broad sharing. + +**Stop conditions:** + +- Internal sandbox cannot manually log in. +- No candidate window appears. +- UIA tree is empty or inaccessible. +- Package contains secrets or message contents that require redaction before review. +- Any step requires automatic login, clicking into conversations, sending, uploading, downloading, injection, hook, memory reading, or endpoint-security bypass. + +**Next node:** N12R-review selector design review gate. Do not implement selectors until N12R evidence is accepted and a separate selector design plan is approved. + +--- + +## N12-current: Current-environment real logged-in UIA capture gate (blocked) + +This is the original N12 definition. It is retained as a blocked/non-active gate for historical clarity. It is not the active route while the repository environment is outside the internal network. + +**Blocked reason:** iSphere is internal-network only, and the current repository environment cannot manually log in. + +**Original purpose:** Capture real iSphere UIA evidence from a manually logged-in iSphere window in the same environment where this repository is running. + +**Original preconditions, if network placement changes later:** + +- N11 accepted. +- Optional N12-pre offline evidence, if used, has been validated only as pre-gate context and not as N12 pass evidence. +- The repository environment can manually open and log in to iSphere / IMPlatformClient using the normal approved process. +- iSphere main window is visible in that same environment. + +**Original allowed actions:** + +Use only these Go MCP tools: ```text win_helper_scan_windows @@ -860,35 +943,9 @@ Save output under: runs/real-loggedin-lab/uia-dumps/ ``` -**Expected outputs:** +**Current decision:** Do not wait on this gate as the active route. Use N12R internal-sandbox live capture instead. -- One or more real iSphere UIA dump JSON files. -- Window metadata showing process name/title/handle. - -**Acceptance conditions:** - -Pass if: - -- Candidate iSphere/IMPlatform window is found. -- UIA root node is a window/control tree. -- Dump includes enough fields to identify controls: - - `name` - - `control_type` - - `automation_id` - - `class_name` - - `children` -- No clicking, typing, sending, uploading, receiving, or downloading occurs. -- No passwords/tokens are captured. - -**Stop conditions:** - -- User is not logged in. -- No candidate window appears. -- UIA tree is empty or inaccessible. -- Captured data contains sensitive secrets that need redaction before reporting. -- Current environment cannot log in; in that case, use only N12-pre offline analysis and do not mark N12 passed. - -**Next node:** Future selector design. Do not implement selectors until N12 passes. +**Next node:** Future selector design may be considered only after N12R evidence is accepted, or after N12-current becomes possible and passes. Do not implement selectors before a separate selector design plan is approved. --- @@ -897,7 +954,7 @@ Pass if: Every node must satisfy these rules: 1. No Python MCP code. -2. No real send/search/file action before N12. +2. No real send/search/file action before an explicitly approved post-N12R selector/action plan. 3. No process injection, hook, credential extraction, token extraction, or memory reading. 4. No broad `git add -A`; stage exact paths. 5. Every code node uses tests first. @@ -911,13 +968,13 @@ Every node must satisfy these rules: Current guidance after N0-N11 completion is: ```text -N12-pre if the current environment cannot log in; N12 only after current-environment manual login is possible. +N12-pre has been used for offline evidence. Because the current environment is outside the internal network, the active live-capture route is N12R internal-sandbox live capture. N12-current remains blocked unless network placement changes. ``` Immediate next action: ```text -If login is unavailable here, collect/validate the full sandbox artifact bundle described in docs/offline-evidence-intake-plan.md. Minimal manifest/scan/dump JSON is optional and only for advanced operators. +If live UI evidence is needed, use `docs/internal-sandbox-live-capture-plan.md` and `docs/internal-sandbox-operator-runbook.md` to collect an N12R package from an internal-network test sandbox. Continue to use `docs/offline-evidence-intake-plan.md` only for offline file evidence. ``` -Do not treat offline evidence as N12 pass. Do not start selector/action design until true N12 passes in the current environment. +Do not treat offline evidence as N12 pass. Do not start selector/action design until N12R live evidence is accepted, or until N12-current becomes possible and passes in the current environment. diff --git a/docs/go-mcp-runbook.md b/docs/go-mcp-runbook.md index 010a86c..94b1bc5 100644 --- a/docs/go-mcp-runbook.md +++ b/docs/go-mcp-runbook.md @@ -211,19 +211,42 @@ Procedure: During this procedure, the operator must not click, type, send, upload, download, modify client state, or capture passwords/tokens. -## 10. If this environment cannot log in +## 10. If this outer-network environment cannot log in -Do not run or mark the real-login UIA capture gate as passed. +Do not run or mark the original current-environment real-login UIA capture gate as passed. -If login is only possible in another test sandbox, follow `docs/offline-evidence-intake-plan.md` and use the recommended **Full sandbox artifact bundle** route. The user does not need to generate JSON. Ask them to copy the logged-in test sandbox's install directory, user-data directories, ProgramData directories, shortcuts, `%TEMP%\importal` / `localcache` candidates, and simple copy notes into `runs\offline-evidence-intake\\`. +iSphere is internal-network only. If the current repository environment is outside that network, use two separate routes: -The minimal `manifest.json` / `scan_windows-redacted.json` / `dump_uia-main-redacted.json` route is optional and only for advanced operators. In either route, the package is `N12-pre` context only. It can support offline inventory and reverse analysis, but it does not replace live current-environment `win_helper_scan_windows` and `win_helper_dump_uia` evidence. +1. **N12-pre offline evidence** for copied files and static analysis. Follow `docs/offline-evidence-intake-plan.md` and place packages under `runs\offline-evidence-intake\\`. +2. **N12R internal-sandbox live capture** for real logged-in UIA evidence. Follow `docs/internal-sandbox-live-capture-plan.md` and `docs/internal-sandbox-operator-runbook.md`; place returned packages under `runs\internal-sandbox-live-capture\\`. -After receiving a full bundle, first do read-only inventory. Do not directly run unknown copied binaries, do not modify original copied files, do not use copied credential-like material to log in, and do not design automatic login, send message, send file, receive file, injection, hook, memory reading, or endpoint-security bypass behavior. +N12R is the active live-capture route when login is only possible in an internal-network test sandbox. It still allows only the four first-phase read-only tools: + +```text +win_helper_version +win_helper_self_check +win_helper_scan_windows +win_helper_dump_uia +``` + +After receiving an N12R package, validate it with `docs/n12r-return-package-validation-checklist.md` before using it as evidence for selector-design review. Prefer the scripted read-only validator: + +```powershell +$captureRoot = "runs\internal-sandbox-live-capture\" +$reportRoot = "runs\internal-sandbox-live-capture-reports" +New-Item -ItemType Directory -Force -Path $reportRoot | Out-Null +powershell -NoProfile -ExecutionPolicy Bypass -File .\scripts\validate-n12r-return-package.ps1 ` + -PackageRoot $captureRoot ` + -ReportPath (Join-Path $reportRoot "-validation.md") +``` + +Keep the validation report outside `$captureRoot`; the returned package is evidence input and should remain unchanged. + +Neither offline evidence nor N12R authorizes automatic login, search contacts, open conversation, send message, send file, receive file, injection, hook, memory reading, credential extraction, or endpoint-security bypass behavior. ## 11. Troubleshooting - If C# helper build fails, run `scripts\build-win-helper.ps1` directly and check for missing .NET Framework reference assemblies. - If `win_helper_version` fails, rerun `powershell -NoProfile -ExecutionPolicy Bypass -File scripts\verify-win-helper.ps1` first. - If Go MCP verification fails, rerun `go test ./...` and `powershell -NoProfile -ExecutionPolicy Bypass -File scripts\verify-go-mcp.ps1`. -- If a client cannot start the MCP server, confirm the configured command points to the built `.exe` and the working directory is the repository root. +- If a client cannot start the MCP server, confirm the configured command points to the built `.exe` and the working directory is the repository root. diff --git a/docs/handoff-2026-07-06.md b/docs/handoff-2026-07-06.md index ede0173..b10f006 100644 --- a/docs/handoff-2026-07-06.md +++ b/docs/handoff-2026-07-06.md @@ -378,4 +378,69 @@ Only after that should the team decide whether to: 2. Ask for a better evidence package. 3. Wait until current-environment live login becomes possible. 4. Re-plan true N12. -``` +``` + + +--- + +## 12. Reality update - 2026-07-08 + +The current repository environment is an outer-network coordination and analysis environment. iSphere / IMPlatformClient is internal-network only, so manual login cannot happen in the current environment. + +Do not keep waiting for current-environment login as the active path. The active live-capture route is now: + +```text +N12R: Internal-sandbox live capture route +``` + +New operator documents: + +```text +docs/internal-sandbox-live-capture-plan.md + Defines N12R node sequence, package contract, acceptance conditions, and boundaries. + +docs/n12r-final-handoff-checklist.md + Short final handoff checklist for internal-sandbox work, outer-network validation, boundaries, and next decision. + +docs/internal-sandbox-operator-runbook.md + Step-by-step instructions for the internal-network sandbox operator. + +docs/n12r-return-package-validation-checklist.md + Outer-network validation checklist for returned N12R packages. + +scripts/validate-n12r-return-package.ps1 + One-command read-only validator for returned N12R packages. It writes validation reports outside the returned package root. + +scripts/test-validate-n12r-return-package.ps1 + Local regression tests for the N12R package validator. +``` + +N12R package destination: + +```text +runs/internal-sandbox-live-capture// +``` + +Suggested outer-network validation command after a package returns: + +```powershell +$captureRoot = "runs\internal-sandbox-live-capture\" +$reportRoot = "runs\internal-sandbox-live-capture-reports" +New-Item -ItemType Directory -Force -Path $reportRoot | Out-Null +powershell -NoProfile -ExecutionPolicy Bypass -File .\scripts\validate-n12r-return-package.ps1 ` + -PackageRoot $captureRoot ` + -ReportPath (Join-Path $reportRoot "-validation.md") +``` + +N12-pre offline evidence remains useful for static analysis, but it does not replace N12R live UIA capture. Original current-environment N12 remains blocked unless the repository environment is moved into the internal network and can manually log in. + +The only allowed live operations in N12R remain: + +```text +win_helper_version +win_helper_self_check +win_helper_scan_windows +win_helper_dump_uia +``` + +Do not implement selector/action tools until N12R evidence is accepted and a separate selector design plan is approved. diff --git a/docs/internal-sandbox-live-capture-plan.md b/docs/internal-sandbox-live-capture-plan.md new file mode 100644 index 0000000..5cb049f --- /dev/null +++ b/docs/internal-sandbox-live-capture-plan.md @@ -0,0 +1,217 @@ +# Internal Sandbox Live Capture Plan + +## 1. Purpose + +This document replaces the unrealistic assumption that the current outer-network repository environment can log in to iSphere. + +iSphere is an internal-network application. The current environment is an outer-network coordination and analysis environment. Therefore, live login and live UI Automation capture must happen in a separate internal-network test sandbox where the operator can manually open and log in to iSphere. + +This path is named: + +```text +N12R: Internal-sandbox live capture route +``` + +`N12R` is a practical replacement route for collecting live read-only UI evidence from the internal sandbox. It does not mean the original current-environment N12 gate has passed. + +## 2. Environment roles + +| Environment | Role | Allowed work | +| --- | --- | --- | +| Current outer-network repo environment | Coordination, planning, static/offline evidence analysis, report validation | Read docs, inspect copied evidence packages, generate reports, validate returned capture packages | +| Internal-network test sandbox | Manual iSphere login and live read-only capture | Build/verify helper, manually log in, run only read-only scan/dump capture, package results | + +## 3. Current node state + +Completed before this plan: + +```text +N0-N11 First Go MCP / C# helper phase completed +N12-pre Offline evidence intake and reports for copied package zyl completed +``` + +Original true N12 remains blocked in the current environment because this environment cannot reach or log in to internal iSphere. + +New route: + +```text +N12R-0 Prepare internal-sandbox runbook and capture package contract +N12R-1 Internal sandbox verifies helper and Go MCP readiness +N12R-2 Operator manually opens and logs in to iSphere in the internal sandbox +N12R-3 Operator performs read-only live window scan and UIA dump +N12R-4 Operator packages redacted live capture outputs +N12R-5 Outer-network environment validates returned package +N12R-6 Review gate: decide whether selector design may start +``` + +No node may be skipped. Offline evidence never replaces live capture evidence. + +## 4. Hard boundaries + +The internal sandbox route only allows read-only evidence capture. + +Allowed operations: + +```text +win_helper_version +win_helper_self_check +win_helper_scan_windows +win_helper_dump_uia +``` + +Equivalent C# helper operations are also allowed only for capture fallback: + +```text +version +self_check +scan_windows +dump_uia +``` + +Forbidden operations and designs: + +```text +search contacts +open conversation +send message +send file +receive/download file +automatic login +using copied credentials +credential/token/cookie extraction +process injection +hook +memory reading +endpoint-security bypass/evasion +stealth/persistence +modifying the iSphere client +running copied executables from offline evidence packages +``` + +The operator must use normal manual login only. + +## 5. N12R package contract + +Returned live capture packages should be placed in this repository under: + +```text +runs/internal-sandbox-live-capture// +``` + +Recommended package shape: + +```text +runs/internal-sandbox-live-capture// + metadata/ + source-notes.txt + os-version.txt + process-list.txt + repo-status.txt + verification-summary.txt + helper/ + version.json + self-check.json + windows/ + scan-windows.json + selected-window.json + uia/ + dump-uia-main.json + dump-uia-main-redacted.json + notes/ + operator-notes.txt +``` + +Optional files: + +```text + helper/ + verify-win-helper-output.txt + mcp/ + verify-go-mcp-output.txt + mcp-client-config.txt + screenshots/ + main-window-redacted.png +``` + +Do not include passwords, tokens, cookies, private keys, full message bodies, or unrelated business documents. + +## 6. Required package notes + +`metadata/source-notes.txt` must include: + +```text +capture_id: +source_environment: internal-network test sandbox +operator: +capture_time_local: +iSphere manually logged in: yes +client opened before capture: yes +main window visible: yes +operations used: version, self_check, scan_windows, dump_uia +forbidden actions performed: no +this package is N12R live evidence, not current-environment N12 pass +``` + +`notes/operator-notes.txt` should include: + +```text +What client was opened: +How login was performed: manual normal company process +Any MFA/SSO used: yes/no, no secrets included +Selected window reason: +Redactions applied: <brief list> +Any capture problems: <brief list> +``` + +## 7. Acceptance conditions for N12R + +N12R package validation passes only if all are true: + +1. Package is under `runs/internal-sandbox-live-capture/<capture-id>/`. +2. `metadata/source-notes.txt` exists and states this came from an internal-network test sandbox. +3. Notes state iSphere was manually logged in and the main window was visible. +4. `helper/version.json` exists and reports `ISphereWinHelper`. +5. `helper/self-check.json` exists and indicates desktop/UIA availability. +6. `windows/scan-windows.json` exists and includes an iSphere / IMPlatformClient candidate window. +7. `windows/selected-window.json` records the selected candidate window metadata. +8. `uia/dump-uia-main.json` exists and contains a UI Automation root tree. +9. Redacted copy `uia/dump-uia-main-redacted.json` exists before broad sharing. +10. No evidence suggests clicking, typing, sending, uploading, downloading, automatic login, injection, hook, memory reading, or bypass behavior. + +## 8. What N12R does not authorize + +N12R does not authorize selector implementation or action tools by itself. It only provides live UI structure evidence. + +After N12R package validation, the next step is a review gate: + +```text +Review whether selector design may start. +``` + +Selector design, if approved later, must begin as a separate written plan. It must still avoid message sending, file transfer, automatic login, injection, hook, memory reading, and bypass behavior unless an explicit later plan changes the project boundary. + +## 9. Relationship to existing offline evidence + +Existing package: + +```text +runs/offline-evidence-intake/zyl/ +``` + +Existing reports: + +```text +runs/offline-evidence-intake-reports/zyl/ +``` + +Those reports remain useful for: + +```text +application inventory +local data discovery +static evidence analysis +MsgLib format hypotheses +timeline and redaction planning +``` + +They do not replace N12R live UIA capture. Offline evidence is context only. diff --git a/docs/internal-sandbox-operator-runbook.md b/docs/internal-sandbox-operator-runbook.md new file mode 100644 index 0000000..9543c95 --- /dev/null +++ b/docs/internal-sandbox-operator-runbook.md @@ -0,0 +1,387 @@ +# Internal Sandbox Operator Runbook + +This runbook is for the operator who has access to an internal-network test sandbox where iSphere / IMPlatformClient can be manually opened and logged in. + +The goal is to produce a live read-only capture package for the outer-network coordination environment. + +## 1. Safety boundary + +Do only these actions: + +```text +manual open iSphere / IMPlatformClient +manual normal login +keep main window visible +run version +run self_check +run scan_windows +run dump_uia +copy JSON/text outputs into a package +``` + +Do not do these actions: + +```text +automatic login +search contacts +open conversation +send message +send file +receive/download file +use copied credentials +extract password/token/cookie/private key +inject process +hook +read target process memory +bypass endpoint security +run executables copied from offline evidence packages +``` + +## 2. Prepare repository in the internal sandbox + +Open PowerShell in the repository root. + +Example: + +```powershell +cd E:\isphere-ai-bridge +``` + +If the repository path is different, use that actual path. All commands below assume the repository root is the current directory. + +Check repository status: + +```powershell +git status --short --branch +``` + +Recommended: record this output into the package later as: + +```text +metadata/repo-status.txt +``` + +## 3. Verify helper and Go MCP readiness + +Run helper verification: + +```powershell +powershell -NoProfile -ExecutionPolicy Bypass -File scripts\verify-win-helper.ps1 +``` + +Expected high-level result: + +```text +ok=true +helper version=0.1.0 +uia_available=true or valid boolean +``` + +If Go is installed in the internal sandbox, also run: + +```powershell +powershell -NoProfile -ExecutionPolicy Bypass -File scripts\verify-go-mcp.ps1 +``` + +Expected high-level result: + +```text +ok=true +tool_count=4 +helper_name=ISphereWinHelper +action_tools_present=false +``` + +If Go is unavailable, do not install unrelated tooling during the capture window unless the operator has approval. The C# helper fallback below is enough to capture the same four read-only helper operations. + +## 4. Create capture directory + +Choose a capture id. Recommended format: + +```text +YYYY-MM-DD-internal-sandbox-<short-name> +``` + +Example: + +```powershell +$captureId = "2026-07-08-internal-sandbox-a" +$captureRoot = "runs\internal-sandbox-live-capture\$captureId" +New-Item -ItemType Directory -Force -Path ` + "$captureRoot\metadata", ` + "$captureRoot\helper", ` + "$captureRoot\windows", ` + "$captureRoot\uia", ` + "$captureRoot\notes" | Out-Null +``` + +## 5. Record metadata + +Record OS version: + +```powershell +Get-ComputerInfo | Select-Object WindowsProductName, WindowsVersion, OsBuildNumber, CsUserName | + Format-List | Out-File -FilePath "$captureRoot\metadata\os-version.txt" -Encoding UTF8 +``` + +Record process list: + +```powershell +Get-Process | Sort-Object ProcessName | + Select-Object ProcessName, Id, MainWindowTitle, Path | + Format-Table -AutoSize | Out-File -FilePath "$captureRoot\metadata\process-list.txt" -Encoding UTF8 +``` + +Record repo status: + +```powershell +git status --short --branch | Out-File -FilePath "$captureRoot\metadata\repo-status.txt" -Encoding UTF8 +``` + +Create source notes: + +```powershell +@" +capture_id: $captureId +source_environment: internal-network test sandbox +operator: <fill operator name or role> +capture_time_local: $(Get-Date -Format "yyyy-MM-dd HH:mm:ss zzz") +iSphere manually logged in: yes/no +client opened before capture: yes/no +main window visible: yes/no +operations used: version, self_check, scan_windows, dump_uia +forbidden actions performed: no +this package is N12R live evidence, not current-environment N12 pass +"@ | Set-Content -Path "$captureRoot\metadata\source-notes.txt" -Encoding UTF8 +``` + +Before packaging, edit the `yes/no` fields so they are accurate. + +## 6. Manual login + +1. Open iSphere / IMPlatformClient using the normal internal sandbox method. +2. Log in manually using the normal company process. +3. Complete SSO/MFA if required. +4. Do not use copied credentials or offline evidence files. +5. Keep the main iSphere window visible and not minimized. + +Do not click into contacts or conversations unless normal login itself requires it. Do not send or receive anything. + +## 7. Build helper path variable + +After `verify-win-helper.ps1`, the helper should exist at: + +```text +runs\win-helper\ISphereWinHelper.exe +``` + +Set: + +```powershell +$helper = Resolve-Path -LiteralPath "runs\win-helper\ISphereWinHelper.exe" +``` + +## 8. Capture helper version + +```powershell +@{ + protocol = "isphere.helper.v1" + request_id = "n12r-version" + op = "version" + timeout_ms = 5000 + args = @{} +} | ConvertTo-Json -Depth 8 -Compress | + & $helper --json | + Set-Content -Path "$captureRoot\helper\version.json" -Encoding UTF8 +``` + +## 9. Capture self check + +```powershell +@{ + protocol = "isphere.helper.v1" + request_id = "n12r-self-check" + op = "self_check" + timeout_ms = 5000 + args = @{} +} | ConvertTo-Json -Depth 8 -Compress | + & $helper --json | + Set-Content -Path "$captureRoot\helper\self-check.json" -Encoding UTF8 +``` + +## 10. Scan visible windows + +```powershell +@{ + protocol = "isphere.helper.v1" + request_id = "n12r-scan-windows" + op = "scan_windows" + timeout_ms = 5000 + args = @{ include_all_visible = $true } +} | ConvertTo-Json -Depth 8 -Compress | + & $helper --json | + Set-Content -Path "$captureRoot\windows\scan-windows.json" -Encoding UTF8 +``` + +Open the scan result and identify the likely iSphere / IMPlatformClient window by process name, title, class name, visibility, bounds, and score: + +```powershell +Get-Content -Path "$captureRoot\windows\scan-windows.json" -Encoding UTF8 | ConvertFrom-Json | + Select-Object -ExpandProperty data | + Select-Object -ExpandProperty windows | + Where-Object { $_.process_name -match "iSphere|IMPlatform|IMPP" -or $_.title -match "iSphere|IMPlatform|IMPP" } | + Format-Table hwnd,pid,process_name,title,class_name,visible,score -AutoSize +``` + +## 11. Save selected window metadata + +Replace `<HWND>` with the selected window handle from the scan output, such as `0x001A0B2C`. + +```powershell +$selectedHwnd = "<HWND>" + +$scanObj = Get-Content -Path "$captureRoot\windows\scan-windows.json" -Encoding UTF8 | ConvertFrom-Json +$selected = $scanObj.data.windows | Where-Object { $_.hwnd -eq $selectedHwnd } | Select-Object -First 1 +if (-not $selected) { throw "selected hwnd not found in scan result: $selectedHwnd" } +$selected | ConvertTo-Json -Depth 8 | Set-Content -Path "$captureRoot\windows\selected-window.json" -Encoding UTF8 +``` + +## 12. Capture UIA dump + +Use conservative limits first: + +```powershell +@{ + protocol = "isphere.helper.v1" + request_id = "n12r-dump-uia-main" + op = "dump_uia" + timeout_ms = 15000 + args = @{ + hwnd = $selectedHwnd + max_depth = 8 + include_text = $true + max_children = 100 + } +} | ConvertTo-Json -Depth 12 -Compress | + & $helper --json | + Set-Content -Path "$captureRoot\uia\dump-uia-main.json" -Encoding UTF8 +``` + +If the dump is too large or truncated, repeat once with adjusted limits and save as an additional file, for example: + +```text +uia/dump-uia-main-depth10-children150.json +``` + +Do not click, type, send, upload, download, or modify the iSphere client during capture. + +## 13. Create a redacted UIA copy + +Create a copy for sharing: + +```powershell +Copy-Item -LiteralPath "$captureRoot\uia\dump-uia-main.json" -Destination "$captureRoot\uia\dump-uia-main-redacted.json" -Force +``` + +Then manually review `dump-uia-main-redacted.json` and redact sensitive visible text before sharing broadly. + +Redact: + +```text +person names +phone numbers +emails +message text +contact names +organization-sensitive content +credential/token-like strings +business document titles if sensitive +``` + +Keep structural selector fields when possible: + +```text +control_type +automation_id +class_name +framework_id +bounds +is_enabled +is_offscreen +children shape +``` + +## 14. Write operator notes + +```powershell +@" +What client was opened: <iSphere / IMPlatformClient> +How login was performed: manual normal company process +Any MFA/SSO used: yes/no, no secrets included +Selected window hwnd: $selectedHwnd +Selected window reason: <process/title/class/score reason> +Redactions applied: <brief list> +Any capture problems: <brief list> +Forbidden actions performed: no +"@ | Set-Content -Path "$captureRoot\notes\operator-notes.txt" -Encoding UTF8 +``` + +## 15. Verify package before copying out + +Run: + +```powershell +$required = @( + "$captureRoot\metadata\source-notes.txt", + "$captureRoot\helper\version.json", + "$captureRoot\helper\self-check.json", + "$captureRoot\windows\scan-windows.json", + "$captureRoot\windows\selected-window.json", + "$captureRoot\uia\dump-uia-main.json", + "$captureRoot\uia\dump-uia-main-redacted.json", + "$captureRoot\notes\operator-notes.txt" +) +foreach ($path in $required) { + if (-not (Test-Path -LiteralPath $path)) { throw "missing required file: $path" } +} +"N12R package files present: $captureRoot" | Tee-Object -FilePath "$captureRoot\metadata\verification-summary.txt" +``` + +Check JSON parseability: + +```powershell +Get-Content "$captureRoot\helper\version.json" -Encoding UTF8 | ConvertFrom-Json | Out-Null +Get-Content "$captureRoot\helper\self-check.json" -Encoding UTF8 | ConvertFrom-Json | Out-Null +Get-Content "$captureRoot\windows\scan-windows.json" -Encoding UTF8 | ConvertFrom-Json | Out-Null +Get-Content "$captureRoot\windows\selected-window.json" -Encoding UTF8 | ConvertFrom-Json | Out-Null +Get-Content "$captureRoot\uia\dump-uia-main.json" -Encoding UTF8 | ConvertFrom-Json | Out-Null +Get-Content "$captureRoot\uia\dump-uia-main-redacted.json" -Encoding UTF8 | ConvertFrom-Json | Out-Null +``` + +## 16. Package for transfer + +Compress the capture directory: + +```powershell +Compress-Archive -LiteralPath $captureRoot -DestinationPath "$captureRoot.zip" -Force +``` + +Copy either the directory or zip back to the outer-network repository environment under: + +```text +runs/internal-sandbox-live-capture/<capture-id>/ +``` + +## 17. What to report back + +Tell the coordinating session: + +```text +N12R package is ready. +Path: runs/internal-sandbox-live-capture/<capture-id>/ +Manual iSphere login: yes +Main window visible during capture: yes +Forbidden actions performed: no +``` + +The coordinating session will then validate the returned package. Do not mark original current-environment N12 as passed. diff --git a/docs/n12r-final-handoff-checklist.md b/docs/n12r-final-handoff-checklist.md new file mode 100644 index 0000000..bcecb86 --- /dev/null +++ b/docs/n12r-final-handoff-checklist.md @@ -0,0 +1,219 @@ +# N12R Final Handoff Checklist + +Date: 2026-07-08 + +This is the short handoff entry for the current reality: + +- The current repository environment is an outer-network coordination and analysis environment. +- iSphere / IMPlatformClient is internal-network only. +- Do not treat the current environment as capable of completing live login capture. +- The active live-capture route is `N12R: Internal-sandbox live capture route`. +- The original current-environment live-login gate remains blocked unless the repository environment itself is moved into the internal network and an operator can manually log in there. + +## 1. Read this first + +Use these files in this order: + +```text +docs/handoff-2026-07-06.md +docs/n12r-final-handoff-checklist.md +docs/internal-sandbox-live-capture-plan.md +docs/internal-sandbox-operator-runbook.md +docs/n12r-return-package-validation-checklist.md +``` + +If you only need the immediate next action, follow section 3 and section 4 below. + +## 2. Current node map + +```text +N0-N11 + Completed first-phase Go MCP / WinHelper foundation. + +N12-pre + Offline evidence intake and static analysis. + Evidence input: runs/offline-evidence-intake/zyl + Reports: runs/offline-evidence-intake-reports/zyl + Status: useful supporting analysis, not a substitute for live UIA capture. + +N12-current + Current-environment real logged-in UIA capture. + Status: blocked in this outer-network environment. + +N12R + Internal-sandbox live capture route. + Status: active live-capture route. + +N13 + Selector/UIA design review. + Status: do not start until an N12R package is accepted. +``` + +## 3. What the internal-network sandbox operator must do + +The internal operator must use: + +```text +docs/internal-sandbox-operator-runbook.md +``` + +Required operator flow: + +1. Prepare the repository inside an internal-network test sandbox. +2. Start iSphere / IMPlatformClient normally. +3. Complete manual interactive login using the normal company process. +4. Keep the main iSphere window visible. +5. Run only read-only helper checks: + - `version` + - `self_check` + - `scan_windows` + - `dump_uia` +6. Save the returned package under: + +```text +runs/internal-sandbox-live-capture/<capture-id>/ +``` + +Required returned package files: + +```text +metadata/source-notes.txt +helper/version.json +helper/self-check.json +windows/scan-windows.json +windows/selected-window.json +uia/dump-uia-main.json +uia/dump-uia-main-redacted.json +notes/operator-notes.txt +``` + +Recommended returned package files: + +```text +metadata/os-version.txt +metadata/process-list.txt +metadata/repo-status.txt +metadata/verification-summary.txt +``` + +## 4. What the outer-network receiver must do + +After the internal sandbox returns a package, place it at: + +```text +runs/internal-sandbox-live-capture/<capture-id>/ +``` + +Then run the read-only validator: + +```powershell +$captureRoot = "runs\internal-sandbox-live-capture\<capture-id>" +$reportRoot = "runs\internal-sandbox-live-capture-reports" +New-Item -ItemType Directory -Force -Path $reportRoot | Out-Null + +powershell -NoProfile -ExecutionPolicy Bypass -File .\scripts\validate-n12r-return-package.ps1 ` + -PackageRoot $captureRoot ` + -ReportPath (Join-Path $reportRoot "<capture-id>-validation.md") +``` + +Keep validation reports outside `$captureRoot`. The returned package is evidence input and should remain unchanged. + +Run the validator regression tests before changing the validator: + +```powershell +powershell -NoProfile -ExecutionPolicy Bypass -File .\scripts\test-validate-n12r-return-package.ps1 +``` + +## 5. Acceptance gate for N12R + +Accept an N12R package only if all of these are true: + +1. Required files exist. +2. Required JSON files parse. +3. `helper/version.json` reports `ISphereWinHelper`. +4. `helper/self-check.json` reports `ok=true` and UIA availability. +5. `windows/scan-windows.json` includes an iSphere / IMPlatformClient candidate window. +6. `windows/selected-window.json` records the selected hwnd and title or process metadata. +7. `uia/dump-uia-main.json` contains a parseable UIA root/tree. +8. `uia/dump-uia-main-redacted.json` exists and parses. +9. Operator notes state manual login and no forbidden action. +10. The validator decision is `ACCEPT`. + +If any acceptance item fails, keep N12R open and request a corrected internal-sandbox capture. + +## 6. Hard boundary + +Allowed live operations remain only: + +```text +win_helper_version +win_helper_self_check +win_helper_scan_windows +win_helper_dump_uia +``` + +Do not add or use any tool for: + +```text +automatic login +search contacts +open conversation +send message +send file +receive file +upload file +download file +credential extraction +injection +hooking +memory reading +endpoint-security bypass +``` + +Do not run copied client binaries from an evidence package. + +## 7. Offline evidence status + +Offline evidence already received: + +```text +runs/offline-evidence-intake/zyl +``` + +Generated reports are outside the original evidence package: + +```text +runs/offline-evidence-intake-reports/zyl +``` + +Important offline conclusions: + +- Two IMPlatformClient builds were observed: `4.1.2.6842` and `4.1.2.6839`. +- `MsgLib.db` artifacts are not standard SQLite. +- Static .NET metadata and logs support format/owner hypotheses. +- Offline evidence is useful for context, but it cannot prove live UIA selector behavior. + +## 8. Environment caveats + +Known local caveat: + +```text +go is not installed in the current outer-network environment +``` + +This affects `scripts/verify-go-mcp.ps1` in this environment only. It does not change the N12R route. + +## 9. Next decision after N12R acceptance + +If the returned N12R package is accepted: + +1. Record the validation report path. +2. Review the UIA tree structure. +3. Decide whether selector design is stable enough for N13. +4. Write a separate N13 selector design plan before adding any selector logic. + +If the returned N12R package is rejected: + +1. Keep N12R as active. +2. Send the validation failure list back to the internal-network operator. +3. Request a corrected read-only capture. diff --git a/docs/n12r-return-package-validation-checklist.md b/docs/n12r-return-package-validation-checklist.md new file mode 100644 index 0000000..6be1cf3 --- /dev/null +++ b/docs/n12r-return-package-validation-checklist.md @@ -0,0 +1,223 @@ +# N12R Return Package Validation Checklist + +Use this checklist in the outer-network coordination environment after an internal-network operator returns a live capture package. + +N12R packages are expected under: + +```text +runs/internal-sandbox-live-capture/<capture-id>/ +``` + +This checklist validates package completeness and boundary compliance. It does not execute copied client binaries and does not mark original current-environment N12 as passed. + +## 1. Required files + +A package is structurally complete only if these files exist: + +```text +metadata/source-notes.txt +helper/version.json +helper/self-check.json +windows/scan-windows.json +windows/selected-window.json +uia/dump-uia-main.json +uia/dump-uia-main-redacted.json +notes/operator-notes.txt +``` + +Recommended metadata files: + +```text +metadata/os-version.txt +metadata/process-list.txt +metadata/repo-status.txt +metadata/verification-summary.txt +``` + +## 2. Read-only validation commands + +Set package path: + +```powershell +$captureRoot = "runs\internal-sandbox-live-capture\<capture-id>" +``` + +Preferred one-command validation: + +```powershell +$reportRoot = "runs\internal-sandbox-live-capture-reports" +New-Item -ItemType Directory -Force -Path $reportRoot | Out-Null +powershell -NoProfile -ExecutionPolicy Bypass -File .\scripts\validate-n12r-return-package.ps1 ` + -PackageRoot $captureRoot ` + -ReportPath (Join-Path $reportRoot "<capture-id>-validation.md") +``` + +The report path must stay outside the returned package root so validation does not modify the returned evidence package. + +Check required files: + +```powershell +$required = @( + "metadata\source-notes.txt", + "helper\version.json", + "helper\self-check.json", + "windows\scan-windows.json", + "windows\selected-window.json", + "uia\dump-uia-main.json", + "uia\dump-uia-main-redacted.json", + "notes\operator-notes.txt" +) +foreach ($rel in $required) { + $path = Join-Path $captureRoot $rel + if (-not (Test-Path -LiteralPath $path)) { throw "missing required file: $rel" } +} +``` + +Check JSON parseability: + +```powershell +$jsonFiles = @( + "helper\version.json", + "helper\self-check.json", + "windows\scan-windows.json", + "windows\selected-window.json", + "uia\dump-uia-main.json", + "uia\dump-uia-main-redacted.json" +) +foreach ($rel in $jsonFiles) { + Get-Content -LiteralPath (Join-Path $captureRoot $rel) -Encoding UTF8 | ConvertFrom-Json | Out-Null +} +``` + +## 3. Metadata acceptance checks + +`metadata/source-notes.txt` must state: + +```text +source_environment: internal-network test sandbox +iSphere manually logged in: yes +client opened before capture: yes +main window visible: yes +operations used: version, self_check, scan_windows, dump_uia +forbidden actions performed: no +this package is N12R live evidence, not current-environment N12 pass +``` + +`notes/operator-notes.txt` must state: + +```text +How login was performed: manual normal company process +Forbidden actions performed: no +``` + +If either note file says automatic login, sending, receiving, injection, hook, memory reading, or bypass occurred, reject the package. + +## 4. Helper JSON checks + +`helper/version.json` should parse to an object where: + +```text +ok == true +data.helper_name == ISphereWinHelper +``` + +`helper/self-check.json` should parse to an object where: + +```text +ok == true +data.uia_available is a boolean +``` + +## 5. Window scan checks + +`windows/scan-windows.json` should include at least one candidate where one or more of these fields indicates iSphere / IMPlatformClient / IMPP: + +```text +process_name +title +class_name +score +``` + +`windows/selected-window.json` should be one selected object from `scan-windows.json` and should include: + +```text +hwnd +pid +process_name +title +class_name +visible +bounds +score +``` + +## 6. UIA dump checks + +`uia/dump-uia-main.json` should parse to a helper response where: + +```text +ok == true +data.root exists +``` + +The UIA root tree should include selector-useful fields such as: + +```text +name +control_type +automation_id +class_name +framework_id +bounds +children +``` + +The redacted copy `uia/dump-uia-main-redacted.json` must exist before broad sharing. + +## 7. Boundary rejection checks + +Reject the package for N12R evidence use if any of these are true: + +```text +source notes do not confirm manual login +main window was not visible +scan result has no iSphere / IMPlatformClient candidate +UIA dump is missing or empty +redacted UIA copy is missing +package includes raw passwords, tokens, cookies, private keys, or copied credential material +operator notes indicate automatic login, search, opening conversations, sending, receiving, injection, hook, memory reading, or endpoint-security bypass +package was produced by running copied executables from an offline evidence package +``` + +## 8. Validation result template + +After validation, write a short report under: + +```text +runs/internal-sandbox-live-capture/<capture-id>/metadata/outer-validation-summary.txt +``` + +Template: + +```text +capture_id: <capture-id> +validated_at: <local time> +validated_by: outer-network coordination environment +required_files_present: yes/no +json_parse_ok: yes/no +manual_login_confirmed: yes/no +main_window_visible_confirmed: yes/no +candidate_window_found: yes/no +uia_dump_ok: yes/no +redacted_copy_present: yes/no +forbidden_actions_indicated: yes/no +validation_result: accepted/rejected +notes: <short notes> +``` + +## 9. Next decision after acceptance + +If accepted, N12R evidence can be used for a selector-design review gate. + +Do not implement selectors or action tools directly from this checklist. Selector design requires a separate written plan and explicit approval. diff --git a/docs/offline-evidence-intake-plan.md b/docs/offline-evidence-intake-plan.md index e3f1f12..9bcef28 100644 --- a/docs/offline-evidence-intake-plan.md +++ b/docs/offline-evidence-intake-plan.md @@ -426,6 +426,36 @@ True N12 can start only when all of these are true: If the current environment still cannot log in, only N12-pre offline analysis is allowed. N12 remains blocked and must not be marked passed. +## 8.1 Reality update: internal-sandbox live capture route + +As of 2026-07-08, the repository environment is outside the internal network, while iSphere / IMPlatformClient is internal-network only. Therefore the active live-capture route is not current-environment N12. It is: + +```text +N12R: Internal-sandbox live capture route +``` + +Use these documents for N12R: + +```text +docs/internal-sandbox-live-capture-plan.md +docs/internal-sandbox-operator-runbook.md +docs/n12r-return-package-validation-checklist.md +``` + +N12-pre offline packages continue to support static file inventory and offline analysis. N12R packages support live read-only UIA evidence collected from an internal-network test sandbox after manual login. + +Returned N12R packages should be placed under: + +```text +runs/internal-sandbox-live-capture/<capture-id>/ +``` + +Do not mix N12-pre and N12R semantics: + +- `runs/offline-evidence-intake/<evidence-id>/` is copied-file offline evidence only. +- `runs/internal-sandbox-live-capture/<capture-id>/` is live read-only UIA capture from the internal sandbox. +- Neither route authorizes automatic login, sending, receiving, injection, hook, memory reading, or endpoint-security bypass. + ## 9. Coordinator request checklist for the user Use this final user-facing request. It intentionally avoids asking the user to understand JSON. @@ -461,4 +491,4 @@ notes/copy-notes.txt 简单写明从哪里复制、是否登录后复制 - 这只是 N12-pre 离线证据,不是 N12 通过。 - 不需要生成 manifest/scan/dump JSON;那些是高级可选路径。 - 不要做自动登录、发送消息、发送文件、接收文件、注入、hook、读内存或规避安防。 -``` +``` diff --git a/scripts/test-validate-n12r-return-package.ps1 b/scripts/test-validate-n12r-return-package.ps1 new file mode 100644 index 0000000..eb889a9 --- /dev/null +++ b/scripts/test-validate-n12r-return-package.ps1 @@ -0,0 +1,179 @@ +$ErrorActionPreference = "Stop" +Set-StrictMode -Version Latest + +$repo = (Resolve-Path -LiteralPath (Join-Path $PSScriptRoot "..")).Path +$validator = Join-Path $repo "scripts\validate-n12r-return-package.ps1" +$tempRoot = Join-Path ([System.IO.Path]::GetTempPath()) ("n12r-validator-tests-" + [guid]::NewGuid().ToString("N")) + +function Assert-True([bool]$Condition, [string]$Message) { + if (-not $Condition) { + throw $Message + } +} + +function Assert-Contains([string]$Haystack, [string]$Needle, [string]$Message) { + if ($Haystack -notlike "*$Needle*") { + throw "$Message`nExpected to contain: $Needle`nActual: $Haystack" + } +} + +function Write-Utf8Text([string]$Path, [string]$Content) { + $parent = Split-Path -Parent $Path + if ($parent -and -not (Test-Path -LiteralPath $parent)) { + New-Item -ItemType Directory -Force -Path $parent | Out-Null + } + Set-Content -LiteralPath $Path -Value $Content -Encoding UTF8 +} + +function Write-Json([string]$Path, [object]$Value) { + Write-Utf8Text -Path $Path -Content ($Value | ConvertTo-Json -Depth 32) +} + +function New-GoodPackage([string]$Path) { + New-Item -ItemType Directory -Force -Path $Path | Out-Null + foreach ($dir in @("metadata", "helper", "windows", "uia", "notes")) { + New-Item -ItemType Directory -Force -Path (Join-Path $Path $dir) | Out-Null + } + + Write-Utf8Text (Join-Path $Path "metadata\source-notes.txt") @" +Capture source: internal-network test sandbox. +Login method: human operator completed normal interactive sign-in. +Boundary: read-only UI Automation capture only. +"@ + Write-Utf8Text (Join-Path $Path "metadata\os-version.txt") "Microsoft Windows test sandbox" + Write-Utf8Text (Join-Path $Path "metadata\process-list.txt") "IMPlatformClient 4242" + Write-Utf8Text (Join-Path $Path "metadata\repo-status.txt") "## main...origin/main" + Write-Utf8Text (Join-Path $Path "metadata\verification-summary.txt") "N12R package files present." + + Write-Json (Join-Path $Path "helper\version.json") @{ + ok = $true + data = @{ + helper_name = "ISphereWinHelper" + helper_version = "0.1.0" + } + } + Write-Json (Join-Path $Path "helper\self-check.json") @{ + ok = $true + data = @{ + desktop_available = $true + uia_available = $true + } + } + Write-Json (Join-Path $Path "windows\scan-windows.json") @{ + ok = $true + data = @{ + windows = @( + @{ + hwnd = "0x123456" + title = "iSphere - Test Sandbox" + process_name = "IMPlatformClient" + process_id = 4242 + class_name = "WindowsForms10.Window.8.app.0" + } + ) + } + } + Write-Json (Join-Path $Path "windows\selected-window.json") @{ + hwnd = "0x123456" + title = "iSphere - Test Sandbox" + process_name = "IMPlatformClient" + process_id = 4242 + } + $uiaTree = @{ + ok = $true + data = @{ + root = @{ + name = "iSphere - Test Sandbox" + control_type = "Window" + automation_id = "MainWindow" + children = @( + @{ + name = "Conversation list" + control_type = "List" + children = @() + } + ) + } + } + } + Write-Json (Join-Path $Path "uia\dump-uia-main.json") $uiaTree + Write-Json (Join-Path $Path "uia\dump-uia-main-redacted.json") $uiaTree + Write-Utf8Text (Join-Path $Path "notes\operator-notes.txt") "Read-only capture. No message/file action was performed." +} + +function Invoke-Validator([string]$PackageRoot, [string]$ReportPath) { + $output = & powershell -NoProfile -ExecutionPolicy Bypass -File $validator -PackageRoot $PackageRoot -ReportPath $ReportPath 2>&1 + [pscustomobject]@{ + ExitCode = $LASTEXITCODE + Output = ($output | Out-String) + } +} + +function Run-Test([string]$Name, [scriptblock]$Body) { + Write-Host "## $Name" + & $Body + Write-Host "PASS $Name" +} + +try { + New-Item -ItemType Directory -Force -Path $tempRoot | Out-Null + + Run-Test "accepts a complete read-only N12R package" { + $pkg = Join-Path $tempRoot "good" + $report = Join-Path $tempRoot "good-report.md" + New-GoodPackage $pkg + $result = Invoke-Validator -PackageRoot $pkg -ReportPath $report + Assert-True ($result.ExitCode -eq 0) "Expected validator success, got exit $($result.ExitCode): $($result.Output)" + Assert-Contains $result.Output "Decision: ACCEPT" "Expected accept decision in output." + Assert-True (Test-Path -LiteralPath $report) "Expected markdown report to be written outside package root." + Assert-Contains (Get-Content -LiteralPath $report -Raw -Encoding UTF8) "Decision: ACCEPT" "Expected accept decision in report." + } + + Run-Test "rejects a package missing the redacted UIA dump" { + $pkg = Join-Path $tempRoot "missing-redacted" + $report = Join-Path $tempRoot "missing-redacted-report.md" + New-GoodPackage $pkg + Remove-Item -LiteralPath (Join-Path $pkg "uia\dump-uia-main-redacted.json") -Force + $result = Invoke-Validator -PackageRoot $pkg -ReportPath $report + Assert-True ($result.ExitCode -ne 0) "Expected validator failure for missing redacted UIA dump." + Assert-Contains $result.Output "uia/dump-uia-main-redacted.json" "Expected missing required path in output." + Assert-Contains $result.Output "Decision: REJECT" "Expected reject decision in output." + } + + Run-Test "rejects malformed required JSON" { + $pkg = Join-Path $tempRoot "bad-json" + $report = Join-Path $tempRoot "bad-json-report.md" + New-GoodPackage $pkg + Write-Utf8Text (Join-Path $pkg "helper\version.json") "{not-json" + $result = Invoke-Validator -PackageRoot $pkg -ReportPath $report + Assert-True ($result.ExitCode -ne 0) "Expected validator failure for malformed JSON." + Assert-Contains $result.Output "helper/version.json" "Expected malformed JSON path in output." + Assert-Contains $result.Output "JSON parse failed" "Expected JSON parse failure message." + } + + Run-Test "rejects obvious forbidden action evidence" { + $pkg = Join-Path $tempRoot "forbidden-action" + $report = Join-Path $tempRoot "forbidden-action-report.md" + New-GoodPackage $pkg + Write-Utf8Text (Join-Path $pkg "notes\operator-notes.txt") "operator accidentally ran send_message during capture" + $result = Invoke-Validator -PackageRoot $pkg -ReportPath $report + Assert-True ($result.ExitCode -ne 0) "Expected validator failure for forbidden action evidence." + Assert-Contains $result.Output "send_message" "Expected forbidden action token in output." + Assert-Contains $result.Output "Decision: REJECT" "Expected reject decision in output." + } + + Run-Test "rejects report paths inside the evidence package" { + $pkg = Join-Path $tempRoot "report-inside-package" + New-GoodPackage $pkg + $report = Join-Path $pkg "validation-report.md" + $result = Invoke-Validator -PackageRoot $pkg -ReportPath $report + Assert-True ($result.ExitCode -ne 0) "Expected validator failure when report path is inside package root." + Assert-Contains $result.Output "ReportPath must be outside PackageRoot" "Expected package immutability error." + Assert-True (-not (Test-Path -LiteralPath $report)) "Validator must not write a report inside the package root." + } +} +finally { + if (Test-Path -LiteralPath $tempRoot) { + Remove-Item -LiteralPath $tempRoot -Recurse -Force + } +} diff --git a/scripts/validate-n12r-return-package.ps1 b/scripts/validate-n12r-return-package.ps1 new file mode 100644 index 0000000..f6f72b2 --- /dev/null +++ b/scripts/validate-n12r-return-package.ps1 @@ -0,0 +1,537 @@ +param( + [Parameter(Mandatory = $true)] + [string]$PackageRoot, + + [string]$ReportPath +) + +$ErrorActionPreference = "Stop" +Set-StrictMode -Version Latest + +$results = New-Object System.Collections.Generic.List[object] +$parsedJson = @{} +$rawFiles = @{} + +$requiredFiles = @( + "metadata/source-notes.txt", + "helper/version.json", + "helper/self-check.json", + "windows/scan-windows.json", + "windows/selected-window.json", + "uia/dump-uia-main.json", + "uia/dump-uia-main-redacted.json", + "notes/operator-notes.txt" +) + +$optionalFiles = @( + "metadata/os-version.txt", + "metadata/process-list.txt", + "metadata/repo-status.txt", + "metadata/verification-summary.txt" +) + +$jsonFiles = @( + "helper/version.json", + "helper/self-check.json", + "windows/scan-windows.json", + "windows/selected-window.json", + "uia/dump-uia-main.json", + "uia/dump-uia-main-redacted.json" +) + +$forbiddenTokens = @( + "send_message", + "search_contact", + "upload_file", + "download_file", + "receive_file", + "open_conversation", + "auto_login", + "autologin", + "file_transfer", + "read_memory", + "memory_dump", + "inject", + "hook" +) + +function Add-Result([string]$Status, [string]$Category, [string]$Message) { + $script:results.Add([pscustomobject]@{ + Status = $Status + Category = $Category + Message = $Message + }) | Out-Null +} + +function Add-Pass([string]$Category, [string]$Message) { + Add-Result "PASS" $Category $Message +} + +function Add-Warn([string]$Category, [string]$Message) { + Add-Result "WARN" $Category $Message +} + +function Add-Fail([string]$Category, [string]$Message) { + Add-Result "FAIL" $Category $Message +} + +function Resolve-AbsolutePath([string]$Path) { + if ([System.IO.Path]::IsPathRooted($Path)) { + return [System.IO.Path]::GetFullPath($Path) + } + return [System.IO.Path]::GetFullPath((Join-Path (Get-Location).Path $Path)) +} + +function Ensure-TrailingSeparator([string]$Path) { + $trimmed = $Path.TrimEnd([System.IO.Path]::DirectorySeparatorChar, [System.IO.Path]::AltDirectorySeparatorChar) + return $trimmed + [System.IO.Path]::DirectorySeparatorChar +} + +function Join-PackagePath([string]$Root, [string]$RelativePath) { + return Join-Path $Root ($RelativePath -replace '/', [System.IO.Path]::DirectorySeparatorChar) +} + +function Read-TextFile([string]$Root, [string]$RelativePath) { + $path = Join-PackagePath $Root $RelativePath + if (-not (Test-Path -LiteralPath $path -PathType Leaf)) { + return $null + } + try { + $text = Get-Content -LiteralPath $path -Raw -Encoding UTF8 + $script:rawFiles[$RelativePath] = $text + return $text + } + catch { + Add-Fail "read" "$RelativePath could not be read as UTF-8 text: $($_.Exception.Message)" + return $null + } +} + +function Read-JsonFile([string]$Root, [string]$RelativePath) { + $text = Read-TextFile $Root $RelativePath + if ($null -eq $text) { + return $null + } + + try { + $json = $text | ConvertFrom-Json -ErrorAction Stop + $script:parsedJson[$RelativePath] = $json + Add-Pass "json" "$RelativePath parsed as JSON" + return $json + } + catch { + Add-Fail "json" "$RelativePath JSON parse failed: $($_.Exception.Message)" + return $null + } +} + +function Get-PropertyValue([object]$Object, [string[]]$Names) { + if ($null -eq $Object) { + return $null + } + + foreach ($name in $Names) { + $prop = $Object.PSObject.Properties | Where-Object { $_.Name -ieq $name } | Select-Object -First 1 + if ($null -ne $prop) { + return $prop.Value + } + } + return $null +} + +function Has-Property([object]$Object, [string]$Name) { + if ($null -eq $Object) { + return $false + } + return $null -ne ($Object.PSObject.Properties | Where-Object { $_.Name -ieq $Name } | Select-Object -First 1) +} + +function Test-OkField([object]$Object) { + if ($null -eq $Object) { + return $false + } + if (-not (Has-Property $Object "ok")) { + return $false + } + return [bool](Get-PropertyValue $Object @("ok")) +} + +function Convert-ToArray([object]$Value) { + if ($null -eq $Value) { + return @() + } + if ($Value -is [System.Array]) { + return @($Value) + } + return @($Value) +} + +function Get-NestedValue([object]$Object, [string[]]$Path) { + $current = $Object + foreach ($segment in $Path) { + if ($null -eq $current) { + return $null + } + $current = Get-PropertyValue $current @($segment) + } + return $current +} + +function Get-UiRoot([object]$Json) { + foreach ($path in @( + @("data", "root"), + @("data", "tree"), + @("root"), + @("tree") + )) { + $candidate = Get-NestedValue $Json $path + if ($null -ne $candidate) { + return $candidate + } + } + return $Json +} + +function Count-UiNodes([object]$Node) { + if ($null -eq $Node) { + return 0 + } + + if (($Node -is [System.Collections.IEnumerable]) -and -not ($Node -is [string]) -and -not ($Node -is [pscustomobject])) { + $sum = 0 + foreach ($item in $Node) { + $sum += Count-UiNodes $item + } + return $sum + } + + $count = 1 + $children = Get-PropertyValue $Node @("children", "Children") + foreach ($child in (Convert-ToArray $children)) { + $count += Count-UiNodes $child + } + return $count +} + +function Format-Value([object]$Value) { + if ($null -eq $Value) { + return "" + } + return [string]$Value +} + +function Test-ForbiddenEvidence([string]$RelativePath, [string]$Text) { + if ($null -eq $Text) { + return + } + + foreach ($token in $script:forbiddenTokens) { + if ($Text -match [regex]::Escape($token)) { + Add-Fail "boundary" "$RelativePath contains forbidden action token: $token" + } + } + + $opPattern = '"(op|tool|action|operation)"\s*:\s*"(send|search|upload|download|receive|auto_login|autologin|open_conversation|file_transfer|read_memory|inject|hook)[^"]*"' + if ($Text -match $opPattern) { + Add-Fail "boundary" "$RelativePath contains forbidden operation evidence: $($Matches[0])" + } +} + +function Write-Report([string]$Path, [object[]]$Rows, [string]$Decision, [int]$PassCount, [int]$WarnCount, [int]$FailCount) { + $lines = New-Object System.Collections.Generic.List[string] + $lines.Add("# N12R Return Package Validation Report") | Out-Null + $lines.Add("") | Out-Null + $lines.Add("- Decision: $Decision") | Out-Null + $lines.Add("- PASS: $PassCount") | Out-Null + $lines.Add("- WARN: $WarnCount") | Out-Null + $lines.Add("- FAIL: $FailCount") | Out-Null + $lines.Add("") | Out-Null + $lines.Add("| Status | Category | Message |") | Out-Null + $lines.Add("|---|---|---|") | Out-Null + foreach ($row in $Rows) { + $message = ([string]$row.Message).Replace("|", "\|") + $category = ([string]$row.Category).Replace("|", "\|") + $lines.Add("| $($row.Status) | $category | $message |") | Out-Null + } + $parent = Split-Path -Parent $Path + if ($parent -and -not (Test-Path -LiteralPath $parent)) { + New-Item -ItemType Directory -Force -Path $parent | Out-Null + } + Set-Content -LiteralPath $Path -Value $lines -Encoding UTF8 +} + +$packageFullPath = $null +$reportFullPath = $null +$canWriteReport = $false + +try { + $packageFullPath = Resolve-AbsolutePath $PackageRoot + if (-not (Test-Path -LiteralPath $packageFullPath -PathType Container)) { + Add-Fail "package" "PackageRoot does not exist or is not a directory: $packageFullPath" + } + else { + $packageFullPath = (Resolve-Path -LiteralPath $packageFullPath).Path + Add-Pass "package" "PackageRoot exists: $packageFullPath" + } + + if ($ReportPath) { + $reportFullPath = Resolve-AbsolutePath $ReportPath + if ($null -ne $packageFullPath) { + $packagePrefix = Ensure-TrailingSeparator $packageFullPath + if ($reportFullPath.StartsWith($packagePrefix, [System.StringComparison]::OrdinalIgnoreCase) -or + $reportFullPath.Equals($packageFullPath, [System.StringComparison]::OrdinalIgnoreCase)) { + Add-Fail "package" "ReportPath must be outside PackageRoot to keep the returned evidence package immutable: $reportFullPath" + } + else { + $canWriteReport = $true + Add-Pass "package" "ReportPath is outside PackageRoot: $reportFullPath" + } + } + } + + if ((Test-Path -LiteralPath $packageFullPath -PathType Container)) { + foreach ($relative in $requiredFiles) { + $path = Join-PackagePath $packageFullPath $relative + if (Test-Path -LiteralPath $path -PathType Leaf) { + Add-Pass "required-files" "$relative exists" + } + else { + Add-Fail "required-files" "$relative is missing" + } + } + + foreach ($relative in $optionalFiles) { + $path = Join-PackagePath $packageFullPath $relative + if (Test-Path -LiteralPath $path -PathType Leaf) { + Add-Pass "optional-files" "$relative exists" + } + else { + Add-Warn "optional-files" "$relative is not present" + } + } + + foreach ($relative in $jsonFiles) { + $path = Join-PackagePath $packageFullPath $relative + if (Test-Path -LiteralPath $path -PathType Leaf) { + [void](Read-JsonFile $packageFullPath $relative) + } + } + + foreach ($relative in ($requiredFiles + $optionalFiles)) { + $path = Join-PackagePath $packageFullPath $relative + if (Test-Path -LiteralPath $path -PathType Leaf) { + $text = if ($rawFiles.ContainsKey($relative)) { $rawFiles[$relative] } else { Read-TextFile $packageFullPath $relative } + Test-ForbiddenEvidence $relative $text + } + } + + $sourceNotes = Read-TextFile $packageFullPath "metadata/source-notes.txt" + if ($null -ne $sourceNotes) { + if ($sourceNotes -match '(?i)(internal|intranet|内网)') { + Add-Pass "metadata" "metadata/source-notes.txt states internal/intranet context" + } + else { + Add-Fail "metadata" "metadata/source-notes.txt must state this came from an internal-network test sandbox" + } + + if ($sourceNotes -match '(?i)(sandbox|沙盒)') { + Add-Pass "metadata" "metadata/source-notes.txt states sandbox context" + } + else { + Add-Fail "metadata" "metadata/source-notes.txt must state sandbox context" + } + + if ($sourceNotes -match '(?i)(human|manual|operator|interactive|人工|手工)') { + Add-Pass "metadata" "metadata/source-notes.txt states human/manual operation context" + } + else { + Add-Warn "metadata" "metadata/source-notes.txt does not clearly state human/manual operation context" + } + } + + $version = if ($parsedJson.ContainsKey("helper/version.json")) { $parsedJson["helper/version.json"] } else { $null } + if ($null -ne $version) { + if (Test-OkField $version) { + Add-Pass "helper" "helper/version.json ok=true" + } + else { + Add-Fail "helper" "helper/version.json must have ok=true" + } + + $versionData = Get-PropertyValue $version @("data") + $helperName = Get-PropertyValue $versionData @("helper_name", "helperName", "name") + if ($helperName -eq "ISphereWinHelper") { + Add-Pass "helper" "helper/version.json reports ISphereWinHelper" + } + else { + Add-Fail "helper" "helper/version.json must report helper_name=ISphereWinHelper" + } + } + + $selfCheck = if ($parsedJson.ContainsKey("helper/self-check.json")) { $parsedJson["helper/self-check.json"] } else { $null } + if ($null -ne $selfCheck) { + if (Test-OkField $selfCheck) { + Add-Pass "helper" "helper/self-check.json ok=true" + } + else { + Add-Fail "helper" "helper/self-check.json must have ok=true" + } + + $selfData = Get-PropertyValue $selfCheck @("data") + $uiaAvailable = Get-PropertyValue $selfData @("uia_available", "uiaAvailable", "uia") + if ([bool]$uiaAvailable) { + Add-Pass "helper" "helper/self-check.json indicates UIA availability" + } + else { + Add-Fail "helper" "helper/self-check.json must indicate UIA availability" + } + } + + $scan = if ($parsedJson.ContainsKey("windows/scan-windows.json")) { $parsedJson["windows/scan-windows.json"] } else { $null } + $scanWindows = @() + if ($null -ne $scan) { + if (Test-OkField $scan) { + Add-Pass "windows" "windows/scan-windows.json ok=true" + } + else { + Add-Fail "windows" "windows/scan-windows.json must have ok=true" + } + + $scanWindows = @(Convert-ToArray (Get-NestedValue $scan @("data", "windows"))) + if ($scanWindows.Count -eq 0) { + $scanWindows = @(Convert-ToArray (Get-PropertyValue $scan @("windows"))) + } + + if ($scanWindows.Count -gt 0) { + Add-Pass "windows" "windows/scan-windows.json contains $($scanWindows.Count) window row(s)" + } + else { + Add-Fail "windows" "windows/scan-windows.json must contain data.windows" + } + + $candidateCount = 0 + foreach ($window in $scanWindows) { + $title = Format-Value (Get-PropertyValue $window @("title", "name")) + $process = Format-Value (Get-PropertyValue $window @("process_name", "processName", "process", "exe")) + $className = Format-Value (Get-PropertyValue $window @("class_name", "className")) + $haystack = "$title $process $className" + if ($haystack -match '(?i)(isphere|IMPlatformClient|IMPlatform)') { + $candidateCount += 1 + } + } + + if ($candidateCount -gt 0) { + Add-Pass "windows" "windows/scan-windows.json includes $candidateCount iSphere/IMPlatform candidate window(s)" + } + else { + Add-Fail "windows" "windows/scan-windows.json must include an iSphere/IMPlatform candidate window" + } + } + + $selected = if ($parsedJson.ContainsKey("windows/selected-window.json")) { $parsedJson["windows/selected-window.json"] } else { $null } + if ($null -ne $selected) { + $selectedHwnd = Format-Value (Get-PropertyValue $selected @("hwnd", "handle")) + $selectedTitle = Format-Value (Get-PropertyValue $selected @("title", "name")) + $selectedProcess = Format-Value (Get-PropertyValue $selected @("process_name", "processName", "process", "exe")) + if ($selectedHwnd) { + Add-Pass "windows" "windows/selected-window.json records hwnd=$selectedHwnd" + } + else { + Add-Fail "windows" "windows/selected-window.json must record hwnd" + } + if ($selectedTitle -or $selectedProcess) { + Add-Pass "windows" "windows/selected-window.json records title/process metadata" + } + else { + Add-Fail "windows" "windows/selected-window.json must record title or process metadata" + } + + $matchFound = $false + foreach ($window in $scanWindows) { + $windowHwnd = Format-Value (Get-PropertyValue $window @("hwnd", "handle")) + if ($selectedHwnd -and $windowHwnd -and $selectedHwnd -ieq $windowHwnd) { + $matchFound = $true + break + } + } + if ($scanWindows.Count -gt 0) { + if ($matchFound) { + Add-Pass "windows" "selected hwnd appears in scan-windows" + } + else { + Add-Warn "windows" "selected hwnd was not found in scan-windows" + } + } + } + + $uiaMain = if ($parsedJson.ContainsKey("uia/dump-uia-main.json")) { $parsedJson["uia/dump-uia-main.json"] } else { $null } + if ($null -ne $uiaMain) { + if ((Has-Property $uiaMain "ok") -and -not (Test-OkField $uiaMain)) { + Add-Fail "uia" "uia/dump-uia-main.json has ok=false" + } + else { + Add-Pass "uia" "uia/dump-uia-main.json is a successful or root-level UIA response" + } + + $root = Get-UiRoot $uiaMain + $nodeCount = Count-UiNodes $root + if ($nodeCount -gt 0) { + Add-Pass "uia" "uia/dump-uia-main.json contains UIA root/tree with $nodeCount node(s)" + } + else { + Add-Fail "uia" "uia/dump-uia-main.json must contain a UIA root/tree" + } + } + + $uiaRedacted = if ($parsedJson.ContainsKey("uia/dump-uia-main-redacted.json")) { $parsedJson["uia/dump-uia-main-redacted.json"] } else { $null } + if ($null -ne $uiaRedacted) { + $root = Get-UiRoot $uiaRedacted + $nodeCount = Count-UiNodes $root + if ($nodeCount -gt 0) { + Add-Pass "uia" "uia/dump-uia-main-redacted.json contains parseable UIA root/tree with $nodeCount node(s)" + } + else { + Add-Fail "uia" "uia/dump-uia-main-redacted.json must contain a UIA root/tree" + } + + if ($rawFiles.ContainsKey("uia/dump-uia-main.json") -and $rawFiles.ContainsKey("uia/dump-uia-main-redacted.json")) { + if ($rawFiles["uia/dump-uia-main.json"] -eq $rawFiles["uia/dump-uia-main-redacted.json"]) { + Add-Warn "uia" "redacted UIA dump is byte-identical to main dump; manually confirm no sensitive text is present" + } + else { + Add-Pass "uia" "redacted UIA dump differs from main dump" + } + } + } + } +} +catch { + Add-Fail "validator" "Unexpected validator error: $($_.Exception.Message)" +} + +$passCount = @($results | Where-Object { $_.Status -eq "PASS" }).Count +$warnCount = @($results | Where-Object { $_.Status -eq "WARN" }).Count +$failCount = @($results | Where-Object { $_.Status -eq "FAIL" }).Count +$decision = if ($failCount -eq 0) { "ACCEPT" } else { "REJECT" } + +foreach ($row in $results) { + Write-Host ("[{0}] {1}: {2}" -f $row.Status, $row.Category, $row.Message) +} +Write-Host ("Summary: PASS={0} WARN={1} FAIL={2}" -f $passCount, $warnCount, $failCount) +Write-Host "Decision: $decision" + +if ($ReportPath -and $canWriteReport -and $null -ne $reportFullPath) { + try { + Write-Report -Path $reportFullPath -Rows ([object[]]$results.ToArray()) -Decision $decision -PassCount $passCount -WarnCount $warnCount -FailCount $failCount + Write-Host "Report: $reportFullPath" + } + catch { + Write-Host "[FAIL] report: could not write report: $($_.Exception.Message)" + exit 1 + } +} + +if ($failCount -gt 0) { + exit 1 +} +exit 0