@shomra/agent 0.3.29 → 0.3.30

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 (77) hide show
  1. package/package.json +1 -1
  2. package/src/agents/hook-command.mjs +1 -1
  3. package/src/artifacts/matchers.mjs +7 -0
  4. package/src/cli/flags.mjs +2 -2
  5. package/src/cli/help-sections.mjs +7 -0
  6. package/src/cli/help.mjs +1 -1
  7. package/src/commands/check.mjs +3 -11
  8. package/src/commands/gate.mjs +26 -4
  9. package/src/commands/git-hooks.mjs +2 -2
  10. package/src/commands/ledger.mjs +0 -1
  11. package/src/commands/mcp-add.mjs +2 -1
  12. package/src/commands/memory-scan.mjs +135 -47
  13. package/src/commands/pr.mjs +7 -10
  14. package/src/commands/provenance.mjs +8 -13
  15. package/src/commands/scan.mjs +7 -1
  16. package/src/commands/secrets.mjs +4 -5
  17. package/src/core/git-exec.mjs +79 -0
  18. package/src/core/yaml-lite.mjs +300 -0
  19. package/src/core/zip-lite.mjs +37 -0
  20. package/src/detect/local-redact.mjs +1 -3
  21. package/src/detect/sast/rules-config.mjs +1 -1
  22. package/src/detect/sast/scanner.mjs +1 -1
  23. package/src/detect/signals/agent-frameworks.mjs +231 -0
  24. package/src/detect/signals/agent-graph-surface.mjs +113 -0
  25. package/src/detect/signals/agentic-ci-surface.mjs +314 -0
  26. package/src/detect/signals/agentic-shim.mjs +82 -0
  27. package/src/detect/signals/artifacts.mjs +7 -35
  28. package/src/detect/signals/chat-template.mjs +211 -0
  29. package/src/detect/signals/ci-workflow.mjs +169 -0
  30. package/src/detect/signals/gate.mjs +77 -9
  31. package/src/detect/signals/guardrail-shape.mjs +564 -0
  32. package/src/detect/signals/guardrail-surface.mjs +221 -0
  33. package/src/detect/signals/injection.mjs +8 -0
  34. package/src/detect/signals/inspect-shim.mjs +7 -0
  35. package/src/detect/signals/instruction-paths.mjs +60 -0
  36. package/src/detect/signals/manifests.mjs +302 -0
  37. package/src/detect/signals/mcp-advisories.mjs +109 -0
  38. package/src/detect/signals/mcp-config.mjs +598 -0
  39. package/src/detect/signals/memory-directives.mjs +661 -0
  40. package/src/detect/signals/memory-locations.mjs +158 -0
  41. package/src/detect/signals/memory.mjs +47 -29
  42. package/src/detect/signals/model-config-rules.mjs +655 -0
  43. package/src/detect/signals/model-config.mjs +61 -0
  44. package/src/detect/signals/prose-context.mjs +6 -9
  45. package/src/detect/signals/scan.mjs +4 -4
  46. package/src/detect/signals/secret-scanner.mjs +241 -0
  47. package/src/detect/signals/secrets.mjs +1 -48
  48. package/src/detect/signals/shell.mjs +3 -3
  49. package/src/gate/advisories.mjs +16 -0
  50. package/src/gate/batch.mjs +10 -0
  51. package/src/gate/environment.mjs +8 -53
  52. package/src/guard/artifact-paths.mjs +107 -0
  53. package/src/guard/classify.mjs +165 -7
  54. package/src/guard/command-resolve.mjs +35 -5
  55. package/src/guard/memory-write.mjs +218 -0
  56. package/src/guard/prompt-guard.mjs +0 -1
  57. package/src/guard/tool-guard.mjs +52 -77
  58. package/src/inventory/agent-posture.mjs +236 -57
  59. package/src/inventory/artifacts/classify.mjs +10 -1
  60. package/src/inventory/artifacts/discover.mjs +113 -3
  61. package/src/inventory/artifacts/extensions.mjs +70 -0
  62. package/src/inventory/artifacts/hook-scripts.mjs +128 -0
  63. package/src/inventory/artifacts/limits.mjs +1 -1
  64. package/src/inventory/artifacts/plugins.mjs +105 -0
  65. package/src/inventory/artifacts/roots.mjs +40 -0
  66. package/src/inventory/discovery/ai-dependencies.mjs +39 -12
  67. package/src/inventory/discovery/all.mjs +4 -0
  68. package/src/inventory/discovery/cloud-clis.mjs +472 -0
  69. package/src/inventory/discovery/coding-agents.mjs +19 -4
  70. package/src/inventory/discovery/mcp-clients.mjs +16 -10
  71. package/src/inventory/discovery/mcp-servers.mjs +125 -35
  72. package/src/inventory/discovery/mcp-stores.mjs +207 -0
  73. package/src/inventory/env-redirect.mjs +148 -0
  74. package/src/inventory/grant-extract.mjs +463 -0
  75. package/src/inventory/project-roots.mjs +108 -0
  76. package/src/inventory/vscode-state.mjs +153 -0
  77. package/src/mcp/server-tools.mjs +1 -1
