project-athena 0.4.0 → 0.5.0

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 (51) hide show
  1. package/CHANGELOG.md +31 -0
  2. package/README.md +10 -0
  3. package/dist/{activity-BKW7G4I5.js → activity-XCHKBZTG.js} +4 -4
  4. package/dist/{agents-DFSNEOP5.js → agents-HUJ4JXIB.js} +4 -4
  5. package/dist/{ai-ASL4FFQ7.js → ai-LGTLQKL4.js} +3 -3
  6. package/dist/{analyze-ZY3MXBOI.js → analyze-AP4XJT7D.js} +7 -7
  7. package/dist/{chunk-ZB7LC6FP.js → chunk-3KFOI2X4.js} +2 -2
  8. package/dist/{chunk-MPG3IEGB.js → chunk-44EGCEB2.js} +23 -14
  9. package/dist/{chunk-GGXPG6OE.js → chunk-4FETKOY3.js} +8 -7
  10. package/dist/chunk-4IIBVTBS.js +76 -0
  11. package/dist/{chunk-LEMPCNAY.js → chunk-56EXAGTM.js} +3 -3
  12. package/dist/{chunk-DZDI2HRT.js → chunk-6OSQMXUP.js} +5 -5
  13. package/dist/{chunk-OLPSEQS5.js → chunk-AKOI4VTD.js} +10 -1
  14. package/dist/{chunk-FVCDMQUQ.js → chunk-C25FDXOO.js} +133 -23
  15. package/dist/{chunk-7DZK6WSQ.js → chunk-DH5AGULY.js} +2 -2
  16. package/dist/{chunk-TDNZ2RYA.js → chunk-F65QINMR.js} +2 -2
  17. package/dist/{chunk-WVQC2EGO.js → chunk-GU3ZX2J7.js} +2 -2
  18. package/dist/{chunk-UAZLPNVC.js → chunk-GYQG4Q2N.js} +4 -4
  19. package/dist/chunk-IDODPGTX.js +595 -0
  20. package/dist/chunk-KJYSNZXS.js +934 -0
  21. package/dist/{chunk-3XSDI5WF.js → chunk-L2EHJCH4.js} +5 -5
  22. package/dist/{chunk-65TSRDQ3.js → chunk-P4TRQMES.js} +2 -2
  23. package/dist/{chunk-KHMBNT66.js → chunk-PZPMWCXS.js} +5 -5
  24. package/dist/{chunk-XDCKZ5TV.js → chunk-R5AX7VST.js} +2 -2
  25. package/dist/{chunk-QLW657NT.js → chunk-RQKGT2EK.js} +2 -2
  26. package/dist/{chunk-VSYBC3VY.js → chunk-UMWX4UTK.js} +2 -2
  27. package/dist/{chunk-RXNRMIZP.js → chunk-WUIC65QO.js} +2 -2
  28. package/dist/{chunk-JCGDR4UV.js → chunk-WWQ5NKEC.js} +3 -1
  29. package/dist/{chunk-RVTNSNGK.js → chunk-X7EYOAEX.js} +2 -2
  30. package/dist/chunk-Y6SDMZET.js +278 -0
  31. package/dist/{clean-MDBYH62P.js → clean-LQOG56AM.js} +2 -2
  32. package/dist/cli.js +2 -2
  33. package/dist/{context-PLPCAPCO.js → context-G5YHY3L2.js} +8 -8
  34. package/dist/{doctor-BX677KRE.js → doctor-L34QJ7WR.js} +8 -8
  35. package/dist/{event-TNT6CQZR.js → event-6YW6TGFI.js} +3 -3
  36. package/dist/{git-hook-YZNZGAAP.js → git-hook-Y7Y262F7.js} +2 -2
  37. package/dist/{init-WQCAOJ2R.js → init-427QR5AS.js} +7 -7
  38. package/dist/{mcp-U7EPH43L.js → mcp-DWNWRGJW.js} +51 -14
  39. package/dist/{memory-VSVNLEJC.js → memory-XBZCYQLW.js} +3 -3
  40. package/dist/{open-UJZRYF4E.js → open-LJMOB6U2.js} +33 -31
  41. package/dist/{program-CWJNGCTZ.js → program-DCP526LU.js} +46 -32
  42. package/dist/{review-OOKXSYMP.js → review-VEJBPACX.js} +10 -9
  43. package/dist/{rules-4FXJUCXB.js → rules-M4DLLXBL.js} +4 -4
  44. package/dist/scan-JVKCVHPD.js +939 -0
  45. package/dist/{security-LME3XFL3.js → security-JTJN3E7K.js} +13 -11
  46. package/dist/{staged-check-X24C3YRD.js → staged-check-PQSLGQBW.js} +11 -11
  47. package/dist/{status-RR57ZNGI.js → status-PO27BSOZ.js} +4 -4
  48. package/dist/{sync-KFJANRXF.js → sync-GVO2VHPI.js} +10 -10
  49. package/dist/{watch-CY7QHIYN.js → watch-TEHFO47V.js} +10 -10
  50. package/package.json +3 -1
  51. package/dist/chunk-SJ4DQFIJ.js +0 -379
