@tinycloud/courier 0.2.0-beta.37 → 0.2.0-beta.38

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.
Files changed (87) hide show
  1. package/README.md +331 -12
  2. package/dist/a2a.d.ts +6 -0
  3. package/dist/a2a.js +10 -0
  4. package/dist/a2a.js.map +1 -1
  5. package/dist/approval.d.ts +6 -5
  6. package/dist/approval.js +7 -16
  7. package/dist/approval.js.map +1 -1
  8. package/dist/args.js +1 -1
  9. package/dist/args.js.map +1 -1
  10. package/dist/broker-config.d.ts +24 -0
  11. package/dist/broker-config.js +99 -0
  12. package/dist/broker-config.js.map +1 -0
  13. package/dist/broker.d.ts +77 -0
  14. package/dist/broker.js +253 -0
  15. package/dist/broker.js.map +1 -0
  16. package/dist/commands.js +5 -2
  17. package/dist/commands.js.map +1 -1
  18. package/dist/consumed.d.ts +10 -0
  19. package/dist/consumed.js +54 -0
  20. package/dist/consumed.js.map +1 -0
  21. package/dist/guard/binaries.d.ts +17 -0
  22. package/dist/guard/binaries.js +89 -0
  23. package/dist/guard/binaries.js.map +1 -0
  24. package/dist/guard/cleanhome.d.ts +27 -0
  25. package/dist/guard/cleanhome.js +111 -0
  26. package/dist/guard/cleanhome.js.map +1 -0
  27. package/dist/guard/command.d.ts +13 -0
  28. package/dist/guard/command.js +158 -0
  29. package/dist/guard/command.js.map +1 -0
  30. package/dist/guard/expansion.d.ts +10 -0
  31. package/dist/guard/expansion.js +74 -0
  32. package/dist/guard/expansion.js.map +1 -0
  33. package/dist/guard/hook.d.ts +38 -0
  34. package/dist/guard/hook.js +124 -0
  35. package/dist/guard/hook.js.map +1 -0
  36. package/dist/guard/install.d.ts +67 -0
  37. package/dist/guard/install.js +193 -0
  38. package/dist/guard/install.js.map +1 -0
  39. package/dist/guard/network.d.ts +7 -0
  40. package/dist/guard/network.js +78 -0
  41. package/dist/guard/network.js.map +1 -0
  42. package/dist/guard/normalize.d.ts +28 -0
  43. package/dist/guard/normalize.js +257 -0
  44. package/dist/guard/normalize.js.map +1 -0
  45. package/dist/guard/paseo.d.ts +30 -0
  46. package/dist/guard/paseo.js +148 -0
  47. package/dist/guard/paseo.js.map +1 -0
  48. package/dist/guard/paths.d.ts +31 -0
  49. package/dist/guard/paths.js +180 -0
  50. package/dist/guard/paths.js.map +1 -0
  51. package/dist/guard/policy.d.ts +65 -0
  52. package/dist/guard/policy.js +1179 -0
  53. package/dist/guard/policy.js.map +1 -0
  54. package/dist/guard/shell.d.ts +55 -0
  55. package/dist/guard/shell.js +727 -0
  56. package/dist/guard/shell.js.map +1 -0
  57. package/dist/index.d.ts +2 -0
  58. package/dist/index.js +2 -0
  59. package/dist/index.js.map +1 -1
  60. package/dist/keys.d.ts +4 -1
  61. package/dist/keys.js +7 -2
  62. package/dist/keys.js.map +1 -1
  63. package/dist/main.js +44 -4
  64. package/dist/main.js.map +1 -1
  65. package/dist/node.d.ts +1 -1
  66. package/dist/node.js +3 -3
  67. package/dist/node.js.map +1 -1
  68. package/dist/notify.d.ts +39 -0
  69. package/dist/notify.js +88 -0
  70. package/dist/notify.js.map +1 -0
  71. package/dist/pause.d.ts +26 -0
  72. package/dist/pause.js +99 -0
  73. package/dist/pause.js.map +1 -0
  74. package/dist/profile.d.ts +3 -3
  75. package/dist/profile.js +12 -12
  76. package/dist/profile.js.map +1 -1
  77. package/dist/wake.d.ts +17 -0
  78. package/dist/wake.js +74 -13
  79. package/dist/wake.js.map +1 -1
  80. package/dist/watch-cmd.d.ts +3 -0
  81. package/dist/watch-cmd.js +56 -2
  82. package/dist/watch-cmd.js.map +1 -1
  83. package/dist/watch.d.ts +70 -0
  84. package/dist/watch.js +292 -11
  85. package/dist/watch.js.map +1 -1
  86. package/package.json +1 -1
  87. package/skills/courier/SKILL.md +94 -8
