@orangecheck/agent-core 1.2.0 → 2.1.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/dist/canonical.js.map +1 -1
- package/dist/canonical.mjs.map +1 -1
- package/dist/index.d.mts +3 -2
- package/dist/index.d.ts +3 -2
- package/dist/index.js +38 -2
- package/dist/index.js.map +1 -1
- package/dist/index.mjs +38 -2
- package/dist/index.mjs.map +1 -1
- package/dist/scope.js +7 -2
- package/dist/scope.js.map +1 -1
- package/dist/scope.mjs +7 -2
- package/dist/scope.mjs.map +1 -1
- package/package.json +4 -4
- package/src/federation.test.ts +1 -0
- package/src/private-scope.test.ts +4 -3
- package/src/revocation-failclosed.test.ts +78 -0
- package/src/scope.test.ts +44 -0
- package/src/scope.ts +17 -2
- package/src/test-vectors.test.ts +5 -0
- package/src/verify.ts +56 -6
package/dist/scope.js
CHANGED
|
@@ -217,10 +217,15 @@ function rangeSubset(ex, g) {
|
|
|
217
217
|
if (!exRange || !gRange) return false;
|
|
218
218
|
return gRange.lo <= exRange.lo && exRange.hi <= gRange.hi;
|
|
219
219
|
}
|
|
220
|
+
function parseDecimalInteger(value) {
|
|
221
|
+
if (!/^-?\d+$/.test(value)) return null;
|
|
222
|
+
const n = Number(value);
|
|
223
|
+
return Number.isSafeInteger(n) ? n : null;
|
|
224
|
+
}
|
|
220
225
|
function opToRange(c) {
|
|
221
226
|
if (c.value === void 0) return null;
|
|
222
|
-
const n =
|
|
223
|
-
if (
|
|
227
|
+
const n = parseDecimalInteger(c.value);
|
|
228
|
+
if (n === null) return null;
|
|
224
229
|
switch (c.op) {
|
|
225
230
|
case "=":
|
|
226
231
|
return { lo: n, hi: n };
|
package/dist/scope.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"sources":["../src/scope.ts"],"names":[],"mappings":"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","file":"scope.js","sourcesContent":["// Scope grammar, canonicalization, and sub-scope relation. See SPEC.md §7.\n//\n// A scope is <product>:<verb>(<constraint-list>).\n// Constraints are <key><op><value>, op ∈ { =, !=, <, <=, >, >=, * }.\n// Canonical form: constraints sorted by key; no whitespace.\n\nexport type ScopeOp = '=' | '!=' | '<' | '<=' | '>' | '>=' | '*';\n\nexport interface ScopeConstraint {\n key: string;\n op: ScopeOp;\n /** `undefined` for the wildcard `*` op; otherwise the raw textual value (unquoted). */\n value: string | undefined;\n /** True if the value was supplied as a quoted string; preserved for round-trip fidelity. */\n quoted: boolean;\n}\n\nexport interface Scope {\n product: string;\n verb: string;\n constraints: ScopeConstraint[];\n}\n\n// ─────────────────────────────────────────────────────────────────────────────\n// Registered products/verbs (SPEC §7.3) and constraint keys (SPEC §7.6).\n// ─────────────────────────────────────────────────────────────────────────────\n\nexport const REGISTERED_SCOPES: Record<string, { keys: string[] }> = {\n 'lock:seal': { keys: ['recipient', 'mime', 'max_bytes'] },\n 'lock:chat': { keys: ['recipient', 'max_bytes_per_msg', 'max_msgs'] },\n 'stamp:sign': { keys: ['mime', 'max_bytes', 'content_hash_prefix'] },\n 'vote:cast': { keys: ['poll_id', 'choice'] },\n 'nostr:publish': { keys: ['kind', 'relay', 'max_bytes'] },\n 'http:request': { keys: ['origin', 'method', 'max_rps', 'max_bytes_out'] },\n 'ln:send': { keys: ['max_sats', 'node', 'max_fee_sats'] },\n 'mcp:invoke': { keys: ['server', 'tool', 'max_invocations'] },\n};\n\n/** Keys whose values are compared numerically for sub-scope ordering. */\nconst NUMERIC_KEYS = new Set<string>([\n 'max_bytes',\n 'max_bytes_per_msg',\n 'max_msgs',\n 'max_bytes_out',\n 'max_rps',\n 'max_sats',\n 'max_fee_sats',\n 'max_invocations',\n 'kind',\n]);\n\nconst IDENT_RE = /^[a-z][a-z0-9_]*$/;\nconst BARE_TOKEN_RE = /^[A-Za-z0-9_.:/@+\\-]+$/;\n\n// ─────────────────────────────────────────────────────────────────────────────\n// Parse\n// ─────────────────────────────────────────────────────────────────────────────\n\nexport class ScopeParseError extends Error {\n constructor(message: string) {\n super(message);\n this.name = 'ScopeParseError';\n }\n}\n\nexport function parseScope(input: string): Scope {\n if (typeof input !== 'string' || input.length === 0) {\n throw new ScopeParseError('scope must be a non-empty string');\n }\n if (/\\s/.test(input)) {\n throw new ScopeParseError(`scope may not contain whitespace: ${JSON.stringify(input)}`);\n }\n\n const colonIdx = input.indexOf(':');\n if (colonIdx < 0) throw new ScopeParseError('scope missing \"product:verb\" separator');\n\n const product = input.slice(0, colonIdx);\n if (!IDENT_RE.test(product)) throw new ScopeParseError(`invalid product: ${product}`);\n\n const rest = input.slice(colonIdx + 1);\n const parenIdx = rest.indexOf('(');\n\n let verb: string;\n let constraintText = '';\n if (parenIdx < 0) {\n verb = rest;\n } else {\n verb = rest.slice(0, parenIdx);\n if (!rest.endsWith(')')) throw new ScopeParseError('scope constraint list must end with \")\"');\n constraintText = rest.slice(parenIdx + 1, -1);\n }\n if (!IDENT_RE.test(verb)) throw new ScopeParseError(`invalid verb: ${verb}`);\n\n const constraints: ScopeConstraint[] = [];\n if (constraintText.length > 0) {\n for (const piece of splitTopLevelCommas(constraintText)) {\n constraints.push(parseConstraint(piece));\n }\n }\n\n // No duplicate keys.\n const seen = new Set<string>();\n for (const c of constraints) {\n if (seen.has(c.key)) throw new ScopeParseError(`duplicate constraint key: ${c.key}`);\n seen.add(c.key);\n }\n\n return { product, verb, constraints };\n}\n\nfunction splitTopLevelCommas(text: string): string[] {\n const out: string[] = [];\n let depth = 0;\n let inQuotes = false;\n let start = 0;\n for (let i = 0; i < text.length; i++) {\n const ch = text[i];\n if (inQuotes) {\n if (ch === '\\\\' && i + 1 < text.length) {\n i++;\n continue;\n }\n if (ch === '\"') inQuotes = false;\n continue;\n }\n if (ch === '\"') {\n inQuotes = true;\n continue;\n }\n if (ch === '(') depth++;\n else if (ch === ')') depth--;\n else if (ch === ',' && depth === 0) {\n out.push(text.slice(start, i));\n start = i + 1;\n }\n }\n out.push(text.slice(start));\n return out;\n}\n\nfunction parseConstraint(piece: string): ScopeConstraint {\n if (piece.length === 0) throw new ScopeParseError('empty constraint');\n\n // The `*` op (wildcard) is an op with no value. Recognized by \"key=*\" form.\n // SPEC uses \"key=*\"; we also accept \"key*\" as legacy alias.\n // Ops in descending length so \">=\" beats \">\" and \"!=\" beats \"!\".\n const OPS: ScopeOp[] = ['>=', '<=', '!=', '=', '>', '<'];\n\n // Special-case wildcard: \"key=*\" or \"key*\".\n const wildcardMatch = /^([a-z][a-z0-9_]*)(?:=\\*|\\*)$/.exec(piece);\n if (wildcardMatch) {\n return { key: wildcardMatch[1]!, op: '*', value: undefined, quoted: false };\n }\n\n for (const op of OPS) {\n const idx = piece.indexOf(op);\n if (idx <= 0) continue; // key must come first and be non-empty\n const key = piece.slice(0, idx);\n if (!IDENT_RE.test(key)) continue;\n const raw = piece.slice(idx + op.length);\n const { value, quoted } = parseValue(raw);\n return { key, op, value, quoted };\n }\n throw new ScopeParseError(`constraint missing operator: ${piece}`);\n}\n\nfunction parseValue(raw: string): { value: string; quoted: boolean } {\n if (raw.length === 0) throw new ScopeParseError('constraint value is empty');\n if (raw.startsWith('\"')) {\n if (!raw.endsWith('\"') || raw.length < 2) {\n throw new ScopeParseError(`unterminated quoted value: ${raw}`);\n }\n let v = '';\n for (let i = 1; i < raw.length - 1; i++) {\n const ch = raw[i]!;\n if (ch === '\\\\' && i + 1 < raw.length - 1) {\n const next = raw[++i]!;\n v += next;\n } else if (ch === '\"') {\n throw new ScopeParseError(`unescaped quote in value: ${raw}`);\n } else {\n v += ch;\n }\n }\n return { value: v, quoted: true };\n }\n if (!BARE_TOKEN_RE.test(raw)) {\n throw new ScopeParseError(`invalid bare-token value: ${JSON.stringify(raw)}`);\n }\n return { value: raw, quoted: false };\n}\n\n// ─────────────────────────────────────────────────────────────────────────────\n// Canonicalize\n// ─────────────────────────────────────────────────────────────────────────────\n\nexport function canonicalizeScope(scope: Scope): string {\n const sorted = [...scope.constraints].sort((a, b) => (a.key < b.key ? -1 : a.key > b.key ? 1 : 0));\n const parts = sorted.map(serializeConstraint);\n const inner = parts.join(',');\n return `${scope.product}:${scope.verb}${parts.length === 0 ? '' : `(${inner})`}`;\n}\n\nexport function canonicalizeScopeString(input: string): string {\n return canonicalizeScope(parseScope(input));\n}\n\nfunction serializeConstraint(c: ScopeConstraint): string {\n if (c.op === '*') return `${c.key}=*`;\n const v = c.quoted ? quoteValue(c.value ?? '') : c.value ?? '';\n return `${c.key}${c.op}${v}`;\n}\n\nfunction quoteValue(v: string): string {\n let out = '\"';\n for (const ch of v) {\n if (ch === '\"' || ch === '\\\\') out += '\\\\' + ch;\n else out += ch;\n }\n out += '\"';\n return out;\n}\n\n// ─────────────────────────────────────────────────────────────────────────────\n// Registry-based validation\n// ─────────────────────────────────────────────────────────────────────────────\n\nexport interface ValidationOptions {\n /**\n * Strict: reject unknown products/verbs and unknown constraint keys.\n * Permissive: accept unknown products/verbs; ignore unknown keys without treating them as wider.\n * Default: 'strict'.\n */\n mode?: 'strict' | 'permissive';\n}\n\nexport function validateScope(scope: Scope, options: ValidationOptions = {}): void {\n const mode = options.mode ?? 'strict';\n const reg = REGISTERED_SCOPES[`${scope.product}:${scope.verb}`];\n if (!reg) {\n if (mode === 'strict') {\n throw new ScopeParseError(`unregistered scope: ${scope.product}:${scope.verb}`);\n }\n return; // permissive: no further checks\n }\n const registered = new Set(reg.keys);\n for (const c of scope.constraints) {\n if (!registered.has(c.key)) {\n if (mode === 'strict') {\n throw new ScopeParseError(\n `unregistered constraint key for ${scope.product}:${scope.verb}: ${c.key}`\n );\n }\n // permissive: ignore\n }\n }\n}\n\n// ─────────────────────────────────────────────────────────────────────────────\n// Sub-scope relation (SPEC §7.4)\n// ─────────────────────────────────────────────────────────────────────────────\n\n/**\n * Is `exercised` a sub-scope of `granted`?\n * Returns true iff every constraint of `granted` admits the corresponding constraint\n * (or absence) in `exercised`, per SPEC §7.4.\n */\nexport function isSubScope(exercised: Scope, granted: Scope): boolean {\n if (exercised.product !== granted.product) return false;\n if (exercised.verb !== granted.verb) return false;\n\n const exIndex = new Map<string, ScopeConstraint>();\n for (const c of exercised.constraints) exIndex.set(c.key, c);\n\n for (const g of granted.constraints) {\n const ex = exIndex.get(g.key);\n if (g.op === '*') continue; // wildcard: no requirement\n\n if (g.op === '=') {\n if (!ex) return false;\n if (ex.op !== '=' || ex.value !== g.value) return false;\n continue;\n }\n\n if (g.op === '!=') {\n if (!ex) return false;\n if (ex.op === '=' && ex.value !== g.value) continue;\n if (ex.op === '!=' && ex.value === g.value) continue;\n return false;\n }\n\n // Ordered ops: >=, <=, >, <. Exercised's implied range must be ⊆ granted's.\n if (g.op === '<' || g.op === '<=' || g.op === '>' || g.op === '>=') {\n if (!ex) return false;\n if (!NUMERIC_KEYS.has(g.key)) return false;\n if (ex.op === '*') return false;\n if (ex.value === undefined || g.value === undefined) return false;\n if (!rangeSubset(ex, g)) return false;\n continue;\n }\n }\n return true;\n}\n\nfunction rangeSubset(ex: ScopeConstraint, g: ScopeConstraint): boolean {\n const exRange = opToRange(ex);\n const gRange = opToRange(g);\n if (!exRange || !gRange) return false;\n return gRange.lo <= exRange.lo && exRange.hi <= gRange.hi;\n}\n\nfunction opToRange(c: ScopeConstraint): { lo: number; hi: number } | null {\n if (c.value === undefined) return null;\n const n = Number(c.value);\n if (!Number.isFinite(n)) return null;\n switch (c.op) {\n case '=':\n return { lo: n, hi: n };\n case '<':\n return { lo: -Infinity, hi: n - 1 }; // integers only\n case '<=':\n return { lo: -Infinity, hi: n };\n case '>':\n return { lo: n + 1, hi: Infinity };\n case '>=':\n return { lo: n, hi: Infinity };\n default:\n return null;\n }\n}\n"]}
|
|
1
|
+
{"version":3,"sources":["../src/scope.ts"],"names":[],"mappings":"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","file":"scope.js","sourcesContent":["// Scope grammar, canonicalization, and sub-scope relation. See SPEC.md §7.\n//\n// A scope is <product>:<verb>(<constraint-list>).\n// Constraints are <key><op><value>, op ∈ { =, !=, <, <=, >, >=, * }.\n// Canonical form: constraints sorted by key; no whitespace.\n\nexport type ScopeOp = '=' | '!=' | '<' | '<=' | '>' | '>=' | '*';\n\nexport interface ScopeConstraint {\n key: string;\n op: ScopeOp;\n /** `undefined` for the wildcard `*` op; otherwise the raw textual value (unquoted). */\n value: string | undefined;\n /** True if the value was supplied as a quoted string; preserved for round-trip fidelity. */\n quoted: boolean;\n}\n\nexport interface Scope {\n product: string;\n verb: string;\n constraints: ScopeConstraint[];\n}\n\n// ─────────────────────────────────────────────────────────────────────────────\n// Registered products/verbs (SPEC §7.3) and constraint keys (SPEC §7.6).\n// ─────────────────────────────────────────────────────────────────────────────\n\nexport const REGISTERED_SCOPES: Record<string, { keys: string[] }> = {\n 'lock:seal': { keys: ['recipient', 'mime', 'max_bytes'] },\n 'lock:chat': { keys: ['recipient', 'max_bytes_per_msg', 'max_msgs'] },\n 'stamp:sign': { keys: ['mime', 'max_bytes', 'content_hash_prefix'] },\n 'vote:cast': { keys: ['poll_id', 'choice'] },\n 'nostr:publish': { keys: ['kind', 'relay', 'max_bytes'] },\n 'http:request': { keys: ['origin', 'method', 'max_rps', 'max_bytes_out'] },\n 'ln:send': { keys: ['max_sats', 'node', 'max_fee_sats'] },\n 'mcp:invoke': { keys: ['server', 'tool', 'max_invocations'] },\n};\n\n/** Keys whose values are compared numerically for sub-scope ordering. */\nconst NUMERIC_KEYS = new Set<string>([\n 'max_bytes',\n 'max_bytes_per_msg',\n 'max_msgs',\n 'max_bytes_out',\n 'max_rps',\n 'max_sats',\n 'max_fee_sats',\n 'max_invocations',\n 'kind',\n]);\n\nconst IDENT_RE = /^[a-z][a-z0-9_]*$/;\nconst BARE_TOKEN_RE = /^[A-Za-z0-9_.:/@+\\-]+$/;\n\n// ─────────────────────────────────────────────────────────────────────────────\n// Parse\n// ─────────────────────────────────────────────────────────────────────────────\n\nexport class ScopeParseError extends Error {\n constructor(message: string) {\n super(message);\n this.name = 'ScopeParseError';\n }\n}\n\nexport function parseScope(input: string): Scope {\n if (typeof input !== 'string' || input.length === 0) {\n throw new ScopeParseError('scope must be a non-empty string');\n }\n if (/\\s/.test(input)) {\n throw new ScopeParseError(`scope may not contain whitespace: ${JSON.stringify(input)}`);\n }\n\n const colonIdx = input.indexOf(':');\n if (colonIdx < 0) throw new ScopeParseError('scope missing \"product:verb\" separator');\n\n const product = input.slice(0, colonIdx);\n if (!IDENT_RE.test(product)) throw new ScopeParseError(`invalid product: ${product}`);\n\n const rest = input.slice(colonIdx + 1);\n const parenIdx = rest.indexOf('(');\n\n let verb: string;\n let constraintText = '';\n if (parenIdx < 0) {\n verb = rest;\n } else {\n verb = rest.slice(0, parenIdx);\n if (!rest.endsWith(')')) throw new ScopeParseError('scope constraint list must end with \")\"');\n constraintText = rest.slice(parenIdx + 1, -1);\n }\n if (!IDENT_RE.test(verb)) throw new ScopeParseError(`invalid verb: ${verb}`);\n\n const constraints: ScopeConstraint[] = [];\n if (constraintText.length > 0) {\n for (const piece of splitTopLevelCommas(constraintText)) {\n constraints.push(parseConstraint(piece));\n }\n }\n\n // No duplicate keys.\n const seen = new Set<string>();\n for (const c of constraints) {\n if (seen.has(c.key)) throw new ScopeParseError(`duplicate constraint key: ${c.key}`);\n seen.add(c.key);\n }\n\n return { product, verb, constraints };\n}\n\nfunction splitTopLevelCommas(text: string): string[] {\n const out: string[] = [];\n let depth = 0;\n let inQuotes = false;\n let start = 0;\n for (let i = 0; i < text.length; i++) {\n const ch = text[i];\n if (inQuotes) {\n if (ch === '\\\\' && i + 1 < text.length) {\n i++;\n continue;\n }\n if (ch === '\"') inQuotes = false;\n continue;\n }\n if (ch === '\"') {\n inQuotes = true;\n continue;\n }\n if (ch === '(') depth++;\n else if (ch === ')') depth--;\n else if (ch === ',' && depth === 0) {\n out.push(text.slice(start, i));\n start = i + 1;\n }\n }\n out.push(text.slice(start));\n return out;\n}\n\nfunction parseConstraint(piece: string): ScopeConstraint {\n if (piece.length === 0) throw new ScopeParseError('empty constraint');\n\n // The `*` op (wildcard) is an op with no value. Recognized by \"key=*\" form.\n // SPEC uses \"key=*\"; we also accept \"key*\" as legacy alias.\n // Ops in descending length so \">=\" beats \">\" and \"!=\" beats \"!\".\n const OPS: ScopeOp[] = ['>=', '<=', '!=', '=', '>', '<'];\n\n // Special-case wildcard: \"key=*\" or \"key*\".\n const wildcardMatch = /^([a-z][a-z0-9_]*)(?:=\\*|\\*)$/.exec(piece);\n if (wildcardMatch) {\n return { key: wildcardMatch[1]!, op: '*', value: undefined, quoted: false };\n }\n\n for (const op of OPS) {\n const idx = piece.indexOf(op);\n if (idx <= 0) continue; // key must come first and be non-empty\n const key = piece.slice(0, idx);\n if (!IDENT_RE.test(key)) continue;\n const raw = piece.slice(idx + op.length);\n const { value, quoted } = parseValue(raw);\n return { key, op, value, quoted };\n }\n throw new ScopeParseError(`constraint missing operator: ${piece}`);\n}\n\nfunction parseValue(raw: string): { value: string; quoted: boolean } {\n if (raw.length === 0) throw new ScopeParseError('constraint value is empty');\n if (raw.startsWith('\"')) {\n if (!raw.endsWith('\"') || raw.length < 2) {\n throw new ScopeParseError(`unterminated quoted value: ${raw}`);\n }\n let v = '';\n for (let i = 1; i < raw.length - 1; i++) {\n const ch = raw[i]!;\n if (ch === '\\\\' && i + 1 < raw.length - 1) {\n const next = raw[++i]!;\n v += next;\n } else if (ch === '\"') {\n throw new ScopeParseError(`unescaped quote in value: ${raw}`);\n } else {\n v += ch;\n }\n }\n return { value: v, quoted: true };\n }\n if (!BARE_TOKEN_RE.test(raw)) {\n throw new ScopeParseError(`invalid bare-token value: ${JSON.stringify(raw)}`);\n }\n return { value: raw, quoted: false };\n}\n\n// ─────────────────────────────────────────────────────────────────────────────\n// Canonicalize\n// ─────────────────────────────────────────────────────────────────────────────\n\nexport function canonicalizeScope(scope: Scope): string {\n const sorted = [...scope.constraints].sort((a, b) => (a.key < b.key ? -1 : a.key > b.key ? 1 : 0));\n const parts = sorted.map(serializeConstraint);\n const inner = parts.join(',');\n return `${scope.product}:${scope.verb}${parts.length === 0 ? '' : `(${inner})`}`;\n}\n\nexport function canonicalizeScopeString(input: string): string {\n return canonicalizeScope(parseScope(input));\n}\n\nfunction serializeConstraint(c: ScopeConstraint): string {\n if (c.op === '*') return `${c.key}=*`;\n const v = c.quoted ? quoteValue(c.value ?? '') : c.value ?? '';\n return `${c.key}${c.op}${v}`;\n}\n\nfunction quoteValue(v: string): string {\n let out = '\"';\n for (const ch of v) {\n if (ch === '\"' || ch === '\\\\') out += '\\\\' + ch;\n else out += ch;\n }\n out += '\"';\n return out;\n}\n\n// ─────────────────────────────────────────────────────────────────────────────\n// Registry-based validation\n// ─────────────────────────────────────────────────────────────────────────────\n\nexport interface ValidationOptions {\n /**\n * Strict: reject unknown products/verbs and unknown constraint keys.\n * Permissive: accept unknown products/verbs; ignore unknown keys without treating them as wider.\n * Default: 'strict'.\n */\n mode?: 'strict' | 'permissive';\n}\n\nexport function validateScope(scope: Scope, options: ValidationOptions = {}): void {\n const mode = options.mode ?? 'strict';\n const reg = REGISTERED_SCOPES[`${scope.product}:${scope.verb}`];\n if (!reg) {\n if (mode === 'strict') {\n throw new ScopeParseError(`unregistered scope: ${scope.product}:${scope.verb}`);\n }\n return; // permissive: no further checks\n }\n const registered = new Set(reg.keys);\n for (const c of scope.constraints) {\n if (!registered.has(c.key)) {\n if (mode === 'strict') {\n throw new ScopeParseError(\n `unregistered constraint key for ${scope.product}:${scope.verb}: ${c.key}`\n );\n }\n // permissive: ignore\n }\n }\n}\n\n// ─────────────────────────────────────────────────────────────────────────────\n// Sub-scope relation (SPEC §7.4)\n// ─────────────────────────────────────────────────────────────────────────────\n\n/**\n * Is `exercised` a sub-scope of `granted`?\n * Returns true iff every constraint of `granted` admits the corresponding constraint\n * (or absence) in `exercised`, per SPEC §7.4.\n */\nexport function isSubScope(exercised: Scope, granted: Scope): boolean {\n if (exercised.product !== granted.product) return false;\n if (exercised.verb !== granted.verb) return false;\n\n const exIndex = new Map<string, ScopeConstraint>();\n for (const c of exercised.constraints) exIndex.set(c.key, c);\n\n for (const g of granted.constraints) {\n const ex = exIndex.get(g.key);\n if (g.op === '*') continue; // wildcard: no requirement\n\n if (g.op === '=') {\n if (!ex) return false;\n if (ex.op !== '=' || ex.value !== g.value) return false;\n continue;\n }\n\n if (g.op === '!=') {\n if (!ex) return false;\n if (ex.op === '=' && ex.value !== g.value) continue;\n if (ex.op === '!=' && ex.value === g.value) continue;\n return false;\n }\n\n // Ordered ops: >=, <=, >, <. Exercised's implied range must be ⊆ granted's.\n if (g.op === '<' || g.op === '<=' || g.op === '>' || g.op === '>=') {\n if (!ex) return false;\n if (!NUMERIC_KEYS.has(g.key)) return false;\n if (ex.op === '*') return false;\n if (ex.value === undefined || g.value === undefined) return false;\n if (!rangeSubset(ex, g)) return false;\n continue;\n }\n }\n return true;\n}\n\nfunction rangeSubset(ex: ScopeConstraint, g: ScopeConstraint): boolean {\n const exRange = opToRange(ex);\n const gRange = opToRange(g);\n if (!exRange || !gRange) return false;\n return gRange.lo <= exRange.lo && exRange.hi <= gRange.hi;\n}\n\n/**\n * SPEC §7.4: \"values MUST be decimal integers for ordered ops\".\n *\n * `Number()` is far more permissive than that and accepts `1e3`, `0x3e8`,\n * `+1000`, `\" 1000 \"`, `1.5` — and `\"\"`, which becomes 0, turning an empty\n * constraint into a silent zero bound. A verifier using a strict decimal parser\n * rejects every one of those, so the same bytes produced different accept/reject\n * answers on different implementations. SECURITY §5 requires they not.\n */\nfunction parseDecimalInteger(value: string): number | null {\n if (!/^-?\\d+$/.test(value)) return null;\n const n = Number(value);\n return Number.isSafeInteger(n) ? n : null;\n}\n\nfunction opToRange(c: ScopeConstraint): { lo: number; hi: number } | null {\n if (c.value === undefined) return null;\n const n = parseDecimalInteger(c.value);\n if (n === null) return null;\n switch (c.op) {\n case '=':\n return { lo: n, hi: n };\n case '<':\n return { lo: -Infinity, hi: n - 1 }; // integers only\n case '<=':\n return { lo: -Infinity, hi: n };\n case '>':\n return { lo: n + 1, hi: Infinity };\n case '>=':\n return { lo: n, hi: Infinity };\n default:\n return null;\n }\n}\n"]}
|
package/dist/scope.mjs
CHANGED
|
@@ -215,10 +215,15 @@ function rangeSubset(ex, g) {
|
|
|
215
215
|
if (!exRange || !gRange) return false;
|
|
216
216
|
return gRange.lo <= exRange.lo && exRange.hi <= gRange.hi;
|
|
217
217
|
}
|
|
218
|
+
function parseDecimalInteger(value) {
|
|
219
|
+
if (!/^-?\d+$/.test(value)) return null;
|
|
220
|
+
const n = Number(value);
|
|
221
|
+
return Number.isSafeInteger(n) ? n : null;
|
|
222
|
+
}
|
|
218
223
|
function opToRange(c) {
|
|
219
224
|
if (c.value === void 0) return null;
|
|
220
|
-
const n =
|
|
221
|
-
if (
|
|
225
|
+
const n = parseDecimalInteger(c.value);
|
|
226
|
+
if (n === null) return null;
|
|
222
227
|
switch (c.op) {
|
|
223
228
|
case "=":
|
|
224
229
|
return { lo: n, hi: n };
|
package/dist/scope.mjs.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"sources":["../src/scope.ts"],"names":[],"mappings":"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","file":"scope.mjs","sourcesContent":["// Scope grammar, canonicalization, and sub-scope relation. See SPEC.md §7.\n//\n// A scope is <product>:<verb>(<constraint-list>).\n// Constraints are <key><op><value>, op ∈ { =, !=, <, <=, >, >=, * }.\n// Canonical form: constraints sorted by key; no whitespace.\n\nexport type ScopeOp = '=' | '!=' | '<' | '<=' | '>' | '>=' | '*';\n\nexport interface ScopeConstraint {\n key: string;\n op: ScopeOp;\n /** `undefined` for the wildcard `*` op; otherwise the raw textual value (unquoted). */\n value: string | undefined;\n /** True if the value was supplied as a quoted string; preserved for round-trip fidelity. */\n quoted: boolean;\n}\n\nexport interface Scope {\n product: string;\n verb: string;\n constraints: ScopeConstraint[];\n}\n\n// ─────────────────────────────────────────────────────────────────────────────\n// Registered products/verbs (SPEC §7.3) and constraint keys (SPEC §7.6).\n// ─────────────────────────────────────────────────────────────────────────────\n\nexport const REGISTERED_SCOPES: Record<string, { keys: string[] }> = {\n 'lock:seal': { keys: ['recipient', 'mime', 'max_bytes'] },\n 'lock:chat': { keys: ['recipient', 'max_bytes_per_msg', 'max_msgs'] },\n 'stamp:sign': { keys: ['mime', 'max_bytes', 'content_hash_prefix'] },\n 'vote:cast': { keys: ['poll_id', 'choice'] },\n 'nostr:publish': { keys: ['kind', 'relay', 'max_bytes'] },\n 'http:request': { keys: ['origin', 'method', 'max_rps', 'max_bytes_out'] },\n 'ln:send': { keys: ['max_sats', 'node', 'max_fee_sats'] },\n 'mcp:invoke': { keys: ['server', 'tool', 'max_invocations'] },\n};\n\n/** Keys whose values are compared numerically for sub-scope ordering. */\nconst NUMERIC_KEYS = new Set<string>([\n 'max_bytes',\n 'max_bytes_per_msg',\n 'max_msgs',\n 'max_bytes_out',\n 'max_rps',\n 'max_sats',\n 'max_fee_sats',\n 'max_invocations',\n 'kind',\n]);\n\nconst IDENT_RE = /^[a-z][a-z0-9_]*$/;\nconst BARE_TOKEN_RE = /^[A-Za-z0-9_.:/@+\\-]+$/;\n\n// ─────────────────────────────────────────────────────────────────────────────\n// Parse\n// ─────────────────────────────────────────────────────────────────────────────\n\nexport class ScopeParseError extends Error {\n constructor(message: string) {\n super(message);\n this.name = 'ScopeParseError';\n }\n}\n\nexport function parseScope(input: string): Scope {\n if (typeof input !== 'string' || input.length === 0) {\n throw new ScopeParseError('scope must be a non-empty string');\n }\n if (/\\s/.test(input)) {\n throw new ScopeParseError(`scope may not contain whitespace: ${JSON.stringify(input)}`);\n }\n\n const colonIdx = input.indexOf(':');\n if (colonIdx < 0) throw new ScopeParseError('scope missing \"product:verb\" separator');\n\n const product = input.slice(0, colonIdx);\n if (!IDENT_RE.test(product)) throw new ScopeParseError(`invalid product: ${product}`);\n\n const rest = input.slice(colonIdx + 1);\n const parenIdx = rest.indexOf('(');\n\n let verb: string;\n let constraintText = '';\n if (parenIdx < 0) {\n verb = rest;\n } else {\n verb = rest.slice(0, parenIdx);\n if (!rest.endsWith(')')) throw new ScopeParseError('scope constraint list must end with \")\"');\n constraintText = rest.slice(parenIdx + 1, -1);\n }\n if (!IDENT_RE.test(verb)) throw new ScopeParseError(`invalid verb: ${verb}`);\n\n const constraints: ScopeConstraint[] = [];\n if (constraintText.length > 0) {\n for (const piece of splitTopLevelCommas(constraintText)) {\n constraints.push(parseConstraint(piece));\n }\n }\n\n // No duplicate keys.\n const seen = new Set<string>();\n for (const c of constraints) {\n if (seen.has(c.key)) throw new ScopeParseError(`duplicate constraint key: ${c.key}`);\n seen.add(c.key);\n }\n\n return { product, verb, constraints };\n}\n\nfunction splitTopLevelCommas(text: string): string[] {\n const out: string[] = [];\n let depth = 0;\n let inQuotes = false;\n let start = 0;\n for (let i = 0; i < text.length; i++) {\n const ch = text[i];\n if (inQuotes) {\n if (ch === '\\\\' && i + 1 < text.length) {\n i++;\n continue;\n }\n if (ch === '\"') inQuotes = false;\n continue;\n }\n if (ch === '\"') {\n inQuotes = true;\n continue;\n }\n if (ch === '(') depth++;\n else if (ch === ')') depth--;\n else if (ch === ',' && depth === 0) {\n out.push(text.slice(start, i));\n start = i + 1;\n }\n }\n out.push(text.slice(start));\n return out;\n}\n\nfunction parseConstraint(piece: string): ScopeConstraint {\n if (piece.length === 0) throw new ScopeParseError('empty constraint');\n\n // The `*` op (wildcard) is an op with no value. Recognized by \"key=*\" form.\n // SPEC uses \"key=*\"; we also accept \"key*\" as legacy alias.\n // Ops in descending length so \">=\" beats \">\" and \"!=\" beats \"!\".\n const OPS: ScopeOp[] = ['>=', '<=', '!=', '=', '>', '<'];\n\n // Special-case wildcard: \"key=*\" or \"key*\".\n const wildcardMatch = /^([a-z][a-z0-9_]*)(?:=\\*|\\*)$/.exec(piece);\n if (wildcardMatch) {\n return { key: wildcardMatch[1]!, op: '*', value: undefined, quoted: false };\n }\n\n for (const op of OPS) {\n const idx = piece.indexOf(op);\n if (idx <= 0) continue; // key must come first and be non-empty\n const key = piece.slice(0, idx);\n if (!IDENT_RE.test(key)) continue;\n const raw = piece.slice(idx + op.length);\n const { value, quoted } = parseValue(raw);\n return { key, op, value, quoted };\n }\n throw new ScopeParseError(`constraint missing operator: ${piece}`);\n}\n\nfunction parseValue(raw: string): { value: string; quoted: boolean } {\n if (raw.length === 0) throw new ScopeParseError('constraint value is empty');\n if (raw.startsWith('\"')) {\n if (!raw.endsWith('\"') || raw.length < 2) {\n throw new ScopeParseError(`unterminated quoted value: ${raw}`);\n }\n let v = '';\n for (let i = 1; i < raw.length - 1; i++) {\n const ch = raw[i]!;\n if (ch === '\\\\' && i + 1 < raw.length - 1) {\n const next = raw[++i]!;\n v += next;\n } else if (ch === '\"') {\n throw new ScopeParseError(`unescaped quote in value: ${raw}`);\n } else {\n v += ch;\n }\n }\n return { value: v, quoted: true };\n }\n if (!BARE_TOKEN_RE.test(raw)) {\n throw new ScopeParseError(`invalid bare-token value: ${JSON.stringify(raw)}`);\n }\n return { value: raw, quoted: false };\n}\n\n// ─────────────────────────────────────────────────────────────────────────────\n// Canonicalize\n// ─────────────────────────────────────────────────────────────────────────────\n\nexport function canonicalizeScope(scope: Scope): string {\n const sorted = [...scope.constraints].sort((a, b) => (a.key < b.key ? -1 : a.key > b.key ? 1 : 0));\n const parts = sorted.map(serializeConstraint);\n const inner = parts.join(',');\n return `${scope.product}:${scope.verb}${parts.length === 0 ? '' : `(${inner})`}`;\n}\n\nexport function canonicalizeScopeString(input: string): string {\n return canonicalizeScope(parseScope(input));\n}\n\nfunction serializeConstraint(c: ScopeConstraint): string {\n if (c.op === '*') return `${c.key}=*`;\n const v = c.quoted ? quoteValue(c.value ?? '') : c.value ?? '';\n return `${c.key}${c.op}${v}`;\n}\n\nfunction quoteValue(v: string): string {\n let out = '\"';\n for (const ch of v) {\n if (ch === '\"' || ch === '\\\\') out += '\\\\' + ch;\n else out += ch;\n }\n out += '\"';\n return out;\n}\n\n// ─────────────────────────────────────────────────────────────────────────────\n// Registry-based validation\n// ─────────────────────────────────────────────────────────────────────────────\n\nexport interface ValidationOptions {\n /**\n * Strict: reject unknown products/verbs and unknown constraint keys.\n * Permissive: accept unknown products/verbs; ignore unknown keys without treating them as wider.\n * Default: 'strict'.\n */\n mode?: 'strict' | 'permissive';\n}\n\nexport function validateScope(scope: Scope, options: ValidationOptions = {}): void {\n const mode = options.mode ?? 'strict';\n const reg = REGISTERED_SCOPES[`${scope.product}:${scope.verb}`];\n if (!reg) {\n if (mode === 'strict') {\n throw new ScopeParseError(`unregistered scope: ${scope.product}:${scope.verb}`);\n }\n return; // permissive: no further checks\n }\n const registered = new Set(reg.keys);\n for (const c of scope.constraints) {\n if (!registered.has(c.key)) {\n if (mode === 'strict') {\n throw new ScopeParseError(\n `unregistered constraint key for ${scope.product}:${scope.verb}: ${c.key}`\n );\n }\n // permissive: ignore\n }\n }\n}\n\n// ─────────────────────────────────────────────────────────────────────────────\n// Sub-scope relation (SPEC §7.4)\n// ─────────────────────────────────────────────────────────────────────────────\n\n/**\n * Is `exercised` a sub-scope of `granted`?\n * Returns true iff every constraint of `granted` admits the corresponding constraint\n * (or absence) in `exercised`, per SPEC §7.4.\n */\nexport function isSubScope(exercised: Scope, granted: Scope): boolean {\n if (exercised.product !== granted.product) return false;\n if (exercised.verb !== granted.verb) return false;\n\n const exIndex = new Map<string, ScopeConstraint>();\n for (const c of exercised.constraints) exIndex.set(c.key, c);\n\n for (const g of granted.constraints) {\n const ex = exIndex.get(g.key);\n if (g.op === '*') continue; // wildcard: no requirement\n\n if (g.op === '=') {\n if (!ex) return false;\n if (ex.op !== '=' || ex.value !== g.value) return false;\n continue;\n }\n\n if (g.op === '!=') {\n if (!ex) return false;\n if (ex.op === '=' && ex.value !== g.value) continue;\n if (ex.op === '!=' && ex.value === g.value) continue;\n return false;\n }\n\n // Ordered ops: >=, <=, >, <. Exercised's implied range must be ⊆ granted's.\n if (g.op === '<' || g.op === '<=' || g.op === '>' || g.op === '>=') {\n if (!ex) return false;\n if (!NUMERIC_KEYS.has(g.key)) return false;\n if (ex.op === '*') return false;\n if (ex.value === undefined || g.value === undefined) return false;\n if (!rangeSubset(ex, g)) return false;\n continue;\n }\n }\n return true;\n}\n\nfunction rangeSubset(ex: ScopeConstraint, g: ScopeConstraint): boolean {\n const exRange = opToRange(ex);\n const gRange = opToRange(g);\n if (!exRange || !gRange) return false;\n return gRange.lo <= exRange.lo && exRange.hi <= gRange.hi;\n}\n\nfunction opToRange(c: ScopeConstraint): { lo: number; hi: number } | null {\n if (c.value === undefined) return null;\n const n = Number(c.value);\n if (!Number.isFinite(n)) return null;\n switch (c.op) {\n case '=':\n return { lo: n, hi: n };\n case '<':\n return { lo: -Infinity, hi: n - 1 }; // integers only\n case '<=':\n return { lo: -Infinity, hi: n };\n case '>':\n return { lo: n + 1, hi: Infinity };\n case '>=':\n return { lo: n, hi: Infinity };\n default:\n return null;\n }\n}\n"]}
|
|
1
|
+
{"version":3,"sources":["../src/scope.ts"],"names":[],"mappings":"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","file":"scope.mjs","sourcesContent":["// Scope grammar, canonicalization, and sub-scope relation. See SPEC.md §7.\n//\n// A scope is <product>:<verb>(<constraint-list>).\n// Constraints are <key><op><value>, op ∈ { =, !=, <, <=, >, >=, * }.\n// Canonical form: constraints sorted by key; no whitespace.\n\nexport type ScopeOp = '=' | '!=' | '<' | '<=' | '>' | '>=' | '*';\n\nexport interface ScopeConstraint {\n key: string;\n op: ScopeOp;\n /** `undefined` for the wildcard `*` op; otherwise the raw textual value (unquoted). */\n value: string | undefined;\n /** True if the value was supplied as a quoted string; preserved for round-trip fidelity. */\n quoted: boolean;\n}\n\nexport interface Scope {\n product: string;\n verb: string;\n constraints: ScopeConstraint[];\n}\n\n// ─────────────────────────────────────────────────────────────────────────────\n// Registered products/verbs (SPEC §7.3) and constraint keys (SPEC §7.6).\n// ─────────────────────────────────────────────────────────────────────────────\n\nexport const REGISTERED_SCOPES: Record<string, { keys: string[] }> = {\n 'lock:seal': { keys: ['recipient', 'mime', 'max_bytes'] },\n 'lock:chat': { keys: ['recipient', 'max_bytes_per_msg', 'max_msgs'] },\n 'stamp:sign': { keys: ['mime', 'max_bytes', 'content_hash_prefix'] },\n 'vote:cast': { keys: ['poll_id', 'choice'] },\n 'nostr:publish': { keys: ['kind', 'relay', 'max_bytes'] },\n 'http:request': { keys: ['origin', 'method', 'max_rps', 'max_bytes_out'] },\n 'ln:send': { keys: ['max_sats', 'node', 'max_fee_sats'] },\n 'mcp:invoke': { keys: ['server', 'tool', 'max_invocations'] },\n};\n\n/** Keys whose values are compared numerically for sub-scope ordering. */\nconst NUMERIC_KEYS = new Set<string>([\n 'max_bytes',\n 'max_bytes_per_msg',\n 'max_msgs',\n 'max_bytes_out',\n 'max_rps',\n 'max_sats',\n 'max_fee_sats',\n 'max_invocations',\n 'kind',\n]);\n\nconst IDENT_RE = /^[a-z][a-z0-9_]*$/;\nconst BARE_TOKEN_RE = /^[A-Za-z0-9_.:/@+\\-]+$/;\n\n// ─────────────────────────────────────────────────────────────────────────────\n// Parse\n// ─────────────────────────────────────────────────────────────────────────────\n\nexport class ScopeParseError extends Error {\n constructor(message: string) {\n super(message);\n this.name = 'ScopeParseError';\n }\n}\n\nexport function parseScope(input: string): Scope {\n if (typeof input !== 'string' || input.length === 0) {\n throw new ScopeParseError('scope must be a non-empty string');\n }\n if (/\\s/.test(input)) {\n throw new ScopeParseError(`scope may not contain whitespace: ${JSON.stringify(input)}`);\n }\n\n const colonIdx = input.indexOf(':');\n if (colonIdx < 0) throw new ScopeParseError('scope missing \"product:verb\" separator');\n\n const product = input.slice(0, colonIdx);\n if (!IDENT_RE.test(product)) throw new ScopeParseError(`invalid product: ${product}`);\n\n const rest = input.slice(colonIdx + 1);\n const parenIdx = rest.indexOf('(');\n\n let verb: string;\n let constraintText = '';\n if (parenIdx < 0) {\n verb = rest;\n } else {\n verb = rest.slice(0, parenIdx);\n if (!rest.endsWith(')')) throw new ScopeParseError('scope constraint list must end with \")\"');\n constraintText = rest.slice(parenIdx + 1, -1);\n }\n if (!IDENT_RE.test(verb)) throw new ScopeParseError(`invalid verb: ${verb}`);\n\n const constraints: ScopeConstraint[] = [];\n if (constraintText.length > 0) {\n for (const piece of splitTopLevelCommas(constraintText)) {\n constraints.push(parseConstraint(piece));\n }\n }\n\n // No duplicate keys.\n const seen = new Set<string>();\n for (const c of constraints) {\n if (seen.has(c.key)) throw new ScopeParseError(`duplicate constraint key: ${c.key}`);\n seen.add(c.key);\n }\n\n return { product, verb, constraints };\n}\n\nfunction splitTopLevelCommas(text: string): string[] {\n const out: string[] = [];\n let depth = 0;\n let inQuotes = false;\n let start = 0;\n for (let i = 0; i < text.length; i++) {\n const ch = text[i];\n if (inQuotes) {\n if (ch === '\\\\' && i + 1 < text.length) {\n i++;\n continue;\n }\n if (ch === '\"') inQuotes = false;\n continue;\n }\n if (ch === '\"') {\n inQuotes = true;\n continue;\n }\n if (ch === '(') depth++;\n else if (ch === ')') depth--;\n else if (ch === ',' && depth === 0) {\n out.push(text.slice(start, i));\n start = i + 1;\n }\n }\n out.push(text.slice(start));\n return out;\n}\n\nfunction parseConstraint(piece: string): ScopeConstraint {\n if (piece.length === 0) throw new ScopeParseError('empty constraint');\n\n // The `*` op (wildcard) is an op with no value. Recognized by \"key=*\" form.\n // SPEC uses \"key=*\"; we also accept \"key*\" as legacy alias.\n // Ops in descending length so \">=\" beats \">\" and \"!=\" beats \"!\".\n const OPS: ScopeOp[] = ['>=', '<=', '!=', '=', '>', '<'];\n\n // Special-case wildcard: \"key=*\" or \"key*\".\n const wildcardMatch = /^([a-z][a-z0-9_]*)(?:=\\*|\\*)$/.exec(piece);\n if (wildcardMatch) {\n return { key: wildcardMatch[1]!, op: '*', value: undefined, quoted: false };\n }\n\n for (const op of OPS) {\n const idx = piece.indexOf(op);\n if (idx <= 0) continue; // key must come first and be non-empty\n const key = piece.slice(0, idx);\n if (!IDENT_RE.test(key)) continue;\n const raw = piece.slice(idx + op.length);\n const { value, quoted } = parseValue(raw);\n return { key, op, value, quoted };\n }\n throw new ScopeParseError(`constraint missing operator: ${piece}`);\n}\n\nfunction parseValue(raw: string): { value: string; quoted: boolean } {\n if (raw.length === 0) throw new ScopeParseError('constraint value is empty');\n if (raw.startsWith('\"')) {\n if (!raw.endsWith('\"') || raw.length < 2) {\n throw new ScopeParseError(`unterminated quoted value: ${raw}`);\n }\n let v = '';\n for (let i = 1; i < raw.length - 1; i++) {\n const ch = raw[i]!;\n if (ch === '\\\\' && i + 1 < raw.length - 1) {\n const next = raw[++i]!;\n v += next;\n } else if (ch === '\"') {\n throw new ScopeParseError(`unescaped quote in value: ${raw}`);\n } else {\n v += ch;\n }\n }\n return { value: v, quoted: true };\n }\n if (!BARE_TOKEN_RE.test(raw)) {\n throw new ScopeParseError(`invalid bare-token value: ${JSON.stringify(raw)}`);\n }\n return { value: raw, quoted: false };\n}\n\n// ─────────────────────────────────────────────────────────────────────────────\n// Canonicalize\n// ─────────────────────────────────────────────────────────────────────────────\n\nexport function canonicalizeScope(scope: Scope): string {\n const sorted = [...scope.constraints].sort((a, b) => (a.key < b.key ? -1 : a.key > b.key ? 1 : 0));\n const parts = sorted.map(serializeConstraint);\n const inner = parts.join(',');\n return `${scope.product}:${scope.verb}${parts.length === 0 ? '' : `(${inner})`}`;\n}\n\nexport function canonicalizeScopeString(input: string): string {\n return canonicalizeScope(parseScope(input));\n}\n\nfunction serializeConstraint(c: ScopeConstraint): string {\n if (c.op === '*') return `${c.key}=*`;\n const v = c.quoted ? quoteValue(c.value ?? '') : c.value ?? '';\n return `${c.key}${c.op}${v}`;\n}\n\nfunction quoteValue(v: string): string {\n let out = '\"';\n for (const ch of v) {\n if (ch === '\"' || ch === '\\\\') out += '\\\\' + ch;\n else out += ch;\n }\n out += '\"';\n return out;\n}\n\n// ─────────────────────────────────────────────────────────────────────────────\n// Registry-based validation\n// ─────────────────────────────────────────────────────────────────────────────\n\nexport interface ValidationOptions {\n /**\n * Strict: reject unknown products/verbs and unknown constraint keys.\n * Permissive: accept unknown products/verbs; ignore unknown keys without treating them as wider.\n * Default: 'strict'.\n */\n mode?: 'strict' | 'permissive';\n}\n\nexport function validateScope(scope: Scope, options: ValidationOptions = {}): void {\n const mode = options.mode ?? 'strict';\n const reg = REGISTERED_SCOPES[`${scope.product}:${scope.verb}`];\n if (!reg) {\n if (mode === 'strict') {\n throw new ScopeParseError(`unregistered scope: ${scope.product}:${scope.verb}`);\n }\n return; // permissive: no further checks\n }\n const registered = new Set(reg.keys);\n for (const c of scope.constraints) {\n if (!registered.has(c.key)) {\n if (mode === 'strict') {\n throw new ScopeParseError(\n `unregistered constraint key for ${scope.product}:${scope.verb}: ${c.key}`\n );\n }\n // permissive: ignore\n }\n }\n}\n\n// ─────────────────────────────────────────────────────────────────────────────\n// Sub-scope relation (SPEC §7.4)\n// ─────────────────────────────────────────────────────────────────────────────\n\n/**\n * Is `exercised` a sub-scope of `granted`?\n * Returns true iff every constraint of `granted` admits the corresponding constraint\n * (or absence) in `exercised`, per SPEC §7.4.\n */\nexport function isSubScope(exercised: Scope, granted: Scope): boolean {\n if (exercised.product !== granted.product) return false;\n if (exercised.verb !== granted.verb) return false;\n\n const exIndex = new Map<string, ScopeConstraint>();\n for (const c of exercised.constraints) exIndex.set(c.key, c);\n\n for (const g of granted.constraints) {\n const ex = exIndex.get(g.key);\n if (g.op === '*') continue; // wildcard: no requirement\n\n if (g.op === '=') {\n if (!ex) return false;\n if (ex.op !== '=' || ex.value !== g.value) return false;\n continue;\n }\n\n if (g.op === '!=') {\n if (!ex) return false;\n if (ex.op === '=' && ex.value !== g.value) continue;\n if (ex.op === '!=' && ex.value === g.value) continue;\n return false;\n }\n\n // Ordered ops: >=, <=, >, <. Exercised's implied range must be ⊆ granted's.\n if (g.op === '<' || g.op === '<=' || g.op === '>' || g.op === '>=') {\n if (!ex) return false;\n if (!NUMERIC_KEYS.has(g.key)) return false;\n if (ex.op === '*') return false;\n if (ex.value === undefined || g.value === undefined) return false;\n if (!rangeSubset(ex, g)) return false;\n continue;\n }\n }\n return true;\n}\n\nfunction rangeSubset(ex: ScopeConstraint, g: ScopeConstraint): boolean {\n const exRange = opToRange(ex);\n const gRange = opToRange(g);\n if (!exRange || !gRange) return false;\n return gRange.lo <= exRange.lo && exRange.hi <= gRange.hi;\n}\n\n/**\n * SPEC §7.4: \"values MUST be decimal integers for ordered ops\".\n *\n * `Number()` is far more permissive than that and accepts `1e3`, `0x3e8`,\n * `+1000`, `\" 1000 \"`, `1.5` — and `\"\"`, which becomes 0, turning an empty\n * constraint into a silent zero bound. A verifier using a strict decimal parser\n * rejects every one of those, so the same bytes produced different accept/reject\n * answers on different implementations. SECURITY §5 requires they not.\n */\nfunction parseDecimalInteger(value: string): number | null {\n if (!/^-?\\d+$/.test(value)) return null;\n const n = Number(value);\n return Number.isSafeInteger(n) ? n : null;\n}\n\nfunction opToRange(c: ScopeConstraint): { lo: number; hi: number } | null {\n if (c.value === undefined) return null;\n const n = parseDecimalInteger(c.value);\n if (n === null) return null;\n switch (c.op) {\n case '=':\n return { lo: n, hi: n };\n case '<':\n return { lo: -Infinity, hi: n - 1 }; // integers only\n case '<=':\n return { lo: -Infinity, hi: n };\n case '>':\n return { lo: n + 1, hi: Infinity };\n case '>=':\n return { lo: n, hi: Infinity };\n default:\n return null;\n }\n}\n"]}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@orangecheck/agent-core",
|
|
3
|
-
"version": "1.
|
|
3
|
+
"version": "2.1.0",
|
|
4
4
|
"description": "OC Agent canonical messages, envelope formats (delegation/action/revocation), scope grammar, and verification. See https://github.com/orangecheck/oc-agent-protocol.",
|
|
5
5
|
"keywords": [
|
|
6
6
|
"bitcoin",
|
|
@@ -64,14 +64,14 @@
|
|
|
64
64
|
},
|
|
65
65
|
"dependencies": {
|
|
66
66
|
"@noble/hashes": "^1.5.0",
|
|
67
|
-
"@orangecheck/
|
|
68
|
-
"@orangecheck/
|
|
67
|
+
"@orangecheck/lock-core": "^1.0.1",
|
|
68
|
+
"@orangecheck/stamp-core": "^1.0.0"
|
|
69
69
|
},
|
|
70
70
|
"devDependencies": {
|
|
71
71
|
"@types/node": "^22.10.2",
|
|
72
72
|
"tsup": "^8.3.5",
|
|
73
73
|
"typescript": "^5.7.2",
|
|
74
|
-
"vitest": "^
|
|
74
|
+
"vitest": "^4.1.11"
|
|
75
75
|
},
|
|
76
76
|
"publishConfig": {
|
|
77
77
|
"access": "public"
|
package/src/federation.test.ts
CHANGED
|
@@ -118,7 +118,7 @@ describe('verifyDelegation with v1.2 scopes_encrypted', () => {
|
|
|
118
118
|
const env = await buildPrivateEnvelope(['ln:send(max_sats<=100)']);
|
|
119
119
|
// Inject a public scopes field too — illegal.
|
|
120
120
|
const both = { ...env, scopes: ['ln:send(max_sats<=100)'] } as DelegationEnvelope;
|
|
121
|
-
const r = await verifyDelegation({ envelope: both, skipSignatureVerification: true });
|
|
121
|
+
const r = await verifyDelegation({ envelope: both, skipSignatureVerification: true, skipRevocationCheck: true });
|
|
122
122
|
expect(r.ok).toBe(false);
|
|
123
123
|
if (!r.ok) expect(r.code).toBe('E_SCOPES_BOTH_PROVIDED');
|
|
124
124
|
});
|
|
@@ -127,14 +127,14 @@ describe('verifyDelegation with v1.2 scopes_encrypted', () => {
|
|
|
127
127
|
const env = await buildPrivateEnvelope(['ln:send(max_sats<=100)']);
|
|
128
128
|
const neither = { ...env } as DelegationEnvelope;
|
|
129
129
|
delete neither.scopes_encrypted;
|
|
130
|
-
const r = await verifyDelegation({ envelope: neither, skipSignatureVerification: true });
|
|
130
|
+
const r = await verifyDelegation({ envelope: neither, skipSignatureVerification: true, skipRevocationCheck: true });
|
|
131
131
|
expect(r.ok).toBe(false);
|
|
132
132
|
if (!r.ok) expect(r.code).toBe('E_SCOPES_NEITHER_PROVIDED');
|
|
133
133
|
});
|
|
134
134
|
|
|
135
135
|
it('returns E_SCOPES_UNREADABLE when no decryption key is supplied', async () => {
|
|
136
136
|
const env = await buildPrivateEnvelope(['ln:send(max_sats<=100)']);
|
|
137
|
-
const r = await verifyDelegation({ envelope: env, skipSignatureVerification: true });
|
|
137
|
+
const r = await verifyDelegation({ envelope: env, skipSignatureVerification: true, skipRevocationCheck: true });
|
|
138
138
|
expect(r.ok).toBe(false);
|
|
139
139
|
if (!r.ok) expect(r.code).toBe('E_SCOPES_UNREADABLE');
|
|
140
140
|
});
|
|
@@ -144,6 +144,7 @@ describe('verifyDelegation with v1.2 scopes_encrypted', () => {
|
|
|
144
144
|
'ln:send(max_sats<=100)',
|
|
145
145
|
]);
|
|
146
146
|
const r = await verifyDelegation({
|
|
147
|
+
skipRevocationCheck: true,
|
|
147
148
|
envelope,
|
|
148
149
|
skipSignatureVerification: true,
|
|
149
150
|
decryptScopesWith: { device_id: 'agent', secretKey: agentSecret },
|
|
@@ -0,0 +1,78 @@
|
|
|
1
|
+
// SECURITY §7 item 7: "Query revocation feeds (Nostr kind-30085 by
|
|
2
|
+
// `#delegation`) for the cited delegation id before reporting OK, **unless the
|
|
3
|
+
// caller explicitly opts out** of revocation checking."
|
|
4
|
+
//
|
|
5
|
+
// The default was inverted. verifyDelegation had NO revocations parameter at
|
|
6
|
+
// all — a revoked delegation returned ok:true and a caller had no way to say
|
|
7
|
+
// otherwise. verifyAction accepted one but checked only `if (input.revocations
|
|
8
|
+
// && length > 0)`, so omitting it skipped the check silently and "checked,
|
|
9
|
+
// clean" was indistinguishable from "never checked".
|
|
10
|
+
//
|
|
11
|
+
// This library has no network by design, so the caller fetches the feed. What
|
|
12
|
+
// changed is that declining to is now a decision they have to state.
|
|
13
|
+
import { describe, expect, it } from 'vitest';
|
|
14
|
+
|
|
15
|
+
import { verifyDelegation } from './verify.js';
|
|
16
|
+
import type { DelegationEnvelope } from './types.js';
|
|
17
|
+
|
|
18
|
+
// Inlined rather than read from oc-agent-protocol/test-vectors: this test is
|
|
19
|
+
// about the revocation guard, not vector conformance, and a relative path into
|
|
20
|
+
// a sibling repo resolves locally but not in CI, where the spec repo is not
|
|
21
|
+
// checked out beside this one. test-vectors.test.ts is the place that depends
|
|
22
|
+
// on the real vectors, and CI checks them out for it.
|
|
23
|
+
const delegation: DelegationEnvelope = {
|
|
24
|
+
v: 1,
|
|
25
|
+
kind: 'agent-delegation',
|
|
26
|
+
id: '36d79600191db871baa3fc9aa3b5e77750a5c423b1f620ec26cf16bd122e19a7',
|
|
27
|
+
principal: { address: 'bc1qprincipal000000000000000000000000000000', alg: 'bip322' },
|
|
28
|
+
agent: { address: 'bc1qagent0000000000000000000000000000000000', alg: 'bip322' },
|
|
29
|
+
scopes: ['lock:seal(recipient=bc1qalice000000000000000000000000000000000)'],
|
|
30
|
+
bond: null,
|
|
31
|
+
issued_at: '2026-04-22T12:00:00Z',
|
|
32
|
+
expires_at: '2026-04-29T12:00:00Z',
|
|
33
|
+
nonce: '0123456789abcdef0123456789abcdef',
|
|
34
|
+
revocation: { holders: ['principal'], ref: null },
|
|
35
|
+
sig: { alg: 'bip322', pubkey: 'bc1qprincipal000000000000000000000000000000', value: 'AAAA' },
|
|
36
|
+
} as DelegationEnvelope;
|
|
37
|
+
|
|
38
|
+
const AT = new Date('2026-04-23T12:00:00Z'); // inside the delegation's window
|
|
39
|
+
|
|
40
|
+
describe('verifyDelegation fails closed on revocation', () => {
|
|
41
|
+
it('refuses when given neither revocations nor an explicit skip', async () => {
|
|
42
|
+
const r = await verifyDelegation({
|
|
43
|
+
envelope: delegation,
|
|
44
|
+
skipSignatureVerification: true,
|
|
45
|
+
now: AT,
|
|
46
|
+
});
|
|
47
|
+
expect(r.ok).toBe(false);
|
|
48
|
+
expect(r.ok === false && r.code).toBe('E_MALFORMED');
|
|
49
|
+
expect(r.ok === false && r.message).toMatch(/requires `revocations`/i);
|
|
50
|
+
});
|
|
51
|
+
|
|
52
|
+
it('accepts an EMPTY revocation list — "I looked, there were none"', async () => {
|
|
53
|
+
const r = await verifyDelegation({
|
|
54
|
+
envelope: delegation,
|
|
55
|
+
skipSignatureVerification: true,
|
|
56
|
+
now: AT,
|
|
57
|
+
revocations: [],
|
|
58
|
+
});
|
|
59
|
+
expect(r.ok).toBe(true);
|
|
60
|
+
});
|
|
61
|
+
|
|
62
|
+
it('accepts an explicit skip, for callers that genuinely cannot check', async () => {
|
|
63
|
+
const r = await verifyDelegation({
|
|
64
|
+
envelope: delegation,
|
|
65
|
+
skipSignatureVerification: true,
|
|
66
|
+
now: AT,
|
|
67
|
+
skipRevocationCheck: true,
|
|
68
|
+
});
|
|
69
|
+
expect(r.ok).toBe(true);
|
|
70
|
+
});
|
|
71
|
+
|
|
72
|
+
// An empty list and a skip both verify — but they mean different things, and
|
|
73
|
+
// only one of them is a claim about the feed. That distinction is the point
|
|
74
|
+
// of the change.
|
|
75
|
+
it('the two are not the same statement', () => {
|
|
76
|
+
expect('revocations: []').not.toBe('skipRevocationCheck: true');
|
|
77
|
+
});
|
|
78
|
+
});
|
package/src/scope.test.ts
CHANGED
|
@@ -160,3 +160,47 @@ describe('isSubScope (SPEC §7.4)', () => {
|
|
|
160
160
|
).toBe(false);
|
|
161
161
|
});
|
|
162
162
|
});
|
|
163
|
+
|
|
164
|
+
// SPEC §7.4: "values MUST be decimal integers for ordered ops". `Number()` is
|
|
165
|
+
// much looser and accepted 1e3, 0x3e8, +1000, " 1000 " and 1.5 — plus "", which
|
|
166
|
+
// becomes 0 and turns an empty constraint into a silent zero bound. A verifier
|
|
167
|
+
// with a strict decimal parser rejects all of them, so identical bytes produced
|
|
168
|
+
// different accept/reject answers across implementations, which SECURITY §5
|
|
169
|
+
// rules out.
|
|
170
|
+
describe('ordered-op values must be decimal integers (SPEC §7.4)', () => {
|
|
171
|
+
const granted = parseScope('ln:send(max_sats<=1000)');
|
|
172
|
+
|
|
173
|
+
it('still admits a plain decimal integer', () => {
|
|
174
|
+
expect(isSubScope(parseScope('ln:send(max_sats<=500)'), granted)).toBe(true);
|
|
175
|
+
expect(isSubScope(parseScope('ln:send(max_sats<=1000)'), granted)).toBe(true);
|
|
176
|
+
});
|
|
177
|
+
|
|
178
|
+
it('still rejects a decimal integer outside the granted range', () => {
|
|
179
|
+
expect(isSubScope(parseScope('ln:send(max_sats<=1001)'), granted)).toBe(false);
|
|
180
|
+
});
|
|
181
|
+
|
|
182
|
+
// Each of these previously compared numerically and could be ADMITTED.
|
|
183
|
+
for (const v of ['1e3', '0x3e8', '+1000', '1.5']) {
|
|
184
|
+
it(`rejects ${JSON.stringify(v)} rather than coercing it`, () => {
|
|
185
|
+
let exercised;
|
|
186
|
+
try {
|
|
187
|
+
exercised = parseScope(`ln:send(max_sats<=${v})`);
|
|
188
|
+
} catch {
|
|
189
|
+
return; // rejected at parse time is also a rejection
|
|
190
|
+
}
|
|
191
|
+
expect(isSubScope(exercised, granted)).toBe(false);
|
|
192
|
+
});
|
|
193
|
+
}
|
|
194
|
+
|
|
195
|
+
// The sharpest case: Number('') === 0, so an empty constraint became
|
|
196
|
+
// max_sats<=0 — a real bound nobody wrote.
|
|
197
|
+
it('does not silently read an empty value as zero', () => {
|
|
198
|
+
let exercised;
|
|
199
|
+
try {
|
|
200
|
+
exercised = parseScope('ln:send(max_sats<=)');
|
|
201
|
+
} catch {
|
|
202
|
+
return;
|
|
203
|
+
}
|
|
204
|
+
expect(isSubScope(exercised, parseScope('ln:send(max_sats<=0)'))).toBe(false);
|
|
205
|
+
});
|
|
206
|
+
});
|
package/src/scope.ts
CHANGED
|
@@ -309,10 +309,25 @@ function rangeSubset(ex: ScopeConstraint, g: ScopeConstraint): boolean {
|
|
|
309
309
|
return gRange.lo <= exRange.lo && exRange.hi <= gRange.hi;
|
|
310
310
|
}
|
|
311
311
|
|
|
312
|
+
/**
|
|
313
|
+
* SPEC §7.4: "values MUST be decimal integers for ordered ops".
|
|
314
|
+
*
|
|
315
|
+
* `Number()` is far more permissive than that and accepts `1e3`, `0x3e8`,
|
|
316
|
+
* `+1000`, `" 1000 "`, `1.5` — and `""`, which becomes 0, turning an empty
|
|
317
|
+
* constraint into a silent zero bound. A verifier using a strict decimal parser
|
|
318
|
+
* rejects every one of those, so the same bytes produced different accept/reject
|
|
319
|
+
* answers on different implementations. SECURITY §5 requires they not.
|
|
320
|
+
*/
|
|
321
|
+
function parseDecimalInteger(value: string): number | null {
|
|
322
|
+
if (!/^-?\d+$/.test(value)) return null;
|
|
323
|
+
const n = Number(value);
|
|
324
|
+
return Number.isSafeInteger(n) ? n : null;
|
|
325
|
+
}
|
|
326
|
+
|
|
312
327
|
function opToRange(c: ScopeConstraint): { lo: number; hi: number } | null {
|
|
313
328
|
if (c.value === undefined) return null;
|
|
314
|
-
const n =
|
|
315
|
-
if (
|
|
329
|
+
const n = parseDecimalInteger(c.value);
|
|
330
|
+
if (n === null) return null;
|
|
316
331
|
switch (c.op) {
|
|
317
332
|
case '=':
|
|
318
333
|
return { lo: n, hi: n };
|
package/src/test-vectors.test.ts
CHANGED
|
@@ -262,6 +262,7 @@ describe('oc-agent-protocol test vectors', () => {
|
|
|
262
262
|
it(`${name} — declared envelope passes verify() with skipSignatureVerification`, async () => {
|
|
263
263
|
if (data.kind === 'delegation') {
|
|
264
264
|
const r = await verifyDelegation({
|
|
265
|
+
skipRevocationCheck: true,
|
|
265
266
|
envelope: data.expected.envelope,
|
|
266
267
|
skipSignatureVerification: true,
|
|
267
268
|
skipTemporalCheck: true,
|
|
@@ -273,6 +274,7 @@ describe('oc-agent-protocol test vectors', () => {
|
|
|
273
274
|
throw new Error(`action vector ${name} references missing delegation ${data.inputs.delegation_id}`);
|
|
274
275
|
}
|
|
275
276
|
const r = await verifyAction({
|
|
277
|
+
skipRevocationCheck: true,
|
|
276
278
|
action: data.expected.envelope,
|
|
277
279
|
delegation,
|
|
278
280
|
skipSignatureVerification: true,
|
|
@@ -454,6 +456,7 @@ describe('oc-agent-protocol negative test vectors', () => {
|
|
|
454
456
|
sig: { alg: 'bip322', pubkey: v.inputs.address, value: 'AAAA' },
|
|
455
457
|
};
|
|
456
458
|
const r = await verifyAction({
|
|
459
|
+
skipRevocationCheck: true,
|
|
457
460
|
action,
|
|
458
461
|
delegation,
|
|
459
462
|
skipSignatureVerification: true,
|
|
@@ -704,6 +707,7 @@ describe('oc-agent-protocol private-scope vectors (v1.2)', () => {
|
|
|
704
707
|
);
|
|
705
708
|
}
|
|
706
709
|
const r = await verifyDelegation({
|
|
710
|
+
skipRevocationCheck: true,
|
|
707
711
|
envelope: env,
|
|
708
712
|
skipSignatureVerification: true,
|
|
709
713
|
skipTemporalCheck: true,
|
|
@@ -721,6 +725,7 @@ describe('oc-agent-protocol private-scope vectors (v1.2)', () => {
|
|
|
721
725
|
} else {
|
|
722
726
|
const negative = data as PrivateScopeNegativeVector;
|
|
723
727
|
const r = await verifyDelegation({
|
|
728
|
+
skipRevocationCheck: true,
|
|
724
729
|
envelope: env,
|
|
725
730
|
skipSignatureVerification: true,
|
|
726
731
|
skipTemporalCheck: true,
|