@sema-agent/server 1.283.0 → 1.284.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.
@@ -5,7 +5,7 @@ import { sessionLogDigest, sessionLogDigestsComparable, uuidv7, callKeyOrdinal,
5
5
  import { decideParkedAgent, findParkedAgentForCheckpoint } from "../parked-decide.js";
6
6
  import { matchCatalogModel } from "../model-select.js";
7
7
  import { mcpForScenario } from "../sema-registry.js";
8
- import { HttpError, principalFrom, verifiedPrincipal, setSsoPrincipal, ssoVerifiedPrincipal, setSsoScope, isUuidV7, isUuidShape, verifyDirectDoorProof } from "../security.js";
8
+ import { HttpError, principalFrom, verifiedPrincipal, setSsoPrincipal, ssoVerifiedPrincipal, setSsoScope, isUuidV7, isUuidShape, verifyDirectDoorProof, MAX_APPROVAL_REASON_CHARS } from "../security.js";
9
9
  import { exportSession, exportSessionManifest, importSession, classifySyncRelationshipByIds, SyncConflictError, stagingIdFor } from "../session-sync.js";
10
10
  import { windowMessages, truncateMessageBlobs } from "../audit.js";
11
11
  import { parseMemorySyncRequest } from "../memory-sync.js";
@@ -2898,6 +2898,10 @@ export function createHttpServer(deps) {
2898
2898
  sendJson(res, 400, { error: "reason must be a string" });
2899
2899
  return;
2900
2900
  }
2901
+ if (typeof body.reason === "string" && body.reason.length > MAX_APPROVAL_REASON_CHARS) {
2902
+ sendJson(res, 413, { error: `reason too large (max ${MAX_APPROVAL_REASON_CHARS} chars)`, errorCode: "reason_too_large" });
2903
+ return;
2904
+ }
2901
2905
  const run = await deps.runStore.getRun(taskId);
2902
2906
  if (!run) {
2903
2907
  if (deps.config.directDoorActive) {
@@ -2910,7 +2914,7 @@ export function createHttpServer(deps) {
2910
2914
  let deciderPrincipal = principal;
2911
2915
  if (deps.config.directDoorActive) {
2912
2916
  const hdr = (n) => { const h = req.headers[n]; return Array.isArray(h) ? h[0] : h; };
2913
- const proof = verifyDirectDoorProof({ jwt: hdr("x-approval-principal-token"), mac: hdr("x-approval-mac"), kid: hdr("x-approval-mac-kid") }, { sessionId: run.sessionId, decision }, deps.config, { actionBinding: false });
2917
+ const proof = verifyDirectDoorProof({ jwt: hdr("x-approval-principal-token"), mac: hdr("x-approval-mac"), kid: hdr("x-approval-mac-kid") }, { sessionId: run.sessionId, decision, reason: typeof body.reason === "string" ? body.reason : null }, deps.config, { actionBinding: false });
2914
2918
  if (!proof.ok) {
2915
2919
  sendJson(res, proof.status, { error: proof.error, errorCode: proof.errorCode });
2916
2920
  return;
@@ -2945,6 +2949,14 @@ export function createHttpServer(deps) {
2945
2949
  sendJson(res, 400, { error: "body must be { decision: 'approve' | 'deny', reason?, answer?, checkpointToken?, boundCallId?, boundInputHash?, updatedInput?, remember? }" });
2946
2950
  return;
2947
2951
  }
2952
+ if (body.reason !== undefined && typeof body.reason !== "string") {
2953
+ sendJson(res, 400, { error: "reason must be a string" });
2954
+ return;
2955
+ }
2956
+ if (typeof body.reason === "string" && body.reason.length > MAX_APPROVAL_REASON_CHARS) {
2957
+ sendJson(res, 413, { error: `reason too large (max ${MAX_APPROVAL_REASON_CHARS} chars)`, errorCode: "reason_too_large" });
2958
+ return;
2959
+ }
2948
2960
  let remember = false;
2949
2961
  if (body.remember !== undefined) {
2950
2962
  if (body.remember !== "session") {
@@ -3002,7 +3014,7 @@ export function createHttpServer(deps) {
3002
3014
  const h = req.headers[n];
3003
3015
  return Array.isArray(h) ? h[0] : h;
3004
3016
  };
3005
- const proof = verifyDirectDoorProof({ jwt: hdr("x-approval-principal-token"), mac: hdr("x-approval-mac"), kid: hdr("x-approval-mac-kid") }, { sessionId, boundCallId: binding.boundCallId, boundInputHash: binding.boundInputHash, decision }, deps.config);
3017
+ const proof = verifyDirectDoorProof({ jwt: hdr("x-approval-principal-token"), mac: hdr("x-approval-mac"), kid: hdr("x-approval-mac-kid") }, { sessionId, boundCallId: binding.boundCallId, boundInputHash: binding.boundInputHash, decision, reason: typeof body.reason === "string" ? body.reason : null }, deps.config);
3006
3018
  if (!proof.ok) {
3007
3019
  sendJson(res, proof.status, { error: proof.error, errorCode: proof.errorCode });
3008
3020
  return;
@@ -103,12 +103,15 @@ export declare function approvalHmacMessage(env: {
103
103
  boundCallId?: string | null;
104
104
  boundInputHash?: string | null;
105
105
  decision: string;
106
+ reason?: string | null;
106
107
  }): string;
108
+ export declare const MAX_APPROVAL_REASON_CHARS = 4096;
107
109
  export declare function verifyApprovalHmac(env: {
108
110
  sessionId: string;
109
111
  boundCallId?: string | null;
110
112
  boundInputHash?: string | null;
111
113
  decision: string;
114
+ reason?: string | null;
112
115
  }, mac: string, kid: string | undefined, keys: ApprovalHmacKey[]): boolean;
113
116
  export interface PrincipalJwtKey {
114
117
  kid: string;
@@ -141,6 +144,7 @@ export declare function verifyDirectDoorProof(proof: {
141
144
  boundCallId?: string | null;
142
145
  boundInputHash?: string | null;
143
146
  decision: string;
147
+ reason?: string | null;
144
148
  }, config: {
145
149
  principalJwtPubkeys: PrincipalJwtKey[];
146
150
  principalJwtIss?: string;
package/dist/security.js CHANGED
@@ -176,8 +176,9 @@ export function parseApprovalHmacKeys(raw) {
176
176
  }
177
177
  }
178
178
  export function approvalHmacMessage(env) {
179
- return JSON.stringify([env.sessionId, env.boundCallId ?? null, env.boundInputHash ?? null, env.decision]);
179
+ return JSON.stringify([env.sessionId, env.boundCallId ?? null, env.boundInputHash ?? null, env.decision, env.reason ?? null]);
180
180
  }
181
+ export const MAX_APPROVAL_REASON_CHARS = 4_096;
181
182
  export function verifyApprovalHmac(env, mac, kid, keys) {
182
183
  if (keys.length === 0)
183
184
  return false;
@@ -303,7 +304,7 @@ export function verifyDirectDoorProof(proof, envelope, config, opts) {
303
304
  return { ok: false, status: 401, errorCode: "principal_unbound", error: "principal token is not bound to this decision (cnf.bnd mismatch)" };
304
305
  if (!proof.mac)
305
306
  return { ok: false, status: 401, errorCode: "approval_mac_required", error: "direct door requires an approval MAC (X-Approval-Mac)" };
306
- const ok = verifyApprovalHmac({ sessionId: envelope.sessionId, boundCallId: envelope.boundCallId ?? null, boundInputHash: envelope.boundInputHash ?? null, decision: envelope.decision }, proof.mac, proof.kid, config.approvalHmacKeys);
307
+ const ok = verifyApprovalHmac({ sessionId: envelope.sessionId, boundCallId: envelope.boundCallId ?? null, boundInputHash: envelope.boundInputHash ?? null, decision: envelope.decision, reason: envelope.reason ?? null }, proof.mac, proof.kid, config.approvalHmacKeys);
307
308
  if (!ok)
308
309
  return { ok: false, status: 401, errorCode: "approval_mac_invalid", error: "approval envelope MAC invalid" };
309
310
  return { ok: true, principal: verified.sub };
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@sema-agent/server",
3
- "version": "1.283.0",
3
+ "version": "1.284.0",
4
4
  "description": "Sema Server — the server/API implementation layer for Sema, wiring core, registry, model providers, and cloud agent execution. Built on @sema-agent/core.",
5
5
  "type": "module",
6
6
  "license": "BUSL-1.1",