@@ -0,0 +1,109 @@
1
+ import { gradeMcpDocument, knownVulnerable, launchPull } from './mcp-config.mjs';
2
+
3
+ const OSV_BATCH = 'https://api.osv.dev/v1/querybatch';
4
+ const OSV_VULN = 'https://api.osv.dev/v1/vulns/';
5
+ const MAX_PACKAGES = 40;
6
+ const MAX_DETAILS = 12;
7
+ const cache = new Map();
8
+
9
+ async function postJson(url, body, fetchImpl, timeoutMs) {
10
+ const ctl = new AbortController();
11
+ const t = setTimeout(() => ctl.abort(), timeoutMs);
12
+ t.unref?.();
13
+ try {
14
+ const res = await fetchImpl(url, { method: 'POST', headers: { 'content-type': 'application/json' }, body: JSON.stringify(body), signal: ctl.signal });
15
+ if (!res.ok) throw new Error(`OSV ${res.status}`);
16
+ return await res.json();
17
+ } finally {
18
+ clearTimeout(t);
19
+ }
20
+ }
21
+
22
+ async function getJson(url, fetchImpl, timeoutMs) {
23
+ const ctl = new AbortController();
24
+ const t = setTimeout(() => ctl.abort(), timeoutMs);
25
+ t.unref?.();
26
+ try {
27
+ const res = await fetchImpl(url, { signal: ctl.signal });
28
+ if (!res.ok) throw new Error(`OSV ${res.status}`);
29
+ return await res.json();
30
+ } finally {
31
+ clearTimeout(t);
32
+ }
33
+ }
34
+
35
+ function severityOf(vuln) {
36
+ const s = String(vuln?.database_specific?.severity ?? '').toUpperCase();
37
+ if (s === 'CRITICAL' || s === 'HIGH') return s;
38
+ if (s === 'MODERATE' || s === 'MEDIUM' || s === 'LOW') return 'MEDIUM';
39
+ return 'HIGH';
40
+ }
41
+
42
+ function fixedOf(vuln, name) {
43
+ const out = new Set();
44
+ for (const aff of Array.isArray(vuln?.affected) ? vuln.affected : []) {
45
+ if (aff?.package?.name && aff.package.name.toLowerCase() !== name.toLowerCase()) continue;
46
+ for (const r of Array.isArray(aff?.ranges) ? aff.ranges : []) for (const e of Array.isArray(r?.events) ? r.events : []) if (e?.fixed) out.add(String(e.fixed));
47
+ }
48
+ return [...out];
49
+ }
50
+
51
+ export async function mcpAdvisoryFindings(content, path = '', { fetchImpl = globalThis.fetch, timeoutMs = 3500 } = {}) {
52
+ const { servers } = gradeMcpDocument(content, path);
53
+ const wanted = new Map();
54
+ for (const s of servers ?? []) {
55
+ if (s.disabled) continue;
56
+ const p = launchPull(s.command, s.args);
57
+ if (!p?.version) continue;
58
+ if (knownVulnerable(p.ecosystem === 'pypi' ? 'python' : 'npm', p.name, p.version)) continue; // the seed row already says it
59
+ const key = `${p.ecosystem}:${p.name.toLowerCase()}@${p.version}`;
60
+ const slot = wanted.get(key) ?? { ...p, servers: [] };
61
+ slot.servers.push(s.name);
62
+ wanted.set(key, slot);
63
+ }
64
+ const pkgs = [...wanted.entries()].slice(0, MAX_PACKAGES);
65
+ if (!pkgs.length) return { status: 'none', checked: 0, findings: [] };
66
+ if (typeof fetchImpl !== 'function') return { status: 'unavailable', checked: 0, findings: [] };
67
+
68
+ const todo = pkgs.filter(([k]) => !cache.has(k));
69
+ if (todo.length) {
70
+ let batch;
71
+ try {
72
+ batch = await postJson(OSV_BATCH, { queries: todo.map(([, p]) => ({ package: { ecosystem: p.ecosystem === 'pypi' ? 'PyPI' : 'npm', name: p.name }, version: p.version })) }, fetchImpl, timeoutMs);
73
+ } catch {
74
+ return { status: 'unavailable', checked: 0, findings: [] };
75
+ }
76
+ todo.forEach(([k], i) => cache.set(k, (batch?.results?.[i]?.vulns ?? []).map((v) => v.id).filter(Boolean)));
77
+ }
78
+
79
+ const ids = [...new Set(pkgs.flatMap(([k]) => cache.get(k) ?? []))].slice(0, MAX_DETAILS);
80
+ const details = new Map();
81
+ await Promise.all(ids.map(async (id) => {
82
+ try { details.set(id, await getJson(OSV_VULN + encodeURIComponent(id), fetchImpl, timeoutMs)); } catch { /* the id alone still stands */ }
83
+ }));
84
+
85
+ const findings = [];
86
+ const RANK = { MEDIUM: 1, HIGH: 2, CRITICAL: 3 };
87
+ for (const [k, p] of pkgs) {
88
+ const vids = cache.get(k) ?? [];
89
+ if (!vids.length) continue;
90
+ const vulns = vids.map((id) => details.get(id)).filter(Boolean);
91
+ const severity = vulns.length ? vulns.map(severityOf).reduce((w, s) => (RANK[s] > RANK[w] ? s : w), 'MEDIUM') : 'HIGH';
92
+ const fixed = [...new Set(vulns.flatMap((v) => fixedOf(v, p.name)))].slice(0, 3);
93
+ const cves = [...new Set(vulns.flatMap((v) => [v.id, ...(v.aliases ?? [])]).filter((x) => /^CVE-/i.test(x)))].slice(0, 3);
94
+ for (const server of p.servers) {
95
+ findings.push({
96
+ severity,
97
+ title: `MCP server "${server}" runs ${p.name}@${p.version}, which has known advisories`,
98
+ remediationText: `Upgrade "${p.name}"${fixed.length ? ` to ${fixed.join(' / ')} or later` : ' past the affected range'} and pin that release. Advisories: ${[...cves, ...vids.filter((v) => !cves.includes(v))].slice(0, 5).join(', ')}.`,
99
+ anchor: `${p.name}`,
100
+ server,
101
+ });
102
+ }
103
+ }
104
+ return { status: 'checked', checked: pkgs.length, findings };
105
+ }
106
+
107
+ export function advisoriesDisabled(flags = {}) {
108
+ return !!flags.offline || /^(1|true|yes)$/i.test(String(process.env.SHOMRA_OFFLINE ?? ''));
109
+ }