@scopebond/hook 0.2.1 → 0.4.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 +36 -6
- package/dist/cli.js +289 -23
- 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 +3 -2
- package/dist/index.js.map +1 -1
- package/dist/init.d.ts +1 -1
- package/dist/init.d.ts.map +1 -1
- package/dist/init.js +25 -12
- package/dist/init.js.map +1 -1
- package/dist/install.d.ts +26 -0
- package/dist/install.d.ts.map +1 -0
- package/dist/install.js +132 -0
- package/dist/install.js.map +1 -0
- package/dist/map.d.ts +12 -6
- package/dist/map.d.ts.map +1 -1
- package/dist/map.js +116 -49
- package/dist/map.js.map +1 -1
- package/dist/minimize.d.ts +5 -0
- package/dist/minimize.d.ts.map +1 -1
- package/dist/minimize.js +58 -12
- package/dist/minimize.js.map +1 -1
- package/dist/runtime.d.ts +11 -3
- package/dist/runtime.d.ts.map +1 -1
- package/dist/runtime.js +57 -9
- package/dist/runtime.js.map +1 -1
- package/dist/shell.d.ts +26 -0
- package/dist/shell.d.ts.map +1 -0
- package/dist/shell.js +331 -0
- package/dist/shell.js.map +1 -0
- package/dist/version.d.ts +11 -0
- package/dist/version.d.ts.map +1 -0
- package/dist/version.js +30 -0
- package/dist/version.js.map +1 -0
- package/package.json +5 -4
package/dist/map.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"map.js","sourceRoot":"","sources":["../src/map.ts"],"names":[],"mappings":"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","sourcesContent":["// The mapper: a pure function from a coding agent's native tool call to a\n// normalized Scopebond action (Action Taxonomy v1). This is the connector's core\n// — deterministic and side-effect free, so it can be conformance-tested directly.\n\nimport { digest, redactCommand } from \"./minimize.js\";\n\nexport interface NormalizedIntent {\n action_type: string;\n params: Record<string, unknown>;\n}\nexport interface Mapped {\n intent: NormalizedIntent;\n /** false when no taxonomy type applies: emitted as tool.<name> and routed to the\n * observation path (not_evaluated), never granted. */\n evaluated: boolean;\n /** The native tool/event name, for diagnostics. */\n source: string;\n}\n\nconst rel = (value: unknown, cwd?: string): string => {\n const s = String(value ?? \"\");\n if (cwd && s.startsWith(cwd)) return s.slice(cwd.length).replace(/^[\\\\/]+/, \"\");\n return s;\n};\n\n/** The invoked program's basename, after stripping sudo and `env VAR=val` prefixes. */\nfunction programOf(command: string): string {\n const stripped = command.trim().replace(/^sudo\\s+/, \"\").replace(/^(?:env\\s+\\S+=\\S+\\s+)+/, \"\");\n const first = stripped.split(/\\s+/)[0] ?? \"\";\n return first.replace(/^.*[\\\\/]/, \"\");\n}\n\n// Best-effort `git push [--force|-f] [remote] [ref]` parse. Ambiguous pushes omit\n// the ref, which a ref bound then denies (fail closed).\nfunction parseGitPush(command: string): Record<string, unknown> | null {\n const t = command.trim().replace(/^sudo\\s+/, \"\");\n if (!/^git\\s+push(\\s|$)/.test(t)) return null;\n const force = /(?:^|\\s)(?:--force\\b|--force-with-lease\\b|-f\\b)/.test(t);\n const rest = t.replace(/^git\\s+push\\b/, \"\").trim();\n const positional = rest.split(/\\s+/).filter((a) => a && !a.startsWith(\"-\"));\n const params: Record<string, unknown> = { force };\n if (positional[0]) params.remote = positional[0];\n if (positional[1]) params.ref = positional[1].replace(/^[^:]*:/, \"\"); // src:dst → dst\n return params;\n}\n\nfunction mapShell(command: string, cwd?: string): Mapped {\n const push = parseGitPush(command);\n if (push) return { intent: { action_type: \"git.push\", params: push }, evaluated: true, source: \"shell\" };\n return {\n intent: {\n action_type: \"shell.exec\",\n params: { command: redactCommand(command), program: programOf(command), ...(cwd ? { cwd } : {}) },\n },\n evaluated: true, source: \"shell\",\n };\n}\n\nfunction parseMcpName(name: string): { server: string; tool: string } | null {\n if (!name.startsWith(\"mcp__\")) return null;\n const parts = name.split(\"__\");\n if (parts.length < 3) return null;\n return { server: parts[1], tool: parts.slice(2).join(\"__\") };\n}\n\nfunction splitUrl(url: string): { host: string; path: string } {\n try { const u = new URL(url); return { host: u.host, path: u.pathname }; }\n catch { return { host: url, path: \"\" }; }\n}\n\n/** Map a Claude Code PreToolUse payload to a normalized taxonomy action. */\nexport function mapClaudeToolUse(input: Record<string, unknown>): Mapped {\n const name = String(input?.tool_name ?? \"\");\n const ti = (input?.tool_input ?? {}) as Record<string, unknown>;\n const cwd = input?.cwd ? String(input.cwd) : undefined;\n if (name === \"Bash\") return mapShell(String(ti.command ?? \"\"), cwd);\n if (name === \"Write\" || name === \"Edit\" || name === \"MultiEdit\")\n return { intent: { action_type: \"file.write\", params: { path: rel(ti.file_path, cwd) } }, evaluated: true, source: name };\n if (name === \"NotebookEdit\")\n return { intent: { action_type: \"file.write\", params: { path: rel(ti.notebook_path ?? ti.file_path, cwd) } }, evaluated: true, source: name };\n if (name === \"Read\")\n return { intent: { action_type: \"file.read\", params: { path: rel(ti.file_path, cwd) } }, evaluated: true, source: name };\n if (name === \"WebFetch\") {\n const { host, path } = splitUrl(String(ti.url ?? \"\"));\n return { intent: { action_type: \"net.fetch\", params: { host, path, method: \"GET\" } }, evaluated: true, source: name };\n }\n const mcp = parseMcpName(name);\n if (mcp) return { intent: { action_type: \"mcp.tool.call\", params: { server: mcp.server, tool: mcp.tool, args_digest: digest(ti) } }, evaluated: true, source: name };\n return { intent: { action_type: `tool.${name.toLowerCase()}`, params: {} }, evaluated: false, source: name };\n}\n\n/** Map a Cursor hook event to a normalized taxonomy action. */\nexport function mapCursorEvent(event: string, payload: Record<string, unknown>): Mapped {\n const p = payload ?? {};\n const cwd = p.cwd ? String(p.cwd) : undefined;\n switch (event) {\n case \"beforeShellExecution\":\n return mapShell(String(p.command ?? \"\"), cwd);\n case \"beforeReadFile\":\n return { intent: { action_type: \"file.read\", params: { path: rel(p.path ?? p.file_path, cwd) } }, evaluated: true, source: event };\n case \"afterFileEdit\":\n return { intent: { action_type: \"file.write\", params: { path: rel(p.path ?? p.file_path, cwd) } }, evaluated: true, source: event };\n case \"beforeMCPExecution\": {\n const server = String(p.server ?? p.server_name ?? \"\");\n const tool = String(p.tool ?? p.tool_name ?? \"\");\n return { intent: { action_type: \"mcp.tool.call\", params: { server, tool, args_digest: digest(p.args ?? p.arguments ?? {}) } }, evaluated: true, source: event };\n }\n default:\n return { intent: { action_type: `tool.${event.toLowerCase()}`, params: {} }, evaluated: false, source: event };\n }\n}\n"]}
|
|
1
|
+
{"version":3,"file":"map.js","sourceRoot":"","sources":["../src/map.ts"],"names":[],"mappings":"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","sourcesContent":["// The mapper: a pure function from a coding agent's native tool call to the\n// normalized Scopebond actions (Action Taxonomy v1) it represents. Deterministic\n// and side-effect free, so it can be conformance-tested directly.\n//\n// A shell tool call can carry several commands (`a && b`, `$(c)`, `bash -c '…'`);\n// the mapper decomposes it and returns one intent per simple command, so the\n// runtime evaluates every one and denies if any is out of policy. Non-shell tools\n// map to a single intent (a one-element array).\n\nimport { digest, redactCommand, scrubParam, scrubSecrets } from \"./minimize.js\";\nimport { decomposeShell, parseGitPush, type SimpleCommand } from \"./shell.js\";\n\nexport interface NormalizedIntent {\n action_type: string;\n params: Record<string, unknown>;\n}\nexport interface Mapped {\n intent: NormalizedIntent;\n /** false when no taxonomy type applies (an unknown tool, or a shell command that\n * could not be parsed): emitted to the observation path (not_evaluated) in normal\n * mode and denied by the closed allowlist in strict mode — never granted. */\n evaluated: boolean;\n /** The native tool/event name, for diagnostics. */\n source: string;\n}\n\nconst basename = (t: string): string => t.replace(/^.*[\\\\/]/, \"\");\n\n// Normalize Windows backslash separators to \"/\" so a path guard written with \"/\"\n// (the hook's own .scopebond/**, .claude/settings*, *.key rules) cannot be bypassed\n// on Windows by a backslash path. The taxonomy path space is \"/\"-separated.\nconst normPath = (s: string): string => s.replace(/\\\\/g, \"/\");\n\nconst rel = (value: unknown, cwd?: string): string => {\n const s = normPath(String(value ?? \"\"));\n const c = cwd ? normPath(cwd) : undefined;\n if (c && s.startsWith(c)) return s.slice(c.length).replace(/^\\/+/, \"\");\n return s;\n};\n\n// Shell programs that READ a file given as an operand. A read of the signing keys\n// or a secret file through the shell (`cat .scopebond/attester.key`) must reach the\n// same file.read guard as the Read tool, or the cooperative protection is a fiction.\nconst READERS = new Set([\n \"cat\", \"tac\", \"less\", \"more\", \"head\", \"tail\", \"nl\", \"od\", \"xxd\", \"hexdump\",\n \"strings\", \"base64\", \"bat\", \"type\", \"get-content\", \"gc\",\n]);\n\n/** Derive the additional file.read / file.write intents a simple shell command\n * implies: operands of a reader program, and the targets of `>`/`>>` redirections.\n * These flow through the same protect-read / protect-write clauses as the native\n * file tools. A false positive (a non-protected operand) is a harmless extra\n * receipt that the starter policy allows. */\nfunction fileOpsFromShell(sc: SimpleCommand, cwd?: string): Mapped[] {\n const ops: Mapped[] = [];\n if (READERS.has(sc.program.toLowerCase())) {\n for (const t of sc.argv) {\n if (t.startsWith(\"-\") || /^\\d+$/.test(t)) continue; // skip flags and flag values (head -n 5)\n ops.push({ intent: { action_type: \"file.read\", params: { path: rel(scrubParam(t), cwd) } }, evaluated: true, source: \"shell\" });\n }\n }\n for (let i = 0; i < sc.argv.length; i++) {\n // `> file`, `>>file`, `2>file`, `&>file` — the token after the redirection op is a write target.\n const m = /^(?:\\d|&)?(>>?)(.*)$/.exec(sc.argv[i]);\n if (!m) continue;\n const target = m[2] || sc.argv[i + 1];\n if (target && !target.startsWith(\"-\") && !/^(?:\\d|&)?>>?/.test(target))\n ops.push({ intent: { action_type: \"file.write\", params: { path: rel(scrubParam(target), cwd) } }, evaluated: true, source: \"shell\" });\n }\n return ops;\n}\n\n/** Map one parsed simple command to the intents it implies: the git.push or\n * shell.exec itself, plus any file reads/writes it performs. An opaque\n * (unparseable) command becomes a single un-evaluated shell.exec so it fails closed. */\nfunction mapSimpleCommand(sc: SimpleCommand, cwd?: string): Mapped[] {\n if (sc.opaque) {\n return [{\n intent: { action_type: \"shell.exec\", params: { command: redactCommand(sc.raw), program: \"\", ...(cwd ? { cwd } : {}) } },\n evaluated: false, source: \"shell\",\n }];\n }\n const push = parseGitPush(sc);\n if (push) {\n const params: Record<string, unknown> = { force: push.force };\n if (push.remote !== undefined) params.remote = scrubParam(push.remote);\n if (push.ref !== undefined) params.ref = scrubParam(push.ref);\n return [{ intent: { action_type: \"git.push\", params }, evaluated: true, source: \"shell\" }];\n }\n const exec: Mapped = {\n intent: {\n action_type: \"shell.exec\",\n // Scrub before storing: the raw command through the blob-aware scrubber, and\n // the whole first token BEFORE taking its basename, so a bare-secret command\n // containing \"/\" cannot leak a path-fragment as the program.\n params: { command: redactCommand(sc.raw), program: basename(scrubSecrets(sc.programRaw)), ...(cwd ? { cwd } : {}) },\n },\n evaluated: true, source: \"shell\",\n };\n return [exec, ...fileOpsFromShell(sc, cwd)];\n}\n\n/** Decompose a shell command into the intents it will run. An empty command yields\n * a single un-evaluated placeholder (nothing to grant). */\nfunction mapShell(command: string, cwd?: string): Mapped[] {\n const commands = decomposeShell(command);\n if (commands.length === 0) {\n return [{ intent: { action_type: \"shell.exec\", params: { command: redactCommand(command), program: \"\" } }, evaluated: false, source: \"shell\" }];\n }\n return commands.flatMap((sc) => mapSimpleCommand(sc, cwd));\n}\n\nfunction parseMcpName(name: string): { server: string; tool: string } | null {\n if (!name.startsWith(\"mcp__\")) return null;\n const parts = name.split(\"__\");\n if (parts.length < 3) return null;\n return { server: parts[1], tool: parts.slice(2).join(\"__\") };\n}\n\nfunction splitUrl(url: string): { host: string; path: string } {\n // The query string is dropped; a token in the path or an unparseable URL is scrubbed.\n try { const u = new URL(url); return { host: u.host, path: scrubParam(u.pathname) }; }\n catch { return { host: scrubParam(url), path: \"\" }; }\n}\n\nconst one = (intent: NormalizedIntent, evaluated: boolean, source: string): Mapped[] => [{ intent, evaluated, source }];\n\n/** A bare `git push` (no ref) pushes the current branch. The mapper cannot know it,\n * so the runtime resolves it and fills it in here, before evaluation — otherwise the\n * starter policy's ref bound would fail-closed on every plain push. Only git.push\n * intents with no ref are touched. */\nexport function fillPushBranch(mapped: Mapped[], branch: string | null | undefined): Mapped[] {\n if (!branch) return mapped;\n return mapped.map((m) =>\n m.intent.action_type === \"git.push\" && m.intent.params.ref === undefined\n ? { ...m, intent: { ...m.intent, params: { ...m.intent.params, ref: scrubParam(branch) } } }\n : m);\n}\n\n/** Map a Claude Code PreToolUse payload to the normalized actions it represents. */\nexport function mapClaudeToolUse(input: Record<string, unknown>): Mapped[] {\n const name = String(input?.tool_name ?? \"\");\n const ti = (input?.tool_input ?? {}) as Record<string, unknown>;\n const cwd = input?.cwd ? String(input.cwd) : undefined;\n // Every shell-executing tool decomposes the same way. Claude Code exposes Bash;\n // some hosts/agents expose PowerShell or a generic Shell tool — mapping only Bash\n // let a PowerShell command (e.g. `Remove-Item -Recurse -Force .`) fall through to\n // an un-evaluated tool.<name> and be allowed.\n if (name === \"Bash\" || name === \"PowerShell\" || name === \"Shell\")\n return mapShell(String(ti.command ?? \"\"), cwd);\n if (name === \"Write\" || name === \"Edit\" || name === \"MultiEdit\")\n return one({ action_type: \"file.write\", params: { path: rel(ti.file_path, cwd) } }, true, name);\n if (name === \"NotebookEdit\")\n return one({ action_type: \"file.write\", params: { path: rel(ti.notebook_path ?? ti.file_path, cwd) } }, true, name);\n if (name === \"Read\")\n return one({ action_type: \"file.read\", params: { path: rel(ti.file_path, cwd) } }, true, name);\n if (name === \"WebFetch\") {\n const { host, path } = splitUrl(String(ti.url ?? \"\"));\n return one({ action_type: \"net.fetch\", params: { host, path, method: \"GET\" } }, true, name);\n }\n const mcp = parseMcpName(name);\n if (mcp) return one({ action_type: \"mcp.tool.call\", params: { server: mcp.server, tool: mcp.tool, args_digest: digest(ti) } }, true, name);\n return one({ action_type: `tool.${name.toLowerCase()}`, params: {} }, false, name);\n}\n\n/** Map a Cursor hook event to the normalized actions it represents. */\nexport function mapCursorEvent(event: string, payload: Record<string, unknown>): Mapped[] {\n const p = payload ?? {};\n const cwd = p.cwd ? String(p.cwd) : undefined;\n switch (event) {\n case \"beforeShellExecution\":\n return mapShell(String(p.command ?? \"\"), cwd);\n case \"beforeReadFile\":\n return one({ action_type: \"file.read\", params: { path: rel(p.path ?? p.file_path, cwd) } }, true, event);\n case \"afterFileEdit\":\n return one({ action_type: \"file.write\", params: { path: rel(p.path ?? p.file_path, cwd) } }, true, event);\n case \"beforeMCPExecution\": {\n const server = String(p.server ?? p.server_name ?? \"\");\n const tool = String(p.tool ?? p.tool_name ?? \"\");\n return one({ action_type: \"mcp.tool.call\", params: { server, tool, args_digest: digest(p.args ?? p.arguments ?? {}) } }, true, event);\n }\n default:\n return one({ action_type: `tool.${event.toLowerCase()}`, params: {} }, false, event);\n }\n}\n"]}
|
package/dist/minimize.d.ts
CHANGED
|
@@ -1,5 +1,10 @@
|
|
|
1
1
|
export declare const sha256: (value: string) => string;
|
|
2
2
|
export declare const digest: (value: unknown) => string;
|
|
3
|
+
/** Scrub a structured parameter (a program name, a remote, a URL path): credential
|
|
4
|
+
* assignments and recognizable token shapes only, so policy matching on ordinary
|
|
5
|
+
* values is unaffected. */
|
|
6
|
+
export declare function scrubParam(text: string): string;
|
|
7
|
+
/** Scrub free text: everything `scrubParam` removes plus generic high-entropy blobs. */
|
|
3
8
|
export declare function scrubSecrets(text: string): string;
|
|
4
9
|
/** A privacy-preserving representation of a shell command: a scrubbed, truncated
|
|
5
10
|
* head plus a digest of the full original. The full command is never retained. */
|
package/dist/minimize.d.ts.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"minimize.d.ts","sourceRoot":"","sources":["../src/minimize.ts"],"names":[],"mappings":"AAOA,eAAO,MAAM,MAAM,GAAI,OAAO,MAAM,KAAG,MAAsE,CAAC;AAC9G,eAAO,MAAM,MAAM,GAAI,OAAO,OAAO,KAAG,MAA2C,CAAC;
|
|
1
|
+
{"version":3,"file":"minimize.d.ts","sourceRoot":"","sources":["../src/minimize.ts"],"names":[],"mappings":"AAOA,eAAO,MAAM,MAAM,GAAI,OAAO,MAAM,KAAG,MAAsE,CAAC;AAC9G,eAAO,MAAM,MAAM,GAAI,OAAO,OAAO,KAAG,MAA2C,CAAC;AAmDpF;;4BAE4B;AAC5B,wBAAgB,UAAU,CAAC,IAAI,EAAE,MAAM,GAAG,MAAM,CAI/C;AAED,wFAAwF;AACxF,wBAAgB,YAAY,CAAC,IAAI,EAAE,MAAM,GAAG,MAAM,CAIjD;AAED;mFACmF;AACnF,wBAAgB,aAAa,CAAC,OAAO,EAAE,MAAM,EAAE,OAAO,SAAK,GAAG,MAAM,CAInE"}
|
package/dist/minimize.js
CHANGED
|
@@ -5,20 +5,66 @@ import { createHash } from "node:crypto";
|
|
|
5
5
|
import { canonical } from "@scopebond/policy-schema/canonical";
|
|
6
6
|
export const sha256 = (value) => "sha256:" + createHash("sha256").update(value).digest("hex");
|
|
7
7
|
export const digest = (value) => sha256(canonical(value));
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
8
|
+
const MASK = "***";
|
|
9
|
+
// A flag or header value: a quoted string or a run of non-whitespace.
|
|
10
|
+
const VALUE = String.raw `("[^"]*"|'[^']*'|\S+)`;
|
|
11
|
+
// Each rule states its own replacement. `$1` keeps a non-secret label (a flag or
|
|
12
|
+
// header name); the secret itself is always dropped whole. Over-scrubbing is safe.
|
|
13
|
+
// Every pattern is a single pass over simple character classes (no nested
|
|
14
|
+
// quantifiers), so matching stays linear in the command length.
|
|
15
|
+
const SECRET_RULES = [
|
|
16
|
+
[/-----BEGIN [A-Z ]*PRIVATE KEY-----[\s\S]*?(?:-----END [A-Z ]*PRIVATE KEY-----|$)/g, MASK],
|
|
17
|
+
[new RegExp(String.raw `(--?(?:password|passwd|pwd|token|secret|api[-_]?key|access[-_]?key|auth|credentials?)[=\s]+)${VALUE}`, "gi"), `$1${MASK}`],
|
|
18
|
+
[new RegExp(String.raw `((?:^|\s)(?:-u|--user)[=\s]+)${VALUE}`, "g"), `$1${MASK}`],
|
|
19
|
+
[/(Authorization:\s*(?:Bearer|Basic|Token)\s+)[^\s"']+/gi, `$1${MASK}`],
|
|
20
|
+
[/((?:x-api-key|api-key|x-auth-token|x-access-token|private-token)\s*:\s*)[^\s"']+/gi, `$1${MASK}`],
|
|
21
|
+
[/(:\/\/)[^\s/@:]+:[^\s/@]+@/g, `$1${MASK}@`], // URL userinfo
|
|
22
|
+
[/\bgh[pousr]_[A-Za-z0-9]{20,}/g, MASK], // GitHub tokens
|
|
23
|
+
[/\bgithub_pat_[A-Za-z0-9_]{20,}/g, MASK], // GitHub fine-grained tokens
|
|
24
|
+
[/\bglpat-[A-Za-z0-9_-]{20,}/g, MASK], // GitLab tokens
|
|
25
|
+
[/\bnpm_[A-Za-z0-9]{20,}/g, MASK], // npm tokens
|
|
26
|
+
[/\bxox[abeoprs]-[A-Za-z0-9-]{10,}/g, MASK], // Slack tokens
|
|
27
|
+
[/\b[sr]k_(?:live|test)_[A-Za-z0-9]{16,}/g, MASK], // Stripe keys
|
|
28
|
+
[/\bsk-[A-Za-z0-9_-]{20,}/g, MASK], // OpenAI/Anthropic-style keys
|
|
29
|
+
[/\bAIza[0-9A-Za-z_-]{30,}/g, MASK], // Google API keys
|
|
30
|
+
[/\b(?:AKIA|ASIA)[0-9A-Z]{16}\b/g, MASK], // AWS access key ids
|
|
31
|
+
[/\beyJ[A-Za-z0-9_-]{8,}\.[A-Za-z0-9_-]{8,}\.[A-Za-z0-9_-]{8,}/g, MASK], // JWTs
|
|
16
32
|
];
|
|
33
|
+
// Generic high-entropy shapes. Applied to free text only (the command head), never to
|
|
34
|
+
// structured parameters a policy matches on: a long path or a commit id is not a secret.
|
|
35
|
+
const BLOB_RULES = [
|
|
36
|
+
[/\b[A-Fa-f0-9]{32,}\b/g, MASK], // long hex blobs
|
|
37
|
+
[/[A-Za-z0-9+/_-]{40,}={0,2}/g, MASK], // long base64/base64url blobs
|
|
38
|
+
];
|
|
39
|
+
const SECRET_NAME = /token|secret|passw|pwd|api_?key|access_?key|private_?key|auth|credential|session/i;
|
|
40
|
+
// `NAME=value` where the name looks like a credential (`GH_TOKEN=…`, `export
|
|
41
|
+
// AWS_SECRET_ACCESS_KEY=…`). Done per whitespace-delimited word rather than with
|
|
42
|
+
// one regex so a long identifier cannot cause backtracking.
|
|
43
|
+
function scrubAssignments(text) {
|
|
44
|
+
return text.replace(/\S+/g, (word) => {
|
|
45
|
+
const eq = word.indexOf("=");
|
|
46
|
+
if (eq <= 0 || eq === word.length - 1)
|
|
47
|
+
return word;
|
|
48
|
+
const name = word.slice(0, eq);
|
|
49
|
+
if (name.startsWith("-") || !SECRET_NAME.test(name))
|
|
50
|
+
return word; // flags are handled by SECRET_RULES
|
|
51
|
+
return `${name}=${MASK}`;
|
|
52
|
+
});
|
|
53
|
+
}
|
|
54
|
+
/** Scrub a structured parameter (a program name, a remote, a URL path): credential
|
|
55
|
+
* assignments and recognizable token shapes only, so policy matching on ordinary
|
|
56
|
+
* values is unaffected. */
|
|
57
|
+
export function scrubParam(text) {
|
|
58
|
+
let out = scrubAssignments(text);
|
|
59
|
+
for (const [re, replacement] of SECRET_RULES)
|
|
60
|
+
out = out.replace(re, replacement);
|
|
61
|
+
return out;
|
|
62
|
+
}
|
|
63
|
+
/** Scrub free text: everything `scrubParam` removes plus generic high-entropy blobs. */
|
|
17
64
|
export function scrubSecrets(text) {
|
|
18
|
-
let out = text;
|
|
19
|
-
for (const re of
|
|
20
|
-
out = out.replace(re,
|
|
21
|
-
}
|
|
65
|
+
let out = scrubParam(text);
|
|
66
|
+
for (const [re, replacement] of BLOB_RULES)
|
|
67
|
+
out = out.replace(re, replacement);
|
|
22
68
|
return out;
|
|
23
69
|
}
|
|
24
70
|
/** A privacy-preserving representation of a shell command: a scrubbed, truncated
|
package/dist/minimize.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"minimize.js","sourceRoot":"","sources":["../src/minimize.ts"],"names":[],"mappings":"AAAA,iFAAiF;AACjF,6EAA6E;AAC7E,uDAAuD;AAEvD,OAAO,EAAE,UAAU,EAAE,MAAM,aAAa,CAAC;AACzC,OAAO,EAAE,SAAS,EAAE,MAAM,oCAAoC,CAAC;AAE/D,MAAM,CAAC,MAAM,MAAM,GAAG,CAAC,KAAa,EAAU,EAAE,CAAC,SAAS,GAAG,UAAU,CAAC,QAAQ,CAAC,CAAC,MAAM,CAAC,KAAK,CAAC,CAAC,MAAM,CAAC,KAAK,CAAC,CAAC;AAC9G,MAAM,CAAC,MAAM,MAAM,GAAG,CAAC,KAAc,EAAU,EAAE,CAAC,MAAM,CAAC,SAAS,CAAC,KAAc,CAAC,CAAC,CAAC;AAEpF,
|
|
1
|
+
{"version":3,"file":"minimize.js","sourceRoot":"","sources":["../src/minimize.ts"],"names":[],"mappings":"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","sourcesContent":["// Data minimization for the hook: file contents are never stored, shell commands\n// are reduced to a scrubbed head plus a digest, and common secret shapes are\n// removed before anything is written or signed (SB09).\n\nimport { createHash } from \"node:crypto\";\nimport { canonical } from \"@scopebond/policy-schema/canonical\";\n\nexport const sha256 = (value: string): string => \"sha256:\" + createHash(\"sha256\").update(value).digest(\"hex\");\nexport const digest = (value: unknown): string => sha256(canonical(value as never));\n\nconst MASK = \"***\";\n// A flag or header value: a quoted string or a run of non-whitespace.\nconst VALUE = String.raw`(\"[^\"]*\"|'[^']*'|\\S+)`;\n\n// Each rule states its own replacement. `$1` keeps a non-secret label (a flag or\n// header name); the secret itself is always dropped whole. Over-scrubbing is safe.\n// Every pattern is a single pass over simple character classes (no nested\n// quantifiers), so matching stays linear in the command length.\nconst SECRET_RULES: ReadonlyArray<readonly [RegExp, string]> = [\n [/-----BEGIN [A-Z ]*PRIVATE KEY-----[\\s\\S]*?(?:-----END [A-Z ]*PRIVATE KEY-----|$)/g, MASK],\n [new RegExp(String.raw`(--?(?:password|passwd|pwd|token|secret|api[-_]?key|access[-_]?key|auth|credentials?)[=\\s]+)${VALUE}`, \"gi\"), `$1${MASK}`],\n [new RegExp(String.raw`((?:^|\\s)(?:-u|--user)[=\\s]+)${VALUE}`, \"g\"), `$1${MASK}`],\n [/(Authorization:\\s*(?:Bearer|Basic|Token)\\s+)[^\\s\"']+/gi, `$1${MASK}`],\n [/((?:x-api-key|api-key|x-auth-token|x-access-token|private-token)\\s*:\\s*)[^\\s\"']+/gi, `$1${MASK}`],\n [/(:\\/\\/)[^\\s/@:]+:[^\\s/@]+@/g, `$1${MASK}@`], // URL userinfo\n [/\\bgh[pousr]_[A-Za-z0-9]{20,}/g, MASK], // GitHub tokens\n [/\\bgithub_pat_[A-Za-z0-9_]{20,}/g, MASK], // GitHub fine-grained tokens\n [/\\bglpat-[A-Za-z0-9_-]{20,}/g, MASK], // GitLab tokens\n [/\\bnpm_[A-Za-z0-9]{20,}/g, MASK], // npm tokens\n [/\\bxox[abeoprs]-[A-Za-z0-9-]{10,}/g, MASK], // Slack tokens\n [/\\b[sr]k_(?:live|test)_[A-Za-z0-9]{16,}/g, MASK], // Stripe keys\n [/\\bsk-[A-Za-z0-9_-]{20,}/g, MASK], // OpenAI/Anthropic-style keys\n [/\\bAIza[0-9A-Za-z_-]{30,}/g, MASK], // Google API keys\n [/\\b(?:AKIA|ASIA)[0-9A-Z]{16}\\b/g, MASK], // AWS access key ids\n [/\\beyJ[A-Za-z0-9_-]{8,}\\.[A-Za-z0-9_-]{8,}\\.[A-Za-z0-9_-]{8,}/g, MASK], // JWTs\n];\n\n// Generic high-entropy shapes. Applied to free text only (the command head), never to\n// structured parameters a policy matches on: a long path or a commit id is not a secret.\nconst BLOB_RULES: ReadonlyArray<readonly [RegExp, string]> = [\n [/\\b[A-Fa-f0-9]{32,}\\b/g, MASK], // long hex blobs\n [/[A-Za-z0-9+/_-]{40,}={0,2}/g, MASK], // long base64/base64url blobs\n];\n\nconst SECRET_NAME = /token|secret|passw|pwd|api_?key|access_?key|private_?key|auth|credential|session/i;\n\n// `NAME=value` where the name looks like a credential (`GH_TOKEN=…`, `export\n// AWS_SECRET_ACCESS_KEY=…`). Done per whitespace-delimited word rather than with\n// one regex so a long identifier cannot cause backtracking.\nfunction scrubAssignments(text: string): string {\n return text.replace(/\\S+/g, (word) => {\n const eq = word.indexOf(\"=\");\n if (eq <= 0 || eq === word.length - 1) return word;\n const name = word.slice(0, eq);\n if (name.startsWith(\"-\") || !SECRET_NAME.test(name)) return word; // flags are handled by SECRET_RULES\n return `${name}=${MASK}`;\n });\n}\n\n/** Scrub a structured parameter (a program name, a remote, a URL path): credential\n * assignments and recognizable token shapes only, so policy matching on ordinary\n * values is unaffected. */\nexport function scrubParam(text: string): string {\n let out = scrubAssignments(text);\n for (const [re, replacement] of SECRET_RULES) out = out.replace(re, replacement);\n return out;\n}\n\n/** Scrub free text: everything `scrubParam` removes plus generic high-entropy blobs. */\nexport function scrubSecrets(text: string): string {\n let out = scrubParam(text);\n for (const [re, replacement] of BLOB_RULES) out = out.replace(re, replacement);\n return out;\n}\n\n/** A privacy-preserving representation of a shell command: a scrubbed, truncated\n * head plus a digest of the full original. The full command is never retained. */\nexport function redactCommand(command: string, headLen = 64): string {\n const scrubbed = scrubSecrets(command);\n const head = scrubbed.length > headLen ? `${scrubbed.slice(0, headLen)}…` : scrubbed;\n return `${head} (${sha256(command)})`;\n}\n"]}
|
package/dist/runtime.d.ts
CHANGED
|
@@ -21,11 +21,15 @@ export interface RuntimeConfig {
|
|
|
21
21
|
export interface Decision {
|
|
22
22
|
decision: "allow" | "deny" | "not_evaluated";
|
|
23
23
|
reason: string;
|
|
24
|
+
/** The deciding receipt (the denied one, or the first allow). */
|
|
24
25
|
receipt?: unknown;
|
|
26
|
+
/** Every receipt produced — one per simple command in a decomposed shell call. */
|
|
27
|
+
receipts?: unknown[];
|
|
25
28
|
}
|
|
26
29
|
/** The default starter policy for a coding agent: protect release branches, deny
|
|
27
|
-
* destructive programs, allow workspace file access
|
|
28
|
-
* machine key. Every threshold is the
|
|
30
|
+
* destructive programs, allow workspace file access except the hook's own config
|
|
31
|
+
* and keys, and trust only the enrolled machine key. Every threshold is the
|
|
32
|
+
* operator's to edit. */
|
|
29
33
|
export declare function starterPolicy(agentKid: string): Record<string, unknown>;
|
|
30
34
|
/** Build the runtime. Throws on any setup failure (unparseable policy, missing
|
|
31
35
|
* key, unavailable store) — the CLI turns that into a fail-closed deny. */
|
|
@@ -36,6 +40,10 @@ export declare function createHookRuntime(config: RuntimeConfig): {
|
|
|
36
40
|
/** Deliver queued receipts to Cloud with a bounded timeout, then it is safe to
|
|
37
41
|
* exit. Undelivered receipts persist in the durable outbox for the next run. */
|
|
38
42
|
flush(): Promise<void>;
|
|
39
|
-
|
|
43
|
+
/** Decide one mapped action, recording a receipt either way. */
|
|
44
|
+
evaluateOne(mapped: Mapped): Promise<Decision>;
|
|
45
|
+
/** Decide a whole tool call. A shell call decomposes into several simple
|
|
46
|
+
* commands; every one is recorded, and a single deny denies the call. */
|
|
47
|
+
evaluate(mapped: Mapped | Mapped[]): Promise<Decision>;
|
|
40
48
|
};
|
|
41
49
|
//# sourceMappingURL=runtime.d.ts.map
|
package/dist/runtime.d.ts.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"runtime.d.ts","sourceRoot":"","sources":["../src/runtime.ts"],"names":[],"mappings":"AAMA,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;AAE/E,MAAM,WAAW,aAAa;IAC5B,UAAU,EAAE,MAAM,CAAC;IACnB,OAAO,EAAE,MAAM,CAAC;IAChB,YAAY,EAAE,MAAM,CAAC;IACrB,MAAM,EAAE,MAAM,CAAC;IACf;;uFAEmF;IACnF,MAAM,CAAC,EAAE,OAAO,CAAC;IACjB;sEACkE;IAClE,KAAK,CAAC,EAAE;QAAE,UAAU,EAAE,cAAc,CAAC;QAAC,KAAK,CAAC,EAAE,OAAO,KAAK,CAAC;QAAC,cAAc,CAAC,EAAE,MAAM,CAAA;KAAE,CAAC;CACvF;AAED,MAAM,WAAW,QAAQ;IACvB,QAAQ,EAAE,OAAO,GAAG,MAAM,GAAG,eAAe,CAAC;IAC7C,MAAM,EAAE,MAAM,CAAC;IACf,OAAO,CAAC,EAAE,OAAO,CAAC;
|
|
1
|
+
{"version":3,"file":"runtime.d.ts","sourceRoot":"","sources":["../src/runtime.ts"],"names":[],"mappings":"AAMA,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;AAE/E,MAAM,WAAW,aAAa;IAC5B,UAAU,EAAE,MAAM,CAAC;IACnB,OAAO,EAAE,MAAM,CAAC;IAChB,YAAY,EAAE,MAAM,CAAC;IACrB,MAAM,EAAE,MAAM,CAAC;IACf;;uFAEmF;IACnF,MAAM,CAAC,EAAE,OAAO,CAAC;IACjB;sEACkE;IAClE,KAAK,CAAC,EAAE;QAAE,UAAU,EAAE,cAAc,CAAC;QAAC,KAAK,CAAC,EAAE,OAAO,KAAK,CAAC;QAAC,cAAc,CAAC,EAAE,MAAM,CAAA;KAAE,CAAC;CACvF;AAED,MAAM,WAAW,QAAQ;IACvB,QAAQ,EAAE,OAAO,GAAG,MAAM,GAAG,eAAe,CAAC;IAC7C,MAAM,EAAE,MAAM,CAAC;IACf,iEAAiE;IACjE,OAAO,CAAC,EAAE,OAAO,CAAC;IAClB,kFAAkF;IAClF,QAAQ,CAAC,EAAE,OAAO,EAAE,CAAC;CACtB;AAUD;;;0BAG0B;AAC1B,wBAAgB,aAAa,CAAC,QAAQ,EAAE,MAAM,GAAG,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,CAgCvE;AAED;4EAC4E;AAC5E,wBAAgB,iBAAiB,CAAC,MAAM,EAAE,aAAa;;;;IAuBnD;qFACiF;aAClE,OAAO,CAAC,IAAI,CAAC;IAG5B,gEAAgE;wBACtC,MAAM,GAAG,OAAO,CAAC,QAAQ,CAAC;IAapD;8EAC0E;qBACnD,MAAM,GAAG,MAAM,EAAE,GAAG,OAAO,CAAC,QAAQ,CAAC;EAkB/D"}
|
package/dist/runtime.js
CHANGED
|
@@ -7,9 +7,17 @@ import { createGateway, StaticPrincipalKeyRegistry } from "@scopebond/gateway";
|
|
|
7
7
|
import { loadOrCreateAttester, openReceiptStore } from "@scopebond/gateway/node";
|
|
8
8
|
import { createSigner } from "@scopebond/sdk";
|
|
9
9
|
import { attachExporter, flushBounded } from "./cloud.js";
|
|
10
|
+
// The hook's own config and keys must be off-limits to the agent it governs:
|
|
11
|
+
// otherwise the agent could rewrite its policy or read the signing keys and the
|
|
12
|
+
// receipts stop meaning anything. These patterns are "allow if the path does NOT
|
|
13
|
+
// match a protected location"; they run against the cwd-relative path the mapper
|
|
14
|
+
// produces (and equally against an absolute one).
|
|
15
|
+
const PROTECTED_WRITE = "^(?!(?:.*/)?\\.scopebond/)(?!(?:.*/)?\\.claude/settings)(?!(?:.*/)?\\.cursor/hooks)(?!(?:.*/)?\\.git/hooks/)(?!(?:.*/)?\\.github/workflows/)(?!(?:.*/)?\\.github/actions/)(?!(?:.*/)?\\.gitlab-ci\\.yml$)(?!(?:.*/)?\\.circleci/)(?!(?:.*/)?azure-pipelines\\.yml$)(?!(?:.*/)?Jenkinsfile$).+";
|
|
16
|
+
const PROTECTED_READ = "^(?!(?:.*/)?\\.scopebond/)(?!.*\\.key$)(?!(?:.*/)?\\.env(?:\\.(?!example$|sample$|template$)[^/]*)?$).+";
|
|
10
17
|
/** The default starter policy for a coding agent: protect release branches, deny
|
|
11
|
-
* destructive programs, allow workspace file access
|
|
12
|
-
* machine key. Every threshold is the
|
|
18
|
+
* destructive programs, allow workspace file access except the hook's own config
|
|
19
|
+
* and keys, and trust only the enrolled machine key. Every threshold is the
|
|
20
|
+
* operator's to edit. */
|
|
13
21
|
export function starterPolicy(agentKid) {
|
|
14
22
|
return {
|
|
15
23
|
vocabulary_version: "1.0", policy_id: "coding-agent", version: 1,
|
|
@@ -21,10 +29,24 @@ export function starterPolicy(agentKid) {
|
|
|
21
29
|
},
|
|
22
30
|
{
|
|
23
31
|
id: "safe-shell", type: "action_allowlist", mode: "enforce", action_types: ["shell.exec"],
|
|
24
|
-
param_bounds: { program: { pattern: "^(?!(?:rm|sudo|shutdown|reboot|mkfs|dd)$).+" } },
|
|
25
|
-
description: "Deny destructive programs (rm, sudo,
|
|
32
|
+
param_bounds: { program: { pattern: "^(?!(?:rm|sudo|shutdown|reboot|mkfs|dd|del|rd|rmdir|erase|deltree|format|Remove-Item|ri)$).+" } },
|
|
33
|
+
description: "Deny destructive programs — POSIX (rm, sudo, shutdown, reboot, mkfs, dd) and Windows/PowerShell (del, rd, rmdir, erase, deltree, format, Remove-Item). An empty program (an unparseable command) is denied.",
|
|
34
|
+
},
|
|
35
|
+
{
|
|
36
|
+
id: "protect-write", type: "action_allowlist", mode: "enforce", action_types: ["file.write"],
|
|
37
|
+
param_bounds: { path: { pattern: PROTECTED_WRITE } },
|
|
38
|
+
description: "Allow workspace writes, but never to the hook's policy/keys, .claude/settings, .cursor/hooks, git hooks, or CI config (.github/workflows, .github/actions, .gitlab-ci.yml, .circleci, azure-pipelines.yml, Jenkinsfile).",
|
|
39
|
+
},
|
|
40
|
+
{
|
|
41
|
+
id: "protect-read", type: "action_allowlist", mode: "enforce", action_types: ["file.read"],
|
|
42
|
+
param_bounds: { path: { pattern: PROTECTED_READ } },
|
|
43
|
+
description: "Allow workspace reads, but never the signing keys (*.key), environment secret files (.env, .env.*, except .env.example/.sample/.template) or the hook's own .scopebond directory.",
|
|
44
|
+
},
|
|
45
|
+
{
|
|
46
|
+
id: "observe-net-mcp", type: "action_allowlist", mode: "monitor",
|
|
47
|
+
action_types: ["net.fetch", "mcp.tool.call"],
|
|
48
|
+
description: "Observe network fetches and MCP tool calls (recorded, not blocked) — add bounds to enforce.",
|
|
26
49
|
},
|
|
27
|
-
{ id: "workspace-files", type: "action_allowlist", mode: "enforce", action_types: ["file.write", "file.read"] },
|
|
28
50
|
{ id: "keys", type: "key_policy", active_keys: [agentKid], description: "Only the enrolled machine key may sign." },
|
|
29
51
|
],
|
|
30
52
|
};
|
|
@@ -59,18 +81,44 @@ export function createHookRuntime(config) {
|
|
|
59
81
|
if (exporter)
|
|
60
82
|
await flushBounded(exporter, config.cloud?.flushTimeoutMs);
|
|
61
83
|
},
|
|
62
|
-
|
|
84
|
+
/** Decide one mapped action, recording a receipt either way. */
|
|
85
|
+
async evaluateOne(mapped) {
|
|
63
86
|
const signed = agent.sign(mapped.intent);
|
|
64
87
|
if (!mapped.evaluated && !config.strict) {
|
|
65
|
-
// Unknown tool, non-strict: observe without
|
|
88
|
+
// Unknown tool or unparseable command, non-strict: observe without
|
|
89
|
+
// evaluating — grants nothing (D30).
|
|
66
90
|
const { receipt } = await gateway.observeAction({ intent: signed.intent, authorization: signed.authorization });
|
|
67
91
|
return { decision: "not_evaluated", reason: `no policy applies to ${mapped.intent.action_type}`, receipt };
|
|
68
92
|
}
|
|
69
|
-
// Evaluated
|
|
70
|
-
// through policy — a closed allowlist denies an unlisted action.
|
|
93
|
+
// Evaluated actions, and (in strict mode) unmapped tool.<name>/opaque commands,
|
|
94
|
+
// go through policy — a closed allowlist denies an unlisted action.
|
|
71
95
|
const result = await gateway.handleAction({ intent: signed.intent, authorization: signed.authorization });
|
|
72
96
|
return { decision: result.allowed ? "allow" : "deny", reason: result.reason, receipt: result.receipt };
|
|
73
97
|
},
|
|
98
|
+
/** Decide a whole tool call. A shell call decomposes into several simple
|
|
99
|
+
* commands; every one is recorded, and a single deny denies the call. */
|
|
100
|
+
async evaluate(mapped) {
|
|
101
|
+
const list = Array.isArray(mapped) ? mapped : [mapped];
|
|
102
|
+
if (list.length === 0)
|
|
103
|
+
return { decision: "not_evaluated", reason: "no action", receipts: [] };
|
|
104
|
+
const receipts = [];
|
|
105
|
+
let allow = null;
|
|
106
|
+
let notEvaluated = null;
|
|
107
|
+
for (const m of list) {
|
|
108
|
+
const d = await this.evaluateOne(m);
|
|
109
|
+
if (d.receipt !== undefined)
|
|
110
|
+
receipts.push(d.receipt);
|
|
111
|
+
if (d.decision === "deny")
|
|
112
|
+
return { ...d, receipts }; // any deny denies the call
|
|
113
|
+
if (d.decision === "allow" && !allow)
|
|
114
|
+
allow = d;
|
|
115
|
+
if (d.decision === "not_evaluated" && !notEvaluated)
|
|
116
|
+
notEvaluated = d;
|
|
117
|
+
}
|
|
118
|
+
// No deny: allow if any command was evaluated-and-allowed, else not_evaluated.
|
|
119
|
+
const chosen = allow ?? notEvaluated;
|
|
120
|
+
return { ...chosen, receipts };
|
|
121
|
+
},
|
|
74
122
|
};
|
|
75
123
|
}
|
|
76
124
|
//# sourceMappingURL=runtime.js.map
|
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 } from \"node:fs\";\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\";\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 /** 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 receipt?: unknown;\n}\n\n/** The default starter policy for a coding agent: protect release branches, deny\n * destructive programs, allow workspace file access, and trust only the enrolled\n * machine key. Every threshold is the 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: \"^(?!(?:main|master)$)(?!release/).+\" } },\n description: \"Deny pushes to main, master and release/*.\",\n },\n {\n id: \"safe-shell\", type: \"action_allowlist\", mode: \"enforce\", action_types: [\"shell.exec\"],\n param_bounds: { program: { pattern: \"^(?!(?:rm|sudo|shutdown|reboot|mkfs|dd)$).+\" } },\n description: \"Deny destructive programs (rm, sudo, …).\",\n },\n { id: \"workspace-files\", type: \"action_allowlist\", mode: \"enforce\", action_types: [\"file.write\", \"file.read\"] },\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 = 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 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 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 }\n const gateway = createGateway({ policy, authentication: { keys }, attester, store, mode: \"check_only\" });\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) await flushBounded(exporter, config.cloud?.flushTimeoutMs);\n },\n async evaluate(mapped: Mapped): Promise<Decision> {\n const signed = agent.sign(mapped.intent);\n if (!mapped.evaluated && !config.strict) {\n // Unknown tool, non-strict: observe without 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 tools, and (in strict mode) unmapped tool.<name> actions, go\n // through policy — a closed allowlist denies an unlisted action.\n const result = await gateway.handleAction({ intent: signed.intent, authorization: signed.authorization });\n return { decision: result.allowed ? \"allow\" : \"deny\", reason: result.reason, receipt: result.receipt };\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 } from \"node:fs\";\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\";\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 /** 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 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}\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).\nconst PROTECTED_WRITE = \"^(?!(?:.*/)?\\\\.scopebond/)(?!(?:.*/)?\\\\.claude/settings)(?!(?:.*/)?\\\\.cursor/hooks)(?!(?:.*/)?\\\\.git/hooks/)(?!(?:.*/)?\\\\.github/workflows/)(?!(?:.*/)?\\\\.github/actions/)(?!(?:.*/)?\\\\.gitlab-ci\\\\.yml$)(?!(?:.*/)?\\\\.circleci/)(?!(?:.*/)?azure-pipelines\\\\.yml$)(?!(?:.*/)?Jenkinsfile$).+\";\nconst PROTECTED_READ = \"^(?!(?:.*/)?\\\\.scopebond/)(?!.*\\\\.key$)(?!(?:.*/)?\\\\.env(?:\\\\.(?!example$|sample$|template$)[^/]*)?$).+\";\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: \"^(?!(?:main|master)$)(?!release/).+\" } },\n description: \"Deny pushes to main, master and release/*.\",\n },\n {\n id: \"safe-shell\", type: \"action_allowlist\", mode: \"enforce\", action_types: [\"shell.exec\"],\n param_bounds: { program: { pattern: \"^(?!(?:rm|sudo|shutdown|reboot|mkfs|dd|del|rd|rmdir|erase|deltree|format|Remove-Item|ri)$).+\" } },\n description: \"Deny destructive programs — POSIX (rm, sudo, shutdown, reboot, mkfs, dd) and Windows/PowerShell (del, rd, rmdir, erase, deltree, format, Remove-Item). An empty program (an unparseable command) is denied.\",\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/settings, .cursor/hooks, git hooks, or CI config (.github/workflows, .github/actions, .gitlab-ci.yml, .circleci, azure-pipelines.yml, Jenkinsfile).\",\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 the signing keys (*.key), environment secret files (.env, .env.*, except .env.example/.sample/.template) or the hook's own .scopebond directory.\",\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 = 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 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 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 }\n const gateway = createGateway({ policy, authentication: { keys }, attester, store, mode: \"check_only\" });\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) await flushBounded(exporter, config.cloud?.flushTimeoutMs);\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 return { decision: result.allowed ? \"allow\" : \"deny\", reason: result.reason, receipt: result.receipt };\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[]): Promise<Decision> {\n const list = Array.isArray(mapped) ? mapped : [mapped];\n if (list.length === 0) return { decision: \"not_evaluated\", reason: \"no action\", receipts: [] };\n const receipts: unknown[] = [];\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 if (d.decision === \"deny\") return { ...d, receipts }; // 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 return { ...chosen, receipts };\n },\n };\n}\n"]}
|
package/dist/shell.d.ts
ADDED
|
@@ -0,0 +1,26 @@
|
|
|
1
|
+
export interface SimpleCommand {
|
|
2
|
+
/** The invoked program's basename, after stripping sudo/env/assignment prefixes.
|
|
3
|
+
* Empty when it cannot be determined. */
|
|
4
|
+
program: string;
|
|
5
|
+
/** The first token whole (pre-basename), so a caller can scrub it before splitting
|
|
6
|
+
* on "/": a bare-secret command must not leak a path-fragment as the program. */
|
|
7
|
+
programRaw: string;
|
|
8
|
+
/** Remaining argv (quotes removed), best-effort — used to parse git push. */
|
|
9
|
+
argv: string[];
|
|
10
|
+
/** The raw simple-command text, for the redacted command digest. */
|
|
11
|
+
raw: string;
|
|
12
|
+
/** True when parsing was not confident; the caller must fail closed. */
|
|
13
|
+
opaque: boolean;
|
|
14
|
+
}
|
|
15
|
+
/** Decompose a command line into the simple commands it will run. Recurses into
|
|
16
|
+
* `-c` scripts and command substitutions up to a bounded depth. */
|
|
17
|
+
export declare function decomposeShell(command: string, depth?: number): SimpleCommand[];
|
|
18
|
+
/** Parse a `git … push …` simple command into git.push params, or null if it is
|
|
19
|
+
* not a push. Handles global options that take an argument (`-C dir`, `-c k=v`),
|
|
20
|
+
* `--force`/`-f`/`--force-with-lease`, and a leading `+` force refspec. */
|
|
21
|
+
export declare function parseGitPush(cmd: SimpleCommand): {
|
|
22
|
+
force: boolean;
|
|
23
|
+
remote?: string;
|
|
24
|
+
ref?: string;
|
|
25
|
+
} | null;
|
|
26
|
+
//# sourceMappingURL=shell.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"shell.d.ts","sourceRoot":"","sources":["../src/shell.ts"],"names":[],"mappings":"AAYA,MAAM,WAAW,aAAa;IAC5B;8CAC0C;IAC1C,OAAO,EAAE,MAAM,CAAC;IAChB;sFACkF;IAClF,UAAU,EAAE,MAAM,CAAC;IACnB,6EAA6E;IAC7E,IAAI,EAAE,MAAM,EAAE,CAAC;IACf,oEAAoE;IACpE,GAAG,EAAE,MAAM,CAAC;IACZ,wEAAwE;IACxE,MAAM,EAAE,OAAO,CAAC;CACjB;AAuJD;oEACoE;AACpE,wBAAgB,cAAc,CAAC,OAAO,EAAE,MAAM,EAAE,KAAK,SAAI,GAAG,aAAa,EAAE,CA0B1E;AAED;;4EAE4E;AAC5E,wBAAgB,YAAY,CAAC,GAAG,EAAE,aAAa,GAAG;IAAE,KAAK,EAAE,OAAO,CAAC;IAAC,MAAM,CAAC,EAAE,MAAM,CAAC;IAAC,GAAG,CAAC,EAAE,MAAM,CAAA;CAAE,GAAG,IAAI,CA2BzG"}
|