@tenuo/core 0.3.2-beta.0 → 0.3.4-beta.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/index.js.map CHANGED
@@ -1 +1 @@
1
- {"version":3,"file":"index.js","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAMA,OAAO,EACL,mBAAmB,EACnB,qBAAqB,EACrB,mBAAmB,EACnB,eAAe,GAChB,MAAM,WAAW,CAAC;AA4EnB,OAAO,EACL,qBAAqB,EACrB,wBAAwB,EACxB,uBAAuB,EACvB,UAAU,GACX,MAAM,aAAa,CAAC;AAErB,OAAO,EACL,GAAG,EACH,KAAK,EACL,GAAG,EACH,IAAI,EACJ,QAAQ,EACR,KAAK,EACL,KAAK,EACL,GAAG,EACH,GAAG,EACH,GAAG,EACH,QAAQ,EACR,KAAK,EACL,QAAQ,EACR,OAAO,EACP,KAAK,EACL,KAAK,EACL,KAAK,EACL,MAAM,EACN,KAAK,EACL,UAAU,EACV,OAAO,EACP,QAAQ,GACT,MAAM,kBAAkB,CAAC;AAE1B,OAAO,EAAE,gBAAgB,EAAE,MAAM,YAAY,CAAC;AAE9C,OAAO,EAAE,OAAO,IAAI,aAAa,EAAE,SAAS,EAAE,MAAM,cAAc,CAAC;AAEnE,OAAO,EAAE,YAAY,EAAE,iBAAiB,EAAE,MAAM,cAAc,CAAC;AAG/D,OAAO,EAAE,cAAc,EAAE,gBAAgB,EAAE,eAAe,EAAE,MAAM,eAAe,CAAC;AAElF,OAAO,EAAE,OAAO,EAAE,MAAM,cAAc,CAAC;AAGvC,OAAO,EAAE,WAAW,EAAE,MAAM,aAAa,CAAC","sourcesContent":["export type {\n ApprovalGateInspection,\n ApprovalRequirement,\n ApprovalRequirementStatus,\n} from \"./wasm.ts\";\n\nexport {\n approvalRequirement,\n evaluateApprovalGates,\n inspectApprovalGate,\n parseStrictJson,\n} from \"./wasm.ts\";\n\nexport type {\n AllConstraint,\n AllowPolicy,\n AnyOfConstraint,\n ApprovalContextAttestation,\n ApprovalInfo,\n ApprovalRequest,\n ApprovalRequestSummary,\n ArgApprovalGate,\n CelConstraint,\n CidrConstraint,\n Clearance,\n ConstraintExpr,\n ContainsConstraint,\n ControlPlaneApprovalRequestV1,\n ControlPlaneApprovalResponseV1,\n CreateTenuoOptions,\n Decision,\n DevRoot,\n EmailConstraint,\n ExactConstraint,\n ExecuteOptions,\n ExplainedField,\n Explanation,\n IssueInput,\n IssuerKey,\n MaxConstraint,\n McpAttachOptions,\n McpCallParams,\n McpHandlerPolicy,\n McpJsonRpcError,\n McpVerifyOptions,\n MinConstraint,\n NarrowInput,\n NarrowOptions,\n NonceStore,\n NotConstraint,\n NotOneOfConstraint,\n OneOfConstraint,\n PatternConstraint,\n PresentedCall,\n ProtectedTool,\n PublicKeyHandle,\n RangeConstraint,\n ReceiptChainInfo,\n ReceiptInfo,\n RegexConstraint,\n RequireApproval,\n RevocationListInfo,\n RevocationListInput,\n Session,\n SessionAllow,\n SessionFromWireInput,\n SessionInfo,\n SessionInput,\n ShlexConstraint,\n SignApprovalOptions,\n SubsetConstraint,\n Tenuo,\n LegacyTenuoErrorCode,\n ProtocolDenialCode,\n TenuoErrorCode,\n TenuoMcp,\n TenuoMcpMeta,\n ToolApprovalGate,\n ToolLike,\n ToolPolicy,\n UnderConstraint,\n UrlPatternConstraint,\n UrlSafeConstraint,\n WarrantPart,\n WildcardConstraint,\n} from \"./api.ts\";\n\nexport {\n ApprovalRequiredError,\n AuthorizationDeniedError,\n TenuoConfigurationError,\n TenuoError,\n} from \"./errors.ts\";\n\nexport {\n all,\n anyOf,\n cel,\n cidr,\n contains,\n email,\n exact,\n max,\n min,\n not,\n notOneOf,\n oneOf,\n pathGlob,\n pattern,\n range,\n regex,\n shlex,\n subset,\n under,\n urlPattern,\n urlSafe,\n wildcard,\n} from \"./constraints.ts\";\n\nexport { memoryNonceStore } from \"./nonce.ts\";\n\nexport { Session as SessionHandle, isSession } from \"./session.ts\";\n\nexport { ConnectToken, parseConnectToken } from \"./connect.ts\";\nexport type { ResolveEndpointOptions } from \"./connect.ts\";\n\nexport { HolderIdentity, generateIdentity, identityFromKey } from \"./identity.ts\";\n\nexport { Runtime } from \"./runtime.ts\";\nexport type { ReceiptsMode, RuntimeOptions, SessionWarrant } from \"./runtime.ts\";\n\nexport { createTenuo } from \"./client.ts\";\n"]}
1
+ {"version":3,"file":"index.js","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AASA,OAAO,EACL,mBAAmB,EACnB,UAAU,EACV,mBAAmB,EACnB,qBAAqB,EACrB,mBAAmB,EACnB,eAAe,GAChB,MAAM,WAAW,CAAC;AA4EnB,OAAO,EACL,qBAAqB,EACrB,wBAAwB,EACxB,uBAAuB,EACvB,UAAU,GACX,MAAM,aAAa,CAAC;AAErB,OAAO,EACL,GAAG,EACH,KAAK,EACL,GAAG,EACH,IAAI,EACJ,QAAQ,EACR,KAAK,EACL,KAAK,EACL,GAAG,EACH,GAAG,EACH,GAAG,EACH,QAAQ,EACR,KAAK,EACL,QAAQ,EACR,OAAO,EACP,KAAK,EACL,KAAK,EACL,KAAK,EACL,MAAM,EACN,KAAK,EACL,UAAU,EACV,OAAO,EACP,QAAQ,GACT,MAAM,kBAAkB,CAAC;AAE1B,OAAO,EAAE,gBAAgB,EAAE,MAAM,YAAY,CAAC;AAE9C,OAAO,EAAE,OAAO,IAAI,aAAa,EAAE,SAAS,EAAE,MAAM,cAAc,CAAC;AAEnE,OAAO,EAAE,YAAY,EAAE,iBAAiB,EAAE,MAAM,cAAc,CAAC;AAG/D,OAAO,EAAE,cAAc,EAAE,gBAAgB,EAAE,eAAe,EAAE,MAAM,eAAe,CAAC;AAElF,OAAO,EAAE,OAAO,EAAE,MAAM,cAAc,CAAC;AAGvC,OAAO,EAAE,WAAW,EAAE,MAAM,aAAa,CAAC","sourcesContent":["export type {\n ApprovalGateInspection,\n ApprovalRequirement,\n ApprovalRequirementStatus,\n ConstraintDiagnostics,\n ConstraintViolationKind,\n ShlexCheck,\n} from \"./wasm.ts\";\n\nexport {\n approvalRequirement,\n checkShlex,\n diagnoseConstraints,\n evaluateApprovalGates,\n inspectApprovalGate,\n parseStrictJson,\n} from \"./wasm.ts\";\n\nexport type {\n AllConstraint,\n AllowPolicy,\n AnyOfConstraint,\n ApprovalContextAttestation,\n ApprovalInfo,\n ApprovalRequest,\n ApprovalRequestSummary,\n ArgApprovalGate,\n CelConstraint,\n CidrConstraint,\n Clearance,\n ConstraintExpr,\n ContainsConstraint,\n ControlPlaneApprovalRequestV1,\n ControlPlaneApprovalResponseV1,\n CreateTenuoOptions,\n Decision,\n DevRoot,\n EmailConstraint,\n ExactConstraint,\n ExecuteOptions,\n ExplainedField,\n Explanation,\n IssueInput,\n IssuerKey,\n MaxConstraint,\n McpAttachOptions,\n McpCallParams,\n McpHandlerPolicy,\n McpJsonRpcError,\n McpVerifyOptions,\n MinConstraint,\n NarrowInput,\n NarrowOptions,\n NonceStore,\n NotConstraint,\n NotOneOfConstraint,\n OneOfConstraint,\n PatternConstraint,\n PresentedCall,\n ProtectedTool,\n PublicKeyHandle,\n RangeConstraint,\n ReceiptChainInfo,\n ReceiptInfo,\n RegexConstraint,\n RequireApproval,\n RevocationListInfo,\n RevocationListInput,\n Session,\n SessionAllow,\n SessionFromWireInput,\n SessionInfo,\n SessionInput,\n ShlexConstraint,\n SignApprovalOptions,\n SubsetConstraint,\n Tenuo,\n LegacyTenuoErrorCode,\n ProtocolDenialCode,\n TenuoErrorCode,\n TenuoMcp,\n TenuoMcpMeta,\n ToolApprovalGate,\n ToolLike,\n ToolPolicy,\n UnderConstraint,\n UrlPatternConstraint,\n UrlSafeConstraint,\n WarrantPart,\n WildcardConstraint,\n} from \"./api.ts\";\n\nexport {\n ApprovalRequiredError,\n AuthorizationDeniedError,\n TenuoConfigurationError,\n TenuoError,\n} from \"./errors.ts\";\n\nexport {\n all,\n anyOf,\n cel,\n cidr,\n contains,\n email,\n exact,\n max,\n min,\n not,\n notOneOf,\n oneOf,\n pathGlob,\n pattern,\n range,\n regex,\n shlex,\n subset,\n under,\n urlPattern,\n urlSafe,\n wildcard,\n} from \"./constraints.ts\";\n\nexport { memoryNonceStore } from \"./nonce.ts\";\n\nexport { Session as SessionHandle, isSession } from \"./session.ts\";\n\nexport { ConnectToken, parseConnectToken } from \"./connect.ts\";\nexport type { ResolveEndpointOptions } from \"./connect.ts\";\n\nexport { HolderIdentity, generateIdentity, identityFromKey } from \"./identity.ts\";\n\nexport { Runtime } from \"./runtime.ts\";\nexport type { ReceiptsMode, RuntimeOptions, SessionWarrant } from \"./runtime.ts\";\n\nexport { createTenuo } from \"./client.ts\";\n"]}
package/dist/mcp.d.ts CHANGED
@@ -1,5 +1,5 @@
1
1
  import type { McpAttachOptions, McpVerifyOptions, PresentedCall, Session as SessionContract, TenuoMcp } from "./api.ts";
