@agent-beam/beam 0.2.20 → 0.2.22

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 (67) hide show
  1. package/README.md +19 -0
  2. package/dist/account.js +1 -1
  3. package/dist/account.js.map +1 -1
  4. package/dist/agents.d.ts +8 -2
  5. package/dist/agents.js +7 -7
  6. package/dist/agents.js.map +1 -1
  7. package/dist/cli.js +289 -41
  8. package/dist/cli.js.map +1 -1
  9. package/dist/client.js +23 -6
  10. package/dist/client.js.map +1 -1
  11. package/dist/connect.js +1 -1
  12. package/dist/connect.js.map +1 -1
  13. package/dist/enroll.d.ts +0 -4
  14. package/dist/enroll.js +2 -60
  15. package/dist/enroll.js.map +1 -1
  16. package/dist/forward.d.ts +2 -0
  17. package/dist/forward.js +19 -0
  18. package/dist/forward.js.map +1 -1
  19. package/dist/hook-adapters.js +14 -1
  20. package/dist/hook-adapters.js.map +1 -1
  21. package/dist/install.d.ts +2 -0
  22. package/dist/install.js +58 -6
  23. package/dist/install.js.map +1 -1
  24. package/dist/policy.d.ts +1 -0
  25. package/dist/policy.js +11 -3
  26. package/dist/policy.js.map +1 -1
  27. package/dist/serve.d.ts +2 -0
  28. package/dist/serve.js +47 -2
  29. package/dist/serve.js.map +1 -1
  30. package/dist/setup.js +33 -2
  31. package/dist/setup.js.map +1 -1
  32. package/dist/skill-context.d.ts +11 -0
  33. package/dist/skill-context.js +78 -0
  34. package/dist/skill-context.js.map +1 -0
  35. package/dist/skill-gate.d.ts +21 -0
  36. package/dist/skill-gate.js +180 -0
  37. package/dist/skill-gate.js.map +1 -0
  38. package/dist/skill-inventory.d.ts +81 -0
  39. package/dist/skill-inventory.js +167 -0
  40. package/dist/skill-inventory.js.map +1 -0
  41. package/dist/skill-risk-rules.d.ts +17 -0
  42. package/dist/skill-risk-rules.js +61 -0
  43. package/dist/skill-risk-rules.js.map +1 -0
  44. package/dist/skill-scan.d.ts +27 -0
  45. package/dist/skill-scan.js +157 -0
  46. package/dist/skill-scan.js.map +1 -0
  47. package/dist/skill-setup.d.ts +11 -0
  48. package/dist/skill-setup.js +26 -0
  49. package/dist/skill-setup.js.map +1 -0
  50. package/dist/skill-sync.d.ts +40 -0
  51. package/dist/skill-sync.js +79 -0
  52. package/dist/skill-sync.js.map +1 -0
  53. package/dist/skill-watch.d.ts +16 -0
  54. package/dist/skill-watch.js +41 -0
  55. package/dist/skill-watch.js.map +1 -0
  56. package/dist/skills.d.ts +33 -0
  57. package/dist/skills.js +139 -0
  58. package/dist/skills.js.map +1 -0
  59. package/dist/tamper.d.ts +9 -0
  60. package/dist/tamper.js +116 -0
  61. package/dist/tamper.js.map +1 -0
  62. package/dist/trusted-skills.d.ts +9 -0
  63. package/dist/trusted-skills.js +31 -0
  64. package/dist/trusted-skills.js.map +1 -0
  65. package/dist/tty-prompt.js +3 -1
  66. package/dist/tty-prompt.js.map +1 -1
  67. package/package.json +2 -2
