opencode-secrets-obfuscation 0.1.0 → 0.1.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (104) hide show
  1. package/LICENSE +21 -21
  2. package/README.md +1 -31
  3. package/package.json +29 -23
  4. package/plugin.mjs +3841 -0
  5. package/.editorconfig +0 -12
  6. package/.github/workflows/ci.yml +0 -75
  7. package/.github/workflows/release.yml +0 -50
  8. package/.node-version +0 -1
  9. package/AGENTS.md +0 -23
  10. package/INSTALL.md +0 -104
  11. package/docs/checkpoint.md +0 -68
  12. package/docs/history/1-spec-review.md +0 -233
  13. package/docs/history/10-p0-launch-prerequisites-review.md +0 -186
  14. package/docs/history/11-p0-job-containment-review.md +0 -188
  15. package/docs/history/12-p0-api-client.md +0 -15
  16. package/docs/history/13-p0-status-review.md +0 -122
  17. package/docs/history/14-p0-containment-final-review.md +0 -84
  18. package/docs/history/15-config-source-implementation.md +0 -27
  19. package/docs/history/16-sanitizer-implementation.md +0 -65
  20. package/docs/history/17-store-policy-implementation.md +0 -36
  21. package/docs/history/18-pure-mvp-review.md +0 -182
  22. package/docs/history/19-pure-mvp-re-review.md +0 -142
  23. package/docs/history/2-p0-runtime-prerequisites.md +0 -163
  24. package/docs/history/20-pure-mvp-final-review.md +0 -116
  25. package/docs/history/21-pure-mvp-review-final.md +0 -109
  26. package/docs/history/22-pure-mvp-signoff-review.md +0 -113
  27. package/docs/history/23-pure-mvp-signoff-review.md +0 -75
  28. package/docs/history/24-owned-tools-implementation.md +0 -27
  29. package/docs/history/25-v2-plugin-integration.md +0 -53
  30. package/docs/history/26-owned-tools-review.md +0 -138
  31. package/docs/history/27-owned-tools-final-review.md +0 -93
  32. package/docs/history/28-owned-tools-signoff.md +0 -98
  33. package/docs/history/3-p0-harness-foundation.md +0 -105
  34. package/docs/history/4-p0-source-opening.md +0 -78
  35. package/docs/history/5-qualification-gate-review.md +0 -133
  36. package/docs/history/6-p0-infrastructure-review.md +0 -275
  37. package/docs/history/7-p0-job-containment.md +0 -29
  38. package/docs/history/8-p0-evidence-support.md +0 -52
  39. package/docs/history/9-p0-launch-plan.md +0 -88
  40. package/docs/spec/design.md +0 -111
  41. package/docs/spec/intent.md +0 -81
  42. package/docs/spec/plan.md +0 -78
  43. package/docs/testing.md +0 -157
  44. package/scripts/build-plugin.mjs +0 -84
  45. package/scripts/install-opencode-ci.ps1 +0 -39
  46. package/scripts/install-opencode-ci.sh +0 -27
  47. package/scripts/smoke.mjs +0 -105
  48. package/scripts/verify-package.mjs +0 -18
  49. package/src/README.md +0 -22
  50. package/src/budgets/index.ts +0 -49
  51. package/src/config/README.md +0 -4
  52. package/src/config/options.ts +0 -76
  53. package/src/diagnostics/README.md +0 -4
  54. package/src/opencode/v2/filter.ts +0 -350
  55. package/src/opencode/v2/index.ts +0 -86
  56. package/src/opencode/v2/runtime.ts +0 -207
  57. package/src/opencode/v2/transports/README.md +0 -5
  58. package/src/runtime/http-transport.ts +0 -20
  59. package/src/sanitize/README.md +0 -4
  60. package/src/sanitize/index.ts +0 -454
  61. package/src/sources/README.md +0 -5
  62. package/src/sources/dotenv.ts +0 -133
  63. package/src/sources/native/README.md +0 -5
  64. package/src/store/index.ts +0 -64
  65. package/tests/README.md +0 -26
  66. package/tests/contract/plugin-v2.test.ts +0 -29
  67. package/tests/fixtures/README.md +0 -9
  68. package/tests/fixtures/job-child.mjs +0 -18
  69. package/tests/fixtures/plugins/README.md +0 -8
  70. package/tests/fixtures/process-child.mjs +0 -20
  71. package/tests/fixtures/services/README.md +0 -10
  72. package/tests/integration/components.test.ts +0 -5
  73. package/tests/integration/transparent-runtime.test.ts +0 -97
  74. package/tests/integration/windows-job.test.ts +0 -71
  75. package/tests/p0-host/README.md +0 -24
  76. package/tests/p0-host/linux-job.sh +0 -61
  77. package/tests/p0-host/windows-job.ps1 +0 -51
  78. package/tests/p0-native/README.md +0 -33
  79. package/tests/p0-native/windows-handle-loader.tests.ps1 +0 -191
  80. package/tests/scaffold/repository.test.ts +0 -105
  81. package/tests/smoke/README.md +0 -37
  82. package/tests/smoke/cases.json +0 -17
  83. package/tests/smoke/qualification.json +0 -20
  84. package/tests/smoke/runtime-candidate.json +0 -111
  85. package/tests/support/README.md +0 -32
  86. package/tests/support/child-process.ts +0 -235
  87. package/tests/support/host-api.ts +0 -123
  88. package/tests/support/opencode-environment.ts +0 -239
  89. package/tests/support/qualification.ts +0 -154
  90. package/tests/support/recording-endpoint.ts +0 -242
  91. package/tests/support/run-directory.ts +0 -179
  92. package/tests/support/smoke-cases.ts +0 -289
  93. package/tests/unit/budgets.test.ts +0 -35
  94. package/tests/unit/config.test.ts +0 -40
  95. package/tests/unit/dotenv.test.ts +0 -72
  96. package/tests/unit/filter-size.test.ts +0 -132
  97. package/tests/unit/harness.test.ts +0 -228
  98. package/tests/unit/host-api.test.ts +0 -122
  99. package/tests/unit/opencode-environment.test.ts +0 -161
  100. package/tests/unit/qualification.test.ts +0 -128
  101. package/tests/unit/recording-endpoint.test.ts +0 -188
  102. package/tests/unit/sanitize.test.ts +0 -159
  103. package/tests/unit/smoke-cases.test.ts +0 -20
  104. package/tsconfig.json +0 -20