@@ -1,8 +1,11 @@
1
1
  #!/usr/bin/env node
2
2
  import {
3
- runSecurityScan
4
- } from "./chunk-SJ4DQFIJ.js";
5
- import "./chunk-VSYBC3VY.js";
3
+ runSecurityScanWithFindings,
4
+ saveScan
5
+ } from "./chunk-4IIBVTBS.js";
6
+ import "./chunk-IDODPGTX.js";
7
+ import "./chunk-UMWX4UTK.js";
8
+ import "./chunk-Y6SDMZET.js";
6
9
  import {
7
10
  c,
8
11
  dim,
@@ -22,21 +25,20 @@ import {
22
25
  import "./chunk-KBO7XRBW.js";
23
26
  import {
24
27
  projectSession
25
- } from "./chunk-WVQC2EGO.js";
28
+ } from "./chunk-GU3ZX2J7.js";
26
29
  import {
27
30
  RATED_SEVERITIES,
28
31
  SEVERITY_ORDER,
29
32
  analyzeProject,
30
33
  meetsThreshold,
31
- saveScan,
32
34
  unratedFindings
33
- } from "./chunk-MPG3IEGB.js";
34
- import "./chunk-ELKFRTZT.js";
35
+ } from "./chunk-44EGCEB2.js";
35
36
  import "./chunk-PVCIGUIT.js";
36
- import "./chunk-OLPSEQS5.js";
37
+ import "./chunk-ELKFRTZT.js";
38
+ import "./chunk-AKOI4VTD.js";
37
39
  import "./chunk-SVTEJYMS.js";
38
40
  import "./chunk-QCC4VJUH.js";
39
- import "./chunk-JCGDR4UV.js";
41
+ import "./chunk-WWQ5NKEC.js";
40
42
  import "./chunk-2DY4PUR4.js";
41
43
  import "./chunk-IQQBAWSM.js";
42
44
  import {
@@ -125,7 +127,7 @@ async function securityCommand(opts) {
125
127
  try {
126
128
  model = await loadModel(root, opts.signal);
127
129
  sp.update("Scanning files for secrets...");
128
- scan = await runSecurityScan(root, model, { signal: opts.signal, skipAudit: opts.noAudit, onTool: (tool) => sp.update(`Running ${tool}...`) });
130
+ scan = (await runSecurityScanWithFindings(root, model, { signal: opts.signal, skipAudit: opts.noAudit, onTool: (tool) => sp.update(`Running ${tool}...`) })).scan;
129
131
  sp.succeed(`Security scan finished in ${(scan.durationMs / 1e3).toFixed(1)}s`);
130
132
  } catch (err) {
131
133
  sp.stop();
@@ -174,4 +176,4 @@ export {
174
176
  printScan,
175
177
  securityCommand
176
178
  };
177
- //# sourceMappingURL=security-LME3XFL3.js.map
179
+ //# sourceMappingURL=security-JTJN3E7K.js.map
@@ -1,33 +1,33 @@
1
1
  #!/usr/bin/env node
2
2
  import {
3
3
  HISTORY_SECTIONS
4
- } from "./chunk-KHMBNT66.js";
5
- import "./chunk-WVQC2EGO.js";
4
+ } from "./chunk-PZPMWCXS.js";
5
+ import "./chunk-GU3ZX2J7.js";
6
6
  import "./chunk-GR4DL2VM.js";
7
7
  import {
8
8
  planKnowledge
9
- } from "./chunk-GGXPG6OE.js";
9
+ } from "./chunk-4FETKOY3.js";
10
+ import "./chunk-2R6PXY6Z.js";
10
11
  import {
11
12
  REAL_ENV_NAMES,
12
13
  SCAN_FILE,
13
14
  analyzeProject,
14
15
  redactDeep
15
- } from "./chunk-MPG3IEGB.js";
16
- import {
17
- mapLimit
18
- } from "./chunk-ELKFRTZT.js";
19
- import "./chunk-2R6PXY6Z.js";
16
+ } from "./chunk-44EGCEB2.js";
20
17
  import {
21
18
  readLocalConfig
22
19
  } from "./chunk-PVCIGUIT.js";
