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.
- package/.claude/settings.example.json +30 -0
- package/ARCHITECTURE.md +410 -0
- package/LICENSE +21 -0
- package/README.md +153 -0
- package/ROADMAP.md +117 -0
- package/data/vulnkb.json +666 -0
- package/dist/bin/aegis.d.ts +3 -0
- package/dist/bin/aegis.d.ts.map +1 -0
- package/dist/bin/aegis.js +489 -0
- package/dist/bin/aegis.js.map +1 -0
- package/dist/cache.d.ts +9 -0
- package/dist/cache.d.ts.map +1 -0
- package/dist/cache.js +146 -0
- package/dist/cache.js.map +1 -0
- package/dist/engines/ai-sinks.d.ts +52 -0
- package/dist/engines/ai-sinks.d.ts.map +1 -0
- package/dist/engines/ai-sinks.js +204 -0
- package/dist/engines/ai-sinks.js.map +1 -0
- package/dist/engines/eslint.d.ts +9 -0
- package/dist/engines/eslint.d.ts.map +1 -0
- package/dist/engines/eslint.js +245 -0
- package/dist/engines/eslint.js.map +1 -0
- package/dist/engines/joern.d.ts +3 -0
- package/dist/engines/joern.d.ts.map +1 -0
- package/dist/engines/joern.js +98 -0
- package/dist/engines/joern.js.map +1 -0
- package/dist/engines/js-sinks.d.ts +70 -0
- package/dist/engines/js-sinks.d.ts.map +1 -0
- package/dist/engines/js-sinks.js +370 -0
- package/dist/engines/js-sinks.js.map +1 -0
- package/dist/engines/llm-critic.d.ts +130 -0
- package/dist/engines/llm-critic.d.ts.map +1 -0
- package/dist/engines/llm-critic.js +551 -0
- package/dist/engines/llm-critic.js.map +1 -0
- package/dist/engines/pragma.d.ts +20 -0
- package/dist/engines/pragma.d.ts.map +1 -0
- package/dist/engines/pragma.js +83 -0
- package/dist/engines/pragma.js.map +1 -0
- package/dist/engines/property-test.d.ts +3 -0
- package/dist/engines/property-test.d.ts.map +1 -0
- package/dist/engines/property-test.js +134 -0
- package/dist/engines/property-test.js.map +1 -0
- package/dist/engines/pyright.d.ts +10 -0
- package/dist/engines/pyright.d.ts.map +1 -0
- package/dist/engines/pyright.js +143 -0
- package/dist/engines/pyright.js.map +1 -0
- package/dist/engines/pysa.d.ts +3 -0
- package/dist/engines/pysa.d.ts.map +1 -0
- package/dist/engines/pysa.js +83 -0
- package/dist/engines/pysa.js.map +1 -0
- package/dist/engines/python-sinks.d.ts +82 -0
- package/dist/engines/python-sinks.d.ts.map +1 -0
- package/dist/engines/python-sinks.js +459 -0
- package/dist/engines/python-sinks.js.map +1 -0
- package/dist/engines/registry.d.ts +26 -0
- package/dist/engines/registry.d.ts.map +1 -0
- package/dist/engines/registry.js +70 -0
- package/dist/engines/registry.js.map +1 -0
- package/dist/engines/secret-scan.d.ts +22 -0
- package/dist/engines/secret-scan.d.ts.map +1 -0
- package/dist/engines/secret-scan.js +179 -0
- package/dist/engines/secret-scan.js.map +1 -0
- package/dist/engines/semgrep.d.ts +10 -0
- package/dist/engines/semgrep.d.ts.map +1 -0
- package/dist/engines/semgrep.js +200 -0
- package/dist/engines/semgrep.js.map +1 -0
- package/dist/engines/treesitter.d.ts +18 -0
- package/dist/engines/treesitter.d.ts.map +1 -0
- package/dist/engines/treesitter.js +135 -0
- package/dist/engines/treesitter.js.map +1 -0
- package/dist/engines/tsc.d.ts +10 -0
- package/dist/engines/tsc.d.ts.map +1 -0
- package/dist/engines/tsc.js +142 -0
- package/dist/engines/tsc.js.map +1 -0
- package/dist/engines/types.d.ts +47 -0
- package/dist/engines/types.d.ts.map +1 -0
- package/dist/engines/types.js +27 -0
- package/dist/engines/types.js.map +1 -0
- package/dist/findings.d.ts +121 -0
- package/dist/findings.d.ts.map +1 -0
- package/dist/findings.js +98 -0
- package/dist/findings.js.map +1 -0
- package/dist/hooks/claude-code.d.ts +3 -0
- package/dist/hooks/claude-code.d.ts.map +1 -0
- package/dist/hooks/claude-code.js +187 -0
- package/dist/hooks/claude-code.js.map +1 -0
- package/dist/index/context.d.ts +127 -0
- package/dist/index/context.d.ts.map +1 -0
- package/dist/index/context.js +267 -0
- package/dist/index/context.js.map +1 -0
- package/dist/index/embeddings.d.ts +68 -0
- package/dist/index/embeddings.d.ts.map +1 -0
- package/dist/index/embeddings.js +570 -0
- package/dist/index/embeddings.js.map +1 -0
- package/dist/index/graph_routing.d.ts +36 -0
- package/dist/index/graph_routing.d.ts.map +1 -0
- package/dist/index/graph_routing.js +170 -0
- package/dist/index/graph_routing.js.map +1 -0
- package/dist/index/joern.d.ts +76 -0
- package/dist/index/joern.d.ts.map +1 -0
- package/dist/index/joern.js +782 -0
- package/dist/index/joern.js.map +1 -0
- package/dist/index/property-test.d.ts +88 -0
- package/dist/index/property-test.d.ts.map +1 -0
- package/dist/index/property-test.js +466 -0
- package/dist/index/property-test.js.map +1 -0
- package/dist/index/proto/scip.proto +897 -0
- package/dist/index/pysa.d.ts +91 -0
- package/dist/index/pysa.d.ts.map +1 -0
- package/dist/index/pysa.js +617 -0
- package/dist/index/pysa.js.map +1 -0
- package/dist/index/scip.d.ts +76 -0
- package/dist/index/scip.d.ts.map +1 -0
- package/dist/index/scip.js +541 -0
- package/dist/index/scip.js.map +1 -0
- package/dist/index/vulrag.d.ts +86 -0
- package/dist/index/vulrag.d.ts.map +1 -0
- package/dist/index/vulrag.js +242 -0
- package/dist/index/vulrag.js.map +1 -0
- package/dist/index.d.ts +9 -0
- package/dist/index.d.ts.map +1 -0
- package/dist/index.js +8 -0
- package/dist/index.js.map +1 -0
- package/dist/install/claude-code.d.ts +31 -0
- package/dist/install/claude-code.d.ts.map +1 -0
- package/dist/install/claude-code.js +447 -0
- package/dist/install/claude-code.js.map +1 -0
- package/dist/lang.d.ts +5 -0
- package/dist/lang.d.ts.map +1 -0
- package/dist/lang.js +52 -0
- package/dist/lang.js.map +1 -0
- package/dist/learning/suppressions.d.ts +70 -0
- package/dist/learning/suppressions.d.ts.map +1 -0
- package/dist/learning/suppressions.js +179 -0
- package/dist/learning/suppressions.js.map +1 -0
- package/dist/mcp/server.d.ts +2 -0
- package/dist/mcp/server.d.ts.map +1 -0
- package/dist/mcp/server.js +187 -0
- package/dist/mcp/server.js.map +1 -0
- package/dist/mcp/tools/explain.d.ts +58 -0
- package/dist/mcp/tools/explain.d.ts.map +1 -0
- package/dist/mcp/tools/explain.js +60 -0
- package/dist/mcp/tools/explain.js.map +1 -0
- package/dist/mcp/tools/precheck.d.ts +29 -0
- package/dist/mcp/tools/precheck.d.ts.map +1 -0
- package/dist/mcp/tools/precheck.js +42 -0
- package/dist/mcp/tools/precheck.js.map +1 -0
- package/dist/mcp/tools/validate.d.ts +73 -0
- package/dist/mcp/tools/validate.d.ts.map +1 -0
- package/dist/mcp/tools/validate.js +66 -0
- package/dist/mcp/tools/validate.js.map +1 -0
- package/dist/mcp/warm.d.ts +88 -0
- package/dist/mcp/warm.d.ts.map +1 -0
- package/dist/mcp/warm.js +331 -0
- package/dist/mcp/warm.js.map +1 -0
- package/dist/orchestrator.d.ts +46 -0
- package/dist/orchestrator.d.ts.map +1 -0
- package/dist/orchestrator.js +596 -0
- package/dist/orchestrator.js.map +1 -0
- package/dist/policy.d.ts +51 -0
- package/dist/policy.d.ts.map +1 -0
- package/dist/policy.js +201 -0
- package/dist/policy.js.map +1 -0
- package/dist/risk.d.ts +31 -0
- package/dist/risk.d.ts.map +1 -0
- package/dist/risk.js +92 -0
- package/dist/risk.js.map +1 -0
- package/dist/stats.d.ts +72 -0
- package/dist/stats.d.ts.map +1 -0
- package/dist/stats.js +217 -0
- package/dist/stats.js.map +1 -0
- package/dist/telemetry/collector.d.ts +10 -0
- package/dist/telemetry/collector.d.ts.map +1 -0
- package/dist/telemetry/collector.js +75 -0
- package/dist/telemetry/collector.js.map +1 -0
- package/dist/telemetry/consent.d.ts +9 -0
- package/dist/telemetry/consent.d.ts.map +1 -0
- package/dist/telemetry/consent.js +42 -0
- package/dist/telemetry/consent.js.map +1 -0
- package/dist/telemetry/installation.d.ts +2 -0
- package/dist/telemetry/installation.d.ts.map +1 -0
- package/dist/telemetry/installation.js +32 -0
- package/dist/telemetry/installation.js.map +1 -0
- package/dist/telemetry/sanitizer.d.ts +5 -0
- package/dist/telemetry/sanitizer.d.ts.map +1 -0
- package/dist/telemetry/sanitizer.js +60 -0
- package/dist/telemetry/sanitizer.js.map +1 -0
- package/dist/telemetry/types.d.ts +39 -0
- package/dist/telemetry/types.d.ts.map +1 -0
- package/dist/telemetry/types.js +4 -0
- package/dist/telemetry/types.js.map +1 -0
- package/dist/telemetry/uploader.d.ts +12 -0
- package/dist/telemetry/uploader.d.ts.map +1 -0
- package/dist/telemetry/uploader.js +92 -0
- package/dist/telemetry/uploader.js.map +1 -0
- package/dist/util/logger.d.ts +19 -0
- package/dist/util/logger.d.ts.map +1 -0
- package/dist/util/logger.js +58 -0
- package/dist/util/logger.js.map +1 -0
- package/dist/util/safe-paths.d.ts +8 -0
- package/dist/util/safe-paths.d.ts.map +1 -0
- package/dist/util/safe-paths.js +102 -0
- package/dist/util/safe-paths.js.map +1 -0
- package/dist/util/subprocess.d.ts +32 -0
- package/dist/util/subprocess.d.ts.map +1 -0
- package/dist/util/subprocess.js +137 -0
- package/dist/util/subprocess.js.map +1 -0
- 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 @@
|
|
|
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":"AAAA;;;;;;;;;;;;;;;;;;;GAmBG;AACH,OAAO,EAAE,UAAU,EAAE,MAAM,SAAS,CAAC;AAErC,OAAO,EAAW,aAAa,EAAE,aAAa,EAAE,MAAM,gBAAgB,CAAC;AACvE,OAAO,EAAE,gBAAgB,EAA0B,cAAc,EAAE,MAAM,2BAA2B,CAAC;AACrG,OAAO,EAAE,OAAO,EAAE,MAAM,kBAAkB,CAAC;AAC3C,OAAO,EAAE,SAAS,EAAE,MAAM,mBAAmB,CAAC;AAG9C,MAAM,GAAG,GAAG,SAAS,CAAC,uBAAuB,CAAC,CAAC;AAE/C,MAAM,kBAAkB;IACb,IAAI,GAAG,eAAwB,CAAC;IAChC,SAAS,GAAG,CAAC,QAAQ,CAAU,CAAC;IAEjC,iBAAiB,GAAiD,IAAI,CAAC;IACvE,MAAM,CAAU,mBAAmB,GAAG,MAAM,CAAC;IAErD,KAAK,CAAC,SAAS;QACb,MAAM,GAAG,GAAG,IAAI,CAAC,GAAG,EAAE,CAAC;QACvB,IACE,IAAI,CAAC,iBAAiB;YACtB,GAAG,GAAG,IAAI,CAAC,iBAAiB,CAAC,SAAS,GAAG,kBAAkB,CAAC,mBAAmB,EAC/E,CAAC;YACD,OAAO,IAAI,CAAC,iBAAiB,CAAC,KAAK,CAAC;QACtC,CAAC;QACD,mEAAmE;QACnE,gBAAgB;QAChB,IAAI,KAAK,GAAG,KAAK,CAAC;QAClB,IAAI,OAAO,CAAC,QAAQ,KAAK,OAAO,EAAE,CAAC;YACjC,KAAK,GAAG,OAAO,EAAE,KAAK,IAAI,CAAC;QAC7B,CAAC;aAAM,CAAC;YACN,yDAAyD;YACzD,uBAAuB;YACvB,KAAK,GAAG,KAAK,CAAC;QAChB,CAAC;QACD,MAAM,QAAQ,GAAG,CAAC,MAAM,cAAc,EAAE,CAAC,KAAK,IAAI,CAAC;QACnD,MAAM,EAAE,GAAG,KAAK,IAAI,QAAQ,CAAC;QAC7B,IAAI,CAAC,iBAAiB,GAAG,EAAE,KAAK,EAAE,EAAE,EAAE,SAAS,EAAE,GAAG,EAAE,CAAC;QACvD,IAAI,CAAC,EAAE,EAAE,CAAC;YACR,GAAG,CAAC,KAAK,CAAC,2BAA2B,EAAE,EAAE,KAAK,EAAE,QAAQ,EAAE,CAAC,CAAC;QAC9D,CAAC;QACD,OAAO,EAAE,CAAC;IACZ,CAAC;IAED,KAAK,CAAC,GAAG,CAAC,KAAqB;QAC7B,MAAM,EAAE,GAAG,IAAI,CAAC,GAAG,EAAE,CAAC;QACtB,IAAI,OAA4B,CAAC;QACjC,IAAI,CAAC;YACH,OAAO,GAAG,MAAM,gBAAgB,CAAC,KAAK,CAAC,OAAO,CAAC,CAAC;QAClD,CAAC;QAAC,OAAO,GAAG,EAAE,CAAC;YACb,GAAG,CAAC,IAAI,CAAC,0BAA0B,EAAE,EAAE,GAAG,EAAE,MAAM,CAAC,GAAG,CAAC,EAAE,CAAC,CAAC;YAC3D,OAAO;gBACL,MAAM,EAAE,IAAI,CAAC,IAAI;gBACjB,QAAQ,EAAE,EAAE;gBACZ,WAAW,EAAE,KAAK;gBAClB,UAAU,EAAE,IAAI,CAAC,GAAG,EAAE,GAAG,EAAE;gBAC3B,MAAM,EAAE,MAAM,CAAC,GAAG,CAAC,CAAC,KAAK,CAAC,CAAC,EAAE,GAAG,CAAC;aAClC,CAAC;QACJ,CAAC;QAED,MAAM,QAAQ,GAAc,EAAE,CAAC;QAC/B,KAAK,MAAM,CAAC,IAAI,OAAO,EAAE,CAAC;YACxB,IAAI,CAAC,CAAC,CAAC,QAAQ;gBAAE,SAAS;YAC1B,MAAM,MAAM,GAAG,0BAA0B,GAAG,CAAC,CAAC,SAAS,CAAC,IAAI,CAAC;YAC7D,MAAM,OAAO,GAAY;gBACvB,EAAE,EAAE,aAAa,CAAC;oBAChB,MAAM,EAAE,YAAY,EAAE,uDAAuD;oBAC7E,IAAI,EAAE,KAAK,CAAC,QAAQ;oBACpB,IAAI,EAAE,CAAC,CAAC,SAAS,CAAC,IAAI;oBACtB,OAAO,EAAE,MAAM;iBAChB,CAAC;gBACF,gEAAgE;gBAChE,0DAA0D;gBAC1D,sDAAsD;gBACtD,gEAAgE;gBAChE,WAAW;gBACX,MAAM,EAAE,YAAY;gBACpB,IAAI,EAAE,KAAK,CAAC,QAAQ;gBACpB,IAAI,EAAE,CAAC,CAAC,SAAS,CAAC,IAAI;gBACtB,OAAO,EAAE,MAAM;gBACf,QAAQ,EAAE,MAAM;gBAChB,gEAAgE;gBAChE,8DAA8D;gBAC9D,aAAa;gBACb,UAAU,EAAE,IAAI;gBAChB,MAAM,EAAE,QAAQ;gBAChB,OAAO,EACL,kDAAkD;oBAClD,CAAC,CAAC,SAAS,CAAC,IAAI;oBAChB,KAAK;oBACL,CAAC,CAAC,CAAC,SAAS,IAAI,oBAAoB,CAAC;oBACrC,KAAK;oBACL,CAAC,CAAC,CAAC,cAAc,IAAI,EAAE,CAAC,CAAC,KAAK,CAAC,IAAI,CAAC,CAAC,CAAC,CAAE,CAAC,KAAK,CAAC,CAAC,EAAE,GAAG,CAAC;gBACxD,QAAQ,EAAE;oBACR,OAAO,EACL,YAAY;wBACZ,CAAC,CAAC,SAAS,CAAC,IAAI;wBAChB,UAAU;wBACV,MAAM,CAAC,CAAC,CAAC,SAAS,CAAC,IAAI,CAAC;wBACxB,gBAAgB;wBAChB,CAAC,CAAC,CAAC,SAAS,IAAI,eAAe,CAAC;wBAChC,yCAAyC;wBACzC,CAAC,CAAC,CAAC,cAAc,IAAI,gBAAgB,CAAC,CAAC,KAAK,CAAC,CAAC,EAAE,IAAI,CAAC;iBACjC;gBACxB,WAAW,EACT,8NAA8N;aACjO,CAAC;YACF,MAAM,MAAM,GAAG,aAAa,CAAC,SAAS,CAAC,OAAO,CAAC,CAAC;YAChD,IAAI,MAAM,CAAC,OAAO;gBAAE,QAAQ,CAAC,IAAI,CAAC,MAAM,CAAC,IAAI,CAAC,CAAC;QACjD,CAAC;QAED,OAAO;YACL,MAAM,EAAE,IAAI,CAAC,IAAI;YACjB,QAAQ;YACR,WAAW,EAAE,KAAK;YAClB,UAAU,EAAE,IAAI,CAAC,GAAG,EAAE,GAAG,EAAE;SAC5B,CAAC;IACJ,CAAC;;AAGH,MAAM,CAAC,MAAM,kBAAkB,GAAW,IAAI,kBAAkB,EAAE,CAAC;AAEnE,yEAAyE;AACzE,wEAAwE;AACxE,KAAK,UAAU,CAAC"}
|
|
@@ -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":"AAAA;;;;;GAKG;AACH,OAAO,EAAE,SAAS,EAAE,KAAK,EAAE,EAAE,EAAE,MAAM,kBAAkB,CAAC;AACxD,OAAO,EAAE,MAAM,EAAE,MAAM,SAAS,CAAC;AACjC,OAAO,EAAE,IAAI,EAAE,MAAM,WAAW,CAAC;AAGjC,OAAO,EAAW,aAAa,EAAY,MAAM,gBAAgB,CAAC;AAElE,OAAO,EAAE,GAAG,EAAE,KAAK,EAAE,MAAM,uBAAuB,CAAC;AACnD,OAAO,EAAE,SAAS,EAAE,MAAM,mBAAmB,CAAC;AAE9C,MAAM,GAAG,GAAG,SAAS,CAAC,sBAAsB,CAAC,CAAC;AAE9C,MAAM,SAAS,GAAwB,CAAC,QAAQ,CAAC,CAAC;AAiBlD,SAAS,MAAM,CAAC,CAAqB;IACnC,QAAQ,CAAC,EAAE,CAAC;QACV,KAAK,OAAO;YACV,OAAO,MAAM,CAAC;QAChB,KAAK,SAAS;YACZ,OAAO,QAAQ,CAAC;QAClB;YACE,OAAO,MAAM,CAAC;IAClB,CAAC;AACH,CAAC;AAED,SAAS,aAAa,CAAC,CAAqB;IAC1C,OAAO,CAAC,KAAK,OAAO,CAAC,CAAC,CAAC,IAAI,CAAC,CAAC,CAAC,CAAC,KAAK,SAAS,CAAC,CAAC,CAAC,GAAG,CAAC,CAAC,CAAC,GAAG,CAAC;AAC5D,CAAC;AAED,KAAK,UAAU,WAAW;IACxB,MAAM,QAAQ,GAAG,OAAO,CAAC,GAAG,CAAC,iBAAiB,CAAC;IAC/C,IAAI,QAAQ;QAAE,OAAO,QAAQ,CAAC;IAC9B,OAAO,CAAC,MAAM,KAAK,CAAC,SAAS,CAAC,CAAC,IAAI,CAAC,MAAM,KAAK,CAAC,aAAa,CAAC,CAAC,CAAC;AAClE,CAAC;AAED,MAAM,OAAO,aAAa;IACf,IAAI,GAAG,SAAS,CAAC;IACjB,SAAS,GAAG,SAAS,CAAC;IAEvB,aAAa,GAAmB,IAAI,CAAC;IAE7C,KAAK,CAAC,SAAS;QACb,IAAI,IAAI,CAAC,aAAa,KAAK,IAAI;YAAE,OAAO,IAAI,CAAC,aAAa,CAAC;QAC3D,IAAI,CAAC,aAAa,GAAG,CAAC,MAAM,WAAW,EAAE,CAAC,KAAK,IAAI,CAAC;QACpD,OAAO,IAAI,CAAC,aAAa,CAAC;IAC5B,CAAC;IAED,KAAK,CAAC,GAAG,CAAC,KAAqB;QAC7B,MAAM,EAAE,GAAG,IAAI,CAAC,GAAG,EAAE,CAAC;QACtB,MAAM,GAAG,GAAG,MAAM,WAAW,EAAE,CAAC;QAChC,IAAI,CAAC,GAAG,EAAE,CAAC;YACT,OAAO;gBACL,MAAM,EAAE,IAAI,CAAC,IAAI;gBACjB,QAAQ,EAAE,EAAE;gBACZ,WAAW,EAAE,IAAI;gBACjB,UAAU,EAAE,IAAI,CAAC,GAAG,EAAE,GAAG,EAAE;gBAC3B,MAAM,EAAE,uBAAuB;aAChC,CAAC;QACJ,CAAC;QACD,4CAA4C;QAC5C,MAAM,OAAO,GAAG,CAAC,MAAM,KAAK,CAC1B,IAAI,CAAC,MAAM,EAAE,EAAE,iBAAiB,OAAO,CAAC,GAAG,IAAI,IAAI,CAAC,GAAG,EAAE,EAAE,CAAC,EAC5D,EAAE,SAAS,EAAE,IAAI,EAAE,CACpB,CAAW,CAAC;QACb,MAAM,WAAW,GAAG,IAAI,CAAC,OAAO,EAAE,QAAQ,CAAC,KAAK,CAAC,QAAQ,CAAC,CAAC,CAAC;QAC5D,IAAI,CAAC;YACH,MAAM,SAAS,CAAC,WAAW,EAAE,KAAK,CAAC,OAAO,EAAE,MAAM,CAAC,CAAC;YACpD,MAAM,CAAC,GAAG,MAAM,GAAG,CAAC,GAAG,EAAE;gBACvB,IAAI,EAAE,CAAC,cAAc,EAAE,WAAW,CAAC;gBACnC,SAAS,EAAE,IAAI,CAAC,GAAG,CAAC,KAAK,CAAC,SAAS,EAAE,MAAM,CAAC;aAC7C,CAAC,CAAC;YACH,IAAI,CAAC,CAAC,QAAQ,EAAE,CAAC;gBACf,OAAO;oBACL,MAAM,EAAE,IAAI,CAAC,IAAI;oBACjB,QAAQ,EAAE,EAAE;oBACZ,WAAW,EAAE,IAAI;oBACjB,UAAU,EAAE,IAAI,CAAC,GAAG,EAAE,GAAG,EAAE;oBAC3B,MAAM,EAAE,SAAS;iBAClB,CAAC;YACJ,CAAC;YACD,IAAI,CAAC,CAAC,CAAC,MAAM,CAAC,IAAI,EAAE,EAAE,CAAC;gBACrB,OAAO;oBACL,MAAM,EAAE,IAAI,CAAC,IAAI;oBACjB,QAAQ,EAAE,EAAE;oBACZ,WAAW,EAAE,KAAK;oBAClB,UAAU,EAAE,IAAI,CAAC,GAAG,EAAE,GAAG,EAAE;iBAC5B,CAAC;YACJ,CAAC;YACD,IAAI,IAAmB,CAAC;YACxB,IAAI,CAAC;gBACH,IAAI,GAAG,IAAI,CAAC,KAAK,CAAC,CAAC,CAAC,MAAM,CAAkB,CAAC;YAC/C,CAAC;YAAC,OAAO,GAAG,EAAE,CAAC;gBACb,GAAG,CAAC,IAAI,CAAC,iBAAiB,EAAE,EAAE,GAAG,EAAE,MAAM,CAAC,GAAG,CAAC,EAAE,CAAC,CAAC;gBAClD,OAAO;oBACL,MAAM,EAAE,IAAI,CAAC,IAAI;oBACjB,QAAQ,EAAE,EAAE;oBACZ,WAAW,EAAE,IAAI;oBACjB,UAAU,EAAE,IAAI,CAAC,GAAG,EAAE,GAAG,EAAE;oBAC3B,MAAM,EAAE,iBAAiB;iBAC1B,CAAC;YACJ,CAAC;YACD,MAAM,QAAQ,GAAc,EAAE,CAAC;YAC/B,KAAK,MAAM,IAAI,IAAI,IAAI,CAAC,kBAAkB,IAAI,EAAE,EAAE,CAAC;gBACjD,MAAM,GAAG,GAAG,IAAI,CAAC,QAAQ,IAAI,aAAa,CAAC;gBAC3C,IAAI,GAAG,KAAK,aAAa;oBAAE,SAAS;gBACpC,MAAM,IAAI,GAAG,CAAC,IAAI,CAAC,KAAK,EAAE,KAAK,EAAE,IAAI,IAAI,CAAC,CAAC,GAAG,CAAC,CAAC;gBAChD,MAAM,GAAG,GAAG,CAAC,IAAI,CAAC,KAAK,EAAE,KAAK,EAAE,SAAS,IAAI,CAAC,CAAC,GAAG,CAAC,CAAC;gBACpD,MAAM,IAAI,GAAG,IAAI,CAAC,IAAI,IAAI,SAAS,CAAC;gBACpC,QAAQ,CAAC,IAAI,CAAC;oBACZ,EAAE,EAAE,aAAa,CAAC;wBAChB,MAAM,EAAE,IAAI,CAAC,IAAI;wBACjB,IAAI,EAAE,KAAK,CAAC,QAAQ;wBACpB,IAAI;wBACJ,OAAO,EAAE,IAAI;qBACd,CAAC;oBACF,MAAM,EAAE,SAAS;oBACjB,IAAI,EAAE,KAAK,CAAC,QAAQ;oBACpB,IAAI;oBACJ,GAAG;oBACH,OAAO,EAAE,IAAI;oBACb,QAAQ,EAAE,MAAM,CAAC,GAAG,CAAC;oBACrB,OAAO,EAAE,CAAC,IAAI,CAAC,OAAO,IAAI,YAAY,CAAC,CAAC,KAAK,CAAC,CAAC,EAAE,IAAI,CAAC;oBACtD,UAAU,EAAE,aAAa,CAAC,GAAG,CAAC;oBAC9B,MAAM,EAAE,MAAM;iBACf,CAAC,CAAC;YACL,CAAC;YACD,OAAO;gBACL,MAAM,EAAE,IAAI,CAAC,IAAI;gBACjB,QAAQ;gBACR,WAAW,EAAE,KAAK;gBAClB,UAAU,EAAE,IAAI,CAAC,GAAG,EAAE,GAAG,EAAE;aAC5B,CAAC;QACJ,CAAC;gBAAS,CAAC;YACT,IAAI,CAAC;gBACH,MAAM,EAAE,CAAC,OAAO,EAAE,EAAE,SAAS,EAAE,IAAI,EAAE,KAAK,EAAE,IAAI,EAAE,CAAC,CAAC;YACtD,CAAC;YAAC,MAAM,CAAC;gBACP,SAAS;YACX,CAAC;QACH,CAAC;IACH,CAAC;CACF;AAED,SAAS,QAAQ,CAAC,CAAS;IACzB,MAAM,CAAC,GAAG,CAAC,CAAC,OAAO,CAAC,KAAK,EAAE,GAAG,CAAC,CAAC,KAAK,CAAC,GAAG,CAAC,CAAC;IAC3C,OAAO,CAAC,CAAC,CAAC,CAAC,MAAM,GAAG,CAAC,CAAC,IAAI,SAAS,CAAC;AACtC,CAAC"}
|
|
@@ -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"}
|