2
- import type { WasmContext, WasmDecision } from "./wasm.ts";
2
+ import { type WasmContext, type WasmDecision } from "./wasm.ts";
3
3
  export type Decide = (decision: WasmDecision, tool: string, native?: object, args?: Record<string, unknown>) => void;
4
4
  /**
5
5
  * Transport-agnostic half of `attach`: authorize locally, then sign
package/dist/mcp.js CHANGED
@@ -1,6 +1,8 @@
1
1
  import { AuthorizationDeniedError, TenuoConfigurationError, TenuoError } from "./errors.js";
2
2
  import { collectReceipt, emitIsolatedReceipt } from "./receipts.js";
3
+ import { admitNonce, consumeApprovals, snapshotApprovals } from "./replay.js";
3
4
  import { nativeSession } from "./session.js";
5
+ import { loadWasm } from "./wasm.js";
4
6
  /**
5
7
  * Transport-agnostic half of `attach`: authorize locally, then sign
6
8
  * proof-of-possession over the call. Shared by `tenuo.present()` and
@@ -38,15 +40,19 @@ export async function verifyPresented(context, decide, presented, name, args, op
38
40
  if (envelope === undefined) {
39
41
  throw new TenuoConfigurationError(`${label} needs { warrant, signature } from tenuo.present() or tenuo.mcp.attach()`);
40
42
  }
43
+ const approvals = snapshotApprovals(envelope.approvals);
41
44
  // Capture once: replay admission and receipt callbacks may yield or mutate
42
45
  // the caller's object. Execution must use the exact snapshot verified here.
43
46
  const snapshot = captureArguments(args);
44
- const decision = context.authorizePresented(envelope.warrant, name, snapshot.json, envelope.signature, envelope.approvals, options?.allow, options?.requestId);
47
+ const decision = context.authorizePresented(envelope.warrant, name, snapshot.json, envelope.signature, approvals, options?.allow, options?.requestId);
45
48
  // Rust already signed this envelope. Emit before the nonce store can
46
49
  // refuse a replay — otherwise an attacker leaves no audit artifact.
47
50
  emitReceipt(options?.onReceipt, decision.receipt, undefined, host);
48
51
  if (decision.outcome === "allow") {
49
52
  await admitPop(options?.nonceStore, envelope.signature, options?.onNonceStoreError);
53
+ // A fresh PoP over the same approvals is a new token; consume the
54
+ // approvals themselves so a nonce store makes them one-use here too.
55
+ await consumeApprovals(options?.nonceStore, approvals, receiptRequestHash(decision.receipt), name, options?.onNonceStoreError);
50
56
  }
51
57
  decide(decision, name);
52
58
  // Run the host parse of the JSON text the proof covers. The raw host
@@ -137,7 +143,6 @@ const VERIFY_OPTION_KEYS = new Set([
137
143
  "onNonceStoreError",
138
144
  "requestId",
139
145
  ]);
140
- const REPLAY_STORE_UNAVAILABLE = "Replay store unavailable";
141
146
  const ATTACH_OPTION_KEYS = new Set(["approvals", "onReceipt", "requestId"]);
142
147
  function assertKnownKeys(value, known, label) {
143
148
  const unknown = Object.keys(value).filter((key) => !known.has(key));
@@ -158,57 +163,21 @@ function isHandlerPolicy(value) {
158
163
  const hasStoreError = "onNonceStoreError" in record && typeof record.onNonceStoreError === "function";
159
164
  return keys.length === 0 || hasAllow || hasReceipt || hasNonce || hasStoreError;
160
165
  }
161
- async function admitPop(store, signature, onNonceStoreError) {
162
- if (store === undefined) {
163
- return;
164
- }
165
- let admitted;
166
- try {
167
- admitted = store.checkAndRecord(signature);
168
- }
169
- catch (error) {
170
- throw replayStoreUnavailable(error, onNonceStoreError);
171
- }
172
- if (isThenable(admitted)) {
173
- try {
174
- admitted = await admitted;
175
- }
176
- catch (error) {
177
- throw replayStoreUnavailable(error, onNonceStoreError);
178
- }
179
- }
180
- if (typeof admitted !== "boolean") {
181
- throw replayStoreUnavailable(new TenuoConfigurationError("NonceStore.checkAndRecord() must return boolean or Promise<boolean>"), onNonceStoreError);
182
- }
183
- if (!admitted) {
184
- throw new AuthorizationDeniedError("TENUO_INVALID_POP", "PoP replay detected — this exact authorization token was already consumed.");
185
- }
186
- }
187
- function replayStoreUnavailable(cause, onNonceStoreError) {
188
- emitIsolated(onNonceStoreError, cause);
189
- const error = new TenuoConfigurationError(REPLAY_STORE_UNAVAILABLE);
190
- if (cause !== undefined) {
191
- error.cause = cause;
192
- }
193
- return error;
166
+ function admitPop(store, signature, onNonceStoreError) {
167
+ return admitNonce(store, signature, onNonceStoreError, () => new AuthorizationDeniedError("TENUO_INVALID_POP", "PoP replay detected — this exact authorization token was already consumed."));
194
168
  }
195
- function emitIsolated(hook, error) {
196
- if (hook === undefined) {
197
- return;
169
+ /** The request hash core wrote into the decision receipt, when there is one. */
170
+ function receiptRequestHash(receipt) {
171
+ if (receipt === undefined) {
172
+ return undefined;
198
173
  }
199
174
  try {
200
- const result = hook(error);
201
- if (isThenable(result)) {
202
- void Promise.resolve(result).catch(() => undefined);
203
- }
175
+ return loadWasm().sdkVerifyReceipt(receipt).request_hash;
204
176
  }
205
177
  catch {
206
- // Isolated — must not change the public error.
178
+ return undefined;
207
179
  }
208
180
  }