20
+ import {
21
+ mapLimit
22
+ } from "./chunk-ELKFRTZT.js";
23
23
  import {
24
24
  git,
25
25
  gitInfo,
26
26
  isGitRepo
27
- } from "./chunk-OLPSEQS5.js";
27
+ } from "./chunk-AKOI4VTD.js";
28
28
  import "./chunk-SVTEJYMS.js";
29
29
  import "./chunk-QCC4VJUH.js";
30
- import "./chunk-JCGDR4UV.js";
30
+ import "./chunk-WWQ5NKEC.js";
31
31
  import "./chunk-YRIS7ZS6.js";
32
32
  import "./chunk-GHFRQAYT.js";
33
33
  import "./chunk-INT3IGMQ.js";
@@ -499,4 +499,4 @@ async function checkStaged(rootInput, opts = {}) {
499
499
  export {
500
500
  checkStaged
501
501
  };
502
- //# sourceMappingURL=staged-check-X24C3YRD.js.map
502
+ //# sourceMappingURL=staged-check-PQSLGQBW.js.map
@@ -1,7 +1,7 @@
1
1
  #!/usr/bin/env node
2
2
  import {
3
3
  buildStatus
4
- } from "./chunk-ZB7LC6FP.js";
4
+ } from "./chunk-3KFOI2X4.js";
5
5
  import {
6
6
  bullet,
7
7
  c,
@@ -20,7 +20,7 @@ import {
20
20
  import "./chunk-KBO7XRBW.js";
21
21
  import "./chunk-GR4DL2VM.js";
22
22
  import "./chunk-ELKFRTZT.js";
23
- import "./chunk-OLPSEQS5.js";
23
+ import "./chunk-AKOI4VTD.js";
24
24
  import "./chunk-673EPYYH.js";
25
25
  import "./chunk-FQEGEVLY.js";
26
26
  import "./chunk-2DY4PUR4.js";
@@ -33,7 +33,7 @@ import "./chunk-EUPETUWA.js";
33
33
  async function statusCommand(opts) {
34
34
  const root = await requireProjectRoot(opts);
35
35
  const report = await buildStatus(root, opts.signal);
36
- const memory = await (await import("./memory-VSVNLEJC.js")).memorySummary(root);
36
+ const memory = await (await import("./memory-XBZCYQLW.js")).memorySummary(root);
37
37
  if (isJson()) {
38
38
  json(memory ? { ...report, memory: { total: memory.total, unreviewed: memory.unreviewed, stale: memory.stale } } : report);
39
39
  return;
@@ -78,4 +78,4 @@ async function statusCommand(opts) {
78
78
  export {
79
79
  statusCommand
80
80
  };
81
- //# sourceMappingURL=status-RR57ZNGI.js.map
81
+ //# sourceMappingURL=status-PO27BSOZ.js.map
@@ -1,7 +1,7 @@
1
1
  #!/usr/bin/env node
2
2
  import {
3
3
  confirm
4
- } from "./chunk-7DZK6WSQ.js";
4
+ } from "./chunk-DH5AGULY.js";
5
5
  import {
6
6
  bullet,
7
7
  c,
@@ -28,18 +28,18 @@ import {
28
28
  planSync,
29
29
  staleForCheck,
30
30
  summarizePlan
31
- } from "./chunk-KHMBNT66.js";
32
- import "./chunk-WVQC2EGO.js";
31
+ } from "./chunk-PZPMWCXS.js";
32
+ import "./chunk-GU3ZX2J7.js";
33
33
  import "./chunk-GR4DL2VM.js";
34
- import "./chunk-GGXPG6OE.js";
35
- import "./chunk-MPG3IEGB.js";
36
- import "./chunk-ELKFRTZT.js";
34
+ import "./chunk-4FETKOY3.js";
37
35
  import "./chunk-2R6PXY6Z.js";
36
+ import "./chunk-44EGCEB2.js";
38
37
  import "./chunk-PVCIGUIT.js";
39
- import "./chunk-OLPSEQS5.js";
38
+ import "./chunk-ELKFRTZT.js";
39
+ import "./chunk-AKOI4VTD.js";
40
40
  import "./chunk-SVTEJYMS.js";
41
41
  import "./chunk-QCC4VJUH.js";
42
- import "./chunk-JCGDR4UV.js";
42
+ import "./chunk-WWQ5NKEC.js";
43
43
  import "./chunk-YRIS7ZS6.js";
44
44
  import "./chunk-GHFRQAYT.js";
45
45
  import "./chunk-INT3IGMQ.js";
@@ -183,7 +183,7 @@ async function syncCommand(opts) {
183
183
  return EXIT.OK;
184
184
  }
185
185
  async function stagedCheckCommand(root, opts) {
186
- const { checkStaged } = await import("./staged-check-X24C3YRD.js");
186
+ const { checkStaged } = await import("./staged-check-PQSLGQBW.js");
187
187
  const sp = spinner("Checking staged knowledge...");
188
188
  let r;
189
189
  try {
@@ -215,4 +215,4 @@ export {
215
215
  printPlan,
216
216
  syncCommand
217
217
  };
218
- //# sourceMappingURL=sync-KFJANRXF.js.map
218
+ //# sourceMappingURL=sync-GVO2VHPI.js.map
@@ -1,8 +1,8 @@
1
1
  #!/usr/bin/env node
2
2
  import {
3
3
  watchProject
4
- } from "./chunk-DZDI2HRT.js";
5
- import "./chunk-RVTNSNGK.js";
4
+ } from "./chunk-6OSQMXUP.js";
5
+ import "./chunk-X7EYOAEX.js";
6
6
  import {
7
7
  c,
8
8
  dim,
@@ -15,18 +15,18 @@ import {
15
15
  requireProjectRoot
16
16
  } from "./chunk-XFN7UZCS.js";
17
17
  import "./chunk-KBO7XRBW.js";
18
- import "./chunk-KHMBNT66.js";
19
- import "./chunk-WVQC2EGO.js";
18
+ import "./chunk-PZPMWCXS.js";
19
+ import "./chunk-GU3ZX2J7.js";
20
20
  import "./chunk-GR4DL2VM.js";
21
- import "./chunk-GGXPG6OE.js";
22
- import "./chunk-MPG3IEGB.js";
23
- import "./chunk-ELKFRTZT.js";
21
+ import "./chunk-4FETKOY3.js";
24
22
  import "./chunk-2R6PXY6Z.js";
23
+ import "./chunk-44EGCEB2.js";
25
24
  import "./chunk-PVCIGUIT.js";
26
- import "./chunk-OLPSEQS5.js";
25
+ import "./chunk-ELKFRTZT.js";
26
+ import "./chunk-AKOI4VTD.js";
27
27
  import "./chunk-SVTEJYMS.js";
28
28
  import "./chunk-QCC4VJUH.js";
29
- import "./chunk-JCGDR4UV.js";
29
+ import "./chunk-WWQ5NKEC.js";
30
30
  import "./chunk-YRIS7ZS6.js";
31
31
  import "./chunk-GHFRQAYT.js";
32
32
  import "./chunk-INT3IGMQ.js";
@@ -120,4 +120,4 @@ async function watchCommand(opts) {
120
120
  export {
121
121
  watchCommand
122
122
  };
123
- //# sourceMappingURL=watch-CY7QHIYN.js.map
123
+ //# sourceMappingURL=watch-TEHFO47V.js.map
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "project-athena",
3
- "version": "0.4.0",
3
+ "version": "0.5.0",
4
4
  "description": "Athena — local-first project intelligence layer for AI coding agents",
5
5
  "author": "Sabarivasan",
6
6
  "license": "MIT",
@@ -68,6 +68,8 @@
68
68
  "@types/react": "^19.3.0",
69
69
  "@types/react-dom": "^19.3.0",
70
70
  "@vitejs/plugin-react": "^6.1.1",
71
+ "ajv": "^8.20.0",
72
+ "ajv-formats": "^3.0.1",
71
73
  "codemirror": "^6.0.2",
72
74
  "dompurify": "^3.4.15",
73
75
  "jsdom": "^30.1.0",
@@ -1,379 +0,0 @@
1
- #!/usr/bin/env node
2
- import {
3
- whichExecutable
4
- } from "./chunk-VSYBC3VY.js";
5
- import {
6
- scanFilesForSecrets,
7
- secretFingerprinter
8
- } from "./chunk-MPG3IEGB.js";
9
- import {
10
- walkProject
11
- } from "./chunk-ELKFRTZT.js";
12
- import {
13
- loadConfig
14
- } from "./chunk-2DY4PUR4.js";
15
-
16
- // src/services/security.ts
17
- import { execFile } from "child_process";
18
- import { promises as fs } from "fs";
19
- import path from "path";
20
- var AuditToolError = class extends Error {
21
- };
22
- var asRecord = (v) => v && typeof v === "object" && !Array.isArray(v) ? v : {};
23
- var isObject = (v) => Boolean(v) && typeof v === "object" && !Array.isArray(v);
24
- var sev = (s) => {
25
- const v = String(s ?? "").toLowerCase();
26
- if (v === "critical") return "critical";
27
- if (v === "high") return "high";
28
- if (v === "moderate" || v === "medium") return "moderate";
29
- if (v === "low" || v === "info") return "low";
30
- return "unrated";
31
- };
32
- function parseJsonObject(stdout, tool) {
33
- const text = stdout.trim();
34
- if (!text) throw new AuditToolError(`${tool} produced no output`);
35
- const json = JSON.parse(text);
36
- if (!isObject(json)) throw new AuditToolError(`${tool} produced unexpected output`);
37
- return json;
38
- }
39
- function throwIfToolError(json, tool) {
40
- if (json.error === void 0) return;
41
- const e = asRecord(json.error);
42
- const parts = [e.code, e.summary ?? e.message].filter((x) => typeof x === "string" && x.trim());
43
- const text = parts.length ? parts.join(": ") : typeof json.error === "string" ? json.error : "unknown error";
44
- throw new AuditToolError(`${tool} reported an error: ${text.split("\n")[0].slice(0, 200)}`);
45
- }
46
- function parseAdvisories(advisories) {
47
- const out = [];
48
- for (const [key, raw] of Object.entries(advisories)) {
49
- const a = asRecord(raw);
50
- const versions = [...new Set((Array.isArray(a.findings) ? a.findings : []).map((f) => asRecord(f).version).filter((v) => typeof v === "string"))];
51
- const patched = typeof a.patched_versions === "string" ? a.patched_versions.trim() : "";
52
- out.push({
53
- package: String(a.module_name ?? "unknown"),
54
- severity: sev(a.severity),
55
- title: String(a.title ?? `Vulnerable dependency: ${String(a.module_name ?? key)}`),
56
- id: typeof a.github_advisory_id === "string" ? a.github_advisory_id : Array.isArray(a.cves) && typeof a.cves[0] === "string" ? a.cves[0] : String(a.id ?? key),
57
- url: typeof a.url === "string" ? a.url : void 0,
58
- vulnerableRange: typeof a.vulnerable_versions === "string" ? a.vulnerable_versions + (versions.length ? ` (installed ${versions.join(", ")})` : "") : void 0,
59
- fixAvailable: Boolean(patched) && patched !== "<0.0.0"
60
- });
61
- }
62
- return out;
63
- }
64
- function parseNpmAudit(stdout) {
65
- const json = parseJsonObject(stdout, "npm audit");
66
- throwIfToolError(json, "npm audit");
67
- if (!isObject(json.vulnerabilities)) {
68
- if (isObject(json.advisories)) return parseAdvisories(json.advisories);
69
- throw new AuditToolError('npm audit output has no "vulnerabilities" report');
70
- }
71
- const out = [];
72
- for (const [name, raw] of Object.entries(json.vulnerabilities)) {
73
- const v = asRecord(raw);
74
- const via = Array.isArray(v.via) ? v.via : [];
75
- const advisory = via.find((x) => x && typeof x === "object");
76
- const viaNames = via.filter((x) => typeof x === "string");
77
- out.push({
78
- package: name,
79
- severity: sev(v.severity),
80
- title: String(advisory?.title ?? (viaNames.length ? `Depends on vulnerable ${viaNames.join(", ")}` : `Vulnerable dependency: ${name}`)),
81
- id: advisory?.source !== void 0 ? String(advisory.source) : void 0,
82
- url: typeof advisory?.url === "string" ? advisory.url : void 0,
83
- vulnerableRange: typeof v.range === "string" ? v.range : void 0,
84
- fixAvailable: Boolean(v.fixAvailable)
85
- });
86
- }
87
- return out;
88
- }
89
- function parsePnpmAudit(stdout) {
90
- const json = parseJsonObject(stdout, "pnpm audit");
91
- throwIfToolError(json, "pnpm audit");
92
- if (!isObject(json.advisories)) throw new AuditToolError('pnpm audit output has no "advisories" report');
93
- return parseAdvisories(json.advisories);
94
- }
95
- function parsePipAudit(stdout) {
96
- const text = stdout.trim();
97
- if (!text) throw new AuditToolError("pip-audit produced no output");
98
- const json = JSON.parse(text);
99
- const deps = isObject(json) && Array.isArray(json.dependencies) ? json.dependencies : Array.isArray(json) ? json : null;
100
- if (!deps) throw new AuditToolError('pip-audit output has no "dependencies" list');
101
- const out = [];
102
- for (const raw of deps) {
103
- const d = asRecord(raw);
104
- for (const vRaw of Array.isArray(d.vulns) ? d.vulns : []) {
105
- const v = asRecord(vRaw);
106
- const aliases = Array.isArray(v.aliases) ? v.aliases.filter((a) => typeof a === "string") : [];
107
- const id = typeof v.id === "string" ? v.id : void 0;
108
- out.push({
109
- package: String(d.name ?? "unknown"),
110
- severity: "unrated",
111
- // pip-audit does not report severity
112
- title: String(v.description ?? id ?? "Known vulnerability").split("\n")[0].slice(0, 300),
113
- id: id ?? aliases[0],
114
- url: id ? `https://osv.dev/vulnerability/${id}` : void 0,
115
- vulnerableRange: typeof d.version === "string" ? `installed ${d.version}` : void 0,
116
- fixAvailable: Array.isArray(v.fix_versions) && v.fix_versions.length > 0
117
- });
118
- }
119
- }
120
- return out;
121
- }
122
- function splitJsonStream(text) {
123
- const out = [];
124
- let depth = 0;
125
- let inString = false;
126
- let escaped = false;
127
- let start = -1;
128
- for (let i = 0; i < text.length; i++) {
129
- const ch = text[i];
130
- if (inString) {
131
- if (escaped) escaped = false;
132
- else if (ch === "\\") escaped = true;
133
- else if (ch === '"') inString = false;
134
- continue;
135
- }
136
- if (ch === '"') {
137
- if (depth > 0) inString = true;
138
- } else if (ch === "{") {
139
- if (depth === 0) start = i;
140
- depth++;
141
- } else if (ch === "}" && depth > 0) {
142
- depth--;
143
- if (depth === 0) {
144
- out.push(JSON.parse(text.slice(start, i + 1)));
145
- start = -1;
146
- }
147
- }
148
- }
149
- if (depth !== 0) throw new AuditToolError("truncated JSON output");
150
- return out;
151
- }
152
- var REACH_RANK = { required: 0, imported: 1, called: 2 };
153
- function parseGovulncheck(stdout) {
154
- const messages = splitJsonStream(stdout).map(asRecord);
155
- if (!messages.some((m) => isObject(m.config))) throw new AuditToolError("govulncheck produced no JSON report");
156
- const osvs = /* @__PURE__ */ new Map();
157
- for (const m of messages) {
158
- const osv = asRecord(m.osv);
159
- if (typeof osv.id === "string") osvs.set(osv.id, osv);
160
- }
161
- const found = /* @__PURE__ */ new Map();
162
- for (const m of messages) {
163
- const f = asRecord(m.finding);
164
- if (typeof f.osv !== "string") continue;
165
- const trace = (Array.isArray(f.trace) ? f.trace : []).map(asRecord);
166
- const reach = trace.some((t) => typeof t.function === "string") ? "called" : trace.some((t) => typeof t.package === "string") ? "imported" : "required";
167
- const first = trace[0] ?? {};
168
- const prev = found.get(f.osv);
169
- if (prev && REACH_RANK[prev.reach] >= REACH_RANK[reach]) continue;
170
- found.set(f.osv, {
171
- module: typeof first.module === "string" ? first.module : prev?.module,
172
- version: typeof first.version === "string" ? first.version : prev?.version,
173
- fixed: typeof f.fixed_version === "string" ? f.fixed_version : prev?.fixed,
174
- reach
175
- });
176
- }
177
- const label = { called: "called by your code", imported: "package imported, vulnerable symbol not called", required: "module required, package not imported" };
178
- return [...found].map(([id, f]) => {
179
- const osv = osvs.get(id) ?? {};
180
- const affected = Array.isArray(osv.affected) ? asRecord(osv.affected[0]) : {};
181
- return {
182
- package: f.module ?? String(asRecord(affected.package).name ?? "unknown"),
183
- severity: "unrated",
184
- // the Go vulnerability database does not assign severities
185
- title: `${String(osv.summary ?? id)} (${label[f.reach]})`,
186
- id,
187
- url: `https://pkg.go.dev/vuln/${id}`,
188
- vulnerableRange: f.version ? `installed ${f.version}` : void 0,
189
- fixAvailable: Boolean(f.fixed)
190
- };
191
- });
192
- }
193
- function parseCargoAudit(stdout) {
194
- const json = parseJsonObject(stdout, "cargo audit");
195
- if (!isObject(json.vulnerabilities)) throw new AuditToolError('cargo audit output has no "vulnerabilities" report');
196
- const list = Array.isArray(json.vulnerabilities.list) ? json.vulnerabilities.list : [];
197
- return list.map((raw) => {
198
- const v = asRecord(raw);
199
- const advisory = asRecord(v.advisory);
200
- const pkg = asRecord(v.package);
201
- return {
202
- package: String(pkg.name ?? "unknown"),
203
- severity: sev(asRecord(advisory.cvss).severity ?? advisory.severity),
204
- title: String(advisory.title ?? "Known vulnerability"),
205
- id: typeof advisory.id === "string" ? advisory.id : void 0,
206
- url: typeof advisory.url === "string" ? advisory.url : void 0
207
- };
208
- });
209
- }
210
- function parseComposerAudit(stdout) {
211
- const json = parseJsonObject(stdout, "composer audit");
212
- if (json.advisories === void 0) throw new AuditToolError('composer audit output has no "advisories" report');
213
- const advisories = asRecord(json.advisories);
214
- const out = [];
215
- for (const [pkg, raw] of Object.entries(advisories)) {
216
- for (const aRaw of Array.isArray(raw) ? raw : Object.values(asRecord(raw))) {
217
- const a = asRecord(aRaw);
218
- out.push({
219
- package: pkg,
220
- severity: sev(a.severity),
221
- title: String(a.title ?? "Known vulnerability"),
222
- id: typeof a.cve === "string" ? a.cve : typeof a.advisoryId === "string" ? a.advisoryId : void 0,
223
- url: typeof a.link === "string" ? a.link : void 0,
224
- vulnerableRange: typeof a.affectedVersions === "string" ? a.affectedVersions : void 0
225
- });
226
- }
227
- }
228
- return out;
229
- }
230
- var MAX_PIP_TARGETS = 10;
231
- var REQUIREMENTS_FILE = /(^|\/)requirements[\w.-]*\.txt$/i;
232
- async function planPipAudit(ctx) {
233
- const common = ["--format", "json", "--progress-spinner", "off"];
234
- const reqs = ctx.files.filter((f) => REQUIREMENTS_FILE.test(f)).sort();
235
- const reqDirs = new Set(reqs.map((f) => path.posix.dirname(f)));
236
- const out = reqs.map((f) => ({ target: f, args: ["-r", f, ...common] }));
237
- for (const f of ctx.files.filter((x) => x === "pyproject.toml" || x.endsWith("/pyproject.toml")).sort()) {
238
- const dir = path.posix.dirname(f);
239
- if (reqDirs.has(dir)) continue;
240
- const text = await fs.readFile(path.join(ctx.root, f), "utf8").catch(() => "");
241
- if (!/^\s*\[project\]\s*$/m.test(text)) continue;
242
- out.push({ target: f, args: [...common, dir === "." ? "." : `./${dir}`] });
243
- }
244
- if (!out.length) return "no requirements file to audit (pip-audit needs requirements*.txt or a PEP 621 pyproject.toml; the active Python environment is never audited)";
245
- return out.slice(0, MAX_PIP_TARGETS);
246
- }
247
- var single = (args) => async () => [{ args }];
248
- var RUNNERS = [
249
- {
250
- tool: "npm audit",
251
- ecosystem: "npm",
252
- command: "npm",
253
- applies: (_m, has) => has("package-lock.json") || has("npm-shrinkwrap.json"),
254
- plan: single(["audit", "--json", "--audit-level=low"]),
255
- parse: parseNpmAudit
256
- },
257
- {
258
- tool: "pnpm audit",
259
- ecosystem: "npm",
260
- command: "pnpm",
261
- applies: (_m, has) => has("pnpm-lock.yaml"),
262
- plan: single(["audit", "--json"]),
263
- parse: parsePnpmAudit
264
- },
265
- {
266
- tool: "pip-audit",
267
- ecosystem: "python",
268
- command: "pip-audit",
269
- applies: (m, _has, ctx) => m.manifests.some((x) => x.ecosystem === "python") || ctx.files.some((f) => REQUIREMENTS_FILE.test(f)),
270
- plan: planPipAudit,
271
- parse: parsePipAudit
272
- },
273
- {
274
- tool: "govulncheck",
275
- ecosystem: "go",
276
- command: "govulncheck",
277
- applies: (_m, has) => has("go.mod"),
278
- plan: single(["-format=json", "./..."]),
279
- parse: parseGovulncheck
280
- },
281
- {
282
- tool: "cargo audit",
283
- ecosystem: "cargo",
284
- command: "cargo",
285
- applies: (_m, has) => has("Cargo.lock"),
286
- plan: single(["audit", "--json"]),
287
- parse: parseCargoAudit
288
- },
289
- {
290
- tool: "composer audit",
291
- ecosystem: "composer",
292
- command: "composer",
293
- applies: (_m, has) => has("composer.lock"),
294
- plan: single(["audit", "--format=json"]),
295
- parse: parseComposerAudit
296
- }
297
- ];
298
- function run(command, args, cwd, timeoutMs) {
299
- return new Promise((resolve) => {
300
- execFile(command, args, { cwd, timeout: timeoutMs, maxBuffer: 32 * 1024 * 1024, windowsHide: true, env: { ...process.env, NO_COLOR: "1", npm_config_color: "false" } }, (error, stdout, stderr) => {
301
- const timedOut = Boolean(error && (error.code === "ETIMEDOUT" || error.killed));
302
- resolve({ stdout: String(stdout ?? ""), stderr: String(stderr ?? ""), timedOut, error: error ?? null });
303
- });
304
- });
305
- }
306
- async function runTool(runner, inv, root, timeoutMs) {
307
- const t = Date.now();
308
- const base = { tool: runner.tool, ecosystem: runner.ecosystem, ...inv.target ? { target: inv.target } : {} };
309
- const r = await run(runner.command, inv.args, root, timeoutMs);
310
- if (r.timedOut) return { ...base, status: "timeout", message: `Timed out after ${timeoutMs / 1e3}s`, durationMs: Date.now() - t, findings: [] };
311
- try {
312
- const findings = runner.parse(r.stdout, r.stderr);
313
- return { ...base, status: "ok", durationMs: Date.now() - t, findings };
314
- } catch (err) {
315
- const reason = err instanceof AuditToolError ? err.message : `Could not parse output: ${r.stderr.trim().split("\n")[0] || r.error?.message || err.message || "unreadable output"}`;
316
- return { ...base, status: "failed", message: reason.slice(0, 300), durationMs: Date.now() - t, findings: [] };
317
- }
318
- }
319
- async function runSecurityScan(root, model, opts = {}) {
320
- const started = Date.now();
321
- const timeoutMs = opts.timeoutMs ?? 12e4;
322
- const { config } = await loadConfig(root);
323
- const walk = await walkProject(root, { config, signal: opts.signal });
324
- const ctx = { root, files: walk.files.map((f) => f.path) };
325
- const files = new Set(model.manifests.map((m) => m.path));
326
- for (const pm of model.packageManagers) for (const e of pm.provenance.evidence) files.add(e.file);
327
- for (const f of ctx.files) files.add(f);
328
- const has = (file) => files.has(file);
329
- const tools = [];
330
- if (!opts.skipAudit) {
331
- for (const runner of RUNNERS) {
332
- if (!runner.applies(model, has, ctx)) continue;
333
- opts.signal?.throwIfAborted();
334
- const t = Date.now();
335
- opts.onTool?.(runner.tool);
336
- const bin = await whichExecutable(runner.command);
337
- if (!bin) {
338
- tools.push({ tool: runner.tool, ecosystem: runner.ecosystem, status: "unavailable", message: `${runner.command} is not installed or not on PATH`, durationMs: Date.now() - t, findings: [] });
339
- continue;
340
- }
341
- const plan = await runner.plan(ctx);
342
- if (typeof plan === "string") {
343
- tools.push({ tool: runner.tool, ecosystem: runner.ecosystem, status: "unavailable", message: plan, durationMs: Date.now() - t, findings: [] });
344
- continue;
345
- }
346
- for (const inv of plan) {
347
- opts.signal?.throwIfAborted();
348
- tools.push(await runTool(runner, inv, root, timeoutMs));
349
- }
350
- }
351
- }
352
- const counts = { critical: 0, high: 0, moderate: 0, low: 0, unrated: 0 };
353
- for (const t of tools) for (const f of t.findings) counts[f.severity]++;
354
- const secrets = await scanProjectSecrets(root, walk.files, opts.signal);
355
- const scan = {
356
- schemaVersion: 1,
357
- scannedAt: (/* @__PURE__ */ new Date()).toISOString(),
358
- durationMs: Date.now() - started,
359
- tools,
360
- counts,
361
- secrets: {
362
- count: secrets.findings.length,
363
- files: [...new Set(secrets.findings.map((s) => s.file))].slice(0, 50),
364
- skippedLongLines: secrets.skippedLongLines,
365
- ...secrets.truncated ? { truncated: true } : {}
366
- }
367
- };
368
- return scan;
369
- }
370
- async function scanProjectSecrets(root, files, signal) {
371
- const fingerprint = await secretFingerprinter(root);
372
- const read = async (rel) => fs.readFile(path.join(root, rel), "utf8").catch(() => null);
373
- return scanFilesForSecrets(files, read, { fingerprint, signal });
374
- }
375
-
376
- export {
377
- runSecurityScan
378
- };
379
- //# sourceMappingURL=chunk-SJ4DQFIJ.js.map