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,65 +0,0 @@
1
- # 16 — Pure sanitizer implementation
2
-
3
- Status: implemented pure MVP components; host qualification deferred. R19 residual sanitizer blockers remediated.
4
-
5
- ## Scope
6
-
7
- `src/sanitize/index.ts` provides a dependency-free, internally derived representation inventory,
8
- whole-snapshot admission predicate, finite UUID/reserved-output conflict checks,
9
- transitive interval unions, one-pass replacement with final boundary validation,
10
- canonical newline-joined text sinks, and bounded canonical projections of plain
11
- structured data. Ambiguous unions use redaction-only output. Construction checks
12
- finite default output, traversal depth, node, and match bounds and withholds rather
13
- than returning partial data. Pattern count, text, representation, index, and
14
- temporary construction limits are checked before expansion. Interval materialization
15
- is capped before each hit is allocated; active object identities are rejected. Dotenv inventory uses the parser's
16
- double/single quote, escape, and multiline lexical forms. Caller representation
17
- metadata is not trusted.
18
-
19
- All public pure boundaries validate plain data descriptors and catch foreign proxy,
20
- accessor, malformed-pattern, budget, and serialization failures, returning only the
21
- fixed admission denial or `null` (or an empty interval result). The implementation
22
- does not claim universal encoding coverage, noninterference,
23
- or protection against arbitrary host serializers. It rejects getters, non-plain
24
- prototypes, arrays, binary data, and unqualified host projections; it does not invoke
25
- `toJSON`.
26
-
27
- ## Test-first evidence
28
-
29
- The initial `tests/unit/sanitize.test.ts` run was red because
30
- `src/sanitize/index.ts` did not exist. After implementation:
31
-
32
- ```text
33
- node --test tests/unit/sanitize.test.ts
34
- 12 passed, 0 failed
35
- ```
36
-
37
- The focused suite covers transitive `abc`/`bcdef` overlap, ambiguous replacement
38
- and idempotence, UUID/reserved admission (`e`, `true`, `!`), canonical cross-part
39
- joining, scalar/key handling, and construction limits.
40
-
41
- `npm run typecheck` remains blocked by the unrelated pre-existing malformed
42
- `src/budgets/index.ts` placeholder. The repository-wide unit command also has
43
- unrelated missing config/source/store modules and Windows private-directory
44
- harness failures. No host or OpenCode process was launched.
45
-
46
- Residual aggregate-work findings are closed: public transforms reserve shared finite
47
- pattern, index, identity, and temporary-work capacity before construction and release
48
- it on every return path.
49
-
50
- ## R21 budget ceiling remediation
51
-
52
- The four caller-controlled operation budgets have fixed hard ceilings equal to their
53
- defaults: 2 MiB output, depth 32, 50,000 nodes, and 50,000 matches. An explicit
54
- nonnegative safe integer above any ceiling is rejected (not clamped), so no public
55
- sanitizer path can opt into unbounded interval, traversal, or output work. Rejected
56
- operations reserve no partial output and all successful reservations remain released
57
- on every return path.
58
-
59
- ## R22 boundary and key-work remediation
60
-
61
- Canonical text-sink inspection and reservation calculation are contained by one
62
- fail-closed `try`/`finally`, including hostile array proxies. Plain projection collects
63
- enumerable keys incrementally under the finite node/object-key ceiling before sorting;
64
- it does not call an unbounded `Object.keys(...).sort()` operation. Denials return no
65
- partial projection and reservations remain reusable.
@@ -1,36 +0,0 @@
1
- # 17 — Pure store, policy, and budget implementation
2
-
3
- Status: implemented as unit-tested pure components; host qualification remains deferred.
4
-
5
- The MVP components are activation-local and memory-only. `ActivationStore` creates
6
- cryptographically random `secret-<UUIDv4>` references, keeps redaction references distinct
7
- from capability references, requires explicit session/agent enrollment, checks
8
- generation and canonical Location on every use, and clears all state on teardown.
9
- Raw values are only passed to a caller-owned callback after a successful policy
10
- proof; they are not included in errors or public records.
11
-
12
- `createPolicy` supports only explicit `plugin-preauthorized` authorization with
13
- `hostPermissions: not-integrated`. It intersects session, agent, Location,
14
- generation, capability, operation, and resource, and rejects malformed records
15
- without echoing input. Host-mediated authorization is intentionally unavailable.
16
-
17
- `BudgetLedger` performs atomic reservation-before-work accounting across source,
18
- index, payload, traversal, session/handle/call, and working-memory dimensions.
19
- Admission is denied at capacity and checked additions cannot exceed safe integers;
20
- reservations are idempotently released only by
21
- explicit disposal and no eviction or waiting queue exists. `ActivationStore` accepts
22
- an injected ledger, reserves session/handle/secret/index/working capacity before
23
- mutating its maps, bounds UUID collision retries, and disposes all reservations on
24
- teardown. Session handle quotas apply to handles explicitly enrolled to that session.
25
- Malformed budget records and requests are denied without propagating foreign errors.
26
- The ledger is activation-local and does not establish the process-wide quota
27
- arbiter required by the production profile.
28
-
29
- Evidence: `tests/unit/store-policy.test.ts` and `tests/unit/budgets.test.ts` are
30
- red before implementation and pass after implementation. These tests do not
31
- qualify OpenCode host hooks, process-wide quota sharing, or movement epochs.
32
- The Windows Job Object/real-host harness remains deferred per `docs/spec/plan.md`.
33
-
34
- The store now always uses a validated bounded ledger, including safe defaults when no
35
- ledger is supplied, and charges UTF-8 byte lengths for secret, index, and working
36
- reservations.
@@ -1,182 +0,0 @@
1
- # 18 — Adversarial review of pure MVP components
2
-
3
- Status: review only. No source changes were made. The focused pure tests pass
4
- (19/19), and `npm run typecheck` passes. The ACL/host harness was not run; this
5
- review makes no host or OpenCode qualification claim.
6
-
7
- ## Findings
8
-
9
- ### High — Store references do not implement the specified handle language
10
-
11
- `src/store/index.ts:7,50-57` accepts and generates arbitrary 64-hex strings.
12
- The design requires independently generated `secret-<UUIDv4>` references
13
- (`docs/spec/design.md:288-293, 316-322`). The current handles are therefore
14
- not compatible with the public grammar or its admission oracle, and the
15
- store's `isHandle` check admits values that are not valid MVP references.
16
-
17
- **Required fix:** generate lowercase CSPRNG UUIDv4 handles with the exact
18
- `secret-` prefix, validate that finite grammar (including version/variant), and
19
- make redaction-only and capability handles use the same versioned public
20
- language without deriving either from a secret.
21
-
22
- ### High — Raw-secret callback authorization is forgeable and operation-free
23
-
24
- `src/store/index.ts:43-47` treats any structurally supplied
25
- `{ok: true, capability: ref}` as a valid proof. `AuthorizationProof` is an
26
- unbranded public interface (`src/policy/index.ts:7`), so a caller can forge it
27
- without invoking `createPolicy`. The store also accepts no operation/resource
28
- at the callback boundary; `_operation` is ignored after `grant`
29
- (`src/store/index.ts:36-40`). This violates the private resolver boundary and
30
- the requirement that policy, operation, resource, session, generation and
31
- Location all be intersected immediately before resolution
32
- (`docs/spec/design.md:162, 393`).
33
-
34
- **Required fix:** use a non-forgeable/internal policy decision or have the
35
- store perform the policy check itself with an issuer-owned opaque proof bound
36
- to the complete request (including operation and resource). Do not expose a
37
- plain object that is sufficient to release the raw value. Add tests that forge
38
- every proof field and that change operation/resource after authorization.
39
-
40
- ### High — Sanitizer can recurse unboundedly on cyclic structured data
41
-
42
- `src/sanitize/index.ts:183-214` has node/depth limits only when callers supply
43
- them and has no visited-set. A cyclic plain object with the default budget
44
- recurses until a `RangeError`; the error is neither a fixed omission nor a
45
- bounded denial. This conflicts with the explicit cycle/inspectability and
46
- bounded traversal requirements (`docs/spec/design.md:339-340`; `plan.md:126-129`).
47
-
48
- **Required fix:** track object identity on the active traversal path (or reject
49
- all repeated identities), enforce finite default limits before descending, and
50
- convert all malformed/cyclic/foreign-object cases to `null` without exposing
51
- the thrown error or retaining partial output.
52
-
53
- ### High — Dotenv lexical representation inventory misses actual parser spellings
54
-
55
- `src/sanitize/index.ts:53-55` returns dotenv content without the surrounding
56
- quotes, while `src/sources/dotenv.ts:17-23` records quoted lexical values with
57
- those quotes. It also omits the `\\t` spelling even though
58
- `src/sources/dotenv.ts:31-33` decodes it. Consequently a semantic value can
59
- remain in a supported dotenv lexical representation while the matcher scans
60
- only the semantic/content spelling. The stated requirement is to preserve and
61
- protect relevant lexical representations (`docs/spec/design.md:251-253, 314`).
62
-
63
- **Required fix:** define the grammar's exact lexical forms in one place and
64
- generate every accepted form, including quote delimiters, escaped quote,
65
- backslash, `n/r/t`, multiline spelling, and any accepted unquoted form. Add
66
- accepted-snapshot tests that assert each lexical byte sequence is absent.
67
-
68
- ### High — Dotenv parser mishandles escaped quote grammar
69
-
70
- `src/sources/dotenv.ts:19-22` finds the first quote with `indexOf`, without
71
- recognizing escaped quotes. A valid value such as `KEY="a\\"b"` is closed at
72
- the escaped quote and is rejected or misparsed. This contradicts the claimed
73
- documented quoting support (`docs/spec/design.md:249-253`) and makes the
74
- parser's lexical contract inconsistent with its decoder.
75
-
76
- **Required fix:** scan quoted bodies with escape state, distinguish escaped
77
- quotes from terminators, and test escaped quote/backslash/control spellings
78
- under CRLF and multiline input. Reject unsupported escapes explicitly rather
79
- than silently changing their bytes.
80
-
81
- ### Medium — Sanitizer construction is not fully bounded during allocation
82
-
83
- `src/sanitize/index.ts:142-158` checks output only after `output +=` has already
84
- constructed each fragment, and appends the final unsanitized suffix before the
85
- final budget check (`145-156`). `collectIntervals` also materializes every
86
- match without a node/match bound (`110-134`). This does not satisfy
87
- `outputBytes`-while-constructing and bounded matching requirements
88
- (`docs/spec/design.md:171-174, 329`).
89
-
90
- **Required fix:** reserve/count input, match storage and each output fragment
91
- before allocation; reject when a reservation would exceed the configured
92
- limit. Bound pattern count, match count and interval-cache/identity storage,
93
- and return only a complete validated result.
94
-
95
- ### Medium — Admission depends on caller-supplied, incomplete pattern metadata
96
-
97
- `src/sanitize/index.ts:61-63,99-107` trusts `Pattern.representations` and does
98
- not derive or verify the complete representation set. A caller can pass only
99
- `text` and receive `ok: true` even when an omitted supported encoding is unsafe.
100
- The source/parser integration may intend to call `representationsFor`, but the
101
- pure admission API itself does not enforce the all-pattern contract.
102
-
103
- **Required fix:** admit a canonical secret value/source entry and derive the
104
- versioned representation set internally, or validate that supplied
105
- representations exactly match the inventory. Reject malformed/accessor pattern
106
- objects with fixed diagnostics.
107
-
108
- ### Medium — Policy malformed inputs can throw instead of fixed denial
109
-
110
- `src/policy/index.ts:34-39` indexes `options.sessions[request.sessionId]` after
111
- only shallow record checks. A request with a key such as `toString` or
112
- `__proto__` can select an inherited non-array value and then throw when
113
- `.includes` is called, rather than returning `denied()`. Proxy/accessor inputs
114
- can likewise throw from `Object.getOwnPropertyDescriptors` or property access
115
- (`18-21,27-38`). The contract requires malformed inputs to fail closed with
116
- safe, fixed behavior.
117
-
118
- **Required fix:** normalize sessions into null-prototype maps during validated
119
- construction, validate request field types before lookup, use own-property
120
- checks, and catch/reject uninspectable objects without propagating foreign
121
- errors. Add prototype-key, proxy, accessor and cyclic tests.
122
-
123
- ### Medium — Config accepts unsafe/semantically invalid resource and path forms
124
-
125
- `src/config/options.ts:67-70,80` rejects only slash traversal and a drive
126
- prefix. A rooted Windows path beginning with `\\`, NUL-containing paths, and
127
- `.`/`..` resource identifiers can pass. Resource identifiers also have no
128
- explicit 100-byte limit, while the operation contract requires rejecting
129
- `.`/`..`, ambiguous spellings and values over 100 bytes
130
- (`docs/spec/design.md:377`). Capabilities are not checked against an actual
131
- key in their source, so a typo can create an accepted but unusable grant.
132
-
133
- **Required fix:** validate canonical relative path syntax including NUL and
134
- Windows rooted/UNC forms, enforce the exact resource grammar and byte limits,
135
- reject `.`/`..`, and require every capability source/key pair to exist in the
136
- validated source schema or fail the whole configuration.
137
-
138
- ### Medium — Budget ledger can lose atomic accounting at safe-integer overflow
139
-
140
- `src/budgets/index.ts:15-25` validates each reservation value but not
141
- `used + value`. Repeated reservations near `Number.MAX_SAFE_INTEGER` can lose
142
- precision, allowing counters to diverge from actual reservations. The ledger
143
- also remains activation-local; it cannot establish the process-wide quota
144
- required by `docs/spec/design.md:177`.
145
-
146
- **Required fix:** reject additions that exceed `Number.MAX_SAFE_INTEGER` (or
147
- use checked bigint accounting), and keep process-wide sharing/dedicated-service
148
- restriction as an explicit integration gate rather than implying this ledger
149
- proves aggregate quotas.
150
-
151
- ## Sound choices
152
-
153
- - Configuration errors, source parse errors, policy denial, and budget denial
154
- use fixed messages and do not intentionally echo input values
155
- (`options.ts:19-23`, `dotenv.ts:3`, `policy.ts:16`, `budgets.ts:17`).
156
- - `checked` rejects non-plain objects, accessors and cycles before ordinary
157
- option traversal (`src/config/options.ts:35-45`), and options reject unknown
158
- fields and raised limits (`53-84`). This remains useful after proxy/error
159
- hardening.
160
- - Dotenv parsing is non-executing, does not consult `process.env`, rejects
161
- duplicate keys, and returns an immutable snapshot (`src/sources/dotenv.ts:9-29`).
162
- - Sanitization correctly computes transitive overlap unions, performs one
163
- replacement, scans the final output, joins text sink parts before matching,
164
- and emits one text part (`src/sanitize/index.ts:110-159,163-174`).
165
- - Plain projection rejects non-plain objects/accessors and independently
166
- handles scalar JSON spellings rather than invoking `toJSON` or prototypes
167
- (`src/sanitize/index.ts:176-214`). It still needs cycle and allocation fixes.
168
- - Store teardown clears maps and checks active state, session/agent enrollment,
169
- generation and Location before callback invocation (`src/store/index.ts:43-49`).
170
- - Budget reservations are checked before commit and disposal is idempotent
171
- (`src/budgets/index.ts:15-25`).
172
-
173
- ## MVP disposition
174
-
175
- The pure tests are useful regression coverage but do not close the above
176
- findings. The handle-language, callback-proof, cycle, lexical-representation,
177
- and escaped-quote findings must be fixed before MVP-owned tools are built on
178
- these APIs. The policy/config hardening and checked accounting are also needed
179
- before treating malformed inputs or resource limits as fail-closed. These
180
- results are limited to pure code; source-opening ACLs, host hooks, publication
181
- ordering, aggregate quota ownership, and transport behavior remain unverified
182
- per `docs/spec/plan.md`.
@@ -1,142 +0,0 @@
1
- # 19 — Fresh adversarial re-review of pure MVP after R18
2
-
3
- Status: store/budget blockers remediated in the pure MVP; host qualification remains
4
- deferred. The ACL/source-opening harness, OpenCode, real transport, and host
5
- qualification were not run.
6
-
7
- ## Verdict
8
-
9
- **Not ready for MVP-owned tools or an MVP qualification claim.** R18 resolves
10
- the previously reported handle grammar, authorization-proof binding, cycle and
11
- default traversal bounds, dotenv escaped-quote parsing, derived
12
- representation inventory, malformed policy/config handling, and safe-integer
13
- budget-overflow findings. The pure components remain useful, but the findings
14
- below are residual blockers, and the host/source gates remain entirely open.
15
-
16
- ## R18 findings verified resolved
17
-
18
- - Store-generated and accepted references use lowercase `secret-<UUIDv4>` with
19
- version and variant validation.
20
- - Policy proofs are issuer-held `WeakMap` capabilities and bind session,
21
- agent, Location, generation, capability, operation, and resource. Forged
22
- objects and changed operation/resource are rejected by the store boundary.
23
- - Structured projection has finite default depth/node limits and rejects active
24
- cycles.
25
- - Dotenv quoted scanning is escape-aware; the focused tests cover escaped
26
- quotes, backslashes, control escapes, CRLF, and multiline values.
27
- - Admission derives the finite representation inventory instead of trusting
28
- caller-supplied `Pattern.representations`; UUID-language and reserved-output
29
- conflicts reject the whole set.
30
- - Config checks relative paths, resource grammar/byte limits, declared source
31
- keys, malformed proxy/cyclic/accessor inputs, and fixed safe errors.
32
- - Budget reservation checks `current + value` against the safe-integer range.
33
-
34
- ## Residual findings
35
-
36
- ### High — Sanitizer malformed inputs can escape as foreign exceptions
37
-
38
- `src/sanitize/index.ts` does not put all public pure entry points behind a
39
- fail-closed exception boundary. `projectPlain` calls `Object.getPrototypeOf`,
40
- `Object.keys`, and descriptors on untrusted objects; proxy traps can throw. The
41
- pattern path similarly calls `patternRepresentations` and `representationsFor`
42
- without validating/catching malformed patterns, accessors, or non-string
43
- `text`. `admitPatterns`, `sanitizeText`, and `projectPlain` can therefore
44
- propagate a caller-controlled error instead of returning the fixed denial/null
45
- required for malformed or uninspectable data.
46
-
47
- This was reproduced without source modification with proxies whose
48
- `getPrototypeOf`, `ownKeys`, or pattern property access throws. The exception
49
- message was observable (`x`).
50
-
51
- **Required gate:** validate immutable, plain pattern records and budgets at the
52
- boundary; catch all object/proxy/serialization failures and return the fixed
53
- admission failure or `null`. Add tests for throwing traps on every sanitizer
54
- entry point and ensure no error/cause is returned or logged.
55
-
56
- ### High — Pure sanitizer allocation accounting is still incomplete
57
-
58
- `collectIntervalsBounded` bounds matches, but there is no bound on pattern count,
59
- pattern text/representation size, derived representation construction, or
60
- pattern metadata storage before those allocations occur. `admitPatterns` has no
61
- budget at all. `sanitizeText` does preflight fragment byte counts, but still
62
- constructs intermediate concatenations and repeatedly derives representations;
63
- the pure API does not reserve the configured working/index memory required by
64
- the design. A bounded input alone therefore does not establish bounded total
65
- allocation for adversarial pattern arrays.
66
-
67
- **Required gate:** impose explicit pattern/representation/index limits and
68
- reserve before construction, including temporary copies and interval identity
69
- storage; reject the complete operation before partial output. Test oversized
70
- pattern arrays, oversized pattern values, and representation-expansion limits.
71
-
72
- ### Medium — Store handle/map growth and collision retry are unbounded
73
-
74
- **Remediated:** `ActivationStore` now accepts an activation-local `BudgetLedger`,
75
- reserves capacity before map mutation, enforces active-session and explicit
76
- per-session handle quotas, bounds UUID collision attempts, and releases all
77
- reservations on teardown. Deterministic collision exhaustion is fixed denial.
78
-
79
- `src/store/index.ts` has no handle/session/map quota and `newHandle()` retries
80
- forever on collisions. A supplied or failing deterministic random source can
81
- make registration hang, and repeated registration/grant calls can grow the
82
- activation maps without the configured `handlesPerSession`, `secretCount`, or
83
- working/index reservations. The R18 UUID formatting fix does not close the
84
- resource-accounting contract.
85
-
86
- **Required gate:** inject the budget ledger and enforce admission before map or
87
- handle allocation, with a finite collision-attempt bound and fixed denial.
88
- Cover per-session and activation exhaustion, collision exhaustion, teardown,
89
- and reservation release.
90
-
91
- ### Medium — Budget ledger malformed-object paths are not fail-closed
92
-
93
- **Remediated:** limits and requests are descriptor-validated, symbol/unknown and
94
- accessor/proxy/cyclic shapes are rejected, and request failures return `{ ok: false }`.
95
-
96
- `src/budgets/index.ts` directly uses `Object.entries` and later property reads
97
- on caller-supplied limits/requests. Throwing proxies or accessors can escape
98
- from the constructor or `reserve` rather than producing the fixed invalid
99
- constructor error or `{ ok: false }`. Checked arithmetic is fixed, but malformed
100
- input handling is not equivalent to the hardened config/policy boundaries.
101
-
102
- **Required gate:** normalize/validate own data descriptors or catch foreign
103
- exceptions at both ledger boundaries; add proxy, accessor, symbol/unknown-field,
104
- and cyclic request tests with safe denial and no diagnostic echo.
105
-
106
- ## Exact residual release gates
107
-
108
- The following are still open and are not closed by the 29 pure tests:
109
-
110
- 1. **P0/R8 source gate:** opened-handle, canonical containment, symlink/reparse,
111
- hard-link, ancestor replacement, filesystem identity, owner/ACL, and
112
- tracked-file tests on each advertised OS/filesystem; no insecure fallback.
113
- 2. **P0 quota/deployment gate:** prove shared process-wide accounting or adopt
114
- the explicitly restricted supervised single-instance/Location envelope;
115
- measure actual RSS under the process memory ceiling.
116
- 3. **P1/P2 integration:** package/config loading through the pinned V2 runtime,
117
- activation-closure ownership, source loading, immutable generations,
118
- lifecycle cleanup, and source rotation semantics.
119
- 4. **P3 residual pure gate:** the sanitizer malformed-input and allocation
120
- findings above, plus independent property tests for all declared sink
121
- representations and final-boundary validation.
122
- 5. **P4/P5 host/tool gate:** native Effect entrypoint, enrollment and private
123
- credential executor, fixed status-only HTTPS operation, DNS/address/peer
124
- policy, cancellation/body withholding, and actual sink/network evidence.
125
- 6. **P0/P5 active-only limitations:** readiness/unload/fallback rollback,
126
- captured executors, late overlays, queued/resumed work, A→B→A moves,
127
- sessionless generation, provider-hosted tools, WebSocket/SDK routes,
128
- shell/progress/spill paths, and host permission deny/ask remain unverified or
129
- expected gaps exactly as the specification says.
130
- 7. **P6+ production gates:** independent mandatory readiness/revision control,
131
- host-mediated authorization, final model-egress/publication gates, and
132
- isolated broker/sandbox enforcement are absent; no enforced-profile claim is
133
- permitted.
134
-
135
- ## Evidence
136
-
137
- - `npm run typecheck` — pass.
138
- - `node --test tests/unit/config.test.ts tests/unit/dotenv.test.ts
139
- tests/unit/sanitize.test.ts tests/unit/store-policy.test.ts
140
- tests/unit/budgets.test.ts` — **29 passed, 0 failed**.
141
- - No source, ACL, OpenCode, smoke, integration, or host process was launched or
142
- modified for this review.
@@ -1,163 +0,0 @@
1
- # P0-A: published runtime and startup-isolation research
2
-
3
- Research date: 2026-10-13. This is source research only; no OpenCode executable
4
- was run, and no shared service, user configuration, or credentials were accessed.
5
- The separately identified source checkout was inspected read-only.
6
-
7
- ## Result
8
-
9
- **The published artifact/source pin is now established; runtime qualification
10
- remains unqualified.** The extracted Windows executables are real PE
11
- byte PE executable, not a stub: it has `MZ`/`PE` headers and contains compiled
12
- V2 CLI/server code, the `serve` command, `--standalone`, `OPENCODE_CONFIG_DIR`,
13
- the embedded version `2.0.28`, and the compiled source path logic described
14
- below. The npm package description is therefore not evidence that the binary
15
- cannot run. Registry metadata has no `gitHead`, but that is not required here:
16
- the official Git tag `v2.0.28` resolves through GitHub's ref API to commit
17
- `026b106bc5d41b679ffc413264229c340871e4d3`, and the tag source has the exact
18
- host/plugin/AI versions and Effect pin recorded below.
19
-
20
- The source snapshot does materially improve isolation. It exposes enough
21
- documented-by-source environment controls to construct a sterile launch, but
22
- those controls still require a real-host proof before use. The exact source
23
- commit and binary checksum remain recorded as candidate evidence, not as a
24
- qualification claim.
25
-
26
- ## Published artifact evidence
27
-
28
- Registry metadata was queried from `https://registry.npmjs.org/` and package
29
- archives were downloaded only into an ACL-restricted directory under
30
- `C:\Users\jfomh\AppData\Local\Temp\opencode\p0-runtime-20261013`. The
31
- Windows archive was inspected, not executed.
32
-
33
- | Role | Published version | Evidence / integrity |
34
- |---|---:|---|
35
- | Host bootstrap `@opencode/cli` | 2.0.28 | tarball `https://registry.npmjs.org/@opencode/cli/-/cli-2.0.28.tgz`; SHA-256 `088D38127C529232164381213718F91652450BAF3F65CFD380A4E84FD1A79245`; npm integrity `sha512-nKStJJVG6to58gSPv5AuzNQZhYtou6b1jfI5EIckshWT9w8teesSLgP3gH/iSA/madUhvkNqsFjUFuD3NRne9Q==` |
36
- | Windows x64 host binary | 2.0.28 | `@opencode/cli-windows-x64`; tarball SHA-256 `A5A212125E497726A95D6F9C3CDCFE6F1FA35CA61F3F263E6575D557747FEAE2`; SHA-512 `D7FA7230C8BAD511EC44E3C96CB52C3BC53684850CC33350BD81FF5D4945E488315EF21577DA0D23C931999703EF6AED6B7B35EDDA0A00407E30A76C9E6785C5`; npm integrity `sha512-1/pyMMi61RHsROPJbLUsO8U2hIUMwzNQvY/XUlF5IgxXvIVd9oNI8kxmZcD72rta3s17doKAEB+MKdsnmeFxQ==` |
37
- | Windows ARM64 host binary | 2.0.28 | `@opencode/cli-windows-arm64`; tarball SHA-256 `5DD7944A1EE879E7D9794263D945386B706F404F3EEE885CB599E81ABC5E59EF`; executable member SHA-256 `65F0E7ACCC7D5FD77CA7380FDE87E3289D0CF27687851E5240BBDE6CAA7BDB66`; npm integrity `sha512-9HXcqjQa2ghY6tgqjRqaVCZfBuP10b55ffo2QdgHESzsItTwL0yKWaorzj91q96dqmQJ6MOvpl31PdhJnhuJ4g==` |
38
- | Plugin API | 2.0.28 | `@opencode/plugin`; npm shasum `2afadd0ef92eb7cbd7ecad515cadd9e39522d56b`; npm integrity `sha512-YqcpULwPXLX70ICm/EPeFacnWsYtBmaCwVgO67HCuyFDiBNnQtY8DMhHzBgUtAAI0djYLGPbwEHvHp0ZTyrnHw==` |
39
- | Host Effect runtime dependency | 4.0.1 required by plugin | `@opencode/plugin@2.0.28` declares exact dependency `effect: 4.0.1`; registry latest independently is 4.0.4, which must **not** replace the host copy without qualification. |
40
- | AI/provider runtime | 2.0.28 | `@opencode/ai@2.0.28`; plugin declares this exact host family. |
41
- | Candidate provider | 2.0.28 | `@opencode/ai/providers/openai-compatible`; V2 provider docs identify this package and its OpenAI-compatible HTTP protocol. The exact model/endpoint is still a fixture decision, not a qualified provider. |
42
-
43
- The platform archive contains `package/bin/opencode.exe` and its package JSON
44
- points to the OpenCode repository. Static inspection of the executable found
45
- the exact source-derived strings `nH="2.0.28"`, `ju="latest"`, the command
46
- descriptions, and minified implementations of `Global.layerWith`, XDG roots,
47
- `OPENCODE_CONFIG_DIR`, `OPENCODE_CONFIG_CONTENT`, and the standalone server.
48
- This establishes that the “bootstrap” label is stale for this archive. It does
49
- not establish a reproducible-build proof; the candidate relies on the official
50
- same-version tag association rather than claiming byte-for-byte source
51
- reproduction.
52
-
53
- Read-only source comparison:
54
-
55
- * The official Git ref `refs/tags/v2.0.28` resolves to
56
- `026b106bc5d41b679ffc413264229c340871e4d3`. Tag source files report root,
57
- `packages/cli`, `packages/plugin`, and `packages/ai` version `2.0.28`; the
58
- root catalog pins `effect` and Effect platform packages to `4.0.1`.
59
- * The local checkout is the separate research snapshot
60
- `e9def88a7790efc6abace15b67f3625ff4d66237`; it was useful for initial
61
- inspection only and is not the release source pin.
62
- * The executable’s embedded version/channel strings and minified code match
63
- those source files. This is a **candidate source mapping**, not a signed
64
- build attestation. This source association meets the repository plan's
65
- published-release/source-commit pin requirement. It does **not** claim a
66
- reproducible-build equality between the tag and the npm archive; that is a
67
- stronger supply-chain property and is outside this P0 pin gate.
68
-
69
- The V2 docs do establish the intended native interfaces: `@opencode/plugin/effect`
70
- with `Plugin.define({ id, effect })`, host-provided `@opencode/plugin` and
71
- `effect`, and externalized public Effect subpaths. They explicitly require
72
- testing against the installed package and compatible host release.
73
-
74
- ## Official distribution path
75
-
76
- The official V2 landing page documents `npm install -g @opencode/cli` and
77
- direct Windows downloads from the update service, including
78
- `https://opencode.ai/update/api/latest/cli/opencode/opencode-windows-x64.zip`
79
- and its baseline variant. It explicitly says Windows package managers are not
80
- supported. The pinned source's updater constructs the same endpoint as
81
- `https://opencode.ai/update/api/{channel}/{artifact}/{distribution}`; its
82
- publish workflow builds platform binaries and signs Windows binaries. This
83
- confirms that `@opencode/cli@2.0.28` and the update-service binary are official
84
- V2 distribution paths, rather than an arbitrary guessed executable.
85
-
86
- The update endpoint does not publish a reproducible-build attestation in the
87
- metadata inspected. That does not defeat the source pin: the published npm
88
- version, official same-version Git tag, exact archive integrity, and private
89
- binary checksum are recorded as the candidate identity. Reproducibility is
90
- not asserted.
91
-
92
- ## Verified isolation facts and implications
93
-
94
- Evidence: [V2 CLI](https://opencode.ai/v2/docs/cli), [commands](https://opencode.ai/v2/docs/cli/commands),
95
- [config](https://opencode.ai/v2/docs/config), and
96
- [troubleshooting](https://opencode.ai/v2/docs/troubleshooting).
97
-
98
- * The default client discovers/starts one per-user shared background service.
99
- `--standalone` is the documented private-server mode; `--server URL` targets
100
- an explicit server. `serve` starts a foreground API server.
101
- * Config is loaded from Windows `%ProgramData%\opencode`, the global config
102
- root, and current directory through filesystem ancestors. Plugin/config
103
- entries accumulate by precedence; ancestor config is not excluded by
104
- choosing a private working directory.
105
- * Documented persistent locations include service registration
106
- `~/.local/state/opencode/service.json`, service config
107
- `~/.config/opencode/service.json`, database
108
- `~/.local/share/opencode/opencode.db`, and logs
109
- `~/.local/share/opencode/log/opencode.log`. `OPENCODE_DB` changes only the
110
- database location.
111
- * Source inspection adds these exact controls for a sterile process:
112
- `XDG_DATA_HOME`, `XDG_CACHE_HOME`, `XDG_CONFIG_HOME`, and `XDG_STATE_HOME`
113
- root the four persistent OpenCode trees; `TEMP`/`TMP` control the native
114
- temporary root; `ProgramData` controls Windows system-config discovery;
115
- `OPENCODE_CONFIG_DIR` overrides the effective global config directory;
116
- `OPENCODE_CONFIG_PROJECT_DISABLE=1` disables the ancestor/project walk;
117
- `OPENCODE_CONFIG_CONTENT` supplies virtual config; and `OPENCODE_DB` or
118
- `:memory:` selects the database. These names are source-verified in
119
- `packages/util/src/global-roots.ts`, `packages/util/src/global.ts`,
120
- `packages/core/src/config/discovery.ts`, `packages/cli/src/server-process.ts`,
121
- and `packages/cli/src/database-path.ts` at the pinned commit. The harness
122
- must set all of them in an allowlisted environment and use a fixture-owned
123
- cwd; setting only `OPENCODE_CONFIG_DIR` is insufficient because system config
124
- is otherwise still read.
125
- * `service set disabled true` disables the shared service but edits the user's
126
- service configuration and stops the shared service. It is not an acceptable
127
- harness isolation mechanism.
128
- * `snapshots: false` and `warming: false` are configuration controls, not
129
- relocation or service-isolation controls.
130
-
131
- `--standalone` source starts a child `serve --stdio --port 0` with a random
132
- password and no service-registration path, so it is preferable to the shared
133
- service. Its child inherits the parent environment (`extendEnv: true`), making
134
- the allowlist mandatory. A foreground `serve` is also source-supported. The
135
- remaining isolation work is executable proof: verify all filesystem reads and
136
- network endpoints with synthetic markers, and verify no service discovery or
137
- shared process connection. Do not run the user's installed CLI or use
138
- `service` commands.
139
-
140
- ## Required next evidence
141
-
142
- 1. Treat the exact candidate pins in
143
- [`../tests/smoke/runtime-candidate.json`](../../tests/smoke/runtime-candidate.json)
144
- as the selected P0 artifact identity. Record additional platform archives
145
- and exact protocol/model routes as they are selected; do not substitute an
146
- unpinned channel artifact.
147
- 2. In a fresh ACL-protected fixture, set the source-verified XDG/ProgramData/
148
- config/project/database/temp controls and prove startup isolation before any plugin
149
- or synthetic provider runs: no shared service connection, no inherited API
150
- keys, no user/system/ancestor config, and all DB/state/cache/log/temp files
151
- under owned paths. Probe using only synthetic values and safe path markers.
152
- 3. Then run the repository's planned `P0-BOOT` identity check. Until those
153
- proofs exist, `runtime-candidate.json` remains explicitly `unqualified` and
154
- no host smoke/qualification result may be labeled passed.
155
-
156
- ## Parent verification correction
157
-
158
- Direct `Get-FileHash -Algorithm SHA256` on the extracted `package/bin/opencode.exe`
159
- returned `9C59FCD39AA9C611AEDA04711BAB9D668CE3145FA5CDE9868F69C31ADCCFD3E3`.
160
- The original candidate accidentally used the **tarball** SHA-256 for the executable.
161
- `runtime-candidate.json` now distinguishes both hashes and records the archive
162
- member instead of a machine-specific executable path. A future runner must verify
163
- the extracted binary hash, not compare it against the compressed archive hash.