@scopebond/hook 0.7.0 → 0.8.1
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 -7
- package/dist/cli.d.ts.map +1 -1
- package/dist/cli.js +260 -49
- package/dist/cli.js.map +1 -1
- package/dist/index.d.ts +5 -4
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +3 -3
- package/dist/index.js.map +1 -1
- package/dist/init.d.ts +35 -4
- package/dist/init.d.ts.map +1 -1
- package/dist/init.js +74 -6
- package/dist/init.js.map +1 -1
- package/dist/install.d.ts +29 -4
- package/dist/install.d.ts.map +1 -1
- package/dist/install.js +109 -6
- package/dist/install.js.map +1 -1
- package/dist/minimize.d.ts +11 -3
- package/dist/minimize.d.ts.map +1 -1
- package/dist/minimize.js +35 -6
- package/dist/minimize.js.map +1 -1
- package/package.json +3 -3
package/dist/install.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"install.js","sourceRoot":"","sources":["../src/install.ts"],"names":[],"mappings":"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","sourcesContent":["// SB112 — the user-level installer. One install per developer machine (not per repo):\n// keys and a starter policy live in a user-level home (~/.scopebond, override\n// SCOPEBOND_HOME), and the hook is registered by absolute path in the user-level agent\n// config (~/.claude/settings.json, ~/.cursor/hooks.json, ~/.codex/hooks.json). When a\n// user-level install exists, a project-local `.scopebond` is used only if the user\n// trusted that exact policy (`scopebond trust`, or `init` in the project): a cloned\n// repository, or an agent inside it, must not be able to swap in its own policy.\n\nimport { createHash } from \"node:crypto\";\nimport { mkdirSync, writeFileSync, existsSync, readFileSync, rmSync, copyFileSync } from \"node:fs\";\nimport { homedir } from \"node:os\";\nimport { dirname, join, resolve } from \"node:path\";\n\nexport type Harness = \"claude\" | \"cursor\" | \"codex\";\n\n/** The user-level Scopebond home. `SCOPEBOND_HOME` overrides it (tests, CI). */\nexport function userHome(): string {\n return process.env.SCOPEBOND_HOME ?? join(homedir(), \".scopebond\");\n}\n\n/** The user-level agent config file for a harness. `HOME`/`USERPROFILE` (via homedir)\n * roots it; SCOPEBOND_HOME does not move the agent's own config. */\nexport function userHarnessFile(harness: Harness): string {\n if (harness === \"cursor\") return join(homedir(), \".cursor\", \"hooks.json\");\n if (harness === \"codex\") return join(homedir(), \".codex\", \"hooks.json\");\n return join(homedir(), \".claude\", \"settings.json\");\n}\n\n/** The project-level agent config file for a harness — what `init` writes, so the\n * hook travels with the repository rather than the machine. */\nexport function projectHarnessFile(harness: Harness, cwd: string = process.cwd()): string {\n if (harness === \"cursor\") return join(cwd, \".cursor\", \"hooks.json\");\n if (harness === \"codex\") return join(cwd, \".codex\", \"hooks.json\");\n return join(cwd, \".claude\", \"settings.json\");\n}\n\n/** Which config files actually carry a Scopebond hook for this harness. There are two\n * places it can live — the project config `init` writes and the user config `install`\n * writes — and `status`/`doctor` must look at both: checking only the user one told\n * everyone who ran `init` that their install had failed. */\nexport interface HarnessScopes { project: string | null; user: string | null }\n\nexport function harnessScopes(harness: Harness, cwd: string = process.cwd()): HarnessScopes {\n const project = projectHarnessFile(harness, cwd);\n const user = userHarnessFile(harness);\n return {\n project: isHarnessConfigured(project) ? project : null,\n user: isHarnessConfigured(user) ? user : null,\n };\n}\n\n/** One word for where a harness is wired, for a status line. */\nexport function harnessScopeLabel(scopes: HarnessScopes): string {\n if (scopes.project && scopes.user) return \"configured (project + user)\";\n if (scopes.project) return \"configured (this project)\";\n if (scopes.user) return \"configured (user-level)\";\n return \"\";\n}\n\n/** The file in the user home that pins trusted project policies: absolute project\n * config dir → SHA-256 of its policy.json. It lives under the protected home, so the\n * governed agent cannot write it. */\nexport function trustedProjectsFile(): string {\n return join(userHome(), \"trusted-projects.json\");\n}\n\nfunction policyDigest(dir: string): string | null {\n try { return createHash(\"sha256\").update(readFileSync(join(dir, \"policy.json\"))).digest(\"hex\"); } catch { return null; }\n}\n\nfunction readTrusted(): Record<string, string> {\n try { const p = JSON.parse(readFileSync(trustedProjectsFile(), \"utf8\")); return isRecord(p) ? (p as Record<string, string>) : {}; } catch { return {}; }\n}\n\nconst trustKey = (dir: string): string => resolve(dir).replace(/\\\\/g, \"/\").toLowerCase();\n\n/** Pin the project's current policy as trusted. Editing the policy afterwards un-trusts\n * it until the user trusts it again, so a rewrite by the agent never takes effect. */\nexport function trustProjectPolicy(dir: string): string {\n const digest = policyDigest(dir);\n if (!digest) throw new Error(`no policy at ${join(dir, \"policy.json\")}`);\n const trusted = readTrusted();\n trusted[trustKey(dir)] = digest;\n mkdirSync(userHome(), { recursive: true });\n writeFileSync(trustedProjectsFile(), JSON.stringify(trusted, null, 2) + \"\\n\");\n return digest;\n}\n\n/** Whether the project's policy is exactly the one the user trusted. */\nexport function isTrustedProject(dir: string): boolean {\n const digest = policyDigest(dir);\n return !!digest && readTrusted()[trustKey(dir)] === digest;\n}\n\n/** Resolve the config dir for a hook event, most specific first:\n * explicit override → the payload's project → $CLAUDE_PROJECT_DIR → the user home.\n * A project dir counts only when it has been scaffolded (a policy.json), so a bare\n * working directory never shadows the user-level install — and, once a user-level\n * install exists, only when its policy is trusted (`isTrustedProject`). Otherwise the\n * user's own policy governs: a repository cannot bring a weaker one with it. */\nexport function resolveConfigDir(payloadCwd: string | undefined): string {\n if (process.env.SCOPEBOND_HOOK_DIR) return process.env.SCOPEBOND_HOOK_DIR;\n const candidates: string[] = [];\n if (payloadCwd) candidates.push(join(payloadCwd, \".scopebond\"));\n if (process.env.CLAUDE_PROJECT_DIR) candidates.push(join(process.env.CLAUDE_PROJECT_DIR, \".scopebond\"));\n const home = userHome();\n const homeInstalled = existsSync(join(home, \"policy.json\"));\n for (const dir of candidates) {\n if (existsSync(join(dir, \"policy.json\")) && (!homeInstalled || isTrustedProject(dir))) return dir;\n }\n if (homeInstalled) return home;\n // Nothing scaffolded yet: prefer the payload project, else the user home.\n return candidates[0] ?? home;\n}\n\n/** A project policy present but ignored because it is not trusted (for status/doctor). */\nexport function untrustedProjectPolicy(payloadCwd: string | undefined): string | null {\n if (process.env.SCOPEBOND_HOOK_DIR || !existsSync(join(userHome(), \"policy.json\"))) return null;\n const dirs = [payloadCwd, process.env.CLAUDE_PROJECT_DIR].filter(Boolean).map((d) => join(d as string, \".scopebond\"));\n return dirs.find((d) => existsSync(join(d, \"policy.json\")) && !isTrustedProject(d)) ?? null;\n}\n\nconst isRecord = (v: unknown): v is Record<string, unknown> => typeof v === \"object\" && v !== null && !Array.isArray(v);\n\n/** Whether a harness command string is one of ours. This is the single matcher for\n * every form the installer has ever written, and it is what makes re-running `init`\n * or `install` replace the entry instead of appending a second one:\n * - `npx -y @scopebond/hook@0.6.0 claude` (portable)\n * - `\"<node>\" \"<…/@scopebond/hook/dist/cli.js>\" claude` (pinned, POSIX)\n * - `\"<node>\" \"<…\\@scopebond\\hook\\dist\\cli.js>\" claude` (pinned, Windows)\n * - `scopebond-hook claude` (legacy global binary)\n * The separator class matters: a Windows pinned path spells the scope\n * `@scopebond\\hook`, and matching only `@scopebond/hook` let a second `init`\n * install a duplicate hook — which would double-check every tool call.\n *\n * Written with substring and token tests rather than one regular expression: the\n * previous `(^|[\"\\s])scopebond([\"'\\s]).*\\b(claude|cursor|codex)\\b` backtracked\n * polynomially on a long run of `\\tscopebond\\t` (CodeQL js/polynomial-redos). The\n * input is a command out of a config file rather than a request, so this was not\n * reachable by an attacker — but a linear check is also the clearer one. */\nconst HARNESS_SUBCOMMANDS = new Set([\"claude\", \"cursor\", \"codex\"]);\n/** Long enough for any real command; a bound keeps the work linear and small. */\nconst MAX_COMMAND = 4096;\nconst unquote = (token: string): string => token.replace(/^[\"']+|[\"']+$/g, \"\");\n\nexport const isScopebondHookCommand = (cmd: unknown): boolean => {\n if (typeof cmd !== \"string\" || cmd.length === 0 || cmd.length > MAX_COMMAND) return false;\n const lower = cmd.toLowerCase();\n // The package itself, in either path spelling (`@scopebond/hook`, `@scopebond\\hook`).\n if (lower.includes(\"@scopebond/hook\") || lower.includes(\"@scopebond\\\\hook\")) return true;\n // The legacy global binary, as a whole token.\n if (/(^|[\\s\"'])scopebond-hook([\\s\"']|$)/.test(cmd)) return true;\n // An absolute or bare form whose last argument is one of our harness subcommands:\n // `<node> <…/cli.js> claude`, `scopebond cursor`.\n const tokens = cmd.split(/\\s+/).filter(Boolean);\n const last = unquote(tokens[tokens.length - 1] ?? \"\");\n if (!HARNESS_SUBCOMMANDS.has(last)) return false;\n return tokens.some((token) => {\n const t = unquote(token).toLowerCase();\n return t.includes(\"scopebond\") || /cli\\.(m|c)?js$/.test(t);\n });\n};\nconst isScopebond = isScopebondHookCommand;\n\n/** Whether a harness hook entry (either shape: a bare command, or a group with a\n * nested `hooks` array) is a Scopebond entry. */\nexport const harnessEntryMatches = (e: unknown): boolean =>\n isRecord(e) && (isScopebond(e.command) || (Array.isArray(e.hooks) && e.hooks.some((h) => isRecord(h) && isScopebond(h.command))));\nconst entryMatches = harnessEntryMatches;\n\n/** Read an agent's JSON config for merging. A missing file is an empty config; a file\n * that exists but does not parse as a JSON object is an error — rewriting it would\n * silently delete the user's other settings, so the installer stops instead. */\nexport function readHarnessConfig(file: string): Record<string, unknown> {\n if (!existsSync(file)) return {};\n const text = readFileSync(file, \"utf8\");\n if (!text.trim()) return {};\n let parsed: unknown;\n try { parsed = JSON.parse(text); } catch (error) {\n throw new Error(`${file} is not valid JSON (${(error as Error).message}); fix or move it, then run this again — it was left unchanged`);\n }\n if (!isRecord(parsed)) throw new Error(`${file} is not a JSON object; fix or move it, then run this again — it was left unchanged`);\n return parsed;\n}\n\n/** Copy an agent config aside before changing it, once. Re-running an installer must not\n * overwrite the backup with the already-modified file, or the original is lost on the\n * second run — so an existing backup is left alone. Returns the backup path when one was\n * written.\n *\n * This exists because these installers rewrite files the user did not create and may not\n * know are involved (`~/.claude/settings.json` holds their theme, plugins and\n * permissions), and the undo was previously \"hope the merge was right\". */\nexport function backupHarnessConfig(file: string): string | null {\n if (!existsSync(file)) return null;\n const backup = `${file}.scopebond-backup`;\n if (existsSync(backup)) return null;\n try { copyFileSync(file, backup); return backup; } catch { return null; }\n}\n\n/** Write (idempotently) a harness hook entry pointing at an explicit command string.\n * Shared by the project installer (npx command) and the user installer (absolute path). */\nexport function writeHarnessConfig(file: string, harness: Harness, command: string): string {\n const config = readHarnessConfig(file);\n backupHarnessConfig(file);\n mkdirSync(dirname(file), { recursive: true });\n const hooks = isRecord(config.hooks) ? config.hooks : (config.hooks = {});\n if (harness === \"cursor\") {\n config.version = config.version ?? 1;\n for (const event of [\"beforeShellExecution\", \"beforeMCPExecution\", \"beforeReadFile\", \"afterFileEdit\"]) {\n const list = Array.isArray((hooks as Record<string, unknown>)[event]) ? (hooks as Record<string, unknown[]>)[event] : ((hooks as Record<string, unknown[]>)[event] = []);\n const at = list.findIndex(entryMatches);\n const entry = { command: command.replace(/\\bclaude$/, \"cursor\") };\n if (at >= 0) list[at] = entry; else list.push(entry);\n }\n } else {\n const list = Array.isArray((hooks as Record<string, unknown>).PreToolUse) ? (hooks as Record<string, unknown[]>).PreToolUse : ((hooks as Record<string, unknown[]>).PreToolUse = []);\n const at = list.findIndex(entryMatches);\n // Codex matchers are regular expressions; omitting the matcher means every tool.\n // A literal \"*\" is not a valid regular expression and causes Codex to skip the group.\n const entry = harness === \"codex\"\n ? { hooks: [{ type: \"command\", command, timeout: 30, statusMessage: \"Checking this action with Scopebond\" }] }\n : { matcher: \"*\", hooks: [{ type: \"command\", command }] };\n if (at >= 0) list[at] = entry; else list.push(entry);\n }\n writeFileSync(file, JSON.stringify(config, null, 2) + \"\\n\");\n return file;\n}\n\n/** Remove any Scopebond hook entry from a user-level harness config, leaving the rest\n * intact. Returns true if the file existed. */\nexport function removeHarnessConfig(file: string): boolean {\n if (!existsSync(file)) return false;\n let config: Record<string, unknown>;\n try { config = readHarnessConfig(file); } catch { return true; } // unreadable: leave it untouched\n const hooks = isRecord(config.hooks) ? config.hooks : {};\n for (const [event, value] of Object.entries(hooks)) {\n if (Array.isArray(value)) (hooks as Record<string, unknown[]>)[event] = value.filter((e) => !entryMatches(e));\n }\n writeFileSync(file, JSON.stringify(config, null, 2) + \"\\n\");\n return true;\n}\n\n/** Detect a Cursor install by its user config directory. */\nexport function cursorDetected(): boolean {\n return existsSync(join(homedir(), \".cursor\"));\n}\n\n/** Detect a Codex install by its user config directory. */\nexport function codexDetected(): boolean {\n return existsSync(join(homedir(), \".codex\"));\n}\n\n/** The absolute command a harness entry runs: the current Node executable and the\n * absolute path to this CLI, so it does not depend on PATH.\n *\n * Both paths are always quoted on Windows, even without a space in them. A harness\n * runs this string through a shell, and that shell may be bash (Git Bash, WSL, a\n * dev container), where an unquoted Windows path loses every backslash and the\n * hook dies with MODULE_NOT_FOUND. Inside double quotes bash leaves a backslash\n * alone unless it precedes $ ` \" \\ or a newline — none of which occur in a Windows\n * path — and cmd.exe and PowerShell accept quoted paths too. */\nexport function absoluteHookCommand(cliPath: string, harness: Harness): string {\n const quote = (s: string) => (process.platform === \"win32\" || /\\s/.test(s) ? `\"${s}\"` : s);\n return `${quote(process.execPath)} ${quote(cliPath)} ${harness}`;\n}\n\nexport function isHarnessConfigured(file: string): boolean {\n if (!existsSync(file)) return false;\n try {\n const p = JSON.parse(readFileSync(file, \"utf8\"));\n if (!isRecord(p) || !isRecord(p.hooks)) return false;\n return Object.values(p.hooks).some((v) => Array.isArray(v) && v.some(entryMatches));\n } catch { return false; }\n}\n\n/** Every Scopebond command string a harness config currently runs. `doctor` uses it to\n * check that a pinned command still resolves: a hook entry that cannot start is worse\n * than none, because a harness can read the failure as \"no hook\". */\nexport function configuredHookCommands(file: string): string[] {\n if (!existsSync(file)) return [];\n const found: string[] = [];\n const collect = (entry: unknown): void => {\n if (!isRecord(entry)) return;\n if (isScopebond(entry.command)) found.push(String(entry.command));\n if (Array.isArray(entry.hooks)) for (const h of entry.hooks) collect(h);\n };\n try {\n const p = JSON.parse(readFileSync(file, \"utf8\"));\n if (!isRecord(p) || !isRecord(p.hooks)) return [];\n for (const list of Object.values(p.hooks)) if (Array.isArray(list)) for (const e of list) collect(e);\n } catch { return []; }\n return found;\n}\n\n/** Whether a configured hook command can actually start. An `npx` form always can\n * (npm resolves it); a pinned command names two absolute paths — the Node binary and\n * the CLI — and either can go away: a Node version manager switching versions, or the\n * Scopebond home being cleared. Both are exactly what this check exists to catch. */\nexport function hookCommandResolves(command: string): boolean {\n if (/(^|\\s)npx(\\.cmd)?\\s/.test(command)) return true;\n // `\"<node>\" \"<cli.js>\" claude`: check every token that names a file on disk.\n const tokens = command.match(/\"[^\"]+\"|\\S+/g) ?? [];\n let checked = 0;\n for (const raw of tokens) {\n const candidate = raw.replace(/^\"|\"$/g, \"\");\n if (!/\\.(m|c)?js$|\\.exe$/i.test(candidate)) continue;\n checked += 1;\n if (!existsSync(candidate)) return false;\n }\n // A bare `node cli.js` form on POSIX has no .exe to check; the .js still counted.\n return checked > 0 || !/[\\\\/]/.test(command.split(/\\s+/)[0] ?? \"\");\n}\n\n/** Remove the user-level home (keys, policy, receipts). */\nexport function purgeHome(): void {\n rmSync(userHome(), { recursive: true, force: true });\n}\n"]}
|
|
1
|
+
{"version":3,"file":"install.js","sourceRoot":"","sources":["../src/install.ts"],"names":[],"mappings":"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","sourcesContent":["// SB112 — the user-level installer. One install per developer machine (not per repo):\n// keys and a starter policy live in a user-level home (~/.scopebond, override\n// SCOPEBOND_HOME), and the hook is registered by absolute path in the user-level agent\n// config (~/.claude/settings.json, ~/.cursor/hooks.json, ~/.codex/hooks.json). When a\n// user-level install exists, a project-local `.scopebond` is used only if the user\n// trusted that exact policy (`scopebond trust`, or `init` in the project): a cloned\n// repository, or an agent inside it, must not be able to swap in its own policy.\n\nimport { execFileSync } from \"node:child_process\";\nimport { createHash } from \"node:crypto\";\nimport { mkdirSync, writeFileSync, existsSync, readFileSync, rmSync, copyFileSync } from \"node:fs\";\nimport { homedir } from \"node:os\";\nimport { basename, dirname, isAbsolute, join, resolve } from \"node:path\";\n\nexport type Harness = \"claude\" | \"cursor\" | \"codex\";\n\n/** The user-level Scopebond home. `SCOPEBOND_HOME` overrides it (tests, CI). */\nexport function userHome(): string {\n return process.env.SCOPEBOND_HOME ?? join(homedir(), \".scopebond\");\n}\n\n/** The user-level agent config file for a harness. `HOME`/`USERPROFILE` (via homedir)\n * roots it; SCOPEBOND_HOME does not move the agent's own config. */\nexport function userHarnessFile(harness: Harness): string {\n if (harness === \"cursor\") return join(homedir(), \".cursor\", \"hooks.json\");\n if (harness === \"codex\") return join(homedir(), \".codex\", \"hooks.json\");\n return join(homedir(), \".claude\", \"settings.json\");\n}\n\n/** The project-level agent config file for a harness — what `init` writes, so the\n * hook travels with the repository rather than the machine. */\nexport function projectHarnessFile(harness: Harness, cwd: string = process.cwd()): string {\n if (harness === \"cursor\") return join(cwd, \".cursor\", \"hooks.json\");\n if (harness === \"codex\") return join(cwd, \".codex\", \"hooks.json\");\n return join(cwd, \".claude\", \"settings.json\");\n}\n\n/** The personal, per-machine project config for a harness, or null when the harness\n * has none. Claude Code reads `.claude/settings.local.json` beside `settings.json` and\n * merges the hooks of both; it is the place for anything that names a path on this\n * machine. Cursor and Codex have no such file. */\nexport function localHarnessFile(harness: Harness, cwd: string = process.cwd()): string | null {\n return harness === \"claude\" ? join(cwd, \".claude\", \"settings.local.json\") : null;\n}\n\n/** Which config files actually carry a Scopebond hook for this harness. There are three\n * places it can live — the shared project config, the personal project config (Claude\n * Code's `settings.local.json`, where `init` pins the fast command) and the user config\n * `install` writes — and `status`/`doctor` must look at all of them: checking only the\n * user one told everyone who ran `init` that their install had failed. */\nexport interface HarnessScopes { project: string | null; local: string | null; user: string | null }\n\nexport function harnessScopes(harness: Harness, cwd: string = process.cwd()): HarnessScopes {\n const project = projectHarnessFile(harness, cwd);\n const local = localHarnessFile(harness, cwd);\n const user = userHarnessFile(harness);\n return {\n project: isHarnessConfigured(project) ? project : null,\n local: local && isHarnessConfigured(local) ? local : null,\n user: isHarnessConfigured(user) ? user : null,\n };\n}\n\n/** One word for where a harness is wired, for a status line. */\nexport function harnessScopeLabel(scopes: HarnessScopes): string {\n const where = [\n scopes.project ? \"project\" : \"\",\n scopes.local ? \"this machine\" : \"\",\n scopes.user ? \"user\" : \"\",\n ].filter(Boolean);\n if (where.length === 0) return \"\";\n if (where.length === 1 && scopes.project) return \"configured (this project)\";\n if (where.length === 1 && scopes.local) return \"configured (this project, this machine only)\";\n if (where.length === 1 && scopes.user) return \"configured (user-level)\";\n return `configured (${where.join(\" + \")})`;\n}\n\n/** Whether a hook command names a path that exists only on this machine — the pinned\n * `\"<node>\" \"<…/cli.js>\" claude` form — rather than the portable `npx` form. */\nexport function isMachineSpecificCommand(command: string): boolean {\n if (/(^|\\s)npx(\\.cmd)?\\s/.test(command)) return false;\n const first = (command.match(/\"[^\"]+\"|\\S+/) ?? [\"\"])[0].replace(/^\"|\"$/g, \"\");\n return isAbsolute(first) || /^[A-Za-z]:[\\\\/]/.test(first);\n}\n\n/** How git treats a config file: committed (`tracked`), kept out (`ignored`), neither\n * yet (`untracked`, so a `git add .` would commit it), or not in a repository / no git\n * on this machine (`none`). A file git shares must never carry a machine-specific\n * command: on a teammate's machine the path does not exist, the command cannot start,\n * and Claude Code, Cursor and Codex all treat a hook that cannot start as a non-blocking\n * error — the agent runs with no check at all. */\nexport type GitShare = \"tracked\" | \"ignored\" | \"untracked\" | \"none\";\n\nexport function gitShareState(file: string): GitShare {\n const cwd = dirname(file);\n const git = (args: string[]): number => {\n try {\n mkdirSync(cwd, { recursive: true });\n execFileSync(\"git\", args, { cwd, stdio: \"ignore\", timeout: 5000 });\n return 0;\n } catch (error) {\n const status = (error as { status?: number | null }).status;\n return typeof status === \"number\" ? status : -1;\n }\n };\n if (git([\"rev-parse\", \"--is-inside-work-tree\"]) !== 0) return \"none\";\n if (git([\"ls-files\", \"--error-unmatch\", \"--\", file]) === 0) return \"tracked\";\n return git([\"check-ignore\", \"-q\", \"--\", file]) === 0 ? \"ignored\" : \"untracked\";\n}\n\n/** Keep a file out of git for this clone only, through `.git/info/exclude` — never the\n * repository's own `.gitignore`, which is the team's file. Returns true when the file is\n * ignored afterwards. */\nexport function excludeFromGit(file: string): boolean {\n try {\n const cwd = dirname(file);\n mkdirSync(cwd, { recursive: true });\n // The entry is built from git's own view of where this directory sits in the work\n // tree (`--show-prefix`), not from comparing file-system paths: the same directory\n // can be spelled two ways (a Windows 8.3 short name, a symlinked temp dir), and a\n // path computed across the two spellings excludes the wrong file.\n const prefix = execFileSync(\"git\", [\"rev-parse\", \"--show-prefix\"], { cwd, encoding: \"utf8\", timeout: 5000 }).trim();\n const exclude = resolve(cwd, execFileSync(\"git\", [\"rev-parse\", \"--git-path\", \"info/exclude\"], { cwd, encoding: \"utf8\", timeout: 5000 }).trim());\n const entry = \"/\" + prefix + basename(file);\n const existing = existsSync(exclude) ? readFileSync(exclude, \"utf8\") : \"\";\n if (!existing.split(/\\r?\\n/).includes(entry)) {\n mkdirSync(dirname(exclude), { recursive: true });\n writeFileSync(exclude, `${existing}${existing && !existing.endsWith(\"\\n\") ? \"\\n\" : \"\"}${entry}\\n`);\n }\n return gitShareState(file) === \"ignored\";\n } catch { return false; }\n}\n\n/** Remove the Scopebond entries whose command `remove` selects, leaving every other\n * setting alone. Returns the number of entries removed. */\nexport function pruneHarnessEntries(file: string, remove: (command: string) => boolean): number {\n if (!existsSync(file)) return 0;\n let config: Record<string, unknown>;\n try { config = readHarnessConfig(file); } catch { return 0; }\n const hooks = isRecord(config.hooks) ? config.hooks : null;\n if (!hooks) return 0;\n let removed = 0;\n const commandsOf = (e: unknown): string[] => {\n if (!isRecord(e)) return [];\n const own = isScopebond(e.command) ? [String(e.command)] : [];\n const nested = Array.isArray(e.hooks) ? e.hooks.flatMap(commandsOf) : [];\n return [...own, ...nested];\n };\n for (const [event, value] of Object.entries(hooks)) {\n if (!Array.isArray(value)) continue;\n const kept = value.filter((e) => !(entryMatches(e) && commandsOf(e).some(remove)));\n removed += value.length - kept.length;\n (hooks as Record<string, unknown[]>)[event] = kept;\n }\n if (removed > 0) writeFileSync(file, JSON.stringify(config, null, 2) + \"\\n\");\n return removed;\n}\n\n/** The file in the user home that pins trusted project policies: absolute project\n * config dir → SHA-256 of its policy.json. It lives under the protected home, so the\n * governed agent cannot write it. */\nexport function trustedProjectsFile(): string {\n return join(userHome(), \"trusted-projects.json\");\n}\n\nfunction policyDigest(dir: string): string | null {\n try { return createHash(\"sha256\").update(readFileSync(join(dir, \"policy.json\"))).digest(\"hex\"); } catch { return null; }\n}\n\nfunction readTrusted(): Record<string, string> {\n try { const p = JSON.parse(readFileSync(trustedProjectsFile(), \"utf8\")); return isRecord(p) ? (p as Record<string, string>) : {}; } catch { return {}; }\n}\n\nconst trustKey = (dir: string): string => resolve(dir).replace(/\\\\/g, \"/\").toLowerCase();\n\n/** Pin the project's current policy as trusted. Editing the policy afterwards un-trusts\n * it until the user trusts it again, so a rewrite by the agent never takes effect. */\nexport function trustProjectPolicy(dir: string): string {\n const digest = policyDigest(dir);\n if (!digest) throw new Error(`no policy at ${join(dir, \"policy.json\")}`);\n const trusted = readTrusted();\n trusted[trustKey(dir)] = digest;\n mkdirSync(userHome(), { recursive: true });\n writeFileSync(trustedProjectsFile(), JSON.stringify(trusted, null, 2) + \"\\n\");\n return digest;\n}\n\n/** Whether the project's policy is exactly the one the user trusted. */\nexport function isTrustedProject(dir: string): boolean {\n const digest = policyDigest(dir);\n return !!digest && readTrusted()[trustKey(dir)] === digest;\n}\n\n/** Resolve the config dir for a hook event, most specific first:\n * explicit override → the payload's project → $CLAUDE_PROJECT_DIR → the user home.\n * A project dir counts only when it has been scaffolded (a policy.json), so a bare\n * working directory never shadows the user-level install — and, once a user-level\n * install exists, only when its policy is trusted (`isTrustedProject`). Otherwise the\n * user's own policy governs: a repository cannot bring a weaker one with it. */\nexport function resolveConfigDir(payloadCwd: string | undefined): string {\n if (process.env.SCOPEBOND_HOOK_DIR) return process.env.SCOPEBOND_HOOK_DIR;\n const candidates: string[] = [];\n if (payloadCwd) candidates.push(join(payloadCwd, \".scopebond\"));\n if (process.env.CLAUDE_PROJECT_DIR) candidates.push(join(process.env.CLAUDE_PROJECT_DIR, \".scopebond\"));\n const home = userHome();\n const homeInstalled = existsSync(join(home, \"policy.json\"));\n for (const dir of candidates) {\n if (existsSync(join(dir, \"policy.json\")) && (!homeInstalled || isTrustedProject(dir))) return dir;\n }\n if (homeInstalled) return home;\n // Nothing scaffolded yet: prefer the payload project, else the user home.\n return candidates[0] ?? home;\n}\n\n/** A project policy present but ignored because it is not trusted (for status/doctor). */\nexport function untrustedProjectPolicy(payloadCwd: string | undefined): string | null {\n if (process.env.SCOPEBOND_HOOK_DIR || !existsSync(join(userHome(), \"policy.json\"))) return null;\n const dirs = [payloadCwd, process.env.CLAUDE_PROJECT_DIR].filter(Boolean).map((d) => join(d as string, \".scopebond\"));\n return dirs.find((d) => existsSync(join(d, \"policy.json\")) && !isTrustedProject(d)) ?? null;\n}\n\nconst isRecord = (v: unknown): v is Record<string, unknown> => typeof v === \"object\" && v !== null && !Array.isArray(v);\n\n/** Whether a harness command string is one of ours. This is the single matcher for\n * every form the installer has ever written, and it is what makes re-running `init`\n * or `install` replace the entry instead of appending a second one:\n * - `npx -y @scopebond/hook@0.6.0 claude` (portable)\n * - `\"<node>\" \"<…/@scopebond/hook/dist/cli.js>\" claude` (pinned, POSIX)\n * - `\"<node>\" \"<…\\@scopebond\\hook\\dist\\cli.js>\" claude` (pinned, Windows)\n * - `scopebond-hook claude` (legacy global binary)\n * The separator class matters: a Windows pinned path spells the scope\n * `@scopebond\\hook`, and matching only `@scopebond/hook` let a second `init`\n * install a duplicate hook — which would double-check every tool call.\n *\n * Written with substring and token tests rather than one regular expression: the\n * previous `(^|[\"\\s])scopebond([\"'\\s]).*\\b(claude|cursor|codex)\\b` backtracked\n * polynomially on a long run of `\\tscopebond\\t` (CodeQL js/polynomial-redos). The\n * input is a command out of a config file rather than a request, so this was not\n * reachable by an attacker — but a linear check is also the clearer one. */\nconst HARNESS_SUBCOMMANDS = new Set([\"claude\", \"cursor\", \"codex\"]);\n/** Long enough for any real command; a bound keeps the work linear and small. */\nconst MAX_COMMAND = 4096;\nconst unquote = (token: string): string => token.replace(/^[\"']+|[\"']+$/g, \"\");\n\nexport const isScopebondHookCommand = (cmd: unknown): boolean => {\n if (typeof cmd !== \"string\" || cmd.length === 0 || cmd.length > MAX_COMMAND) return false;\n const lower = cmd.toLowerCase();\n // The package itself, in either path spelling (`@scopebond/hook`, `@scopebond\\hook`).\n if (lower.includes(\"@scopebond/hook\") || lower.includes(\"@scopebond\\\\hook\")) return true;\n // The legacy global binary, as a whole token.\n if (/(^|[\\s\"'])scopebond-hook([\\s\"']|$)/.test(cmd)) return true;\n // An absolute or bare form whose last argument is one of our harness subcommands:\n // `<node> <…/cli.js> claude`, `scopebond cursor`.\n const tokens = cmd.split(/\\s+/).filter(Boolean);\n const last = unquote(tokens[tokens.length - 1] ?? \"\");\n if (!HARNESS_SUBCOMMANDS.has(last)) return false;\n return tokens.some((token) => {\n const t = unquote(token).toLowerCase();\n return t.includes(\"scopebond\") || /cli\\.(m|c)?js$/.test(t);\n });\n};\nconst isScopebond = isScopebondHookCommand;\n\n/** Whether a harness hook entry (either shape: a bare command, or a group with a\n * nested `hooks` array) is a Scopebond entry. */\nexport const harnessEntryMatches = (e: unknown): boolean =>\n isRecord(e) && (isScopebond(e.command) || (Array.isArray(e.hooks) && e.hooks.some((h) => isRecord(h) && isScopebond(h.command))));\nconst entryMatches = harnessEntryMatches;\n\n/** Read an agent's JSON config for merging. A missing file is an empty config; a file\n * that exists but does not parse as a JSON object is an error — rewriting it would\n * silently delete the user's other settings, so the installer stops instead. */\nexport function readHarnessConfig(file: string): Record<string, unknown> {\n if (!existsSync(file)) return {};\n const text = readFileSync(file, \"utf8\");\n if (!text.trim()) return {};\n let parsed: unknown;\n try { parsed = JSON.parse(text); } catch (error) {\n throw new Error(`${file} is not valid JSON (${(error as Error).message}); fix or move it, then run this again — it was left unchanged`);\n }\n if (!isRecord(parsed)) throw new Error(`${file} is not a JSON object; fix or move it, then run this again — it was left unchanged`);\n return parsed;\n}\n\n/** Copy an agent config aside before changing it, once. Re-running an installer must not\n * overwrite the backup with the already-modified file, or the original is lost on the\n * second run — so an existing backup is left alone. Returns the backup path when one was\n * written.\n *\n * This exists because these installers rewrite files the user did not create and may not\n * know are involved (`~/.claude/settings.json` holds their theme, plugins and\n * permissions), and the undo was previously \"hope the merge was right\". */\nexport function backupHarnessConfig(file: string): string | null {\n if (!existsSync(file)) return null;\n const backup = `${file}.scopebond-backup`;\n if (existsSync(backup)) return null;\n try { copyFileSync(file, backup); return backup; } catch { return null; }\n}\n\n/** Write (idempotently) a harness hook entry pointing at an explicit command string.\n * Shared by the project installer (npx command) and the user installer (absolute path). */\nexport function writeHarnessConfig(file: string, harness: Harness, command: string): string {\n const config = readHarnessConfig(file);\n backupHarnessConfig(file);\n mkdirSync(dirname(file), { recursive: true });\n const hooks = isRecord(config.hooks) ? config.hooks : (config.hooks = {});\n if (harness === \"cursor\") {\n config.version = config.version ?? 1;\n for (const event of [\"beforeShellExecution\", \"beforeMCPExecution\", \"beforeReadFile\", \"afterFileEdit\"]) {\n const list = Array.isArray((hooks as Record<string, unknown>)[event]) ? (hooks as Record<string, unknown[]>)[event] : ((hooks as Record<string, unknown[]>)[event] = []);\n const at = list.findIndex(entryMatches);\n const entry = { command: command.replace(/\\bclaude$/, \"cursor\") };\n if (at >= 0) list[at] = entry; else list.push(entry);\n }\n } else {\n const list = Array.isArray((hooks as Record<string, unknown>).PreToolUse) ? (hooks as Record<string, unknown[]>).PreToolUse : ((hooks as Record<string, unknown[]>).PreToolUse = []);\n const at = list.findIndex(entryMatches);\n // Codex matchers are regular expressions; omitting the matcher means every tool.\n // A literal \"*\" is not a valid regular expression and causes Codex to skip the group.\n const entry = harness === \"codex\"\n ? { hooks: [{ type: \"command\", command, timeout: 30, statusMessage: \"Checking this action with Scopebond\" }] }\n : { matcher: \"*\", hooks: [{ type: \"command\", command }] };\n if (at >= 0) list[at] = entry; else list.push(entry);\n }\n writeFileSync(file, JSON.stringify(config, null, 2) + \"\\n\");\n return file;\n}\n\n/** Remove any Scopebond hook entry from a user-level harness config, leaving the rest\n * intact. Returns true if the file existed. */\nexport function removeHarnessConfig(file: string): boolean {\n if (!existsSync(file)) return false;\n let config: Record<string, unknown>;\n try { config = readHarnessConfig(file); } catch { return true; } // unreadable: leave it untouched\n const hooks = isRecord(config.hooks) ? config.hooks : {};\n for (const [event, value] of Object.entries(hooks)) {\n if (Array.isArray(value)) (hooks as Record<string, unknown[]>)[event] = value.filter((e) => !entryMatches(e));\n }\n writeFileSync(file, JSON.stringify(config, null, 2) + \"\\n\");\n return true;\n}\n\n/** Detect a Cursor install by its user config directory. */\nexport function cursorDetected(): boolean {\n return existsSync(join(homedir(), \".cursor\"));\n}\n\n/** Detect a Codex install by its user config directory. */\nexport function codexDetected(): boolean {\n return existsSync(join(homedir(), \".codex\"));\n}\n\n/** The absolute command a harness entry runs: the current Node executable and the\n * absolute path to this CLI, so it does not depend on PATH.\n *\n * Both paths are always quoted on Windows, even without a space in them. A harness\n * runs this string through a shell, and that shell may be bash (Git Bash, WSL, a\n * dev container), where an unquoted Windows path loses every backslash and the\n * hook dies with MODULE_NOT_FOUND. Inside double quotes bash leaves a backslash\n * alone unless it precedes $ ` \" \\ or a newline — none of which occur in a Windows\n * path — and cmd.exe and PowerShell accept quoted paths too. */\nexport function absoluteHookCommand(cliPath: string, harness: Harness): string {\n const quote = (s: string) => (process.platform === \"win32\" || /\\s/.test(s) ? `\"${s}\"` : s);\n return `${quote(process.execPath)} ${quote(cliPath)} ${harness}`;\n}\n\nexport function isHarnessConfigured(file: string): boolean {\n if (!existsSync(file)) return false;\n try {\n const p = JSON.parse(readFileSync(file, \"utf8\"));\n if (!isRecord(p) || !isRecord(p.hooks)) return false;\n return Object.values(p.hooks).some((v) => Array.isArray(v) && v.some(entryMatches));\n } catch { return false; }\n}\n\n/** Every Scopebond command string a harness config currently runs. `doctor` uses it to\n * check that a pinned command still resolves: a hook entry that cannot start is worse\n * than none, because a harness can read the failure as \"no hook\". */\nexport function configuredHookCommands(file: string): string[] {\n if (!existsSync(file)) return [];\n const found: string[] = [];\n const collect = (entry: unknown): void => {\n if (!isRecord(entry)) return;\n if (isScopebond(entry.command)) found.push(String(entry.command));\n if (Array.isArray(entry.hooks)) for (const h of entry.hooks) collect(h);\n };\n try {\n const p = JSON.parse(readFileSync(file, \"utf8\"));\n if (!isRecord(p) || !isRecord(p.hooks)) return [];\n for (const list of Object.values(p.hooks)) if (Array.isArray(list)) for (const e of list) collect(e);\n } catch { return []; }\n return found;\n}\n\n/** Whether a configured hook command can actually start. An `npx` form always can\n * (npm resolves it); a pinned command names two absolute paths — the Node binary and\n * the CLI — and either can go away: a Node version manager switching versions, or the\n * Scopebond home being cleared. Both are exactly what this check exists to catch. */\nexport function hookCommandResolves(command: string): boolean {\n if (/(^|\\s)npx(\\.cmd)?\\s/.test(command)) return true;\n // `\"<node>\" \"<cli.js>\" claude`: check every token that names a file on disk.\n const tokens = command.match(/\"[^\"]+\"|\\S+/g) ?? [];\n let checked = 0;\n for (const raw of tokens) {\n const candidate = raw.replace(/^\"|\"$/g, \"\");\n if (!/\\.(m|c)?js$|\\.exe$/i.test(candidate)) continue;\n checked += 1;\n if (!existsSync(candidate)) return false;\n }\n // A bare `node cli.js` form on POSIX has no .exe to check; the .js still counted.\n return checked > 0 || !/[\\\\/]/.test(command.split(/\\s+/)[0] ?? \"\");\n}\n\n/** Remove the user-level home (keys, policy, receipts). */\nexport function purgeHome(): void {\n rmSync(userHome(), { recursive: true, force: true });\n}\n"]}
|
package/dist/minimize.d.ts
CHANGED
|
@@ -1,4 +1,12 @@
|
|
|
1
|
+
/** A plain SHA-256 content hash, for values that are not secret. */
|
|
1
2
|
export declare const sha256: (value: string) => string;
|
|
3
|
+
/** Set the key for keyed digests (32 random bytes, hex). Without one, a random key for
|
|
4
|
+
* this process is used — safe, but digests then compare only within the process. */
|
|
5
|
+
export declare function useDigestKey(key: string | null): void;
|
|
6
|
+
/** The per-machine digest key in a config dir, created on first use. */
|
|
7
|
+
export declare function loadOrCreateDigestKey(dir: string): string;
|
|
8
|
+
/** HMAC-SHA-256 under the digest key, labelled so it is never mistaken for a plain hash. */
|
|
9
|
+
export declare const keyedDigest: (value: string) => string;
|
|
2
10
|
export declare const digest: (value: unknown) => string;
|
|
3
11
|
/** Scrub a structured parameter (a program name, a remote, a URL path): credential
|
|
4
12
|
* assignments and recognizable token shapes only, so policy matching on ordinary
|
|
@@ -8,8 +16,8 @@ export declare function scrubParam(text: string): string;
|
|
|
8
16
|
* header shapes and generic high-entropy blobs. */
|
|
9
17
|
export declare function scrubSecrets(text: string): string;
|
|
10
18
|
/** A privacy-preserving representation of a shell command: a scrubbed, truncated head
|
|
11
|
-
* plus a digest of the scrubbed command. The full original is never retained
|
|
12
|
-
* digest is taken over the scrubbed text so a secret the head removed
|
|
13
|
-
*
|
|
19
|
+
* plus a keyed digest of the scrubbed command. The full original is never retained; the
|
|
20
|
+
* digest is taken over the scrubbed text so a secret the head removed is not in it, and
|
|
21
|
+
* it is keyed so a secret the scrubber missed cannot be guessed back from it. */
|
|
14
22
|
export declare function redactCommand(command: string, headLen?: number): string;
|
|
15
23
|
//# sourceMappingURL=minimize.d.ts.map
|
package/dist/minimize.d.ts.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"minimize.d.ts","sourceRoot":"","sources":["../src/minimize.ts"],"names":[],"mappings":"
|
|
1
|
+
{"version":3,"file":"minimize.d.ts","sourceRoot":"","sources":["../src/minimize.ts"],"names":[],"mappings":"AASA,oEAAoE;AACpE,eAAO,MAAM,MAAM,GAAI,OAAO,MAAM,KAAG,MAAsE,CAAC;AAU9G;qFACqF;AACrF,wBAAgB,YAAY,CAAC,GAAG,EAAE,MAAM,GAAG,IAAI,GAAG,IAAI,CAErD;AAED,wEAAwE;AACxE,wBAAgB,qBAAqB,CAAC,GAAG,EAAE,MAAM,GAAG,MAAM,CASzD;AAED,4FAA4F;AAC5F,eAAO,MAAM,WAAW,GAAI,OAAO,MAAM,KAAG,MACwD,CAAC;AACrG,eAAO,MAAM,MAAM,GAAI,OAAO,OAAO,KAAG,MAAgD,CAAC;AAmEzF;;4BAE4B;AAC5B,wBAAgB,UAAU,CAAC,IAAI,EAAE,MAAM,GAAG,MAAM,CAI/C;AAED;oDACoD;AACpD,wBAAgB,YAAY,CAAC,IAAI,EAAE,MAAM,GAAG,MAAM,CAKjD;AAED;;;kFAGkF;AAClF,wBAAgB,aAAa,CAAC,OAAO,EAAE,MAAM,EAAE,OAAO,SAAK,GAAG,MAAM,CAInE"}
|
package/dist/minimize.js
CHANGED
|
@@ -1,10 +1,39 @@
|
|
|
1
1
|
// Data minimization for the hook: file contents are never stored, shell commands
|
|
2
2
|
// are reduced to a scrubbed head plus a digest, and common secret shapes are
|
|
3
3
|
// removed before anything is written or signed (SB09).
|
|
4
|
-
import { createHash } from "node:crypto";
|
|
4
|
+
import { createHash, createHmac, randomBytes } from "node:crypto";
|
|
5
|
+
import { existsSync, readFileSync, writeFileSync } from "node:fs";
|
|
6
|
+
import { join } from "node:path";
|
|
5
7
|
import { canonical } from "@scopebond/policy-schema/canonical";
|
|
8
|
+
/** A plain SHA-256 content hash, for values that are not secret. */
|
|
6
9
|
export const sha256 = (value) => "sha256:" + createHash("sha256").update(value).digest("hex");
|
|
7
|
-
|
|
10
|
+
// Digests of what an agent ran — a scrubbed shell command, an MCP tool's arguments — are
|
|
11
|
+
// keyed. The scrubber cannot recognise every secret, and a plain hash of a command whose
|
|
12
|
+
// head is shown beside it leaves only the unseen remainder to guess: a short password the
|
|
13
|
+
// scrubber missed could be recovered offline by anyone holding the receipt. Keyed with a
|
|
14
|
+
// per-machine secret that never leaves `.scopebond/`, the digest still tells two identical
|
|
15
|
+
// actions on this machine apart from different ones, but cannot be tested against guesses.
|
|
16
|
+
let digestKey = null;
|
|
17
|
+
/** Set the key for keyed digests (32 random bytes, hex). Without one, a random key for
|
|
18
|
+
* this process is used — safe, but digests then compare only within the process. */
|
|
19
|
+
export function useDigestKey(key) {
|
|
20
|
+
digestKey = key ? Buffer.from(key, "hex") : null;
|
|
21
|
+
}
|
|
22
|
+
/** The per-machine digest key in a config dir, created on first use. */
|
|
23
|
+
export function loadOrCreateDigestKey(dir) {
|
|
24
|
+
const file = join(dir, "digest.key");
|
|
25
|
+
if (existsSync(file)) {
|
|
26
|
+
const key = readFileSync(file, "utf8").trim();
|
|
27
|
+
if (/^[0-9a-f]{64}$/.test(key))
|
|
28
|
+
return key;
|
|
29
|
+
}
|
|
30
|
+
const key = randomBytes(32).toString("hex");
|
|
31
|
+
writeFileSync(file, key + "\n", { mode: 0o600 });
|
|
32
|
+
return key;
|
|
33
|
+
}
|
|
34
|
+
/** HMAC-SHA-256 under the digest key, labelled so it is never mistaken for a plain hash. */
|
|
35
|
+
export const keyedDigest = (value) => "hmac-sha256:" + createHmac("sha256", (digestKey ??= randomBytes(32))).update(value).digest("hex");
|
|
36
|
+
export const digest = (value) => keyedDigest(canonical(value));
|
|
8
37
|
const MASK = "***";
|
|
9
38
|
// A flag or header value: a quoted string or a run of non-whitespace.
|
|
10
39
|
const VALUE = String.raw `("[^"]*"|'[^']*'|\S+)`;
|
|
@@ -86,12 +115,12 @@ export function scrubSecrets(text) {
|
|
|
86
115
|
return out;
|
|
87
116
|
}
|
|
88
117
|
/** A privacy-preserving representation of a shell command: a scrubbed, truncated head
|
|
89
|
-
* plus a digest of the scrubbed command. The full original is never retained
|
|
90
|
-
* digest is taken over the scrubbed text so a secret the head removed
|
|
91
|
-
*
|
|
118
|
+
* plus a keyed digest of the scrubbed command. The full original is never retained; the
|
|
119
|
+
* digest is taken over the scrubbed text so a secret the head removed is not in it, and
|
|
120
|
+
* it is keyed so a secret the scrubber missed cannot be guessed back from it. */
|
|
92
121
|
export function redactCommand(command, headLen = 64) {
|
|
93
122
|
const scrubbed = scrubSecrets(command);
|
|
94
123
|
const head = scrubbed.length > headLen ? `${scrubbed.slice(0, headLen)}…` : scrubbed;
|
|
95
|
-
return `${head} (${
|
|
124
|
+
return `${head} (${keyedDigest(scrubbed)})`;
|
|
96
125
|
}
|
|
97
126
|
//# sourceMappingURL=minimize.js.map
|
package/dist/minimize.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
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// Free-text-only rules (the command head), never applied to structured parameters a\n// policy matches on. These cover common shapes that carry a secret on argv without a\n// recognizable token format:\n// - an attached `-p`/`-u` value (`mysql -phunter2`, `psql -uadmin`) — the single most\n// common way a password reaches a shell. A space-separated `-p value` (the mkdir\n// flag, or `-u origin`) is not attached and is left alone; the `--password`/`--user`\n// forms are handled by the labelled rules above.\n// - a header whose name looks like a credential (`X-Custom-Secret: …`, `My-Token: …`).\n// Bare `key:` is deliberately excluded so ordinary `key: value` text is untouched.\nconst HEAD_RULES: ReadonlyArray<readonly [RegExp, string]> = [\n [/(^|\\s)(-[pu])[^\\s=-]\\S*/g, `$1$2${MASK}`],\n // `auth` is omitted: `Authorization:` is handled above (with its Bearer/Basic label),\n // and other auth headers (`X-Auth-Token`) still match on `token`.\n [/(\\b[\\w-]*(?:secret|token|api[-_]?key|apikey|password|passwd|credential)[\\w-]*\\s*:\\s*)[^\\s\"']+/gi, `$1${MASK}`],\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 the free-text-only argv and\n * header shapes and generic high-entropy blobs. */\nexport function scrubSecrets(text: string): string {\n let out = scrubParam(text);\n for (const [re, replacement] of HEAD_RULES) out = out.replace(re, replacement);\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 head\n * plus a digest of the scrubbed command. The full original is never retained, and the\n * digest is taken over the scrubbed text so a secret the head removed cannot be\n * brute-forced back from the digest. */\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(scrubbed)})`;\n}\n"]}
|
|
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, createHmac, randomBytes } from \"node:crypto\";\nimport { existsSync, readFileSync, writeFileSync } from \"node:fs\";\nimport { join } from \"node:path\";\nimport { canonical } from \"@scopebond/policy-schema/canonical\";\n\n/** A plain SHA-256 content hash, for values that are not secret. */\nexport const sha256 = (value: string): string => \"sha256:\" + createHash(\"sha256\").update(value).digest(\"hex\");\n\n// Digests of what an agent ran — a scrubbed shell command, an MCP tool's arguments — are\n// keyed. The scrubber cannot recognise every secret, and a plain hash of a command whose\n// head is shown beside it leaves only the unseen remainder to guess: a short password the\n// scrubber missed could be recovered offline by anyone holding the receipt. Keyed with a\n// per-machine secret that never leaves `.scopebond/`, the digest still tells two identical\n// actions on this machine apart from different ones, but cannot be tested against guesses.\nlet digestKey: Buffer | null = null;\n\n/** Set the key for keyed digests (32 random bytes, hex). Without one, a random key for\n * this process is used — safe, but digests then compare only within the process. */\nexport function useDigestKey(key: string | null): void {\n digestKey = key ? Buffer.from(key, \"hex\") : null;\n}\n\n/** The per-machine digest key in a config dir, created on first use. */\nexport function loadOrCreateDigestKey(dir: string): string {\n const file = join(dir, \"digest.key\");\n if (existsSync(file)) {\n const key = readFileSync(file, \"utf8\").trim();\n if (/^[0-9a-f]{64}$/.test(key)) return key;\n }\n const key = randomBytes(32).toString(\"hex\");\n writeFileSync(file, key + \"\\n\", { mode: 0o600 });\n return key;\n}\n\n/** HMAC-SHA-256 under the digest key, labelled so it is never mistaken for a plain hash. */\nexport const keyedDigest = (value: string): string =>\n \"hmac-sha256:\" + createHmac(\"sha256\", (digestKey ??= randomBytes(32))).update(value).digest(\"hex\");\nexport const digest = (value: unknown): string => keyedDigest(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// Free-text-only rules (the command head), never applied to structured parameters a\n// policy matches on. These cover common shapes that carry a secret on argv without a\n// recognizable token format:\n// - an attached `-p`/`-u` value (`mysql -phunter2`, `psql -uadmin`) — the single most\n// common way a password reaches a shell. A space-separated `-p value` (the mkdir\n// flag, or `-u origin`) is not attached and is left alone; the `--password`/`--user`\n// forms are handled by the labelled rules above.\n// - a header whose name looks like a credential (`X-Custom-Secret: …`, `My-Token: …`).\n// Bare `key:` is deliberately excluded so ordinary `key: value` text is untouched.\nconst HEAD_RULES: ReadonlyArray<readonly [RegExp, string]> = [\n [/(^|\\s)(-[pu])[^\\s=-]\\S*/g, `$1$2${MASK}`],\n // `auth` is omitted: `Authorization:` is handled above (with its Bearer/Basic label),\n // and other auth headers (`X-Auth-Token`) still match on `token`.\n [/(\\b[\\w-]*(?:secret|token|api[-_]?key|apikey|password|passwd|credential)[\\w-]*\\s*:\\s*)[^\\s\"']+/gi, `$1${MASK}`],\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 the free-text-only argv and\n * header shapes and generic high-entropy blobs. */\nexport function scrubSecrets(text: string): string {\n let out = scrubParam(text);\n for (const [re, replacement] of HEAD_RULES) out = out.replace(re, replacement);\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 head\n * plus a keyed digest of the scrubbed command. The full original is never retained; the\n * digest is taken over the scrubbed text so a secret the head removed is not in it, and\n * it is keyed so a secret the scrubber missed cannot be guessed back from it. */\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} (${keyedDigest(scrubbed)})`;\n}\n"]}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@scopebond/hook",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.8.1",
|
|
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/
|
|
45
|
+
"@scopebond/policy-schema": "^0.4.1",
|
|
46
46
|
"@scopebond/gateway": "^0.8.0",
|
|
47
|
-
"@scopebond/
|
|
47
|
+
"@scopebond/sdk": "^0.1.2"
|
|
48
48
|
},
|
|
49
49
|
"publishConfig": {
|
|
50
50
|
"access": "public"
|