@@ -0,0 +1,180 @@
1
+ import { readdir, stat } from "node:fs/promises";
2
+ import { basename, dirname, isAbsolute, join, resolve, sep } from "node:path";
3
+ import { homedir } from "node:os";
4
+ import { SKILL_DIRS, isBlocked } from "./skills.js";
5
+ import { scanSkillDir, scanSkillFile } from "./skill-scan.js";
6
+ import { readInventory } from "./skill-inventory.js";
7
+ import { readTrustedSkills } from "./trusted-skills.js";
8
+ // How each agent loads a skill (tool names are matched case-insensitively):
9
+ // - Claude Code `Skill`, OpenCode `skill`, Gemini CLI `activate_skill`: a native tool taking a name.
10
+ // - Claude Code `Read`, Cursor `Read`, Copilot CLI `view`, Gemini `read_file`/`read_many_files`,
11
+ // OpenCode `read`: a file-read tool on SKILL.md or one of the skill's reference files.
12
+ // - Codex (no read tool at all) and every other agent's shell tool: `cat`/`sed`/`head`... on the file.
13
+ const NAME_TOOLS = /^(?:skill|activate_skill|use_skill|load_skill)$/i;
14
+ const READ_TOOLS = /^(?:read|read_file|readfile|read_text_file|read_many_files|view|open|cat|fs_read)$/i;
15
+ const SHELL_TOOLS = /^(?:bash|shell|sh|zsh|powershell|pwsh|run_shell_command|run_terminal_cmd|execute_bash|exec_command|exec|local_shell|terminal|command)$/i;
16
+ // Verbs that put a file's content in front of the model. Deliberately not `rm`/`cp`/`git`: a
17
+ // command merely *mentioning* a skill path isn't loading it.
18
+ const READ_VERBS = /^(?:cat|sed|head|tail|less|more|bat|batcat|nl|awk|type|get-content|gc|tac|cut|strings|xxd|od|rg|grep|view|open|python3?|node|ruby|perl)$/i;
19
+ const SKILL_MD = /^skills?\.md$/i;
20
+ const PATH_KEYS = ["file_path", "path", "filePath", "absolute_path", "target_file", "file", "filename"];
21
+ async function isFile(p) { try {
22
+ return (await stat(p)).isFile();
23
+ }
24
+ catch {
25
+ return false;
26
+ } }
27
+ // Copilot CLI sends toolArgs as a JSON *string*; other agents send an object. Accept both.
28
+ export function coerceInput(input) {
29
+ if (typeof input === "string") {
30
+ try {
31
+ return coerceInput(JSON.parse(input));
32
+ }
33
+ catch {
34
+ return { command: input };
35
+ }
36
+ }
37
+ return input && typeof input === "object" && !Array.isArray(input) ? input : {};
38
+ }
39
+ // Finds <name>/SKILL.md in the standard skill roots, then the machine-wide inventory, then
40
+ // (best effort, bounded) inside installed plugins, whose layout varies. A "plugin:skill" name is
41
+ // reduced to its skill part.
42
+ async function findNamedSkill(rawName, cwd, home) {
43
+ const name = rawName.replace(/^\//, "").split(":").pop() ?? "";
44
+ if (!/^[\w.-]+$/.test(name))
45
+ return null;
46
+ for (const root of [cwd, home]) {
47
+ for (const rel of SKILL_DIRS) {
48
+ const dir = join(root, rel, name);
49
+ if (root && await isFile(join(dir, "SKILL.md")))
50
+ return dir;
51
+ }
52
+ }
53
+ const fromIndex = (await readInventory())?.skills.find(s => s.name === name);
54
+ if (fromIndex && await isFile(join(fromIndex.dir, "SKILL.md")))
55
+ return fromIndex.dir;
56
+ const walk = async (dir, depth) => {
57
+ if (depth > 6)
58
+ return null;
59
+ let entries;
60
+ try {
61
+ entries = await readdir(dir, { withFileTypes: true });
62
+ }
63
+ catch {
64
+ return null;
65
+ }
66
+ for (const e of entries) {
67
+ if (!e.isDirectory() || e.name === "node_modules" || e.name === ".git")
68
+ continue;
69
+ const full = join(dir, e.name);
70
+ if (e.name === name && await isFile(join(full, "SKILL.md")))
71
+ return full;
72
+ const hit = await walk(full, depth + 1);
73
+ if (hit)
74
+ return hit;
75
+ }
76
+ return null;
77
+ };
78
+ return walk(join(home, ".claude", "plugins"), 0);
79
+ }
80
+ // Markdown files a read tool is pointed at: the usual path keys, plus any string (or list of
81
+ // strings) in the input that looks like a .md path -- covers Gemini's read_many_files.
82
+ function readPaths(input) {
83
+ const out = new Set();
84
+ for (const key of PATH_KEYS)
85
+ if (typeof input[key] === "string")
86
+ out.add(input[key]);
87
+ for (const value of Object.values(input)) {
88
+ if (typeof value === "string")
89
+ out.add(value);
90
+ else if (Array.isArray(value))
91
+ for (const v of value)
92
+ if (typeof v === "string")
93
+ out.add(v);
94
+ }
95
+ return [...out].filter(p => /\.md$/i.test(p.trim()));
96
+ }
97
+ // Paths a shell command reads with a content-printing verb: `cat ~/.codex/skills/x/SKILL.md`,
98
+ // `sed -n '1,200p' SKILL.md`, `head -50 skills.md && echo ok`, `Get-Content .\SKILL.md`.
99
+ export function shellReadPaths(command) {
100
+ const out = [];
101
+ for (const segment of command.split(/&&|\|\||[;|\n]/)) {
102
+ const tokens = segment.trim().match(/"[^"]*"|'[^']*'|\S+/g)?.map(t => t.replace(/^["']|["']$/g, "")) ?? [];
103
+ const verb = tokens.findIndex(t => READ_VERBS.test(basename(t)));
104
+ if (verb < 0)
105
+ continue;
106
+ for (const t of tokens.slice(verb + 1))
107
+ if (/\.md$/i.test(t))
108
+ out.push(t.replace(/^~(?=$|[\\/])/, homedir()));
109
+ }
110
+ return out;
111
+ }
112
+ async function targetForFile(raw, cwd) {
113
+ const file = isAbsolute(raw) ? raw : resolve(cwd || ".", raw);
114
+ const parts = file.split(sep);
115
+ const at = parts.lastIndexOf("skills");
116
+ if (at >= 0 && at < parts.length - 2) {
117
+ // <...>/skills/<name>/<anything>.md -> the whole skill folder, so a poisoned reference file
118
+ // is judged together with the SKILL.md that pulls it in.
119
+ const dir = parts.slice(0, at + 2).join(sep);
120
+ if (await isFile(join(dir, "SKILL.md")))
121
+ return { kind: "dir", path: dir, name: parts[at + 1] };
122
+ }
123
+ if (SKILL_MD.test(basename(file)) && await isFile(file)) {
124
+ return /^skill\.md$/i.test(basename(file)) ? { kind: "dir", path: dirname(file), name: basename(dirname(file)) } : { kind: "file", path: file, name: basename(file) };
125
+ }
126
+ return null;
127
+ }
128
+ // Every skill this tool call loads into the agent. Empty when the call is unrelated to skills.
129
+ export async function resolveSkillTargets(tool, rawInput, cwd = "", home = homedir()) {
130
+ const input = coerceInput(rawInput);
131
+ const targets = new Map();
132
+ const add = (t) => { if (t)
133
+ targets.set(t.path, t); };
134
+ if (NAME_TOOLS.test(tool)) {
135
+ const name = String(input.skill ?? input.name ?? input.skill_name ?? input.command ?? "");
136
+ const dir = name ? await findNamedSkill(name, cwd, home) : null;
137
+ if (dir)
138
+ add({ kind: "dir", path: dir, name: basename(dir) });
139
+ }
140
+ else if (READ_TOOLS.test(tool)) {
141
+ for (const p of readPaths(input))
142
+ add(await targetForFile(p.trim(), cwd));
143
+ }
144
+ else if (SHELL_TOOLS.test(tool)) {
145
+ const command = Array.isArray(input.command) ? input.command.join(" ") : String(input.command ?? input.cmd ?? "");
146
+ for (const p of shellReadPaths(command))
147
+ add(await targetForFile(p, cwd));
148
+ }
149
+ return [...targets.values()];
150
+ }
151
+ export async function resolveSkillTarget(tool, input, cwd = "", home = homedir()) {
152
+ return (await resolveSkillTargets(tool, input, cwd, home))[0] ?? null;
153
+ }
154
+ export function formatSkillRisk(scan) {
155
+ const top = scan.findings.slice(0, 3).map(f => `${f.title} (${f.file}${f.line ? `:${f.line}` : ""})`).join("; ");
156
+ return `Skill "${scan.name}" risk score ${scan.score}/100 (${scan.level})${top ? `: ${top}` : ""}${scan.findings.length > 3 ? `; +${scan.findings.length - 3} more` : ""}. Review with: beam skills scan ${scan.dir}. If you trust it: beam skills trust ${scan.dir}`;
157
+ }
158
+ // Returns null when the call doesn't involve a skill at all. Otherwise: trusted (by content hash)
159
+ // or clean-enough skills pass; prompt-injection / critical skills are held for approval, or denied
160
+ // outright under an enforce policy. Medium-risk skills pass but are still scored on the event. If
161
+ // one call loads several skills, the riskiest one decides.
162
+ export async function gateSkill(tool, input, cwd, enforce, home = homedir()) {
163
+ const targets = await resolveSkillTargets(tool, input, cwd, home);
164
+ if (!targets.length)
165
+ return null;
166
+ const trusted = new Set((await readTrustedSkills()).map(t => t.hash));
167
+ const results = [];
168
+ for (const target of targets) {
169
+ const scan = target.kind === "dir" ? await scanSkillDir(target.path, target.name) : await scanSkillFile(target.path);
170
+ if (trusted.has(scan.hash))
171
+ results.push({ action: "allow", scan });
172
+ else if (!isBlocked(scan))
173
+ results.push({ action: "allow", reason: scan.findings.length ? formatSkillRisk(scan) : undefined, scan });
174
+ else
175
+ results.push({ action: enforce ? "deny" : "ask", reason: formatSkillRisk(scan), scan });
176
+ }
177
+ const rank = (r) => (r.action === "allow" ? 0 : 1000) + r.scan.score;
178
+ return results.sort((a, b) => rank(b) - rank(a))[0];
179
+ }
180
+ //# sourceMappingURL=skill-gate.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"skill-gate.js","sourceRoot":"","sources":["../src/skill-gate.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,81 @@
1
+ import { type SkillScan } from "./skill-scan.js";
2
+ export type SkillFindingSummary = {
3
+ id: string;
4
+ title: string;
5
+ severity: string;
6
+ file: string;
7
+ line?: number;
8
+ explanation?: string;
9
+ evidence?: string;
10
+ fp?: string;
11
+ };
12
+ /** Every finding, minimal: enough for the dashboard to re-score a skill after some are reviewed as safe. */
13
+ export type SkillFindingRef = {
14
+ id: string;
15
+ severity: string;
16
+ fp: string;
17
+ };
18
+ export type InventorySkill = {
19
+ /** Stable per-machine id (hash of the folder), so a report can name a skill without its path. Absent in files written before this field existed. */
20
+ id?: string;
21
+ topFindings?: SkillFindingSummary[];
22
+ allFindings?: SkillFindingRef[];
23
+ name: string;
24
+ dir: string;
25
+ agent: string;
26
+ scope: "global" | "project" | "other";
27
+ repo: string | null;
28
+ hash: string;
29
+ score: number;
30
+ level: SkillScan["level"];
31
+ findings: number;
32
+ injection: boolean;
33
+ files: number;
34
+ firstSeen: string;
35
+ lastSeen: string;
36
+ modifiedAt: string | null;
37
+ };
38
+ export type Inventory = {
39
+ version: 1;
40
+ generatedAt: string;
41
+ roots: string[];
42
+ truncated: boolean;
43
+ skills: InventorySkill[];
44
+ /** When a whole-home search last ran (`beam skills index`). Null/absent: only agent skill folders have been checked. */
45
+ lastFullScanAt?: string | null;
46
+ };
47
+ export type IndexChanges = {
48
+ added: string[];
49
+ modified: string[];
50
+ removed: string[];
51
+ };
52
+ export declare const inventoryFile: () => string;
53
+ export declare function agentSkillRoots(home?: string): Promise<string[]>;
54
+ export type DiscoverOptions = {
55
+ roots?: string[];
56
+ maxDepth?: number;
57
+ maxDirs?: number;
58
+ budgetMs?: number;
59
+ };
60
+ export declare function discoverAllSkills(options?: DiscoverOptions): Promise<{
61
+ found: Array<{
62
+ dir: string;
63
+ repo: string | null;
64
+ }>;
65
+ roots: string[];
66
+ truncated: boolean;
67
+ }>;
68
+ export declare function readInventory(): Promise<Inventory | null>;
69
+ export declare function indexSkills(options?: DiscoverOptions, home?: string): Promise<{
70
+ inventory: Inventory;
71
+ changes: IndexChanges;
72
+ }>;
73
+ export declare function trustedHashes(): Promise<Set<string>>;
74
+ export declare function deviceSkillsScore(skills: Array<{
75
+ score: number;
76
+ level: string;
77
+ hash: string;
78
+ }>, trusted: Set<string>): {
79
+ score: number;
80
+ highCount: number;
81
+ };
@@ -0,0 +1,167 @@
1
+ import { createHash } from "node:crypto";
2
+ import { access, mkdir, readFile, readdir, rename, writeFile } from "node:fs/promises";
3
+ import { homedir } from "node:os";
4
+ import { basename, join, sep } from "node:path";
5
+ import { getDataDirectory } from "./config.js";
6
+ import { redact } from "./core.js";
7
+ import { findingFingerprint, scanSkillDir } from "./skill-scan.js";
8
+ const MAX_ALL_FINDINGS = 200;
9
+ import { readTrustedSkills } from "./trusted-skills.js";
10
+ export const inventoryFile = () => join(getDataDirectory(), "skills-inventory.json");
11
+ const SKIP = new Set(["node_modules", ".git", "Library", ".Trash", "Applications", "venv", ".venv", "__pycache__", "site-packages", "target", "Pods", ".gradle", ".m2", ".cargo", ".rustup", ".npm", ".pnpm-store", ".cache", ".next", ".turbo", "DerivedData"]);
12
+ // Dot-directories are skipped wholesale (caches, tool state) except the ones agents keep skills in.
13
+ const DOT_ALLOW = new Set([".claude", ".agents", ".codex", ".cursor", ".gemini", ".github", ".windsurf", ".roo", ".cline", ".continue", ".opencode", ".copilot"]);
14
+ function classify(dir, home) {
15
+ const p = dir.split(sep);
16
+ const agent = p.includes(".claude") ? "claude-code" : p.includes(".codex") ? "codex" : p.includes(".cursor") ? "cursor"
17
+ : p.includes(".gemini") ? "gemini" : p.includes(".copilot") ? "copilot-cli" : p.includes(".opencode") ? "opencode" : p.includes(".agents") ? "universal" : p.includes(".windsurf") ? "windsurf" : "unknown";
18
+ const rel = dir.startsWith(home + sep) ? dir.slice(home.length + 1).split(sep) : [];
19
+ const globalRoot = rel.length === 3 && rel[0].startsWith(".") && rel[1] === "skills";
20
+ return { agent, scope: globalRoot ? "global" : agent === "unknown" ? "other" : "project" };
21
+ }
22
+ // The folders agents keep their own skills and plugins in. These are dot-directories directly under
23
+ // home, which macOS does not protect -- unlike Desktop, Documents, Downloads, iCloud and external
24
+ // volumes, which raise a "beam would like to access files in..." prompt the moment something walks
25
+ // them. Anything that runs unattended (the background service) must stay inside these; walking the
26
+ // rest of home is reserved for an explicit `beam skills index` / `beam skills list`.
27
+ const AGENT_HOME_DIRS = [".claude", ".agents", ".codex", ".cursor", ".gemini", ".copilot", ".windsurf", ".opencode", ".continue", ".config/opencode"];
28
+ export async function agentSkillRoots(home = homedir()) {
29
+ const found = [];
30
+ for (const rel of AGENT_HOME_DIRS) {
31
+ const dir = join(home, rel);
32
+ try {
33
+ await access(dir);
34
+ found.push(dir);
35
+ }
36
+ catch { /* not installed */ }
37
+ }
38
+ return found;
39
+ }
40
+ // Finds every folder that contains a SKILL.md. Does not descend into a skill it found (bundled
41
+ // fixtures/examples aren't separate skills), never follows symlinks, and stops at the dir/time
42
+ // budget rather than hanging on a huge disk -- `truncated` reports when that happened.
43
+ export async function discoverAllSkills(options = {}) {
44
+ // An explicit empty list means "search nothing", never "fall back to the whole home directory".
45
+ const roots = options.roots ?? [homedir()];
46
+ const maxDepth = options.maxDepth ?? 10, maxDirs = options.maxDirs ?? 400_000;
47
+ const deadline = Date.now() + (options.budgetMs ?? 60_000);
48
+ const found = [];
49
+ const seen = new Set();
50
+ let visited = 0, truncated = false;
51
+ const stack = roots.map(r => ({ dir: r, depth: 0, repo: null }));
52
+ while (stack.length) {
53
+ if (visited++ > maxDirs || Date.now() > deadline) {
54
+ truncated = true;
55
+ break;
56
+ }
57
+ const { dir, depth, repo } = stack.pop();
58
+ let entries;
59
+ try {
60
+ entries = await readdir(dir, { withFileTypes: true });
61
+ }
62
+ catch {
63
+ continue;
64
+ }
65
+ if (entries.some(e => e.isFile() && e.name === "SKILL.md")) {
66
+ if (!seen.has(dir)) {
67
+ seen.add(dir);
68
+ found.push({ dir, repo });
69
+ }
70
+ continue;
71
+ }
72
+ const here = entries.some(e => e.name === ".git") ? dir : repo;
73
+ if (depth >= maxDepth)
74
+ continue;
75
+ for (const e of entries) {
76
+ if (!e.isDirectory() || e.isSymbolicLink() || SKIP.has(e.name))
77
+ continue;
78
+ if (e.name.startsWith(".") && !DOT_ALLOW.has(e.name))
79
+ continue;
80
+ stack.push({ dir: join(dir, e.name), depth: depth + 1, repo: here });
81
+ }
82
+ }
83
+ return { found, roots, truncated };
84
+ }
85
+ export async function readInventory() {
86
+ try {
87
+ const inv = JSON.parse(await readFile(inventoryFile(), "utf8"));
88
+ return inv?.version === 1 && Array.isArray(inv.skills) ? inv : null;
89
+ }
90
+ catch {
91
+ return null;
92
+ }
93
+ }
94
+ async function write(inv) {
95
+ await mkdir(getDataDirectory(), { recursive: true, mode: 0o700 });
96
+ const file = inventoryFile();
97
+ const temp = `${file}.tmp`;
98
+ await writeFile(temp, JSON.stringify(inv, null, 2) + "\n", { mode: 0o600 });
99
+ await rename(temp, file);
100
+ }
101
+ // Full re-index: discover, scan each skill, diff against the previous file, write it back.
102
+ // Skills that vanished are dropped -- but only when the walk finished; a truncated walk can't
103
+ // prove absence, so previously known skills under the same roots are kept in that case.
104
+ export async function indexSkills(options = {}, home = homedir()) {
105
+ const previous = await readInventory();
106
+ const prev = new Map((previous?.skills ?? []).map(s => [s.dir, s]));
107
+ const full = options.roots === undefined;
108
+ const { found, roots, truncated } = await discoverAllSkills(full ? { ...options, roots: [home] } : options);
109
+ const now = new Date().toISOString();
110
+ const skills = [];
111
+ const changes = { added: [], modified: [], removed: [] };
112
+ for (const { dir, repo } of found) {
113
+ let scan;
114
+ try {
115
+ scan = await scanSkillDir(dir, basename(dir));
116
+ }
117
+ catch {
118
+ continue;
119
+ }
120
+ const old = prev.get(dir);
121
+ if (!old)
122
+ changes.added.push(dir);
123
+ else if (old.hash !== scan.hash)
124
+ changes.modified.push(dir);
125
+ skills.push({
126
+ id: createHash("sha256").update(dir).digest("hex").slice(0, 16),
127
+ topFindings: scan.findings.slice(0, 5).map(f => ({
128
+ id: f.id, title: f.title, severity: f.severity, file: f.file, ...(f.line ? { line: f.line } : {}),
129
+ explanation: f.explanation.slice(0, 300), ...(f.evidence ? { evidence: redact(f.evidence).slice(0, 300) } : {}),
130
+ fp: findingFingerprint(f, f.file),
131
+ })),
132
+ allFindings: scan.findings.slice(0, MAX_ALL_FINDINGS).map(f => ({ id: f.id, severity: f.severity, fp: findingFingerprint(f, f.file) })),
133
+ name: scan.name, dir, ...classify(dir, home), repo, hash: scan.hash, score: scan.score, level: scan.level,
134
+ findings: scan.findings.length, injection: scan.findings.some(f => f.id.startsWith("injection.")), files: scan.filesScanned,
135
+ firstSeen: old?.firstSeen ?? now, lastSeen: now, modifiedAt: !old ? null : old.hash !== scan.hash ? now : old.modifiedAt,
136
+ });
137
+ }
138
+ const scanned = new Set(skills.map(s => s.dir));
139
+ for (const old of prev.values()) {
140
+ if (scanned.has(old.dir))
141
+ continue;
142
+ const inScope = roots.some(r => old.dir === r || old.dir.startsWith(r + sep));
143
+ if (truncated && inScope)
144
+ skills.push(old); // can't prove it's gone
145
+ else if (inScope)
146
+ changes.removed.push(old.dir);
147
+ else
148
+ skills.push(old); // outside this run's roots (e.g. an ad-hoc --root): leave untouched
149
+ }
150
+ skills.sort((a, b) => b.score - a.score || a.name.localeCompare(b.name));
151
+ const inventory = { version: 1, generatedAt: now, roots: [...new Set([...(previous?.roots ?? []), ...roots])], truncated, skills, lastFullScanAt: full ? now : previous?.lastFullScanAt ?? null };
152
+ await write(inventory);
153
+ return { inventory, changes };
154
+ }
155
+ export async function trustedHashes() { return new Set((await readTrustedSkills()).map(t => t.hash)); }
156
+ // One 0-100 skills risk score for this machine (higher = riskier). Starts from the worst skill and
157
+ // adds 5 for every further HIGH/CRITICAL one (capped at +20). Skills you have trusted are left
158
+ // out. The workspace collector computes the same number for each device
159
+ // (beam/apps/collector/src/skill-score.ts) -- keep the two in step.
160
+ export function deviceSkillsScore(skills, trusted) {
161
+ const counted = skills.filter(s => !trusted.has(s.hash));
162
+ if (!counted.length)
163
+ return { score: 0, highCount: 0 };
164
+ const highCount = counted.filter(s => s.level === "HIGH" || s.level === "CRITICAL").length;
165
+ return { score: Math.min(100, Math.max(...counted.map(s => s.score)) + Math.min(20, 5 * Math.max(0, highCount - 1))), highCount };
166
+ }
167
+ //# sourceMappingURL=skill-inventory.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"skill-inventory.js","sourceRoot":"","sources":["../src/skill-inventory.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,17 @@
1
+ import { type Finding, type Severity } from "./core.js";
2
+ type RiskRule = {
3
+ id: string;
4
+ title: string;
5
+ severity: Severity;
6
+ explanation: string;
7
+ re: RegExp;
8
+ };
9
+ export declare const riskRules: RiskRule[];
10
+ export declare function scanRisky(content: string): Finding[];
11
+ export declare function comboFindings(perFile: Array<{
12
+ file: string;
13
+ ids: Set<string>;
14
+ }>): Array<Finding & {
15
+ file: string;
16
+ }>;
17
+ export {};
@@ -0,0 +1,61 @@
1
+ import { redact } from "./core.js";
2
+ import { firstInstruction } from "./skill-context.js";
3
+ export const riskRules = [
4
+ { id: "risk.privilege_escalation", title: "Privilege escalation (sudo / su / setuid)", severity: "high", explanation: "The skill runs commands with elevated privileges. Confirm it needs root and prefer least-privilege alternatives.",
5
+ re: /\b(?:sudo|doas)\b|(?:^|[\s;&|`(])su\s+(?:-\s*\w*|root)\b|\bchown\s+(?:-R\s+)?root\b|\b(?:setuid|setgid)\b|\bchmod\s+(?:[ugoa]*\+s\b|[2467][0-7]{3}\b)/i },
6
+ { id: "risk.sudoers", title: "Sudoers or admin-group modification", severity: "critical", explanation: "The skill edits sudo configuration or grants admin rights, which permanently widens what any process on the machine can do.",
7
+ re: /\/etc\/sudoers|\bvisudo\b|\bNOPASSWD\b|\busermod\s+-aG\s+(?:sudo|wheel|admin)\b/i },
8
+ { id: "risk.persistence", title: "Persistence mechanism", severity: "high", explanation: "The skill schedules itself or edits startup files, so its effects survive after the agent session ends.",
9
+ re: /\bcrontab\s+(?:-[a-z]+\s+)?\S|\/etc\/cron\.\w+|\blaunchctl\s+(?:load|bootstrap|enable)\b|Launch(?:Agents|Daemons)\b|\bsystemctl\s+(?:--user\s+)?enable\b|\bschtasks(?:\.exe)?\s+\/create\b|Register-ScheduledTask|\bsc(?:\.exe)?\s+create\b|(?:>>|\btee\s+-a)\s*~?\/?(?:\S*\/)?\.(?:bashrc|zshrc|profile|bash_profile|zprofile)\b|\/etc\/(?:rc\.local|profile\.d)\b/i },
10
+ { id: "risk.sensitive_files", title: "Credential or identity file access", severity: "high", explanation: "The skill touches SSH keys, cloud credentials, browser stores or password files. Confirm the agent needs their contents.",
11
+ re: /(?:~|\$HOME)?\/?\.ssh\/|\bid_(?:rsa|ed25519|ecdsa)\b|\/etc\/(?:shadow|passwd)\b|(?:~|\$HOME)\/\.aws\/|\.config\/gcloud\b|\.kube\/config\b|\.docker\/config\.json\b|\.gnupg\b|\.netrc\b|\.git-credentials\b|\bLogin Data\b|\bcookies\.sqlite\b|\bwallet\.dat\b|Library\/Keychains/i },
12
+ { id: "risk.exfil_tools", title: "Data exfiltration tooling", severity: "high", explanation: "The skill pipes data to a network tool, uploads files, or sends to a public collection endpoint.",
13
+ re: /\|\s*(?:curl|wget|nc|ncat|netcat|socat)\b|\b(?:scp|rsync)\s+[^\n]*\S+@\S+:|\b(?:sendmail|swaks)\b|\bmailx?\s+-s\b|\bcurl\b[^\n]*(?:\s-d\s+[@<]|--data(?:-binary|-raw)?\s+[@<]|\s-F\s|\s-T\s|--upload-file)|\b(?:transfer\.sh|pastebin\.com|webhook\.site|requestbin\.\w+|interact\.sh|ngrok\.io|burpcollaborator\.net|oast\.\w+)\b/i },
14
+ { id: "risk.obfuscation", title: "Obfuscated or encoded content", severity: "medium", explanation: "Encoded strings, hex escapes or URL shorteners hide what will actually run or where it goes.",
15
+ re: /\bbase64\s+(?:-d|--decode|-D)\b|\batob\s*\(|\bFromBase64String\b|(?:\\x[0-9a-f]{2}){4,}|\b(?:bit\.ly|tinyurl\.com|t\.co|is\.gd|goo\.gl|cutt\.ly)\/\w|\bchr\s*\(\s*\d+\s*\)\s*\+\s*chr/i },
16
+ { id: "risk.decode_exec", title: "Decode then execute", severity: "high", explanation: "Decompressed or decoded data is piped straight into a shell or interpreter, so its contents are never reviewed.",
17
+ re: /\b(?:gunzip|zcat|gzip\s+-d|xxd\s+-r|openssl\s+enc\s+-d)\b[^\n]*\|\s*(?:sudo\s+)?(?:ba|z|da)?sh\b|\bbase64\s+(?:-d|--decode)\b[^\n]*\|\s*(?:sudo\s+)?(?:python3?|node|ruby|perl)\b/i },
18
+ { id: "risk.code_exec", title: "Dynamic code execution", severity: "medium", explanation: "Strings are evaluated or shells are spawned from code. Check what feeds them.",
19
+ re: /\beval\s*\(|\bnew\s+Function\s*\(|\b__import__\s*\(|\bimportlib\.import_module\b|\bos\.system\s*\(|\bsubprocess\.\w+\([^\n]*shell\s*=\s*True|\bRuntime\.getRuntime\(\)\.exec|\bchild_process\b[^\n]*\bexec(?:Sync)?\s*\(/i },
20
+ { id: "risk.download_exec", title: "Download and run", severity: "high", explanation: "The skill fetches a file or script and executes it in the same step.",
21
+ re: /\b(?:curl|wget)\b[^\n]*(?:\s-o\s|\s-O\b|--output\b)[^\n]*(?:&&|;)\s*(?:chmod\s+\+x|(?:ba|z)?sh\b|python3?\b|node\b|\.\/)|\bcurl\b[^\n]*\|\s*(?:sudo\s+)?(?:python3?|node|ruby|perl)\b|\b(?:iwr|Invoke-WebRequest)\b[^\n]*\|\s*(?:iex|Invoke-Expression)\b|\bDownloadString\s*\(|\b(?:iex|Invoke-Expression)\b[^\n]*\b(?:iwr|Invoke-WebRequest|DownloadString)\b/i },
22
+ { id: "risk.supply_chain", title: "Unpinned or non-registry install", severity: "medium", explanation: "Packages come from a git URL, a custom index, or a mutable tag instead of a reviewed registry version.",
23
+ re: /\bpip3?\s+install\s+[^\n]*git\+|\bnpm\s+(?:i|install)\s+(?:-g\s+)?(?:github:|git\+|https?:\/\/)|\bnpx\s+(?:-y\s+)?(?:github:|https?:\/\/)|(?:--index-url|--extra-index-url|--trusted-host|--allow-unverified)\b|\bgo\s+install\s+\S+@latest\b|\bcargo\s+install\s+--git\b|\bnpm\s+config\s+set\s+registry\b|\bnpm\s+(?:i|install)\s+[^\n]*--registry\b/i },
24
+ { id: "risk.destructive_broad", title: "Broad destructive command", severity: "high", explanation: "The command can wipe a disk, the home directory, or shared git history.",
25
+ re: /\brm\s+(?:-[a-z]*[rf][a-z]*\s+)+(?:\/(?:\s|$|\*)|~\/?(?:\s|$|\*)|\$HOME\/?(?:\s|$|\*))|\bmkfs\.\w+|\bdd\s+if=[^\n]*\bof=\/dev\/|:\(\)\s*\{\s*:\s*\|\s*:\s*&\s*\}\s*;\s*:|>\s*\/dev\/sd[a-z]|\bchmod\s+-R\s+777\s+\/(?:\s|$)|\bgit\s+push\s+[^\n]*(?:--force|\s-f\b)[^\n]*\b(?:main|master)\b/i },
26
+ ];
27
+ export function scanRisky(content) {
28
+ const findings = [];
29
+ const lines = content.split(/\r?\n/);
30
+ for (const rule of riskRules) {
31
+ for (let i = 0; i < lines.length; i++) {
32
+ if (lines[i].length > 2000 || !firstInstruction(lines[i], rule.re, "command", { prev: lines[i - 1], next: lines[i + 1] }))
33
+ continue;
34
+ findings.push({ id: rule.id, title: rule.title, severity: rule.severity, explanation: rule.explanation, evidence: redact(lines[i].trim()).slice(0, 200), line: i + 1 });
35
+ break;
36
+ }
37
+ }
38
+ return findings;
39
+ }
40
+ // Pairs that are worse together than either alone (explainx escalates the same way: privilege
41
+ // escalation plus destruction is critical). "skill" combos look across the whole skill, since an
42
+ // instruction file and the script it runs are separate files. The credential/exfil pair is
43
+ // "file"-scoped: both halves are common in unrelated reference docs (an API example with curl,
44
+ // a path-traversal note mentioning /etc/passwd), so co-occurring anywhere in a big skill means
45
+ // nothing -- they only count together in one file.
46
+ const COMBOS = [
47
+ { scope: "skill", all: ["risk.privilege_escalation", "risk.destructive_broad"], id: "risk.combo.privileged_destructive", title: "Elevated privileges combined with destructive commands", explanation: "Root access plus a broad delete or overwrite can destroy the whole machine." },
48
+ { scope: "skill", all: ["risk.privilege_escalation", "risk.download_exec"], id: "risk.combo.privileged_download_exec", title: "Downloads and runs code with elevated privileges", explanation: "Unreviewed remote code would run as root." },
49
+ { scope: "file", all: ["risk.sensitive_files", "risk.exfil_tools"], id: "risk.combo.credential_exfil", title: "Credential files together with outbound transfer tooling", explanation: "Reading key or credential files and having a way to send data out is the shape of credential theft." },
50
+ ];
51
+ export function comboFindings(perFile) {
52
+ const all = new Set(perFile.flatMap(f => [...f.ids]));
53
+ const out = [];
54
+ for (const c of COMBOS) {
55
+ const hits = c.scope === "skill" ? (c.all.every(id => all.has(id)) ? ["(whole skill)"] : []) : perFile.filter(f => c.all.every(id => f.ids.has(id))).map(f => f.file);
56
+ for (const file of hits)
57
+ out.push({ id: c.id, title: c.title, severity: "critical", explanation: c.explanation, evidence: `Together: ${c.all.join(" + ")}`, file });
58
+ }
59
+ return out;
60
+ }
61
+ //# sourceMappingURL=skill-risk-rules.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"skill-risk-rules.js","sourceRoot":"","sources":["../src/skill-risk-rules.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,27 @@
1
+ import { type Finding, type Severity } from "./core.js";
2
+ export declare function skillRiskScore(findings: Finding[]): {
3
+ score: number;
4
+ level: SkillScan["level"];
5
+ };
6
+ export interface SkillFileScan {
7
+ file: string;
8
+ findings: Finding[];
9
+ }
10
+ export interface SkillScan {
11
+ name: string;
12
+ dir: string;
13
+ hash: string;
14
+ score: number;
15
+ level: "LOW" | "MEDIUM" | "HIGH" | "CRITICAL";
16
+ risk: Severity;
17
+ files: SkillFileScan[];
18
+ findings: Array<Finding & {
19
+ file: string;
20
+ }>;
21
+ filesScanned: number;
22
+ skipped: string[];
23
+ }
24
+ export declare function findingFingerprint(f: Pick<Finding, "id" | "evidence">, file: string): string;
25
+ export declare function scanInjection(content: string): Finding[];
26
+ export declare function scanSkillDir(dir: string, name: string): Promise<SkillScan>;
27
+ export declare function scanSkillFile(file: string): Promise<SkillScan>;