@attested-intelligence/aga-mcp-server 3.3.3 → 3.6.0
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.
- package/CHANGELOG.md +204 -0
- package/DEPLOYMENT.md +36 -5
- package/README.md +25 -12
- package/THREAT_BOUNDARY.md +3 -3
- package/dist/core/portal.d.ts.map +1 -1
- package/dist/core/portal.js +17 -2
- package/dist/core/portal.js.map +1 -1
- package/dist/proxy/control.d.ts +6 -0
- package/dist/proxy/control.d.ts.map +1 -1
- package/dist/proxy/control.js +25 -1
- package/dist/proxy/control.js.map +1 -1
- package/dist/proxy/index.d.ts.map +1 -1
- package/dist/proxy/index.js +46 -10
- package/dist/proxy/index.js.map +1 -1
- package/dist/proxy/profiles.d.ts +16 -0
- package/dist/proxy/profiles.d.ts.map +1 -1
- package/dist/proxy/profiles.js +34 -0
- package/dist/proxy/profiles.js.map +1 -1
- package/dist/proxy/server.d.ts +12 -0
- package/dist/proxy/server.d.ts.map +1 -1
- package/dist/proxy/server.js +15 -2
- package/dist/proxy/server.js.map +1 -1
- package/dist/sep/gateway-key.d.ts +53 -0
- package/dist/sep/gateway-key.d.ts.map +1 -0
- package/dist/sep/gateway-key.js +79 -0
- package/dist/sep/gateway-key.js.map +1 -0
- package/dist/sep/index.d.ts +1 -0
- package/dist/sep/index.d.ts.map +1 -1
- package/dist/sep/index.js +1 -0
- package/dist/sep/index.js.map +1 -1
- package/dist/sep/verify.d.ts.map +1 -1
- package/dist/sep/verify.js +35 -1
- package/dist/sep/verify.js.map +1 -1
- package/dist/server.d.ts.map +1 -1
- package/dist/server.js +19 -26
- package/dist/server.js.map +1 -1
- package/package.json +7 -3
package/CHANGELOG.md
CHANGED
|
@@ -2,6 +2,210 @@
|
|
|
2
2
|
|
|
3
3
|
All notable changes to `@attested-intelligence/aga-mcp-server` are recorded here, newest first. This package follows [Semantic Versioning](https://semver.org). The signed receipt and evidence-bundle wire format is the canonical SEP profile; any format-affecting change is called out explicitly.
|
|
4
4
|
|
|
5
|
+
## 3.6.0 — 2026-09-18
|
|
6
|
+
|
|
7
|
+
### `aga-proxy` now honours `AGA_GATEWAY_KEY` / `AGA_GATEWAY_KEY_FILE`
|
|
8
|
+
|
|
9
|
+
This package ships two binaries and only one of them read the operator's key contract. `aga-mcp-server`
|
|
10
|
+
honoured both variables; `GovernanceProxy` called `generateSigner()` unconditionally in its
|
|
11
|
+
constructor, read neither variable, and printed no warning. An operator who set `AGA_GATEWAY_KEY`
|
|
12
|
+
correctly and started the proxy was **silently ignored**: no effect, no notice, and evidence bundles
|
|
13
|
+
whose issuing key rotated on every restart. That is worse than an unsupported feature — silence
|
|
14
|
+
defeats a correct configuration. It is checkable in the published 3.5.0 tarball, where `dist/proxy/`
|
|
15
|
+
contains zero references to either variable while `dist/server.js` contains seven.
|
|
16
|
+
|
|
17
|
+
**What changes for you:** if you already set `AGA_GATEWAY_KEY` or `AGA_GATEWAY_KEY_FILE` and ran
|
|
18
|
+
`aga-proxy`, that variable now actually takes effect, so the proxy's `gateway_public_key` stops
|
|
19
|
+
rotating and becomes the key your seed derives. Nothing breaks: a proxy with neither variable set
|
|
20
|
+
behaves exactly as before except that it now says so. If you *want* the old throwaway-key behaviour
|
|
21
|
+
with a variable set, pass the new `--ephemeral` flag.
|
|
22
|
+
|
|
23
|
+
- **One resolver, both binaries** (`src/sep/gateway-key.ts`). Order: `AGA_GATEWAY_KEY`, then
|
|
24
|
+
`AGA_GATEWAY_KEY_FILE`, then an ephemeral key. An invalid or unreadable key **warns and falls
|
|
25
|
+
back** rather than exiting — deliberately matching what `aga-mcp-server` has always done, because
|
|
26
|
+
refusing to start would take a governed boundary offline over a key that only affects whether
|
|
27
|
+
provenance is *pinnable*. Integrity, chaining and the deny path do not depend on which key signs.
|
|
28
|
+
A second variable name for the proxy was rejected: one installable must not carry two key
|
|
29
|
+
contracts.
|
|
30
|
+
- **The proxy prints its active PUBLIC key at startup**, with its source:
|
|
31
|
+
`Signing gateway key <64-hex> (persisted via AGA_GATEWAY_KEY_FILE)`. This is the line that makes an
|
|
32
|
+
honest pin possible. A verifier handed a key lifted out of the bundle it is checking will print
|
|
33
|
+
`provenance verified` and has proved nothing about issuance — the check is circular. A key printed
|
|
34
|
+
before any bundle exists is a key you can obtain **out of band**.
|
|
35
|
+
- **`--ephemeral`** turns the old silent default into a stated choice, on both `start` and `run`.
|
|
36
|
+
- **Never logs key material.** Only the variable name that was tried and the derived public key.
|
|
37
|
+
- `aga-mcp-server` is behaviourally unchanged. It passes its portal keypair's secret as the fallback
|
|
38
|
+
seed, so its unconfigured case stays byte-identical to previous releases; extracting the resolver
|
|
39
|
+
must not silently rotate either binary's key, and a test now holds that.
|
|
40
|
+
|
|
41
|
+
**Not a turnkey guarantee, stated plainly.** Persisting the key makes provenance *pinnable*; it does
|
|
42
|
+
not make it *pinned*. The default on both binaries is still an ephemeral key, an unconfigured
|
|
43
|
+
deployment carries the old risk in full, and nothing in the package detects a deployment that skipped
|
|
44
|
+
the configuration. `THREAT_BOUNDARY.md` item 4 now says exactly that.
|
|
45
|
+
|
|
46
|
+
### Two halves of the above that were written but never shipped
|
|
47
|
+
|
|
48
|
+
`describeKey()` and the `--ephemeral` option both existed in the 3.5.0 tree and **neither was reachable
|
|
49
|
+
from the entry point**: `dist/proxy/index.js` never called `describeKey()`, so the shipped binary
|
|
50
|
+
printed no key line at all, and `new GovernanceProxy({...})` never passed `ephemeral`, so the flag was
|
|
51
|
+
parsed and discarded. Both are now wired, and the integration test below drives the built artifact
|
|
52
|
+
rather than the source, which is what catches this class of defect. A declared flag the entry point
|
|
53
|
+
does not read is not a feature.
|
|
54
|
+
|
|
55
|
+
### Tests
|
|
56
|
+
|
|
57
|
+
- `tests/sep/gateway-key.test.ts` (17): env key, key file, precedence when both are set, an invalid
|
|
58
|
+
value, an unreadable file, malformed file contents, the unconfigured case, `forceEphemeral`,
|
|
59
|
+
`fallbackSeed` identity across all three ephemeral paths, the default warning sink being
|
|
60
|
+
`console.error` and never `console.log`, the log prefix, and that no path emits key material.
|
|
61
|
+
- `tests/integration/proxy-gateway-key.test.ts` (7): drives the **built** `dist/proxy/index.js` as a
|
|
62
|
+
real child process. One key file, two processes, identical public key on the banner and zero
|
|
63
|
+
warnings; the env var and the key file agree; unconfigured rotates between runs and says so;
|
|
64
|
+
`--ephemeral` overrides a configured key; an invalid key warns, falls back, and still announces what
|
|
65
|
+
it signs with; the seed appears in neither stream; the key line is on stdout and the warning on
|
|
66
|
+
stderr.
|
|
67
|
+
- Suite: **404 → 428** tests across 45 → 47 files. SEP conformance 6/6. `check:pack` clean.
|
|
68
|
+
|
|
69
|
+
### Documentation corrected in the same release
|
|
70
|
+
|
|
71
|
+
`DEPLOYMENT.md` §2 and `THREAT_BOUNDARY.md` item 4 ship inside this tarball and described the *old*
|
|
72
|
+
proxy behaviour in the same breath as the new. The §2 comparison table had been updated in one cell
|
|
73
|
+
while the two paragraphs under it still told operators the proxy ignores both variables; `README.md`
|
|
74
|
+
still said so outright. All three are now consistent and **version-scoped**, so a reader running 3.5.0
|
|
75
|
+
or earlier still gets the truth for the version they are running rather than a claim that only holds
|
|
76
|
+
after upgrading. README test counts updated to the recomputed figure.
|
|
77
|
+
|
|
78
|
+
## 3.5.0 — 2026-08-29
|
|
79
|
+
|
|
80
|
+
**Version ruled 2026-08-29.** `3.4.0` was claimed by two different trees ~93 commits apart, so
|
|
81
|
+
publishing either line under that number would permanently burn it for the other. This line ships as
|
|
82
|
+
**3.5.0** and `3.4.0` is abandoned unpublished. Everything below — the 2026-08-28 honesty and safety
|
|
83
|
+
work *and* the 3.4.0 work completed 2026-07-31 — releases together; npm `latest` has been 3.3.3 since
|
|
84
|
+
2026-07-03.
|
|
85
|
+
|
|
86
|
+
### BEHAVIOR CHANGE — TTL expiry now fails closed
|
|
87
|
+
|
|
88
|
+
**If you rely on measurement continuing after an artifact's TTL lapses, this release breaks that.**
|
|
89
|
+
Founder decision D1, ruled 2026-08-29.
|
|
90
|
+
|
|
91
|
+
Previously `Portal.measure()` treated TTL expiry as *graceful degradation*: it moved the portal to
|
|
92
|
+
`SAFE_STATE`, logged the reason, and **kept accepting measurements indefinitely**. An expired artifact
|
|
93
|
+
therefore went on being measured, and the receipt recorded no enforcement because none occurred.
|
|
94
|
+
|
|
95
|
+
Now TTL expiry sets `TERMINATED`, exactly as the sibling revocation branch always has — TTL was the
|
|
96
|
+
odd one out, not the new behavior. Concretely:
|
|
97
|
+
|
|
98
|
+
- The first post-expiry `measure()` returns `ttl_ok: false`, `degraded: true`, and the portal is
|
|
99
|
+
`TERMINATED`.
|
|
100
|
+
- **Any subsequent `measure()` throws `Portal is terminated`.** Re-attestation is the only way back.
|
|
101
|
+
Callers that looped on `measure()` past expiry must handle this.
|
|
102
|
+
- `measure_integrity` now seals `enforcement_action: 'TERMINATE'` on TTL expiry, and that is accurate:
|
|
103
|
+
termination genuinely happens. The invariant across every version of this code is unchanged — the
|
|
104
|
+
receipt says what actually happened. Only the underlying behavior moved.
|
|
105
|
+
- The `TTL_EXPIRED` degradation entry is still written. It is the forensic record of *why* the portal
|
|
106
|
+
terminated, and dropping it would trade one honesty problem for another.
|
|
107
|
+
|
|
108
|
+
**Semver note, stated plainly:** this is a behavioral break and a strict reading argues for a major
|
|
109
|
+
version. It ships as a minor because the previous behavior contradicted the documented intent, and
|
|
110
|
+
because no released consumer depends on post-expiry measurement (npm `latest` has been 3.3.3 since
|
|
111
|
+
2026-07-03). If that reading is wrong for your deployment, pin `3.3.3`.
|
|
112
|
+
|
|
113
|
+
Found while implementing this: `tests/core/fail-closed.test.ts` contained a test named
|
|
114
|
+
**"fail-closed: expired TTL blocks execution"** that asserted `SAFE_STATE` — the state in which
|
|
115
|
+
measurement *continues*. A test whose name claimed the boundary blocked execution had been green while
|
|
116
|
+
proving it did not. It now asserts termination and that a second call throws. Six other assertions
|
|
117
|
+
across four files encoded the old behavior and were updated with it; all were seen to go red before
|
|
118
|
+
going green.
|
|
119
|
+
|
|
120
|
+
Checked and unchanged: no shipped document and no public page makes a TTL enforcement claim, so this
|
|
121
|
+
change corrects no external copy.
|
|
122
|
+
|
|
123
|
+
### Documentation: proxy key-persistence claims corrected
|
|
124
|
+
|
|
125
|
+
`DEPLOYMENT.md` and `THREAT_BOUNDARY.md` ship inside this tarball, and both described key persistence
|
|
126
|
+
that **`aga-proxy` does not implement**. DEPLOYMENT §1 is explicitly about the proxy; §2 then told
|
|
127
|
+
operators to set `AGA_GATEWAY_KEY` / `AGA_GATEWAY_KEY_FILE`, said the ephemeral fallback "warns on
|
|
128
|
+
stderr", and pointed at `get_server_info`. For the proxy none of that is true — an operator following
|
|
129
|
+
it pinned a key that rotates on the next restart.
|
|
130
|
+
|
|
131
|
+
- `GovernanceProxy` calls `generateSigner()` unconditionally in its constructor: no key CLI option, no
|
|
132
|
+
environment variable, no signer through `ProxyServerOptions`. Two runs with an identical
|
|
133
|
+
`AGA_GATEWAY_KEY` produce different gateway public keys (measured). `dist/server.js` carries the
|
|
134
|
+
`EPHEMERAL gateway signing key` stderr warning; `dist/proxy/` has none.
|
|
135
|
+
- `THREAT_BOUNDARY.md` §3.4 previously read **"Key persistence — mitigated in 3.0"** while §3.1–3.3 are
|
|
136
|
+
each prefixed "Proxy —" and §3.4 was not, so it scanned as covering the package. A residual-risk
|
|
137
|
+
register must not record an unmitigated risk as mitigated. It now states the asymmetry.
|
|
138
|
+
- Both files gained an entry-point comparison table, and record that a verifier handed a key taken from
|
|
139
|
+
the bundle under test will still print `provenance verified` — that check is **circular**, and only a
|
|
140
|
+
key obtained beforehand proves issuance.
|
|
141
|
+
|
|
142
|
+
**KNOWN LIMITATION, unchanged in this release:** `aga-proxy` still has no way to persist its signing
|
|
143
|
+
key, so proxy-issued bundles are integrity-verifiable but **not provenance-pinnable across restarts**.
|
|
144
|
+
`aga-mcp-server` (the stdio server) is unaffected and persists normally. Closing the proxy gap is
|
|
145
|
+
runtime work that is deliberately out of scope here.
|
|
146
|
+
|
|
147
|
+
### Honesty + safety fixes (2026-08-28)
|
|
148
|
+
|
|
149
|
+
- **Security: two HIGH-severity production advisories cleared, both newly in range.** `npm audit
|
|
150
|
+
--omit=dev --audit-level=high` was **failing** on this branch:
|
|
151
|
+
- `fast-uri` — GHSA-v2hh-gcrm-f6hx (host confusion via backslash authority delimiter, CVSS 7.5).
|
|
152
|
+
**The 3.4.0 entry below is not wrong about what it did** — it refreshed `fast-uri` to 3.1.4, which
|
|
153
|
+
was clean at the time. The advisory range has since **widened to `<3.1.5`**, so the pinned version
|
|
154
|
+
became vulnerable without anything in this repo changing. Now overridden to `^3.1.5` (resolves 3.1.6).
|
|
155
|
+
- `ip-address` — GHSA-mwp4-54f8-5fhr (Address4 decodes leading-zero octets as decimal while
|
|
156
|
+
resolvers decode them as octal; SSRF / trust-boundary bypass). Overridden to `>=10.3.1`
|
|
157
|
+
(resolves 10.5.0). Reached via `@modelcontextprotocol/sdk` → `express-rate-limit`.
|
|
158
|
+
- `hono` — the existing `>=4.12.25` override no longer covered a moderate CORS ReDoS whose range
|
|
159
|
+
extends to `<4.12.34`; bumped accordingly (resolves 4.13.5).
|
|
160
|
+
|
|
161
|
+
Production dependencies are once again **clean at all severities** (`npm audit --omit=dev`:
|
|
162
|
+
found 0 vulnerabilities), which is what the 3.4.0 entry claims — that claim had silently gone false.
|
|
163
|
+
**This is a standing hazard worth naming: a dependency claim is only true as of the day it was
|
|
164
|
+
measured.** Advisory ranges widen over unchanged code, so a "clean" statement in a changelog decays
|
|
165
|
+
on its own. Re-run the audit immediately before publishing, not once at RC time.
|
|
166
|
+
Verified after the change: suite 404/404, `npm run build` clean, cross-stack 61/61.
|
|
167
|
+
|
|
168
|
+
> Resolved 2026-08-29: this line ships as **3.5.0**; `3.4.0` is abandoned unpublished so the number
|
|
169
|
+
> stays free for the other tree. See the release heading above.
|
|
170
|
+
|
|
171
|
+
- **TTL expiry no longer signs an enforcement that never happened.** `measure_integrity` sealed
|
|
172
|
+
`enforcement_action: "TERMINATE"` into a signed receipt on TTL expiry and described the branch as
|
|
173
|
+
"fail-closed termination". Nothing terminates: the portal degrades to `SAFE_STATE` and keeps
|
|
174
|
+
accepting measurements, and `portal.enforce()` is never called on that path — nor *can* it be, since
|
|
175
|
+
it throws unless the state is `DRIFT_DETECTED`. Post-expiry calls kept succeeding and kept minting
|
|
176
|
+
fresh receipts, each asserting a termination that did not occur. The receipt now records what
|
|
177
|
+
actually happened. The sibling revocation branch was already honest and is unchanged.
|
|
178
|
+
**Not changed here:** whether TTL expiry *should* hard-terminate and force re-attestation. That is an
|
|
179
|
+
open product decision; until it is ruled, the record must not claim a behavior the code lacks.
|
|
180
|
+
- **`aga-proxy export --output` no longer destroys an existing file.** Pointing `--output` at an
|
|
181
|
+
existing path silently truncated it and exited 0 reporting success — an ordinary path, no symlink,
|
|
182
|
+
no attacker, on the very artifact a verifier consumes. Export is now exclusive-create by default;
|
|
183
|
+
replacing a file requires an explicit `--force`.
|
|
184
|
+
- **A malformed `--pubkey` is now a hard error (exit 2) instead of a silent downgrade.** A truncated or
|
|
185
|
+
mistyped key previously produced `VERIFIED (integrity only)` at exit 0, so an operator who intended
|
|
186
|
+
to pin provenance got a green result. The reference verifier has always guarded this; the guard had
|
|
187
|
+
never propagated to the CLI that becomes the published package.
|
|
188
|
+
- **The cross-stack release gate now exercises the pinned path.** The corpus had no wrong-pin case, and
|
|
189
|
+
the harness collapses each stack to VERIFIED/FAILED without reading `issuerVerified`, so a verifier
|
|
190
|
+
that silently discarded the pin still read VERIFIED and the gate stayed green. A valid-but-wrong pin
|
|
191
|
+
control was added; all six verifiers agree FAILED across 61 cases.
|
|
192
|
+
Recorded, not fixed: on a 64-hex pin that is **not a valid curve point**, the stacks split 1-vs-5 on
|
|
193
|
+
whether that means "no pin" or "a pin that cannot match". Needs a spec ruling on malformed-pin
|
|
194
|
+
semantics before "six verifiers agree" is stated without qualification.
|
|
195
|
+
|
|
196
|
+
### Included from the abandoned 3.4.0 line (work completed 2026-07-31, never published)
|
|
197
|
+
|
|
198
|
+
> **Not published.** This header previously read as a dated, shipped release. It is not on npm and has
|
|
199
|
+
> not been since the work was completed. Nothing below is available to a consumer running
|
|
200
|
+
> `npm install @attested-intelligence/aga-mcp-server`, which still resolves 3.3.3.
|
|
201
|
+
|
|
202
|
+
- **Verification now fail-closed rejects integers outside ±2^53 in receipt/checkpoint numeric fields — bundles previously VERIFIED may now FAIL; this closes the cross-language verdict split.** JavaScript loses integer precision beyond `Number.MAX_SAFE_INTEGER`, so a bundle carrying e.g. `leaf_count > 2^53` could VERIFY in the JS stack on bytes the Go and Python verifiers read as a different number. Every stack now rejects the same out-of-range bundles at the same floor.
|
|
203
|
+
- Security: cleared GHSA-frvp-7c67-39w9 (`@hono/node-server` < 2.0.5, Windows encoded-backslash path traversal in `serve-static`, moderate): `@modelcontextprotocol/sdk` bumped to `^1.30.0` (resolves `@hono/node-server` 2.0.12) and an `overrides` pin holds `@hono/node-server` at `>= 2.0.5`. Production dependencies (`npm audit --omit=dev`) are clean at all severities; `body-parser` (2.3.0) and `fast-uri` (3.1.4) transitives refreshed in the same pass.
|
|
204
|
+
- Verifier CLI entry guard (developed in this repo, shipping alongside in `@attested-intelligence/aga-verify` 2.2.0): the CLI now runs only when the verifier module itself is the executed entry script (resolved-path identity against `import.meta.url`), replacing a substring check that ran the CLI — hijacking stdout and calling `process.exit()` — whenever the entry script's path merely contained `verify`.
|
|
205
|
+
- Regression coverage for the safe-integer floor above (`tests/sep/safe-integer-floor.test.ts`): a consistently re-signed bundle carrying a numeric field isolates the floor from the signature and structural checks, so removing the floor flips the verdict and the suite goes red. The cross-stack corpus also gains the ±2^53 and non-finite raw-literal cases (all six verifiers agree FAILED).
|
|
206
|
+
- Proxy CLI hardening: an unrecognized `--profile` value now exits 2 listing the valid names (`permissive`, `standard`, `restrictive`) instead of silently falling back to `permissive`; starting with an `audit_only` policy prints a loud multi-line stderr banner stating that every call is permitted and recorded and no call is denied in that mode. The default profile is unchanged. `aga-proxy run` (a documented foreground alias of `start`) is fixed — it exited 1 on every invocation from 3.0.0 through 3.3.3 because of a stray-argument delegation bug, and now shares the `start` implementation directly; `policy switch` gained the same own-property profile guard as `start`.
|
|
207
|
+
- MCP registry: `package.json` gains `"mcpName": "io.github.attestedintelligence/aga-mcp-server"` for npm package-ownership validation by the official registry.
|
|
208
|
+
|
|
5
209
|
## 3.3.3 — 2026-07-03
|
|
6
210
|
|
|
7
211
|
- Internal source-comment cleanup only: **no runtime, verifier, or evidence-bundle change** (behavior is byte-identical to 3.3.2).
|
package/DEPLOYMENT.md
CHANGED
|
@@ -38,22 +38,51 @@ Only `method === "tools/call"` is policy-evaluated. Other JSON-RPC methods are f
|
|
|
38
38
|
|
|
39
39
|
The **gateway key** is the Ed25519 key that signs every receipt and checkpoint. Pinning its public key is what turns a bundle from "internally consistent" into "provably issued by *this* gateway."
|
|
40
40
|
|
|
41
|
+
> ### ⚠️ Which entry point are you running? The two do NOT handle the key the same way.
|
|
42
|
+
>
|
|
43
|
+
> | | `aga-mcp-server` (stdio MCP server) | `aga-proxy` (governance proxy) |
|
|
44
|
+
> |---|---|---|
|
|
45
|
+
> | Reads `AGA_GATEWAY_KEY` / `AGA_GATEWAY_KEY_FILE` | **yes** | **yes, since 3.6.0** — silently ignored in 3.5.0 and earlier |
|
|
46
|
+
> | Key when unset | ephemeral, **warns on stderr** | ephemeral, **warns on stderr** since 3.6.0 (no warning in 3.5.0 and earlier) |
|
|
47
|
+
> | Says which key is active at startup | `get_server_info` → `gateway_public_key` | startup banner, since 3.6.0 |
|
|
48
|
+
> | Provenance pinnable across restarts | yes, once persisted | yes, once persisted, since 3.6.0 — **no**, at any earlier version |
|
|
49
|
+
>
|
|
50
|
+
> **If you are on 3.5.0 or earlier:** `aga-proxy` calls `generateSigner()` unconditionally at construction — no key CLI option, no environment variable, no signer through its constructor. Setting `AGA_GATEWAY_KEY` before starting it has **no effect and produces no warning**, and two runs with an identical `AGA_GATEWAY_KEY` produce different gateway public keys. Treat evidence from such a proxy as integrity-verifiable and **not** provenance-pinnable across restarts. Upgrade, or accept that scope.
|
|
51
|
+
>
|
|
52
|
+
> **From 3.6.0 on** both binaries resolve the key through the same function, in the same order (`AGA_GATEWAY_KEY`, then `AGA_GATEWAY_KEY_FILE`, then ephemeral), with the same warnings. `aga-proxy --ephemeral` makes a throwaway key a stated choice rather than a silent default.
|
|
53
|
+
>
|
|
54
|
+
> **Pinning is still not automatic, on either binary.** A verifier given a key lifted out of the same bundle it is checking will print `provenance verified` — it cannot know where you got the key. That check is circular. Only a key you obtained **out of band, before** the bundle proves issuance; persisting the key is what makes such a key *exist*, not a substitute for obtaining it independently.
|
|
55
|
+
>
|
|
56
|
+
> Verified against 3.6.0 (`tests/integration/proxy-gateway-key.test.ts` drives the built `dist/proxy/index.js` in separate processes and asserts one key file yields one public key with zero warnings).
|
|
57
|
+
|
|
41
58
|
### Generate a 32-byte seed (64-hex)
|
|
42
59
|
```bash
|
|
43
60
|
node -e "console.log(require('crypto').randomBytes(32).toString('hex'))"
|
|
44
61
|
```
|
|
45
62
|
|
|
46
|
-
### Persist it (so provenance survives restarts and is pinnable)
|
|
63
|
+
### Persist it (so provenance survives restarts and is pinnable) — both binaries, since 3.6.0
|
|
47
64
|
Either:
|
|
48
65
|
```bash
|
|
49
66
|
export AGA_GATEWAY_KEY=<64-hex-seed> # environment variable
|
|
50
67
|
# or
|
|
51
68
|
export AGA_GATEWAY_KEY_FILE=/run/secrets/aga-gateway-key # file containing the hex seed
|
|
52
69
|
```
|
|
53
|
-
If neither is set,
|
|
70
|
+
If neither is set, `aga-mcp-server` uses an **ephemeral** key that rotates on every restart (it warns on stderr). Ephemeral is fine for local experiments but means **provenance cannot be pinned across restarts** — avoid it in anything you'll later audit.
|
|
71
|
+
|
|
72
|
+
`aga-proxy` honours both variables as of **3.6.0**, through the same resolver the MCP server uses, and prints the active public key at startup so you can pin it out of band. Pass `--ephemeral` to deliberately use a throwaway key instead. **In 3.5.0 and earlier the proxy ignored both variables silently** — if you are on an older version, a key you set had no effect and no warning was printed.
|
|
54
73
|
|
|
55
74
|
### Obtain the public key to pin
|
|
56
|
-
|
|
75
|
+
For `aga-mcp-server`: call the `get_server_info` tool → **`gateway_public_key`**. That 64-hex value is what verifiers pin.
|
|
76
|
+
|
|
77
|
+
For `aga-proxy`, since **3.6.0**: the startup banner prints the active public key and where it came from, e.g.
|
|
78
|
+
|
|
79
|
+
```
|
|
80
|
+
Signing gateway key 248acbdb… (persisted via AGA_GATEWAY_KEY_FILE)
|
|
81
|
+
```
|
|
82
|
+
|
|
83
|
+
That line is printed before any bundle exists, which is exactly what makes it usable as an out-of-band pin. Record it from the console or from your process supervisor's log, not from the bundle. `aga-proxy status` still reports only `running` and `pid`.
|
|
84
|
+
|
|
85
|
+
The gateway key also appears inside the exported bundle (`public_key`) — whether saved to a file or fetched live from the loopback control channel's `GET /export`. **Do not pin that one.** It is the key you are trying to check, so a verifier fed it will agree with itself; and on a proxy left unconfigured (or run with `--ephemeral`) it is a throwaway that rotates on restart. Pinning it proves the bundle is internally consistent — not who issued it.
|
|
57
86
|
|
|
58
87
|
### Pin it when verifying
|
|
59
88
|
```bash
|
|
@@ -74,7 +103,9 @@ The gateway key is a signing secret — **anyone who holds it can mint a fully V
|
|
|
74
103
|
## 3. Recommended hardened configuration
|
|
75
104
|
|
|
76
105
|
```jsonc
|
|
77
|
-
// Claude Desktop
|
|
106
|
+
// Claude Desktop MCP config — the stdio server with a persisted gateway key.
|
|
107
|
+
// (This runs `aga-mcp-server`. For an aga-proxy-in-front-of-upstream deployment see §1;
|
|
108
|
+
// since 3.6.0 the proxy reads the same variable — §2.)
|
|
78
109
|
{
|
|
79
110
|
"mcpServers": {
|
|
80
111
|
"aga": {
|
|
@@ -86,7 +117,7 @@ The gateway key is a signing secret — **anyone who holds it can mint a fully V
|
|
|
86
117
|
}
|
|
87
118
|
```
|
|
88
119
|
Checklist:
|
|
89
|
-
- [ ] stdio upstream (no HTTP upstream, or HTTP only behind network isolation).
|
|
120
|
+
- [ ] For a proxy deployment: stdio upstream (no HTTP upstream, or HTTP only behind network isolation).
|
|
90
121
|
- [ ] Persisted gateway key from a secret manager / restricted file.
|
|
91
122
|
- [ ] The agent's **only** route to tools is through the proxy (network isolation).
|
|
92
123
|
- [ ] Verifiers **pin** `gateway_public_key`; an unpinned PASS is treated as integrity-only, not provenance.
|
package/README.md
CHANGED
|
@@ -1,20 +1,20 @@
|
|
|
1
1
|
# AGA - Attested Governance Artifacts
|
|
2
2
|
|
|
3
|
-
|
|
3
|
+
Verifiable decision records for AI agents: each governed tool-call decision becomes a signed, hash-chained receipt, exported in evidence bundles anyone can verify offline against the published format.
|
|
4
4
|
|
|
5
5
|
[](https://www.npmjs.com/package/@attested-intelligence/aga-mcp-server)
|
|
6
6
|
[](https://pypi.org/project/aga-governance/)
|
|
7
7
|
[](https://github.com/attestedintelligence/aga-mcp-server/blob/main/LICENSE)
|
|
8
8
|
[](https://www.npmjs.com/package/@attested-intelligence/aga-mcp-server)
|
|
9
9
|
|
|
10
|
-
> **Status: published to npm; this release carries SLSA build provenance (check it: `npm audit signatures`).** The server tools and the `aga-proxy` emit the **canonical SEP evidence bundle**, verifiable offline by the published `@attested-intelligence/aga-verify` and the reference verifier `aga-receipt-spec/verify/verify-sep.mjs`. **Since 3.2.0 the verifier is algorithm-agile and ships a post-quantum profile:** v1 `Ed25519-SHA256-JCS` (the default the gateway emits) and v2 `ML-DSA-65+Ed25519-SHA256-JCS` (a NIST FIPS-204 ML-DSA-65 + RFC-8032 Ed25519 **composite**, both must verify), selected per-bundle by the `algorithm` field with a `VERIFIED / FAILED / UNSUPPORTED_PROFILE` trichotomy. Pre-3.0 releases (a legacy continuity-chain bundle that does *not* verify under the SEP verifier) are deprecated; use `^3.0.0`. Claim scope and residual attack surface are documented honestly in `THREAT_BOUNDARY.md`.
|
|
10
|
+
> **Status: published to npm; this release carries SLSA build provenance (check it: `npm audit signatures`).** The server tools and the `aga-proxy` emit the **canonical SEP evidence bundle**, verifiable offline by the published `@attested-intelligence/aga-verify` and the reference verifier `aga-receipt-spec/verify/verify-sep.mjs`. **Since 3.2.0 the verifier is algorithm-agile and ships a post-quantum profile:** v1 `Ed25519-SHA256-JCS` (the default the gateway emits) and v2 `ML-DSA-65+Ed25519-SHA256-JCS` (a NIST FIPS-204 ML-DSA-65 + RFC-8032 Ed25519 **composite**, both must verify), selected per-bundle by the `algorithm` field with a `VERIFIED / FAILED / UNSUPPORTED_PROFILE` trichotomy. Pre-3.0 releases (a legacy continuity-chain bundle that does *not* verify under the SEP verifier) are deprecated; use `^3.0.0`. Claim scope and residual attack surface are documented honestly in `THREAT_BOUNDARY.md`. **3.5.0 (2026-08-29) changes one behavior:** an artifact's TTL now *fails closed* — on expiry the portal terminates and a further measurement is refused, where earlier releases degraded and kept measuring. If you depend on the old post-expiry behavior, pin `3.3.3`. **3.6.0 (2026-09-18) changes no existing behavior:** it makes `aga-proxy` honour `AGA_GATEWAY_KEY` / `AGA_GATEWAY_KEY_FILE`, which it had silently ignored. See `CHANGELOG.md`.
|
|
11
11
|
|
|
12
12
|
```bash
|
|
13
13
|
# This package IS the AGA MCP server (TypeScript, runs over stdio). Use it from any MCP client:
|
|
14
14
|
npx -y @attested-intelligence/aga-mcp-server
|
|
15
15
|
```
|
|
16
16
|
|
|
17
|
-
A Python companion SDK (`aga-governance`) is documented in the Python SDK section below.
|
|
17
|
+
A Python companion SDK (`aga-governance`) is documented in the Python SDK section below. Read the advisory at the top of that section before you install it.
|
|
18
18
|
|
|
19
19
|
## Verify this yourself (don't take our word)
|
|
20
20
|
|
|
@@ -28,11 +28,13 @@ node aga-receipt-spec/verify/verify-sep.mjs fixtures/valid_minimal.json # OVER
|
|
|
28
28
|
node aga-receipt-spec/verify/verify-sep.mjs fixtures/tampered.json # OVERALL: FAILED
|
|
29
29
|
```
|
|
30
30
|
|
|
31
|
-
The published `@attested-intelligence/aga-verify` CLI renders the identical verdict, and `npm run conformance:cross-stack` (first: `npm run build && npm --prefix independent-verifier run build`) proves six v1 verifier configurations
|
|
31
|
+
The published `@attested-intelligence/aga-verify` CLI renders the identical verdict, and `npm run conformance:cross-stack` (first: `npm run build && npm --prefix independent-verifier run build`) proves **six v1 verifier configurations** — spanning **three independent toolchains (JavaScript, Go, and Python, including a pure-stdlib, no-third-party-crypto path)** — agree on the **54 object-level cases**, and the **five file-parsing verifiers** agree on the **7 raw-byte/file-parse cases** (**61 total**). The in-server engine is library-only, receiving parsed objects rather than raw file bytes, so it does not run the file-parse cases; six configurations do not agree on all 61 and this no longer claims they do. `npm run conformance:cross-stack-v2` proves **two genuinely independent-language oracles (@noble/JS and CIRCL/Go)** agree on the v2 composite corpus. For a full trust-free reproduction (build the package yourself, reproduce the published tarball byte-for-byte, re-run every gate), see the **[REVIEWER_GUIDE.md](https://github.com/attestedintelligence/aga-mcp-server/blob/main/REVIEWER_GUIDE.md)** (a command-by-command self-service path), **[REPRODUCIBILITY.md](https://github.com/attestedintelligence/aga-mcp-server/blob/main/REPRODUCIBILITY.md)**, and the step-by-step **[SKEPTICAL_AUDITOR.md](https://github.com/attestedintelligence/aga-mcp-server/blob/main/SKEPTICAL_AUDITOR.md)**. This release carries SLSA build provenance, checkable with `npm audit signatures`.
|
|
32
32
|
|
|
33
33
|
## What This Does
|
|
34
34
|
|
|
35
|
-
|
|
35
|
+
This is built for teams shipping agentic-AI products into financial services and insurance, at the moment a customer's vendor-risk, model-risk, or internal-audit review asks what your agent did and how anyone would know.
|
|
36
|
+
|
|
37
|
+
Tool calls routed through the AGA gateway are evaluated against the sealed policy, and each decision (PERMITTED or DENIED) is recorded as a signed, hash-linked governance receipt. Receipts are collected into evidence bundles that anyone holding the published format and the public key can verify offline, with no callback to us.
|
|
36
38
|
|
|
37
39
|
**Record. Prove. Verify.**
|
|
38
40
|
|
|
@@ -59,6 +61,8 @@ Claude can then seal artifacts, measure integrity, generate evidence bundles, an
|
|
|
59
61
|
|
|
60
62
|
By default the gateway signs with an **ephemeral** key that rotates on every restart. That is fine for a first look, but evidence-bundle provenance cannot be pinned across restarts (and the server warns about it on stderr). Set one stable 64-hex Ed25519 seed so provenance stays pinnable:
|
|
61
63
|
|
|
64
|
+
> **Since 3.6.0 this applies to both binaries.** `aga-proxy` reads the same two variables through the same resolver and prints the active public key at startup so you can pin it out of band; `--ephemeral` makes a throwaway key a stated choice. **In 3.5.0 and earlier `aga-proxy` ignored both variables silently** — a key you set had no effect and no warning was printed, so evidence from such a proxy is integrity-verifiable but not provenance-pinnable across restarts. See `DEPLOYMENT.md` §2.
|
|
65
|
+
|
|
62
66
|
```bash
|
|
63
67
|
# generate a seed once (32 random bytes, hex)
|
|
64
68
|
node -e "console.log(require('node:crypto').randomBytes(32).toString('hex'))"
|
|
@@ -168,6 +172,8 @@ A **separate** `aga-proxy export` invocation reads that file and fetches the sam
|
|
|
168
172
|
npx -p @attested-intelligence/aga-mcp-server aga-proxy start \
|
|
169
173
|
--upstream "npx -y @modelcontextprotocol/server-filesystem /tmp/test" --profile standard
|
|
170
174
|
|
|
175
|
+
# (First, drive at least one tools/call through the proxy from your MCP client — an empty
|
|
176
|
+
# ledger has no receipts to checkpoint, and the export reports there is nothing to export.)
|
|
171
177
|
# Terminal B — export the live ledger from a different shell, then verify it offline
|
|
172
178
|
npx -p @attested-intelligence/aga-mcp-server aga-proxy export -o evidence.json
|
|
173
179
|
npx -y @attested-intelligence/aga-verify evidence.json --pubkey <gateway-public-key>
|
|
@@ -184,6 +190,8 @@ Three built-in policy profiles:
|
|
|
184
190
|
- **standard** - rate limits + blocks destructive operations
|
|
185
191
|
- **restrictive** - explicit tool allowlist, all unknown tools denied
|
|
186
192
|
|
|
193
|
+
Because the default (`permissive`) is audit-only, starting with an `audit_only` policy prints a loud stderr banner stating that every call is permitted and recorded and **no call is denied** in that mode — denial happens only under an allowlist-mode policy (`standard`, `restrictive`, or a custom `--policy` file). An unrecognized `--profile` value is a hard error (exit 2 listing the valid names), never a silent fallback to `permissive`.
|
|
194
|
+
|
|
187
195
|
## Verification _(canonical SEP 3.0; normative §6 algorithm in `aga-receipt-spec/verify/verify-sep.mjs`)_
|
|
188
196
|
|
|
189
197
|
1. **Structural floor** - Bundle declares Ed25519-SHA256-JCS, public key well-formed (all small-order encodings + non-canonical `y ≥ p` rejected), `receipts.length > 0`, proof count = receipt count
|
|
@@ -216,8 +224,10 @@ curl https://aga-mcp-gateway.attested-intelligence.workers.dev/bundle -o evidenc
|
|
|
216
224
|
|
|
217
225
|
## Python SDK
|
|
218
226
|
|
|
227
|
+
> **Advisory, recomputed against PyPI on 2026-09-18: do not use the published Python verifier on untrusted input.** The latest release on the registry is `0.3.0` (uploaded 2026-08-29, not yanked). Its bundle verifier has no depth guard: on a deeply nested `receipts` payload it raises instead of returning a `FAILED` verdict, so a caller that treats an exception as anything other than a rejection reads a hostile bundle as unverified rather than refused. A corrected release is built and staged. **It is not on PyPI, so `pip install aga-governance` installs the affected version today.** Until a newer version appears at <https://pypi.org/project/aga-governance/>, treat this SDK as a recording and export client only, and verify with the JavaScript reference verifier (`aga-receipt-spec/verify/verify-sep.mjs`) or the published `@attested-intelligence/aga-verify` CLI. Recheck the registry before you rely on any of this.
|
|
228
|
+
|
|
219
229
|
```bash
|
|
220
|
-
pip install aga-governance
|
|
230
|
+
pip install aga-governance # installs 0.3.0 today; see the advisory above
|
|
221
231
|
```
|
|
222
232
|
|
|
223
233
|
```python
|
|
@@ -231,7 +241,7 @@ with AgentSession(gateway_id="my-gateway") as session:
|
|
|
231
241
|
request_id="req-1",
|
|
232
242
|
)
|
|
233
243
|
bundle = session.export_bundle()
|
|
234
|
-
result = session.verify()
|
|
244
|
+
result = session.verify() # see the advisory: not for untrusted input on 0.3.0
|
|
235
245
|
assert result["overall_valid"]
|
|
236
246
|
```
|
|
237
247
|
|
|
@@ -239,9 +249,9 @@ with AgentSession(gateway_id="my-gateway") as session:
|
|
|
239
249
|
|
|
240
250
|
Automated tests across TypeScript and Python, plus a conformance corpus:
|
|
241
251
|
|
|
242
|
-
- **TypeScript MCP server:**
|
|
252
|
+
- **TypeScript MCP server:** 428 automated tests (vitest), including provable-denial and behavioral-monitor regressions
|
|
243
253
|
- **SEP conformance corpus:** `npm run test:conformance` (valid → VERIFIED, negatives → FAILED)
|
|
244
|
-
- **Python companion SDK:** the separately-published `aga-governance` PyPI package (install + smoke-checked here; its full pytest suite runs from the source tree)
|
|
254
|
+
- **Python companion SDK:** the separately-published `aga-governance` PyPI package (install + smoke-checked here; its full pytest suite runs from the source tree). The smoke check imports the package and prints its version. It does not exercise the verifier, which on the published 0.3.0 is subject to the advisory in the Python SDK section.
|
|
245
255
|
|
|
246
256
|
```bash
|
|
247
257
|
npm test # TypeScript tests (vitest)
|
|
@@ -251,7 +261,7 @@ pip install aga-governance && python -c "import aga; print(aga.__version__)" #
|
|
|
251
261
|
|
|
252
262
|
## Benchmarks
|
|
253
263
|
|
|
254
|
-
Receipt-format determinism is reproducible here: `npm test` runs the cross-language vectors, and `npm run conformance:cross-stack` (first: `npm run build && npm --prefix independent-verifier run build`) shows the six v1 verifier configurations (across three independent toolchains: JS, Go, Python) agree on the canonical
|
|
264
|
+
Receipt-format determinism is reproducible here: `npm test` runs the cross-language vectors, and `npm run conformance:cross-stack` (first: `npm run build && npm --prefix independent-verifier run build`) shows the six v1 verifier configurations (across three independent toolchains: JS, Go, Python) agree on the 54 object-level cases of the canonical 61-case corpus — the remaining 7 are raw-byte/file-parse cases run by the five file-parsing verifiers, since the in-server engine never receives raw bytes — while `npm run conformance:cross-stack-v2` shows the two independent-language v2 oracles agree on the composite corpus.
|
|
255
265
|
|
|
256
266
|
## Project Structure
|
|
257
267
|
|
|
@@ -260,11 +270,11 @@ src/
|
|
|
260
270
|
sep/ # Canonical SEP evidence engine: single source of truth (canon, merkle, receipt, checkpoint, bundle, verify)
|
|
261
271
|
core/ # Governance primitives (portal, artifact, attestation, disclosure, delegation, behavioral) + internal continuity-chain profile
|
|
262
272
|
crypto/ # Internal continuity-chain crypto: Ed25519 (node:crypto), SHA-256/blake2b, salt
|
|
263
|
-
proxy/ # MCP governance proxy (transparent interception + policy
|
|
273
|
+
proxy/ # MCP governance proxy (transparent interception + policy evaluation; emits SEP bundles)
|
|
264
274
|
middleware/ # Governance PEP wrapper (records a signed PERMITTED/DENIED receipt per governed call)
|
|
265
275
|
independent-verifier/ # @attested-intelligence/aga-verify: standalone SEP verifier, zero AGA imports
|
|
266
276
|
scenarios/ # Demo scenarios (SCADA, autonomous vehicle, AI agent) that emit SEP bundles
|
|
267
|
-
tests/ # TypeScript test suite (
|
|
277
|
+
tests/ # TypeScript test suite (428 automated tests)
|
|
268
278
|
```
|
|
269
279
|
|
|
270
280
|
## Links
|
|
@@ -276,6 +286,9 @@ tests/ # TypeScript test suite (384 automated tests)
|
|
|
276
286
|
- [Diligence Materials](https://attestedintelligence.com/diligence)
|
|
277
287
|
- [MCP Server (npm)](https://www.npmjs.com/package/@attested-intelligence/aga-mcp-server)
|
|
278
288
|
- [Python SDK (PyPI)](https://pypi.org/project/aga-governance/)
|
|
289
|
+
- [Changelog](https://github.com/attestedintelligence/aga-mcp-server/blob/main/CHANGELOG.md)
|
|
290
|
+
- [Threat boundary](https://github.com/attestedintelligence/aga-mcp-server/blob/main/THREAT_BOUNDARY.md)
|
|
291
|
+
- [Deployment guide](https://github.com/attestedintelligence/aga-mcp-server/blob/main/DEPLOYMENT.md)
|
|
279
292
|
|
|
280
293
|
## Security
|
|
281
294
|
|
package/THREAT_BOUNDARY.md
CHANGED
|
@@ -46,11 +46,11 @@ Claim scope is **integrity-of-present-receipts, NOT non-omission**: a PASS prove
|
|
|
46
46
|
1. **Proxy — mandatory mediation is a deployment property.** `GovernanceProxy` governs the path *through it*; if the agent can reach the upstream MCP server **directly** (network), it bypasses governance entirely. `upstreamUrl` (HTTP) is directly reachable. → **Deployment requirement:** the agent must be network-isolated so the proxy is its *only* route to tools. Documented, not code-enforceable here. *(Empirically demonstrated in CP3 A7 — a direct POST to the upstream fired a side effect with no receipt; the red-team scripts are maintained in the private validation tree, not shipped in this package.)* **See `DEPLOYMENT.md` §1: stdio-upstream is the hardened default — the upstream is a non-network-reachable child process, closing this bypass by construction; HTTP-upstream mode now warns on stderr.**
|
|
47
47
|
2. **Proxy — only `method === "tools/call"` is policy-evaluated; partially mitigated in 3.0.** Other JSON-RPC methods are still forwarded (not policy-evaluated), but they are now recorded as **signed passthrough receipts** in the evidence bundle for auditability — so a side-effecting non-`tools/call` method is no longer *invisible* (the CP3 A7 `admin/deleteAllData` would now appear as a `(passthrough)` receipt with `method` set and `reason: forwarded, not policy-evaluated`). Benign protocol methods (`initialize`, `ping`, `tools/list`, `*/list`, `notifications/*`, …) are excluded by default; an optional `denyMethods` denylist rejects known side-effecting methods (a **DENIED** passthrough receipt, not forwarded). **What this closes:** invisibility — every forwarded non-benign method is now in the tamper-evident, checkpoint-bound bundle. **What it does NOT close:** the method is not policy-evaluated and still *executes* unless explicitly denylisted (this is *visibility, not governance*), and a direct-to-upstream call (§3.1) still bypasses the proxy entirely. Full per-method policy evaluation remains future work.
|
|
48
48
|
3. **Proxy — denial is observable.** A DENIED call returns a distinctive `-32600` error, which an adversary can use as a policy-probing oracle. Low severity; structured denial metadata could be tightened later.
|
|
49
|
-
4. **Key persistence —
|
|
49
|
+
4. **Key persistence — available on BOTH binaries as of 3.6.0, and mitigated only where an operator configures it.** The gateway signing key **can be** persisted via `AGA_GATEWAY_KEY` (64-hex seed) or `AGA_GATEWAY_KEY_FILE` (`DEPLOYMENT.md` §2), which makes `gateway_public_key` stable and **pinnable across restarts**. The default on both binaries is still an ephemeral key, so an unconfigured deployment carries this risk in full — availability of the control is not the same as the control being in effect, and nothing here detects a deployment that skipped it. Both binaries resolve the key through one shared function and warn identically on stderr when they fall back, and `aga-proxy --ephemeral` makes a throwaway key a stated choice rather than a silent one. The proxy also prints its active public key at startup, which is what makes an honest out-of-band pin possible. **Through 3.5.0 this was NOT true of `aga-proxy`:** it called `generateSigner()` unconditionally, read neither variable, and emitted no warning — so an operator who set the variable correctly was silently ignored and their proxy-issued bundles were not pinnable. If you are running 3.5.0 or earlier, treat proxy evidence as integrity-verifiable only. HSM/KMS backing remains future work. A verifier MUST still pin the key it expects — **and MUST obtain that key independently of the bundle**, since a key lifted from the bundle under test yields a circular `provenance verified` (see §3.7).
|
|
50
50
|
5. **Storage durability (Sprint 5).** Default storage is in-memory → the live chain is lost on **process restart**. The cryptographic record survives via the **exported signed bundle**; durable cross-restart retention needs the SQLite/persistent backend (Sprint 5). The raw quarantine forensic buffer is in-memory by design (only the `arguments_hash` commitment is signed — privacy-preserving and sufficient to *prove* a capture).
|
|
51
51
|
6. **Out of scope entirely (not what AGA does).** AGA does not prevent: model jailbreaks, model-weight theft, credential compromise, or infrastructure compromise. It provides *accountability and provenance* for governed decisions, not prevention of those classes. If an attacker holds the gateway signing key, they can author receipts — protect the key (Sprint 4).
|
|
52
52
|
7. **Verifier-UX / unpinned consumers (NEW — CP3 A5).** A consumer that verifies a bundle **without pinning** the gateway key gets an integrity-only `VERIFIED` with `issuerVerified=false` — *even on a forged, attacker-signed, denial-free bundle*. This is correct (integrity-of-present-receipts ≠ provenance, and the result object/CLI say so explicitly), but a UI that shows a bare "VERIFIED" without prominently propagating `issuerVerified=false` could mislead a non-expert. → Downstream consumers (esp. the website demo) MUST pin the gateway key and never present an unpinned PASS as proof of *who* issued the bundle. **Mitigated in 3.0:** the verify result now carries a prominent `summary` — `VERIFIED (provenance verified …)` vs `VERIFIED (integrity only — NOT provenance …)` — surfaced by `verify_bundle_offline` and the reference/`aga-verify` CLIs; key-pinning ergonomics are in `DEPLOYMENT.md` §2. Consumers must still pin.
|
|
53
|
-
8. **Cross-stack verifier conformance (CLOSED — 2026-06-07).** Earlier in the 3.0 hardening only `src/sep/verify.ts` carried the full strict floor; the reference `verify-sep.mjs`, the published `aga-verify`, Go, and Python lagged. **That asymmetry is now closed.** All six verifiers — engine (`src/sep`), reference (`verify-sep.mjs`), `aga-verify`, Go (`verify.go`), and Python (audited library + pure-stdlib) — apply the identical strict floor and return **byte-identical verdicts**. The shared floor: strict field allowlist; `envelope_consistency` (binds the unsigned `gateway_id`/`merkle_root`/`generated_at` to the signed/recomputed values); checkpoint-algorithm binding; lexicographic-string canonicalization with RFC-8785 integral-number normalization; complete small-order/non-canonical-key rejection; **one library-free canonical-timestamp rule** (exact `.sssZ` UTC form via an ASCII regex + integer-arithmetic calendar + lexicographic ordering — no native date parser); merkle-direction-token strictness (`left`/`right` only, length-matched); unpaired-UTF-16-surrogate rejection; depth-bounded never-throw; and whole-document parse (trailing content rejected). Verified by `npm run conformance:cross-stack` — six
|
|
53
|
+
8. **Cross-stack verifier conformance (CLOSED — 2026-06-07).** Earlier in the 3.0 hardening only `src/sep/verify.ts` carried the full strict floor; the reference `verify-sep.mjs`, the published `aga-verify`, Go, and Python lagged. **That asymmetry is now closed.** All six verifiers — engine (`src/sep`), reference (`verify-sep.mjs`), `aga-verify`, Go (`verify.go`), and Python (audited library + pure-stdlib) — apply the identical strict floor and return **byte-identical verdicts**. The shared floor: strict field allowlist; `envelope_consistency` (binds the unsigned `gateway_id`/`merkle_root`/`generated_at` to the signed/recomputed values); checkpoint-algorithm binding; lexicographic-string canonicalization with RFC-8785 integral-number normalization; complete small-order/non-canonical-key rejection; **one library-free canonical-timestamp rule** (exact `.sssZ` UTC form via an ASCII regex + integer-arithmetic calendar + lexicographic ordering — no native date parser); merkle-direction-token strictness (`left`/`right` only, length-matched); unpaired-UTF-16-surrogate rejection; depth-bounded never-throw; and whole-document parse (trailing content rejected). Verified by `npm run conformance:cross-stack` — six verifier configurations agree on the 54 object-level cases of the committed corpus, and the five file-parsing verifiers agree on its 7 raw-byte/file-parse cases (61 total, incl. an uppercase-Merkle-sibling cross-stack case). The agreement itself was confirmed across multiple rounds of independent blind differential re-audit. The engine is library-only and never receives raw file bytes, so it does not run the file-parse subset; the earlier phrasing here ("six verifiers agree on every case … 57 cases incl. raw-byte/file-parse") overclaimed by one verifier on those seven and carried a stale total. See `fixtures/cross-stack/README.md`. **Residual (by design, not a divergence):** the bundle envelope still carries four *unsigned* metadata fields with no signed counterpart — `bundle_id`, `schema_version`, `policy_reference`, `offline_capable`. They are informational and are **not** security-identity fields (the identity fields `gateway_id`/`merkle_root`/`generated_at` ARE bound); a relying party must still trust only signed/verified values and pin the gateway key (§3.7). Of the four, **`policy_reference` is the only identity-grade one** — but the governing policy IS captured and cryptographically verified inside **every signed receipt's own `policy_reference` field** (one of the 15 signed fields), so the unsigned *envelope* `policy_reference` is only a convenience mirror, not the source of truth. Binding the envelope copy is a recommended near-term (3.1) format revision; a coordinated verifier-output flag that marks these four envelope fields as unsigned/not-verified is a recommended enhancement, deliberately deferred here to avoid adding an untested cross-language output surface immediately after the cross-stack-consistency campaign (the verdict surface already attests only the steps it runs, and never claims these fields). One further residual is **unreachable by construction:** SEP signed fields are strings plus the single integer `leaf_count` (the emit guard `assertSignedReceiptFieldsAreStrings` forbids any other number in a signed field), so a verifier's canonicalization of a *non-integer / exponential* number placed in a signed field — which no conformant gateway can emit — is intentionally left unspecified and MAY differ across language stdlibs; it cannot affect any bundle a conformant gateway produces. Reachable string content is fully cross-stack-consistent (an exhaustive 0..0x10FFFF code-point sweep confirms the only ever-divergent characters, `U+2028`/`U+2029`, are normalized identically by every verifier).
|
|
54
54
|
|
|
55
55
|
---
|
|
56
56
|
|
|
@@ -74,4 +74,4 @@ A focused adversarial workflow ran **7 independent red-team attacks** on the cor
|
|
|
74
74
|
|
|
75
75
|
## 5. Net boundary statement (for public copy)
|
|
76
76
|
|
|
77
|
-
> Every governed decision produces a signed, chained, checkpoint-bound receipt; denials cannot be silently erased by the agent through tampering, truncation, reordering, or re-attestation, and
|
|
77
|
+
> Every governed decision produces a signed, chained, checkpoint-bound receipt; denials cannot be silently erased by the agent through tampering, truncation, reordering, or re-attestation, and can be verified offline against the published format, with provenance only when the gateway key is pinned out of band. The verifier to run for a verdict is the published `@attested-intelligence/aga-verify` CLI; the in-repo reference implementations that the cross-stack conformance suite exercises are described in the README. The behavioral monitor is detective-only by default. Mandatory mediation (network isolation) and cross-restart ledger durability are deployment/roadmap properties, documented above; cross-session key persistence is available on both binaries as of 3.6.0 but only takes effect once an operator configures it, and only helps a verifier who obtains the key out of band. AGA proves *what was governed*; it does not claim to prevent jailbreaks, key theft, or non-`tools/call` side channels.
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"portal.d.ts","sourceRoot":"","sources":["../../src/core/portal.ts"],"names":[],"mappings":"AASA,OAAO,KAAK,EAAE,cAAc,EAAE,WAAW,EAAE,iBAAiB,EAAE,eAAe,EAAE,MAAM,YAAY,CAAC;AAClG,OAAO,KAAK,EAAE,OAAO,EAAE,MAAM,oBAAoB,CAAC;AAElD,MAAM,WAAW,iBAAiB;IAChC,KAAK,EAAE,OAAO,CAAC;IACf,gBAAgB,EAAE,OAAO,CAAC;IAC1B,eAAe,EAAE,OAAO,CAAC;IACzB,iBAAiB,EAAE,OAAO,CAAC;IAC3B,gBAAgB,EAAE,OAAO,CAAC;IAC1B,MAAM,EAAE,OAAO,CAAC;IAChB,OAAO,EAAE,OAAO,CAAC;IACjB,QAAQ,CAAC,EAAE,OAAO,CAAC;CACpB;AAED,yDAAyD;AACzD,MAAM,WAAW,gBAAgB;IAC/B,MAAM,EAAE,MAAM,CAAC;IACf,SAAS,EAAE,MAAM,CAAC;IAClB,kBAAkB,EAAE,OAAO,CAAC;IAC5B,cAAc,EAAE,MAAM,CAAC;CACxB;AAED,qBAAa,MAAM;IACjB,KAAK,EAAE,WAAW,CAAoB;IACtC,QAAQ,EAAE,cAAc,GAAG,IAAI,CAAQ;IACvC,eAAe,SAAK;IACpB,YAAY,EAAE,OAAO,GAAG,IAAI,CAAQ;IACpC,WAAW,EAAE,GAAG,CAAC,MAAM,CAAC,CAAa;IACrC,cAAc,EAAE,gBAAgB,EAAE,CAAM;IAExC,YAAY,CAAC,QAAQ,EAAE,cAAc,EAAE,WAAW,EAAE,MAAM,GAAG;QAAE,EAAE,EAAE,OAAO,CAAC;QAAC,KAAK,CAAC,EAAE,MAAM,CAAA;KAAE;IAiB5F,OAAO,CAAC,YAAY,EAAE,UAAU,EAAE,IAAI,EAAE,eAAe,GAAG,iBAAiB;
|
|
1
|
+
{"version":3,"file":"portal.d.ts","sourceRoot":"","sources":["../../src/core/portal.ts"],"names":[],"mappings":"AASA,OAAO,KAAK,EAAE,cAAc,EAAE,WAAW,EAAE,iBAAiB,EAAE,eAAe,EAAE,MAAM,YAAY,CAAC;AAClG,OAAO,KAAK,EAAE,OAAO,EAAE,MAAM,oBAAoB,CAAC;AAElD,MAAM,WAAW,iBAAiB;IAChC,KAAK,EAAE,OAAO,CAAC;IACf,gBAAgB,EAAE,OAAO,CAAC;IAC1B,eAAe,EAAE,OAAO,CAAC;IACzB,iBAAiB,EAAE,OAAO,CAAC;IAC3B,gBAAgB,EAAE,OAAO,CAAC;IAC1B,MAAM,EAAE,OAAO,CAAC;IAChB,OAAO,EAAE,OAAO,CAAC;IACjB,QAAQ,CAAC,EAAE,OAAO,CAAC;CACpB;AAED,yDAAyD;AACzD,MAAM,WAAW,gBAAgB;IAC/B,MAAM,EAAE,MAAM,CAAC;IACf,SAAS,EAAE,MAAM,CAAC;IAClB,kBAAkB,EAAE,OAAO,CAAC;IAC5B,cAAc,EAAE,MAAM,CAAC;CACxB;AAED,qBAAa,MAAM;IACjB,KAAK,EAAE,WAAW,CAAoB;IACtC,QAAQ,EAAE,cAAc,GAAG,IAAI,CAAQ;IACvC,eAAe,SAAK;IACpB,YAAY,EAAE,OAAO,GAAG,IAAI,CAAQ;IACpC,WAAW,EAAE,GAAG,CAAC,MAAM,CAAC,CAAa;IACrC,cAAc,EAAE,gBAAgB,EAAE,CAAM;IAExC,YAAY,CAAC,QAAQ,EAAE,cAAc,EAAE,WAAW,EAAE,MAAM,GAAG;QAAE,EAAE,EAAE,OAAO,CAAC;QAAC,KAAK,CAAC,EAAE,MAAM,CAAA;KAAE;IAiB5F,OAAO,CAAC,YAAY,EAAE,UAAU,EAAE,IAAI,EAAE,eAAe,GAAG,iBAAiB;IAsD3E,OAAO,CAAC,MAAM,EAAE,iBAAiB,GAAG,IAAI;IAUxC,MAAM,CAAC,UAAU,EAAE,MAAM,GAAG,IAAI;IAKhC,SAAS,CAAC,UAAU,EAAE,MAAM,GAAG,OAAO;IAEtC,KAAK,IAAI,IAAI;CAId"}
|
package/dist/core/portal.js
CHANGED
|
@@ -42,11 +42,26 @@ export class Portal {
|
|
|
42
42
|
const empty = { currentBytesHash: '', currentMetaHash: '',
|
|
43
43
|
expectedBytesHash: this.artifact.subject_identifier.bytes_hash,
|
|
44
44
|
expectedMetaHash: this.artifact.subject_identifier.metadata_hash };
|
|
45
|
-
//
|
|
45
|
+
// FAIL CLOSED on TTL expiry (founder decision D1, ruled 2026-08-29).
|
|
46
|
+
//
|
|
47
|
+
// This branch previously set SAFE_STATE and kept accepting measurements, so an expired artifact
|
|
48
|
+
// went on being measured indefinitely and every public page describing TTL as an enforcement
|
|
49
|
+
// boundary was describing a behavior the code did not have. It now terminates, which is exactly
|
|
50
|
+
// what the revocation branch immediately below already does — TTL was the odd one out, not the
|
|
51
|
+
// new behavior.
|
|
52
|
+
//
|
|
53
|
+
// Consequence, deliberate: the entry guard above throws on TERMINATED, so the FIRST post-expiry
|
|
54
|
+
// measurement returns this result and any SUBSEQUENT call throws 'Portal is terminated'. That is
|
|
55
|
+
// the point of failing closed — an expired artifact stops being a usable measurement channel and
|
|
56
|
+
// re-attestation is required to get one back. Callers that previously looped on measure() past
|
|
57
|
+
// expiry must handle the throw.
|
|
58
|
+
//
|
|
59
|
+
// The degradation entry is still recorded. It is the forensic record of WHY termination happened,
|
|
60
|
+
// and dropping it would trade one honesty problem for another.
|
|
46
61
|
const ttl_ok = !isExpired(this.artifact.issued_timestamp, this.artifact.enforcement_parameters.ttl_seconds);
|
|
47
62
|
if (!ttl_ok) {
|
|
48
63
|
const prevState = this.state;
|
|
49
|
-
this.state = '
|
|
64
|
+
this.state = 'TERMINATED';
|
|
50
65
|
this.degradationLog.push({
|
|
51
66
|
reason: 'TTL_EXPIRED',
|
|
52
67
|
timestamp: utcNow(),
|
package/dist/core/portal.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"portal.js","sourceRoot":"","sources":["../../src/core/portal.ts"],"names":[],"mappings":"AAAA;;;;GAIG;AACH,OAAO,EAAE,WAAW,EAAE,SAAS,EAAE,MAAM,mBAAmB,CAAC;AAC3D,OAAO,EAAE,QAAQ,EAAE,OAAO,EAAE,SAAS,EAAE,MAAM,mBAAmB,CAAC;AACjE,OAAO,EAAE,YAAY,EAAE,MAAM,uBAAuB,CAAC;AACrD,OAAO,EAAE,cAAc,EAAE,SAAS,EAAE,MAAM,EAAE,MAAM,uBAAuB,CAAC;AAuB1E,MAAM,OAAO,MAAM;IACjB,KAAK,GAAgB,gBAAgB,CAAC;IACtC,QAAQ,GAA0B,IAAI,CAAC;IACvC,eAAe,GAAG,CAAC,CAAC;IACpB,YAAY,GAAmB,IAAI,CAAC;IACpC,WAAW,GAAgB,IAAI,GAAG,EAAE,CAAC;IACrC,cAAc,GAAuB,EAAE,CAAC;IAExC,YAAY,CAAC,QAAwB,EAAE,WAAmB;QACxD,IAAI,CAAC,KAAK,GAAG,uBAAuB,CAAC;QACrC,MAAM,EAAE,SAAS,EAAE,GAAG,QAAQ,EAAE,GAAG,QAAQ,CAAC;QAC5C,IAAI,CAAC,SAAS,CAAC,QAAQ,CAAC,SAAS,CAAC,EAAE,YAAY,CAAC,QAAQ,CAAC,EAAE,OAAO,CAAC,WAAW,CAAC,CAAC,EAAE,CAAC;YAClF,IAAI,CAAC,KAAK,GAAG,YAAY,CAAC;YAAC,OAAO,EAAE,EAAE,EAAE,KAAK,EAAE,KAAK,EAAE,+BAA+B,EAAE,CAAC;QAC1F,CAAC;QACD,IAAI,CAAC,cAAc,CAAC,MAAM,EAAE,EAAE,QAAQ,CAAC,mBAAmB,EAAE,QAAQ,CAAC,oBAAoB,CAAC,EAAE,CAAC;YAC3F,IAAI,CAAC,KAAK,GAAG,YAAY,CAAC;YAAC,OAAO,EAAE,EAAE,EAAE,KAAK,EAAE,KAAK,EAAE,mCAAmC,EAAE,CAAC;QAC9F,CAAC;QACD,IAAI,IAAI,CAAC,WAAW,CAAC,GAAG,CAAC,QAAQ,CAAC,WAAW,CAAC,EAAE,CAAC;YAC/C,IAAI,CAAC,KAAK,GAAG,YAAY,CAAC;YAAC,OAAO,EAAE,EAAE,EAAE,KAAK,EAAE,KAAK,EAAE,2BAA2B,EAAE,CAAC;QACtF,CAAC;QACD,IAAI,CAAC,QAAQ,GAAG,QAAQ,CAAC;QACzB,IAAI,CAAC,KAAK,GAAG,mBAAmB,CAAC;QACjC,OAAO,EAAE,EAAE,EAAE,IAAI,EAAE,CAAC;IACtB,CAAC;IAED,OAAO,CAAC,YAAwB,EAAE,IAAqB;QACrD,IAAI,CAAC,IAAI,CAAC,QAAQ;YAAE,MAAM,IAAI,KAAK,CAAC,oBAAoB,CAAC,CAAC;QAC1D,IAAI,IAAI,CAAC,KAAK,KAAK,YAAY;YAAE,MAAM,IAAI,KAAK,CAAC,sBAAsB,CAAC,CAAC;QACzE,sDAAsD;QACtD,MAAM,KAAK,GAAG,EAAE,gBAAgB,EAAE,EAAE,EAAE,eAAe,EAAE,EAAE;YACvD,iBAAiB,EAAE,IAAI,CAAC,QAAQ,CAAC,kBAAkB,CAAC,UAAU;YAC9D,gBAAgB,EAAE,IAAI,CAAC,QAAQ,CAAC,kBAAkB,CAAC,aAAa,EAAE,CAAC;QAErE,
|
|
1
|
+
{"version":3,"file":"portal.js","sourceRoot":"","sources":["../../src/core/portal.ts"],"names":[],"mappings":"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"}
|
package/dist/proxy/control.d.ts
CHANGED
|
@@ -88,12 +88,18 @@ export declare function fetchBundleViaControl(loc: ControlLocator): Promise<unkn
|
|
|
88
88
|
* live proxy over its loopback control channel. Never writes an empty/placeholder bundle — if no
|
|
89
89
|
* live proxy can be reached it throws {@link ExportUnavailableError}.
|
|
90
90
|
*/
|
|
91
|
+
export declare class ExportTargetExistsError extends Error {
|
|
92
|
+
readonly output: string;
|
|
93
|
+
constructor(output: string);
|
|
94
|
+
}
|
|
91
95
|
export declare function exportBundleToFile(opts: {
|
|
92
96
|
proxy: {
|
|
93
97
|
exportBundle(): unknown;
|
|
94
98
|
} | null;
|
|
95
99
|
dataDir: string;
|
|
96
100
|
output: string;
|
|
101
|
+
/** RC9-06 / D-21.7: replacement is opt-in only. Default refuses an existing destination. */
|
|
102
|
+
force?: boolean;
|
|
97
103
|
writeFile?: (filePath: string, data: string) => void;
|
|
98
104
|
}): Promise<{
|
|
99
105
|
source: 'in-process' | 'control-channel';
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"control.d.ts","sourceRoot":"","sources":["../../src/proxy/control.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;GAoBG;AAMH,qEAAqE;AACrE,eAAO,MAAM,YAAY,cAAc,CAAC;AACxC,oGAAoG;AACpG,eAAO,MAAM,oBAAoB,QAAQ,CAAC;AAC1C,oGAAoG;AACpG,eAAO,MAAM,cAAc,kBAAkB,CAAC;AAC9C,eAAO,MAAM,oBAAoB,cAAc,CAAC;AAEhD;;;;;GAKG;AACH,wBAAgB,gBAAgB,CAAC,CAAC,EAAE,OAAO,GAAG,OAAO,CAUpD;AAED,iFAAiF;AACjF,MAAM,WAAW,kBAAkB;IACjC,YAAY,IAAI,OAAO,CAAC;IACxB,SAAS,IAAI,OAAO,CAAC;IACrB,WAAW,IAAI,OAAO,CAAC;CACxB;AAED;;;GAGG;AACH,qBAAa,kBAAkB;IAC7B,OAAO,CAAC,MAAM,CAA4B;IAC1C,OAAO,CAAC,SAAS,CAAK;IACtB,OAAO,CAAC,QAAQ,CAAC,MAAM,CAAqB;gBAEhC,MAAM,EAAE,kBAAkB;IAItC,8FAA8F;IACxF,KAAK,CAAC,IAAI,GAAE,MAA6B,GAAG,OAAO,CAAC;QAAE,OAAO,EAAE,MAAM,CAAC;QAAC,IAAI,EAAE,MAAM,CAAA;KAAE,CAAC;IAmB5F,wFAAwF;IACxF,OAAO,IAAI,MAAM;IAKjB,IAAI,IAAI,IAAI,MAAM,CAEjB;IAED,OAAO,CAAC,MAAM;IAgCd,OAAO,CAAC,IAAI;IAQZ,qCAAqC;IAC/B,IAAI,IAAI,OAAO,CAAC,IAAI,CAAC;CAO5B;AAID,yEAAyE;AACzE,MAAM,WAAW,cAAc;IAC7B,IAAI,EAAE,MAAM,CAAC;IACb,IAAI,EAAE,MAAM,CAAC;IACb,GAAG,EAAE,MAAM,CAAC;CACb;AAED,wBAAgB,eAAe,CAAC,OAAO,EAAE,MAAM,GAAG,MAAM,CAEvD;AAED,wBAAgB,gBAAgB,CAAC,OAAO,EAAE,MAAM,EAAE,GAAG,EAAE,cAAc,GAAG,IAAI,CAE3E;AAED,wBAAgB,eAAe,CAAC,OAAO,EAAE,MAAM,GAAG,cAAc,GAAG,IAAI,CAYtE;AAED,wBAAgB,iBAAiB,CAAC,OAAO,EAAE,MAAM,GAAG,IAAI,CAMvD;AAID,6FAA6F;AAC7F,eAAO,MAAM,gBAAgB,8EACgD,CAAC;AAE9E,sFAAsF;AACtF,qBAAa,sBAAuB,SAAQ,KAAK;gBACnC,OAAO,GAAE,MAAyB;CAI/C;AAED;;;;GAIG;AACH,wBAAsB,qBAAqB,CAAC,GAAG,EAAE,cAAc,GAAG,OAAO,CAAC,OAAO,CAAC,CA4BjF;AAED;;;;GAIG;AACH,wBAAsB,kBAAkB,CAAC,IAAI,EAAE;IAC7C,KAAK,EAAE;QAAE,YAAY,IAAI,OAAO,CAAA;KAAE,GAAG,IAAI,CAAC;IAC1C,OAAO,EAAE,MAAM,CAAC;IAChB,MAAM,EAAE,MAAM,CAAC;IACf,SAAS,CAAC,EAAE,CAAC,QAAQ,EAAE,MAAM,EAAE,IAAI,EAAE,MAAM,KAAK,IAAI,CAAC;CACtD,GAAG,OAAO,CAAC;IAAE,MAAM,EAAE,YAAY,GAAG,iBAAiB,CAAC;IAAC,MAAM,EAAE,MAAM,CAAC;IAAC,YAAY,EAAE,MAAM,CAAA;CAAE,CAAC,
|
|
1
|
+
{"version":3,"file":"control.d.ts","sourceRoot":"","sources":["../../src/proxy/control.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;GAoBG;AAMH,qEAAqE;AACrE,eAAO,MAAM,YAAY,cAAc,CAAC;AACxC,oGAAoG;AACpG,eAAO,MAAM,oBAAoB,QAAQ,CAAC;AAC1C,oGAAoG;AACpG,eAAO,MAAM,cAAc,kBAAkB,CAAC;AAC9C,eAAO,MAAM,oBAAoB,cAAc,CAAC;AAEhD;;;;;GAKG;AACH,wBAAgB,gBAAgB,CAAC,CAAC,EAAE,OAAO,GAAG,OAAO,CAUpD;AAED,iFAAiF;AACjF,MAAM,WAAW,kBAAkB;IACjC,YAAY,IAAI,OAAO,CAAC;IACxB,SAAS,IAAI,OAAO,CAAC;IACrB,WAAW,IAAI,OAAO,CAAC;CACxB;AAED;;;GAGG;AACH,qBAAa,kBAAkB;IAC7B,OAAO,CAAC,MAAM,CAA4B;IAC1C,OAAO,CAAC,SAAS,CAAK;IACtB,OAAO,CAAC,QAAQ,CAAC,MAAM,CAAqB;gBAEhC,MAAM,EAAE,kBAAkB;IAItC,8FAA8F;IACxF,KAAK,CAAC,IAAI,GAAE,MAA6B,GAAG,OAAO,CAAC;QAAE,OAAO,EAAE,MAAM,CAAC;QAAC,IAAI,EAAE,MAAM,CAAA;KAAE,CAAC;IAmB5F,wFAAwF;IACxF,OAAO,IAAI,MAAM;IAKjB,IAAI,IAAI,IAAI,MAAM,CAEjB;IAED,OAAO,CAAC,MAAM;IAgCd,OAAO,CAAC,IAAI;IAQZ,qCAAqC;IAC/B,IAAI,IAAI,OAAO,CAAC,IAAI,CAAC;CAO5B;AAID,yEAAyE;AACzE,MAAM,WAAW,cAAc;IAC7B,IAAI,EAAE,MAAM,CAAC;IACb,IAAI,EAAE,MAAM,CAAC;IACb,GAAG,EAAE,MAAM,CAAC;CACb;AAED,wBAAgB,eAAe,CAAC,OAAO,EAAE,MAAM,GAAG,MAAM,CAEvD;AAED,wBAAgB,gBAAgB,CAAC,OAAO,EAAE,MAAM,EAAE,GAAG,EAAE,cAAc,GAAG,IAAI,CAE3E;AAED,wBAAgB,eAAe,CAAC,OAAO,EAAE,MAAM,GAAG,cAAc,GAAG,IAAI,CAYtE;AAED,wBAAgB,iBAAiB,CAAC,OAAO,EAAE,MAAM,GAAG,IAAI,CAMvD;AAID,6FAA6F;AAC7F,eAAO,MAAM,gBAAgB,8EACgD,CAAC;AAE9E,sFAAsF;AACtF,qBAAa,sBAAuB,SAAQ,KAAK;gBACnC,OAAO,GAAE,MAAyB;CAI/C;AAED;;;;GAIG;AACH,wBAAsB,qBAAqB,CAAC,GAAG,EAAE,cAAc,GAAG,OAAO,CAAC,OAAO,CAAC,CA4BjF;AAED;;;;GAIG;AACH,qBAAa,uBAAwB,SAAQ,KAAK;aACpB,MAAM,EAAE,MAAM;gBAAd,MAAM,EAAE,MAAM;CAQ3C;AAED,wBAAsB,kBAAkB,CAAC,IAAI,EAAE;IAC7C,KAAK,EAAE;QAAE,YAAY,IAAI,OAAO,CAAA;KAAE,GAAG,IAAI,CAAC;IAC1C,OAAO,EAAE,MAAM,CAAC;IAChB,MAAM,EAAE,MAAM,CAAC;IACf,4FAA4F;IAC5F,KAAK,CAAC,EAAE,OAAO,CAAC;IAChB,SAAS,CAAC,EAAE,CAAC,QAAQ,EAAE,MAAM,EAAE,IAAI,EAAE,MAAM,KAAK,IAAI,CAAC;CACtD,GAAG,OAAO,CAAC;IAAE,MAAM,EAAE,YAAY,GAAG,iBAAiB,CAAC;IAAC,MAAM,EAAE,MAAM,CAAC;IAAC,YAAY,EAAE,MAAM,CAAA;CAAE,CAAC,CA+B9F"}
|
package/dist/proxy/control.js
CHANGED
|
@@ -222,8 +222,32 @@ export async function fetchBundleViaControl(loc) {
|
|
|
222
222
|
* live proxy over its loopback control channel. Never writes an empty/placeholder bundle — if no
|
|
223
223
|
* live proxy can be reached it throws {@link ExportUnavailableError}.
|
|
224
224
|
*/
|
|
225
|
+
export class ExportTargetExistsError extends Error {
|
|
226
|
+
output;
|
|
227
|
+
constructor(output) {
|
|
228
|
+
super(`Refusing to overwrite an existing file: ${output}\n` +
|
|
229
|
+
`Export writes with exclusive-create by default so it can never destroy an operator's file.\n` +
|
|
230
|
+
`Choose a different --output path, or pass --force to replace this file deliberately.`);
|
|
231
|
+
this.output = output;
|
|
232
|
+
this.name = 'ExportTargetExistsError';
|
|
233
|
+
}
|
|
234
|
+
}
|
|
225
235
|
export async function exportBundleToFile(opts) {
|
|
226
|
-
|
|
236
|
+
// RC9-06 (founder ruling D-21.7): this previously called fs.writeFileSync unconditionally, so
|
|
237
|
+
// `export --output <existing-file>` silently TRUNCATED whatever the operator pointed at — a
|
|
238
|
+
// config, a key file, a prior bundle — and exited 0 reporting success. No attacker and no
|
|
239
|
+
// unusual deployment required, on the exact artifact a verifier consumes. Default is now
|
|
240
|
+
// exclusive-create ('wx', which fails if the path exists); replacement requires --force.
|
|
241
|
+
const write = opts.writeFile ?? ((p, d) => {
|
|
242
|
+
try {
|
|
243
|
+
fs.writeFileSync(p, d, opts.force ? undefined : { flag: 'wx' });
|
|
244
|
+
}
|
|
245
|
+
catch (e) {
|
|
246
|
+
if (!opts.force && e?.code === 'EEXIST')
|
|
247
|
+
throw new ExportTargetExistsError(p);
|
|
248
|
+
throw e;
|
|
249
|
+
}
|
|
250
|
+
});
|
|
227
251
|
let bundle;
|
|
228
252
|
let source;
|
|
229
253
|
if (opts.proxy) {
|