ADR-1244 Phase 4 (D5 trust + D6 upgrade/compat). capability-trust.cjs (disclosure/consent, strict_known_registries, engines+compatVersions, reserved namespace) + capability-lifecycle.cjs (install/upgrade/remove/reconcile; ledger-as-commit-point _pending intent; atomic stage-then-swap; surgical marker-isolated shared-edit strip; owner-token lock) + capability-source promote/skipEnginesGate seams + loader pending-skip + config keys. No sandbox re-derived (consent+integrity+reversibility). 6 Codex adversarial rounds + /security-review (no HIGH) + /code-review; gsd-test green both platforms; CI green. Phase 5 (#1434) wires the CLI dispatch. Closes #1433. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
90 lines
5.5 KiB
Markdown
90 lines
5.5 KiB
Markdown
# The capability trust model
|
||
|
||
> **Status:** Implemented in ADR-1244 Phase 4 (`gsd-core/bin/lib/capability-trust.cjs`,
|
||
> `capability-lifecycle.cjs`). This document is the *honest* explanation of why GSD trusts
|
||
> third-party capabilities the way it does — including what it does **not** protect against.
|
||
|
||
GSD capabilities can ship the same executable artifacts GSD itself ships — hook scripts,
|
||
command modules, and (for third parties) MCP server declarations. That is full first-party
|
||
parity, chosen deliberately (epic #1244, alternative 2). Parity means a third-party
|
||
capability is, in the limit, **arbitrary code you have invited into your agent runtime**.
|
||
This page explains the barrier GSD puts in front of that invitation, and why the barrier is
|
||
*consent + integrity + reversibility* rather than a sandbox.
|
||
|
||
## Why there is no sandbox (Ask #1)
|
||
|
||
ADR-857 D8 originally earned "no sandbox" on a *declarative-only* premise: capabilities were
|
||
data, not code. This feature reverses that premise, so the justification must be re-derived on
|
||
grounds that survive third-party **code execution**. It is:
|
||
|
||
**GSD has no process boundary to interpose.** A capability's executable surfaces all run inside
|
||
the host agent runtime's own trust context:
|
||
|
||
- **hook scripts** run as the runtime's configured hook commands (your shell, your
|
||
permissions);
|
||
- **command modules** are `require()`'d directly into the GSD CLI's own Node process;
|
||
- **MCP servers** are spawned by the host runtime, not by GSD.
|
||
|
||
GSD never forks a sandboxed child to run any of this. It *could* not meaningfully do so: the
|
||
runtime loads hooks and MCP servers itself, and a command module is in-process by definition.
|
||
A `sandboxTier` enum on capabilities that GSD does not actually enforce would be security
|
||
theater — worse than nothing, because it implies a boundary that isn't there.
|
||
|
||
The runtime descriptor's existing `sandboxTier` axis (`none` | `codex-agent-sandbox`,
|
||
`runtime-config-adapter-registry.cts`) is the **runtime's own** sandbox over everything it
|
||
executes — capability code included. That sandbox keeps applying. But it belongs to the
|
||
runtime (e.g. Codex's `workspace-write`), not to GSD, and GSD does not extend or simulate it.
|
||
|
||
So the barrier GSD owns is the same posture the Obsidian and VS Code marketplaces settled on
|
||
after their own supply-chain incidents:
|
||
|
||
1. **No execution at install.** Staging is copy-only. `npm` sources use `--ignore-scripts`;
|
||
there is no `postinstall`. Installing a capability never runs its code.
|
||
2. **Integrity before extraction.** Tarball sources verify a `sha512` digest over the fetched
|
||
bytes *before* anything is written to the install root; a mismatch aborts.
|
||
3. **Disclosure + explicit consent.** Every executable surface a capability declares is
|
||
disclosed at install, and installing one that ships *any* executable surface requires
|
||
explicit consent. Declining aborts cleanly, writing nothing.
|
||
4. **Namespace reservation.** Third parties cannot claim the `gsd-`, `gsd-core-`, or
|
||
`anthropic-` id prefixes, so a hostile capability cannot impersonate a first-party one.
|
||
5. **Reversibility.** The ledger records exactly what each install wrote; `remove` deletes
|
||
exactly those files and surgically strips exactly the shared-config entries the capability
|
||
added, touching nothing the user owns.
|
||
|
||
Consent is meaningful precisely because (1)–(2) guarantee nothing has run *yet* when you are
|
||
asked, and (4)–(5) guarantee you can fully undo it. That is the trade GSD makes instead of a
|
||
sandbox it cannot honestly provide.
|
||
|
||
## `strictKnownRegistries` default (Ask #4a)
|
||
|
||
`capabilities.strict_known_registries` gates **install itself**, not merely auto-update.
|
||
|
||
| Value | Meaning |
|
||
| --- | --- |
|
||
| unset / `null` *(default)* | External installs (git / npm / tarball / registry) are allowed, each still passing the consent + integrity gate. Local filesystem installs always allowed. |
|
||
| `[]` *(explicit empty array)* | **Blocks all external installs** — managed/enterprise lockdown. Local installs only. |
|
||
| `["github.com", "registry.example.com"]` | Allowlist — only sources whose host matches an entry (exact host or a subdomain of it) are permitted. |
|
||
|
||
The default is **permissive-with-consent**, not Obsidian-style restricted-by-default. The epic
|
||
deliberately chose decentralized URL/git distribution over a curated-registry gate (alternative
|
||
3), so the consent prompt is the default barrier and full lockdown is one config key away. The
|
||
allowlist match is **host-based**, not substring: `github.com` does not match
|
||
`evilgithub.com`.
|
||
|
||
## The npm transitive-dependency boundary (Ask #4b)
|
||
|
||
`integrity` (sha512) pins **only the top-level fetched artifact** — the tarball, or the output
|
||
of `npm pack`. It does **not** pin the resolved dependency graph.
|
||
|
||
`--ignore-scripts` and copy-only staging stop *install-time* execution (no `postinstall`). But
|
||
when a capability's command module is later `require()`'d, Node resolves and executes that
|
||
module's **transitive dependency tree**, and sha512 says nothing about those packages — they
|
||
can be mutable or compromised independently of the pinned top artifact. This is exactly the
|
||
Wiz / VS Code supply-chain lesson.
|
||
|
||
For the `npm` source kind, therefore: a green integrity check means "the package tarball is the
|
||
one you pinned," **not** "every line of code that will run is the code you reviewed." Authors
|
||
who want a stronger guarantee should vendor their dependencies into the capability or ship a
|
||
lockfile; the consent prompt always discloses when a capability ships command modules, which is
|
||
the surface through which transitive code reaches your process.
|