Skip to content

Commit 398b53e

Browse files
snapsynapseclaude
andcommitted
Add 0.7.x verifier enforcement implementation plan
docs/0.7-verifier-enforcement-plan.md sequences the reference and hosted verifier enforcement of the bounded-execution findings that 0.7.0 defines but does not yet emit. Scoped by the adversarial pass's decidability limits: block only the decidable named-local-script case; treat the ambiguous middle (make targets, -m modules, npm run) as documented under-enforcement; exec-sha256 unverified in local mode and verified via the github.com repository-file channel in hosted mode. Roadmap and finding-ids point to it. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
1 parent c520519 commit 398b53e

3 files changed

Lines changed: 229 additions & 4 deletions

File tree

Lines changed: 224 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,224 @@
1+
# 0.7.x Verifier Enforcement Implementation Plan (bounded execution)
2+
3+
Status: committed implementation plan. The bounded-execution rule is normative in
4+
`spec.md` section 12 as of 0.7.0; the reference and hosted verifiers do not yet
5+
emit its findings. This plan sequences that enforcement. Until it lands, the
6+
`action.exec-unbounded`, `action.exec-opaque`, `exec-sha256.mismatch`,
7+
`exec-sha256.transitive-unpinned`, and `exec-sha256.unverified` ids are defined
8+
but not emitted, and bounded-execution compliance is self-asserted.
9+
10+
This plan is non-normative. It does not change the conformance contract; it
11+
describes how to make the reference verifier check what the 0.7.0 spec already
12+
requires.
13+
14+
## Goal
15+
16+
Emit the five bounded-execution findings from the reference verifier
17+
(`scripts/guidecheck_verify.py`) and the hosted verifier (`api/verify.py`,
18+
`scripts/guidecheck_hosted_anchors.py`), scoped by the decidability limits the
19+
adversarial pass established, so that a guide invoking an unbounded in-repo script
20+
fails Level 3 while honest guides and exempt dependency installers stay
21+
conformant.
22+
23+
## Design constraints carried from the adversarial pass
24+
25+
These are the corrections that shaped the 0.7.0 spec text and must shape the
26+
implementation. They are recorded in `threat-register.md` under
27+
"Runtime-indirection and transitive execution".
28+
29+
1. Classify by invocation shape, not program head. `make`, `npm`, `pip`, `cargo`,
30+
and `go` stay in `_PACKAGE_TOOLS` for class detection (they are correctly
31+
`code-executing`), but the exempt-versus-bound decision is separate and keys on
32+
the subcommand and argv shape. `npm install` is exempt; `npm run <script>` is
33+
bound.
34+
2. Block only the decidable case. Reserve blocking `action.exec-unbounded` for a
35+
named local script file invoked without a pin: a path-qualified executable
36+
(`./x`, `../x`, `/abs/x`) or an interpreter with a local script-file argument
37+
(`bash scripts/setup.sh`, `sh ./install`). These are already detected as
38+
`code-executing` by `_segment_runs_code` as of the 0.6.x detection work.
39+
3. Do not block the undecidable middle. `make <target>`, `npm run <script>`,
40+
`pip install .`, `python -m <module>`, and `cargo`/`go build` with an in-repo
41+
build hook are bound per the spec, but the verifier cannot statically tell a
42+
first-party module or a malicious recipe from a benign one. Per spec section 19
43+
(best-effort, allowlist-bounded), the verifier emits an informational finding
44+
for these rather than a false-positive block. This is a deliberate, documented
45+
under-enforcement: the verifier is weaker than the spec on the ambiguous middle,
46+
and human review remains required.
47+
4. The inline carve-out is absolute. An action whose executed instructions are in
48+
the `command` field (an inline `-c`/`-e`/`-E`/`--eval`/`--exec` program, or a
49+
bare-program interpreter such as `awk`/`perl`) is bounded by definition and is
50+
never flagged. The tokenizer is quote-naive, so the trigger is the presence of
51+
an external artifact reference, never content inspection of an inline program.
52+
5. `exec-opaque` is a control only for the exempt class. On an exempt installer it
53+
is `action.exec-opaque` (warning) and fully conformant. On a bound artifact it
54+
is `action.exec-unbounded` (blocking): a bound artifact is always inlinable or
55+
pinnable, so opaque there is a bypass.
56+
6. A pin is worth nothing unverified. `exec-sha256` can only be verified where the
57+
verifier can read the artifact. Local-file mode cannot fetch, so it always
58+
reports `exec-sha256.unverified` (info) and renders the pin as declared, not
59+
satisfied. Hosted mode reads the artifact through the github.com repository-file
60+
channel and recomputes.
61+
62+
## Building blocks already in place
63+
64+
Landed under the 0.6.x line (commits on `main`):
65+
66+
- `_segment_runs_code` recognizes path-qualified executables, script-extension
67+
program names, interpreters with a path-qualified positional, and container
68+
`build`/`run`/`exec`/`start` (and `compose up|run|build`).
69+
- `_NET_TOOLS` includes DNS clients and `/dev/tcp`; `command.fetch-execute` fires
70+
on `dig TXT | bash` as on `curl | sh`.
71+
- `_command_head`, `_invoked_path_token`, `_is_path_arg`, `_container_runs_code`,
72+
`_command_segments`, `_PACKAGE_TOOLS`, `_INTERPRETERS`, `_CODE_FLAGS`,
73+
`_SCRIPT_EXTENSION`.
74+
- False-positive guards proven: `docker ps`, `docker version`, `sed`, `jq`,
75+
`hostname`, `.venv/bin/python`, `python3 --version`, `git clone`.
76+
77+
## Work items
78+
79+
### Phase 1: bound-versus-exempt classifier
80+
81+
Add `classify_exec_target(command) -> str` returning one of `inline`,
82+
`bound-script`, `exempt-installer`, `ambiguous`, `none`:
83+
84+
- `inline`: any segment head is an interpreter with an inline-code flag
85+
(`_CODE_FLAGS`) or a bare-program interpreter. Bounded; no finding.
86+
- `bound-script`: a path-qualified executable, or an interpreter whose positional
87+
argument is a local script file (matches `_SCRIPT_EXTENSION` or `_is_path_arg`).
88+
Decidable; subject to the pin-or-inline requirement.
89+
- `exempt-installer`: head plus subcommand in a new `_DEPENDENCY_INSTALLERS`
90+
set (`npm ci`, `npm install` with no `run`, `pnpm install`, `yarn install`,
91+
`pip install -r`, `pip install <named-non-local>`, `bundle install`,
92+
`cargo build` with no `build.rs`, `go build` with no invoked `go generate`), plus
93+
committed bootstrap wrappers whose basename is `gradlew`, `mvnw`, `configure`, or
94+
`autogen.sh`.
95+
- `ambiguous`: `make <target>`, `npm run <script>`, `just <recipe>`,
96+
`pip install .`/`-e .`, `python -m <module>`, `go generate`, `docker build`,
97+
and `cargo`/`go build` when an in-repo build hook is present. Not blocked.
98+
- `none`: not code-executing.
99+
100+
Publish `_DEPENDENCY_INSTALLERS` as the spec-level exempt table in
101+
`verifier-conformance.md` so independent verifiers agree, rather than leaving it a
102+
private constant.
103+
104+
### Phase 2: field parsing
105+
106+
Extend the action-block parser to read `exec-sha256` and `exec-opaque`:
107+
108+
- `exec-sha256`: must be 64 lowercase hex characters, else a malformed-field
109+
finding (reuse `action-block.malformed`).
110+
- `exec-opaque`: value must be `acknowledged`; require a `notes` rationale on the
111+
same action; else a malformed-field finding.
112+
113+
### Phase 3: local emission
114+
115+
In `check_actions`/`check_command`, for each code-executing action:
116+
117+
- `bound-script` with no `exec-sha256` and no `exec-opaque` -> `action.exec-unbounded`
118+
(error).
119+
- `exec-opaque` on a `bound-script` -> `action.exec-unbounded` (error).
120+
- `exec-opaque` on an `exempt-installer` -> `action.exec-opaque` (warning).
121+
- `exec-sha256` present -> `exec-sha256.unverified` (info) in local-file mode, and
122+
suppress `action.exec-unbounded` (the pin is declared).
123+
- `ambiguous` -> at most an info finding; never blocking.
124+
- `inline`, `exempt-installer` with no opaque, `none` -> no bounded-execution
125+
finding.
126+
127+
Inlining (spec option a) needs no detection: a guide satisfies it by replacing the
128+
artifact invocation with inline action blocks, so there is simply no bound-script
129+
action to flag. An action that both inlines a copy and re-invokes the artifact
130+
still has the bound-script invocation and is flagged, which is the intended guard.
131+
132+
Remove the "not yet emitted" note from the `finding-ids.md` "Bounded execution"
133+
section and the `verifier-conformance.md` section 19 addition for the ids the
134+
local verifier now emits (`action.exec-unbounded`, `action.exec-opaque`,
135+
`exec-sha256.unverified`).
136+
137+
### Phase 4: hosted exec-sha256 verification
138+
139+
In the hosted path, when an action declares `exec-sha256` and the artifact is
140+
reachable through the github.com repository-file channel
141+
(`scripts/guidecheck_hosted_anchors.py`):
142+
143+
- recompute the artifact SHA-256; on divergence emit `exec-sha256.mismatch`
144+
(error).
145+
- byte-scan the fetched artifact for further in-repo invocations (`source`/`.`,
146+
`bash x.sh`, an interpreter with a script argument, `make`, `npm run`); require
147+
each to be inlined or pinned, else `exec-sha256.transitive-unpinned` (error).
148+
- account for the added fetches against the hosted per-request fetch budget
149+
(currently seven in `api/verify.py`); cap artifact reads per request and prefer
150+
deduplication. Decide whether to raise the budget or bound the number of pinned
151+
artifacts verified per request.
152+
153+
Local-file mode never reaches this phase; it always reports
154+
`exec-sha256.unverified`.
155+
156+
### Phase 5: corpus and example ripple
157+
158+
- Example guides that invoke a named local script become `action.exec-unbounded`.
159+
`examples/` currently has `.venv/bin/python prompter_kit.py ...` in the
160+
PrompterKit example; add `exec-sha256` pins (or restructure to inline) and
161+
regenerate the affected fixtures. Confirm no other tracked guide invokes a named
162+
local script (the self-guide uses `python -m venv` and `sed`/`jq`, which are not
163+
bound-script shapes).
164+
- Add fixtures: valid (a pinned leaf artifact, `exec-opaque` on an exempt
165+
installer), invalid (`action.exec-unbounded` for `bash scripts/setup.sh` and
166+
`./scripts/setup.sh`, `exec-opaque` on a bound artifact, and in hosted-replay
167+
form `exec-sha256.mismatch` and `exec-sha256.transitive-unpinned`).
168+
- Generate each `expected.json` from real verifier output, as the DNS and
169+
local-script fixtures were built.
170+
171+
### Phase 6: Change 3 (separate decision)
172+
173+
Promoting the unambiguous class-understatement warnings
174+
(`action-block.class.code-executing-missing`, `network.command-implies-networked`)
175+
from warning to blocking at Level 3 and above is a separable conformance change
176+
with a large corpus ripple (every under-declaration fixture and any adopter guide
177+
that understates a class). Decide it on its own, with its own fixture pass; it is
178+
not a prerequisite for Phases 1 through 5, because `action.exec-unbounded` keys off
179+
detected code execution, not the declared class.
180+
181+
## Decidability policy, stated plainly
182+
183+
- Block: a named local script file invoked without a pin; `exec-opaque` on a bound
184+
artifact.
185+
- Warn: `exec-opaque` on an exempt installer.
186+
- Info: `exec-sha256.unverified`; the ambiguous middle.
187+
- Never flag: inline programs; exempt installers.
188+
189+
The verifier under-enforces the spec on the ambiguous middle by design. A guide
190+
that hides a payload in a `make` recipe or a first-party `-m` module is
191+
non-conformant under section 12 but is not mechanically blocked, because the
192+
verifier cannot decide that shape without executing or fully reading the project.
193+
This is the section 19 best-effort posture, and it is why conformance is not
194+
safety and human review remains required.
195+
196+
## Risks
197+
198+
- Ambiguous-middle under-enforcement (verifier weaker than the spec). Documented
199+
above and in `threat-register.md`; not a silent gap.
200+
- Example ripple and notional pins in fixtures (pins the local verifier reports as
201+
unverified anyway).
202+
- Hosted fetch budget pressure from artifact reads.
203+
- Quote-naive tokenizer: never content-inspect an inline program to decide the
204+
carve-out.
205+
206+
## Test strategy
207+
208+
- Unit tests in `scripts/test_parser_edge_cases.py` for `classify_exec_target`
209+
across every shape in Phase 1 plus the false-positive guards.
210+
- One fixture per emitted finding, `expected.json` generated from real output.
211+
- `make test` green: `eval`, `verify-fixtures`, `validate-contracts`,
212+
`check-guide-artifacts`, and the finding-id contract check
213+
(`check_reference_verifier.py`) which requires every emitted id to be documented
214+
in `finding-ids.md`.
215+
216+
## Suggested release sequencing
217+
218+
- 0.7.1: Phases 1 through 3 and 5 (local enforcement: blocking
219+
`action.exec-unbounded` for named scripts, `exec-opaque` handling,
220+
`exec-sha256.unverified`, fixtures, example pins). Drop the "not yet emitted"
221+
caveat for the locally emitted ids.
222+
- 0.7.2: Phase 4 (hosted `exec-sha256` verification and transitive-closure
223+
scanning).
224+
- Change 3: separate decision, separate release.