@@ -0,0 +1,30 @@
1
+ /**
2
+ * Guard for agents the `paseo` wake adapter starts. Paseo cannot pass `claude --settings`, so the guard goes where
3
+ * Claude Code itself looks for it: `<cwd>/.claude/settings.local.json` (project-local, not meant to be committed).
4
+ * Claude runs its hooks in every permission mode, and project settings merge with the user's, so the hook holds in
5
+ * `default`, `bypassPermissions` and the rest. The adapter refuses to wake anything it cannot prove guarded.
6
+ */
7
+ /** Label every guarded Paseo agent carries. An agent without it (created before the guard existed) is never messaged. */
8
+ export declare const GUARD_LABEL = "courier-guard=1";
9
+ /** Paseo env that would send a nested `paseo run` into the caller's own workspace instead of `--cwd`. */
10
+ export declare const PASEO_CALLER_ENV: string[];
11
+ export declare const projectSettingsFile: (cwd: string) => string;
12
+ export interface ProjectGuard {
13
+ /** `node <entry> [tc prefix] guard` */
14
+ argv: string[];
15
+ entry: string;
16
+ }
17
+ /** Install (or refresh) the guard in `<cwd>/.claude/settings.local.json`, merged with whatever is already there. */
18
+ export declare function installProjectGuard(cwd: string, home: string, guard: ProjectGuard): string;
19
+ export declare function assertNoManagedOverride(files?: readonly string[]): void;
20
+ /**
21
+ * Prove that `file` would block: it parses, hooks are not disabled, the PreToolUse `*` hook is the guard, and that exact
22
+ * command, run the way Claude runs it, refuses a call no approval can unlock (`courier pair`, which needs no network).
23
+ * Throws with the reason otherwise.
24
+ */
25
+ export declare function verifyProjectGuard(file: string, cwd: string): void;
26
+ /** Paseo env for a run: the caller's own agent and workspace must not redirect `--cwd`. */
27
+ export declare function paseoEnv(base: NodeJS.ProcessEnv): NodeJS.ProcessEnv;
28
+ /** True if `provider` (`claude` or `claude/<model>`) is Claude Code, the only agent the project hook covers. */
29
+ export declare const isClaudeProvider: (provider: string) => boolean;
30
+ export declare function sameDir(a: string, b: string): boolean;
@@ -0,0 +1,148 @@
1
+ import fs from 'node:fs';
2
+ import path from 'node:path';
3
+ import { spawnSync } from 'node:child_process';
4
+ import { isRecord } from '../a2a.js';
5
+ import { installBinaryCensus } from './binaries.js';
6
+ import { guardDir } from './install.js';
7
+ import { GUARD_HOOK_RE, guardSettings, projectSettings } from './install.js';
8
+ /**
9
+ * Guard for agents the `paseo` wake adapter starts. Paseo cannot pass `claude --settings`, so the guard goes where
10
+ * Claude Code itself looks for it: `<cwd>/.claude/settings.local.json` (project-local, not meant to be committed).
11
+ * Claude runs its hooks in every permission mode, and project settings merge with the user's, so the hook holds in
12
+ * `default`, `bypassPermissions` and the rest. The adapter refuses to wake anything it cannot prove guarded.
13
+ */
14
+ /** Label every guarded Paseo agent carries. An agent without it (created before the guard existed) is never messaged. */
15
+ export const GUARD_LABEL = 'courier-guard=1';
16
+ /** Paseo env that would send a nested `paseo run` into the caller's own workspace instead of `--cwd`. */
17
+ export const PASEO_CALLER_ENV = ['PASEO_AGENT_ID', 'PASEO_AGENT_CWD', 'PASEO_WORKSPACE_ID'];
18
+ export const projectSettingsFile = (cwd) => path.join(cwd, '.claude', 'settings.local.json');
19
+ /** Install (or refresh) the guard in `<cwd>/.claude/settings.local.json`, merged with whatever is already there. */
20
+ export function installProjectGuard(cwd, home, guard) {
21
+ fs.mkdirSync(guardDir(home), { recursive: true, mode: 0o700 });
22
+ installBinaryCensus(path.join(guardDir(home), 'binaries.json'), guard.entry, guard.argv, cwd);
23
+ const file = projectSettingsFile(cwd);
24
+ fs.mkdirSync(path.dirname(file), { recursive: true });
25
+ let theirs;
26
+ let mode = 0o600;
27
+ try {
28
+ const raw = fs.readFileSync(file, 'utf8');
29
+ mode = fs.statSync(file).mode & 0o777;
30
+ theirs = raw.trim() ? JSON.parse(raw) : undefined;
31
+ }
32
+ catch (error) {
33
+ if (error.code !== 'ENOENT') {
34
+ throw new Error(`cannot merge the guard into ${file}: ${error.message}`);
35
+ }
36
+ }
37
+ if (theirs !== undefined && !isRecord(theirs))
38
+ throw new Error(`${file} is not a JSON object; refusing to overwrite it`);
39
+ const merged = projectSettings(guardSettings({ argv: guard.argv, home, entry: guard.entry }), theirs);
40
+ const next = `${JSON.stringify(merged, null, 2)}\n`;
41
+ if (!fs.existsSync(file) || fs.readFileSync(file, 'utf8') !== next) {
42
+ const tmp = `${file}.${process.pid}.tmp`;
43
+ fs.writeFileSync(tmp, next, { mode });
44
+ fs.renameSync(tmp, file);
45
+ }
46
+ return file;
47
+ }
48
+ /** Managed (policy) settings that switch hooks off or pin hooks to the managed set: the guard would not run. */
49
+ const MANAGED_SETTINGS = ['/etc/claude-code/managed-settings.json', '/Library/Application Support/ClaudeCode/managed-settings.json'];
50
+ export function assertNoManagedOverride(files = MANAGED_SETTINGS) {
51
+ for (const file of files) {
52
+ let text;
53
+ try {
54
+ text = fs.readFileSync(file, 'utf8');
55
+ }
56
+ catch (error) {
57
+ if (error.code === 'ENOENT' || error.code === 'ENOTDIR')
58
+ continue;
59
+ throw new Error(`cannot read managed settings ${file}: ${error.message}`);
60
+ }
61
+ let managed;
62
+ try {
63
+ managed = JSON.parse(text);
64
+ }
65
+ catch {
66
+ throw new Error(`managed settings ${file} are not valid JSON; not waking an agent that may run without the guard`);
67
+ }
68
+ if (isRecord(managed) && (managed.disableAllHooks === true || managed.allowManagedHooksOnly === true)) {
69
+ throw new Error(`managed settings ${file} turn off project hooks (disableAllHooks or allowManagedHooksOnly); the guard would not run, so no agent is woken`);
70
+ }
71
+ }
72
+ }
73
+ /**
74
+ * Prove that `file` would block: it parses, hooks are not disabled, the PreToolUse `*` hook is the guard, and that exact
75
+ * command, run the way Claude runs it, refuses a call no approval can unlock (`courier pair`, which needs no network).
76
+ * Throws with the reason otherwise.
77
+ */
78
+ export function verifyProjectGuard(file, cwd) {
79
+ let parsed;
80
+ try {
81
+ parsed = JSON.parse(fs.readFileSync(file, 'utf8'));
82
+ }
83
+ catch (error) {
84
+ throw new Error(`guard settings ${file} are unreadable: ${error.message}`);
85
+ }
86
+ if (!isRecord(parsed) || parsed.disableAllHooks !== false)
87
+ throw new Error(`guard settings ${file} do not keep hooks enabled`);
88
+ // Nothing but the guard's own hook and deny rules (B1): any other hook event, `env`, `apiKeyHelper`, MCP server or allow rule can run a command.
89
+ const extraKeys = Object.keys(parsed).filter((key) => !['disableAllHooks', 'hooks', 'permissions'].includes(key));
90
+ if (extraKeys.length)
91
+ throw new Error(`guard settings ${file} carry other settings (${extraKeys.join(', ')}); only the guard's hook and deny rules are allowed`);
92
+ const hookEvents = isRecord(parsed.hooks) ? Object.keys(parsed.hooks) : [];
93
+ if (hookEvents.length !== 1 || hookEvents[0] !== 'PreToolUse')
94
+ throw new Error(`guard settings ${file} define other hook events (${hookEvents.join(', ')}); only the guard's PreToolUse hook is allowed`);
95
+ const permKeys = isRecord(parsed.permissions) ? Object.keys(parsed.permissions) : [];
96
+ if (permKeys.some((key) => key !== 'deny'))
97
+ throw new Error(`guard settings ${file} carry permission rules other than deny rules`);
98
+ assertNoManagedOverride();
99
+ const hooks = isRecord(parsed.hooks) && Array.isArray(parsed.hooks.PreToolUse) ? parsed.hooks.PreToolUse : [];
100
+ if (hooks.length !== 1)
101
+ throw new Error(`guard settings ${file} must hold exactly one PreToolUse entry (found ${hooks.length})`);
102
+ let command;
103
+ for (const entry of hooks) {
104
+ if (!isRecord(entry) || entry.matcher !== '*' || !Array.isArray(entry.hooks))
105
+ continue;
106
+ for (const hook of entry.hooks) {
107
+ if (isRecord(hook) && hook.type === 'command' && typeof hook.command === 'string' && GUARD_HOOK_RE.test(hook.command))
108
+ command = hook.command;
109
+ }
110
+ }
111
+ if (!command)
112
+ throw new Error(`guard settings ${file} have no courier guard hook`);
113
+ const probe = spawnSync('/bin/sh', ['-c', command], {
114
+ input: JSON.stringify({ tool_name: 'Bash', tool_input: { command: 'courier pair start probe' }, cwd }),
115
+ encoding: 'utf8',
116
+ timeout: 30_000,
117
+ });
118
+ let denied = probe.status === 2;
119
+ if (probe.status === 0 && probe.stdout.trim()) {
120
+ try {
121
+ const out = JSON.parse(probe.stdout);
122
+ const decision = isRecord(out) && isRecord(out.hookSpecificOutput) ? out.hookSpecificOutput.permissionDecision : undefined;
123
+ denied = decision === 'deny';
124
+ }
125
+ catch { /* not JSON: not a denial */ }
126
+ }
127
+ if (!denied)
128
+ throw new Error('the guard hook did not refuse a probe command; not waking an agent without a working guard');
129
+ }
130
+ /** Paseo env for a run: the caller's own agent and workspace must not redirect `--cwd`. */
131
+ export function paseoEnv(base) {
132
+ const env = { ...base };
133
+ for (const name of PASEO_CALLER_ENV)
134
+ delete env[name];
135
+ return env;
136
+ }
137
+ /** True if `provider` (`claude` or `claude/<model>`) is Claude Code, the only agent the project hook covers. */
138
+ export const isClaudeProvider = (provider) => /^claude(\/|$)/.test(provider);
139
+ export function sameDir(a, b) {
140
+ const real = (p) => { try {
141
+ return fs.realpathSync(p);
142
+ }
143
+ catch {
144
+ return path.resolve(p);
145
+ } };
146
+ return real(a) === real(b);
147
+ }
148
+ //# sourceMappingURL=paseo.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"paseo.js","sourceRoot":"","sources":["../../src/guard/paseo.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,31 @@
1
+ /** Quotes and backslashes removed, so `.n"p"mrc` and `.n\pmrc` read as the name they spell. */
2
+ export declare const unquoted: (text: string) => string;
3
+ export declare function credentialTextRefusal(text: string): string | undefined;
4
+ /** `literals`: absolute paths of this install (courier home, guard dir, package roots) that a command must not name. */
5
+ export declare function protectedTextRefusal(text: string, literals?: readonly string[]): string | undefined;
6
+ /** The real path of `file`, even when it does not exist yet: the nearest existing ancestor is resolved, the rest appended. */
7
+ export declare function realpathBest(file: string): string;
8
+ export interface ProtectedSet {
9
+ /** Absolute paths (courier home, guard install dir, node_modules chain, package roots): nothing under them may be written. */
10
+ roots: string[];
11
+ /** Home directories whose tool configuration may not be written. */
12
+ homes: string[];
13
+ realHome?: string;
14
+ }
15
+ /** True if `file` (relative to `cwd`) may not be written by a woken agent. Compares real paths. */
16
+ export declare function isProtectedFile(file: string, cwd: string, set: ProtectedSet): boolean;
17
+ /** True if a file tool (Read, Grep, Glob, Edit, …) names a credential path. */
18
+ export declare function isCredentialFile(file: string, cwd: string): boolean;
19
+ /**
20
+ * Directories to protect for this install: the entry's directory, its package root (the first `package.json` above it)
21
+ * and every `node_modules` directory the hook's imports can resolve to (the ones above the entry). Not the repository
22
+ * around them: a developer's checkout stays editable.
23
+ */
24
+ export declare function installRoots(entry: string): string[];
25
+ export declare function protectedSet(home: string, entry: string, extra?: {
26
+ agentHome?: string;
27
+ }): ProtectedSet;
28
+ /** Reads/searches must not descend into protected state or enumerate an ancestor that contains it. */
29
+ export declare function isProtectedRead(file: string, cwd: string, set: ProtectedSet): boolean;
30
+ /** Check literal shell paths by realpath, including symlinks and broad ancestor searches. */
31
+ export declare function pathTextRefusal(text: string, cwd: string, home: string, roots: readonly string[], realHome?: string): string | undefined;
@@ -0,0 +1,180 @@
1
+ import fs from 'node:fs';
2
+ import os from 'node:os';
3
+ import path from 'node:path';
4
+ /**
5
+ * Paths and words a woken agent has no business with (R2, B1, B2). Three layers use this module:
6
+ * - `credentialTextRefusal` / `protectedTextRefusal`: substring checks on the raw command text (quotes and backslashes
7
+ * removed first), so `cat ~/.n"p"mrc` and a glob into `.config` are refused. A shell can still build a path
8
+ * out of pieces; the inverted expansion rule and the clean HOME cover that, and the README says what is left.
9
+ * - `isProtectedFile`: the file tools (Edit, Write, Read, …), compared on real paths so a symlink does not hide a target.
10
+ */
11
+ /** Where credentials live. A command that names one is refused, whether it reads or writes. */
12
+ const CREDENTIAL_SUBSTRINGS = [
13
+ '.config/gh', '.config/gcloud', '.config/wrangler', '.config/.wrangler', '.wrangler', '.npmrc', '.netrc', '.ssh', '.aws', '.docker/config',
14
+ '.git-credentials', '.claude/.credentials', '.claude.json', '.paseo', '.tinycloud', 'hosts.yml', '.courier/broker', 'broker/gh.json',
15
+ 'application_default_credentials', '.kube/config', '.gnupg', '.password-store', '.local/share/keyrings',
16
+ ];
17
+ /** Config and hook files that decide what runs next: writing them could switch the guard off or add a command. */
18
+ const PROTECTED_SUBSTRINGS = [
19
+ '.courier-agent.json', '.claude/settings', 'settings.local.json', '.claude/hooks', '.claude/commands', '.claude/agents', '.mcp.json', '.git/hooks', '.git/config', 'guard/settings.json',
20
+ '.config/git', '.gitconfig', '.git-hooks',
21
+ ];
22
+ const CREDENTIAL_PATTERNS = [
23
+ ['owner shell startup files', /(?:^|[\s/])\.(?:bashrc|bash_profile|bash_login|profile|zshenv|zshrc|zprofile)(?:[\s/]|$)/i],
24
+ ['a glob in a process environment path', /\/proc\/[^\s]*[*?[]/i],
25
+ ['a glob hiding a credential directory', /(?:^|[\s/])[^\s/]*[*?[][^\s/]*(?:config|tinycloud|courier)[^\s/]*\//i],
26
+ ['a glob hiding gh hosts', /(?:^|[\s/])host[^\s/]*[*?[][^\s/]*\.ya?ml/i],
27
+ ['an embedded privilege command', /['"(;]\s*(?:sudo|doas|pkexec|runuser)\s/i],
28
+ ['process environments (/proc/*/environ)', /\/proc\b[^\n]*environ/i],
29
+ ['a glob in a dot-directory under .config', /\.config\/[^\s/]*[*?[]/],
30
+ ['a glob in a dot-file name', /(^|[\s/=:])\.[A-Za-z_-]+[*?[]/],
31
+ ['a credential-printing command (gh auth, gh config)', /(^|[\s/;&|(`])gh\s+(auth|config|extension)(\s|$)/],
32
+ ['a credential-printing command (git credential)', /(^|[\s/;&|(`])git\s+([^\s]+\s+)*credential(\s|$|-)/],
33
+ ['a credential-printing command (npm/yarn/pnpm config)', /(^|[\s/;&|(`])(npm|yarn|pnpm|bun)\s+config(\s|$)/],
34
+ ['a keyring or secret-store command', /(^|[\s/;&|(`])(secret-tool|keyctl|gnome-keyring-daemon|pass|gpg|gpg2|ssh-add|ssh-agent|security\s+(find|dump|export)[-\w]*)(\s|$)/],
35
+ ];
36
+ /** Quotes and backslashes removed, so `.n"p"mrc` and `.n\pmrc` read as the name they spell. */
37
+ export const unquoted = (text) => text.replace(/\$?['"]/g, '').replace(/\\\n/g, '').replace(/\\/g, '');
38
+ const variants = (text) => [text, unquoted(text)];
39
+ export function credentialTextRefusal(text) {
40
+ for (const t of variants(text)) {
41
+ const lower = t.toLowerCase();
42
+ for (const s of CREDENTIAL_SUBSTRINGS)
43
+ if (lower.includes(s))
44
+ return `it names a credential path (${s})`;
45
+ for (const [label, re] of CREDENTIAL_PATTERNS)
46
+ if (re.test(t))
47
+ return `it uses ${label}`;
48
+ }
49
+ return undefined;
50
+ }
51
+ /** `literals`: absolute paths of this install (courier home, guard dir, package roots) that a command must not name. */
52
+ export function protectedTextRefusal(text, literals = []) {
53
+ for (const t of variants(text)) {
54
+ for (const s of PROTECTED_SUBSTRINGS)
55
+ if (t.includes(s))
56
+ return `it names a guard or tool configuration file (${s})`;
57
+ for (const literal of literals)
58
+ if (literal && t.includes(literal))
59
+ return `it names the Courier install or profile directory (${literal})`;
60
+ }
61
+ return undefined;
62
+ }
63
+ // ---------- file tools ----------
64
+ /** The real path of `file`, even when it does not exist yet: the nearest existing ancestor is resolved, the rest appended. */
65
+ export function realpathBest(file) {
66
+ const abs = path.resolve(file);
67
+ let probe = abs;
68
+ const tail = [];
69
+ for (;;) {
70
+ try {
71
+ return path.join(fs.realpathSync(probe), ...tail.reverse());
72
+ }
73
+ catch {
74
+ const parent = path.dirname(probe);
75
+ if (parent === probe)
76
+ return abs;
77
+ tail.push(path.basename(probe));
78
+ probe = parent;
79
+ }
80
+ }
81
+ }
82
+ const within = (file, root) => file === root || file.startsWith(root.endsWith(path.sep) ? root : `${root}${path.sep}`);
83
+ /** Every directory from `from` up to the filesystem root. */
84
+ function ancestors(from) {
85
+ const out = [];
86
+ for (let dir = path.resolve(from);; dir = path.dirname(dir)) {
87
+ out.push(dir);
88
+ if (path.dirname(dir) === dir)
89
+ return out;
90
+ }
91
+ }
92
+ /** Names that must not be written in any directory above the agent's cwd. */
93
+ const PER_DIRECTORY = ['.claude/settings.json', '.claude/settings.local.json', '.claude/hooks', '.claude/commands', '.claude/agents', '.mcp.json', '.git/hooks', '.git/config', '.gitmodules'];
94
+ /** Names under a home directory (the real one and the agent's clean one). */
95
+ const PER_HOME = ['.courier-agent.json', '.claude/settings.json', '.claude/settings.local.json', '.claude/hooks', '.claude/commands', '.claude/agents', '.claude.json', '.gitconfig', '.config/git', '.bashrc', '.bash_profile', '.profile', '.zshenv', '.zshrc', '.zprofile', '.npmrc'];
96
+ /** True if `file` (relative to `cwd`) may not be written by a woken agent. Compares real paths. */
97
+ export function isProtectedFile(file, cwd, set) {
98
+ const home = set.homes[set.homes.length - 1] ?? process.env.HOME ?? '';
99
+ const expanded = file.replace(/^~(?=\/|$)/, home).replace(/^\$\{?HOME\}?(?=\/|$)/, home);
100
+ const real = realpathBest(path.resolve(cwd, expanded));
101
+ for (const root of set.roots)
102
+ if (within(real, realpathBest(root)))
103
+ return true;
104
+ for (const dir of ancestors(cwd))
105
+ for (const rel of PER_DIRECTORY)
106
+ if (within(real, realpathBest(path.join(dir, rel))))
107
+ return true;
108
+ for (const home of set.homes)
109
+ for (const rel of PER_HOME)
110
+ if (within(real, realpathBest(path.join(home, rel))))
111
+ return true;
112
+ return false;
113
+ }
114
+ /** True if a file tool (Read, Grep, Glob, Edit, …) names a credential path. */
115
+ export function isCredentialFile(file, cwd) {
116
+ const abs = path.resolve(cwd, file);
117
+ return credentialTextRefusal(abs) !== undefined || credentialTextRefusal(realpathBest(abs)) !== undefined;
118
+ }
119
+ /**
120
+ * Directories to protect for this install: the entry's directory, its package root (the first `package.json` above it)
121
+ * and every `node_modules` directory the hook's imports can resolve to (the ones above the entry). Not the repository
122
+ * around them: a developer's checkout stays editable.
123
+ */
124
+ export function installRoots(entry) {
125
+ const real = realpathBest(entry);
126
+ const roots = new Set([path.dirname(real)]);
127
+ let sawPackage = false;
128
+ for (const dir of ancestors(path.dirname(real))) {
129
+ if (!sawPackage && fs.existsSync(path.join(dir, 'package.json'))) {
130
+ roots.add(dir);
131
+ sawPackage = true;
132
+ }
133
+ if (path.basename(dir) === 'node_modules')
134
+ roots.add(dir);
135
+ if (fs.existsSync(path.join(dir, 'node_modules')))
136
+ roots.add(path.join(dir, 'node_modules'));
137
+ }
138
+ roots.delete(path.parse(real).root);
139
+ return [...roots];
140
+ }
141
+ export function protectedSet(home, entry, extra = {}) {
142
+ return {
143
+ roots: [path.resolve(home), ...installRoots(entry)],
144
+ homes: [...new Set([os.homedir(), ...(extra.agentHome ? [extra.agentHome] : [])])],
145
+ };
146
+ }
147
+ /** Reads/searches must not descend into protected state or enumerate an ancestor that contains it. */
148
+ export function isProtectedRead(file, cwd, set) {
149
+ const home = set.homes[set.homes.length - 1] ?? process.env.HOME ?? '';
150
+ const expanded = file.replace(/^~(?=\/|$)/, home).replace(/^\$\{?HOME\}?(?=\/|$)/, home);
151
+ const real = realpathBest(path.resolve(cwd, expanded));
152
+ for (const root of set.roots) {
153
+ const r = realpathBest(root);
154
+ if (within(real, r) || within(r, real))
155
+ return true;
156
+ }
157
+ const owner = set.realHome ?? set.homes[0];
158
+ if (owner && within(real, realpathBest(owner)) && !within(real, realpathBest(cwd)))
159
+ return true;
160
+ return false;
161
+ }
162
+ /** Check literal shell paths by realpath, including symlinks and broad ancestor searches. */
163
+ export function pathTextRefusal(text, cwd, home, roots, realHome) {
164
+ const flat = unquoted(text);
165
+ if (/\bCOURIER_(HOME|REAL_HOME|BROKER\w*)\b/.test(flat) || /(?:~|\$\{?HOME\}?)\/\.\.(?:\/|\s|$)/.test(flat))
166
+ return 'broker/profile environment or home traversal';
167
+ for (let word of flat.split(/[\s;&|<>]+/)) {
168
+ if (!word || word.startsWith('-') || word.includes('://'))
169
+ continue;
170
+ word = word.replace(/^~(?=\/|$)/, home).replace(/^\$\{?HOME\}?(?=\/|$)/, home);
171
+ if (!word.includes('/') && word !== '.' && word !== '..')
172
+ continue;
173
+ const prefix = word.split(/[*?[]/, 1)[0];
174
+ const file = prefix || '.';
175
+ if (isProtectedRead(file, cwd, { roots: [...roots], homes: realHome ? [realHome] : [], realHome }))
176
+ return 'a protected root or its parent';
177
+ }
178
+ return undefined;
179
+ }
180
+ //# sourceMappingURL=paths.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"paths.js","sourceRoot":"","sources":["../../src/guard/paths.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,65 @@
1
+ import { type BinaryIdentity } from './binaries.js';
2
+ import type { Verb } from './target.js';
3
+ /**
4
+ * Which shell commands a woken agent may not run on its own (spec §7), found by reading the command the way a shell
5
+ * would. It reads quoting, wrappers, chains, subshells and nested `sh -c`/`eval`, and it
6
+ * treats everything it cannot read exactly as a reason to refuse, never as a reason to allow.
7
+ */
8
+ export interface Word {
9
+ value: string;
10
+ dynamic: boolean;
11
+ }
12
+ export type Finding =
13
+ /** A gated command with a fully literal argv; `key` identifies it for approval matching. */
14
+ {
15
+ kind: 'gated';
16
+ label: string;
17
+ verb: Verb;
18
+ target: string;
19
+ key: string;
20
+ display: string;
21
+ }
22
+ /** Never allowed in a guarded session, with or without approval. */
23
+ | {
24
+ kind: 'never';
25
+ label: string;
26
+ }
27
+ /** Could be a gated command and cannot be told apart or approved (dynamic words, loops, scripts, pipes). */
28
+ | {
29
+ kind: 'unresolved';
30
+ label: string;
31
+ };
32
+ export type FileRead = {
33
+ state: 'absent';
34
+ } | {
35
+ state: 'unreadable';
36
+ } | {
37
+ state: 'text';
38
+ text: string;
39
+ };
40
+ export interface PolicyEnv {
41
+ /** Working directory the tool call starts in. */
42
+ cwd: string;
43
+ readFile(file: string): FileRead;
44
+ /** Run `git <args>` in `dir`; ok=false on any failure. */
45
+ git(dir: string, args: string[]): {
46
+ ok: boolean;
47
+ out: string;
48
+ };
49
+ /** Environment variable of the guard process (the woken agent's environment). */
50
+ getenv(name: string): string | undefined;
51
+ /** Absolute paths of this install and profile (courier home, guard dir, package roots) that a command must not name. */
52
+ literals?: string[];
53
+ binaries?: BinaryIdentity[];
54
+ /** Returns a canonical tool name only for a census-verified executable identity (mocked by offline grammar tests). */
55
+ resolveProgram?(word: string, cwd: string): string | undefined;
56
+ protectedRoots?: string[];
57
+ realHome?: string;
58
+ /** Real paths of the courier entry points (`cli.js`, the `tc` entry): `node <entry> pair …` is a courier command. */
59
+ courierEntries?: string[];
60
+ }
61
+ /** The exec opt-out never starts a second agent CLI. */
62
+ export declare function startsAgentCli(command: string): boolean;
63
+ export declare function progName(value: string): string;
64
+ /** Resolve a command string into findings. */
65
+ export declare function analyze(command: string, env: PolicyEnv): Finding[];