@coderifts/agent-guard 15.0.0 → 16.0.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/README.md +3 -1
- package/dist/cjs/adapters/crewai.d.ts +88 -0
- package/dist/cjs/adapters/crewai.d.ts.map +1 -0
- package/dist/cjs/adapters/crewai.js +99 -0
- package/dist/cjs/adapters/crewai.js.map +1 -0
- package/dist/cjs/adapters/langchain.d.ts +93 -0
- package/dist/cjs/adapters/langchain.d.ts.map +1 -0
- package/dist/cjs/adapters/langchain.js +99 -0
- package/dist/cjs/adapters/langchain.js.map +1 -0
- package/dist/cjs/deploy-gate.d.ts +18 -0
- package/dist/cjs/deploy-gate.d.ts.map +1 -1
- package/dist/cjs/deploy-gate.js +18 -6
- package/dist/cjs/deploy-gate.js.map +1 -1
- package/dist/cjs/deploy-receipt-token.d.ts +7 -0
- package/dist/cjs/deploy-receipt-token.d.ts.map +1 -1
- package/dist/cjs/deploy-receipt-token.js +6 -0
- package/dist/cjs/deploy-receipt-token.js.map +1 -1
- package/dist/cjs/execution-grant.d.ts +4 -2
- package/dist/cjs/execution-grant.d.ts.map +1 -1
- package/dist/cjs/execution-grant.js +6 -0
- package/dist/cjs/execution-grant.js.map +1 -1
- package/dist/cjs/guard.d.ts.map +1 -1
- package/dist/cjs/guard.js +63 -0
- package/dist/cjs/guard.js.map +1 -1
- package/dist/cjs/index.d.ts +10 -1
- package/dist/cjs/index.d.ts.map +1 -1
- package/dist/cjs/index.js +33 -3
- package/dist/cjs/index.js.map +1 -1
- package/dist/cjs/next-agent-step.d.ts +50 -0
- package/dist/cjs/next-agent-step.d.ts.map +1 -0
- package/dist/cjs/next-agent-step.js +53 -0
- package/dist/cjs/next-agent-step.js.map +1 -0
- package/dist/cjs/offline-verify.d.ts +75 -0
- package/dist/cjs/offline-verify.d.ts.map +1 -0
- package/dist/cjs/offline-verify.js +74 -0
- package/dist/cjs/offline-verify.js.map +1 -0
- package/dist/cjs/policy.d.ts.map +1 -1
- package/dist/cjs/policy.js +1 -1
- package/dist/cjs/policy.js.map +1 -1
- package/dist/cjs/types.d.ts +64 -1
- package/dist/cjs/types.d.ts.map +1 -1
- package/dist/cjs/unguarded-mutation.d.ts +76 -0
- package/dist/cjs/unguarded-mutation.d.ts.map +1 -0
- package/dist/cjs/unguarded-mutation.js +101 -0
- package/dist/cjs/unguarded-mutation.js.map +1 -0
- package/dist/cjs/vendor/VENDOR.sha256 +13 -0
- package/dist/cjs/vendor/arity.js +53 -0
- package/dist/cjs/vendor/verify.js +454 -0
- package/dist/cjs/with-coderifts.d.ts +21 -0
- package/dist/cjs/with-coderifts.d.ts.map +1 -1
- package/dist/cjs/with-coderifts.js +42 -1
- package/dist/cjs/with-coderifts.js.map +1 -1
- package/dist/esm/adapters/crewai.d.ts +88 -0
- package/dist/esm/adapters/crewai.d.ts.map +1 -0
- package/dist/esm/adapters/crewai.js +95 -0
- package/dist/esm/adapters/crewai.js.map +1 -0
- package/dist/esm/adapters/langchain.d.ts +93 -0
- package/dist/esm/adapters/langchain.d.ts.map +1 -0
- package/dist/esm/adapters/langchain.js +94 -0
- package/dist/esm/adapters/langchain.js.map +1 -0
- package/dist/esm/deploy-gate.d.ts +18 -0
- package/dist/esm/deploy-gate.d.ts.map +1 -1
- package/dist/esm/deploy-gate.js +18 -6
- package/dist/esm/deploy-gate.js.map +1 -1
- package/dist/esm/deploy-receipt-token.d.ts +7 -0
- package/dist/esm/deploy-receipt-token.d.ts.map +1 -1
- package/dist/esm/deploy-receipt-token.js +6 -0
- package/dist/esm/deploy-receipt-token.js.map +1 -1
- package/dist/esm/execution-grant.d.ts +4 -2
- package/dist/esm/execution-grant.d.ts.map +1 -1
- package/dist/esm/execution-grant.js +6 -0
- package/dist/esm/execution-grant.js.map +1 -1
- package/dist/esm/guard.d.ts.map +1 -1
- package/dist/esm/guard.js +63 -0
- package/dist/esm/guard.js.map +1 -1
- package/dist/esm/index.d.ts +10 -1
- package/dist/esm/index.d.ts.map +1 -1
- package/dist/esm/index.js +18 -1
- package/dist/esm/index.js.map +1 -1
- package/dist/esm/next-agent-step.d.ts +50 -0
- package/dist/esm/next-agent-step.d.ts.map +1 -0
- package/dist/esm/next-agent-step.js +49 -0
- package/dist/esm/next-agent-step.js.map +1 -0
- package/dist/esm/offline-verify.d.ts +75 -0
- package/dist/esm/offline-verify.d.ts.map +1 -0
- package/dist/esm/offline-verify.js +70 -0
- package/dist/esm/offline-verify.js.map +1 -0
- package/dist/esm/policy.d.ts.map +1 -1
- package/dist/esm/policy.js +1 -1
- package/dist/esm/policy.js.map +1 -1
- package/dist/esm/types.d.ts +64 -1
- package/dist/esm/types.d.ts.map +1 -1
- package/dist/esm/unguarded-mutation.d.ts +76 -0
- package/dist/esm/unguarded-mutation.d.ts.map +1 -0
- package/dist/esm/unguarded-mutation.js +97 -0
- package/dist/esm/unguarded-mutation.js.map +1 -0
- package/dist/esm/vendor/VENDOR.sha256 +13 -0
- package/dist/esm/vendor/arity.js +53 -0
- package/dist/esm/vendor/verify.js +454 -0
- package/dist/esm/with-coderifts.d.ts +21 -0
- package/dist/esm/with-coderifts.d.ts.map +1 -1
- package/dist/esm/with-coderifts.js +40 -0
- package/dist/esm/with-coderifts.js.map +1 -1
- package/package.json +5 -4
|
@@ -0,0 +1,454 @@
|
|
|
1
|
+
'use strict';
|
|
2
|
+
|
|
3
|
+
/*
|
|
4
|
+
* CodeRifts chain-receipt verifier — PURE LIBRARY. Node >= 20, zero dependencies (node:crypto only).
|
|
5
|
+
*
|
|
6
|
+
* Verify the receipt yourself — offline, no live CodeRifts API call needed.
|
|
7
|
+
* The reference format is frozen in ./RECEIPT_FORMAT.md.
|
|
8
|
+
*
|
|
9
|
+
* THE SPLIT (1282-A'). This file used to be both the library and the command. The command half
|
|
10
|
+
* carried the parts a library must not have: a shebang, a `require.main` block, argument parsing,
|
|
11
|
+
* and `fetchKeyInfo` — a network call whose LEGACY single-key branch returns a key document with
|
|
12
|
+
* no status field, so a receipt signed by a key that was later revoked verified as current. Four
|
|
13
|
+
* repositories vendor this file as `src/verify.js`; every one of them was carrying that command,
|
|
14
|
+
* and none of them could ever run it.
|
|
15
|
+
*
|
|
16
|
+
* The command now lives in ./cli.js. `node cli.js <receipt> …` is the same CLI it always was.
|
|
17
|
+
* Library behaviour is BYTE-IDENTICAL: the cross-language corpus and the envelope-step vectors
|
|
18
|
+
* pass unmodified, which is the proof that this was a move and not a rewrite.
|
|
19
|
+
*
|
|
20
|
+
* WHAT STAYED, and why it is not an inconsistency: `loadKeyring` reads a registry from a URL **or
|
|
21
|
+
* a local file**, and three of the four vendoring repos call it with a FILE path — it is part of
|
|
22
|
+
* the library surface they depend on. `fetchKeyInfo` has no file branch and no consumer outside
|
|
23
|
+
* the command, so it left.
|
|
24
|
+
*
|
|
25
|
+
* CLI usage now:
|
|
26
|
+
* node cli.js <receipt> [--key pub.pem | --keys <url|file>] [--kid <kid>] [--fetch <url>]
|
|
27
|
+
* node cli.js --chain receipts.txt [--key pub.pem | --keys <url|file>] [--kid <kid>] [--fetch <url>]
|
|
28
|
+
*
|
|
29
|
+
* Key discovery: with no --key/--keys, keys are fetched from
|
|
30
|
+
* https://app.coderifts.com/.well-known/coderifts-keys.json (override with --fetch <url>).
|
|
31
|
+
* The fetch-and-resolve path accepts BOTH the registry array (active + retired)
|
|
32
|
+
* and the legacy single-key body from /api/v1/attestation/public-key.
|
|
33
|
+
* --keys resolves each receipt's key by kid from a registry
|
|
34
|
+
* ({ keys: [{ kid, public_key_pem, status, valid_from, retired_at }] }); accepts a URL or file.
|
|
35
|
+
*
|
|
36
|
+
* Output: JSON { valid, reason?, payload?, chain? } to stdout.
|
|
37
|
+
* Exit codes: 0 valid, 1 invalid, 2 usage error.
|
|
38
|
+
*
|
|
39
|
+
* Verification order (matches the reference taxonomy exactly):
|
|
40
|
+
* structure -> json -> kid -> signature
|
|
41
|
+
* Reasons: malformed_structure | bad_json | unknown_kid | signature_error | signature_mismatch
|
|
42
|
+
*/
|
|
43
|
+
|
|
44
|
+
const crypto = require('node:crypto');
|
|
45
|
+
const fs = require('node:fs');
|
|
46
|
+
const { split3ary } = require('./arity');
|
|
47
|
+
|
|
48
|
+
const DEFAULT_FETCH_URL = 'https://app.coderifts.com/.well-known/coderifts-keys.json';
|
|
49
|
+
const SIGNING_PREFIX = 'crchain.v1';
|
|
50
|
+
const MAX_SUPPORTED_V = 4;
|
|
51
|
+
/** ID104 — verification expiry leeway (ms). `exp + leeway < now` → VERIFIED_EXPIRED. */
|
|
52
|
+
const CLOCK_SKEW_LEEWAY_MS = 30_000;
|
|
53
|
+
|
|
54
|
+
/**
|
|
55
|
+
* 0s grace only when context DECLARES destructive AND production.
|
|
56
|
+
* Public verifier has `--environment` / envelope.environment; no `destructive`
|
|
57
|
+
* / `operation_class` field — never guess from operation labels.
|
|
58
|
+
*/
|
|
59
|
+
function expiryLeewayMs(_context) {
|
|
60
|
+
return CLOCK_SKEW_LEEWAY_MS;
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
function isExpiredAt(expiresAtMs, nowMs, context) {
|
|
64
|
+
if (!Number.isFinite(expiresAtMs) || !Number.isFinite(nowMs)) return false;
|
|
65
|
+
return (expiresAtMs + expiryLeewayMs(context)) < nowMs;
|
|
66
|
+
}
|
|
67
|
+
|
|
68
|
+
// Signed fields per the max version — the anti-downgrade delimiter guard rejects any that contain '|'.
|
|
69
|
+
const SIGNED_FIELDS = ['kid', 'fp', 'prev', 'caller', 'ts', 'reg', 'ir', 'expires_at', 'bh'];
|
|
70
|
+
|
|
71
|
+
function sha256hex(str) {
|
|
72
|
+
return crypto.createHash('sha256').update(String(str), 'utf8').digest('hex');
|
|
73
|
+
}
|
|
74
|
+
|
|
75
|
+
/**
|
|
76
|
+
* RFC 8785 (JCS) canonical JSON for our data domain — MUST match the issuer's src/canonical-json.js
|
|
77
|
+
* and verify.py's canonical_json byte-for-byte (ASCII keys, JSON.stringify scalars, sorted keys, no
|
|
78
|
+
* whitespace, reject NaN/Infinity/undefined). Used to recompute decision_body_hash from --envelope.
|
|
79
|
+
*/
|
|
80
|
+
function canonicalJson(value) {
|
|
81
|
+
if (value === null) return 'null';
|
|
82
|
+
const t = typeof value;
|
|
83
|
+
if (t === 'boolean' || t === 'string') return JSON.stringify(value);
|
|
84
|
+
if (t === 'number') {
|
|
85
|
+
if (!Number.isFinite(value)) throw new TypeError('canonicalJson: non-finite number');
|
|
86
|
+
return JSON.stringify(value);
|
|
87
|
+
}
|
|
88
|
+
if (t === 'undefined') throw new TypeError('canonicalJson: undefined');
|
|
89
|
+
if (Array.isArray(value)) return `[${value.map(canonicalJson).join(',')}]`;
|
|
90
|
+
if (t === 'object') {
|
|
91
|
+
const keys = Object.keys(value).sort();
|
|
92
|
+
return `{${keys.map((k) => `${JSON.stringify(k)}:${canonicalJson(value[k])}`).join(',')}}`;
|
|
93
|
+
}
|
|
94
|
+
throw new TypeError(`canonicalJson: unsupported type ${t}`);
|
|
95
|
+
}
|
|
96
|
+
|
|
97
|
+
/**
|
|
98
|
+
* Reconstruct the exact signed bytes from a parsed receipt body.
|
|
99
|
+
* v1 (v absent or 1): the base string.
|
|
100
|
+
* v2 (v === 2): base + '|' + reg.
|
|
101
|
+
* v3 (v === 3): base + '|' + reg + '|' + ir.
|
|
102
|
+
* v4 (v === 4): base + '|' + reg + '|' + ir + '|' + expires_at + '|' + bh.
|
|
103
|
+
* Field order matches the CodeRifts issuer exactly (chain-attestation.js signingInputV4).
|
|
104
|
+
* This string must be byte-identical to the Python implementation.
|
|
105
|
+
*/
|
|
106
|
+
function reconstructSignedInput(payload) {
|
|
107
|
+
const base = `${SIGNING_PREFIX}|${payload.kid}|${payload.fp}|${payload.prev}|${payload.caller}|${payload.ts}`;
|
|
108
|
+
if (payload.v === 4) return `${base}|${payload.reg}|${payload.ir}|${payload.expires_at}|${payload.bh}`;
|
|
109
|
+
if (payload.v === 3) return `${base}|${payload.reg}|${payload.ir}`;
|
|
110
|
+
if (payload.v === 2) return `${base}|${payload.reg}`;
|
|
111
|
+
return base;
|
|
112
|
+
}
|
|
113
|
+
|
|
114
|
+
/**
|
|
115
|
+
* Resolve the verification key for a parsed payload.
|
|
116
|
+
* Two modes:
|
|
117
|
+
* - keyring (from --keys): pick the entry whose kid matches payload.kid; an
|
|
118
|
+
* unlisted kid resolves to null (=> unknown_kid). A retired key still verifies
|
|
119
|
+
* so receipts issued before a rotation stay checkable.
|
|
120
|
+
* - single key (default): the one loaded key, gated by expectedKid when known.
|
|
121
|
+
* Returns a KeyObject, or null when the kid is not accepted.
|
|
122
|
+
*/
|
|
123
|
+
/**
|
|
124
|
+
* 1306(a) — accept the SERVED registry shape, not only a Map.
|
|
125
|
+
*
|
|
126
|
+
* MEASURED black-box 2026-09-02. `https://app.coderifts.com/.well-known/coderifts-keys.json`
|
|
127
|
+
* serves `{keys:[{kid, public_key_pem, status, ...}]}`. Passing that document straight into
|
|
128
|
+
* `verifyReceipt(token, {ctx:{keyring}})` threw `TypeError: ctx.keyring.get is not a function` —
|
|
129
|
+
* the library path rejected the exact bytes this project publishes for it, while the CLI's
|
|
130
|
+
* `--keys <url>` worked because it converts first.
|
|
131
|
+
*
|
|
132
|
+
* The fix is here rather than in a caller: every consumer that fetches the public registry would
|
|
133
|
+
* otherwise have to know to convert, and the ones that do not find out with a TypeError rather
|
|
134
|
+
* than a verdict. `expectedKid` is a separate trap on the same path and is NOT papered over —
|
|
135
|
+
* `{keyring}` with no `expectedKid` still resolves to null by design (see resolveEntry), because
|
|
136
|
+
* silently defaulting a kid gate is a different and worse behaviour than a clear TypeError.
|
|
137
|
+
*/
|
|
138
|
+
function coerceKeyring(k, source) {
|
|
139
|
+
if (!k) return k;
|
|
140
|
+
if (typeof k.get === 'function') return k; // already a Map
|
|
141
|
+
if (Array.isArray(k.keys) || Array.isArray(k)) {
|
|
142
|
+
return keyringFromDocument(Array.isArray(k) ? { keys: k } : k, source || 'ctx.keyring');
|
|
143
|
+
}
|
|
144
|
+
return k;
|
|
145
|
+
}
|
|
146
|
+
|
|
147
|
+
function resolveEntry(ctx, payload) {
|
|
148
|
+
if (ctx.keyring) {
|
|
149
|
+
const entry = ctx.keyring.get(payload.kid);
|
|
150
|
+
if (!entry) return null;
|
|
151
|
+
if (ctx.expectedKid !== null && payload.kid !== ctx.expectedKid) return null;
|
|
152
|
+
return entry; // { publicKey, status, retired_at }
|
|
153
|
+
}
|
|
154
|
+
if (ctx.expectedKid !== null && payload.kid !== ctx.expectedKid) return null;
|
|
155
|
+
return { publicKey: ctx.publicKey, status: null, retired_at: null, revoked_at: null, compromised_at: null };
|
|
156
|
+
}
|
|
157
|
+
|
|
158
|
+
/**
|
|
159
|
+
* Derive the 12-status taxonomy verdict for an already-signature-valid receipt.
|
|
160
|
+
* RETIRED_KEY_VALID_AT_ISSUE — retired key, receipt ts predates retired_at (else INVALID_SIGNATURE).
|
|
161
|
+
* UNSUPPORTED_VERSION — payload.v beyond MAX_SUPPORTED_V.
|
|
162
|
+
* VERIFIED_EXPIRED — v4 receipt whose signed expires_at + 30s leeway is in the past.
|
|
163
|
+
* VERIFIED_WRONG_AUDIENCE / _WRONG_ENVIRONMENT — dormant: only when --envelope carries the field
|
|
164
|
+
* AND a check input (--audience/--environment) is supplied.
|
|
165
|
+
* VERIFIED_SUPERSEDED / _SCOPE_MISMATCH — dormant: no check input defined this round.
|
|
166
|
+
* VERIFIED_CURRENT — otherwise.
|
|
167
|
+
*/
|
|
168
|
+
function deriveStatus(payload, entry, opts) {
|
|
169
|
+
if (typeof payload.v === 'number' && payload.v > MAX_SUPPORTED_V) return 'UNSUPPORTED_VERSION';
|
|
170
|
+
// FAIL CLOSED ON A STATUS WE DO NOT UNDERSTAND.
|
|
171
|
+
//
|
|
172
|
+
// MEASURED 2026-08-26: a registry entry with status "revoked" returned
|
|
173
|
+
// { valid: true, status: "VERIFIED_CURRENT" } here — the status was read for 'retired' and
|
|
174
|
+
// otherwise ignored, so anything else fell through to the healthy path. An operator who marked a
|
|
175
|
+
// stolen key revoked would have believed they had acted while this verifier kept accepting it.
|
|
176
|
+
// The app kernel and verify-attest/verify-toolset already reject an unknown status; these two
|
|
177
|
+
// did not, so the fleet disagreed about the same registry.
|
|
178
|
+
//
|
|
179
|
+
// This is a bug fix, not revocation: the revocation RULE (compromised_at, REVOKED_KEY /
|
|
180
|
+
// REVOKED_KEY_UNDECIDABLE) is a separate, larger change across eight verifiers. What lands here
|
|
181
|
+
// is only the direction of the unknown case. Safe by measurement: the live registry publishes
|
|
182
|
+
// 'active' only, so no real consumer changes behaviour.
|
|
183
|
+
const KNOWN_STATUSES = new Set(['active', 'retired', 'revoked', null, undefined]);
|
|
184
|
+
if (!KNOWN_STATUSES.has(entry.status)) {
|
|
185
|
+
return 'UNKNOWN_KEY_STATUS';
|
|
186
|
+
}
|
|
187
|
+
// 1079 B — OPTIONAL timestamps, additive. Absent both fields → this function continues
|
|
188
|
+
// exactly as before. Signing time is payload.ts (receipts have no iat).
|
|
189
|
+
// revoked_at = compromise: EVERY receipt under the key is invalid, including those
|
|
190
|
+
// whose ts predates revoked_at (the attacker chooses ts).
|
|
191
|
+
// retired_at = planned rotation: ts < retired_at stays on the existing path;
|
|
192
|
+
// ts >= retired_at is KEY_RETIRED_AFTER_SIGNING.
|
|
193
|
+
if (typeof entry.revoked_at === 'string' && entry.revoked_at.length > 0) {
|
|
194
|
+
return 'KEY_REVOKED';
|
|
195
|
+
}
|
|
196
|
+
if (typeof entry.retired_at === 'string' && entry.retired_at.length > 0 && payload.ts) {
|
|
197
|
+
const issued = Date.parse(payload.ts);
|
|
198
|
+
const retired = Date.parse(entry.retired_at);
|
|
199
|
+
if (Number.isFinite(issued) && Number.isFinite(retired) && issued >= retired) {
|
|
200
|
+
return 'KEY_RETIRED_AFTER_SIGNING';
|
|
201
|
+
}
|
|
202
|
+
}
|
|
203
|
+
// REVOKED — RECEIPT_FORMAT.md §7.1 (normative). The attacker chooses ts, so no timestamp may
|
|
204
|
+
// rehabilitate a revoked key's signature: BOTH outcomes are valid:false. UNDECIDABLE is not a
|
|
205
|
+
// softer valid; it reports that we cannot tell a legitimate pre-compromise receipt from a
|
|
206
|
+
// backdated forgery. A missing compromised_at means the whole key history is suspect.
|
|
207
|
+
if (entry.status === 'revoked') {
|
|
208
|
+
const at = entry.compromised_at;
|
|
209
|
+
if (typeof at !== 'string' || at.length === 0) return 'REVOKED_KEY_UNDECIDABLE';
|
|
210
|
+
const boundary = Date.parse(at);
|
|
211
|
+
const issued = Date.parse(payload.ts);
|
|
212
|
+
if (!Number.isFinite(boundary) || !Number.isFinite(issued)) return 'REVOKED_KEY_UNDECIDABLE';
|
|
213
|
+
return issued >= boundary ? 'REVOKED_KEY' : 'REVOKED_KEY_UNDECIDABLE';
|
|
214
|
+
}
|
|
215
|
+
if (entry.status === 'retired') {
|
|
216
|
+
if (entry.retired_at && payload.ts
|
|
217
|
+
&& Date.parse(payload.ts) < Date.parse(entry.retired_at)) {
|
|
218
|
+
return 'RETIRED_KEY_VALID_AT_ISSUE';
|
|
219
|
+
}
|
|
220
|
+
return 'INVALID_SIGNATURE'; // signed by a key already retired at issue -> reject
|
|
221
|
+
}
|
|
222
|
+
const now = opts.now != null ? opts.now : Date.now();
|
|
223
|
+
if (payload.v === 4 && typeof payload.expires_at === 'string') {
|
|
224
|
+
const exp = Date.parse(payload.expires_at);
|
|
225
|
+
const context = opts.envelope || { environment: opts.expectedEnvironment };
|
|
226
|
+
if (isExpiredAt(exp, now, context)) return 'VERIFIED_EXPIRED';
|
|
227
|
+
}
|
|
228
|
+
if (opts.envelope) {
|
|
229
|
+
const env = opts.envelope;
|
|
230
|
+
if (opts.expectedAudience != null && env.audience != null && env.audience !== opts.expectedAudience) {
|
|
231
|
+
return 'VERIFIED_WRONG_AUDIENCE';
|
|
232
|
+
}
|
|
233
|
+
if (opts.expectedEnvironment != null && env.environment != null && env.environment !== opts.expectedEnvironment) {
|
|
234
|
+
return 'VERIFIED_WRONG_ENVIRONMENT';
|
|
235
|
+
}
|
|
236
|
+
}
|
|
237
|
+
return 'VERIFIED_CURRENT';
|
|
238
|
+
}
|
|
239
|
+
|
|
240
|
+
/**
|
|
241
|
+
* Verify a single receipt token against a public key (or keyring) + expected kid.
|
|
242
|
+
* @param {string} token
|
|
243
|
+
* @param {{ publicKey?: import('crypto').KeyObject, keyring?: Map<string,{publicKey:import('crypto').KeyObject}>, expectedKid: (string|null) }} ctx
|
|
244
|
+
* @returns {{ valid: boolean, reason?: string, payload?: object }}
|
|
245
|
+
*/
|
|
246
|
+
function verifyReceiptInner(token, ctx, opts = {}) {
|
|
247
|
+
// 1. structure
|
|
248
|
+
if (typeof token !== 'string' || token.length === 0) {
|
|
249
|
+
return { valid: false, status: 'MALFORMED', reason: 'malformed_structure' };
|
|
250
|
+
}
|
|
251
|
+
const segments = token.split('.');
|
|
252
|
+
if (segments.length !== 2 || segments.some((s) => !s)) {
|
|
253
|
+
return { valid: false, status: 'MALFORMED', reason: 'malformed_structure' };
|
|
254
|
+
}
|
|
255
|
+
|
|
256
|
+
// 2. json
|
|
257
|
+
let payload;
|
|
258
|
+
try {
|
|
259
|
+
payload = JSON.parse(Buffer.from(segments[0], 'base64url').toString('utf8'));
|
|
260
|
+
} catch (_) {
|
|
261
|
+
return { valid: false, status: 'MALFORMED', reason: 'bad_json' };
|
|
262
|
+
}
|
|
263
|
+
if (!payload || typeof payload !== 'object' || Array.isArray(payload)) {
|
|
264
|
+
return { valid: false, status: 'MALFORMED', reason: 'bad_json' };
|
|
265
|
+
}
|
|
266
|
+
|
|
267
|
+
// 3. kid -- resolve the entry by kid (keyring) or gate the single key by expectedKid.
|
|
268
|
+
// 1306(a): a served `{keys:[...]}` document is accepted here as well as a Map.
|
|
269
|
+
const entry = resolveEntry(
|
|
270
|
+
ctx.keyring ? { ...ctx, keyring: coerceKeyring(ctx.keyring, 'ctx.keyring') } : ctx,
|
|
271
|
+
payload,
|
|
272
|
+
);
|
|
273
|
+
if (!entry) {
|
|
274
|
+
return { valid: false, status: 'UNKNOWN_KEY', reason: 'unknown_kid', payload };
|
|
275
|
+
}
|
|
276
|
+
|
|
277
|
+
// 4. signature (raw Ed25519 over the reconstructed UTF-8 bytes)
|
|
278
|
+
const sig = Buffer.from(segments[1], 'base64url');
|
|
279
|
+
let ok = false;
|
|
280
|
+
try {
|
|
281
|
+
ok = crypto.verify(null, Buffer.from(reconstructSignedInput(payload), 'utf8'), entry.publicKey, sig);
|
|
282
|
+
} catch (_) {
|
|
283
|
+
return { valid: false, status: 'INVALID_SIGNATURE', reason: 'signature_error', payload };
|
|
284
|
+
}
|
|
285
|
+
if (!ok) return { valid: false, status: 'INVALID_SIGNATURE', reason: 'signature_mismatch', payload };
|
|
286
|
+
|
|
287
|
+
// 5. anti-downgrade delimiter guard — a signed field containing '|' could re-split into a lower
|
|
288
|
+
// version whose reconstructed bytes collide with these. Legitimate fields never contain '|'.
|
|
289
|
+
for (const k of SIGNED_FIELDS) {
|
|
290
|
+
if (typeof payload[k] === 'string' && payload[k].includes('|')) {
|
|
291
|
+
return { valid: false, status: 'INVALID_SIGNATURE', reason: 'delimiter_in_field', payload };
|
|
292
|
+
}
|
|
293
|
+
}
|
|
294
|
+
|
|
295
|
+
// 6. envelope binding (v4): a supplied envelope's canonical body_hash MUST equal payload.bh.
|
|
296
|
+
if (opts.envelope && payload.v === 4) {
|
|
297
|
+
const rest = { ...opts.envelope };
|
|
298
|
+
delete rest.receipt;
|
|
299
|
+
delete rest.decision_body_hash;
|
|
300
|
+
const recomputed = 'sha256:' + sha256hex(canonicalJson(rest));
|
|
301
|
+
if (recomputed !== payload.bh) {
|
|
302
|
+
return { valid: false, status: 'INVALID_SIGNATURE', reason: 'body_hash_mismatch', payload };
|
|
303
|
+
}
|
|
304
|
+
}
|
|
305
|
+
|
|
306
|
+
// 7. taxonomy status (freshness / retirement / dormant field checks).
|
|
307
|
+
const status = deriveStatus(payload, entry, opts);
|
|
308
|
+
if (status === 'KEY_REVOKED') {
|
|
309
|
+
return { valid: false, status, reason: 'KEY_REVOKED', payload };
|
|
310
|
+
}
|
|
311
|
+
if (status === 'KEY_RETIRED_AFTER_SIGNING') {
|
|
312
|
+
return { valid: false, status, reason: 'KEY_RETIRED_AFTER_SIGNING', payload };
|
|
313
|
+
}
|
|
314
|
+
if (status === 'INVALID_SIGNATURE') {
|
|
315
|
+
return { valid: false, status, reason: 'retired_key_after_issue', payload };
|
|
316
|
+
}
|
|
317
|
+
const valid = status === 'VERIFIED_CURRENT' || status === 'RETIRED_KEY_VALID_AT_ISSUE';
|
|
318
|
+
// UNKNOWN_KEY_STATUS is deliberately absent from the valid set — see deriveStatus.
|
|
319
|
+
return { valid, status, payload };
|
|
320
|
+
}
|
|
321
|
+
|
|
322
|
+
/**
|
|
323
|
+
* Verify a chain of tokens (oldest first): every signature valid AND every non-genesis
|
|
324
|
+
* link's prev == 'sha256:' + sha256hex(previous token string).
|
|
325
|
+
*/
|
|
326
|
+
function verifyChainInner(tokens, ctx, opts = {}) {
|
|
327
|
+
const links = [];
|
|
328
|
+
let allValid = true;
|
|
329
|
+
let first = null;
|
|
330
|
+
|
|
331
|
+
for (let i = 0; i < tokens.length; i++) {
|
|
332
|
+
const token = tokens[i];
|
|
333
|
+
const res = verifyReceiptInner(token, ctx, opts);
|
|
334
|
+
const link = { index: i, signature_valid: res.valid };
|
|
335
|
+
if (!res.valid) link.reason = res.reason;
|
|
336
|
+
const prev = res.payload ? res.payload.prev : undefined;
|
|
337
|
+
if (res.payload) link.prev = prev;
|
|
338
|
+
|
|
339
|
+
if (i === 0) {
|
|
340
|
+
first = prev === 'null' ? 'genesis' : 'continuation';
|
|
341
|
+
link.role = first;
|
|
342
|
+
// A genesis needs prev === 'null'; a continuation links to a token we do not hold.
|
|
343
|
+
link.prev_ok = prev === 'null' ? true : null;
|
|
344
|
+
} else {
|
|
345
|
+
const expected = `sha256:${sha256hex(tokens[i - 1])}`;
|
|
346
|
+
link.expected_prev = expected;
|
|
347
|
+
link.prev_ok = prev === expected;
|
|
348
|
+
if (!link.prev_ok) allValid = false;
|
|
349
|
+
}
|
|
350
|
+
|
|
351
|
+
if (!res.valid) allValid = false;
|
|
352
|
+
links.push(link);
|
|
353
|
+
}
|
|
354
|
+
|
|
355
|
+
return { valid: allValid, chain: { length: tokens.length, first, links } };
|
|
356
|
+
}
|
|
357
|
+
|
|
358
|
+
// ---------------------------------------------------------------------------
|
|
359
|
+
// Key loading
|
|
360
|
+
// ---------------------------------------------------------------------------
|
|
361
|
+
|
|
362
|
+
function keyFromPem(pem) {
|
|
363
|
+
return crypto.createPublicKey(pem);
|
|
364
|
+
}
|
|
365
|
+
|
|
366
|
+
/**
|
|
367
|
+
* Build a kid -> key map from a registry document
|
|
368
|
+
* ({ keys: [{ kid, public_key_pem, status, valid_from, retired_at }] }).
|
|
369
|
+
* Returns null when `keys` is missing/empty so the caller can try the legacy
|
|
370
|
+
* single-key body. --keys still requires a non-empty keys[] (throws).
|
|
371
|
+
*/
|
|
372
|
+
function keyringFromDocument(doc, source) {
|
|
373
|
+
const keys = doc && Array.isArray(doc.keys) ? doc.keys : null;
|
|
374
|
+
if (!keys || keys.length === 0) return null;
|
|
375
|
+
const keyring = new Map();
|
|
376
|
+
for (const k of keys) {
|
|
377
|
+
if (!k || !k.kid || !k.public_key_pem) throw new Error(`registry entry missing kid/public_key_pem in ${source}`);
|
|
378
|
+
// compromised_at MUST be carried through: deriveStatus reads it for the revoked rule
|
|
379
|
+
// (RECEIPT_FORMAT.md 7.1). Dropping it here made the rule inert — every revoked key
|
|
380
|
+
// returned UNDECIDABLE regardless of ts, which looks implemented and decides nothing.
|
|
381
|
+
keyring.set(k.kid, {
|
|
382
|
+
publicKey: keyFromPem(k.public_key_pem),
|
|
383
|
+
status: k.status || null,
|
|
384
|
+
retired_at: k.retired_at || null,
|
|
385
|
+
revoked_at: k.revoked_at || null,
|
|
386
|
+
compromised_at: k.compromised_at || null,
|
|
387
|
+
});
|
|
388
|
+
}
|
|
389
|
+
return keyring;
|
|
390
|
+
}
|
|
391
|
+
|
|
392
|
+
function pickActiveFromKeyring(keyring) {
|
|
393
|
+
for (const [kid, entry] of keyring) {
|
|
394
|
+
if (entry.status === 'active') return { kid, entry };
|
|
395
|
+
}
|
|
396
|
+
const first = keyring.entries().next().value;
|
|
397
|
+
return first ? { kid: first[0], entry: first[1] } : null;
|
|
398
|
+
}
|
|
399
|
+
|
|
400
|
+
/**
|
|
401
|
+
* Build a kid -> key map from a CodeRifts key registry
|
|
402
|
+
* ({ keys: [{ kid, public_key_pem, status, valid_from, retired_at }] }). A --keys source may be
|
|
403
|
+
* an http(s) URL or a local file path. Both active and retired keys are loaded.
|
|
404
|
+
*/
|
|
405
|
+
async function loadKeyring(source) {
|
|
406
|
+
let text;
|
|
407
|
+
if (/^https?:\/\//i.test(source)) {
|
|
408
|
+
const res = await fetch(source, { headers: { Accept: 'application/json' } });
|
|
409
|
+
if (!res.ok) throw new Error(`fetch ${source} -> HTTP ${res.status}`);
|
|
410
|
+
text = await res.text();
|
|
411
|
+
} else {
|
|
412
|
+
text = fs.readFileSync(source, 'utf8');
|
|
413
|
+
}
|
|
414
|
+
const doc = JSON.parse(text);
|
|
415
|
+
const keyring = keyringFromDocument(doc, source);
|
|
416
|
+
if (!keyring) throw new Error(`no keys[] in registry ${source}`);
|
|
417
|
+
return keyring;
|
|
418
|
+
}
|
|
419
|
+
|
|
420
|
+
function verifyReceipt(token, second, third) {
|
|
421
|
+
const { ctx, opts } = split3ary('verifyReceipt', arguments.length, second, third);
|
|
422
|
+
return verifyReceiptInner(token, ctx, opts);
|
|
423
|
+
}
|
|
424
|
+
|
|
425
|
+
function verifyChain(tokens, second, third) {
|
|
426
|
+
const { ctx, opts } = split3ary('verifyChain', arguments.length, second, third);
|
|
427
|
+
return verifyChainInner(tokens, ctx, opts);
|
|
428
|
+
}
|
|
429
|
+
|
|
430
|
+
// Reusable API — require('./verify') imports the pure verify logic WITHOUT running the CLI. The
|
|
431
|
+
// GitHub Action + other embedders use these directly (verifyReceipt/verifyChain/deriveStatus/…);
|
|
432
|
+
// the receipt format + taxonomy are frozen in RECEIPT_FORMAT.md.
|
|
433
|
+
module.exports = {
|
|
434
|
+
verifyReceipt,
|
|
435
|
+
verifyChain,
|
|
436
|
+
deriveStatus,
|
|
437
|
+
resolveEntry,
|
|
438
|
+
reconstructSignedInput,
|
|
439
|
+
canonicalJson,
|
|
440
|
+
loadKeyring,
|
|
441
|
+
keyFromPem,
|
|
442
|
+
keyringFromDocument,
|
|
443
|
+
// Pure keyring helper. Exported for the command (cli.js fetchKeyInfo) after the split —
|
|
444
|
+
// it takes a keyring and returns the active entry; it performs no I/O.
|
|
445
|
+
pickActiveFromKeyring,
|
|
446
|
+
sha256hex,
|
|
447
|
+
DEFAULT_FETCH_URL,
|
|
448
|
+
SIGNING_PREFIX,
|
|
449
|
+
MAX_SUPPORTED_V,
|
|
450
|
+
SIGNED_FIELDS,
|
|
451
|
+
CLOCK_SKEW_LEEWAY_MS,
|
|
452
|
+
expiryLeewayMs,
|
|
453
|
+
isExpiredAt,
|
|
454
|
+
};
|
|
@@ -418,6 +418,27 @@ export type ReceiptThreadHandle = {
|
|
|
418
418
|
*/
|
|
419
419
|
lastSkipReason: () => ReceiptCursorSkipReason | null;
|
|
420
420
|
};
|
|
421
|
+
/**
|
|
422
|
+
* The unversioned spelling still resolves to `_V1`, and always will — see the type doc above:
|
|
423
|
+
* re-pointing it at a future `_V2` would silently move every existing caller, which is the
|
|
424
|
+
* migration this design refuses.
|
|
425
|
+
*
|
|
426
|
+
* SO THIS NOTICE PROMISES NOTHING. It does not say deprecated, it does not name a removal
|
|
427
|
+
* version, and it must not: the alias is permanent by design, and a notice announcing a removal
|
|
428
|
+
* that will never happen is a false statement shipped to every consumer on the alias. What it
|
|
429
|
+
* does is point at the name that carries the version, so a caller who wants the guarantee the
|
|
430
|
+
* suffix gives can ask for it by name.
|
|
431
|
+
*
|
|
432
|
+
* ONCE PER PROCESS, and the flag is module-level for the same reason `policy.ts` uses one: a
|
|
433
|
+
* per-call notice on a construction path that a host may run per request is noise, and noise is
|
|
434
|
+
* how a line stops being read.
|
|
435
|
+
*
|
|
436
|
+
* The wire value is untouched. `GUARD_PROFILE_WIRE_VALUE` stays the suffix-less string that four
|
|
437
|
+
* downstream modules compare against; this observes the spelling, it does not rewrite it.
|
|
438
|
+
*/
|
|
439
|
+
export declare const UNVERSIONED_PROFILE_NOTICE = "ENFORCING_STRICT is the unversioned spelling of ENFORCING_STRICT_V1; prefer the versioned name.";
|
|
440
|
+
/** Test-only reset. Exported so a test can assert the once-per-process behaviour more than once. */
|
|
441
|
+
export declare function _resetUnversionedProfileNoticeForTest(): void;
|
|
421
442
|
/**
|
|
422
443
|
* Wrap guardToolRegistry with a mandatory operation and a separately-computed composition assurance.
|
|
423
444
|
* Fails at CONSTRUCTION (never at first tool call) for a missing client, a missing/empty operation, an
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"with-coderifts.d.ts","sourceRoot":"","sources":["../../src/with-coderifts.ts"],"names":[],"mappings":"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
|
|
1
|
+
{"version":3,"file":"with-coderifts.d.ts","sourceRoot":"","sources":["../../src/with-coderifts.ts"],"names":[],"mappings":"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"}
|
|
@@ -70,9 +70,10 @@
|
|
|
70
70
|
* - `composition_assurance` is the narrower PRODUCT-level statement, computed SEPARATELY.
|
|
71
71
|
*/
|
|
72
72
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
73
|
-
exports.PROFILE_ENFORCING_ATOMIC_V2 = exports.PROFILE_ENFORCING_ATOMIC_V1 = exports.PROFILE_ENFORCING_STRICT_V1 = void 0;
|
|
73
|
+
exports.UNVERSIONED_PROFILE_NOTICE = exports.PROFILE_ENFORCING_ATOMIC_V2 = exports.PROFILE_ENFORCING_ATOMIC_V1 = exports.PROFILE_ENFORCING_STRICT_V1 = void 0;
|
|
74
74
|
exports.foldTableSettledCalls = foldTableSettledCalls;
|
|
75
75
|
exports.guardedFractionAmongRoutes = guardedFractionAmongRoutes;
|
|
76
|
+
exports._resetUnversionedProfileNoticeForTest = _resetUnversionedProfileNoticeForTest;
|
|
76
77
|
exports.withCodeRifts = withCodeRifts;
|
|
77
78
|
const tool_registry_js_1 = require("./tool-registry.js");
|
|
78
79
|
const execution_grant_js_1 = require("./execution-grant.js");
|
|
@@ -189,6 +190,43 @@ function coverageRank(coverage) {
|
|
|
189
190
|
function isEnforcingStrict(input) {
|
|
190
191
|
return input.profile === 'ENFORCING_STRICT' || input.profile === exports.PROFILE_ENFORCING_STRICT_V1;
|
|
191
192
|
}
|
|
193
|
+
/**
|
|
194
|
+
* The unversioned spelling still resolves to `_V1`, and always will — see the type doc above:
|
|
195
|
+
* re-pointing it at a future `_V2` would silently move every existing caller, which is the
|
|
196
|
+
* migration this design refuses.
|
|
197
|
+
*
|
|
198
|
+
* SO THIS NOTICE PROMISES NOTHING. It does not say deprecated, it does not name a removal
|
|
199
|
+
* version, and it must not: the alias is permanent by design, and a notice announcing a removal
|
|
200
|
+
* that will never happen is a false statement shipped to every consumer on the alias. What it
|
|
201
|
+
* does is point at the name that carries the version, so a caller who wants the guarantee the
|
|
202
|
+
* suffix gives can ask for it by name.
|
|
203
|
+
*
|
|
204
|
+
* ONCE PER PROCESS, and the flag is module-level for the same reason `policy.ts` uses one: a
|
|
205
|
+
* per-call notice on a construction path that a host may run per request is noise, and noise is
|
|
206
|
+
* how a line stops being read.
|
|
207
|
+
*
|
|
208
|
+
* The wire value is untouched. `GUARD_PROFILE_WIRE_VALUE` stays the suffix-less string that four
|
|
209
|
+
* downstream modules compare against; this observes the spelling, it does not rewrite it.
|
|
210
|
+
*/
|
|
211
|
+
exports.UNVERSIONED_PROFILE_NOTICE = 'ENFORCING_STRICT is the unversioned spelling of ENFORCING_STRICT_V1; prefer the versioned name.';
|
|
212
|
+
let noticedUnversionedProfile = false;
|
|
213
|
+
/** Test-only reset. Exported so a test can assert the once-per-process behaviour more than once. */
|
|
214
|
+
function _resetUnversionedProfileNoticeForTest() {
|
|
215
|
+
noticedUnversionedProfile = false;
|
|
216
|
+
}
|
|
217
|
+
/**
|
|
218
|
+
* Emitted through `console.warn`, matching `policy.ts`'s `defaultWarn` — the convention this
|
|
219
|
+
* package already has for an advisory that is not an error. Not `process.emitWarning`: that
|
|
220
|
+
* channel is where this package puts DeprecationWarnings, and this is explicitly not one.
|
|
221
|
+
*/
|
|
222
|
+
function noticeUnversionedProfileOnce(profile) {
|
|
223
|
+
if (profile !== 'ENFORCING_STRICT')
|
|
224
|
+
return;
|
|
225
|
+
if (noticedUnversionedProfile)
|
|
226
|
+
return;
|
|
227
|
+
noticedUnversionedProfile = true;
|
|
228
|
+
console.warn(exports.UNVERSIONED_PROFILE_NOTICE);
|
|
229
|
+
}
|
|
192
230
|
/**
|
|
193
231
|
* Explicit opt-downs that contradict ENFORCING_STRICT. Empty = no conflict.
|
|
194
232
|
* `unknownToolPolicy: 'reject'` is stricter than 'mutating' (unclassified throws) — not a weaken.
|
|
@@ -508,6 +546,9 @@ function withCodeRifts(input) {
|
|
|
508
546
|
if (!input || typeof input !== 'object') {
|
|
509
547
|
throw new Error('withCodeRifts: input object is required');
|
|
510
548
|
}
|
|
549
|
+
// The spelling is observed once, here, at the single entry point — before validation, because
|
|
550
|
+
// the notice is about which NAME was used and is true whether or not the rest of the input is.
|
|
551
|
+
noticeUnversionedProfileOnce(input.profile);
|
|
511
552
|
// Pre-registry input validation — collect ALL problems, throw once (a valid operation + client are
|
|
512
553
|
// required even to reach the registry, so these cannot be batched with the post-registry check).
|
|
513
554
|
const problems = [];
|