dravix-agent 0.1.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 (208) hide show
  1. package/.claude/settings.example.json +30 -0
  2. package/ARCHITECTURE.md +410 -0
  3. package/LICENSE +21 -0
  4. package/README.md +153 -0
  5. package/ROADMAP.md +117 -0
  6. package/data/vulnkb.json +666 -0
  7. package/dist/bin/aegis.d.ts +3 -0
  8. package/dist/bin/aegis.d.ts.map +1 -0
  9. package/dist/bin/aegis.js +489 -0
  10. package/dist/bin/aegis.js.map +1 -0
  11. package/dist/cache.d.ts +9 -0
  12. package/dist/cache.d.ts.map +1 -0
  13. package/dist/cache.js +146 -0
  14. package/dist/cache.js.map +1 -0
  15. package/dist/engines/ai-sinks.d.ts +52 -0
  16. package/dist/engines/ai-sinks.d.ts.map +1 -0
  17. package/dist/engines/ai-sinks.js +204 -0
  18. package/dist/engines/ai-sinks.js.map +1 -0
  19. package/dist/engines/eslint.d.ts +9 -0
  20. package/dist/engines/eslint.d.ts.map +1 -0
  21. package/dist/engines/eslint.js +245 -0
  22. package/dist/engines/eslint.js.map +1 -0
  23. package/dist/engines/joern.d.ts +3 -0
  24. package/dist/engines/joern.d.ts.map +1 -0
  25. package/dist/engines/joern.js +98 -0
  26. package/dist/engines/joern.js.map +1 -0
  27. package/dist/engines/js-sinks.d.ts +70 -0
  28. package/dist/engines/js-sinks.d.ts.map +1 -0
  29. package/dist/engines/js-sinks.js +370 -0
  30. package/dist/engines/js-sinks.js.map +1 -0
  31. package/dist/engines/llm-critic.d.ts +130 -0
  32. package/dist/engines/llm-critic.d.ts.map +1 -0
  33. package/dist/engines/llm-critic.js +551 -0
  34. package/dist/engines/llm-critic.js.map +1 -0
  35. package/dist/engines/pragma.d.ts +20 -0
  36. package/dist/engines/pragma.d.ts.map +1 -0
  37. package/dist/engines/pragma.js +83 -0
  38. package/dist/engines/pragma.js.map +1 -0
  39. package/dist/engines/property-test.d.ts +3 -0
  40. package/dist/engines/property-test.d.ts.map +1 -0
  41. package/dist/engines/property-test.js +134 -0
  42. package/dist/engines/property-test.js.map +1 -0
  43. package/dist/engines/pyright.d.ts +10 -0
  44. package/dist/engines/pyright.d.ts.map +1 -0
  45. package/dist/engines/pyright.js +143 -0
  46. package/dist/engines/pyright.js.map +1 -0
  47. package/dist/engines/pysa.d.ts +3 -0
  48. package/dist/engines/pysa.d.ts.map +1 -0
  49. package/dist/engines/pysa.js +83 -0
  50. package/dist/engines/pysa.js.map +1 -0
  51. package/dist/engines/python-sinks.d.ts +82 -0
  52. package/dist/engines/python-sinks.d.ts.map +1 -0
  53. package/dist/engines/python-sinks.js +459 -0
  54. package/dist/engines/python-sinks.js.map +1 -0
  55. package/dist/engines/registry.d.ts +26 -0
  56. package/dist/engines/registry.d.ts.map +1 -0
  57. package/dist/engines/registry.js +70 -0
  58. package/dist/engines/registry.js.map +1 -0
  59. package/dist/engines/secret-scan.d.ts +22 -0
  60. package/dist/engines/secret-scan.d.ts.map +1 -0
  61. package/dist/engines/secret-scan.js +179 -0
  62. package/dist/engines/secret-scan.js.map +1 -0
  63. package/dist/engines/semgrep.d.ts +10 -0
  64. package/dist/engines/semgrep.d.ts.map +1 -0
  65. package/dist/engines/semgrep.js +200 -0
  66. package/dist/engines/semgrep.js.map +1 -0
  67. package/dist/engines/treesitter.d.ts +18 -0
  68. package/dist/engines/treesitter.d.ts.map +1 -0
  69. package/dist/engines/treesitter.js +135 -0
  70. package/dist/engines/treesitter.js.map +1 -0
  71. package/dist/engines/tsc.d.ts +10 -0
  72. package/dist/engines/tsc.d.ts.map +1 -0
  73. package/dist/engines/tsc.js +142 -0
  74. package/dist/engines/tsc.js.map +1 -0
  75. package/dist/engines/types.d.ts +47 -0
  76. package/dist/engines/types.d.ts.map +1 -0
  77. package/dist/engines/types.js +27 -0
  78. package/dist/engines/types.js.map +1 -0
  79. package/dist/findings.d.ts +121 -0
  80. package/dist/findings.d.ts.map +1 -0
  81. package/dist/findings.js +98 -0
  82. package/dist/findings.js.map +1 -0
  83. package/dist/hooks/claude-code.d.ts +3 -0
  84. package/dist/hooks/claude-code.d.ts.map +1 -0
  85. package/dist/hooks/claude-code.js +187 -0
  86. package/dist/hooks/claude-code.js.map +1 -0
  87. package/dist/index/context.d.ts +127 -0
  88. package/dist/index/context.d.ts.map +1 -0
  89. package/dist/index/context.js +267 -0
  90. package/dist/index/context.js.map +1 -0
  91. package/dist/index/embeddings.d.ts +68 -0
  92. package/dist/index/embeddings.d.ts.map +1 -0
  93. package/dist/index/embeddings.js +570 -0
  94. package/dist/index/embeddings.js.map +1 -0
  95. package/dist/index/graph_routing.d.ts +36 -0
  96. package/dist/index/graph_routing.d.ts.map +1 -0
  97. package/dist/index/graph_routing.js +170 -0
  98. package/dist/index/graph_routing.js.map +1 -0
  99. package/dist/index/joern.d.ts +76 -0
  100. package/dist/index/joern.d.ts.map +1 -0
  101. package/dist/index/joern.js +782 -0
  102. package/dist/index/joern.js.map +1 -0
  103. package/dist/index/property-test.d.ts +88 -0
  104. package/dist/index/property-test.d.ts.map +1 -0
  105. package/dist/index/property-test.js +466 -0
  106. package/dist/index/property-test.js.map +1 -0
  107. package/dist/index/proto/scip.proto +897 -0
  108. package/dist/index/pysa.d.ts +91 -0
  109. package/dist/index/pysa.d.ts.map +1 -0
  110. package/dist/index/pysa.js +617 -0
  111. package/dist/index/pysa.js.map +1 -0
  112. package/dist/index/scip.d.ts +76 -0
  113. package/dist/index/scip.d.ts.map +1 -0
  114. package/dist/index/scip.js +541 -0
  115. package/dist/index/scip.js.map +1 -0
  116. package/dist/index/vulrag.d.ts +86 -0
  117. package/dist/index/vulrag.d.ts.map +1 -0
  118. package/dist/index/vulrag.js +242 -0
  119. package/dist/index/vulrag.js.map +1 -0
  120. package/dist/index.d.ts +9 -0
  121. package/dist/index.d.ts.map +1 -0
  122. package/dist/index.js +8 -0
  123. package/dist/index.js.map +1 -0
  124. package/dist/install/claude-code.d.ts +31 -0
  125. package/dist/install/claude-code.d.ts.map +1 -0
  126. package/dist/install/claude-code.js +447 -0
  127. package/dist/install/claude-code.js.map +1 -0
  128. package/dist/lang.d.ts +5 -0
  129. package/dist/lang.d.ts.map +1 -0
  130. package/dist/lang.js +52 -0
  131. package/dist/lang.js.map +1 -0
  132. package/dist/learning/suppressions.d.ts +70 -0
  133. package/dist/learning/suppressions.d.ts.map +1 -0
  134. package/dist/learning/suppressions.js +179 -0
  135. package/dist/learning/suppressions.js.map +1 -0
  136. package/dist/mcp/server.d.ts +2 -0
  137. package/dist/mcp/server.d.ts.map +1 -0
  138. package/dist/mcp/server.js +187 -0
  139. package/dist/mcp/server.js.map +1 -0
  140. package/dist/mcp/tools/explain.d.ts +58 -0
  141. package/dist/mcp/tools/explain.d.ts.map +1 -0
  142. package/dist/mcp/tools/explain.js +60 -0
  143. package/dist/mcp/tools/explain.js.map +1 -0
  144. package/dist/mcp/tools/precheck.d.ts +29 -0
  145. package/dist/mcp/tools/precheck.d.ts.map +1 -0
  146. package/dist/mcp/tools/precheck.js +42 -0
  147. package/dist/mcp/tools/precheck.js.map +1 -0
  148. package/dist/mcp/tools/validate.d.ts +73 -0
  149. package/dist/mcp/tools/validate.d.ts.map +1 -0
  150. package/dist/mcp/tools/validate.js +66 -0
  151. package/dist/mcp/tools/validate.js.map +1 -0
  152. package/dist/mcp/warm.d.ts +88 -0
  153. package/dist/mcp/warm.d.ts.map +1 -0
  154. package/dist/mcp/warm.js +331 -0
  155. package/dist/mcp/warm.js.map +1 -0
  156. package/dist/orchestrator.d.ts +46 -0
  157. package/dist/orchestrator.d.ts.map +1 -0
  158. package/dist/orchestrator.js +596 -0
  159. package/dist/orchestrator.js.map +1 -0
  160. package/dist/policy.d.ts +51 -0
  161. package/dist/policy.d.ts.map +1 -0
  162. package/dist/policy.js +201 -0
  163. package/dist/policy.js.map +1 -0
  164. package/dist/risk.d.ts +31 -0
  165. package/dist/risk.d.ts.map +1 -0
  166. package/dist/risk.js +92 -0
  167. package/dist/risk.js.map +1 -0
  168. package/dist/stats.d.ts +72 -0
  169. package/dist/stats.d.ts.map +1 -0
  170. package/dist/stats.js +217 -0
  171. package/dist/stats.js.map +1 -0
  172. package/dist/telemetry/collector.d.ts +10 -0
  173. package/dist/telemetry/collector.d.ts.map +1 -0
  174. package/dist/telemetry/collector.js +75 -0
  175. package/dist/telemetry/collector.js.map +1 -0
  176. package/dist/telemetry/consent.d.ts +9 -0
  177. package/dist/telemetry/consent.d.ts.map +1 -0
  178. package/dist/telemetry/consent.js +42 -0
  179. package/dist/telemetry/consent.js.map +1 -0
  180. package/dist/telemetry/installation.d.ts +2 -0
  181. package/dist/telemetry/installation.d.ts.map +1 -0
  182. package/dist/telemetry/installation.js +32 -0
  183. package/dist/telemetry/installation.js.map +1 -0
  184. package/dist/telemetry/sanitizer.d.ts +5 -0
  185. package/dist/telemetry/sanitizer.d.ts.map +1 -0
  186. package/dist/telemetry/sanitizer.js +60 -0
  187. package/dist/telemetry/sanitizer.js.map +1 -0
  188. package/dist/telemetry/types.d.ts +39 -0
  189. package/dist/telemetry/types.d.ts.map +1 -0
  190. package/dist/telemetry/types.js +4 -0
  191. package/dist/telemetry/types.js.map +1 -0
  192. package/dist/telemetry/uploader.d.ts +12 -0
  193. package/dist/telemetry/uploader.d.ts.map +1 -0
  194. package/dist/telemetry/uploader.js +92 -0
  195. package/dist/telemetry/uploader.js.map +1 -0
  196. package/dist/util/logger.d.ts +19 -0
  197. package/dist/util/logger.d.ts.map +1 -0
  198. package/dist/util/logger.js +58 -0
  199. package/dist/util/logger.js.map +1 -0
  200. package/dist/util/safe-paths.d.ts +8 -0
  201. package/dist/util/safe-paths.d.ts.map +1 -0
  202. package/dist/util/safe-paths.js +102 -0
  203. package/dist/util/safe-paths.js.map +1 -0
  204. package/dist/util/subprocess.d.ts +32 -0
  205. package/dist/util/subprocess.d.ts.map +1 -0
  206. package/dist/util/subprocess.js +137 -0
  207. package/dist/util/subprocess.js.map +1 -0
  208. package/package.json +93 -0