finding-ids.md

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -120,9 +120,9 @@ files.
120120

121121
Defined for guide-profile 0.7.0 (spec section 12, "Code-executing actions and
122122
the review boundary"). Reference-verifier and hosted-verifier support arrives in
123-
a subsequent 0.7.x release; until then these ids are part of the contract but are
124-
not yet emitted, and a guide's compliance with the bounded-execution rule is
125-
self-asserted.
123+
a subsequent 0.7.x release, sequenced in `docs/0.7-verifier-enforcement-plan.md`;
124+
until then these ids are part of the contract but are not yet emitted, and a
125+
guide's compliance with the bounded-execution rule is self-asserted.
126126

127127
| ID | Severity | Trigger |
128128
|---|---|---|

roadmap.md

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -138,7 +138,8 @@ five finding ids in `finding-ids.md` and `verifier-conformance.md`. The profile
138138
bumped to 0.7.0 across all surfaces. Reference-verifier and hosted-verifier
139139
enforcement of `action.exec-unbounded`, `exec-sha256` verification, and
140140
transitive-closure scanning is the remaining work and is deferred to a 0.7.x
141-
release; until then bounded-execution compliance is self-asserted. Tagging v0.7.0,
141+
release, sequenced in `docs/0.7-verifier-enforcement-plan.md`; until then
142+
bounded-execution compliance is self-asserted. Tagging v0.7.0,
142143
republishing the self-guide DNS TXT hash, and deploying the site are out-of-band
143144
release steps. The bound-versus-exempt axis and the adversarial hardening behind
144145
it are summarized in the covered/landed/candidate notes in this section and in

0 commit comments

Comments
 (0)