209
- function isThenable(value) {
210
- return value !== null && typeof value === "object" && "then" in value && typeof value.then === "function";
211
- }
212
181
  function emitReceipt(onReceipt, receipt, session, host) {
213
182
  collectReceipt(receipt, session, host);
214
183
  emitIsolatedReceipt(onReceipt, receipt);
package/dist/mcp.js.map CHANGED
@@ -1 +1 @@
1
- {"version":3,"file":"mcp.js","sourceRoot":"","sources":["../src/mcp.ts"],"names":[],"mappings":"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","sourcesContent":["import type {\n McpAttachOptions,\n McpCallParams,\n McpHandlerPolicy,\n McpJsonRpcError,\n McpVerifyOptions,\n PresentedCall,\n Session as SessionContract,\n TenuoErrorCode,\n TenuoMcp,\n} from \"./api.ts\";\nimport { AuthorizationDeniedError, TenuoConfigurationError, TenuoError } from \"./errors.ts\";\nimport { collectReceipt, emitIsolatedReceipt } from \"./receipts.ts\";\nimport { nativeSession, type Session } from \"./session.ts\";\nimport type { WasmContext, WasmDecision } from \"./wasm.ts\";\n\nexport type Decide = (decision: WasmDecision, tool: string, native?: object, args?: Record<string, unknown>) => void;\n\n/**\n * Transport-agnostic half of `attach`: authorize locally, then sign\n * proof-of-possession over the call. Shared by `tenuo.present()` and\n * `tenuo.mcp.attach()`.\n */\nexport function presentCall(\n context: WasmContext,\n decide: Decide,\n session: SessionContract,\n name: string,\n args: Readonly<Record<string, unknown>>,\n options: McpAttachOptions | undefined,\n label: string,\n host?: object,\n): { readonly presented: PresentedCall; readonly wireArgs: Record<string, unknown> } {\n if (options !== undefined) {\n assertKnownKeys(options, ATTACH_OPTION_KEYS, `${label} options`);\n }\n const native = nativeSession(session as Session);\n const snapshot = captureArguments(args);\n const local = context.authorize(\n native,\n name,\n snapshot.json,\n options?.approvals,\n undefined,\n options?.requestId,\n );\n emitReceipt(options?.onReceipt, local.receipt, session, host);\n decide(local, name, native, snapshot.executionArgs());\n const envelope = context.signMeta(\n native,\n name,\n snapshot.json,\n Math.floor(Date.now() / 1000),\n options?.approvals ?? null,\n );\n const presented = {\n warrant: envelope.warrant,\n signature: envelope.signature,\n ...(envelope.approvals !== undefined && envelope.approvals.length > 0\n ? { approvals: envelope.approvals }\n : {}),\n };\n return { presented, wireArgs: snapshot.executionArgs() };\n}\n\n/**\n * Transport-agnostic half of `verify`: check a presented warrant chain and\n * proof-of-possession, then apply the host ceiling. Shared by\n * `tenuo.verify()` and `tenuo.mcp.verify()`.\n */\nexport async function verifyPresented(\n context: WasmContext,\n decide: Decide,\n presented: PresentedCall,\n name: string,\n args: Readonly<Record<string, unknown>>,\n options: McpVerifyOptions | undefined,\n label: string,\n host?: object,\n): Promise<Readonly<Record<string, unknown>>> {\n if (options !== undefined) {\n assertKnownKeys(options, VERIFY_OPTION_KEYS, `${label} options`);\n }\n const envelope = presentedEnvelope(presented);\n if (envelope === undefined) {\n throw new TenuoConfigurationError(\n `${label} needs { warrant, signature } from tenuo.present() or tenuo.mcp.attach()`,\n );\n }\n // Capture once: replay admission and receipt callbacks may yield or mutate\n // the caller's object. Execution must use the exact snapshot verified here.\n const snapshot = captureArguments(args);\n const decision = context.authorizePresented(\n envelope.warrant,\n name,\n snapshot.json,\n envelope.signature,\n envelope.approvals,\n options?.allow,\n options?.requestId,\n );\n // Rust already signed this envelope. Emit before the nonce store can\n // refuse a replay — otherwise an attacker leaves no audit artifact.\n emitReceipt(options?.onReceipt, decision.receipt, undefined, host);\n if (decision.outcome === \"allow\") {\n await admitPop(options?.nonceStore, envelope.signature, options?.onNonceStoreError);\n }\n decide(decision, name);\n // Run the host parse of the JSON text the proof covers. The raw host\n // object can contain values that JSON drops.\n return snapshot.executionArgs();\n}\n\nexport function createMcp(context: WasmContext, decide: Decide, host?: object): TenuoMcp {\n const mcp: TenuoMcp = {\n attach(session, name, args, options) {\n const { presented, wireArgs } = presentCall(\n context,\n decide,\n session,\n name,\n args,\n options,\n \"mcp.attach()\",\n host,\n );\n return { name, arguments: wireArgs, _meta: { tenuo: presented } };\n },\n\n async verify(name, args, meta, options) {\n const envelope = tenuoEnvelope(meta);\n if (envelope === undefined) {\n throw new TenuoConfigurationError(\n \"MCP call is missing _meta.tenuo. The client must call tenuo.mcp.attach().\",\n );\n }\n return verifyPresented(context, decide, envelope, name, args, options, \"mcp.verify()\", host);\n },\n\n handler(name, policyOrExecute, maybeExecute?) {\n const policy = isHandlerPolicy(policyOrExecute) ? policyOrExecute : undefined;\n const execute = isHandlerPolicy(policyOrExecute) ? maybeExecute : policyOrExecute;\n if (typeof execute !== \"function\") {\n throw new TenuoConfigurationError(\"mcp.handler() requires an execute function\");\n }\n return async (args, extra) => {\n const authorized = await mcp.verify(name, args, extra?._meta ?? extra?.meta, {\n ...(policy?.allow !== undefined ? { allow: policy.allow } : {}),\n ...(policy?.onReceipt !== undefined ? { onReceipt: policy.onReceipt } : {}),\n ...(policy?.nonceStore !== undefined ? { nonceStore: policy.nonceStore } : {}),\n ...(policy?.onNonceStoreError !== undefined ? { onNonceStoreError: policy.onNonceStoreError } : {}),\n });\n return execute(authorized as never);\n };\n },\n\n jsonRpcError(error) {\n return jsonRpcError(error);\n },\n };\n return mcp;\n}\n\n/** A private immutable input snapshot, not an authorization token. */\nfunction captureArguments(args: Readonly<Record<string, unknown>>): {\n readonly json: string;\n executionArgs(): Record<string, unknown>;\n} {\n const text = JSON.stringify(args);\n if (typeof text !== \"string\") {\n throw new TenuoConfigurationError(\"arguments must be JSON\");\n }\n return Object.freeze({\n json: text,\n executionArgs: () => plainArgs(JSON.parse(text)),\n });\n}\n\nfunction presentedEnvelope(\n value: unknown,\n): { warrant: string; signature: string; approvals?: string[] } | undefined {\n if (value === null || typeof value !== \"object\") {\n return undefined;\n }\n const tenuo = value as { warrant?: unknown; signature?: unknown; approvals?: unknown };\n if (typeof tenuo.warrant !== \"string\" || tenuo.warrant.length === 0) {\n return undefined;\n }\n if (typeof tenuo.signature !== \"string\" || tenuo.signature.length === 0) {\n return undefined;\n }\n if (tenuo.approvals != null &&\n (!Array.isArray(tenuo.approvals) || tenuo.approvals.some((item) => typeof item !== \"string\"))) {\n return undefined;\n }\n const approvals = tenuo.approvals ?? undefined;\n return {\n warrant: tenuo.warrant,\n signature: tenuo.signature,\n ...(approvals !== undefined && approvals.length > 0 ? { approvals } : {}),\n };\n}\n\nfunction tenuoEnvelope(\n meta: unknown,\n): { warrant: string; signature: string; approvals?: string[] } | undefined {\n if (meta === null || typeof meta !== \"object\") {\n return undefined;\n }\n const root = meta as { tenuo?: unknown; _meta?: unknown };\n const block = root.tenuo ?? (root._meta as { tenuo?: unknown } | undefined)?.tenuo;\n return presentedEnvelope(block);\n}\n\nconst HANDLER_POLICY_KEYS = new Set([\"allow\", \"onReceipt\", \"nonceStore\", \"onNonceStoreError\"]);\nconst VERIFY_OPTION_KEYS = new Set([\n \"allow\",\n \"onReceipt\",\n \"nonceStore\",\n \"onNonceStoreError\",\n \"requestId\",\n]);\nconst REPLAY_STORE_UNAVAILABLE = \"Replay store unavailable\";\nconst ATTACH_OPTION_KEYS = new Set([\"approvals\", \"onReceipt\", \"requestId\"]);\nfunction assertKnownKeys(value: object, known: ReadonlySet<string>, label: string): void {\n const unknown = Object.keys(value).filter((key) => !known.has(key));\n if (unknown[0] !== undefined) {\n throw new TenuoConfigurationError(\n `${label} has unknown key '${unknown[0]}'. Use ${[...known].join(\", \")}.`,\n );\n }\n}\n\nfunction isHandlerPolicy(value: unknown): value is McpHandlerPolicy {\n if (value === null || typeof value !== \"object\" || Array.isArray(value) || typeof value === \"function\") {\n return false;\n }\n const keys = Object.keys(value);\n assertKnownKeys(value, HANDLER_POLICY_KEYS, \"mcp.handler() policy\");\n const record = value as {\n allow?: unknown;\n onReceipt?: unknown;\n nonceStore?: unknown;\n onNonceStoreError?: unknown;\n };\n const hasAllow =\n \"allow\" in record && record.allow !== null && typeof record.allow === \"object\" && !Array.isArray(record.allow);\n const hasReceipt = \"onReceipt\" in record && typeof record.onReceipt === \"function\";\n const hasNonce = \"nonceStore\" in record && record.nonceStore !== null && typeof record.nonceStore === \"object\";\n const hasStoreError = \"onNonceStoreError\" in record && typeof record.onNonceStoreError === \"function\";\n return keys.length === 0 || hasAllow || hasReceipt || hasNonce || hasStoreError;\n}\n\nasync function admitPop(\n store: { checkAndRecord(popSignature: string): boolean | Promise<boolean> } | undefined,\n signature: string,\n onNonceStoreError?: (error: unknown) => void | Promise<void>,\n): Promise<void> {\n if (store === undefined) {\n return;\n }\n let admitted: boolean | Promise<boolean>;\n try {\n admitted = store.checkAndRecord(signature);\n } catch (error) {\n throw replayStoreUnavailable(error, onNonceStoreError);\n }\n if (isThenable(admitted)) {\n try {\n admitted = await admitted;\n } catch (error) {\n throw replayStoreUnavailable(error, onNonceStoreError);\n }\n }\n if (typeof admitted !== \"boolean\") {\n throw replayStoreUnavailable(\n new TenuoConfigurationError(\"NonceStore.checkAndRecord() must return boolean or Promise<boolean>\"),\n onNonceStoreError,\n );\n }\n if (!admitted) {\n throw new AuthorizationDeniedError(\n \"TENUO_INVALID_POP\",\n \"PoP replay detected — this exact authorization token was already consumed.\",\n );\n }\n}\n\nfunction replayStoreUnavailable(\n cause: unknown,\n onNonceStoreError?: (error: unknown) => void | Promise<void>,\n): TenuoConfigurationError {\n emitIsolated(onNonceStoreError, cause);\n const error = new TenuoConfigurationError(REPLAY_STORE_UNAVAILABLE);\n if (cause !== undefined) {\n error.cause = cause;\n }\n return error;\n}\n\nfunction emitIsolated(\n hook: ((error: unknown) => void | Promise<void>) | undefined,\n error: unknown,\n): void {\n if (hook === undefined) {\n return;\n }\n try {\n const result = hook(error);\n if (isThenable(result)) {\n void Promise.resolve(result).catch(() => undefined);\n }\n } catch {\n // Isolated — must not change the public error.\n }\n}\n\nfunction isThenable(value: unknown): value is Promise<boolean> {\n return value !== null && typeof value === \"object\" && \"then\" in value && typeof value.then === \"function\";\n}\n\nfunction emitReceipt(\n onReceipt: ((receipt: string) => void | Promise<void>) | undefined,\n receipt: string | undefined,\n session?: SessionContract,\n host?: object,\n): void {\n collectReceipt(receipt, session, host);\n emitIsolatedReceipt(onReceipt, receipt);\n}\n\nfunction plainArgs(value: unknown): Record<string, unknown> {\n if (value instanceof Map) {\n return Object.fromEntries(value);\n }\n if (value !== null && typeof value === \"object\" && !Array.isArray(value)) {\n return { ...(value as Record<string, unknown>) };\n }\n return {};\n}\n\nfunction jsonRpcError(error: unknown): McpJsonRpcError {\n if (error instanceof TenuoError) {\n const code: TenuoErrorCode = error.code;\n const rpc = code === \"TENUO_APPROVAL_REQUIRED\" ? -32002 : code === \"TENUO_CANONICALIZATION\" ? -32602 : -32001;\n return { code: rpc, message: error.message, data: { tenuo: { code } } };\n }\n const message = error instanceof Error ? error.message : String(error);\n return { code: -32001, message };\n}\n"]}
1
+ {"version":3,"file":"mcp.js","sourceRoot":"","sources":["../src/mcp.ts"],"names":[],"mappings":"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","sourcesContent":["import type {\n McpAttachOptions,\n McpCallParams,\n McpHandlerPolicy,\n McpJsonRpcError,\n McpVerifyOptions,\n PresentedCall,\n Session as SessionContract,\n TenuoErrorCode,\n TenuoMcp,\n} from \"./api.ts\";\nimport { AuthorizationDeniedError, TenuoConfigurationError, TenuoError } from \"./errors.ts\";\nimport { collectReceipt, emitIsolatedReceipt } from \"./receipts.ts\";\nimport { admitNonce, consumeApprovals, snapshotApprovals } from \"./replay.ts\";\nimport { nativeSession, type Session } from \"./session.ts\";\nimport { loadWasm, type WasmContext, type WasmDecision } from \"./wasm.ts\";\n\nexport type Decide = (decision: WasmDecision, tool: string, native?: object, args?: Record<string, unknown>) => void;\n\n/**\n * Transport-agnostic half of `attach`: authorize locally, then sign\n * proof-of-possession over the call. Shared by `tenuo.present()` and\n * `tenuo.mcp.attach()`.\n */\nexport function presentCall(\n context: WasmContext,\n decide: Decide,\n session: SessionContract,\n name: string,\n args: Readonly<Record<string, unknown>>,\n options: McpAttachOptions | undefined,\n label: string,\n host?: object,\n): { readonly presented: PresentedCall; readonly wireArgs: Record<string, unknown> } {\n if (options !== undefined) {\n assertKnownKeys(options, ATTACH_OPTION_KEYS, `${label} options`);\n }\n const native = nativeSession(session as Session);\n const snapshot = captureArguments(args);\n const local = context.authorize(\n native,\n name,\n snapshot.json,\n options?.approvals,\n undefined,\n options?.requestId,\n );\n emitReceipt(options?.onReceipt, local.receipt, session, host);\n decide(local, name, native, snapshot.executionArgs());\n const envelope = context.signMeta(\n native,\n name,\n snapshot.json,\n Math.floor(Date.now() / 1000),\n options?.approvals ?? null,\n );\n const presented = {\n warrant: envelope.warrant,\n signature: envelope.signature,\n ...(envelope.approvals !== undefined && envelope.approvals.length > 0\n ? { approvals: envelope.approvals }\n : {}),\n };\n return { presented, wireArgs: snapshot.executionArgs() };\n}\n\n/**\n * Transport-agnostic half of `verify`: check a presented warrant chain and\n * proof-of-possession, then apply the host ceiling. Shared by\n * `tenuo.verify()` and `tenuo.mcp.verify()`.\n */\nexport async function verifyPresented(\n context: WasmContext,\n decide: Decide,\n presented: PresentedCall,\n name: string,\n args: Readonly<Record<string, unknown>>,\n options: McpVerifyOptions | undefined,\n label: string,\n host?: object,\n): Promise<Readonly<Record<string, unknown>>> {\n if (options !== undefined) {\n assertKnownKeys(options, VERIFY_OPTION_KEYS, `${label} options`);\n }\n const envelope = presentedEnvelope(presented);\n if (envelope === undefined) {\n throw new TenuoConfigurationError(\n `${label} needs { warrant, signature } from tenuo.present() or tenuo.mcp.attach()`,\n );\n }\n const approvals = snapshotApprovals(envelope.approvals);\n // Capture once: replay admission and receipt callbacks may yield or mutate\n // the caller's object. Execution must use the exact snapshot verified here.\n const snapshot = captureArguments(args);\n const decision = context.authorizePresented(\n envelope.warrant,\n name,\n snapshot.json,\n envelope.signature,\n approvals,\n options?.allow,\n options?.requestId,\n );\n // Rust already signed this envelope. Emit before the nonce store can\n // refuse a replay — otherwise an attacker leaves no audit artifact.\n emitReceipt(options?.onReceipt, decision.receipt, undefined, host);\n if (decision.outcome === \"allow\") {\n await admitPop(options?.nonceStore, envelope.signature, options?.onNonceStoreError);\n // A fresh PoP over the same approvals is a new token; consume the\n // approvals themselves so a nonce store makes them one-use here too.\n await consumeApprovals(\n options?.nonceStore,\n approvals,\n receiptRequestHash(decision.receipt),\n name,\n options?.onNonceStoreError,\n );\n }\n decide(decision, name);\n // Run the host parse of the JSON text the proof covers. The raw host\n // object can contain values that JSON drops.\n return snapshot.executionArgs();\n}\n\nexport function createMcp(context: WasmContext, decide: Decide, host?: object): TenuoMcp {\n const mcp: TenuoMcp = {\n attach(session, name, args, options) {\n const { presented, wireArgs } = presentCall(\n context,\n decide,\n session,\n name,\n args,\n options,\n \"mcp.attach()\",\n host,\n );\n return { name, arguments: wireArgs, _meta: { tenuo: presented } };\n },\n\n async verify(name, args, meta, options) {\n const envelope = tenuoEnvelope(meta);\n if (envelope === undefined) {\n throw new TenuoConfigurationError(\n \"MCP call is missing _meta.tenuo. The client must call tenuo.mcp.attach().\",\n );\n }\n return verifyPresented(context, decide, envelope, name, args, options, \"mcp.verify()\", host);\n },\n\n handler(name, policyOrExecute, maybeExecute?) {\n const policy = isHandlerPolicy(policyOrExecute) ? policyOrExecute : undefined;\n const execute = isHandlerPolicy(policyOrExecute) ? maybeExecute : policyOrExecute;\n if (typeof execute !== \"function\") {\n throw new TenuoConfigurationError(\"mcp.handler() requires an execute function\");\n }\n return async (args, extra) => {\n const authorized = await mcp.verify(name, args, extra?._meta ?? extra?.meta, {\n ...(policy?.allow !== undefined ? { allow: policy.allow } : {}),\n ...(policy?.onReceipt !== undefined ? { onReceipt: policy.onReceipt } : {}),\n ...(policy?.nonceStore !== undefined ? { nonceStore: policy.nonceStore } : {}),\n ...(policy?.onNonceStoreError !== undefined ? { onNonceStoreError: policy.onNonceStoreError } : {}),\n });\n return execute(authorized as never);\n };\n },\n\n jsonRpcError(error) {\n return jsonRpcError(error);\n },\n };\n return mcp;\n}\n\n/** A private immutable input snapshot, not an authorization token. */\nfunction captureArguments(args: Readonly<Record<string, unknown>>): {\n readonly json: string;\n executionArgs(): Record<string, unknown>;\n} {\n const text = JSON.stringify(args);\n if (typeof text !== \"string\") {\n throw new TenuoConfigurationError(\"arguments must be JSON\");\n }\n return Object.freeze({\n json: text,\n executionArgs: () => plainArgs(JSON.parse(text)),\n });\n}\n\nfunction presentedEnvelope(\n value: unknown,\n): { warrant: string; signature: string; approvals?: string[] } | undefined {\n if (value === null || typeof value !== \"object\") {\n return undefined;\n }\n const tenuo = value as { warrant?: unknown; signature?: unknown; approvals?: unknown };\n if (typeof tenuo.warrant !== \"string\" || tenuo.warrant.length === 0) {\n return undefined;\n }\n if (typeof tenuo.signature !== \"string\" || tenuo.signature.length === 0) {\n return undefined;\n }\n if (tenuo.approvals != null &&\n (!Array.isArray(tenuo.approvals) || tenuo.approvals.some((item) => typeof item !== \"string\"))) {\n return undefined;\n }\n const approvals = tenuo.approvals ?? undefined;\n return {\n warrant: tenuo.warrant,\n signature: tenuo.signature,\n ...(approvals !== undefined && approvals.length > 0 ? { approvals } : {}),\n };\n}\n\nfunction tenuoEnvelope(\n meta: unknown,\n): { warrant: string; signature: string; approvals?: string[] } | undefined {\n if (meta === null || typeof meta !== \"object\") {\n return undefined;\n }\n const root = meta as { tenuo?: unknown; _meta?: unknown };\n const block = root.tenuo ?? (root._meta as { tenuo?: unknown } | undefined)?.tenuo;\n return presentedEnvelope(block);\n}\n\nconst HANDLER_POLICY_KEYS = new Set([\"allow\", \"onReceipt\", \"nonceStore\", \"onNonceStoreError\"]);\nconst VERIFY_OPTION_KEYS = new Set([\n \"allow\",\n \"onReceipt\",\n \"nonceStore\",\n \"onNonceStoreError\",\n \"requestId\",\n]);\nconst ATTACH_OPTION_KEYS = new Set([\"approvals\", \"onReceipt\", \"requestId\"]);\nfunction assertKnownKeys(value: object, known: ReadonlySet<string>, label: string): void {\n const unknown = Object.keys(value).filter((key) => !known.has(key));\n if (unknown[0] !== undefined) {\n throw new TenuoConfigurationError(\n `${label} has unknown key '${unknown[0]}'. Use ${[...known].join(\", \")}.`,\n );\n }\n}\n\nfunction isHandlerPolicy(value: unknown): value is McpHandlerPolicy {\n if (value === null || typeof value !== \"object\" || Array.isArray(value) || typeof value === \"function\") {\n return false;\n }\n const keys = Object.keys(value);\n assertKnownKeys(value, HANDLER_POLICY_KEYS, \"mcp.handler() policy\");\n const record = value as {\n allow?: unknown;\n onReceipt?: unknown;\n nonceStore?: unknown;\n onNonceStoreError?: unknown;\n };\n const hasAllow =\n \"allow\" in record && record.allow !== null && typeof record.allow === \"object\" && !Array.isArray(record.allow);\n const hasReceipt = \"onReceipt\" in record && typeof record.onReceipt === \"function\";\n const hasNonce = \"nonceStore\" in record && record.nonceStore !== null && typeof record.nonceStore === \"object\";\n const hasStoreError = \"onNonceStoreError\" in record && typeof record.onNonceStoreError === \"function\";\n return keys.length === 0 || hasAllow || hasReceipt || hasNonce || hasStoreError;\n}\n\nfunction admitPop(\n store: McpVerifyOptions[\"nonceStore\"],\n signature: string,\n onNonceStoreError?: (error: unknown) => void | Promise<void>,\n): Promise<void> {\n return admitNonce(store, signature, onNonceStoreError, () =>\n new AuthorizationDeniedError(\n \"TENUO_INVALID_POP\",\n \"PoP replay detected — this exact authorization token was already consumed.\",\n ),\n );\n}\n\n/** The request hash core wrote into the decision receipt, when there is one. */\nfunction receiptRequestHash(receipt: string | undefined): string | undefined {\n if (receipt === undefined) {\n return undefined;\n }\n try {\n return loadWasm().sdkVerifyReceipt(receipt).request_hash;\n } catch {\n return undefined;\n }\n}\n\nfunction emitReceipt(\n onReceipt: ((receipt: string) => void | Promise<void>) | undefined,\n receipt: string | undefined,\n session?: SessionContract,\n host?: object,\n): void {\n collectReceipt(receipt, session, host);\n emitIsolatedReceipt(onReceipt, receipt);\n}\n\nfunction plainArgs(value: unknown): Record<string, unknown> {\n if (value instanceof Map) {\n return Object.fromEntries(value);\n }\n if (value !== null && typeof value === \"object\" && !Array.isArray(value)) {\n return { ...(value as Record<string, unknown>) };\n }\n return {};\n}\n\nfunction jsonRpcError(error: unknown): McpJsonRpcError {\n if (error instanceof TenuoError) {\n const code: TenuoErrorCode = error.code;\n const rpc = code === \"TENUO_APPROVAL_REQUIRED\" ? -32002 : code === \"TENUO_CANONICALIZATION\" ? -32602 : -32001;\n return { code: rpc, message: error.message, data: { tenuo: { code } } };\n }\n const message = error instanceof Error ? error.message : String(error);\n return { code: -32001, message };\n}\n"]}
@@ -0,0 +1,20 @@
1
+ import type { NonceStore } from "./api.ts";
2
+ /**
3
+ * Record `key` in a `NonceStore`. Throws `replayed()` when the store has
4
+ * already seen it, and the constant "Replay store unavailable" when the
5
+ * store throws, rejects, or returns a non-boolean. Shared by the MCP PoP
6
+ * check and approval consumption.
7
+ */
8
+ export declare function admitNonce(store: NonceStore | undefined, key: string, onNonceStoreError: ((error: unknown) => void | Promise<void>) | undefined, replayed: () => Error): Promise<void>;
9
+ /**
10
+ * Make approvals one-use: record every approval envelope that counts for
11
+ * this call (valid signature and, when `requestHash` is known, bound to this
12
+ * exact request). Run only after core allowed the call. Do not apply a second
13
+ * expiry check here: core intentionally accepts its configured clock-skew
14
+ * grace window, and an approval it accepted must still be consumed.
15
+ *
16
+ * Keys come from signed approval fields, so re-encoding an envelope (hex,
17
+ * base64, bytes) does not make it fresh again.
18
+ */
19
+ export declare function consumeApprovals(store: NonceStore | undefined, approvals: unknown, requestHash: string | undefined, tool: string, onNonceStoreError: ((error: unknown) => void | Promise<void>) | undefined): Promise<void>;
20
+ export declare function snapshotApprovals(approvals: unknown): unknown;
package/dist/replay.js ADDED
@@ -0,0 +1,117 @@
1
+ import { createHash } from "node:crypto";
2
+ import { AuthorizationDeniedError, TenuoConfigurationError } from "./errors.js";
3
+ import { loadWasm } from "./wasm.js";
4
+ const REPLAY_STORE_UNAVAILABLE = "Replay store unavailable";
5
+ /**
6
+ * Record `key` in a `NonceStore`. Throws `replayed()` when the store has
7
+ * already seen it, and the constant "Replay store unavailable" when the
8
+ * store throws, rejects, or returns a non-boolean. Shared by the MCP PoP
9
+ * check and approval consumption.
10
+ */
11
+ export async function admitNonce(store, key, onNonceStoreError, replayed) {
12
+ if (store === undefined) {
13
+ return;
14
+ }
15
+ let admitted;
16
+ try {
17
+ admitted = store.checkAndRecord(key);
18
+ }
19
+ catch (error) {
20
+ throw replayStoreUnavailable(error, onNonceStoreError);
21
+ }
22
+ if (isThenable(admitted)) {
23
+ try {
24
+ admitted = await admitted;
25
+ }
26
+ catch (error) {
27
+ throw replayStoreUnavailable(error, onNonceStoreError);
28
+ }
29
+ }
30
+ if (typeof admitted !== "boolean") {
31
+ throw replayStoreUnavailable(new TenuoConfigurationError("NonceStore.checkAndRecord() must return boolean or Promise<boolean>"), onNonceStoreError);
32
+ }
33
+ if (!admitted) {
34
+ throw replayed();
35
+ }
36
+ }
37
+ /**
38
+ * Make approvals one-use: record every approval envelope that counts for
39
+ * this call (valid signature and, when `requestHash` is known, bound to this
40
+ * exact request). Run only after core allowed the call. Do not apply a second
41
+ * expiry check here: core intentionally accepts its configured clock-skew
42
+ * grace window, and an approval it accepted must still be consumed.
43
+ *
44
+ * Keys come from signed approval fields, so re-encoding an envelope (hex,
45
+ * base64, bytes) does not make it fresh again.
46
+ */
47
+ export async function consumeApprovals(store, approvals, requestHash, tool, onNonceStoreError) {
48
+ if (store === undefined || !Array.isArray(approvals) || approvals.length === 0) {
49
+ return;
50
+ }
51
+ const expected = requestHash?.toLowerCase();
52
+ const keys = new Set();
53
+ for (const envelope of approvals) {
54
+ const info = inspect(envelope);
55
+ if (!info.signature_valid) {
56
+ continue;
57
+ }
58
+ if (expected !== undefined && info.request_hash.toLowerCase() !== expected) {
59
+ continue;
60
+ }
61
+ keys.add(approvalKey(info));
62
+ }
63
+ for (const key of keys) {
64
+ await admitNonce(store, key, onNonceStoreError, () => new AuthorizationDeniedError("TENUO_INSUFFICIENT_APPROVALS", `Approval already used: an approval for ${tool} was consumed by an earlier call. Request a new approval.`));
65
+ }
66
+ }
67
+ export function snapshotApprovals(approvals) {
68
+ if (!Array.isArray(approvals)) {
69
+ return approvals;
70
+ }
71
+ return approvals.map((envelope) => envelope instanceof Uint8Array ? new Uint8Array(envelope) : envelope);
72
+ }
73
+ function inspect(envelope) {
74
+ if (typeof envelope !== "string" && !(envelope instanceof Uint8Array)) {
75
+ throw new TenuoConfigurationError("could not inspect an authorized approval for replay protection");
76
+ }
77
+ try {
78
+ return loadWasm().sdkInspectApproval(envelope);
79
+ }
80
+ catch (error) {
81
+ const wrapped = new TenuoConfigurationError("could not inspect an authorized approval for replay protection");
82
+ wrapped.cause = error;
83
+ throw wrapped;
84
+ }
85
+ }
86
+ function approvalKey(info) {
87
+ const digest = createHash("sha256")
88
+ .update(JSON.stringify([
89
+ info.approver_public_key.toLowerCase(),
90
+ info.request_hash.toLowerCase(),
91
+ info.nonce.toLowerCase(),
92
+ ]))
93
+ .digest("hex");
94
+ return `tenuo-approval-v1:${digest}`;
95
+ }
96
+ function replayStoreUnavailable(cause, onNonceStoreError) {
97
+ if (onNonceStoreError !== undefined) {
98
+ try {
99
+ const result = onNonceStoreError(cause);
100
+ if (isThenable(result)) {
101
+ void Promise.resolve(result).catch(() => undefined);
102
+ }
103
+ }
104
+ catch {
105
+ // Isolated — must not change the public error.
106
+ }
107
+ }
108
+ const error = new TenuoConfigurationError(REPLAY_STORE_UNAVAILABLE);
109
+ if (cause !== undefined) {
110
+ error.cause = cause;
111
+ }
112
+ return error;
113
+ }
114
+ function isThenable(value) {
115
+ return value !== null && typeof value === "object" && "then" in value && typeof value.then === "function";
116
+ }
117
+ //# sourceMappingURL=replay.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"replay.js","sourceRoot":"","sources":["../src/replay.ts"],"names":[],"mappings":"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","sourcesContent":["import { createHash } from \"node:crypto\";\nimport type { NonceStore } from \"./api.ts\";\nimport { AuthorizationDeniedError, TenuoConfigurationError } from \"./errors.ts\";\nimport { loadWasm, type WasmApprovalInfo } from \"./wasm.ts\";\n\nconst REPLAY_STORE_UNAVAILABLE = \"Replay store unavailable\";\n\n/**\n * Record `key` in a `NonceStore`. Throws `replayed()` when the store has\n * already seen it, and the constant \"Replay store unavailable\" when the\n * store throws, rejects, or returns a non-boolean. Shared by the MCP PoP\n * check and approval consumption.\n */\nexport async function admitNonce(\n store: NonceStore | undefined,\n key: string,\n onNonceStoreError: ((error: unknown) => void | Promise<void>) | undefined,\n replayed: () => Error,\n): Promise<void> {\n if (store === undefined) {\n return;\n }\n let admitted: boolean | Promise<boolean>;\n try {\n admitted = store.checkAndRecord(key);\n } catch (error) {\n throw replayStoreUnavailable(error, onNonceStoreError);\n }\n if (isThenable(admitted)) {\n try {\n admitted = await admitted;\n } catch (error) {\n throw replayStoreUnavailable(error, onNonceStoreError);\n }\n }\n if (typeof admitted !== \"boolean\") {\n throw replayStoreUnavailable(\n new TenuoConfigurationError(\"NonceStore.checkAndRecord() must return boolean or Promise<boolean>\"),\n onNonceStoreError,\n );\n }\n if (!admitted) {\n throw replayed();\n }\n}\n\n/**\n * Make approvals one-use: record every approval envelope that counts for\n * this call (valid signature and, when `requestHash` is known, bound to this\n * exact request). Run only after core allowed the call. Do not apply a second\n * expiry check here: core intentionally accepts its configured clock-skew\n * grace window, and an approval it accepted must still be consumed.\n *\n * Keys come from signed approval fields, so re-encoding an envelope (hex,\n * base64, bytes) does not make it fresh again.\n */\nexport async function consumeApprovals(\n store: NonceStore | undefined,\n approvals: unknown,\n requestHash: string | undefined,\n tool: string,\n onNonceStoreError: ((error: unknown) => void | Promise<void>) | undefined,\n): Promise<void> {\n if (store === undefined || !Array.isArray(approvals) || approvals.length === 0) {\n return;\n }\n const expected = requestHash?.toLowerCase();\n const keys = new Set<string>();\n for (const envelope of approvals as unknown[]) {\n const info = inspect(envelope);\n if (!info.signature_valid) {\n continue;\n }\n if (expected !== undefined && info.request_hash.toLowerCase() !== expected) {\n continue;\n }\n keys.add(approvalKey(info));\n }\n for (const key of keys) {\n await admitNonce(store, key, onNonceStoreError, () =>\n new AuthorizationDeniedError(\n \"TENUO_INSUFFICIENT_APPROVALS\",\n `Approval already used: an approval for ${tool} was consumed by an earlier call. Request a new approval.`,\n ),\n );\n }\n}\n\nexport function snapshotApprovals(approvals: unknown): unknown {\n if (!Array.isArray(approvals)) {\n return approvals;\n }\n return approvals.map((envelope) =>\n envelope instanceof Uint8Array ? new Uint8Array(envelope) : envelope,\n );\n}\n\nfunction inspect(envelope: unknown): WasmApprovalInfo {\n if (typeof envelope !== \"string\" && !(envelope instanceof Uint8Array)) {\n throw new TenuoConfigurationError(\"could not inspect an authorized approval for replay protection\");\n }\n try {\n return loadWasm().sdkInspectApproval(envelope);\n } catch (error) {\n const wrapped = new TenuoConfigurationError(\n \"could not inspect an authorized approval for replay protection\",\n );\n wrapped.cause = error;\n throw wrapped;\n }\n}\n\nfunction approvalKey(info: WasmApprovalInfo): string {\n const digest = createHash(\"sha256\")\n .update(\n JSON.stringify([\n info.approver_public_key.toLowerCase(),\n info.request_hash.toLowerCase(),\n info.nonce.toLowerCase(),\n ]),\n )\n .digest(\"hex\");\n return `tenuo-approval-v1:${digest}`;\n}\n\nfunction replayStoreUnavailable(\n cause: unknown,\n onNonceStoreError?: (error: unknown) => void | Promise<void>,\n): TenuoConfigurationError {\n if (onNonceStoreError !== undefined) {\n try {\n const result = onNonceStoreError(cause);\n if (isThenable(result)) {\n void Promise.resolve(result).catch(() => undefined);\n }\n } catch {\n // Isolated — must not change the public error.\n }\n }\n const error = new TenuoConfigurationError(REPLAY_STORE_UNAVAILABLE);\n if (cause !== undefined) {\n error.cause = cause;\n }\n return error;\n}\n\nfunction isThenable(value: unknown): value is Promise<unknown> {\n return value !== null && typeof value === \"object\" && \"then\" in value && typeof value.then === \"function\";\n}\n"]}
package/dist/session.d.ts CHANGED
@@ -11,6 +11,7 @@ export declare class Session implements SessionContract {
11
11
  toString(): string;
12
12
  [inspect](): string;
13
13
  toWire(): string[];
14
+ toWireString(): string;
14
15
  dedupKey(tool: string, args: Readonly<Record<string, unknown>>): string;
15
16
  inspect(): SessionInfo;
16
17
  /**
package/dist/session.js CHANGED
@@ -30,6 +30,17 @@ export class Session {
30
30
  }
31
31
  return tokens;
32
32
  }
33
+ toWireString() {
34
+ const native = nativeSessions.get(this);
35
+ if (typeof native?.toStackWire !== "function") {
36
+ throw new TenuoConfigurationError("session is not bound to the WASM core");
37
+ }
38
+ const stack = native.toStackWire();
39
+ if (typeof stack !== "string" || stack.length === 0) {
40
+ throw new TenuoConfigurationError("toWireString() did not return a warrant stack");
41
+ }
42
+ return stack;
43
+ }
33
44
  dedupKey(tool, args) {
34
45
  const native = nativeSessions.get(this);
35
46
  if (typeof native?.dedupKey !== "function") {
@@ -1 +1 @@
1
- {"version":3,"file":"session.js","sourceRoot":"","sources":["../src/session.ts"],"names":[],"mappings":"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","sourcesContent":["import type { Session as SessionContract, SessionInfo } from \"./api.ts\";\nimport { TenuoConfigurationError } from \"./errors.ts\";\nimport {\n acknowledgeSessionReceipts,\n drainSessionReceipts,\n peekSessionReceipts,\n} from \"./runtime.ts\";\nimport type { WasmSession, WasmSessionInfo } from \"./wasm.ts\";\n\nconst inspect = Symbol.for(\"nodejs.util.inspect.custom\");\nconst nativeSessions = new WeakMap<Session, WasmSession>();\n\n/**\n * Opaque session handle. Structural clones and JSON are not sessions.\n */\nexport class Session implements SessionContract {\n readonly [Symbol.toStringTag] = \"TenuoSession\" as const;\n\n constructor(native: WasmSession) {\n nativeSessions.set(this, native);\n }\n\n toJSON(): string {\n return \"[TenuoSession]\";\n }\n\n toString(): string {\n return \"[TenuoSession]\";\n }\n\n [inspect](): string {\n return \"[TenuoSession]\";\n }\n\n toWire(): string[] {\n const native = nativeSessions.get(this) as { toWire?: () => unknown } | undefined;\n if (native?.toWire === undefined) {\n throw new TenuoConfigurationError(\"session is not bound to the WASM core\");\n }\n const tokens = native.toWire();\n if (!Array.isArray(tokens) || tokens.some((token) => typeof token !== \"string\")) {\n throw new TenuoConfigurationError(\"toWire() did not return warrant tokens\");\n }\n return tokens;\n }\n\n dedupKey(tool: string, args: Readonly<Record<string, unknown>>): string {\n const native = nativeSessions.get(this) as\n | { dedupKey?: (tool: string, args: unknown) => string }\n | undefined;\n if (typeof native?.dedupKey !== \"function\") {\n throw new TenuoConfigurationError(\"session is not bound to the WASM core\");\n }\n return native.dedupKey(tool, args);\n }\n\n inspect(): SessionInfo {\n const native = nativeSessions.get(this) as { describe?: () => unknown } | undefined;\n if (typeof native?.describe !== \"function\") {\n throw new TenuoConfigurationError(\"session is not bound to the WASM core\");\n }\n const info = native.describe() as WasmSessionInfo;\n const out: {\n -readonly [K in keyof SessionInfo]: SessionInfo[K];\n } = {\n kind: info.kind,\n holderPublicKey: info.holder_public_key,\n rootPublicKey: info.root_public_key,\n depth: info.depth,\n maxDepth: info.max_depth,\n terminal: info.terminal,\n expiresAt: info.expires_at,\n tools: [...info.tools],\n warrantIds: [...info.warrant_ids],\n canAuthorize: info.can_authorize,\n approvalGatedTools: [...info.approval_gated_tools],\n };\n if (info.clearance !== undefined) {\n out.clearance = info.clearance;\n }\n if (info.session_id !== undefined) {\n out.sessionId = info.session_id;\n }\n if (info.agent_id !== undefined) {\n out.agentId = info.agent_id;\n }\n if (info.issuable_tools !== undefined) {\n out.issuableTools = [...info.issuable_tools];\n }\n if (info.max_issue_depth !== undefined) {\n out.maxIssueDepth = info.max_issue_depth;\n }\n if (info.required_approvers !== undefined) {\n out.requiredApprovers = [...info.required_approvers];\n }\n if (info.min_approvals !== undefined) {\n out.minApprovals = info.min_approvals;\n }\n return out;\n }\n\n /**\n * Snapshot of collected receipts. Same as `drainReceipts`; nothing is\n * removed until `acknowledgeReceipts`.\n */\n peekReceipts(): string[] {\n return peekSessionReceipts(this);\n }\n\n drainReceipts(): string[] {\n return drainSessionReceipts(this);\n }\n\n acknowledgeReceipts(count: number): number {\n return acknowledgeSessionReceipts(this, count);\n }\n}\n\nexport function isSession(value: unknown): value is Session {\n return value instanceof Session;\n}\n\nexport function nativeSession(session: Session): WasmSession {\n const native = nativeSessions.get(session);\n if (!native) {\n throw new TenuoConfigurationError(\"session is not bound to the WASM core\");\n }\n return native;\n}\n"]}
1
+ {"version":3,"file":"session.js","sourceRoot":"","sources":["../src/session.ts"],"names":[],"mappings":"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","sourcesContent":["import type { Session as SessionContract, SessionInfo } from \"./api.ts\";\nimport { TenuoConfigurationError } from \"./errors.ts\";\nimport {\n acknowledgeSessionReceipts,\n drainSessionReceipts,\n peekSessionReceipts,\n} from \"./runtime.ts\";\nimport type { WasmSession, WasmSessionInfo } from \"./wasm.ts\";\n\nconst inspect = Symbol.for(\"nodejs.util.inspect.custom\");\nconst nativeSessions = new WeakMap<Session, WasmSession>();\n\n/**\n * Opaque session handle. Structural clones and JSON are not sessions.\n */\nexport class Session implements SessionContract {\n readonly [Symbol.toStringTag] = \"TenuoSession\" as const;\n\n constructor(native: WasmSession) {\n nativeSessions.set(this, native);\n }\n\n toJSON(): string {\n return \"[TenuoSession]\";\n }\n\n toString(): string {\n return \"[TenuoSession]\";\n }\n\n [inspect](): string {\n return \"[TenuoSession]\";\n }\n\n toWire(): string[] {\n const native = nativeSessions.get(this) as { toWire?: () => unknown } | undefined;\n if (native?.toWire === undefined) {\n throw new TenuoConfigurationError(\"session is not bound to the WASM core\");\n }\n const tokens = native.toWire();\n if (!Array.isArray(tokens) || tokens.some((token) => typeof token !== \"string\")) {\n throw new TenuoConfigurationError(\"toWire() did not return warrant tokens\");\n }\n return tokens;\n }\n\n toWireString(): string {\n const native = nativeSessions.get(this) as { toStackWire?: () => unknown } | undefined;\n if (typeof native?.toStackWire !== \"function\") {\n throw new TenuoConfigurationError(\"session is not bound to the WASM core\");\n }\n const stack = native.toStackWire();\n if (typeof stack !== \"string\" || stack.length === 0) {\n throw new TenuoConfigurationError(\"toWireString() did not return a warrant stack\");\n }\n return stack;\n }\n\n dedupKey(tool: string, args: Readonly<Record<string, unknown>>): string {\n const native = nativeSessions.get(this) as\n | { dedupKey?: (tool: string, args: unknown) => string }\n | undefined;\n if (typeof native?.dedupKey !== \"function\") {\n throw new TenuoConfigurationError(\"session is not bound to the WASM core\");\n }\n return native.dedupKey(tool, args);\n }\n\n inspect(): SessionInfo {\n const native = nativeSessions.get(this) as { describe?: () => unknown } | undefined;\n if (typeof native?.describe !== \"function\") {\n throw new TenuoConfigurationError(\"session is not bound to the WASM core\");\n }\n const info = native.describe() as WasmSessionInfo;\n const out: {\n -readonly [K in keyof SessionInfo]: SessionInfo[K];\n } = {\n kind: info.kind,\n holderPublicKey: info.holder_public_key,\n rootPublicKey: info.root_public_key,\n depth: info.depth,\n maxDepth: info.max_depth,\n terminal: info.terminal,\n expiresAt: info.expires_at,\n tools: [...info.tools],\n warrantIds: [...info.warrant_ids],\n canAuthorize: info.can_authorize,\n approvalGatedTools: [...info.approval_gated_tools],\n };\n if (info.clearance !== undefined) {\n out.clearance = info.clearance;\n }\n if (info.session_id !== undefined) {\n out.sessionId = info.session_id;\n }\n if (info.agent_id !== undefined) {\n out.agentId = info.agent_id;\n }\n if (info.issuable_tools !== undefined) {\n out.issuableTools = [...info.issuable_tools];\n }\n if (info.max_issue_depth !== undefined) {\n out.maxIssueDepth = info.max_issue_depth;\n }\n if (info.required_approvers !== undefined) {\n out.requiredApprovers = [...info.required_approvers];\n }\n if (info.min_approvals !== undefined) {\n out.minApprovals = info.min_approvals;\n }\n return out;\n }\n\n /**\n * Snapshot of collected receipts. Same as `drainReceipts`; nothing is\n * removed until `acknowledgeReceipts`.\n */\n peekReceipts(): string[] {\n return peekSessionReceipts(this);\n }\n\n drainReceipts(): string[] {\n return drainSessionReceipts(this);\n }\n\n acknowledgeReceipts(count: number): number {\n return acknowledgeSessionReceipts(this, count);\n }\n}\n\nexport function isSession(value: unknown): value is Session {\n return value instanceof Session;\n}\n\nexport function nativeSession(session: Session): WasmSession {\n const native = nativeSessions.get(session);\n if (!native) {\n throw new TenuoConfigurationError(\"session is not bound to the WASM core\");\n }\n return native;\n}\n"]}
package/dist/wasm.d.ts CHANGED
@@ -15,6 +15,18 @@ export type WasmInspect = {
15
15
  signature_hex: string;
16
16
  id: string;
17
17
  };
18
+ export type ConstraintViolationKind = "tool_not_authorized" | "unknown_field" | "missing_required" | "constraint_mismatch";
19
+ export type ConstraintDiagnostics = {
20
+ closedWorld: boolean;
21
+ violations: Array<{
22
+ field: string;
23
+ kind: ConstraintViolationKind;
24
+ reason: string;
25
+ }>;
26
+ };
27
+ type WasmConstraintDiagnostics = Omit<ConstraintDiagnostics, "closedWorld"> & {
28
+ closed_world: boolean;
29
+ };
18
30
  export type WasmReceipt = {
19
31
  authentic: true;
20
32
  /** Hex key the receipt is signed under — resolve against your authorizer set. */
@@ -193,6 +205,7 @@ export type WasmAttestation = {
193
205
  export type WasmApprovalInfo = {
194
206
  approver_public_key: string;
195
207
  request_hash: string;
208
+ nonce: string;
196
209
  external_id: string;
197
210
  approved_at: number;
198
211
  expires_at: number;
@@ -246,12 +259,13 @@ type Generated = {
246
259
  fromChain(parts: unknown, holder: Uint8Array): WasmSession;
247
260
  };
248
261
  sdkInspectWarrant(wire: string): WasmInspect;
262
+ sdkDiagnoseConstraints(wire: string, tool: string, args: unknown): WasmConstraintDiagnostics;
249
263
  sdkInspectParts(payloadHex: string, signatureHex: string): WasmInspect;
250
264
  sdkProtocolLimits(): WasmProtocolLimits;
251
265
  sdkPublicKeyFromHolderKey(holderSecret: Uint8Array): string;
252
266
  sdkSignApproval(session: WasmSession, tool: string, args: unknown, approverSecret: Uint8Array, externalId: string, asOf?: number): string;
253
267
  sdkSignApprovalForRequest(requestHashHex: string, approverSecret: Uint8Array, externalId: string, ttlSeconds?: number, warrantExpiresAt?: number): string;
254
- sdkInspectApproval(envelope: string): WasmApprovalInfo;
268
+ sdkInspectApproval(envelope: string | Uint8Array): WasmApprovalInfo;
255
269
  sdkSignRevocationList(ids: string[], issuerSecret: Uint8Array): string;
256
270
  sdkSignRevocationListVersioned(ids: string[], version: number | undefined, issuerSecret: Uint8Array): string;
257
271
  sdkSignPublishedRevocationList(ids: string[], version: number, issuerSecret: Uint8Array): string;
@@ -270,7 +284,18 @@ type Generated = {
270
284
  error?: string;
271
285
  };
272
286
  parse_strict_json(jsonText: string): void;
287
+ shlex_inspect(allow: string[], command: string): ShlexCheck;
273
288
  };
289
+ /** Decision from the core Shlex check. The same object Python `Shlex.check` returns. */
290
+ export interface ShlexCheck {
291
+ allowed: boolean;
292
+ reason: string;
293
+ tokens: string[];
294
+ operators: string[];
295
+ expansion: string[];
296
+ controls: string[];
297
+ binary_allowed: boolean;
298
+ }
274
299
  export declare function wasmAvailable(): boolean;
275
300
  export declare function loadWasm(): Generated;
276
301
  export declare function createDevContext(receiptSigner?: Uint8Array): WasmContext;
@@ -281,6 +306,12 @@ export declare function importSessionFromWire(warrant: string, holderKey: Uint8A
281
306
  export declare function importSessionFromParts(payloadHex: string, signatureHex: string, holderKey: Uint8Array): WasmSession;
282
307
  export declare function importSessionFromChain(parts: unknown, holderKey: Uint8Array): WasmSession;
283
308
  export declare function inspectWarrant(wire: string): WasmInspect;
309
+ /**
310
+ * List every capability-constraint failure in deterministic order.
311
+ * Diagnostic only: this does not verify signatures, expiry, revocation,
312
+ * approvals, or proof-of-possession, and must not be used to authorize.
313
+ */
314
+ export declare function diagnoseConstraints(wire: string, tool: string, args: unknown): ConstraintDiagnostics;
284
315
  export declare function inspectParts(payloadHex: string, signatureHex: string): WasmInspect;
285
316
  /** Hex public key for a 32-byte holder secret. Ed25519, derived in Rust. */
286
317
  export declare function publicKeyHexFromHolderKey(holderSecret: Uint8Array): string;
@@ -310,6 +341,14 @@ export type ApprovalGateInspection = {
310
341
  * property and `null` stays `null`.
311
342
  */
312
343
  export declare function parseStrictJson(text: string): unknown;
344
+ /**
345
+ * The core's Shlex decision for one command.
346
+ *
347
+ * `allow` is the binary allowlist. Each entry matches the command word
348
+ * literally, and any command word containing `..` is rejected. The reason is
349
+ * the same string the warrant uses.
350
+ */
351
+ export declare function checkShlex(allow: readonly string[], command: string): ShlexCheck;
313
352
  /**
314
353
  * Typed preflight of a warrant's approval gates for `(tool, args)`.
315
354
  *
package/dist/wasm.js CHANGED
@@ -76,6 +76,15 @@ export function importSessionFromChain(parts, holderKey) {
76
76
  export function inspectWarrant(wire) {
77
77
  return loadWasm().sdkInspectWarrant(wire);
78
78
  }
79
+ /**
80
+ * List every capability-constraint failure in deterministic order.
81
+ * Diagnostic only: this does not verify signatures, expiry, revocation,
82
+ * approvals, or proof-of-possession, and must not be used to authorize.
83
+ */
84
+ export function diagnoseConstraints(wire, tool, args) {
85
+ const result = loadWasm().sdkDiagnoseConstraints(wire, tool, args);
86
+ return { closedWorld: result.closed_world, violations: result.violations };
87
+ }
79
88
  export function inspectParts(payloadHex, signatureHex) {
80
89
  return loadWasm().sdkInspectParts(payloadHex, signatureHex);
81
90
  }
@@ -103,6 +112,16 @@ export function parseStrictJson(text) {
103
112
  loadWasm().parse_strict_json(text);
104
113
  return JSON.parse(text);
105
114
  }
115
+ /**
116
+ * The core's Shlex decision for one command.
117
+ *
118
+ * `allow` is the binary allowlist. Each entry matches the command word
119
+ * literally, and any command word containing `..` is rejected. The reason is
120
+ * the same string the warrant uses.
121
+ */
122
+ export function checkShlex(allow, command) {
123
+ return loadWasm().shlex_inspect([...allow], command);
124
+ }
106
125
  /**
107
126
  * Typed preflight of a warrant's approval gates for `(tool, args)`.
108
127
  *