@scopebond/hook 0.15.2 → 0.16.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +9 -0
- package/dist/cli.d.ts.map +1 -1
- package/dist/cli.js +31 -3
- package/dist/cli.js.map +1 -1
- package/dist/index.d.ts +4 -2
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +2 -1
- package/dist/index.js.map +1 -1
- package/dist/managed.d.ts +16 -1
- package/dist/managed.d.ts.map +1 -1
- package/dist/managed.js +29 -6
- package/dist/managed.js.map +1 -1
- package/dist/override.d.ts +51 -0
- package/dist/override.d.ts.map +1 -0
- package/dist/override.js +193 -0
- package/dist/override.js.map +1 -0
- package/dist/runtime.d.ts +9 -2
- package/dist/runtime.d.ts.map +1 -1
- package/dist/runtime.js +12 -3
- package/dist/runtime.js.map +1 -1
- package/package.json +4 -4
package/dist/override.js
ADDED
|
@@ -0,0 +1,193 @@
|
|
|
1
|
+
// Warn mode: a person at the computer may allow one action a workspace rule blocks, when the workspace set that rule to
|
|
2
|
+
// "Block, user may override" and lets someone override. Only a channel the coding agent cannot answer counts:
|
|
3
|
+
//
|
|
4
|
+
// 1. The Scopebond Agent's own window. The hook asks the resident agent on its local channel and waits; the answer comes
|
|
5
|
+
// from the window, never from whoever called the channel. The person gives a reason; its digest is signed into the receipt
|
|
6
|
+
// and the agent sends the text to the workspace.
|
|
7
|
+
// 2. Claude Code's own permission prompt, only when the workspace allows it, only in a permission mode where Claude Code
|
|
8
|
+
// really asks the person (default, acceptEdits, plan), and only when the window is not available. It takes no reason and
|
|
9
|
+
// does not prove who answered, so the receipt says "offered", not "allowed".
|
|
10
|
+
//
|
|
11
|
+
// Never overridable: a denial that a rule on Block, or Scopebond's own protection, would also make (checked by evaluating the
|
|
12
|
+
// action against the same rules with every overridable rule recorded instead of blocked), and the kill switch (the gateway
|
|
13
|
+
// never asks while it is on). Beyond the daily limit the rule blocks.
|
|
14
|
+
import { createHash } from "node:crypto";
|
|
15
|
+
import { existsSync, mkdirSync, readFileSync } from "node:fs";
|
|
16
|
+
import { join } from "node:path";
|
|
17
|
+
import { userInfo } from "node:os";
|
|
18
|
+
import { evaluate } from "@scopebond/gateway";
|
|
19
|
+
import { compileManaged, floorDocument, MANAGED_DOC_FILE, RULE_OF_CLAUSE, writeAtomic } from "./managed.js";
|
|
20
|
+
import { defaultRules, loadRules } from "./rules.js";
|
|
21
|
+
export const OVERRIDE_STATE_FILE = "overrides.json";
|
|
22
|
+
const AGENT_FILE = "agent.json";
|
|
23
|
+
const TOKEN_HEADER = "x-scopebond-agent-token";
|
|
24
|
+
/** Claude Code permission modes in which it shows the person a prompt for "ask". Any other or missing mode blocks. */
|
|
25
|
+
export const PROMPTING_MODES = new Set(["default", "acceptEdits", "plan"]);
|
|
26
|
+
const DAY_MS = 24 * 60 * 60 * 1000;
|
|
27
|
+
const RULE_TITLE = {
|
|
28
|
+
"force-push-protected": "Force-push to a protected branch",
|
|
29
|
+
"push-protected": "Push to a protected branch",
|
|
30
|
+
"destructive-shell": "Destructive command",
|
|
31
|
+
"secret-read": "Reading a secret file",
|
|
32
|
+
"ci-config-write": "Changing CI configuration",
|
|
33
|
+
"network-egress": "Request to a site not on the allowed list",
|
|
34
|
+
};
|
|
35
|
+
export const digestOf = (text) => createHash("sha256").update(text, "utf8").digest("hex");
|
|
36
|
+
function readState(dir) {
|
|
37
|
+
try {
|
|
38
|
+
const parsed = JSON.parse(readFileSync(join(dir, OVERRIDE_STATE_FILE), "utf8"));
|
|
39
|
+
return Array.isArray(parsed.entries) ? parsed : { entries: [] };
|
|
40
|
+
}
|
|
41
|
+
catch {
|
|
42
|
+
return { entries: [] };
|
|
43
|
+
}
|
|
44
|
+
}
|
|
45
|
+
function writeState(dir, state, now) {
|
|
46
|
+
// Keep two days: enough for today's count and any time window.
|
|
47
|
+
const kept = state.entries.filter((e) => now - e.at < 2 * DAY_MS).slice(-500);
|
|
48
|
+
mkdirSync(dir, { recursive: true });
|
|
49
|
+
writeAtomic(join(dir, OVERRIDE_STATE_FILE), `${JSON.stringify({ entries: kept })}\n`);
|
|
50
|
+
}
|
|
51
|
+
/** One line a person can judge at a glance, from the minimized intent: never file contents or command output. */
|
|
52
|
+
export function actionSummary(intent) {
|
|
53
|
+
const p = intent.params ?? {};
|
|
54
|
+
const pick = (k) => (typeof p[k] === "string" ? String(p[k]) : "");
|
|
55
|
+
const text = intent.action_type === "shell.exec" ? pick("command") || pick("program")
|
|
56
|
+
: intent.action_type === "git.push" ? `git push ${pick("remote")} ${pick("ref")}`.trim()
|
|
57
|
+
: intent.action_type === "net.fetch" ? pick("url") || pick("host")
|
|
58
|
+
: pick("path") || intent.action_type || "an action";
|
|
59
|
+
// Arguments the hook keeps only as a keyed digest stay out of the window.
|
|
60
|
+
const shown = text.slice(0, 2000).replace(/ ?\(hmac-sha256:[0-9a-f]{64}\)/g, "").trim();
|
|
61
|
+
return shown.length > 200 ? `${shown.slice(0, 197)}...` : shown;
|
|
62
|
+
}
|
|
63
|
+
/** The same action, whatever tool call it came from: its type and parameters without the per-call group id. */
|
|
64
|
+
export function actionKey(intent) {
|
|
65
|
+
const { action_group: _group, ...params } = intent.params ?? {};
|
|
66
|
+
const sorted = (v) => Array.isArray(v) ? v.map(sorted)
|
|
67
|
+
: v && typeof v === "object" ? Object.fromEntries(Object.keys(v).sort().map((k) => [k, sorted(v[k])])) : v;
|
|
68
|
+
return digestOf(JSON.stringify({ action_type: intent.action_type ?? "", params: sorted(params) }));
|
|
69
|
+
}
|
|
70
|
+
/** Ask the resident Scopebond Agent to show its window. Returns "unavailable" when no agent answers. */
|
|
71
|
+
export async function askAgent(home, request, timeoutMs, fetchImpl = fetch) {
|
|
72
|
+
let endpoint;
|
|
73
|
+
try {
|
|
74
|
+
endpoint = JSON.parse(readFileSync(join(home, AGENT_FILE), "utf8"));
|
|
75
|
+
}
|
|
76
|
+
catch {
|
|
77
|
+
return { decision: "unavailable" };
|
|
78
|
+
}
|
|
79
|
+
if (!Number.isInteger(endpoint.port) || typeof endpoint.token !== "string")
|
|
80
|
+
return { decision: "unavailable" };
|
|
81
|
+
try {
|
|
82
|
+
const res = await fetchImpl(`http://127.0.0.1:${endpoint.port}/override`, {
|
|
83
|
+
method: "POST", headers: { "content-type": "application/json", [TOKEN_HEADER]: endpoint.token }, body: JSON.stringify(request),
|
|
84
|
+
signal: AbortSignal.timeout(timeoutMs),
|
|
85
|
+
});
|
|
86
|
+
if (!res.ok)
|
|
87
|
+
return { decision: "unavailable" };
|
|
88
|
+
const body = await res.json();
|
|
89
|
+
if (body.decision === "allow" && typeof body.reason === "string")
|
|
90
|
+
return { decision: "allow", reason: body.reason, ...(typeof body.os_user === "string" ? { os_user: body.os_user } : {}) };
|
|
91
|
+
return { decision: body.decision === "deny" ? "deny" : "unavailable" };
|
|
92
|
+
}
|
|
93
|
+
catch {
|
|
94
|
+
return { decision: "unavailable" };
|
|
95
|
+
}
|
|
96
|
+
}
|
|
97
|
+
export function createOverrideHandler(ctx) {
|
|
98
|
+
let doc;
|
|
99
|
+
try {
|
|
100
|
+
doc = JSON.parse(readFileSync(join(ctx.dir, MANAGED_DOC_FILE), "utf8"));
|
|
101
|
+
}
|
|
102
|
+
catch {
|
|
103
|
+
return null;
|
|
104
|
+
}
|
|
105
|
+
if (!Object.values(doc.rules ?? {}).some((r) => r?.mode === "override"))
|
|
106
|
+
return null;
|
|
107
|
+
const now = ctx.now ?? Date.now;
|
|
108
|
+
let last = null;
|
|
109
|
+
const handler = async ({ verdict, action_id, intent, intent_hash }) => {
|
|
110
|
+
const key = actionKey(intent);
|
|
111
|
+
const rule = RULE_OF_CLAUSE[verdict.clause_id ?? ""];
|
|
112
|
+
const setting = rule ? doc.rules[rule] : undefined;
|
|
113
|
+
if (!rule || setting?.mode !== "override" || !setting.override)
|
|
114
|
+
return null;
|
|
115
|
+
const terms = setting.override;
|
|
116
|
+
const title = RULE_TITLE[rule];
|
|
117
|
+
// Would the action still be denied with every overridable rule only recorded? Then a Block rule or Scopebond's own
|
|
118
|
+
// protection denies it too, and no one may override that.
|
|
119
|
+
const floor = compileManaged(loadRules(ctx.dir) ?? defaultRules(), floorDocument(doc), ctx.agentKid);
|
|
120
|
+
const atFloor = evaluate(floor, [], { intent, intent_hash }, new Date(now()).toISOString(), { cooperative: true });
|
|
121
|
+
if (!atFloor.allow) {
|
|
122
|
+
last = { rule, title, outcome: "not_overridable" };
|
|
123
|
+
return null;
|
|
124
|
+
}
|
|
125
|
+
const t = now();
|
|
126
|
+
const state = readState(ctx.dir);
|
|
127
|
+
const dayStart = t - (t % DAY_MS);
|
|
128
|
+
// Inside the time an earlier override allowed, the same action is allowed again without asking, and says so.
|
|
129
|
+
if (terms.minutes > 0) {
|
|
130
|
+
const earlier = [...state.entries].reverse().find((e) => e.rule === rule && e.action_key === key && t - e.at <= terms.minutes * 60_000);
|
|
131
|
+
if (earlier) {
|
|
132
|
+
return { version: 1, rule, method: "agent_dialog", state: "allowed", repeat_of: earlier.action_id, reason_digest: earlier.reason_digest,
|
|
133
|
+
reason_length: earlier.reason_length, os_user_digest: osUserDigest(), decided_at: new Date(t).toISOString() };
|
|
134
|
+
}
|
|
135
|
+
}
|
|
136
|
+
if (state.entries.filter((e) => e.rule === rule && e.at >= dayStart).length >= terms.daily_limit) {
|
|
137
|
+
last = { rule, title, outcome: "limit" };
|
|
138
|
+
return null;
|
|
139
|
+
}
|
|
140
|
+
const answer = await (ctx.ask ?? askAgent)(ctx.home, {
|
|
141
|
+
action_id, rule, title, summary: actionSummary(intent), reason_min: terms.reason_min,
|
|
142
|
+
lasts: terms.minutes > 0 ? `the same action for ${terms.minutes} minutes` : "this action only", timeout_ms: ctx.waitMs ?? 45_000,
|
|
143
|
+
}, (ctx.waitMs ?? 45_000) + 2_000);
|
|
144
|
+
if (answer.decision === "allow") {
|
|
145
|
+
const reason = (answer.reason ?? "").trim();
|
|
146
|
+
if (reason.length < terms.reason_min || reason.length > 500) {
|
|
147
|
+
last = { rule, title, outcome: "declined" };
|
|
148
|
+
return null;
|
|
149
|
+
}
|
|
150
|
+
const record = { version: 1, rule, method: "agent_dialog", state: "allowed", repeat_of: null, reason_digest: digestOf(reason),
|
|
151
|
+
reason_length: reason.length, os_user_digest: answer.os_user ? digestOf(answer.os_user) : osUserDigest(), decided_at: new Date(t).toISOString() };
|
|
152
|
+
state.entries.push({ rule, action_key: key, action_id, at: t, reason_digest: record.reason_digest, reason_length: reason.length });
|
|
153
|
+
try {
|
|
154
|
+
writeState(ctx.dir, state, t);
|
|
155
|
+
}
|
|
156
|
+
catch { /* the receipt still records it; the count is checked again in the workspace */ }
|
|
157
|
+
return record;
|
|
158
|
+
}
|
|
159
|
+
if (answer.decision === "deny") {
|
|
160
|
+
last = { rule, title, outcome: "declined" };
|
|
161
|
+
return null;
|
|
162
|
+
}
|
|
163
|
+
// No window: Claude Code's own prompt, only where the workspace allows it and Claude Code really asks the person.
|
|
164
|
+
if (terms.harness_prompt && ctx.harness === "claude" && PROMPTING_MODES.has(ctx.permissionMode ?? "")) {
|
|
165
|
+
return { version: 1, rule, method: "harness_prompt", state: "offered", repeat_of: null, reason_digest: null, reason_length: null,
|
|
166
|
+
os_user_digest: osUserDigest(), decided_at: new Date(t).toISOString() };
|
|
167
|
+
}
|
|
168
|
+
last = { rule, title, outcome: "unavailable" };
|
|
169
|
+
return null;
|
|
170
|
+
};
|
|
171
|
+
return { handler, note: () => last };
|
|
172
|
+
}
|
|
173
|
+
function osUserDigest() {
|
|
174
|
+
try {
|
|
175
|
+
return digestOf(userInfo().username);
|
|
176
|
+
}
|
|
177
|
+
catch {
|
|
178
|
+
return null;
|
|
179
|
+
}
|
|
180
|
+
}
|
|
181
|
+
/** The sentence added to a denial when the rule allows an override, so the person knows what to do. */
|
|
182
|
+
export function overrideHint(note) {
|
|
183
|
+
if (!note)
|
|
184
|
+
return null;
|
|
185
|
+
switch (note.outcome) {
|
|
186
|
+
case "unavailable": return `Your workspace lets you allow this once with a reason in the Scopebond window, but the Scopebond Agent is not running. Start it (scopebond-agent run) and try again.`;
|
|
187
|
+
case "limit": return `You have used today's overrides for "${note.title}". It blocks until tomorrow.`;
|
|
188
|
+
case "declined": return `The override was not given.`;
|
|
189
|
+
case "not_overridable": return null;
|
|
190
|
+
}
|
|
191
|
+
}
|
|
192
|
+
export const hasOverrideState = (dir) => existsSync(join(dir, OVERRIDE_STATE_FILE));
|
|
193
|
+
//# sourceMappingURL=override.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"override.js","sourceRoot":"","sources":["../src/override.ts"],"names":[],"mappings":"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","sourcesContent":["// Warn mode: a person at the computer may allow one action a workspace rule blocks, when the workspace set that rule to\n// \"Block, user may override\" and lets someone override. Only a channel the coding agent cannot answer counts:\n//\n// 1. The Scopebond Agent's own window. The hook asks the resident agent on its local channel and waits; the answer comes\n// from the window, never from whoever called the channel. The person gives a reason; its digest is signed into the receipt\n// and the agent sends the text to the workspace.\n// 2. Claude Code's own permission prompt, only when the workspace allows it, only in a permission mode where Claude Code\n// really asks the person (default, acceptEdits, plan), and only when the window is not available. It takes no reason and\n// does not prove who answered, so the receipt says \"offered\", not \"allowed\".\n//\n// Never overridable: a denial that a rule on Block, or Scopebond's own protection, would also make (checked by evaluating the\n// action against the same rules with every overridable rule recorded instead of blocked), and the kill switch (the gateway\n// never asks while it is on). Beyond the daily limit the rule blocks.\n\nimport { createHash } from \"node:crypto\";\nimport { existsSync, mkdirSync, readFileSync } from \"node:fs\";\nimport { join } from \"node:path\";\nimport { userInfo } from \"node:os\";\nimport { evaluate, type OverrideHandler, type OverrideRecord } from \"@scopebond/gateway\";\nimport { compileManaged, floorDocument, MANAGED_DOC_FILE, RULE_OF_CLAUSE, writeAtomic, type ManagedDocument, type ManagedRuleId } from \"./managed.js\";\nimport { defaultRules, loadRules } from \"./rules.js\";\n\nexport const OVERRIDE_STATE_FILE = \"overrides.json\";\nconst AGENT_FILE = \"agent.json\";\nconst TOKEN_HEADER = \"x-scopebond-agent-token\";\n/** Claude Code permission modes in which it shows the person a prompt for \"ask\". Any other or missing mode blocks. */\nexport const PROMPTING_MODES = new Set([\"default\", \"acceptEdits\", \"plan\"]);\nconst DAY_MS = 24 * 60 * 60 * 1000;\n\nconst RULE_TITLE: Record<ManagedRuleId, string> = {\n \"force-push-protected\": \"Force-push to a protected branch\",\n \"push-protected\": \"Push to a protected branch\",\n \"destructive-shell\": \"Destructive command\",\n \"secret-read\": \"Reading a secret file\",\n \"ci-config-write\": \"Changing CI configuration\",\n \"network-egress\": \"Request to a site not on the allowed list\",\n};\n\nexport const digestOf = (text: string): string => createHash(\"sha256\").update(text, \"utf8\").digest(\"hex\");\n\ninterface StateEntry { rule: string; action_key: string; action_id: string; at: number; reason_digest: string; reason_length: number }\ninterface OverrideState { entries: StateEntry[] }\n\nfunction readState(dir: string): OverrideState {\n try {\n const parsed = JSON.parse(readFileSync(join(dir, OVERRIDE_STATE_FILE), \"utf8\")) as OverrideState;\n return Array.isArray(parsed.entries) ? parsed : { entries: [] };\n } catch { return { entries: [] }; }\n}\nfunction writeState(dir: string, state: OverrideState, now: number): void {\n // Keep two days: enough for today's count and any time window.\n const kept = state.entries.filter((e) => now - e.at < 2 * DAY_MS).slice(-500);\n mkdirSync(dir, { recursive: true });\n writeAtomic(join(dir, OVERRIDE_STATE_FILE), `${JSON.stringify({ entries: kept })}\\n`);\n}\n\n/** One line a person can judge at a glance, from the minimized intent: never file contents or command output. */\nexport function actionSummary(intent: { action_type?: string; params?: Record<string, unknown> }): string {\n const p = intent.params ?? {};\n const pick = (k: string) => (typeof p[k] === \"string\" ? String(p[k]) : \"\");\n const text = intent.action_type === \"shell.exec\" ? pick(\"command\") || pick(\"program\")\n : intent.action_type === \"git.push\" ? `git push ${pick(\"remote\")} ${pick(\"ref\")}`.trim()\n : intent.action_type === \"net.fetch\" ? pick(\"url\") || pick(\"host\")\n : pick(\"path\") || intent.action_type || \"an action\";\n // Arguments the hook keeps only as a keyed digest stay out of the window.\n const shown = text.slice(0, 2000).replace(/ ?\\(hmac-sha256:[0-9a-f]{64}\\)/g, \"\").trim();\n return shown.length > 200 ? `${shown.slice(0, 197)}...` : shown;\n}\n\n/** The same action, whatever tool call it came from: its type and parameters without the per-call group id. */\nexport function actionKey(intent: { action_type?: string; params?: Record<string, unknown> }): string {\n const { action_group: _group, ...params } = intent.params ?? {};\n const sorted = (v: unknown): unknown => Array.isArray(v) ? v.map(sorted)\n : v && typeof v === \"object\" ? Object.fromEntries(Object.keys(v as object).sort().map((k) => [k, sorted((v as Record<string, unknown>)[k])])) : v;\n return digestOf(JSON.stringify({ action_type: intent.action_type ?? \"\", params: sorted(params) }));\n}\n\nexport interface AgentAnswer { decision: \"allow\" | \"deny\" | \"unavailable\"; reason?: string; os_user?: string }\n\n/** Ask the resident Scopebond Agent to show its window. Returns \"unavailable\" when no agent answers. */\nexport async function askAgent(home: string, request: Record<string, unknown>, timeoutMs: number, fetchImpl: typeof fetch = fetch): Promise<AgentAnswer> {\n let endpoint: { port?: unknown; token?: unknown };\n try { endpoint = JSON.parse(readFileSync(join(home, AGENT_FILE), \"utf8\")) as { port?: unknown; token?: unknown }; }\n catch { return { decision: \"unavailable\" }; }\n if (!Number.isInteger(endpoint.port) || typeof endpoint.token !== \"string\") return { decision: \"unavailable\" };\n try {\n const res = await fetchImpl(`http://127.0.0.1:${endpoint.port as number}/override`, {\n method: \"POST\", headers: { \"content-type\": \"application/json\", [TOKEN_HEADER]: endpoint.token }, body: JSON.stringify(request),\n signal: AbortSignal.timeout(timeoutMs),\n });\n if (!res.ok) return { decision: \"unavailable\" };\n const body = await res.json() as AgentAnswer;\n if (body.decision === \"allow\" && typeof body.reason === \"string\") return { decision: \"allow\", reason: body.reason, ...(typeof body.os_user === \"string\" ? { os_user: body.os_user } : {}) };\n return { decision: body.decision === \"deny\" ? \"deny\" : \"unavailable\" };\n } catch { return { decision: \"unavailable\" }; }\n}\n\nexport interface OverrideContext {\n /** The folder holding the policy and the workspace rules document. */\n dir: string;\n /** The Scopebond home, where the resident agent publishes its local channel. */\n home: string;\n agentKid: string;\n harness: \"claude\" | \"codex\" | \"cursor\";\n /** Claude Code's permission_mode from the hook payload, when it sent one. */\n permissionMode?: string | null;\n now?: () => number;\n /** How long to wait for the person; the agent's own hook timeout must be longer. */\n waitMs?: number;\n ask?: typeof askAgent;\n}\n\n/** What happened when the last override was considered, so a denial can say how an override would be possible. */\nexport interface OverrideNote { rule: ManagedRuleId; title: string; outcome: \"declined\" | \"limit\" | \"unavailable\" | \"not_overridable\" }\n\nexport function createOverrideHandler(ctx: OverrideContext): { handler: OverrideHandler; note(): OverrideNote | null } | null {\n let doc: ManagedDocument;\n try { doc = JSON.parse(readFileSync(join(ctx.dir, MANAGED_DOC_FILE), \"utf8\")) as ManagedDocument; } catch { return null; }\n if (!Object.values(doc.rules ?? {}).some((r) => r?.mode === \"override\")) return null;\n const now = ctx.now ?? Date.now;\n let last: OverrideNote | null = null;\n const handler: OverrideHandler = async ({ verdict, action_id, intent, intent_hash }) => {\n const key = actionKey(intent as never);\n const rule = RULE_OF_CLAUSE[verdict.clause_id ?? \"\"];\n const setting = rule ? doc.rules[rule] : undefined;\n if (!rule || setting?.mode !== \"override\" || !setting.override) return null;\n const terms = setting.override;\n const title = RULE_TITLE[rule];\n // Would the action still be denied with every overridable rule only recorded? Then a Block rule or Scopebond's own\n // protection denies it too, and no one may override that.\n const floor = compileManaged(loadRules(ctx.dir) ?? defaultRules(), floorDocument(doc), ctx.agentKid);\n const atFloor = evaluate(floor as never, [], { intent, intent_hash }, new Date(now()).toISOString(), { cooperative: true });\n if (!atFloor.allow) { last = { rule, title, outcome: \"not_overridable\" }; return null; }\n\n const t = now();\n const state = readState(ctx.dir);\n const dayStart = t - (t % DAY_MS);\n // Inside the time an earlier override allowed, the same action is allowed again without asking, and says so.\n if (terms.minutes > 0) {\n const earlier = [...state.entries].reverse().find((e) => e.rule === rule && e.action_key === key && t - e.at <= terms.minutes * 60_000);\n if (earlier) {\n return { version: 1, rule, method: \"agent_dialog\", state: \"allowed\", repeat_of: earlier.action_id, reason_digest: earlier.reason_digest,\n reason_length: earlier.reason_length, os_user_digest: osUserDigest(), decided_at: new Date(t).toISOString() };\n }\n }\n if (state.entries.filter((e) => e.rule === rule && e.at >= dayStart).length >= terms.daily_limit) { last = { rule, title, outcome: \"limit\" }; return null; }\n\n const answer = await (ctx.ask ?? askAgent)(ctx.home, {\n action_id, rule, title, summary: actionSummary(intent as never), reason_min: terms.reason_min,\n lasts: terms.minutes > 0 ? `the same action for ${terms.minutes} minutes` : \"this action only\", timeout_ms: ctx.waitMs ?? 45_000,\n }, (ctx.waitMs ?? 45_000) + 2_000);\n if (answer.decision === \"allow\") {\n const reason = (answer.reason ?? \"\").trim();\n if (reason.length < terms.reason_min || reason.length > 500) { last = { rule, title, outcome: \"declined\" }; return null; }\n const record: OverrideRecord = { version: 1, rule, method: \"agent_dialog\", state: \"allowed\", repeat_of: null, reason_digest: digestOf(reason),\n reason_length: reason.length, os_user_digest: answer.os_user ? digestOf(answer.os_user) : osUserDigest(), decided_at: new Date(t).toISOString() };\n state.entries.push({ rule, action_key: key, action_id, at: t, reason_digest: record.reason_digest!, reason_length: reason.length });\n try { writeState(ctx.dir, state, t); } catch { /* the receipt still records it; the count is checked again in the workspace */ }\n return record;\n }\n if (answer.decision === \"deny\") { last = { rule, title, outcome: \"declined\" }; return null; }\n // No window: Claude Code's own prompt, only where the workspace allows it and Claude Code really asks the person.\n if (terms.harness_prompt && ctx.harness === \"claude\" && PROMPTING_MODES.has(ctx.permissionMode ?? \"\")) {\n return { version: 1, rule, method: \"harness_prompt\", state: \"offered\", repeat_of: null, reason_digest: null, reason_length: null,\n os_user_digest: osUserDigest(), decided_at: new Date(t).toISOString() };\n }\n last = { rule, title, outcome: \"unavailable\" };\n return null;\n };\n return { handler, note: () => last };\n}\n\nfunction osUserDigest(): string | null {\n try { return digestOf(userInfo().username); } catch { return null; }\n}\n\n/** The sentence added to a denial when the rule allows an override, so the person knows what to do. */\nexport function overrideHint(note: OverrideNote | null): string | null {\n if (!note) return null;\n switch (note.outcome) {\n case \"unavailable\": return `Your workspace lets you allow this once with a reason in the Scopebond window, but the Scopebond Agent is not running. Start it (scopebond-agent run) and try again.`;\n case \"limit\": return `You have used today's overrides for \"${note.title}\". It blocks until tomorrow.`;\n case \"declined\": return `The override was not given.`;\n case \"not_overridable\": return null;\n }\n}\n\nexport const hasOverrideState = (dir: string): boolean => existsSync(join(dir, OVERRIDE_STATE_FILE));\n"]}
|
package/dist/runtime.d.ts
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
import { type CloudExporter } from "@scopebond/gateway";
|
|
2
2
|
import type { Mapped } from "./map.js";
|
|
3
3
|
import { type HookConnection } from "./cloud.js";
|
|
4
|
-
import { type DispatchDecision } from "@scopebond/gateway";
|
|
4
|
+
import { type DispatchDecision, type OverrideHandler } from "@scopebond/gateway";
|
|
5
5
|
export interface RuntimeConfig {
|
|
6
6
|
policyPath: string;
|
|
7
7
|
keyPath: string;
|
|
@@ -21,9 +21,16 @@ export interface RuntimeConfig {
|
|
|
21
21
|
fetch?: typeof fetch;
|
|
22
22
|
flushTimeoutMs?: number;
|
|
23
23
|
};
|
|
24
|
+
/** Warn mode: asked when policy denies an action, so a person may override a rule the workspace made overridable
|
|
25
|
+
* (see override.ts). `hint` explains, in a denial, how an override would have been possible. */
|
|
26
|
+
override?: (agentKid: string) => {
|
|
27
|
+
handler: OverrideHandler;
|
|
28
|
+
hint(): string | null;
|
|
29
|
+
} | null;
|
|
24
30
|
}
|
|
25
31
|
export interface Decision {
|
|
26
|
-
|
|
32
|
+
/** ask: a rule blocked it and the coding agent's own prompt asks the person (warn mode, Claude Code only). */
|
|
33
|
+
decision: "allow" | "deny" | "not_evaluated" | "ask";
|
|
27
34
|
reason: string;
|
|
28
35
|
/** The clause that decided a deny, when the verdict named one. */
|
|
29
36
|
clauseId?: string | null;
|
package/dist/runtime.d.ts.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"runtime.d.ts","sourceRoot":"","sources":["../src/runtime.ts"],"names":[],"mappings":"AAOA,OAAO,EAA6C,KAAK,aAAa,EAAE,MAAM,oBAAoB,CAAC;AAGnG,OAAO,KAAK,EAAE,MAAM,EAAE,MAAM,UAAU,CAAC;AACvC,OAAO,EAAgC,KAAK,cAAc,EAAE,MAAM,YAAY,CAAC;AAQ/E,OAAO,EAAoB,KAAK,gBAAgB,EAAsB,MAAM,oBAAoB,CAAC;
|
|
1
|
+
{"version":3,"file":"runtime.d.ts","sourceRoot":"","sources":["../src/runtime.ts"],"names":[],"mappings":"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"}
|
package/dist/runtime.js
CHANGED
|
@@ -182,6 +182,7 @@ export function createHookRuntime(config) {
|
|
|
182
182
|
let boundaryVerdict = null;
|
|
183
183
|
const gatewayGuard = { authorize: async () => boundaryVerdict ?? { allow: true, reason: "ok", consumed_approvals: [], budgets: [] } };
|
|
184
184
|
const gateway = createGateway({ policy, authentication: { keys }, attester, store, mode: "check_only", dispatchGuard: gatewayGuard });
|
|
185
|
+
const override = config.override?.(agent.kid) ?? null;
|
|
185
186
|
return {
|
|
186
187
|
gateway,
|
|
187
188
|
agentKid: agent.kid,
|
|
@@ -229,7 +230,11 @@ export function createHookRuntime(config) {
|
|
|
229
230
|
}
|
|
230
231
|
// Evaluated actions, and (in strict mode) unmapped tool.<name>/opaque commands,
|
|
231
232
|
// go through policy — a closed allowlist denies an unlisted action.
|
|
232
|
-
const result = await gateway.handleAction({ intent: signed.intent, authorization: signed.authorization });
|
|
233
|
+
const result = await gateway.handleAction({ intent: signed.intent, authorization: signed.authorization }, override ? { override: override.handler } : undefined);
|
|
234
|
+
const granted = result.receipt?.payload?.override;
|
|
235
|
+
if (result.allowed && granted?.state === "offered") {
|
|
236
|
+
return { decision: "ask", reason: "Scopebond: a workspace rule blocks this, and your workspace lets you allow it once. Allow only if you meant it; your answer is recorded.", receipt: result.receipt };
|
|
237
|
+
}
|
|
233
238
|
if (result.allowed)
|
|
234
239
|
return { decision: "allow", reason: result.reason, receipt: result.receipt };
|
|
235
240
|
// A deny is the one message the user and their agent actually read, so it is
|
|
@@ -246,7 +251,7 @@ export function createHookRuntime(config) {
|
|
|
246
251
|
// from `rules.json`, so telling someone to hand-edit it invites a change the
|
|
247
252
|
// next `rules` run would overwrite.
|
|
248
253
|
remedy: rulesRemedy,
|
|
249
|
-
}),
|
|
254
|
+
}) + (override?.hint() ? ` ${override.hint()}` : ""),
|
|
250
255
|
clauseId,
|
|
251
256
|
receipt: result.receipt,
|
|
252
257
|
};
|
|
@@ -266,6 +271,7 @@ export function createHookRuntime(config) {
|
|
|
266
271
|
const receipts = [];
|
|
267
272
|
const dispatched = [];
|
|
268
273
|
let allow = null;
|
|
274
|
+
let ask = null;
|
|
269
275
|
let notEvaluated = null;
|
|
270
276
|
for (const m of list) {
|
|
271
277
|
const d = await this.evaluateOne(m);
|
|
@@ -276,11 +282,14 @@ export function createHookRuntime(config) {
|
|
|
276
282
|
return { ...d, receipts, dispatched }; // any deny denies the call
|
|
277
283
|
if (d.decision === "allow" && !allow)
|
|
278
284
|
allow = d;
|
|
285
|
+
if (d.decision === "ask" && !ask)
|
|
286
|
+
ask = d;
|
|
279
287
|
if (d.decision === "not_evaluated" && !notEvaluated)
|
|
280
288
|
notEvaluated = d;
|
|
281
289
|
}
|
|
282
290
|
// No deny: allow if any command was evaluated-and-allowed, else not_evaluated.
|
|
283
|
-
|
|
291
|
+
// A command the person must be asked about makes the whole call an ask (warn mode); otherwise as before.
|
|
292
|
+
const chosen = ask ?? allow ?? notEvaluated;
|
|
284
293
|
if (boundary) {
|
|
285
294
|
// Immediately before permitted dispatch: approvals are consumed and the budget slot is reserved
|
|
286
295
|
// atomically, once for the whole parent action, or nothing is spent and the call is denied.
|
package/dist/runtime.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"runtime.js","sourceRoot":"","sources":["../src/runtime.ts"],"names":[],"mappings":"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","sourcesContent":["// The hook runtime: a check-only (M0) gateway built once from the enrolled\n// machine key, policy and a durable local receipt log. Each mapped action is\n// signed by the machine key, decided against policy and countersigned — locally,\n// with no HTTP server and no Cloud dependency in 0.1.\n\nimport { readFileSync, existsSync } from \"node:fs\";\nimport { dirname, join } from \"node:path\";\nimport { createGateway, StaticPrincipalKeyRegistry, type CloudExporter } from \"@scopebond/gateway\";\nimport { loadOrCreateAttester, openReceiptStore } from \"@scopebond/gateway/node\";\nimport { createSigner } from \"@scopebond/sdk\";\nimport type { Mapped } from \"./map.js\";\nimport { attachExporter, flushBounded, type HookConnection } from \"./cloud.js\";\nimport { explainDeny, type ExplainIntent } from \"./explain.js\";\nimport { RULES_FILE, loadRules } from \"./rules.js\";\nimport { applyRootScope } from \"./paths.js\";\nimport { withActionGroup, actionGroupId, ACTION_GROUP_PARAM } from \"./group.js\";\nimport { cliCommand } from \"./version.js\";\nimport { recordDeliveryAttempt } from \"./delivery-state.js\";\nimport { openDispatchGuard, DELEGATION_ENV } from \"@scopebond/gateway/node\";\nimport { dispatchIntentOf, type DispatchDecision, type DispatchGuard } from \"@scopebond/gateway\";\n\nexport interface RuntimeConfig {\n policyPath: string;\n keyPath: string;\n attesterPath: string;\n dbPath: string;\n /** Strict mode: an unmapped tool is policy-checked (and denied by a closed\n * allowlist) instead of observed. Fail-closed for tools with no taxonomy\n * mapping; default false (observe, matching the connector conformance vector). */\n strict?: boolean;\n /** The working directory the harness reported, used to resolve workspace roots when\n * the rule set lists `allowed_roots`. Defaults to the process directory. */\n cwd?: string;\n /** When connected to a Cloud workspace, receipts are auto-exported to the portal.\n * Export is best-effort and never changes the local decision. */\n cloud?: { connection: HookConnection; fetch?: typeof fetch; flushTimeoutMs?: number };\n}\n\nexport interface Decision {\n decision: \"allow\" | \"deny\" | \"not_evaluated\";\n reason: string;\n /** The clause that decided a deny, when the verdict named one. */\n clauseId?: string | null;\n /** The deciding receipt (the denied one, or the first allow). */\n receipt?: unknown;\n /** Every receipt produced — one per simple command in a decomposed shell call. */\n receipts?: unknown[];\n /** Each action as actually evaluated (after root scoping and grouping), with its\n * receipt: what the observation emitters bind a request digest to. */\n /** What the dispatch boundary spent or refused for this call, when one is configured. */\n dispatch?: DispatchDecision;\n dispatched?: Array<{ action: { action_type: string; params: Record<string, unknown> }; receipt?: unknown }>;\n}\n\n// The hook's own config and keys must be off-limits to the agent it governs:\n// otherwise the agent could rewrite its policy or read the signing keys and the\n// receipts stop meaning anything. These patterns are \"allow if the path does NOT\n// match a protected location\"; they run against the cwd-relative path the mapper\n// produces (and equally against an absolute one).\n//\n// Matching is case-insensitive — Windows and macOS open `.ENV` and `.env` as the\n// same file — by expanding each letter to a two-case class (policy patterns are\n// plain regular expressions with no flags). `ci()` is applied to literal text only.\nexport const ci = (s: string): string => s.replace(/[A-Za-z]/g, (c) => `[${c.toLowerCase()}${c.toUpperCase()}]`);\nexport const under = (dir: string): string => `(?!(?:.*/)?${ci(dir)}(?:/|$))`; // dir itself or anything inside it\nexport const named = (file: string): string => `(?!(?:.*/)?${ci(file)}$)`; // exactly this file name\nexport const dir = (d: string): string => `(?!(?:.*/)?${ci(d)}/?$)`; // the directory itself (a recursive read or copy)\n\n// The protected-write set, in two typed groups the catalog classifies separately:\n// guardrail/hook configuration (C02) and CI configuration (H03).\nconst GUARDRAIL_WRITE_PREV = [\n under(\"\\\\.scopebond\"), `(?!(?:.*/)?${ci(\"\\\\.claude/settings\")})`, under(\"\\\\.claude/hooks\"), under(\"\\\\.claude/agents\"),\n `(?!(?:.*/)?${ci(\"\\\\.cursor/hooks\")})`, named(\"\\\\.codex/hooks\\\\.json\"), named(\"\\\\.codex/config\\\\.toml\"), named(\"\\\\.mcp\\\\.json\"),\n under(\"\\\\.git/hooks\"), named(\"\\\\.git/config\"), under(\"\\\\.husky\"),\n];\nconst CI_WRITE_PREV = [\n under(\"\\\\.github/workflows\"), under(\"\\\\.github/actions\"), named(\"\\\\.gitlab-ci\\\\.yml\"), named(\"\\\\.gitlab-ci\\\\.yaml\"), under(\"\\\\.circleci\"),\n named(\"azure-pipelines\\\\.yml\"), named(\"Jenkinsfile\"),\n];\nconst GUARDRAIL_WRITE = [...GUARDRAIL_WRITE_PREV, named(\"\\\\.cursor/mcp\\\\.json\"), under(\"\\\\.githooks\")];\nconst CI_WRITE = [\n ...CI_WRITE_PREV,\n named(\"azure-pipelines\\\\.yaml\"), named(\"bitbucket-pipelines\\\\.yml\"), named(\"\\\\.travis\\\\.yml\"), named(\"\\\\.drone\\\\.yml\"),\n named(\"cloudbuild\\\\.yaml\"), named(\"cloudbuild\\\\.yml\"), under(\"\\\\.buildkite\"),\n];\n// What 0.8 compiled, kept so an installed policy that still carries it verbatim is\n// upgraded in memory (see LEGACY_STARTER_PATTERNS).\nconst PROTECTED_WRITE_PREV = \"^\" + [...GUARDRAIL_WRITE_PREV, ...CI_WRITE_PREV].join(\"\") + \".+\";\nconst PROTECTED_WRITE = \"^\" + [...GUARDRAIL_WRITE, ...CI_WRITE].join(\"\") + \".+\";\n/** The write protection that is always on, whoever manages the rules: the hook's own settings and those of the agents it\n * guards. A workspace can relax CI-configuration writes; it can never relax these. */\nexport const GUARDRAIL_WRITE_PATTERN = \"^\" + GUARDRAIL_WRITE.join(\"\") + \".+\";\n/** The always-on floor as bare lookaheads, for a pattern that must also refuse these paths (the hook's own folder included). */\nexport const GUARDRAIL_LOOKAHEADS = under(\"\\\\.scopebond\") + GUARDRAIL_WRITE.join(\"\");\n/** The read protection that is always on, whoever manages the rules: the hook's own folder, which holds this computer's\n * signing key and connection. A workspace can record other protected reads instead of blocking them; never this one. */\nexport const GUARDRAIL_READ_PATTERN = \"^\" + under(\"\\\\.scopebond\") + \".+\";\n\nconst PROTECTED_READ = \"^\" + [\n under(\"\\\\.scopebond\"),\n `(?!.*${ci(\"\\\\.(?:key|pem|p12|pfx|jks|keystore)\")}$)`,\n // .env, .env.* — except templates whose name ends in .example/.sample/.template/.dist\n `(?!(?:.*/)?${ci(\"\\\\.env\")}(?!(?:\\\\.[^/]*)?\\\\.(?:${ci(\"example\")}|${ci(\"sample\")}|${ci(\"template\")}|${ci(\"dist\")})$)(?:\\\\.[^/]*)?$)`,\n named(\"\\\\.envrc\"),\n // Credentials outside the workspace that an agent can reach by absolute or ~ path,\n // and their directories as a whole (`cp -r ~/.ssh`, `grep -r x ~/.aws`).\n `(?!(?:.*/)?${ci(\"\\\\.ssh\")}(?:$|/(?!.*${ci(\"\\\\.pub\")}$)(?!${ci(\"known_hosts\")}$)(?!${ci(\"config\")}$)))`,\n `(?!(?:.*/)?${ci(\"\\\\.aws\")}(?:$|/(?!${ci(\"config\")}$)))`,\n named(\"\\\\.npmrc\"), named(\"\\\\.pypirc\"), named(\"\\\\.netrc\"), named(\"_netrc\"), named(\"\\\\.git-credentials\"),\n dir(\"\\\\.kube\"), named(\"\\\\.kube/config\"), dir(\"\\\\.docker\"), named(\"\\\\.docker/config\\\\.json\"),\n under(\"\\\\.config/gcloud\"), under(\"\\\\.azure\"), under(\"\\\\.gnupg\"),\n dir(\"\\\\.config/gh\"), named(\"\\\\.config/gh/hosts\\\\.yml\"), named(\"\\\\.claude/\\\\.credentials\\\\.json\"),\n].join(\"\") + \".+\";\n\n// Destructive programs, POSIX and Windows. The mapper records the program as typed,\n// so the pattern accepts any case and an executable suffix (`RM.exe`, `Remove-Item`).\nexport const DESTRUCTIVE = [\n \"rm\", \"sudo\", \"doas\", \"shutdown\", \"reboot\", \"halt\", \"poweroff\", \"mkfs\", \"dd\", \"shred\", \"truncate\", \"unlink\", \"wipe\", \"srm\",\n \"del\", \"rd\", \"rmdir\", \"erase\", \"deltree\", \"format\", \"diskpart\", \"remove-item\", \"ri\", \"clear-content\", \"clc\", \"stop-computer\", \"restart-computer\",\n];\nconst SAFE_SHELL = `^(?!(?:${DESTRUCTIVE.map(ci).join(\"|\")})(?:${ci(\"\\\\.(?:exe|cmd|bat|com|ps1)\")})?$).+`;\n\n// A push destination the starter policy refuses: main, master, release/*, the\n// \"every branch\" flags (`--all`, `--mirror`, `--branches`) and a push whose\n// destination the mapper could not read (`--unknown`: a git alias, a configured push\n// refspec, `send-pack`), in any case. `--tags` alone pushes only tags and is allowed.\nconst SAFE_REF = `^(?!(?:${ci(\"main\")}|${ci(\"master\")})$)(?!${ci(\"release\")}/)(?!-(?!-${ci(\"tags\")}$)).+`;\n\n// Patterns written by earlier starter policies (0.3–0.5), upgraded in memory when a\n// policy on disk still carries them verbatim. An operator's own edits never match\n// these strings and are left untouched.\nconst LEGACY_STARTER_PATTERNS: Record<string, string> = {\n [PROTECTED_WRITE_PREV]: PROTECTED_WRITE,\n \"^(?!(?:main|master)$)(?!release/).+\": SAFE_REF,\n \"^(?!(?:rm|sudo|shutdown|reboot|mkfs|dd|del|rd|rmdir|erase|deltree|format|Remove-Item|ri)$).+\": SAFE_SHELL,\n \"^(?!(?:rm|sudo|shutdown|reboot|mkfs|dd)$).+\": SAFE_SHELL,\n \"^(?!(?:.*/)?\\\\.scopebond/)(?!(?:.*/)?\\\\.claude/settings)(?!(?:.*/)?\\\\.cursor/hooks)(?!(?:.*/)?\\\\.codex/(?:hooks\\\\.json|config\\\\.toml)$)(?!(?:.*/)?\\\\.git/hooks/)(?!(?:.*/)?\\\\.github/workflows/)(?!(?:.*/)?\\\\.github/actions/)(?!(?:.*/)?\\\\.gitlab-ci\\\\.yml$)(?!(?:.*/)?\\\\.circleci/)(?!(?:.*/)?azure-pipelines\\\\.yml$)(?!(?:.*/)?Jenkinsfile$).+\": PROTECTED_WRITE,\n \"^(?!(?:.*/)?\\\\.scopebond/)(?!(?:.*/)?\\\\.claude/settings)(?!(?:.*/)?\\\\.cursor/hooks)(?!(?:.*/)?\\\\.git/hooks/)(?!(?:.*/)?\\\\.github/workflows/)(?!(?:.*/)?\\\\.github/actions/)(?!(?:.*/)?\\\\.gitlab-ci\\\\.yml$)(?!(?:.*/)?\\\\.circleci/)(?!(?:.*/)?azure-pipelines\\\\.yml$)(?!(?:.*/)?Jenkinsfile$).+\": PROTECTED_WRITE,\n \"^(?!(?:.*/)?\\\\.scopebond/)(?!(?:.*/)?\\\\.claude/settings)(?!(?:.*/)?\\\\.cursor/hooks)(?!(?:.*/)?\\\\.git/hooks/).+\": PROTECTED_WRITE,\n \"^(?!(?:.*/)?\\\\.scopebond/)(?!.*\\\\.key$)(?!(?:.*/)?\\\\.env(?:\\\\.(?!example$|sample$|template$)[^/]*)?$).+\": PROTECTED_READ,\n \"^(?!(?:.*/)?\\\\.scopebond/)(?!.*\\\\.key$).+\": PROTECTED_READ,\n};\n\n/** Upgrade the starter-policy patterns an older hook wrote, in memory, so a user who\n * installed an earlier version gets the current protections without re-running init.\n * Only exact legacy strings are replaced; any other pattern is the operator's own. */\nexport function upgradeStarterPolicy<T>(policy: T): T {\n const p = policy as { clauses?: Array<{ param_bounds?: Record<string, { pattern?: string }> }> };\n for (const clause of p?.clauses ?? []) {\n for (const bound of Object.values(clause?.param_bounds ?? {})) {\n if (bound && typeof bound.pattern === \"string\" && LEGACY_STARTER_PATTERNS[bound.pattern]) bound.pattern = LEGACY_STARTER_PATTERNS[bound.pattern];\n }\n }\n return policy;\n}\n\n/** The default starter policy for a coding agent: protect release branches, deny\n * destructive programs, allow workspace file access except the hook's own config\n * and keys, and trust only the enrolled machine key. Every threshold is the\n * operator's to edit. */\nexport function starterPolicy(agentKid: string): Record<string, unknown> {\n return {\n vocabulary_version: \"1.0\", policy_id: \"coding-agent\", version: 1,\n clauses: [\n {\n id: \"protect-branches\", type: \"action_allowlist\", mode: \"enforce\", action_types: [\"git.push\"],\n param_bounds: { ref: { pattern: SAFE_REF } },\n description: \"Deny pushes to main, master and release/* (any case, any refspec spelling), pushes of every branch at once (--all, --mirror) and pushes whose destination cannot be read from the command (a git alias, a configured push refspec, send-pack). A tags-only push (--tags) is allowed.\",\n },\n {\n id: \"safe-shell\", type: \"action_allowlist\", mode: \"enforce\", action_types: [\"shell.exec\"],\n param_bounds: { program: { pattern: SAFE_SHELL } },\n description: \"Deny destructive programs — POSIX (rm, sudo, doas, shutdown, reboot, mkfs, dd, shred, truncate, unlink, wipe) and Windows/PowerShell (del, rd, rmdir, erase, deltree, format, diskpart, Remove-Item, Clear-Content) — in any case and with or without .exe. An empty program (a command that could not be parsed, or whose program is only known at run time: $VAR, $(…), eval of a variable) is denied. Argument-shaped deletion (find -delete, git clean) is not a program name and is not covered here.\",\n },\n {\n id: \"protect-write\", type: \"action_allowlist\", mode: \"enforce\", action_types: [\"file.write\"],\n param_bounds: { path: { pattern: PROTECTED_WRITE } },\n description: \"Allow workspace writes, but never to the hook's policy/keys, Claude Code settings, hooks and agents, Cursor or Codex hook settings, .mcp.json and .cursor/mcp.json, git hooks (.git/hooks, .githooks) and git config, Husky hooks, or CI config (.github/workflows, .github/actions, .gitlab-ci.yml/.yaml, .circleci, azure-pipelines, bitbucket-pipelines.yml, .travis.yml, .drone.yml, cloudbuild, .buildkite, Jenkinsfile). Case-insensitive.\",\n },\n {\n id: \"protect-read\", type: \"action_allowlist\", mode: \"enforce\", action_types: [\"file.read\"],\n param_bounds: { path: { pattern: PROTECTED_READ } },\n description: \"Allow workspace reads, but never signing keys and key containers (*.key, *.pem, *.p12, *.pfx, *.jks), environment secret files (.env, .env.*, .envrc — except names ending in .example/.sample/.template/.dist), SSH private keys and the .ssh directory, cloud/registry/git credentials (.aws except .aws/config, .npmrc, .pypirc, .netrc, .git-credentials, .kube/config, .docker/config.json, gcloud, Azure, GnuPG, the GitHub CLI's hosts.yml, Claude Code's .credentials.json) or the hook's own .scopebond directory. Case-insensitive.\",\n },\n {\n id: \"observe-net-mcp\", type: \"action_allowlist\", mode: \"monitor\",\n action_types: [\"net.fetch\", \"mcp.tool.call\"],\n description: \"Observe network fetches and MCP tool calls (recorded, not blocked) — add bounds to enforce.\",\n },\n { id: \"keys\", type: \"key_policy\", active_keys: [agentKid], description: \"Only the enrolled machine key may sign.\" },\n ],\n };\n}\n\n/** Build the runtime. Throws on any setup failure (unparseable policy, missing\n * key, unavailable store) — the CLI turns that into a fail-closed deny. */\nexport function createHookRuntime(config: RuntimeConfig) {\n const policy = upgradeStarterPolicy(JSON.parse(readFileSync(config.policyPath, \"utf8\")));\n const agent = createSigner({ privateKeyPem: readFileSync(config.keyPath, \"utf8\") });\n const keys = new StaticPrincipalKeyRegistry([\n { kid: agent.kid, publicKeyPem: agent.publicKeyPem, purposes: [\"agent\"], status: \"active\" },\n ]);\n const { attester } = loadOrCreateAttester({ file: config.attesterPath });\n // When the project has a readable rule set, a denial names the command that edits it\n // rather than the generated file.\n const rulesRemedy = existsSync(join(dirname(config.policyPath), RULES_FILE))\n ? `run \\`${cliCommand(\"rules\")}\\` to see the limits in plain terms, or edit ${join(dirname(config.policyPath), RULES_FILE)}`\n : undefined;\n const { store: baseStore } = openReceiptStore({ db: config.dbPath });\n // When connected, mirror every stored receipt to the hosted portal through a\n // durable outbox. The wrapped store's decision is unchanged; export is best-effort.\n let store = baseStore;\n let exporter: CloudExporter | undefined;\n let outbox: { close(): void } | undefined;\n if (config.cloud) {\n const attached = attachExporter(config.dbPath + \".cloud-outbox.db\", config.cloud.connection, baseStore, config.cloud.fetch);\n store = attached.store;\n exporter = attached.exporter;\n outbox = attached.outbox;\n }\n const scopeRoots = loadRules(dirname(config.policyPath))?.allowed_roots ?? [];\n // The dispatch boundary is checked once per tool call, after every intent has been allowed, so the\n // gateway's own per-intent hook only reports what the runtime already decided for this call.\n const boundary = openDispatchGuard(dirname(config.policyPath));\n let boundaryVerdict: DispatchDecision | null = null;\n const gatewayGuard: DispatchGuard = { authorize: async () => boundaryVerdict ?? { allow: true, reason: \"ok\", consumed_approvals: [], budgets: [] } };\n const gateway = createGateway({ policy, authentication: { keys }, attester, store, mode: \"check_only\", dispatchGuard: gatewayGuard });\n\n return {\n gateway,\n agentKid: agent.kid,\n exporter,\n /** Deliver queued receipts to Cloud with a bounded timeout, then it is safe to\n * exit. Undelivered receipts persist in the durable outbox for the next run. */\n async flush(): Promise<void> {\n if (!exporter) return;\n const before = exporter.status().lastSuccessAt;\n await flushBounded(exporter, config.cloud?.flushTimeoutMs);\n // The process exits after this call; keep what the attempt saw for `status` and `doctor`.\n try { recordDeliveryAttempt(dirname(config.dbPath), exporter.status(), Date.now(), before); } catch { /* diagnostic only */ }\n },\n /** Release the SQLite handles. The hook is a per-tool-call process, and a writer that\n * exits without closing leaves its write-ahead log on disk for the next process to\n * extend — measured at ~11 KiB of WAL per receipt against ~1.7 KiB when closed, so a\n * busy session was writing tens of megabytes of pure overhead into the user's project.\n * Always safe to call, and every exit path should. */\n close(): void {\n try { baseStore.close?.(); } catch { /* the decision is already recorded */ }\n try { outbox?.close(); } catch { /* best effort */ }\n try { boundary?.close(); } catch { /* best effort */ }\n },\n /** Decide one mapped action, recording a receipt either way. */\n async evaluateOne(mapped: Mapped): Promise<Decision> {\n const signed = agent.sign(mapped.intent);\n if (!mapped.evaluated && !config.strict) {\n // Unknown tool or unparseable command, non-strict: observe without\n // evaluating — grants nothing (D30).\n const { receipt } = await gateway.observeAction({ intent: signed.intent, authorization: signed.authorization });\n return { decision: \"not_evaluated\", reason: `no policy applies to ${mapped.intent.action_type}`, receipt };\n }\n // Evaluated actions, and (in strict mode) unmapped tool.<name>/opaque commands,\n // go through policy — a closed allowlist denies an unlisted action.\n const result = await gateway.handleAction({ intent: signed.intent, authorization: signed.authorization });\n if (result.allowed) return { decision: \"allow\", reason: result.reason, receipt: result.receipt };\n // A deny is the one message the user and their agent actually read, so it is\n // composed from the deciding clause's own words rather than the engine's\n // internal reason (\"param ref fails pattern\").\n const clauseId = result.verdict?.clause_id ?? null;\n return {\n decision: \"deny\",\n reason: explainDeny({\n policy, clauseId, detail: result.reason,\n intent: signed.intent as ExplainIntent, policyPath: config.policyPath,\n postHoc: mapped.postHoc,\n // Point at the editable surface when there is one. `policy.json` is compiled\n // from `rules.json`, so telling someone to hand-edit it invites a change the\n // next `rules` run would overwrite.\n remedy: rulesRemedy,\n }),\n clauseId,\n receipt: result.receipt,\n };\n },\n /** Decide a whole tool call. A shell call decomposes into several simple\n * commands; every one is recorded, and a single deny denies the call. */\n async evaluate(mapped: Mapped | Mapped[], options: { groupKey?: string } = {}): Promise<Decision> {\n const raw = Array.isArray(mapped) ? mapped : [mapped];\n if (raw.length === 0) return { decision: \"not_evaluated\", reason: \"no action\", receipts: [] };\n // Every intent of one tool call carries one parent group id, so the receipts of a\n // decomposed call (a command, its file reads and writes, a rename destination)\n // link to each other and count once as an action. It is part of the signed\n // intent, so it is authenticated by the same signature as the rest.\n const scoped = scopeRoots.length ? applyRootScope(raw, { cwd: config.cwd ?? process.cwd(), roots: scopeRoots }) : raw;\n const list = withActionGroup(scoped, actionGroupId(options.groupKey));\n const receipts: unknown[] = [];\n const dispatched: NonNullable<Decision[\"dispatched\"]> = [];\n let allow: Decision | null = null;\n let notEvaluated: Decision | null = null;\n for (const m of list) {\n const d = await this.evaluateOne(m);\n if (d.receipt !== undefined) receipts.push(d.receipt);\n dispatched.push({ action: { action_type: m.intent.action_type, params: m.intent.params as Record<string, unknown> }, ...(d.receipt !== undefined ? { receipt: d.receipt } : {}) });\n if (d.decision === \"deny\") return { ...d, receipts, dispatched }; // any deny denies the call\n if (d.decision === \"allow\" && !allow) allow = d;\n if (d.decision === \"not_evaluated\" && !notEvaluated) notEvaluated = d;\n }\n // No deny: allow if any command was evaluated-and-allowed, else not_evaluated.\n const chosen = allow ?? notEvaluated!;\n if (boundary) {\n // Immediately before permitted dispatch: approvals are consumed and the budget slot is reserved\n // atomically, once for the whole parent action, or nothing is spent and the call is denied.\n const group = String(list[0]!.intent.params[ACTION_GROUP_PARAM]);\n const delegation = process.env[DELEGATION_ENV] ?? \"\";\n const verdict = await boundary.authorize({\n actor: agent.kid, action_group: group, policy_digest: gateway.policyHash,\n intents: list.map((m) => dispatchIntentOf(m.intent as never)),\n ...(delegation !== \"\" ? { delegation_id: delegation } : {}),\n });\n if (!verdict.allow) {\n // Record the refusal as its own denied receipt, then deny the call.\n boundaryVerdict = verdict;\n try {\n const last = list[list.length - 1]!;\n const signed = agent.sign(last.intent);\n const result = await gateway.handleAction({ intent: signed.intent, authorization: signed.authorization });\n if (result.receipt !== undefined) receipts.push(result.receipt);\n } finally { boundaryVerdict = null; }\n return { decision: \"deny\", reason: `Scopebond blocked this before it ran: ${verdict.reason}${verdict.detail ? ` (${verdict.detail})` : \"\"}`, receipts, dispatched, dispatch: verdict };\n }\n return { ...chosen, receipts, dispatched, dispatch: verdict };\n }\n return { ...chosen, receipts, dispatched };\n },\n };\n}\n"]}
|
|
1
|
+
{"version":3,"file":"runtime.js","sourceRoot":"","sources":["../src/runtime.ts"],"names":[],"mappings":"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","sourcesContent":["// The hook runtime: a check-only (M0) gateway built once from the enrolled\n// machine key, policy and a durable local receipt log. Each mapped action is\n// signed by the machine key, decided against policy and countersigned — locally,\n// with no HTTP server and no Cloud dependency in 0.1.\n\nimport { readFileSync, existsSync } from \"node:fs\";\nimport { dirname, join } from \"node:path\";\nimport { createGateway, StaticPrincipalKeyRegistry, type CloudExporter } from \"@scopebond/gateway\";\nimport { loadOrCreateAttester, openReceiptStore } from \"@scopebond/gateway/node\";\nimport { createSigner } from \"@scopebond/sdk\";\nimport type { Mapped } from \"./map.js\";\nimport { attachExporter, flushBounded, type HookConnection } from \"./cloud.js\";\nimport { explainDeny, type ExplainIntent } from \"./explain.js\";\nimport { RULES_FILE, loadRules } from \"./rules.js\";\nimport { applyRootScope } from \"./paths.js\";\nimport { withActionGroup, actionGroupId, ACTION_GROUP_PARAM } from \"./group.js\";\nimport { cliCommand } from \"./version.js\";\nimport { recordDeliveryAttempt } from \"./delivery-state.js\";\nimport { openDispatchGuard, DELEGATION_ENV } from \"@scopebond/gateway/node\";\nimport { dispatchIntentOf, type DispatchDecision, type DispatchGuard, type OverrideHandler } from \"@scopebond/gateway\";\n\nexport interface RuntimeConfig {\n policyPath: string;\n keyPath: string;\n attesterPath: string;\n dbPath: string;\n /** Strict mode: an unmapped tool is policy-checked (and denied by a closed\n * allowlist) instead of observed. Fail-closed for tools with no taxonomy\n * mapping; default false (observe, matching the connector conformance vector). */\n strict?: boolean;\n /** The working directory the harness reported, used to resolve workspace roots when\n * the rule set lists `allowed_roots`. Defaults to the process directory. */\n cwd?: string;\n /** When connected to a Cloud workspace, receipts are auto-exported to the portal.\n * Export is best-effort and never changes the local decision. */\n cloud?: { connection: HookConnection; fetch?: typeof fetch; flushTimeoutMs?: number };\n /** Warn mode: asked when policy denies an action, so a person may override a rule the workspace made overridable\n * (see override.ts). `hint` explains, in a denial, how an override would have been possible. */\n override?: (agentKid: string) => { handler: OverrideHandler; hint(): string | null } | null;\n}\n\nexport interface Decision {\n /** ask: a rule blocked it and the coding agent's own prompt asks the person (warn mode, Claude Code only). */\n decision: \"allow\" | \"deny\" | \"not_evaluated\" | \"ask\";\n reason: string;\n /** The clause that decided a deny, when the verdict named one. */\n clauseId?: string | null;\n /** The deciding receipt (the denied one, or the first allow). */\n receipt?: unknown;\n /** Every receipt produced — one per simple command in a decomposed shell call. */\n receipts?: unknown[];\n /** Each action as actually evaluated (after root scoping and grouping), with its\n * receipt: what the observation emitters bind a request digest to. */\n /** What the dispatch boundary spent or refused for this call, when one is configured. */\n dispatch?: DispatchDecision;\n dispatched?: Array<{ action: { action_type: string; params: Record<string, unknown> }; receipt?: unknown }>;\n}\n\n// The hook's own config and keys must be off-limits to the agent it governs:\n// otherwise the agent could rewrite its policy or read the signing keys and the\n// receipts stop meaning anything. These patterns are \"allow if the path does NOT\n// match a protected location\"; they run against the cwd-relative path the mapper\n// produces (and equally against an absolute one).\n//\n// Matching is case-insensitive — Windows and macOS open `.ENV` and `.env` as the\n// same file — by expanding each letter to a two-case class (policy patterns are\n// plain regular expressions with no flags). `ci()` is applied to literal text only.\nexport const ci = (s: string): string => s.replace(/[A-Za-z]/g, (c) => `[${c.toLowerCase()}${c.toUpperCase()}]`);\nexport const under = (dir: string): string => `(?!(?:.*/)?${ci(dir)}(?:/|$))`; // dir itself or anything inside it\nexport const named = (file: string): string => `(?!(?:.*/)?${ci(file)}$)`; // exactly this file name\nexport const dir = (d: string): string => `(?!(?:.*/)?${ci(d)}/?$)`; // the directory itself (a recursive read or copy)\n\n// The protected-write set, in two typed groups the catalog classifies separately:\n// guardrail/hook configuration (C02) and CI configuration (H03).\nconst GUARDRAIL_WRITE_PREV = [\n under(\"\\\\.scopebond\"), `(?!(?:.*/)?${ci(\"\\\\.claude/settings\")})`, under(\"\\\\.claude/hooks\"), under(\"\\\\.claude/agents\"),\n `(?!(?:.*/)?${ci(\"\\\\.cursor/hooks\")})`, named(\"\\\\.codex/hooks\\\\.json\"), named(\"\\\\.codex/config\\\\.toml\"), named(\"\\\\.mcp\\\\.json\"),\n under(\"\\\\.git/hooks\"), named(\"\\\\.git/config\"), under(\"\\\\.husky\"),\n];\nconst CI_WRITE_PREV = [\n under(\"\\\\.github/workflows\"), under(\"\\\\.github/actions\"), named(\"\\\\.gitlab-ci\\\\.yml\"), named(\"\\\\.gitlab-ci\\\\.yaml\"), under(\"\\\\.circleci\"),\n named(\"azure-pipelines\\\\.yml\"), named(\"Jenkinsfile\"),\n];\nconst GUARDRAIL_WRITE = [...GUARDRAIL_WRITE_PREV, named(\"\\\\.cursor/mcp\\\\.json\"), under(\"\\\\.githooks\")];\nconst CI_WRITE = [\n ...CI_WRITE_PREV,\n named(\"azure-pipelines\\\\.yaml\"), named(\"bitbucket-pipelines\\\\.yml\"), named(\"\\\\.travis\\\\.yml\"), named(\"\\\\.drone\\\\.yml\"),\n named(\"cloudbuild\\\\.yaml\"), named(\"cloudbuild\\\\.yml\"), under(\"\\\\.buildkite\"),\n];\n// What 0.8 compiled, kept so an installed policy that still carries it verbatim is\n// upgraded in memory (see LEGACY_STARTER_PATTERNS).\nconst PROTECTED_WRITE_PREV = \"^\" + [...GUARDRAIL_WRITE_PREV, ...CI_WRITE_PREV].join(\"\") + \".+\";\nconst PROTECTED_WRITE = \"^\" + [...GUARDRAIL_WRITE, ...CI_WRITE].join(\"\") + \".+\";\n/** The write protection that is always on, whoever manages the rules: the hook's own settings and those of the agents it\n * guards. A workspace can relax CI-configuration writes; it can never relax these. */\nexport const GUARDRAIL_WRITE_PATTERN = \"^\" + GUARDRAIL_WRITE.join(\"\") + \".+\";\n/** The always-on floor as bare lookaheads, for a pattern that must also refuse these paths (the hook's own folder included). */\nexport const GUARDRAIL_LOOKAHEADS = under(\"\\\\.scopebond\") + GUARDRAIL_WRITE.join(\"\");\n/** The read protection that is always on, whoever manages the rules: the hook's own folder, which holds this computer's\n * signing key and connection. A workspace can record other protected reads instead of blocking them; never this one. */\nexport const GUARDRAIL_READ_PATTERN = \"^\" + under(\"\\\\.scopebond\") + \".+\";\n\nconst PROTECTED_READ = \"^\" + [\n under(\"\\\\.scopebond\"),\n `(?!.*${ci(\"\\\\.(?:key|pem|p12|pfx|jks|keystore)\")}$)`,\n // .env, .env.* — except templates whose name ends in .example/.sample/.template/.dist\n `(?!(?:.*/)?${ci(\"\\\\.env\")}(?!(?:\\\\.[^/]*)?\\\\.(?:${ci(\"example\")}|${ci(\"sample\")}|${ci(\"template\")}|${ci(\"dist\")})$)(?:\\\\.[^/]*)?$)`,\n named(\"\\\\.envrc\"),\n // Credentials outside the workspace that an agent can reach by absolute or ~ path,\n // and their directories as a whole (`cp -r ~/.ssh`, `grep -r x ~/.aws`).\n `(?!(?:.*/)?${ci(\"\\\\.ssh\")}(?:$|/(?!.*${ci(\"\\\\.pub\")}$)(?!${ci(\"known_hosts\")}$)(?!${ci(\"config\")}$)))`,\n `(?!(?:.*/)?${ci(\"\\\\.aws\")}(?:$|/(?!${ci(\"config\")}$)))`,\n named(\"\\\\.npmrc\"), named(\"\\\\.pypirc\"), named(\"\\\\.netrc\"), named(\"_netrc\"), named(\"\\\\.git-credentials\"),\n dir(\"\\\\.kube\"), named(\"\\\\.kube/config\"), dir(\"\\\\.docker\"), named(\"\\\\.docker/config\\\\.json\"),\n under(\"\\\\.config/gcloud\"), under(\"\\\\.azure\"), under(\"\\\\.gnupg\"),\n dir(\"\\\\.config/gh\"), named(\"\\\\.config/gh/hosts\\\\.yml\"), named(\"\\\\.claude/\\\\.credentials\\\\.json\"),\n].join(\"\") + \".+\";\n\n// Destructive programs, POSIX and Windows. The mapper records the program as typed,\n// so the pattern accepts any case and an executable suffix (`RM.exe`, `Remove-Item`).\nexport const DESTRUCTIVE = [\n \"rm\", \"sudo\", \"doas\", \"shutdown\", \"reboot\", \"halt\", \"poweroff\", \"mkfs\", \"dd\", \"shred\", \"truncate\", \"unlink\", \"wipe\", \"srm\",\n \"del\", \"rd\", \"rmdir\", \"erase\", \"deltree\", \"format\", \"diskpart\", \"remove-item\", \"ri\", \"clear-content\", \"clc\", \"stop-computer\", \"restart-computer\",\n];\nconst SAFE_SHELL = `^(?!(?:${DESTRUCTIVE.map(ci).join(\"|\")})(?:${ci(\"\\\\.(?:exe|cmd|bat|com|ps1)\")})?$).+`;\n\n// A push destination the starter policy refuses: main, master, release/*, the\n// \"every branch\" flags (`--all`, `--mirror`, `--branches`) and a push whose\n// destination the mapper could not read (`--unknown`: a git alias, a configured push\n// refspec, `send-pack`), in any case. `--tags` alone pushes only tags and is allowed.\nconst SAFE_REF = `^(?!(?:${ci(\"main\")}|${ci(\"master\")})$)(?!${ci(\"release\")}/)(?!-(?!-${ci(\"tags\")}$)).+`;\n\n// Patterns written by earlier starter policies (0.3–0.5), upgraded in memory when a\n// policy on disk still carries them verbatim. An operator's own edits never match\n// these strings and are left untouched.\nconst LEGACY_STARTER_PATTERNS: Record<string, string> = {\n [PROTECTED_WRITE_PREV]: PROTECTED_WRITE,\n \"^(?!(?:main|master)$)(?!release/).+\": SAFE_REF,\n \"^(?!(?:rm|sudo|shutdown|reboot|mkfs|dd|del|rd|rmdir|erase|deltree|format|Remove-Item|ri)$).+\": SAFE_SHELL,\n \"^(?!(?:rm|sudo|shutdown|reboot|mkfs|dd)$).+\": SAFE_SHELL,\n \"^(?!(?:.*/)?\\\\.scopebond/)(?!(?:.*/)?\\\\.claude/settings)(?!(?:.*/)?\\\\.cursor/hooks)(?!(?:.*/)?\\\\.codex/(?:hooks\\\\.json|config\\\\.toml)$)(?!(?:.*/)?\\\\.git/hooks/)(?!(?:.*/)?\\\\.github/workflows/)(?!(?:.*/)?\\\\.github/actions/)(?!(?:.*/)?\\\\.gitlab-ci\\\\.yml$)(?!(?:.*/)?\\\\.circleci/)(?!(?:.*/)?azure-pipelines\\\\.yml$)(?!(?:.*/)?Jenkinsfile$).+\": PROTECTED_WRITE,\n \"^(?!(?:.*/)?\\\\.scopebond/)(?!(?:.*/)?\\\\.claude/settings)(?!(?:.*/)?\\\\.cursor/hooks)(?!(?:.*/)?\\\\.git/hooks/)(?!(?:.*/)?\\\\.github/workflows/)(?!(?:.*/)?\\\\.github/actions/)(?!(?:.*/)?\\\\.gitlab-ci\\\\.yml$)(?!(?:.*/)?\\\\.circleci/)(?!(?:.*/)?azure-pipelines\\\\.yml$)(?!(?:.*/)?Jenkinsfile$).+\": PROTECTED_WRITE,\n \"^(?!(?:.*/)?\\\\.scopebond/)(?!(?:.*/)?\\\\.claude/settings)(?!(?:.*/)?\\\\.cursor/hooks)(?!(?:.*/)?\\\\.git/hooks/).+\": PROTECTED_WRITE,\n \"^(?!(?:.*/)?\\\\.scopebond/)(?!.*\\\\.key$)(?!(?:.*/)?\\\\.env(?:\\\\.(?!example$|sample$|template$)[^/]*)?$).+\": PROTECTED_READ,\n \"^(?!(?:.*/)?\\\\.scopebond/)(?!.*\\\\.key$).+\": PROTECTED_READ,\n};\n\n/** Upgrade the starter-policy patterns an older hook wrote, in memory, so a user who\n * installed an earlier version gets the current protections without re-running init.\n * Only exact legacy strings are replaced; any other pattern is the operator's own. */\nexport function upgradeStarterPolicy<T>(policy: T): T {\n const p = policy as { clauses?: Array<{ param_bounds?: Record<string, { pattern?: string }> }> };\n for (const clause of p?.clauses ?? []) {\n for (const bound of Object.values(clause?.param_bounds ?? {})) {\n if (bound && typeof bound.pattern === \"string\" && LEGACY_STARTER_PATTERNS[bound.pattern]) bound.pattern = LEGACY_STARTER_PATTERNS[bound.pattern];\n }\n }\n return policy;\n}\n\n/** The default starter policy for a coding agent: protect release branches, deny\n * destructive programs, allow workspace file access except the hook's own config\n * and keys, and trust only the enrolled machine key. Every threshold is the\n * operator's to edit. */\nexport function starterPolicy(agentKid: string): Record<string, unknown> {\n return {\n vocabulary_version: \"1.0\", policy_id: \"coding-agent\", version: 1,\n clauses: [\n {\n id: \"protect-branches\", type: \"action_allowlist\", mode: \"enforce\", action_types: [\"git.push\"],\n param_bounds: { ref: { pattern: SAFE_REF } },\n description: \"Deny pushes to main, master and release/* (any case, any refspec spelling), pushes of every branch at once (--all, --mirror) and pushes whose destination cannot be read from the command (a git alias, a configured push refspec, send-pack). A tags-only push (--tags) is allowed.\",\n },\n {\n id: \"safe-shell\", type: \"action_allowlist\", mode: \"enforce\", action_types: [\"shell.exec\"],\n param_bounds: { program: { pattern: SAFE_SHELL } },\n description: \"Deny destructive programs — POSIX (rm, sudo, doas, shutdown, reboot, mkfs, dd, shred, truncate, unlink, wipe) and Windows/PowerShell (del, rd, rmdir, erase, deltree, format, diskpart, Remove-Item, Clear-Content) — in any case and with or without .exe. An empty program (a command that could not be parsed, or whose program is only known at run time: $VAR, $(…), eval of a variable) is denied. Argument-shaped deletion (find -delete, git clean) is not a program name and is not covered here.\",\n },\n {\n id: \"protect-write\", type: \"action_allowlist\", mode: \"enforce\", action_types: [\"file.write\"],\n param_bounds: { path: { pattern: PROTECTED_WRITE } },\n description: \"Allow workspace writes, but never to the hook's policy/keys, Claude Code settings, hooks and agents, Cursor or Codex hook settings, .mcp.json and .cursor/mcp.json, git hooks (.git/hooks, .githooks) and git config, Husky hooks, or CI config (.github/workflows, .github/actions, .gitlab-ci.yml/.yaml, .circleci, azure-pipelines, bitbucket-pipelines.yml, .travis.yml, .drone.yml, cloudbuild, .buildkite, Jenkinsfile). Case-insensitive.\",\n },\n {\n id: \"protect-read\", type: \"action_allowlist\", mode: \"enforce\", action_types: [\"file.read\"],\n param_bounds: { path: { pattern: PROTECTED_READ } },\n description: \"Allow workspace reads, but never signing keys and key containers (*.key, *.pem, *.p12, *.pfx, *.jks), environment secret files (.env, .env.*, .envrc — except names ending in .example/.sample/.template/.dist), SSH private keys and the .ssh directory, cloud/registry/git credentials (.aws except .aws/config, .npmrc, .pypirc, .netrc, .git-credentials, .kube/config, .docker/config.json, gcloud, Azure, GnuPG, the GitHub CLI's hosts.yml, Claude Code's .credentials.json) or the hook's own .scopebond directory. Case-insensitive.\",\n },\n {\n id: \"observe-net-mcp\", type: \"action_allowlist\", mode: \"monitor\",\n action_types: [\"net.fetch\", \"mcp.tool.call\"],\n description: \"Observe network fetches and MCP tool calls (recorded, not blocked) — add bounds to enforce.\",\n },\n { id: \"keys\", type: \"key_policy\", active_keys: [agentKid], description: \"Only the enrolled machine key may sign.\" },\n ],\n };\n}\n\n/** Build the runtime. Throws on any setup failure (unparseable policy, missing\n * key, unavailable store) — the CLI turns that into a fail-closed deny. */\nexport function createHookRuntime(config: RuntimeConfig) {\n const policy = upgradeStarterPolicy(JSON.parse(readFileSync(config.policyPath, \"utf8\")));\n const agent = createSigner({ privateKeyPem: readFileSync(config.keyPath, \"utf8\") });\n const keys = new StaticPrincipalKeyRegistry([\n { kid: agent.kid, publicKeyPem: agent.publicKeyPem, purposes: [\"agent\"], status: \"active\" },\n ]);\n const { attester } = loadOrCreateAttester({ file: config.attesterPath });\n // When the project has a readable rule set, a denial names the command that edits it\n // rather than the generated file.\n const rulesRemedy = existsSync(join(dirname(config.policyPath), RULES_FILE))\n ? `run \\`${cliCommand(\"rules\")}\\` to see the limits in plain terms, or edit ${join(dirname(config.policyPath), RULES_FILE)}`\n : undefined;\n const { store: baseStore } = openReceiptStore({ db: config.dbPath });\n // When connected, mirror every stored receipt to the hosted portal through a\n // durable outbox. The wrapped store's decision is unchanged; export is best-effort.\n let store = baseStore;\n let exporter: CloudExporter | undefined;\n let outbox: { close(): void } | undefined;\n if (config.cloud) {\n const attached = attachExporter(config.dbPath + \".cloud-outbox.db\", config.cloud.connection, baseStore, config.cloud.fetch);\n store = attached.store;\n exporter = attached.exporter;\n outbox = attached.outbox;\n }\n const scopeRoots = loadRules(dirname(config.policyPath))?.allowed_roots ?? [];\n // The dispatch boundary is checked once per tool call, after every intent has been allowed, so the\n // gateway's own per-intent hook only reports what the runtime already decided for this call.\n const boundary = openDispatchGuard(dirname(config.policyPath));\n let boundaryVerdict: DispatchDecision | null = null;\n const gatewayGuard: DispatchGuard = { authorize: async () => boundaryVerdict ?? { allow: true, reason: \"ok\", consumed_approvals: [], budgets: [] } };\n const gateway = createGateway({ policy, authentication: { keys }, attester, store, mode: \"check_only\", dispatchGuard: gatewayGuard });\n const override = config.override?.(agent.kid) ?? null;\n\n return {\n gateway,\n agentKid: agent.kid,\n exporter,\n /** Deliver queued receipts to Cloud with a bounded timeout, then it is safe to\n * exit. Undelivered receipts persist in the durable outbox for the next run. */\n async flush(): Promise<void> {\n if (!exporter) return;\n const before = exporter.status().lastSuccessAt;\n await flushBounded(exporter, config.cloud?.flushTimeoutMs);\n // The process exits after this call; keep what the attempt saw for `status` and `doctor`.\n try { recordDeliveryAttempt(dirname(config.dbPath), exporter.status(), Date.now(), before); } catch { /* diagnostic only */ }\n },\n /** Release the SQLite handles. The hook is a per-tool-call process, and a writer that\n * exits without closing leaves its write-ahead log on disk for the next process to\n * extend — measured at ~11 KiB of WAL per receipt against ~1.7 KiB when closed, so a\n * busy session was writing tens of megabytes of pure overhead into the user's project.\n * Always safe to call, and every exit path should. */\n close(): void {\n try { baseStore.close?.(); } catch { /* the decision is already recorded */ }\n try { outbox?.close(); } catch { /* best effort */ }\n try { boundary?.close(); } catch { /* best effort */ }\n },\n /** Decide one mapped action, recording a receipt either way. */\n async evaluateOne(mapped: Mapped): Promise<Decision> {\n const signed = agent.sign(mapped.intent);\n if (!mapped.evaluated && !config.strict) {\n // Unknown tool or unparseable command, non-strict: observe without\n // evaluating — grants nothing (D30).\n const { receipt } = await gateway.observeAction({ intent: signed.intent, authorization: signed.authorization });\n return { decision: \"not_evaluated\", reason: `no policy applies to ${mapped.intent.action_type}`, receipt };\n }\n // Evaluated actions, and (in strict mode) unmapped tool.<name>/opaque commands,\n // go through policy — a closed allowlist denies an unlisted action.\n const result = await gateway.handleAction({ intent: signed.intent, authorization: signed.authorization }, override ? { override: override.handler } : undefined);\n const granted = (result.receipt as { payload?: { override?: { state?: string } } } | undefined)?.payload?.override;\n if (result.allowed && granted?.state === \"offered\") {\n return { decision: \"ask\", reason: \"Scopebond: a workspace rule blocks this, and your workspace lets you allow it once. Allow only if you meant it; your answer is recorded.\", receipt: result.receipt };\n }\n if (result.allowed) return { decision: \"allow\", reason: result.reason, receipt: result.receipt };\n // A deny is the one message the user and their agent actually read, so it is\n // composed from the deciding clause's own words rather than the engine's\n // internal reason (\"param ref fails pattern\").\n const clauseId = result.verdict?.clause_id ?? null;\n return {\n decision: \"deny\",\n reason: explainDeny({\n policy, clauseId, detail: result.reason,\n intent: signed.intent as ExplainIntent, policyPath: config.policyPath,\n postHoc: mapped.postHoc,\n // Point at the editable surface when there is one. `policy.json` is compiled\n // from `rules.json`, so telling someone to hand-edit it invites a change the\n // next `rules` run would overwrite.\n remedy: rulesRemedy,\n }) + (override?.hint() ? ` ${override.hint()}` : \"\"),\n clauseId,\n receipt: result.receipt,\n };\n },\n /** Decide a whole tool call. A shell call decomposes into several simple\n * commands; every one is recorded, and a single deny denies the call. */\n async evaluate(mapped: Mapped | Mapped[], options: { groupKey?: string } = {}): Promise<Decision> {\n const raw = Array.isArray(mapped) ? mapped : [mapped];\n if (raw.length === 0) return { decision: \"not_evaluated\", reason: \"no action\", receipts: [] };\n // Every intent of one tool call carries one parent group id, so the receipts of a\n // decomposed call (a command, its file reads and writes, a rename destination)\n // link to each other and count once as an action. It is part of the signed\n // intent, so it is authenticated by the same signature as the rest.\n const scoped = scopeRoots.length ? applyRootScope(raw, { cwd: config.cwd ?? process.cwd(), roots: scopeRoots }) : raw;\n const list = withActionGroup(scoped, actionGroupId(options.groupKey));\n const receipts: unknown[] = [];\n const dispatched: NonNullable<Decision[\"dispatched\"]> = [];\n let allow: Decision | null = null;\n let ask: Decision | null = null;\n let notEvaluated: Decision | null = null;\n for (const m of list) {\n const d = await this.evaluateOne(m);\n if (d.receipt !== undefined) receipts.push(d.receipt);\n dispatched.push({ action: { action_type: m.intent.action_type, params: m.intent.params as Record<string, unknown> }, ...(d.receipt !== undefined ? { receipt: d.receipt } : {}) });\n if (d.decision === \"deny\") return { ...d, receipts, dispatched }; // any deny denies the call\n if (d.decision === \"allow\" && !allow) allow = d;\n if (d.decision === \"ask\" && !ask) ask = d;\n if (d.decision === \"not_evaluated\" && !notEvaluated) notEvaluated = d;\n }\n // No deny: allow if any command was evaluated-and-allowed, else not_evaluated.\n // A command the person must be asked about makes the whole call an ask (warn mode); otherwise as before.\n const chosen = ask ?? allow ?? notEvaluated!;\n if (boundary) {\n // Immediately before permitted dispatch: approvals are consumed and the budget slot is reserved\n // atomically, once for the whole parent action, or nothing is spent and the call is denied.\n const group = String(list[0]!.intent.params[ACTION_GROUP_PARAM]);\n const delegation = process.env[DELEGATION_ENV] ?? \"\";\n const verdict = await boundary.authorize({\n actor: agent.kid, action_group: group, policy_digest: gateway.policyHash,\n intents: list.map((m) => dispatchIntentOf(m.intent as never)),\n ...(delegation !== \"\" ? { delegation_id: delegation } : {}),\n });\n if (!verdict.allow) {\n // Record the refusal as its own denied receipt, then deny the call.\n boundaryVerdict = verdict;\n try {\n const last = list[list.length - 1]!;\n const signed = agent.sign(last.intent);\n const result = await gateway.handleAction({ intent: signed.intent, authorization: signed.authorization });\n if (result.receipt !== undefined) receipts.push(result.receipt);\n } finally { boundaryVerdict = null; }\n return { decision: \"deny\", reason: `Scopebond blocked this before it ran: ${verdict.reason}${verdict.detail ? ` (${verdict.detail})` : \"\"}`, receipts, dispatched, dispatch: verdict };\n }\n return { ...chosen, receipts, dispatched, dispatch: verdict };\n }\n return { ...chosen, receipts, dispatched };\n },\n };\n}\n"]}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@scopebond/hook",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.16.0",
|
|
4
4
|
"description": "Scopebond hook for Claude Code, Cursor, and OpenAI Codex — checks coding-agent actions against policy before they run and records a signed receipt locally.",
|
|
5
5
|
"license": "Apache-2.0",
|
|
6
6
|
"author": "Avouro LLC",
|
|
@@ -42,9 +42,9 @@
|
|
|
42
42
|
"dist"
|
|
43
43
|
],
|
|
44
44
|
"dependencies": {
|
|
45
|
-
"@scopebond/
|
|
46
|
-
"@scopebond/
|
|
47
|
-
"@scopebond/
|
|
45
|
+
"@scopebond/sdk": "^0.1.4",
|
|
46
|
+
"@scopebond/gateway": "^0.13.0",
|
|
47
|
+
"@scopebond/policy-schema": "^0.6.0"
|
|
48
48
|
},
|
|
49
49
|
"publishConfig": {
|
|
50
50
|
"access": "public"
|