docs: add N12R internal sandbox handoff workflow
This commit is contained in:
@@ -26,9 +26,15 @@ N0 Baseline cleanup and direction lock
|
|||||||
-> N10 Operator runbook and process scripts
|
-> N10 Operator runbook and process scripts
|
||||||
-> N11 Commit/review checkpoint
|
-> N11 Commit/review checkpoint
|
||||||
-> N12-pre Offline evidence intake
|
-> 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.**
|
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.
|
- Unrelated generated files are staged.
|
||||||
- Worktree contains ignored build outputs outside `runs/`.
|
- 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.
|
- 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.
|
- 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:**
|
**Preconditions:**
|
||||||
|
|
||||||
- N11 accepted.
|
- N11 accepted.
|
||||||
- Optional N12-pre offline evidence, if used, has been validated only as pre-gate context and not as N12 pass evidence.
|
- Optional N12-pre offline evidence, if used, has been validated only as pre-gate context and not as live capture evidence.
|
||||||
- User manually opens and logs in to iSphere.
|
- An internal-network test sandbox can manually open and log in to iSphere / IMPlatformClient using the normal approved process.
|
||||||
- iSphere main window is visible.
|
- 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:**
|
**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/<capture-id>/
|
||||||
|
```
|
||||||
|
|
||||||
|
- 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
|
```text
|
||||||
win_helper_scan_windows
|
win_helper_scan_windows
|
||||||
@@ -860,35 +943,9 @@ Save output under:
|
|||||||
runs/real-loggedin-lab/uia-dumps/
|
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.
|
**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.
|
||||||
- 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.
|
|
||||||
|
|
||||||
---
|
---
|
||||||
|
|
||||||
@@ -897,7 +954,7 @@ Pass if:
|
|||||||
Every node must satisfy these rules:
|
Every node must satisfy these rules:
|
||||||
|
|
||||||
1. No Python MCP code.
|
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.
|
3. No process injection, hook, credential extraction, token extraction, or memory reading.
|
||||||
4. No broad `git add -A`; stage exact paths.
|
4. No broad `git add -A`; stage exact paths.
|
||||||
5. Every code node uses tests first.
|
5. Every code node uses tests first.
|
||||||
@@ -911,13 +968,13 @@ Every node must satisfy these rules:
|
|||||||
Current guidance after N0-N11 completion is:
|
Current guidance after N0-N11 completion is:
|
||||||
|
|
||||||
```text
|
```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:
|
Immediate next action:
|
||||||
|
|
||||||
```text
|
```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.
|
||||||
|
|||||||
@@ -211,15 +211,38 @@ Procedure:
|
|||||||
|
|
||||||
During this procedure, the operator must not click, type, send, upload, download, modify client state, or capture passwords/tokens.
|
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\<evidence-id>\`.
|
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\<evidence-id>\`.
|
||||||
|
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\<capture-id>\`.
|
||||||
|
|
||||||
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\<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 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
|
## 11. Troubleshooting
|
||||||
|
|
||||||
|
|||||||
@@ -379,3 +379,68 @@ Only after that should the team decide whether to:
|
|||||||
3. Wait until current-environment live login becomes possible.
|
3. Wait until current-environment live login becomes possible.
|
||||||
4. Re-plan true N12.
|
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/<capture-id>/
|
||||||
|
```
|
||||||
|
|
||||||
|
Suggested outer-network validation command after a package returns:
|
||||||
|
|
||||||
|
```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")
|
||||||
|
```
|
||||||
|
|
||||||
|
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.
|
||||||
|
|||||||
217
docs/internal-sandbox-live-capture-plan.md
Normal file
217
docs/internal-sandbox-live-capture-plan.md
Normal file
@@ -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/<capture-id>/
|
||||||
|
```
|
||||||
|
|
||||||
|
Recommended package shape:
|
||||||
|
|
||||||
|
```text
|
||||||
|
runs/internal-sandbox-live-capture/<capture-id>/
|
||||||
|
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: <capture-id>
|
||||||
|
source_environment: internal-network test sandbox
|
||||||
|
operator: <name or role>
|
||||||
|
capture_time_local: <YYYY-MM-DD HH:mm:ss timezone>
|
||||||
|
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: <iSphere / IMPlatformClient>
|
||||||
|
How login was performed: manual normal company process
|
||||||
|
Any MFA/SSO used: yes/no, no secrets included
|
||||||
|
Selected window reason: <title/process/class/score>
|
||||||
|
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.
|
||||||
387
docs/internal-sandbox-operator-runbook.md
Normal file
387
docs/internal-sandbox-operator-runbook.md
Normal file
@@ -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.
|
||||||
219
docs/n12r-final-handoff-checklist.md
Normal file
219
docs/n12r-final-handoff-checklist.md
Normal file
@@ -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.
|
||||||
223
docs/n12r-return-package-validation-checklist.md
Normal file
223
docs/n12r-return-package-validation-checklist.md
Normal file
@@ -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.
|
||||||
@@ -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.
|
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
|
## 9. Coordinator request checklist for the user
|
||||||
|
|
||||||
Use this final user-facing request. It intentionally avoids asking the user to understand JSON.
|
Use this final user-facing request. It intentionally avoids asking the user to understand JSON.
|
||||||
|
|||||||
179
scripts/test-validate-n12r-return-package.ps1
Normal file
179
scripts/test-validate-n12r-return-package.ps1
Normal file
@@ -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
|
||||||
|
}
|
||||||
|
}
|
||||||
537
scripts/validate-n12r-return-package.ps1
Normal file
537
scripts/validate-n12r-return-package.ps1
Normal file
@@ -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
|
||||||
Reference in New Issue
Block a user