@@ -0,0 +1,83 @@
1
+ /**
2
+ * Suppression helpers shared across deterministic sink engines.
3
+ *
4
+ * In-line pragma: any line that ends with one of the recognised comment
5
+ * forms causes the sink engines to skip findings on that line:
6
+ *
7
+ * # aegis-allow (Python / shell / Ruby)
8
+ * // aegis-allow (JS / TS / Go / Rust / Java)
9
+ * block-comment with the marker (JS / TS / Java) — slash-star then slash
10
+ *
11
+ * An optional rule id can be added: "# aegis-allow:eval-tainted" only
12
+ * skips that one rule on the line. Without an id, ALL rules skip.
13
+ *
14
+ * Path auto-skip: files that BY DEFINITION contain the very patterns we
15
+ * scan for — test fixtures, the engine source itself, the Vul-RAG KB —
16
+ * return true from `isTestOrFixturePath`. The orchestrator's
17
+ * deterministic sink engines bail early on those paths; the LLM critic
18
+ * still runs (so a real bug planted in a test file is still caught,
19
+ * but the literal pattern matches don't fire).
20
+ */
21
+ import { basename } from "node:path";
22
+ const PRAGMA_RE = /(?:#|\/\/|\/\*)\s*aegis-allow(?::([\w.-]+))?/;
23
+ /** Returns true if the given source LINE has an aegis-allow comment.
24
+ * When a rule id is named in the pragma, the suppression only applies
25
+ * if it matches `ruleId`. */
26
+ export function isLineSuppressed(line, ruleId) {
27
+ const m = PRAGMA_RE.exec(line);
28
+ if (!m)
29
+ return false;
30
+ const targeted = m[1];
31
+ if (!targeted)
32
+ return true;
33
+ return targeted === ruleId || targeted === "*";
34
+ }
35
+ /** Path-based auto-skip for deterministic sink rules. Test files and
36
+ * the sink-engine source files themselves are NOT scanned by the
37
+ * regex-based engines (they would self-detect). The LLM critic still
38
+ * scans them, so a genuine bug planted in such a file IS still caught.
39
+ *
40
+ * Recognised path shapes:
41
+ * - any path containing /tests/, /test/, /__tests__/, /fixtures/
42
+ * - filename ending in .test.<ext> or .spec.<ext>
43
+ * - test_<name>.py or <name>_test.{py,go,rb}
44
+ * - conftest.py
45
+ * - any *-sinks.{ts,js,py} / secret-scan.{ts,js,py} (engine source)
46
+ * - vulnkb.json / vulnkb.jsonl (KB data)
47
+ * - *fixture* in the basename
48
+ */
49
+ export function isTestOrFixturePath(filePath) {
50
+ const p = filePath.replace(/\\/g, "/").toLowerCase();
51
+ const base = basename(p);
52
+ if (p.includes("/tests/") ||
53
+ p.includes("/test/") ||
54
+ p.includes("/__tests__/") ||
55
+ p.includes("/fixtures/") ||
56
+ p.includes("/__fixtures__/")) {
57
+ return true;
58
+ }
59
+ // Engine source files at aegis-v2/src/engines/** + helper scripts —
60
+ // ours by definition. The LLM critic still scans them.
61
+ if (p.includes("/src/engines/") ||
62
+ p.includes("/dist/engines/") ||
63
+ p.includes("/scripts/"))
64
+ return true;
65
+ if (/\.test\.[a-z0-9]+$/.test(base))
66
+ return true;
67
+ if (/\.spec\.[a-z0-9]+$/.test(base))
68
+ return true;
69
+ // Alphanumeric-before-_test so __test.py / __t.py fixture paths used
70
+ // by unit tests calling the engine directly don't auto-skip.
71
+ if (/^test_[a-z0-9].*\.py$/.test(base) || /[a-z0-9]_test\.(?:py|go|rb)$/.test(base))
72
+ return true;
73
+ if (base === "conftest.py")
74
+ return true;
75
+ if (/[-_](?:sinks|scan|signature|rules)\.(?:ts|js|py)$/.test(base))
76
+ return true;
77
+ if (base === "vulnkb.json" || base === "vulnkb.jsonl")
78
+ return true;
79
+ if (/[-_]fixture/.test(base))
80
+ return true;
81
+ return false;
82
+ }
83
+ //# sourceMappingURL=pragma.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"pragma.js","sourceRoot":"","sources":["../../src/engines/pragma.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;GAmBG;AACH,OAAO,EAAE,QAAQ,EAAE,MAAM,WAAW,CAAC;AAErC,MAAM,SAAS,GAAG,8CAA8C,CAAC;AAEjE;;6BAE6B;AAC7B,MAAM,UAAU,gBAAgB,CAAC,IAAY,EAAE,MAAc;IAC3D,MAAM,CAAC,GAAG,SAAS,CAAC,IAAI,CAAC,IAAI,CAAC,CAAC;IAC/B,IAAI,CAAC,CAAC;QAAE,OAAO,KAAK,CAAC;IACrB,MAAM,QAAQ,GAAG,CAAC,CAAC,CAAC,CAAC,CAAC;IACtB,IAAI,CAAC,QAAQ;QAAE,OAAO,IAAI,CAAC;IAC3B,OAAO,QAAQ,KAAK,MAAM,IAAI,QAAQ,KAAK,GAAG,CAAC;AACjD,CAAC;AAED;;;;;;;;;;;;;GAaG;AACH,MAAM,UAAU,mBAAmB,CAAC,QAAgB;IAClD,MAAM,CAAC,GAAG,QAAQ,CAAC,OAAO,CAAC,KAAK,EAAE,GAAG,CAAC,CAAC,WAAW,EAAE,CAAC;IACrD,MAAM,IAAI,GAAG,QAAQ,CAAC,CAAC,CAAC,CAAC;IACzB,IACE,CAAC,CAAC,QAAQ,CAAC,SAAS,CAAC;QACrB,CAAC,CAAC,QAAQ,CAAC,QAAQ,CAAC;QACpB,CAAC,CAAC,QAAQ,CAAC,aAAa,CAAC;QACzB,CAAC,CAAC,QAAQ,CAAC,YAAY,CAAC;QACxB,CAAC,CAAC,QAAQ,CAAC,gBAAgB,CAAC,EAC5B,CAAC;QACD,OAAO,IAAI,CAAC;IACd,CAAC;IACD,oEAAoE;IACpE,uDAAuD;IACvD,IACE,CAAC,CAAC,QAAQ,CAAC,eAAe,CAAC;QAC3B,CAAC,CAAC,QAAQ,CAAC,gBAAgB,CAAC;QAC5B,CAAC,CAAC,QAAQ,CAAC,WAAW,CAAC;QAEvB,OAAO,IAAI,CAAC;IACd,IAAI,oBAAoB,CAAC,IAAI,CAAC,IAAI,CAAC;QAAE,OAAO,IAAI,CAAC;IACjD,IAAI,oBAAoB,CAAC,IAAI,CAAC,IAAI,CAAC;QAAE,OAAO,IAAI,CAAC;IACjD,qEAAqE;IACrE,6DAA6D;IAC7D,IAAI,uBAAuB,CAAC,IAAI,CAAC,IAAI,CAAC,IAAI,8BAA8B,CAAC,IAAI,CAAC,IAAI,CAAC;QAAE,OAAO,IAAI,CAAC;IACjG,IAAI,IAAI,KAAK,aAAa;QAAE,OAAO,IAAI,CAAC;IACxC,IAAI,mDAAmD,CAAC,IAAI,CAAC,IAAI,CAAC;QAAE,OAAO,IAAI,CAAC;IAChF,IAAI,IAAI,KAAK,aAAa,IAAI,IAAI,KAAK,cAAc;QAAE,OAAO,IAAI,CAAC;IACnE,IAAI,aAAa,CAAC,IAAI,CAAC,IAAI,CAAC;QAAE,OAAO,IAAI,CAAC;IAC1C,OAAO,KAAK,CAAC;AACf,CAAC"}
@@ -0,0 +1,3 @@
1
+ import type { Engine } from "./types.js";
2
+ export declare const propertyTestEngine: Engine;
3
+ //# sourceMappingURL=property-test.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"property-test.d.ts","sourceRoot":"","sources":["../../src/engines/property-test.ts"],"names":[],"mappings":"AA0BA,OAAO,KAAK,EAAE,MAAM,EAAmC,MAAM,YAAY,CAAC;AAkH1E,eAAO,MAAM,kBAAkB,EAAE,MAAiC,CAAC"}
@@ -0,0 +1,134 @@
1
+ /**
2
+ * Property-based testing engine — wraps src/index/property-test.ts as an
3
+ * Aegis Engine. Stage 2 (runs after stage 1 like the LLM critic): when a
4
+ * Python file has at least one high-risk candidate AND the orchestrator
5
+ * decides to invoke this engine, we generate + run Hypothesis properties
6
+ * and surface counterexamples as proven-bug Findings.
7
+ *
8
+ * Routing (lives in this engine for now; the orchestrator just calls run):
9
+ * - Languages: Python only (Hypothesis is Python-specific; fast-check
10
+ * for JS/TS is a Phase 3 expansion).
11
+ * - Skip when no high-risk function name pattern matches AND no
12
+ * arithmetic operator appears in the file (`findPropertyCandidates`
13
+ * already returns [] in that case → engine returns no findings, cheap).
14
+ *
15
+ * Findings carry source="critic" because the LLM authored the test, but
16
+ * confidence is **0.95** — much higher than a normal critic finding,
17
+ * because Hypothesis has produced a **concrete failing input**, not a
18
+ * speculative judgement. The orchestrator's risk gate treats this as
19
+ * effectively a definitive block.
20
+ */
21
+ import { existsSync } from "node:fs";
22
+ import { FindingSchema, makeFindingId } from "../findings.js";
23
+ import { runPropertyTests, _findClaudeCli } from "../index/property-test.js";
24
+ import { findWsl } from "../index/pysa.js";
25
+ import { getLogger } from "../util/logger.js";
26
+ const log = getLogger("aegis.engine.property");
27
+ class PropertyTestEngine {
28
+ name = "property-test";
29
+ languages = ["python"];
30
+ availabilityCache = null;
31
+ static AVAILABILITY_TTL_MS = 60_000;
32
+ async available() {
33
+ const now = Date.now();
34
+ if (this.availabilityCache &&
35
+ now - this.availabilityCache.checkedAt < PropertyTestEngine.AVAILABILITY_TTL_MS) {
36
+ return this.availabilityCache.value;
37
+ }
38
+ // Need: WSL (Win) OR python on PATH (POSIX) + claude CLI. We probe
39
+ // both quickly.
40
+ let wslOk = false;
41
+ if (process.platform === "win32") {
42
+ wslOk = findWsl() !== null;
43
+ }
44
+ else {
45
+ // Linux/macOS path — not implemented yet; engine reports
46
+ // unavailable cleanly.
47
+ wslOk = false;
48
+ }
49
+ const claudeOk = (await _findClaudeCli()) !== null;
50
+ const ok = wslOk && claudeOk;
51
+ this.availabilityCache = { value: ok, checkedAt: now };
52
+ if (!ok) {
53
+ log.debug("property-test unavailable", { wslOk, claudeOk });
54
+ }
55
+ return ok;
56
+ }
57
+ async run(input) {
58
+ const t0 = Date.now();
59
+ let results;
60
+ try {
61
+ results = await runPropertyTests(input.content);
62
+ }
63
+ catch (err) {
64
+ log.warn("property test run failed", { err: String(err) });
65
+ return {
66
+ engine: this.name,
67
+ findings: [],
68
+ unavailable: false,
69
+ durationMs: Date.now() - t0,
70
+ reason: String(err).slice(0, 200),
71
+ };
72
+ }
73
+ const findings = [];
74
+ for (const r of results) {
75
+ if (!r.bugFound)
76
+ continue;
77
+ const ruleId = "property.counterexample." + r.candidate.name;
78
+ const finding = {
79
+ id: makeFindingId({
80
+ engine: "llm-critic", // critic-authored test → emit under critic engine slot
81
+ file: input.filePath,
82
+ line: r.candidate.line,
83
+ rule_id: ruleId,
84
+ }),
85
+ // We use the existing "llm-critic" engine slot in EngineEnum to
86
+ // avoid a schema migration. A future schema bump will add
87
+ // "property-test" as its own slot. The rule_id prefix
88
+ // ``property.counterexample.`` makes the distinction obvious in
89
+ // reports.
90
+ engine: "llm-critic",
91
+ file: input.filePath,
92
+ line: r.candidate.line,
93
+ rule_id: ruleId,
94
+ severity: "high",
95
+ // Hypothesis produced a concrete failing input — this is PROVEN
96
+ // wrong code, not a speculative judgement. We rate confidence
97
+ // very high.
98
+ confidence: 0.95,
99
+ source: "critic",
100
+ message: "Property-based test found a counterexample for `" +
101
+ r.candidate.name +
102
+ "`: " +
103
+ (r.invariant ?? "invariant violated") +
104
+ " — " +
105
+ (r.counterexample ?? "").split("\n")[0].slice(0, 200),
106
+ evidence: {
107
+ snippet: "Function `" +
108
+ r.candidate.name +
109
+ "` (line " +
110
+ String(r.candidate.line) +
111
+ ")\nInvariant: " +
112
+ (r.invariant ?? "(none stated)") +
113
+ "\n\nCounterexample (from Hypothesis):\n" +
114
+ (r.counterexample ?? "(not captured)").slice(0, 1500),
115
+ },
116
+ remediation: "Run the Hypothesis test locally to reproduce: paste the function + the property into a test file and run `pytest --hypothesis-seed=42`. The counterexample above is the smallest shrunken input that violates the invariant.",
117
+ };
118
+ const parsed = FindingSchema.safeParse(finding);
119
+ if (parsed.success)
120
+ findings.push(parsed.data);
121
+ }
122
+ return {
123
+ engine: this.name,
124
+ findings,
125
+ unavailable: false,
126
+ durationMs: Date.now() - t0,
127
+ };
128
+ }
129
+ }
130
+ export const propertyTestEngine = new PropertyTestEngine();
131
+ // Defensive: ensure the existsSync import isn't flagged as unused by tsc
132
+ // (we may use it in a future availability check; keep the symbol live).
133
+ void existsSync;
134
+ //# sourceMappingURL=property-test.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"property-test.js","sourceRoot":"","sources":["../../src/engines/property-test.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,10 @@
1
+ import { Engine, EngineRunInput, EngineRunResult } from "./types.js";
2
+ import { Lang } from "../lang.js";
3
+ export declare class PyrightEngine implements Engine {
4
+ readonly name = "pyright";
5
+ readonly languages: readonly Lang[];
6
+ private _availability;
7
+ available(): Promise<boolean>;
8
+ run(input: EngineRunInput): Promise<EngineRunResult>;
9
+ }
10
+ //# sourceMappingURL=pyright.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"pyright.d.ts","sourceRoot":"","sources":["../../src/engines/pyright.ts"],"names":[],"mappings":"AAUA,OAAO,EAAE,MAAM,EAAE,cAAc,EAAE,eAAe,EAAE,MAAM,YAAY,CAAC;AAErE,OAAO,EAAE,IAAI,EAAE,MAAM,YAAY,CAAC;AA4ClC,qBAAa,aAAc,YAAW,MAAM;IAC1C,QAAQ,CAAC,IAAI,aAAa;IAC1B,QAAQ,CAAC,SAAS,kBAAa;IAE/B,OAAO,CAAC,aAAa,CAAwB;IAEvC,SAAS,IAAI,OAAO,CAAC,OAAO,CAAC;IAM7B,GAAG,CAAC,KAAK,EAAE,cAAc,GAAG,OAAO,CAAC,eAAe,CAAC;CA6F3D"}
@@ -0,0 +1,143 @@
1
+ /**
2
+ * Pyright adapter — subprocess to `pyright --outputjson`.
3
+ *
4
+ * Python type errors (unresolved imports, wrong arity, type mismatches, undefined
5
+ * names) catch ~30% of LLM-generated Python bugs without any semantic analysis.
6
+ */
7
+ import { writeFile, mkdir, rm } from "node:fs/promises";
8
+ import { tmpdir } from "node:os";
9
+ import { join } from "node:path";
10
+ import { makeFindingId } from "../findings.js";
11
+ import { run, which } from "../util/subprocess.js";
12
+ import { getLogger } from "../util/logger.js";
13
+ const log = getLogger("aegis.engine.pyright");
14
+ const SUPPORTED = ["python"];
15
+ function sevMap(s) {
16
+ switch (s) {
17
+ case "error":
18
+ return "high";
19
+ case "warning":
20
+ return "medium";
21
+ default:
22
+ return "info";
23
+ }
24
+ }
25
+ function confidenceFor(s) {
26
+ return s === "error" ? 0.85 : s === "warning" ? 0.7 : 0.4;
27
+ }
28
+ async function findPyright() {
29
+ const override = process.env.AEGIS_PYRIGHT_BIN;
30
+ if (override)
31
+ return override;
32
+ return (await which("pyright")) ?? (await which("pyright.cmd"));
33
+ }
34
+ export class PyrightEngine {
35
+ name = "pyright";
36
+ languages = SUPPORTED;
37
+ _availability = null;
38
+ async available() {
39
+ if (this._availability !== null)
40
+ return this._availability;
41
+ this._availability = (await findPyright()) !== null;
42
+ return this._availability;
43
+ }
44
+ async run(input) {
45
+ const t0 = Date.now();
46
+ const bin = await findPyright();
47
+ if (!bin) {
48
+ return {
49
+ engine: this.name,
50
+ findings: [],
51
+ unavailable: true,
52
+ durationMs: Date.now() - t0,
53
+ reason: "pyright_not_installed",
54
+ };
55
+ }
56
+ // Pyright expects a file on disk. Stage it.
57
+ const scratch = (await mkdir(join(tmpdir(), `aegis-pyright-${process.pid}-${Date.now()}`), { recursive: true }));
58
+ const scratchPath = join(scratch, basename(input.filePath));
59
+ try {
60
+ await writeFile(scratchPath, input.content, "utf8");
61
+ const r = await run(bin, {
62
+ args: ["--outputjson", scratchPath],
63
+ timeoutMs: Math.min(input.timeoutMs, 15_000),
64
+ });
65
+ if (r.timedOut) {
66
+ return {
67
+ engine: this.name,
68
+ findings: [],
69
+ unavailable: true,
70
+ durationMs: Date.now() - t0,
71
+ reason: "timeout",
72
+ };
73
+ }
74
+ if (!r.stdout.trim()) {
75
+ return {
76
+ engine: this.name,
77
+ findings: [],
78
+ unavailable: false,
79
+ durationMs: Date.now() - t0,
80
+ };
81
+ }
82
+ let data;
83
+ try {
84
+ data = JSON.parse(r.stdout);
85
+ }
86
+ catch (err) {
87
+ log.warn("non-JSON output", { err: String(err) });
88
+ return {
89
+ engine: this.name,
90
+ findings: [],
91
+ unavailable: true,
92
+ durationMs: Date.now() - t0,
93
+ reason: "non_json_output",
94
+ };
95
+ }
96
+ const findings = [];
97
+ for (const diag of data.generalDiagnostics ?? []) {
98
+ const sev = diag.severity ?? "information";
99
+ if (sev === "information")
100
+ continue;
101
+ const line = (diag.range?.start?.line ?? 0) + 1;
102
+ const col = (diag.range?.start?.character ?? 0) + 1;
103
+ const rule = diag.rule ?? "pyright";
104
+ findings.push({
105
+ id: makeFindingId({
106
+ engine: this.name,
107
+ file: input.filePath,
108
+ line,
109
+ rule_id: rule,
110
+ }),
111
+ engine: "pyright",
112
+ file: input.filePath,
113
+ line,
114
+ col,
115
+ rule_id: rule,
116
+ severity: sevMap(sev),
117
+ message: (diag.message ?? "type error").slice(0, 1900),
118
+ confidence: confidenceFor(sev),
119
+ source: "type",
120
+ });
121
+ }
122
+ return {
123
+ engine: this.name,
124
+ findings,
125
+ unavailable: false,
126
+ durationMs: Date.now() - t0,
127
+ };
128
+ }
129
+ finally {
130
+ try {
131
+ await rm(scratch, { recursive: true, force: true });
132
+ }
133
+ catch {
134
+ // ignore
135
+ }
136
+ }
137
+ }
138
+ }
139
+ function basename(p) {
140
+ const m = p.replace(/\\/g, "/").split("/");
141
+ return m[m.length - 1] || "file.py";
142
+ }
143
+ //# sourceMappingURL=pyright.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"pyright.js","sourceRoot":"","sources":["../../src/engines/pyright.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,3 @@
1
+ import type { Engine } from "./types.js";
2
+ export declare const pysaEngine: Engine;
3
+ //# sourceMappingURL=pysa.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"pysa.d.ts","sourceRoot":"","sources":["../../src/engines/pysa.ts"],"names":[],"mappings":"AAoBA,OAAO,KAAK,EAAE,MAAM,EAAmC,MAAM,YAAY,CAAC;AAiF1E,eAAO,MAAM,UAAU,EAAE,MAAyB,CAAC"}
@@ -0,0 +1,83 @@
1
+ /**
2
+ * Pysa engine adapter — reads pre-computed Pysa taint findings from cache.
3
+ *
4
+ * The actual ``pyre analyze`` invocation (slow, 30s-10min) lives in
5
+ * ``src/index/pysa.ts`` and is driven by ``aegis index --pysa``. This
6
+ * module is the realtime fast path: read the project's ``findings.jsonl``,
7
+ * filter to the file under review, return ``Finding[]``.
8
+ *
9
+ * Like ``JoernEngine``, this is a "batch + cache" engine — never run
10
+ * pyre per-edit (would blow the latency budget by 100x).
11
+ */
12
+ import { existsSync } from "node:fs";
13
+ import { pysaFindingsPath, pysaInfoPath, probePyre, readPysaFindings, } from "../index/pysa.js";
14
+ import { getLogger } from "../util/logger.js";
15
+ const log = getLogger("aegis.engine.pysa");
16
+ class PysaEngine {
17
+ name = "pysa";
18
+ // Pysa is Python-only. We narrow the language list so the orchestrator
19
+ // skips this engine instantly for TS/JS/Go files instead of running it
20
+ // and returning an empty result.
21
+ languages = ["python"];
22
+ availabilityCache = null;
23
+ static AVAILABILITY_TTL_MS = 60_000;
24
+ async available() {
25
+ const now = Date.now();
26
+ if (this.availabilityCache &&
27
+ now - this.availabilityCache.checkedAt < PysaEngine.AVAILABILITY_TTL_MS) {
28
+ return this.availabilityCache.value;
29
+ }
30
+ const probe = await probePyre();
31
+ const ok = probe !== null;
32
+ this.availabilityCache = { value: ok, checkedAt: now };
33
+ if (!ok)
34
+ log.debug("pysa unavailable — pyre not reachable (no WSL or not installed)");
35
+ return ok;
36
+ }
37
+ async run(input) {
38
+ const t0 = Date.now();
39
+ if (!input.projectRoot) {
40
+ return {
41
+ engine: this.name,
42
+ findings: [],
43
+ unavailable: false,
44
+ durationMs: Date.now() - t0,
45
+ reason: "no_project_root",
46
+ };
47
+ }
48
+ const findingsPath = pysaFindingsPath(input.projectRoot);
49
+ const infoPath = pysaInfoPath(input.projectRoot);
50
+ if (!existsSync(findingsPath) || !existsSync(infoPath)) {
51
+ return {
52
+ engine: this.name,
53
+ findings: [],
54
+ unavailable: false,
55
+ durationMs: Date.now() - t0,
56
+ reason: "no_taint_cache — run `aegis index --pysa` to build",
57
+ };
58
+ }
59
+ const all = readPysaFindings(input.projectRoot);
60
+ const relPath = toRelPosix(input.filePath, input.projectRoot);
61
+ const baseName = relPath.split("/").pop() ?? relPath;
62
+ // Same multi-match heuristic as JoernEngine: full rel path, basename,
63
+ // or any path ending with /basename. Tolerant of path-normalization
64
+ // drift between Pysa (Linux/WSL) and Aegis (Windows).
65
+ const fileFindings = all.filter((f) => f.file === relPath || f.file === baseName || f.file.endsWith("/" + baseName));
66
+ return {
67
+ engine: this.name,
68
+ findings: fileFindings,
69
+ unavailable: false,
70
+ durationMs: Date.now() - t0,
71
+ };
72
+ }
73
+ }
74
+ function toRelPosix(filePath, projectRoot) {
75
+ const fp = filePath.replace(/\\/g, "/");
76
+ const root = projectRoot.replace(/\\/g, "/").replace(/\/+$/, "");
77
+ if (fp.toLowerCase().startsWith(root.toLowerCase() + "/")) {
78
+ return fp.slice(root.length + 1);
79
+ }
80
+ return fp;
81
+ }
82
+ export const pysaEngine = new PysaEngine();
83
+ //# sourceMappingURL=pysa.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"pysa.js","sourceRoot":"","sources":["../../src/engines/pysa.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,82 @@
1
+ /**
2
+ * Deterministic Python dangerous-sinks scanner — no external binary.
3
+ *
4
+ * Catches the textbook patterns every Python security linter flags:
5
+ * - ``os.system(<non-literal>)`` CWE-78
6
+ * - ``subprocess.*(<non-literal>, shell=True)`` CWE-78
7
+ * - ``subprocess.*(f"...")`` CWE-78
8
+ * - ``eval(<non-literal>)`` / ``exec(<non-literal>)`` CWE-95
9
+ * - ``compile(<non-literal>, ...)`` CWE-95
10
+ * - ``pickle.{loads,load}(...)`` CWE-502
11
+ * - ``yaml.load(<arg>)`` without ``Loader=`` CWE-502
12
+ * - ``__import__(<non-literal>)`` CWE-94
13
+ * - ``send_file(<tainted>)`` / ``os.path.join + var`` CWE-22
14
+ * - ``urlopen(<non-literal>)`` CWE-918
15
+ * - ``hashlib.{md5,sha1}(...)`` / ``hashlib.new("md5"|"sha1")`` CWE-327
16
+ *
17
+ * Why this exists: when Semgrep / Pyright / Joern / Pysa are not
18
+ * installed-or-indexed for a project, stage-1 has nothing that catches
19
+ * these patterns. The Vul-RAG router fires the critic ONLY above a
20
+ * similarity threshold which routinely misses textbook command-injection
21
+ * snippets (verified 2026-05-26 — ``os.system(f"ping {host}")`` returned
22
+ * top-1 similarity below 0.55). Without a deterministic sink scanner,
23
+ * the gate silently allows CWE-78 writes.
24
+ *
25
+ * Precision strategy: each pattern emits severity HIGH but
26
+ * **confidence 0.75** — high enough to trigger stage-2 critic routing
27
+ * via ``stage1_findings``, low enough that the critic's verdict can
28
+ * still drive the final decision. The critic refines (drops literal-arg
29
+ * FPs, escalates true taint to critical) — see V2-08 design.
30
+ *
31
+ * Lang scope: Python only. The patterns are syntactically Python-specific.
32
+ */
33
+ import { Engine, EngineRunInput, EngineRunResult } from "./types.js";
34
+ import { Lang } from "../lang.js";
35
+ interface SinkRule {
36
+ id: string;
37
+ cwe: string;
38
+ /** Severity to emit BEFORE critic refinement. */
39
+ severity: "high" | "critical";
40
+ /** Short human description for the message. */
41
+ what: string;
42
+ /** Regex that captures the call site. Must use the `g` flag.
43
+ * Group 1 = the FIRST positional arg (text only, may be approximate
44
+ * for multi-arg calls). Group 2 (optional) = the rest of the args. */
45
+ pattern: RegExp;
46
+ /** Decide if the matched call is dangerous given the first arg + full
47
+ * file content (for context-aware heuristics like "is this Flask?"). */
48
+ isDangerous: (firstArg: string, allArgs: string, fullContent?: string) => boolean;
49
+ /** Optional file-level gate — if present and returns false, the rule
50
+ * is SKIPPED entirely. Cheaper than per-match checks for rules that
51
+ * only make sense in specific frameworks. */
52
+ fileGate?: (fullContent: string) => boolean;
53
+ remediation: string;
54
+ }
55
+ /** A snippet "looks tainted" if it is NOT a pure string literal. We treat
56
+ * f-strings as tainted because their substituted parts are interpolation —
57
+ * which is the entire point of an injection bug. */
58
+ declare function looksTainted(arg: string): boolean;
59
+ /** Detect ``shell=True`` anywhere in the call args. */
60
+ declare function hasShellTrue(args: string): boolean;
61
+ /** Detect ``Loader=`` keyword in yaml.load arg list. */
62
+ declare function hasYamlLoader(args: string): boolean;
63
+ /** Approximate first positional argument from a parenthesized call body.
64
+ * Naive: split on the first top-level comma. Good enough for the patterns
65
+ * here; we explicitly do NOT attempt to handle nested calls precisely
66
+ * because the critic refines. */
67
+ declare function firstPositionalArg(body: string): string;
68
+ export declare class PythonSinksEngine implements Engine {
69
+ readonly name: "python-sinks";
70
+ readonly languages: ReadonlyArray<Lang>;
71
+ available(): Promise<boolean>;
72
+ run(input: EngineRunInput): Promise<EngineRunResult>;
73
+ }
74
+ export declare const _testing: {
75
+ RULES: readonly SinkRule[];
76
+ looksTainted: typeof looksTainted;
77
+ firstPositionalArg: typeof firstPositionalArg;
78
+ hasShellTrue: typeof hasShellTrue;
79
+ hasYamlLoader: typeof hasYamlLoader;
80
+ };
81
+ export {};
82
+ //# sourceMappingURL=python-sinks.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"python-sinks.d.ts","sourceRoot":"","sources":["../../src/engines/python-sinks.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GA+BG;AACH,OAAO,EAAE,MAAM,EAAE,cAAc,EAAE,eAAe,EAAE,MAAM,YAAY,CAAC;AAErE,OAAO,EAAE,IAAI,EAAE,MAAM,YAAY,CAAC;AAGlC,UAAU,QAAQ;IAChB,EAAE,EAAE,MAAM,CAAC;IACX,GAAG,EAAE,MAAM,CAAC;IACZ,iDAAiD;IACjD,QAAQ,EAAE,MAAM,GAAG,UAAU,CAAC;IAC9B,+CAA+C;IAC/C,IAAI,EAAE,MAAM,CAAC;IACb;;0EAEsE;IACtE,OAAO,EAAE,MAAM,CAAC;IAChB;4EACwE;IACxE,WAAW,EAAE,CAAC,QAAQ,EAAE,MAAM,EAAE,OAAO,EAAE,MAAM,EAAE,WAAW,CAAC,EAAE,MAAM,KAAK,OAAO,CAAC;IAClF;;iDAE6C;IAC7C,QAAQ,CAAC,EAAE,CAAC,WAAW,EAAE,MAAM,KAAK,OAAO,CAAC;IAC5C,WAAW,EAAE,MAAM,CAAC;CACrB;AAID;;oDAEoD;AACpD,iBAAS,YAAY,CAAC,GAAG,EAAE,MAAM,GAAG,OAAO,CAgB1C;AAED,uDAAuD;AACvD,iBAAS,YAAY,CAAC,IAAI,EAAE,MAAM,GAAG,OAAO,CAE3C;AAED,wDAAwD;AACxD,iBAAS,aAAa,CAAC,IAAI,EAAE,MAAM,GAAG,OAAO,CAE5C;AAED;;;iCAGiC;AACjC,iBAAS,kBAAkB,CAAC,IAAI,EAAE,MAAM,GAAG,MAAM,CAkBhD;AAoXD,qBAAa,iBAAkB,YAAW,MAAM;IAC9C,QAAQ,CAAC,IAAI,EAAG,cAAc,CAAU;IACxC,QAAQ,CAAC,SAAS,EAAE,aAAa,CAAC,IAAI,CAAC,CAAc;IAE/C,SAAS,IAAI,OAAO,CAAC,OAAO,CAAC;IAI7B,GAAG,CAAC,KAAK,EAAE,cAAc,GAAG,OAAO,CAAC,eAAe,CAAC;CA2D3D;AAGD,eAAO,MAAM,QAAQ;;;;;;CAA2E,CAAC"}