@agent-beam/beam 0.2.38 → 0.2.40
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 +7 -13
- package/dist/access-risk.d.ts +10 -0
- package/dist/access-risk.js +43 -0
- package/dist/access-risk.js.map +1 -0
- package/dist/action-context.d.ts +30 -0
- package/dist/action-context.js +203 -0
- package/dist/action-context.js.map +1 -0
- package/dist/activity.d.ts +22 -0
- package/dist/activity.js +132 -0
- package/dist/activity.js.map +1 -0
- package/dist/agent-hook-install.d.ts +8 -0
- package/dist/agent-hook-install.js +278 -0
- package/dist/agent-hook-install.js.map +1 -0
- package/dist/agent-installers.js +16 -3
- package/dist/agent-installers.js.map +1 -1
- package/dist/agent-plugins.d.ts +3 -0
- package/dist/agent-plugins.js +66 -0
- package/dist/agent-plugins.js.map +1 -0
- package/dist/agents.d.ts +1 -0
- package/dist/agents.js +30 -184
- package/dist/agents.js.map +1 -1
- package/dist/approvals.d.ts +13 -1
- package/dist/approvals.js +14 -3
- package/dist/approvals.js.map +1 -1
- package/dist/approved-actions.d.ts +11 -0
- package/dist/approved-actions.js +53 -0
- package/dist/approved-actions.js.map +1 -0
- package/dist/behavioral-anomaly.d.ts +16 -0
- package/dist/behavioral-anomaly.js +66 -0
- package/dist/behavioral-anomaly.js.map +1 -0
- package/dist/cli.js +77 -9
- package/dist/cli.js.map +1 -1
- package/dist/client.d.ts +18 -1
- package/dist/client.js +359 -80
- package/dist/client.js.map +1 -1
- package/dist/core.d.ts +55 -2
- package/dist/core.js +85 -26
- package/dist/core.js.map +1 -1
- package/dist/credential-egress.d.ts +17 -0
- package/dist/credential-egress.js +325 -0
- package/dist/credential-egress.js.map +1 -0
- package/dist/custom-rules.js +3 -2
- package/dist/custom-rules.js.map +1 -1
- package/dist/data-detectors.js +60 -4
- package/dist/data-detectors.js.map +1 -1
- package/dist/destructive-risk.d.ts +1 -0
- package/dist/destructive-risk.js +70 -0
- package/dist/destructive-risk.js.map +1 -0
- package/dist/edit-stats.js +5 -5
- package/dist/edit-stats.js.map +1 -1
- package/dist/entity-redaction.d.ts +11 -0
- package/dist/entity-redaction.js +88 -0
- package/dist/entity-redaction.js.map +1 -0
- package/dist/file-lock.d.ts +1 -0
- package/dist/file-lock.js +54 -0
- package/dist/file-lock.js.map +1 -0
- package/dist/forward.d.ts +13 -6
- package/dist/forward.js +120 -42
- package/dist/forward.js.map +1 -1
- package/dist/hook-adapters.d.ts +2 -2
- package/dist/hook-adapters.js +59 -60
- package/dist/hook-adapters.js.map +1 -1
- package/dist/hook-catalog.d.ts +12 -0
- package/dist/hook-catalog.js +469 -0
- package/dist/hook-catalog.js.map +1 -0
- package/dist/hook-config.d.ts +8 -0
- package/dist/hook-config.js +80 -0
- package/dist/hook-config.js.map +1 -0
- package/dist/hook-spool.d.ts +3 -0
- package/dist/hook-spool.js +56 -0
- package/dist/hook-spool.js.map +1 -0
- package/dist/install.d.ts +5 -5
- package/dist/install.js +48 -199
- package/dist/install.js.map +1 -1
- package/dist/inventory.js +1 -1
- package/dist/inventory.js.map +1 -1
- package/dist/jev.js +2 -1
- package/dist/jev.js.map +1 -1
- package/dist/judge.js +2 -1
- package/dist/judge.js.map +1 -1
- package/dist/mitigation-pattern.d.ts +1 -0
- package/dist/mitigation-pattern.js +140 -0
- package/dist/mitigation-pattern.js.map +1 -0
- package/dist/package-risk-rules.js +2 -2
- package/dist/package-risk-rules.js.map +1 -1
- package/dist/package-scan.js +1 -1
- package/dist/package-scan.js.map +1 -1
- package/dist/packages.js +1 -1
- package/dist/packages.js.map +1 -1
- package/dist/policy-paths.d.ts +20 -0
- package/dist/policy-paths.js +145 -0
- package/dist/policy-paths.js.map +1 -0
- package/dist/policy.d.ts +10 -0
- package/dist/policy.js +99 -11
- package/dist/policy.js.map +1 -1
- package/dist/rule-catalog.d.ts +4 -2
- package/dist/rule-catalog.js +124 -24
- package/dist/rule-catalog.js.map +1 -1
- package/dist/rule-matches.d.ts +13 -0
- package/dist/rule-matches.js +42 -0
- package/dist/rule-matches.js.map +1 -0
- package/dist/serve.js +55 -5
- package/dist/serve.js.map +1 -1
- package/dist/server.js +34 -7
- package/dist/server.js.map +1 -1
- package/dist/setup.js +5 -3
- package/dist/setup.js.map +1 -1
- package/dist/skill-risk-rules.js +2 -2
- package/dist/skill-risk-rules.js.map +1 -1
- package/dist/skill-scan.js +1 -1
- package/dist/skill-scan.js.map +1 -1
- package/dist/skill-setup.js +4 -5
- package/dist/skill-setup.js.map +1 -1
- package/dist/skill-sync.d.ts +5 -1
- package/dist/skill-sync.js +115 -14
- package/dist/skill-sync.js.map +1 -1
- package/dist/store.js +6 -9
- package/dist/store.js.map +1 -1
- package/dist/studio.js +4 -4
- package/package.json +5 -2
- package/stress-report.json +45 -45
package/dist/agents.js
CHANGED
|
@@ -1,188 +1,34 @@
|
|
|
1
|
+
import { HOOK_CATALOG, hookKind } from "./hook-catalog.js";
|
|
2
|
+
import { mergeHookSpec, unmergeHookSpec } from "./hook-config.js";
|
|
1
3
|
import { join } from "node:path";
|
|
2
|
-
|
|
3
|
-
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
return { version: existing.version ?? 1, ...existing, hooks: { ...hooks, [event]: list } };
|
|
31
|
-
};
|
|
32
|
-
}
|
|
33
|
-
// Copilot CLI's dedicated hook file uses `{ type: "command", bash, timeoutSec }` entries.
|
|
34
|
-
function mergeCopilotHooks(event) {
|
|
35
|
-
return (existing, command) => {
|
|
36
|
-
const hooks = obj(existing.hooks);
|
|
37
|
-
const list = arr(hooks[event]).map(obj);
|
|
38
|
-
if (!list.some(entry => entry.bash === command))
|
|
39
|
-
list.push({ type: "command", bash: command, timeoutSec: 30 });
|
|
40
|
-
return { version: existing.version ?? 1, ...existing, hooks: { ...hooks, [event]: list } };
|
|
41
|
-
};
|
|
42
|
-
}
|
|
43
|
-
// Reverse of mergeClaudeStyleHooks: drops beam's command out of each entry's inner hooks array
|
|
44
|
-
// (rather than the whole entry), so a user-added hook sharing the same matcher is left alone.
|
|
45
|
-
function unmergeClaudeStyleHooks(event) {
|
|
46
|
-
return (existing, isOwnCommand) => {
|
|
47
|
-
const hooks = obj(existing.hooks);
|
|
48
|
-
if (!(event in hooks))
|
|
49
|
-
return existing;
|
|
50
|
-
const list = arr(hooks[event]).map(obj)
|
|
51
|
-
.map(entry => ({ ...entry, hooks: arr(entry.hooks).map(obj).filter(h => !isOwnCommand(String(h.command ?? ""))) }))
|
|
52
|
-
.filter(entry => arr(entry.hooks).length > 0);
|
|
53
|
-
return { ...existing, hooks: { ...hooks, [event]: list } };
|
|
54
|
-
};
|
|
55
|
-
}
|
|
56
|
-
// Reverse of mergeCursorHooks.
|
|
57
|
-
function unmergeCursorHooks(event) {
|
|
58
|
-
return (existing, isOwnCommand) => {
|
|
59
|
-
const hooks = obj(existing.hooks);
|
|
60
|
-
if (!(event in hooks))
|
|
61
|
-
return existing;
|
|
62
|
-
const list = arr(hooks[event]).map(obj).filter(entry => !isOwnCommand(String(entry.command ?? "")));
|
|
63
|
-
return { ...existing, hooks: { ...hooks, [event]: list } };
|
|
64
|
-
};
|
|
65
|
-
}
|
|
66
|
-
// Reverse of mergeCopilotHooks.
|
|
67
|
-
function unmergeCopilotHooks(event) {
|
|
68
|
-
return (existing, isOwnCommand) => {
|
|
69
|
-
const hooks = obj(existing.hooks);
|
|
70
|
-
if (!(event in hooks))
|
|
71
|
-
return existing;
|
|
72
|
-
const list = arr(hooks[event]).map(obj).filter(entry => !isOwnCommand(String(entry.bash ?? "")));
|
|
73
|
-
return { ...existing, hooks: { ...hooks, [event]: list } };
|
|
74
|
-
};
|
|
75
|
-
}
|
|
76
|
-
// Installs beam into more than one hook event (e.g. the tool-call gate *and* the prompt-submit
|
|
77
|
-
// gate) by folding each single-event merger over the config in turn. A disabled agent only ever
|
|
78
|
-
// reliably stops at the pre-action gate if that agent never calls a tool in a turn (pure chat) --
|
|
79
|
-
// registering the prompt-submit event too means a disabled agent's policy.disabledAgents block
|
|
80
|
-
// (see policy.ts's evaluate()) fires before the prompt is even processed, not just before a tool
|
|
81
|
-
// call that may never come.
|
|
82
|
-
function mergeAll(mergers) {
|
|
83
|
-
return (existing, command) => mergers.reduce((acc, merge) => merge(acc, command), existing);
|
|
84
|
-
}
|
|
85
|
-
function unmergeAll(unmergers) {
|
|
86
|
-
return (existing, isOwnCommand) => unmergers.reduce((acc, unmerge) => unmerge(acc, isOwnCommand), existing);
|
|
87
|
-
}
|
|
88
|
-
export const AGENTS = [
|
|
89
|
-
{
|
|
90
|
-
id: "claude-code", name: "Claude Code",
|
|
91
|
-
configs: [".claude/settings.json"], artifacts: [".claude/projects"],
|
|
92
|
-
hookConfigPath: ".claude/settings.json", hookEventName: "PreToolUse",
|
|
93
|
-
mergeHookConfig: mergeAll([mergeClaudeStyleHooks("PreToolUse"), mergeClaudeStyleHooks("UserPromptSubmit")]),
|
|
94
|
-
unmergeHookConfig: unmergeAll([unmergeClaudeStyleHooks("PreToolUse"), unmergeClaudeStyleHooks("UserPromptSubmit")]),
|
|
95
|
-
adapter: "passthrough", verifiedPayload: true,
|
|
96
|
-
notes: "Payload verified against code.claude.com/docs/en/hooks. Also installs UserPromptSubmit (same hookSpecificOutput.permissionDecision contract, just hookEventName: \"UserPromptSubmit\") so a disabled agent is stopped before the prompt is processed, not only at its first tool call."
|
|
97
|
-
},
|
|
98
|
-
{
|
|
99
|
-
id: "codex", name: "Codex",
|
|
100
|
-
configs: [".codex/config.toml"], artifacts: [".codex/sessions", ".codex/archived_sessions"],
|
|
101
|
-
hookConfigPath: ".codex/hooks.json", hookEventName: "PreToolUse",
|
|
102
|
-
mergeHookConfig: mergeAll([mergeClaudeStyleHooks("PreToolUse"), mergeClaudeStyleHooks("UserPromptSubmit")]),
|
|
103
|
-
unmergeHookConfig: unmergeAll([unmergeClaudeStyleHooks("PreToolUse"), unmergeClaudeStyleHooks("UserPromptSubmit")]),
|
|
104
|
-
adapter: "passthrough", verifiedPayload: true,
|
|
105
|
-
notes: "Same hooks.json shape and stdin payload fields as Claude Code (session_id, cwd, hook_event_name, tool_name, tool_input, tool_use_id). Also installs UserPromptSubmit for the same disabled-agent-at-prompt-time reason."
|
|
106
|
-
},
|
|
107
|
-
{
|
|
108
|
-
id: "cursor", name: "Cursor",
|
|
109
|
-
// ".cursor/hooks.json" is the file beam itself writes on install, so it can never be used to
|
|
110
|
-
// detect a pre-existing Cursor install (circular). The bare ".cursor" directory is what
|
|
111
|
-
// Cursor itself creates just from being used, so it's a real presence signal.
|
|
112
|
-
configs: [".cursor"], artifacts: [".cursor/projects"],
|
|
113
|
-
hookConfigPath: ".cursor/hooks.json", hookEventName: "preToolUse",
|
|
114
|
-
mergeHookConfig: mergeAll([mergeCursorHooks("preToolUse"), mergeCursorHooks("beforeSubmitPrompt")]),
|
|
115
|
-
unmergeHookConfig: unmergeAll([unmergeCursorHooks("preToolUse"), unmergeCursorHooks("beforeSubmitPrompt")]),
|
|
116
|
-
adapter: "passthrough", verifiedPayload: true,
|
|
117
|
-
notes: "Payload verified against cursor.com/docs/hooks; extra fields (model, conversation_id, ...) are ignored, not misread. Also installs beforeSubmitPrompt -- unlike preToolUse's permission field, that event's block contract is {continue: false, user_message} (see client.ts emitDeny)."
|
|
118
|
-
},
|
|
119
|
-
{
|
|
120
|
-
id: "copilot-cli", name: "GitHub Copilot CLI",
|
|
121
|
-
// Same circularity as Cursor above: ".copilot/hooks/beam.json" is our own output.
|
|
122
|
-
// ".copilot/config.json" is what the real Copilot CLI writes on its own first run.
|
|
123
|
-
configs: [".copilot/config.json"], artifacts: [".copilot/session-state"],
|
|
124
|
-
hookConfigPath: ".copilot/hooks/beam.json", hookEventName: "preToolUse",
|
|
125
|
-
mergeHookConfig: mergeAll([mergeCopilotHooks("preToolUse"), mergeCopilotHooks("userPromptSubmitted")]),
|
|
126
|
-
unmergeHookConfig: unmergeAll([unmergeCopilotHooks("preToolUse"), unmergeCopilotHooks("userPromptSubmitted")]),
|
|
127
|
-
adapter: "copilot-camel", verifiedPayload: true,
|
|
128
|
-
notes: "Payload verified against docs.github.com Copilot hooks reference (camelCase: sessionId, cwd, toolName, toolArgs). userPromptSubmitted is also installed for observation/audit only -- GitHub's docs state its stdout is ignored (no blocking contract), so a disabled agent is only actually stopped at its first preToolUse call."
|
|
129
|
-
},
|
|
130
|
-
{
|
|
131
|
-
id: "gemini", name: "Gemini CLI",
|
|
132
|
-
// ".gemini/settings.json" is our own write target (circular, like Cursor/Copilot above); the
|
|
133
|
-
// bare ".gemini" directory (created by any use of the Gemini CLI, e.g. mcp_config.json,
|
|
134
|
-
// projects/) is the reliable presence signal instead.
|
|
135
|
-
configs: [".gemini"], artifacts: [".gemini/tmp"],
|
|
136
|
-
hookConfigPath: ".gemini/settings.json", hookEventName: "BeforeTool",
|
|
137
|
-
// BeforeTool/BeforeAgent use the exact same {matcher, hooks: [{type, command}]} array shape
|
|
138
|
-
// as Claude Code's PreToolUse/UserPromptSubmit (geminicli.com/docs/hooks/reference) --
|
|
139
|
-
// beam previously targeted ".agents/hooks.json" with a "pre_tool_execution" event and a
|
|
140
|
-
// "beam-security" wrapper, which is actually the unrelated Gemini API "Managed Agents"
|
|
141
|
-
// sandbox hook contract (ai.google.dev/gemini-api/docs/agent-hooks), not Gemini CLI's own.
|
|
142
|
-
mergeHookConfig: mergeAll([mergeClaudeStyleHooks("BeforeTool"), mergeClaudeStyleHooks("BeforeAgent")]),
|
|
143
|
-
unmergeHookConfig: unmergeAll([unmergeClaudeStyleHooks("BeforeTool"), unmergeClaudeStyleHooks("BeforeAgent")]),
|
|
144
|
-
adapter: "gemini", verifiedPayload: true,
|
|
145
|
-
notes: "Payload verified against geminicli.com/docs/hooks/reference: stdin is flat (session_id, cwd, hook_event_name, tool_name, tool_input, prompt), not the nested tool_call shape the \"gemini\" adapter also tolerates as a defensive fallback. Also installs BeforeAgent so a disabled agent is stopped before the prompt reaches the model, not only at its first tool call; both events accept {decision: \"deny\", reason} and both also honor exit code 2 (see client.ts's EXIT_2_FAILS_CLOSED)."
|
|
146
|
-
},
|
|
147
|
-
{
|
|
148
|
-
id: "opencode", name: "OpenCode",
|
|
149
|
-
configs: [".config/opencode/opencode.json", ".config/opencode/opencode.jsonc"], artifacts: [".local/share/opencode"],
|
|
150
|
-
hookConfigPath: null, pluginPath: ".opencode/plugins/beam.ts", hookEventName: "tool.execute.before",
|
|
151
|
-
mergeHookConfig: null, unmergeHookConfig: null, adapter: "passthrough", verifiedPayload: true,
|
|
152
|
-
notes: "Uses OpenCode's native tool.execute.before plugin hook."
|
|
153
|
-
},
|
|
154
|
-
{
|
|
155
|
-
id: "hermes", name: "Hermes Agent",
|
|
156
|
-
configs: [".hermes"], artifacts: [],
|
|
157
|
-
hookConfigPath: ".hermes/config.yaml", hookEventName: "pre_tool_call",
|
|
158
|
-
mergeHookConfig: null, unmergeHookConfig: null, customInstall: true,
|
|
159
|
-
adapter: "pre-tool-alias", verifiedPayload: true,
|
|
160
|
-
notes: "Installs a shell hook under hooks.pre_tool_call in ~/.hermes/config.yaml (default profile), between beam:begin/beam:end comments. Stdin fields (hook_event_name, tool_name, tool_input, session_id, cwd) and the block contract ({decision: \"block\", reason} or exit 2) verified against hermes-agent.nousresearch.com/docs/user-guide/features/hooks; tool_input shapes per Hermes tool are not. Hermes asks you to approve the hook on first use, and a hook that errors or times out fails open unless fail_closed is set."
|
|
161
|
-
},
|
|
162
|
-
{
|
|
163
|
-
id: "openclaw", name: "OpenClaw",
|
|
164
|
-
configs: [".openclaw"], artifacts: [],
|
|
165
|
-
hookConfigPath: null, hookEventName: "before_tool_call",
|
|
166
|
-
mergeHookConfig: null, unmergeHookConfig: null, customInstall: true,
|
|
167
|
-
adapter: "pre-tool-alias", verifiedPayload: false,
|
|
168
|
-
notes: "OpenClaw has no hook config file: beam writes a small plugin that registers api.on(\"before_tool_call\") and links it with 'openclaw plugins install --link' and 'openclaw plugins enable'. Registration and the { block, blockReason } return follow docs.openclaw.ai/plugins/hooks and published plugins, but the event field names are not printed in a reference, so the plugin reads them defensively; treat enforcement as unverified until you see a real event."
|
|
169
|
-
},
|
|
170
|
-
{
|
|
171
|
-
id: "jev", name: "Jev (TypeSafe AI)",
|
|
172
|
-
// TypeSafe's own Jev tooling and at least half a dozen independent third-party CLIs
|
|
173
|
-
// (jev-cli, jev-axi, jev-guard, ...) each pick their own config layout, so unlike the
|
|
174
|
-
// agents above there's no single vendor-documented directory or hook contract to target.
|
|
175
|
-
// These are the paths seen across that ecosystem as of Sept 2026; treat as best-effort.
|
|
176
|
-
configs: [".config/jev", ".config/jev-axi", ".jev"], artifacts: [".config/jev-axi/stats"],
|
|
177
|
-
hookConfigPath: null, hookEventName: null,
|
|
178
|
-
mergeHookConfig: null, unmergeHookConfig: null,
|
|
179
|
-
adapter: "generic", verifiedPayload: false, unverifiedEcosystem: true,
|
|
180
|
-
notes: "Jev is TypeSafe AI's non-generative 'System One' judgment model (launched 2026-09-15), " +
|
|
181
|
-
"increasingly wired into other agents' tool-call gating (e.g. jev-guard) rather than run as " +
|
|
182
|
-
"a coding agent itself. No single vendor hook contract exists yet across the many third-party " +
|
|
183
|
-
"CLIs wrapping it, so this entry is discovery-only (config/artifact presence), not installable."
|
|
184
|
-
},
|
|
185
|
-
];
|
|
4
|
+
export const AGENTS = HOOK_CATALOG.map(spec => ({
|
|
5
|
+
id: spec.id, name: spec.name, configs: spec.id === "copilot-cli" ? [".copilot/config.json"] : [spec.detect], artifacts: { "claude-code": [".claude/projects"], codex: [".codex/sessions", ".codex/archived_sessions"], cursor: [".cursor/projects"], "copilot-cli": [".copilot/session-state"], gemini: [".gemini/tmp"], opencode: [".local/share/opencode"] }[spec.id] ?? [],
|
|
6
|
+
hookConfigPath: spec.format === "plugin" ? null : spec.path,
|
|
7
|
+
pluginPath: spec.format === "plugin" ? spec.path : null,
|
|
8
|
+
hookEventName: spec.events.find(e => hookKind(e) === "pre") ?? null,
|
|
9
|
+
hookEvents: spec.events,
|
|
10
|
+
customInstall: true,
|
|
11
|
+
mergeHookConfig: (existing, command) => mergeHookSpec(spec, existing, command),
|
|
12
|
+
unmergeHookConfig: (existing, own) => unmergeHookSpec(spec, existing, own),
|
|
13
|
+
adapter: ["claude-code", "codex", "cursor"].includes(spec.id) ? "passthrough" : spec.id === "hermes" ? "pre-tool-alias" : spec.id === "copilot-cli" || spec.id === "vscode" ? "copilot-camel" : spec.id === "gemini" ? "gemini" : "generic",
|
|
14
|
+
verifiedPayload: ["claude-code", "codex", "cursor", "copilot-cli", "gemini", "hermes"].includes(spec.id),
|
|
15
|
+
notes: "Configuration and payload mapping based on Numbat's host contracts; validate with a live session on your agent version. See docs/agent-hooks.md.",
|
|
16
|
+
}));
|
|
17
|
+
AGENTS.push({
|
|
18
|
+
id: "jev", name: "Jev (TypeSafe AI)",
|
|
19
|
+
// TypeSafe's own Jev tooling and at least half a dozen independent third-party CLIs
|
|
20
|
+
// (jev-cli, jev-axi, jev-guard, ...) each pick their own config layout, so unlike the
|
|
21
|
+
// agents above there's no single vendor-documented directory or hook contract to target.
|
|
22
|
+
// These are the paths seen across that ecosystem as of Sept 2026; treat as best-effort.
|
|
23
|
+
configs: [".config/jev", ".config/jev-axi", ".jev"], artifacts: [".config/jev-axi/stats"],
|
|
24
|
+
hookConfigPath: null, hookEventName: null,
|
|
25
|
+
mergeHookConfig: null, unmergeHookConfig: null,
|
|
26
|
+
adapter: "generic", verifiedPayload: false, unverifiedEcosystem: true,
|
|
27
|
+
notes: "Jev is TypeSafe AI's non-generative 'System One' judgment model (launched 2026-09-15), " +
|
|
28
|
+
"increasingly wired into other agents' tool-call gating (e.g. jev-guard) rather than run as " +
|
|
29
|
+
"a coding agent itself. No single vendor hook contract exists yet across the many third-party " +
|
|
30
|
+
"CLIs wrapping it, so this entry is discovery-only (config/artifact presence), not installable."
|
|
31
|
+
});
|
|
186
32
|
export function findAgent(id) {
|
|
187
33
|
return AGENTS.find(a => a.id === id);
|
|
188
34
|
}
|
package/dist/agents.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"agents.js","sourceRoot":"","sources":["../src/agents.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,
|
|
1
|
+
{"version":3,"file":"agents.js","sourceRoot":"","sources":["../src/agents.ts"],"names":[],"mappings":"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"}
|
package/dist/approvals.d.ts
CHANGED
|
@@ -33,6 +33,14 @@ export interface ApprovalRequest {
|
|
|
33
33
|
status: ApprovalStatus;
|
|
34
34
|
decision?: ApprovalDecision;
|
|
35
35
|
fingerprint: string;
|
|
36
|
+
/** Why the requester says it needs this access. Shown to the approver; not itself a grant. */
|
|
37
|
+
reason?: string;
|
|
38
|
+
/** The approver's own explanation, set when the decision is applied (see applyApproval()). */
|
|
39
|
+
decision_reason?: string;
|
|
40
|
+
/** How long an APPROVED decision grants: the approver's choice, 10-60 minutes. Only meaningful
|
|
41
|
+
* alongside status "APPROVED"; unset on every other status. Drives the local TTL on the
|
|
42
|
+
* requesting device's approved-actions.json (see beam-cli's approved-actions.ts). */
|
|
43
|
+
grant_minutes?: number;
|
|
36
44
|
}
|
|
37
45
|
export interface ApprovalPolicy {
|
|
38
46
|
level: ApprovalLevel;
|
|
@@ -61,5 +69,9 @@ export declare function createApprovalRequest(input: Omit<ApprovalRequest, "requ
|
|
|
61
69
|
export declare function validateApprovalRequest(request: ApprovalRequest): boolean;
|
|
62
70
|
export declare function approvalAuditEvent(previous: ApprovalRequest, next: ApprovalRequest, approver: string): string;
|
|
63
71
|
export declare function formatApprovalRequest(request: ApprovalRequest): string;
|
|
64
|
-
export declare
|
|
72
|
+
export declare const MIN_GRANT_MINUTES = 10;
|
|
73
|
+
export declare const MAX_GRANT_MINUTES = 60;
|
|
74
|
+
export declare const DEFAULT_GRANT_MINUTES = 15;
|
|
75
|
+
export declare function clampGrantMinutes(minutes: number | undefined): number;
|
|
76
|
+
export declare function applyApproval(request: ApprovalRequest, decision: ApprovalDecision, approver: string, now?: number, decisionReason?: string, grantMinutes?: number): ApprovalRequest;
|
|
65
77
|
export declare function approvalStillValid(request: ApprovalRequest, current: Omit<ApprovalRequest, "request_id" | "created_at" | "expires_at" | "status" | "fingerprint">, now?: number): boolean;
|
package/dist/approvals.js
CHANGED
|
@@ -65,7 +65,15 @@ export function formatApprovalRequest(request) {
|
|
|
65
65
|
const resource = request.path || request.url || request.command || request.tool;
|
|
66
66
|
return `[${request.level}] ${request.agent} ${request.tool}: ${resource} (risk ${request.risk_score}/100 ${request.risk_level})`;
|
|
67
67
|
}
|
|
68
|
-
export
|
|
68
|
+
export const MIN_GRANT_MINUTES = 10;
|
|
69
|
+
export const MAX_GRANT_MINUTES = 60;
|
|
70
|
+
export const DEFAULT_GRANT_MINUTES = 15;
|
|
71
|
+
export function clampGrantMinutes(minutes) {
|
|
72
|
+
if (!Number.isFinite(minutes))
|
|
73
|
+
return DEFAULT_GRANT_MINUTES;
|
|
74
|
+
return Math.min(MAX_GRANT_MINUTES, Math.max(MIN_GRANT_MINUTES, Math.round(minutes)));
|
|
75
|
+
}
|
|
76
|
+
export function applyApproval(request, decision, approver, now = Date.now(), decisionReason, grantMinutes) {
|
|
69
77
|
if (!validateApprovalRequest(request))
|
|
70
78
|
return { ...request, status: "DENIED", decision: "DENY" };
|
|
71
79
|
if (Date.parse(request.expires_at) <= now || decision === "EXPIRE")
|
|
@@ -74,11 +82,14 @@ export function applyApproval(request, decision, approver, now = Date.now()) {
|
|
|
74
82
|
return { ...request, status: "DENIED", decision: "DENY" };
|
|
75
83
|
if (!request.approver.includes(approver))
|
|
76
84
|
return { ...request, status: "DENIED", decision: "DENY" };
|
|
85
|
+
// decisionReason/grantMinutes only ever reflect a genuine decision made below by an authorized
|
|
86
|
+
// approver on a still-pending request -- never attached to the fail-closed guard rejections
|
|
87
|
+
// above, which aren't a real person's explanation and shouldn't be presented as one.
|
|
77
88
|
if (decision === "DENY")
|
|
78
|
-
return { ...request, status: "DENIED", decision };
|
|
89
|
+
return { ...request, status: "DENIED", decision, decision_reason: decisionReason };
|
|
79
90
|
if (request.status !== "PENDING")
|
|
80
91
|
return request;
|
|
81
|
-
return { ...request, status: "APPROVED", decision };
|
|
92
|
+
return { ...request, status: "APPROVED", decision, decision_reason: decisionReason, grant_minutes: clampGrantMinutes(grantMinutes) };
|
|
82
93
|
}
|
|
83
94
|
export function approvalStillValid(request, current, now = Date.now()) {
|
|
84
95
|
return request.status === "APPROVED" && Date.parse(request.expires_at) > now && request.fingerprint === approvalFingerprint(current);
|
package/dist/approvals.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"approvals.js","sourceRoot":"","sources":["../src/approvals.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,UAAU,EAAE,UAAU,EAAE,MAAM,aAAa,CAAC;
|
|
1
|
+
{"version":3,"file":"approvals.js","sourceRoot":"","sources":["../src/approvals.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
export interface ApprovedActionTarget {
|
|
2
|
+
agent: string;
|
|
3
|
+
tool: string;
|
|
4
|
+
command?: string;
|
|
5
|
+
path?: string;
|
|
6
|
+
url?: string;
|
|
7
|
+
}
|
|
8
|
+
export declare function approvedActionsFile(): string;
|
|
9
|
+
export declare function approvedActionFingerprint(target: ApprovedActionTarget): string;
|
|
10
|
+
export declare function recordApprovedAction(target: ApprovedActionTarget, ttlMs?: number): Promise<void>;
|
|
11
|
+
export declare function consumeApprovedAction(target: ApprovedActionTarget): Promise<boolean>;
|
|
@@ -0,0 +1,53 @@
|
|
|
1
|
+
import { mkdir, readFile, rename, writeFile } from "node:fs/promises";
|
|
2
|
+
import { join } from "node:path";
|
|
3
|
+
import { createHash } from "node:crypto";
|
|
4
|
+
import { getDataDirectory } from "./config.js";
|
|
5
|
+
export function approvedActionsFile() { return join(getDataDirectory(), "approved-actions.json"); }
|
|
6
|
+
export function approvedActionFingerprint(target) {
|
|
7
|
+
return createHash("sha256").update(JSON.stringify({
|
|
8
|
+
agent: target.agent.toLowerCase(),
|
|
9
|
+
tool: target.tool.toLowerCase(),
|
|
10
|
+
command: target.command || "",
|
|
11
|
+
path: target.path || "",
|
|
12
|
+
url: target.url || "",
|
|
13
|
+
})).digest("hex");
|
|
14
|
+
}
|
|
15
|
+
async function readApprovedActions() {
|
|
16
|
+
try {
|
|
17
|
+
const parsed = JSON.parse(await readFile(approvedActionsFile(), "utf8"));
|
|
18
|
+
return Array.isArray(parsed)
|
|
19
|
+
? parsed.filter((e) => Boolean(e) && typeof e === "object" && typeof e.fingerprint === "string" && typeof e.expiresAt === "string")
|
|
20
|
+
: [];
|
|
21
|
+
}
|
|
22
|
+
catch {
|
|
23
|
+
return [];
|
|
24
|
+
}
|
|
25
|
+
}
|
|
26
|
+
async function writeApprovedActions(entries) {
|
|
27
|
+
const dir = getDataDirectory();
|
|
28
|
+
await mkdir(dir, { recursive: true, mode: 0o700 });
|
|
29
|
+
const file = approvedActionsFile();
|
|
30
|
+
const temp = `${file}.tmp`;
|
|
31
|
+
await writeFile(temp, JSON.stringify(entries) + "\n", { mode: 0o600 });
|
|
32
|
+
await rename(temp, file);
|
|
33
|
+
}
|
|
34
|
+
// Bounded (default 15 minutes) -- an approval is permission to proceed now, not a standing
|
|
35
|
+
// exception. Capped at 200 entries so a burst of asks can't grow this file unbounded.
|
|
36
|
+
export async function recordApprovedAction(target, ttlMs = 15 * 60_000) {
|
|
37
|
+
const live = (await readApprovedActions()).filter(e => Date.parse(e.expiresAt) > Date.now());
|
|
38
|
+
live.push({ fingerprint: approvedActionFingerprint(target), expiresAt: new Date(Date.now() + ttlMs).toISOString() });
|
|
39
|
+
await writeApprovedActions(live.slice(-200));
|
|
40
|
+
}
|
|
41
|
+
// Single-use: APPROVE_ONCE semantics (see approvals.ts) -- a match is removed on read so it can't
|
|
42
|
+
// silently re-authorize a later, unrelated repeat of the same command without asking again.
|
|
43
|
+
export async function consumeApprovedAction(target) {
|
|
44
|
+
const fingerprint = approvedActionFingerprint(target);
|
|
45
|
+
const now = Date.now();
|
|
46
|
+
const entries = await readApprovedActions();
|
|
47
|
+
const match = entries.find(e => e.fingerprint === fingerprint && Date.parse(e.expiresAt) > now);
|
|
48
|
+
if (!match)
|
|
49
|
+
return false;
|
|
50
|
+
await writeApprovedActions(entries.filter(e => e !== match && Date.parse(e.expiresAt) > now));
|
|
51
|
+
return true;
|
|
52
|
+
}
|
|
53
|
+
//# sourceMappingURL=approved-actions.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"approved-actions.js","sourceRoot":"","sources":["../src/approved-actions.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
/** The rolling window an action burst is measured over. */
|
|
2
|
+
export declare const ANOMALY_WINDOW_MS: number;
|
|
3
|
+
/** High/critical-severity actions within the window that count as a burst. */
|
|
4
|
+
export declare const ANOMALY_THRESHOLD = 5;
|
|
5
|
+
export interface AnomalyResult {
|
|
6
|
+
triggered: boolean;
|
|
7
|
+
count: number;
|
|
8
|
+
windowMinutes: number;
|
|
9
|
+
}
|
|
10
|
+
/**
|
|
11
|
+
* Records one HIGH/CRITICAL-severity action for `sessionId` (a LOW/MEDIUM `riskLevel` is a
|
|
12
|
+
* no-op: it still prunes/persists housekeeping but never adds an event or can trigger), and
|
|
13
|
+
* reports whether this session has hit `ANOMALY_THRESHOLD` such actions within
|
|
14
|
+
* `ANOMALY_WINDOW_MS`. `home` is the beam data directory (injectable for tests).
|
|
15
|
+
*/
|
|
16
|
+
export declare function recordAndCheckAnomaly(sessionId: string, riskLevel: string, home?: string): Promise<AnomalyResult>;
|
|
@@ -0,0 +1,66 @@
|
|
|
1
|
+
// Behavioral anomaly detection: flags a burst of high/critical-severity actions from one agent
|
|
2
|
+
// session within a short window, independent of whether any single action was individually
|
|
3
|
+
// blocked. An agent that's been talked into a chain of risky actions (prompt injection, a
|
|
4
|
+
// compromised skill, a confused multi-step plan) often shows this shape even when each step
|
|
5
|
+
// individually only rates "ask" rather than "deny" -- worth a human's attention as a pattern,
|
|
6
|
+
// not just as isolated findings. Node-only (fs-backed session state), called from the CLI hook
|
|
7
|
+
// path in client.ts only -- same separation from data-detectors.ts/rule-catalog.ts as
|
|
8
|
+
// entity-redaction.ts, which stay pure so they can keep backing the edge-runtime SDK path.
|
|
9
|
+
import { mkdir, readFile, writeFile } from "node:fs/promises";
|
|
10
|
+
import { dirname, join } from "node:path";
|
|
11
|
+
import { getDataDirectory } from "./config.js";
|
|
12
|
+
import { withFileLock } from "./file-lock.js";
|
|
13
|
+
function activityPath(home) {
|
|
14
|
+
return join(home, "behavioral-activity.json");
|
|
15
|
+
}
|
|
16
|
+
async function readActivity(home) {
|
|
17
|
+
try {
|
|
18
|
+
return JSON.parse(await readFile(activityPath(home), "utf8"));
|
|
19
|
+
}
|
|
20
|
+
catch {
|
|
21
|
+
return {};
|
|
22
|
+
}
|
|
23
|
+
}
|
|
24
|
+
async function writeActivity(map, home) {
|
|
25
|
+
const path = activityPath(home);
|
|
26
|
+
await mkdir(dirname(path), { recursive: true, mode: 0o700 });
|
|
27
|
+
await writeFile(path, `${JSON.stringify(map, null, 2)}\n`, { mode: 0o600 });
|
|
28
|
+
}
|
|
29
|
+
/** The rolling window an action burst is measured over. */
|
|
30
|
+
export const ANOMALY_WINDOW_MS = 10 * 60 * 1000; // 10 minutes
|
|
31
|
+
/** High/critical-severity actions within the window that count as a burst. */
|
|
32
|
+
export const ANOMALY_THRESHOLD = 5;
|
|
33
|
+
// A session with no new high/critical event in this long is dropped entirely, so the file
|
|
34
|
+
// doesn't grow forever -- only timestamps live here, nothing sensitive either way.
|
|
35
|
+
const SESSION_TTL_MS = 24 * 60 * 60 * 1000;
|
|
36
|
+
/**
|
|
37
|
+
* Records one HIGH/CRITICAL-severity action for `sessionId` (a LOW/MEDIUM `riskLevel` is a
|
|
38
|
+
* no-op: it still prunes/persists housekeeping but never adds an event or can trigger), and
|
|
39
|
+
* reports whether this session has hit `ANOMALY_THRESHOLD` such actions within
|
|
40
|
+
* `ANOMALY_WINDOW_MS`. `home` is the beam data directory (injectable for tests).
|
|
41
|
+
*/
|
|
42
|
+
export async function recordAndCheckAnomaly(sessionId, riskLevel, home = getDataDirectory()) {
|
|
43
|
+
if (!sessionId)
|
|
44
|
+
return { triggered: false, count: 0, windowMinutes: ANOMALY_WINDOW_MS / 60_000 };
|
|
45
|
+
// Locked end-to-end (read, mutate, write) so concurrent tool calls in the same turn -- each
|
|
46
|
+
// spawning its own `beam hook` process -- don't lose each other's increments to a read-write
|
|
47
|
+
// race (see file-lock.ts). Without this, a genuine 5-action parallel burst could collapse to
|
|
48
|
+
// a single persisted event, since every process would independently read "0 prior events".
|
|
49
|
+
return withFileLock(activityPath(home), async () => {
|
|
50
|
+
const map = await readActivity(home);
|
|
51
|
+
const now = Date.now();
|
|
52
|
+
for (const [id, session] of Object.entries(map)) {
|
|
53
|
+
const last = session.events.at(-1) ?? 0;
|
|
54
|
+
if (now - last > SESSION_TTL_MS)
|
|
55
|
+
delete map[id];
|
|
56
|
+
}
|
|
57
|
+
const session = map[sessionId] ?? { events: [] };
|
|
58
|
+
if (riskLevel === "HIGH" || riskLevel === "CRITICAL")
|
|
59
|
+
session.events.push(now);
|
|
60
|
+
session.events = session.events.filter(at => now - at <= ANOMALY_WINDOW_MS);
|
|
61
|
+
map[sessionId] = session;
|
|
62
|
+
await writeActivity(map, home).catch(() => { });
|
|
63
|
+
return { triggered: session.events.length >= ANOMALY_THRESHOLD, count: session.events.length, windowMinutes: ANOMALY_WINDOW_MS / 60_000 };
|
|
64
|
+
});
|
|
65
|
+
}
|
|
66
|
+
//# sourceMappingURL=behavioral-anomaly.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"behavioral-anomaly.js","sourceRoot":"","sources":["../src/behavioral-anomaly.ts"],"names":[],"mappings":"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"}
|