@@ -1,275 +0,0 @@
1
- # P0 infrastructure adversarial review
2
-
3
- Date: 2026-10-13
4
- Scope: `tests/support/run-directory.ts`, `child-process.ts`,
5
- `recording-endpoint.ts`; their unit tests; `tests/p0-native/*`; and the P0
6
- history documents. `qualification.ts` and runtime-pin work were intentionally
7
- excluded.
8
-
9
- ## Verdict
10
-
11
- The support code is useful synthetic fixture infrastructure, but it is not yet
12
- a safe real-host harness. In particular, child-process teardown is not awaited,
13
- pre-cancellation still reaches `spawn`, Windows executable/source trust is not
14
- established, and cleanup is vulnerable to races. The native Windows fixture is
15
- correctly labelled partial in the history document, but one of its reparse
16
- tests does not exercise the advertised protection. No P0 native or real-host
17
- qualification claim is supported.
18
-
19
- ## Must fix
20
-
21
- ### 1. Child failures settle before the child exits (critical)
22
-
23
- `tests/support/child-process.ts:96-105` sets `settled = true`, calls
24
- `child.kill()`, and rejects immediately. The `close` handler at lines 132-138
25
- is consequently ignored. This applies to timeout, cancellation, startup
26
- timeout, output limit, and child error paths. The helper therefore does not
27
- provide the claimed “owned child has terminated” boundary; it also does not
28
- wait for stdout/stderr streams to close before discarding the private result.
29
-
30
- Reproduction: `node --test tests/unit/harness.test.ts` on the review Windows
31
- host failed the output-limit case during cleanup with:
32
- `EBUSY ... rmdir ...run-...`. The test had already received `OUTPUT_LIMIT`, but
33
- the direct Node child still held the run directory. This is a deterministic
34
- false pass opportunity: callers can proceed, remove the run directory, or
35
- launch another fixture while the failed child is still running. Fix by making
36
- failure enter a terminating state, perform bounded escalation, and reject only
37
- after the direct child's `close`/stream teardown (with a bounded cleanup
38
- failure code if it cannot be proven dead).
39
-
40
- ### 2. “Pre-cancelled does not spawn” is not tested and is false
41
-
42
- `child-process.ts:66-75` calls `spawn` before `child-process.ts:106-110`
43
- checks `signal.aborted`. The test named
44
- `pre-cancelled child execution does not spawn` only checks the returned error
45
- (`tests/unit/harness.test.ts:89-107`); it has no spawn oracle. A pre-aborted
46
- signal therefore starts the executable and then kills it. A cancellation race
47
- after validation has the same issue. Check the signal before `spawn`, and test
48
- with a fixture that leaves an observable synthetic marker (or injected spawn
49
- adapter), not merely the error code.
50
-
51
- ### 3. Direct-child kill is not a process-tree boundary
52
-
53
- The documented limitation in `docs/history/3-p0-harness-foundation.md:24-29`
54
- is honest, but it blocks the real-service P0 harness. `child-process.ts:102-104`
55
- only calls `child.kill()` on the returned PID. Descendants can survive,
56
- retain handles to the private directory, continue network activity, or outlive
57
- cleanup. There is no escalation if the direct child ignores the first signal,
58
- and no Windows Job Object / POSIX process-group ownership proof. The P0 exit
59
- gate requires owned-process cleanup; this helper cannot satisfy it until a
60
- platform-qualified tree boundary is added and tested, including descendants,
61
- timeouts, cancellation, and forced escalation.
62
-
63
- ### 4. Executable trust is only an absolute-path check
64
-
65
- `child-process.ts:46-55` accepts any absolute executable. It does not resolve
66
- the final object, reject symlinks/reparse points, verify ownership/ACLs,
67
- validate the expected file identity/hash, or ensure the executable remains the
68
- same object through launch. An absolute path to a writable or replaced binary
69
- is not a trusted executable resolution. This is especially material because
70
- the child environment is otherwise deliberately minimal. The harness needs an
71
- explicit, pinned artifact identity and platform-appropriate opened-object or
72
- equivalent launch verification; otherwise a test can pass while running an
73
- unexpected executable.
74
-
75
- ### 5. Run-directory privacy and cleanup have TOCTOU gaps
76
-
77
- On Windows, `run-directory.ts:48-64` removes inheritance and grants the
78
- current SID, but does not verify the complete ACL or remove pre-existing
79
- explicit broad/unknown ACEs. The check only requires one `(F)` line and no
80
- `(I)` line (`:59-60`). Thus an existing root with explicit broad access can be
81
- reported private. Children created later are not independently checked, and
82
- the unit suite does not inspect child ACLs (`harness.test.ts:10-22`).
83
-
84
- `run-directory.ts:124-127` validates the marker and then recursively walks by
85
- path. Between `assertOwned` and each `lstat/readdir/rmdir` (`:75-89`), an
86
- attacker or concurrent process can replace a directory with a junction,
87
- symlink, or mount/reparse object, or replace the marker/tree. `lstat` avoids
88
- following a link at the instant it runs but is not a race-free deletion
89
- boundary. Cleanup must use an owned directory handle / no-follow traversal (or
90
- an equivalent OS primitive), revalidate identity, and refuse unsafe races.
91
-
92
- The root itself is created or reused at `:91-95` and is not marker-owned;
93
- `mkdir(..., recursive: true)` plus chmod can operate on a caller-supplied
94
- existing path. The ownership contract is only for the run child, so this must
95
- be explicit in the API and documentation rather than treated as private-root
96
- proof.
97
-
98
- ### 6. Native Windows reparse test disables its own protection
99
-
100
- In `tests/p0-native/windows-handle-loader.tests.ps1:30-35`, `OpenRelative`
101
- builds `options` with `0x00200000` (`FILE_OPEN_REPARSE_POINT`) and then passes
102
- `options & ~0x00200000` to `NtCreateFile`. `OBJECT_ATTRIBUTES.Attributes`
103
- contains only `OBJ_CASE_INSENSITIVE` at line 32; `OBJ_DONT_REPARSE` is declared
104
- but never used. Consequently the fixture does not prove that component opens
105
- reject reparse points. The history claim at
106
- `docs/history/4-p0-source-opening.md:23-27` overstates this specific evidence,
107
- even though the overall document correctly remains partial. Correct the native
108
- flags and add a setup-independent result that distinguishes unavailable link
109
- creation from a genuine rejection test.
110
-
111
- ## Additional findings / false-pass risks
112
-
113
- - `child-process.ts:46-55` assumes typed options at runtime. `null`, missing
114
- `args`/`allowedEnv`, non-string environment values, or non-array values can
115
- throw `TypeError` or reach `spawn` instead of returning `INVALID_OPTIONS`.
116
- Malformed-option tests are absent. Also, `Buffer.concat` at `:123`/`:130`
117
- repeatedly copies all prior data; the byte check bounds retained data but not
118
- transient allocation or chunk size.
119
- - The output-limit unit test (`harness.test.ts:52-69`) proves only an error
120
- code. It does not prove process exit, descendant cleanup, no post-failure
121
- output allocation, or that cleanup can safely run. Its current `EBUSY` is
122
- evidence that the test itself exposes the lifecycle defect rather than a
123
- qualification pass.
124
- - `recording-endpoint.ts:61-105` bounds request capture and clears pending
125
- buffers on close, and the focused recording tests passed. However, the close
126
- test (`recording-endpoint.test.ts:88-99`) asserts only zero public captures;
127
- it does not independently prove that a request handler is no longer pending,
128
- that all sockets are closed before `close()` resolves, or that a late
129
- `data/end/close` event cannot repopulate capture state. Add a post-close
130
- event/barrier assertion and a bounded close failure path. Invalid runtime
131
- shapes such as `options = null`, `responses = null`, or non-string bodies
132
- also produce incidental TypeErrors rather than the fixed diagnostic.
133
- - `recording-endpoint.ts:109` increments `rejected` for `clientError`, while
134
- `attempted` is incremented only in the request handler (`:52`). Counts can
135
- therefore describe different populations during malformed-client tests;
136
- define the counter contract before using it as a zero-egress oracle.
137
- - The Windows native fixture uses `Remove-Item -Recurse -Force` in its finalizer
138
- (`windows-handle-loader.tests.ps1:100`). This is acceptable for its exact
139
- generated synthetic path only after the fixture has proved no handles remain,
140
- but it is not a substitute for the production owned-tree cleanup boundary.
141
-
142
- ## Tests and gate status
143
-
144
- - `node --test tests/unit/harness.test.ts tests/unit/recording-endpoint.test.ts`:
145
- 13 passed, 1 failed. The failed output-limit test ended in `EBUSY` during
146
- run-directory cleanup, as described above.
147
- - `npm.cmd run check`: typecheck passed; the full test command had the two
148
- known `qualification.ts` failures (malformed external evidence and accessor
149
- handling), which are outside this review scope. The remaining suites include
150
- 26 TODO tests; TODO/scaffold passes are not security evidence.
151
- - The Windows native script was not rerun as part of this review. The prior
152
- recorded result is deliberately partial with reparse and hard-link setup
153
- gaps, and the flag defect above makes its reparse result non-qualifying.
154
- - No OpenCode host, user config, credential, shared service, or live endpoint
155
- was accessed. Focused tests used only synthetic values and loopback/local
156
- fixtures.
157
-
158
- ## Remaining P0 blockers
159
-
160
- 1. Pin and verify the runtime/artifacts (separate review work), then build a
161
- real isolated host harness with private config/data/home and a qualified
162
- process-tree boundary.
163
- 2. Make cancellation, timeout, output overflow, and startup failure settle
164
- only after owned process and stream teardown; test forced escalation and
165
- descendants on each target OS.
166
- 3. Establish trusted executable identity and race-safe private run-directory
167
- creation/cleanup, including effective child ACL inheritance.
168
- 4. Repair and rerun the Windows native spike, add the required Linux/ext4
169
- spike, and test ACLs, ancestor replacement, swaps, links, filesystem
170
- identity, and post-read identity/size consistency.
171
- 5. Implement actual P0-BOOT/P0-HOOKS/P0-ABORT/quotas evidence. The inventory in
172
- `tests/smoke/cases.json` is planned only; no real-host case is executable.
173
- 6. Keep the release unqualified. A partial native fixture, unit harness pass,
174
- or expected-gap/TODO result must not be converted into platform or security
175
- qualification.
176
-
177
- ## Re-review: P0-B/P0-C primitive repairs
178
-
179
- Date: 2026-10-13
180
- Scope: repaired run-directory/child-process helpers, their 8-test harness,
181
- the synthetic child fixture, and `tests/p0-native/*`. The host Job Object and
182
- environment-builder work was excluded, as were qualification and recording
183
- helpers.
184
-
185
- ### Verification result
186
-
187
- - `node --test tests/unit/harness.test.ts`: **8 passed, 0 skipped**.
188
- - `npm.cmd exec -- tsc --noEmit --pretty false`: passed.
189
- - `pwsh -NoProfile -File tests/p0-native/windows-handle-loader.tests.ps1`:
190
- returned the intentional partial result
191
- `P0-C-WINDOWS: PARTIAL (NTFS/relative/identity/read/post-read/hardlink/junction-rejection=True; gaps=reparse-create-1314,junction-native-create-4392)`.
192
- The nonzero exit is appropriate for the setup gaps. No user configuration,
193
- credential, shared service, or non-generated path was accessed.
194
-
195
- ### Prior findings verified repaired
196
-
197
- 1. **Failure teardown:** `child-process.ts:166-181,220-230` now enters a
198
- terminating state, stops accepting output, escalates after a grace period,
199
- and rejects only after child/stdout/stderr close. The former Windows `EBUSY`
200
- cleanup failure did not recur.
201
- 2. **Pre-abort:** `child-process.ts:90-95,184-191` checks cancellation before
202
- hashing/spawn. `harness.test.ts:102-114` uses the fixture marker and proves
203
- no launch occurred.
204
- 3. **Executable identity and malformed options:** runtime validation and a
205
- caller-supplied SHA-256 are enforced at `child-process.ts:53-95`; the
206
- mismatch and malformed-input cases are exercised at
207
- `harness.test.ts:116-126`.
208
- 4. **Output bounds:** output is counted before retention at
209
- `child-process.ts:202-218`; the test now proves teardown completes before
210
- cleanup (`harness.test.ts:80-89`). This bounds retained synthetic output,
211
- not the stream chunk allocated by Node before the handler sees it.
212
- 5. **Run-root safety:** existing roots are verified but not chmod/ACL-modified
213
- (`run-directory.ts:118-128`). POSIX ancestor ownership/write checks and
214
- Windows current-SID ACL inspection are present. Windows new-directory ACL
215
- setup removes prior rules and adds the current SID with container/object
216
- inheritance (`run-directory.ts:83-99`). Child verification is exposed and
217
- tested (`harness.test.ts:27-36`).
218
- 6. **Cleanup scope:** cleanup now refuses anything other than the exact
219
- marker-only directory (`run-directory.ts:110-116`), including nested links;
220
- it does not recursively delete arbitrary content. Marker replacement and
221
- outside-file preservation are tested (`harness.test.ts:27-49,52-61`).
222
- 7. **Native no-reparse/open checks:** `OpenRelative` now passes both
223
- `OBJ_DONT_REPARSE` and `FILE_OPEN_REPARSE_POINT` and rejects a returned
224
- reparse handle (`windows-handle-loader.tests.ps1:45-50`). The probe tests
225
- handle identity, same-handle read, post-swap identity/read, hard-link count,
226
- owner/DACL policy, broad inherited ACEs, and an actual junction rejection
227
- when setup permits (`:121-185`).
228
-
229
- ### Must-fix or gate blockers remaining
230
-
231
- - **P0-B is still direct-child only.** `child-process.ts:172-176` explicitly
232
- signals only the PID returned by `spawn`; the comments correctly make no
233
- tree claim. The excluded Job Object/process-group work must establish and
234
- test descendant containment before this can launch a real service. The
235
- `TEARDOWN_FAILED` timer proves only that this helper eventually refuses to
236
- claim teardown, not that descendants are gone.
237
- - **Hash-before-spawn is not race-proof.** `child-process.ts:79-87` uses
238
- `lstat`/`readFile` and then launches by pathname. It is an adequate
239
- synthetic artifact identity check and the history now says so, but a hostile
240
- replacement between hashing and spawn can run another file. A real-host gate
241
- still needs an OS-qualified launch/open identity strategy or trusted artifact
242
- boundary. The hash read itself is also not size-bounded before allocation;
243
- production artifact validation should impose a size limit or streaming
244
- policy.
245
- - **Cleanup is intentionally not race-free.** The marker-only check followed
246
- by `rm(marker)`/`rmdir(path)` (`run-directory.ts:110-116`) narrows damage and
247
- refuses unexpected content, but it is path-based. A hostile concurrent
248
- replacement can race the checks. This is acceptable as a synthetic fixture
249
- primitive with an explicit limitation, not as private real-host evidence;
250
- real qualification still needs handle-relative/no-follow cleanup or an
251
- equivalent owned OS boundary.
252
- - **Windows ACL evidence is shape-limited.** The helper requires exactly one
253
- current-SID rule for its private run directories (`run-directory.ts:70-80`),
254
- and the source probe’s native policy covers the tested current SID,
255
- SYSTEM/Administrators, unknown/broad/deny ACE, owner, and replacement-mask
256
- shapes. It does not establish all effective-token/group/ACE/reparse-tag
257
- combinations. The source probe’s `OpenDirectory` remains path-based for the
258
- fixture root and uses share-write/delete; this is not a production
259
- race-free source loader.
260
- - **P0-C remains Windows partial.** The successful junction fallback is useful
261
- evidence of actual ancestor rejection, but native symlink creation (1314)
262
- and native junction creation (4392) were unavailable on this worker. Linux
263
- ext4, concurrent ACL changes, final size consistency under mutation, and
264
- alternate reparse tags remain untested. No platform qualification follows.
265
-
266
- ### Scope conclusion
267
-
268
- The repaired code now provides credible bounded **synthetic test primitives**
269
- for the previously failing direct-child lifecycle, pre-cancellation,
270
- artifact-hash, private-directory setup, cleanup refusal, and Windows handle
271
- policy cases. It must not be described as hostile real-host isolation,
272
- process-tree cleanup, race-free executable launch, race-free cleanup, or
273
- Windows/Linux source-loader qualification. P0-B/P0-C release gates therefore
274
- remain open pending the excluded host process boundary plus the native/runtime
275
- and Linux evidence listed above.
@@ -1,29 +0,0 @@
1
- # P0 job containment
2
-
3
- The Windows-only test harness now has a native Job Object supervisor in
4
- `tests/p0-host/windows-job.ps1`. It uses `CreateProcessW(CREATE_SUSPENDED)`, assigns
5
- the process before `ResumeThread`, sets `JOB_OBJECT_LIMIT_KILL_ON_JOB_CLOSE` and
6
- `JOB_OBJECT_LIMIT_PROCESS_MEMORY`, and records bounded output privately. Assignment
7
- failure terminates the still-suspended process and never resumes it.
8
-
9
- `tests/integration/windows-job.test.ts` uses only explicit executables and an owned
10
- request root. It covers exact argv backslashes/spaces, descendant timeout cleanup,
11
- assignment failure before resume, output overflow termination, an empty native
12
- environment, and supervisor cancellation. Node's synthetic fixture creates a
13
- grandchild and runs only with private test directories plus essential OS variables;
14
- no user profile, application-data, PATH, or credential values are copied.
15
-
16
- This is a Windows infrastructure prerequisite, not an OpenCode smoke test or a
17
- qualification result; Linux/macOS have no equivalent qualification.
18
-
19
- The current Windows ARM64 worker rejects the mandatory atomic job-list launch
20
- with `ERROR_INVALID_PARAMETER` (87), so it is explicitly blocked rather than
21
- using the weaker assign-after-create fallback. Windows x64 qualification remains
22
- pending on a worker supporting `PROC_THREAD_ATTRIBUTE_JOB_LIST`.
23
-
24
- The ARM64 integration case executes the supervisor and records
25
- `platformBlocked: true` only when its stderr contains
26
- `ATOMIC_JOB_LIST_UNSUPPORTED` and the supervisor exits nonzero. It also asserts
27
- that no child PID was created. This is a passing infrastructure observation,
28
- not a skipped case or a qualification result; any other supervisor failure, or
29
- any child creation on the blocked path, still fails the suite.
@@ -1,52 +0,0 @@
1
- # P0 evidence-support implementation
2
-
3
- Date: 2026-10-13. Scope: **test infrastructure only**, not product code or a P0
4
- exit-gate pass.
5
-
6
- ## Implemented
7
-
8
- - `tests/support/qualification.ts`: bounded plain-data validation and selected-case
9
- evidence assessment. Rejects missing, duplicate, unexpected, TODO/skipped,
10
- stale-build/run and mismatched-profile evidence. Expected gaps require an explicit
11
- active-only expectation and never satisfy an enforced profile.
12
- - `tests/support/recording-endpoint.ts`: scripted loopback HTTP fixture with bounded
13
- request captures, request/connection counters, deadlines and explicit socket/buffer
14
- cleanup. Supports JSON/SSE response content types for later protocol fixtures but
15
- is not itself a model protocol implementation.
16
-
17
- ## Test-first and independent review evidence
18
-
19
- Initial behavior tests were written before either helper existed and failed with
20
- missing-module errors. After implementation, the qualification suite passed 13
21
- cases and the recording suite passed 6, later 8 cases.
22
-
23
- Fresh-eye reviews in [review 5](5-qualification-gate-review.md) and
24
- [review 6](6-p0-infrastructure-review.md) required runtime-shape validation,
25
- accessor rejection and stronger close/counter semantics. Added regression tests
26
- failed against the previous implementations before repairs. The repaired focused
27
- command passes **24 tests** (15 evidence assessor, 9 recording endpoint):
28
-
29
- ```sh
30
- node --test tests/unit/qualification.test.ts tests/unit/recording-endpoint.test.ts
31
- ```
32
-
33
- The reviewer independently reran those 24 tests and found no new must-fix bug in
34
- these two helpers. Earlier full-suite/typecheck failures in review reports include
35
- parallel in-progress files and are preserved as historical observations, not a
36
- claim that those files remain broken.
37
-
38
- ## Boundaries still open
39
-
40
- - Evidence assessment does not establish actual execution, correct/complete case
41
- selection, artifact provenance/checksums, capture ownership or independent oracle
42
- truth. The real smoke runner must supply and validate those before qualification.
43
- - The recorder is synthetic, unauthenticated loopback HTTP. It neither validates
44
- GitHub TLS/address policy nor blocks unrelated outbound network traffic.
45
- - `attempted` counts parsed HTTP requests; `connections` counts accepted sockets
46
- and `malformed` is separate. Zero parsed requests is not zero network attempts.
47
- - Explicit per-instance recorder bounds allow up to 64 MiB of captured body data
48
- at the maximum settings, plus copies/overhead. A future combined real-host
49
- harness must reserve aggregate budgets; these local limits are not a process RSS
50
- guarantee. Captures remain private in memory and are never printed by helpers.
51
- - No product module or OpenCode service was launched by these tests. Runtime,
52
- source-opening and process-tree prerequisites remain independent P0 gates.
@@ -1,88 +0,0 @@
1
- # P0-BOOT launch plan (not yet executable)
2
-
3
- Status: prepared; launch is intentionally blocked until the process-containment
4
- agent completes its review. This plan owns no host process and reads no user
5
- configuration.
6
-
7
- ## Candidate identity
8
-
9
- Use the architecture-matching explicit unqualified candidate in
10
- `tests/smoke/runtime-candidate.json`: OpenCode `2.0.28`, official tag commit
11
- `026b106bc5d41b679ffc413264229c340871e4d3`. On this ARM64 runner the selected
12
- member is `@opencode/cli-windows-arm64@2.0.28`, PE machine `AA64`, executable
13
- member SHA-256
14
- `65F0E7ACCC7D5FD77CA7380FDE87E3289D0CF27687851E5240BBDE6CAA7BDB66`.
15
- The x64 candidate remains recorded for a matching worker. Each downloaded
16
- archive SHA-256 is distinct and must not be used as the executable hash.
17
-
18
- ## Source-verified launch contract
19
-
20
- At tag `v2.0.28`, `packages/cli/src/server-process.ts` supports foreground
21
- `serve` with `--hostname` and `--port`; `packages/cli/src/services/standalone.ts`
22
- uses `serve --stdio --port 0` and emits one JSON readiness line
23
- `{"url":"..."}`. `serve` itself prints `server listening on ...` in foreground
24
- mode. The exact password variable is `OPENCODE_PASSWORD` (legacy
25
- `OPENCODE_SERVER_PASSWORD` is also accepted): `standalone.ts` creates a random
26
- password and passes it to the child as `OPENCODE_PASSWORD`. The client sends
27
- HTTP Basic authentication with username `opencode` and that password. The
28
- first probe must use the standalone stdio contract or an explicit foreground
29
- `serve` with a synthetic `OPENCODE_PASSWORD`, never shared-service discovery.
30
-
31
- The local API identity endpoint is authenticated `GET /api/info`, documented by
32
- the V2 API reference and defined by the tag's `packages/protocol/src/groups/server.ts`.
33
- Its `ServerInfo` schema requires `version: string`, `pid: nonnegative integer`,
34
- `urls: string[]`, and `paths.tmp: string`, with optional capabilities. The probe
35
- must decode this schema, assert version `2.0.28`, assert `pid >= 0`, require a
36
- run-owned URL and `paths.tmp` beneath the private root, and never print the
37
- password or raw response.
38
-
39
- ## Environment construction
40
-
41
- `tests/support/opencode-environment.ts` is a pure builder. It accepts a private
42
- absolute run root and caller-supplied trusted OS values, and returns:
43
-
44
- * a fixture-owned cwd and data/cache/config/state/temp/database paths;
45
- * an explicit allowlist containing only OS runtime values and source-verified
46
- OpenCode controls; and
47
- * synthetic JSON config with only supported fields: `snapshots: false`,
48
- `warming: false`, and `update: "disable"`.
49
-
50
- After review 10, the builder requires an explicit `password` (64 lowercase hex
51
- characters). The future runner generates 32 CSPRNG bytes for each run and passes
52
- them only through the private environment/request channel, never user credentials
53
- or command-line arguments. `OPENCODE_PASSWORD` is in the fixed allowlist. The
54
- database choice is a run-owned file, not `:memory:`, so later sink evidence can be
55
- inspected. Candidate metadata now records the same database and auth controls.
56
-
57
- The builder never reads `process.env`. It omits provider keys, `NODE_OPTIONS`,
58
- proxy variables, plugin-path variables, credential-agent variables, and all
59
- other ambient values. The caller must supply any executable-required OS values
60
- explicitly. `OPENCODE_CONFIG_PROJECT_DISABLE=1` prevents the ancestor/project
61
- walk; `ProgramData` is redirected so system config discovery is also inside the
62
- fixture. `XDG_*`, `TEMP`/`TMP`, `OPENCODE_CONFIG_DIR`, `OPENCODE_CONFIG_CONTENT`,
63
- and `OPENCODE_DB` implement the tag-source isolation controls.
64
-
65
- ## Next-agent execution sequence
66
-
67
- 1. Wait for the containment decision and create an ACL-protected run root.
68
- 2. Materialize only the exact candidate executable member, verify regular-file,
69
- non-link, single-link identity and the corrected SHA-256 before spawn.
70
- 3. Build the environment using the pure builder and pass it to the existing
71
- child runner with `shell: false`, exact cwd, bounded stdout/stderr, startup
72
- and overall deadlines, and owned-process containment.
73
- 4. Start the exact foreground/stdio command selected by the containment agent.
74
- Do not use `opencode`, `service`, `api`, or any installed executable by name.
75
- 5. Parse only the bounded readiness line, call authenticated `GET /api/info`,
76
- assert candidate identity, then stop the owned process through the approved
77
- containment mechanism. Any mismatch, shared-service signal, unexpected
78
- filesystem path, inherited variable, timeout, or unauthenticated endpoint
79
- is a hard failure.
80
- 6. Preserve raw evidence only under the private run root; publish case ID,
81
- safe status, and validated identity fields.
82
-
83
- This document is a launch prerequisite, not P0 evidence. No host startup or
84
- real smoke pass is claimed until the sequence is implemented and executed.
85
-
86
- Parent added red/green malformed-input, accessor, cross-platform separator and
87
- mandatory-auth regressions after initial implementation. The six environment
88
- tests exercise only pure construction/validation, not filesystem or host isolation.
@@ -1,111 +0,0 @@
1
- # Design: transparent OpenCode V2 substitution MVP
2
-
3
- Status: authoritative implementation design, revised 2026-10-16.
4
-
5
- ## Architecture
6
-
7
- ```text
8
- configured dotenv source
9
- |
10
- v
11
- private activation map: raw value -> secret-UUID handle
12
- |
13
- +--> session/model/http/tool hooks --> sanitized existing host objects
14
- ```
15
-
16
- There is no tool transform, capability registry, credential executor, private
17
- HTTP transport, or network request owned by this plugin.
18
-
19
- ## Activation lifecycle
20
-
21
- The Effect activation loads sources and admits a complete snapshot before hooks
22
- can publish substitutions. The map and matcher state belong to that activation
23
- closure. Activation close disposes the map and matcher state. No raw values are
24
- stored in OpenCode storage, project metadata, logs, environment variables or
25
- spills created by the plugin.
26
-
27
- The current implementation uses a bounded in-memory map and a CSPRNG-generated
28
- UUIDv4-shaped handle. Handles are obfuscation identities only; there is no
29
- reverse-resolution API exposed to the host or model.
30
-
31
- ## Source contract
32
-
33
- MVP sources are explicit Location-relative dotenv files with an allowlisted key
34
- set. No shell sourcing, interpolation, environment indirection or arbitrary
35
- path is allowed. A required source failure, unsafe value, admission collision,
36
- or budget failure disables substitution rather than publishing a partial map.
37
-
38
- The path-opening implementation and filesystem security claims remain a
39
- qualification gate. Until each platform is tested, the release must name only
40
- the supported platform/filesystem.
41
-
42
- ## Substitution algorithm
43
-
44
- For each admitted raw value, construct the finite supported representation set
45
- (semantic text, dotenv spellings, JSON escaping, percent encoding and base64
46
- forms). Scan host values with bounded interval collection, replace matches with
47
- the corresponding opaque handle, and rescan the complete output. Overlaps are
48
- unioned before replacement. If the result cannot be bounded or validated,
49
- withhold the affected sink instead of returning raw input.
50
-
51
- The replacement is applied to exact host objects before publication. For
52
- serialized provider payloads, the implementation must preserve valid protocol
53
- shape and validate the final encoded bytes; leaf-only scans are insufficient.
54
-
55
- ## Hook boundary
56
-
57
- The MVP wires these existing V2 surfaces:
58
-
59
- - `session.prompt`
60
- - `session.context`
61
- - `session.compaction`
62
- - `session.generate`
63
- - `session.title`
64
- - `session.model.request`
65
- - `session.http.request`
66
- - `session.http.response`
67
- - `tool.execute.before`
68
- - `tool.execute.after`
69
-
70
- A hook only proves the boundary it actually observes. It cannot retroactively
71
- protect values emitted before the hook, internal writes by ordinary tools,
72
- provider-hosted tools, WebSocket/SDK routes, or host behavior after unload.
73
- Those are recorded as uncovered or deferred, never silently considered safe.
74
-
75
- ## Configuration
76
-
77
- ```json
78
- {
79
- "version": 1,
80
- "sources": [
81
- {
82
- "id": "project",
83
- "type": "file",
84
- "format": "dotenv",
85
- "path": ".env",
86
- "required": true,
87
- "keys": ["EXAMPLE_TOKEN"]
88
- }
89
- ],
90
- "limits": { "...": "all bounded limit keys are required" }
91
- }
92
- ```
93
-
94
- No authorization, capabilities, operation IDs, resource URLs, tool names or
95
- transport endpoints belong in the MVP options.
96
-
97
- ## Explicit non-goals
98
-
99
- - no model-visible credential tools;
100
- - no plugin-owned requests;
101
- - no GitHub-specific behavior;
102
- - no DNS/proxy/egress policy for requests the plugin does not make;
103
- - no universal protection claim against an untrusted or compromised host.
104
-
105
- ## E2E proof
106
-
107
- `P5-TRANSPARENT-ROUNDTRIP` runs a real OpenCode 2.0.28 subprocess with a
108
- synthetic dotenv source and a loopback provider. It verifies existing provider
109
- traffic contains an opaque `secret-<uuid>` replacement and not the raw canary,
110
- while the plugin makes no independent request. Additional sink cases are
111
- required before release qualification.
@@ -1,81 +0,0 @@
1
- # Intent: transparent OpenCode V2 secret substitution
2
-
3
- Status: authoritative product intent, revised 2026-10-16.
4
-
5
- ## Problem
6
-
7
- Project-local configuration may contain credentials that must not appear in
8
- OpenCode prompts, model requests, tool-visible values, errors, logs, or durable
9
- host payloads. The plugin should protect those values **behind the scenes**;
10
- the model and ordinary OpenCode tools should continue using their existing
11
- interfaces.
12
-
13
- ## MVP product boundary
14
-
15
- The plugin is a native OpenCode V2 Effect plugin that:
16
-
17
- 1. Loads explicitly configured dotenv values into private activation memory.
18
- 2. Assigns each admitted value an opaque `secret-<UUIDv4>` replacement.
19
- 3. Applies the replacement through existing OpenCode session/model/http/tool
20
- hooks before public or provider-facing publication.
21
- 4. Withholds or fails closed when a bounded sink cannot be safely transformed.
22
- 5. Disposes the private map when the plugin activation closes.
23
-
24
- The plugin **does not**:
25
-
26
- - register tools or change tool schemas;
27
- - expose a credential catalog to the model;
28
- - resolve handles for a private operation;
29
- - perform credential, GitHub, or other plugin-owned network requests;
30
- - implement authorization for an operation it owns;
31
- - claim protection for host routes that its hooks do not observe.
32
-
33
- ## What the model sees
34
-
35
- Where a configured value would otherwise appear, the public value is an opaque
36
- handle such as `secret-550e8400-e29b-41d4-a716-446655440000`. The raw value
37
- never enters model-visible arguments or provider request bodies through a
38
- qualified path. Handles are references for obfuscation only; this MVP does not
39
- resolve them.
40
-
41
- ## MVP configuration
42
-
43
- Options contain only:
44
-
45
- - `version: 1`;
46
- - relative, explicitly keyed dotenv `sources`;
47
- - bounded `limits` for source size, traversal, output and working memory.
48
-
49
- Paths must remain inside the OpenCode Location. `${env:...}` and `${file:...}`
50
- indirections are rejected. Values are synthetic in tests and never persisted by
51
- the plugin.
52
-
53
- ## Supported boundary
54
-
55
- The first qualified boundary is the existing provider request path plus the
56
- session hooks for prompt, context, compaction, generate and title, and tool
57
- before/after results. Each sink requires its own evidence. Ordinary shell,
58
- MCP, provider-hosted tools, WebSocket/SDK routes, screenshots, transformed or
59
- covert values, and host behavior after plugin unload are not automatically
60
- protected.
61
-
62
- ## Security posture
63
-
64
- This is an **active-plugin-only** MVP. It trusts the OpenCode host, process,
65
- OS account and plugin ordering. It does not claim protection against a
66
- compromised host, a malicious same-process plugin, or an unobserved route.
67
- Broad production enforcement is a later profile requiring host-owned readiness
68
- and route mediation; it is not part of this plugin's MVP.
69
-
70
- ## E2E acceptance
71
-
72
- `P5-TRANSPARENT-ROUNDTRIP` launches pinned OpenCode V2 in a private sandbox,
73
- loads a synthetic `.env`, sends one normal `opencode run` prompt through a
74
- loopback provider, and proves:
75
-
76
- - the existing provider path completes normally;
77
- - provider bytes contain an opaque handle instead of the raw canary;
78
- - public output and sandbox host data contain no raw canary;
79
- - no plugin-owned tool or network request occurs.
80
-
81
- This is a first E2E boundary, not exhaustive qualification.