circle-ir 3.194.0 → 3.195.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 (39) hide show
  1. package/configs/README.md +39 -0
  2. package/dist/analysis/config-loader.d.ts.map +1 -1
  3. package/dist/analysis/config-loader.js +43 -3
  4. package/dist/analysis/config-loader.js.map +1 -1
  5. package/dist/analysis/passes/language-sources-pass.d.ts.map +1 -1
  6. package/dist/analysis/passes/language-sources-pass.js +101 -0
  7. package/dist/analysis/passes/language-sources-pass.js.map +1 -1
  8. package/dist/analysis/taint-matcher.d.ts.map +1 -1
  9. package/dist/analysis/taint-matcher.js +24 -8
  10. package/dist/analysis/taint-matcher.js.map +1 -1
  11. package/dist/browser/circle-ir.js +220 -600
  12. package/dist/core/circle-ir-core.cjs +59 -13
  13. package/dist/core/circle-ir-core.js +59 -13
  14. package/dist/languages/plugins/bash.d.ts.map +1 -1
  15. package/dist/languages/plugins/bash.js +12 -16
  16. package/dist/languages/plugins/bash.js.map +1 -1
  17. package/dist/languages/plugins/go.d.ts +4 -0
  18. package/dist/languages/plugins/go.d.ts.map +1 -1
  19. package/dist/languages/plugins/go.js +16 -32
  20. package/dist/languages/plugins/go.js.map +1 -1
  21. package/dist/languages/plugins/java.d.ts +9 -1
  22. package/dist/languages/plugins/java.d.ts.map +1 -1
  23. package/dist/languages/plugins/java.js +16 -127
  24. package/dist/languages/plugins/java.js.map +1 -1
  25. package/dist/languages/plugins/javascript.d.ts +7 -0
  26. package/dist/languages/plugins/javascript.d.ts.map +1 -1
  27. package/dist/languages/plugins/javascript.js +10 -117
  28. package/dist/languages/plugins/javascript.js.map +1 -1
  29. package/dist/languages/plugins/python.d.ts +7 -0
  30. package/dist/languages/plugins/python.d.ts.map +1 -1
  31. package/dist/languages/plugins/python.js +24 -177
  32. package/dist/languages/plugins/python.js.map +1 -1
  33. package/dist/languages/plugins/rust.d.ts +9 -0
  34. package/dist/languages/plugins/rust.d.ts.map +1 -1
  35. package/dist/languages/plugins/rust.js +22 -110
  36. package/dist/languages/plugins/rust.js.map +1 -1
  37. package/dist/types/config.d.ts +15 -0
  38. package/dist/types/config.d.ts.map +1 -1
  39. package/package.json +3 -2
@@ -0,0 +1,39 @@
1
+ # `configs/` — documentation and export surface (not runtime)
2
+
3
+ **Nothing in `src/` reads these files.** circle-ir must run in the browser and
4
+ in Cloudflare Workers, so the library cannot touch the filesystem at all. The
5
+ taint patterns that actually drive analysis live in TypeScript:
6
+
7
+ | Surface | Location | Role |
8
+ |---|---|---|
9
+ | `DEFAULT_SOURCES` / `DEFAULT_SINKS` / `DEFAULT_SANITIZERS` / `DEFAULT_SINK_SEMANTICS` | `src/analysis/config-loader.ts` | **Canonical.** Language-agnostic by default; scope a pattern with `languages: ['python']` when its method name collides across ecosystems. |
10
+ | `LanguagePlugin.getBuiltinSources()` / `getBuiltinSinks()` | `src/languages/plugins/<lang>.ts` | Supplement for language-specific patterns that have **no** `DEFAULT_*` counterpart. `TaintMatcherPass` merges them after the canonical registry. |
11
+ | `configs/**` (this directory) | — | Documentation, and an export for downstream tools that want a machine-readable view of the ruleset. Editing a file here changes nothing about how code is analysed. |
12
+
13
+ See ADR-004 in `.specifica/mvp/design.md` for the rationale.
14
+
15
+ ## Adding or fixing a pattern
16
+
17
+ Add it to `DEFAULT_SINKS` / `DEFAULT_SOURCES` in `src/analysis/config-loader.ts`
18
+ (scoped with `languages` if it is language-specific). Only reach for a plugin
19
+ builtin when the pattern genuinely has no place in the canonical registry.
20
+
21
+ Never register the same `(class, method, cwe)` in both surfaces:
22
+ `findSinks` dedupes by `location:line:cwe` and keeps the higher-confidence
23
+ match, so the duplicate is invisible — you can "fix" the copy that loses and
24
+ see no change in behaviour. That failure mode cost an extra investigation round
25
+ on Issue #4 (`yaml.safe_load`). It is now locked by
26
+ `tests/languages/sink-registry-contract.test.ts`.
27
+
28
+ ## Drift
29
+
30
+ Because these files are not executable, they lag the runtime registries. To see
31
+ by how much:
32
+
33
+ ```bash
34
+ npm run config:drift
35
+ ```
36
+
37
+ The report lists documented sinks that no longer exist at runtime, and entries
38
+ whose vulnerability type disagrees with the code. It is informational and never
39
+ fails the build — treat a large delta as a documentation debt signal, not a bug.
@@ -1 +1 @@
1
- {"version":3,"file":"config-loader.d.ts","sourceRoot":"","sources":["../../src/analysis/config-loader.ts"],"names":[],"mappings":"AAAA;;;;GAIG;AAEH,OAAO,KAAK,EACV,YAAY,EACZ,UAAU,EACV,mBAAmB,EACnB,kBAAkB,EAClB,WAAW,EACX,aAAa,EACb,WAAW,EACX,gBAAgB,EAChB,UAAU,EACX,MAAM,oBAAoB,CAAC;AAE5B;;;GAGG;AACH,wBAAgB,WAAW,CAAC,CAAC,EAAE,OAAO,EAAE,MAAM,GAAG,CAAC,CAEjD;AAED;;GAEG;AACH,wBAAgB,iBAAiB,CAAC,OAAO,EAAE,YAAY,EAAE,GAAG,aAAa,EAAE,CAiB1E;AAED;;GAEG;AACH,wBAAgB,eAAe,CAAC,OAAO,EAAE,UAAU,EAAE,GAAG;IACtD,KAAK,EAAE,WAAW,EAAE,CAAC;IACrB,UAAU,EAAE,gBAAgB,EAAE,CAAC;CAChC,CAcA;AAED;;;;;GAKG;AACH,wBAAgB,wBAAwB,CACtC,OAAO,EAAE,mBAAmB,EAAE,GAC7B,kBAAkB,EAAE,CAQtB;AAED;;;;;;GAMG;AACH,wBAAgB,iBAAiB,CAC/B,cAAc,EAAE,MAAM,EAAE,EACxB,YAAY,EAAE,MAAM,EAAE,EACtB,qBAAqB,GAAE,MAAM,EAAO,GACnC,WAAW,CAYb;AAED;;;GAGG;AACH,eAAO,MAAM,eAAe,EAAE,aAAa,EA6uB1C,CAAC;AA+KF,eAAO,MAAM,aAAa,EAAE,WAAW,EA2wDtC,CAAC;AAEF,eAAO,MAAM,kBAAkB,EAAE,gBAAgB,EA+hBhD,CAAC;AAEF;;;;;;;;;GASG;AACH,eAAO,MAAM,sBAAsB,EAAE,kBAAkB,EAiDtD,CAAC;AAEF;;GAEG;AACH,wBAAgB,gBAAgB,IAAI,WAAW,CAO9C;AAMD;;;;;;;;GAQG;AACH,eAAO,MAAM,oBAAoB,EAAE,UAAU,EA8F5C,CAAC"}
1
+ {"version":3,"file":"config-loader.d.ts","sourceRoot":"","sources":["../../src/analysis/config-loader.ts"],"names":[],"mappings":"AAAA;;;;GAIG;AAEH,OAAO,KAAK,EACV,YAAY,EACZ,UAAU,EACV,mBAAmB,EACnB,kBAAkB,EAClB,WAAW,EACX,aAAa,EACb,WAAW,EACX,gBAAgB,EAChB,UAAU,EACX,MAAM,oBAAoB,CAAC;AAE5B;;;GAGG;AACH,wBAAgB,WAAW,CAAC,CAAC,EAAE,OAAO,EAAE,MAAM,GAAG,CAAC,CAEjD;AAED;;GAEG;AACH,wBAAgB,iBAAiB,CAAC,OAAO,EAAE,YAAY,EAAE,GAAG,aAAa,EAAE,CAiB1E;AAED;;GAEG;AACH,wBAAgB,eAAe,CAAC,OAAO,EAAE,UAAU,EAAE,GAAG;IACtD,KAAK,EAAE,WAAW,EAAE,CAAC;IACrB,UAAU,EAAE,gBAAgB,EAAE,CAAC;CAChC,CAcA;AAED;;;;;GAKG;AACH,wBAAgB,wBAAwB,CACtC,OAAO,EAAE,mBAAmB,EAAE,GAC7B,kBAAkB,EAAE,CAQtB;AAED;;;;;;GAMG;AACH,wBAAgB,iBAAiB,CAC/B,cAAc,EAAE,MAAM,EAAE,EACxB,YAAY,EAAE,MAAM,EAAE,EACtB,qBAAqB,GAAE,MAAM,EAAO,GACnC,WAAW,CAYb;AAED;;;GAGG;AACH,eAAO,MAAM,eAAe,EAAE,aAAa,EAivB1C,CAAC;AA+KF,eAAO,MAAM,aAAa,EAAE,WAAW,EA+yDtC,CAAC;AAEF,eAAO,MAAM,kBAAkB,EAAE,gBAAgB,EA+hBhD,CAAC;AAEF;;;;;;;;;GASG;AACH,eAAO,MAAM,sBAAsB,EAAE,kBAAkB,EAiDtD,CAAC;AAEF;;GAEG;AACH,wBAAgB,gBAAgB,IAAI,WAAW,CAO9C;AAMD;;;;;;;;GAQG;AACH,eAAO,MAAM,oBAAoB,EAAE,UAAU,EA8F5C,CAAC"}
@@ -500,7 +500,11 @@ export const DEFAULT_SOURCES = [
500
500
  { method: 'Json', type: 'http_body', severity: 'high', return_tainted: true, languages: ['rust'] },
501
501
  { method: 'Query', type: 'http_param', severity: 'high', return_tainted: true, languages: ['rust'] },
502
502
  { method: 'Path', type: 'http_path', severity: 'high', return_tainted: true, languages: ['rust'] },
503
- { method: 'Form', type: 'http_param', severity: 'high', return_tainted: true, languages: ['rust'] },
503
+ // `Form<T>` decodes the urlencoded request *body* (`Query<T>` above is the
504
+ // query-string extractor), so it is http_body — retyped from http_param
505
+ // when the RustPlugin's http_body duplicate was removed, so the two
506
+ // surfaces no longer disagree. Matches Gin's `Context.PostForm`.
507
+ { method: 'Form', type: 'http_body', severity: 'high', return_tainted: true, languages: ['rust'] },
504
508
  // Rust std library
505
509
  { method: 'var', class: 'env', type: 'env_input', severity: 'medium', return_tainted: true },
506
510
  { method: 'var_os', class: 'env', type: 'env_input', severity: 'medium', return_tainted: true },
@@ -1823,7 +1827,11 @@ export const DEFAULT_SINKS = [
1823
1827
  // Also match without receiver (destructured imports: const { exec } = require('child_process'))
1824
1828
  // `exec` is intentionally classless: catches Node.js child_process.exec AND
1825
1829
  // Java Runtime.exec (via `r.exec()` where heuristic can't resolve r → Runtime).
1826
- { method: 'exec', type: 'command_injection', cwe: 'CWE-78', severity: 'high', arg_positions: [0] },
1830
+ // Excluded from Python: there, bare `exec(code)` is the code-execution
1831
+ // builtin, registered below as code_injection / CWE-94. Before the exclusion
1832
+ // a Python `exec(user_code)` matched both patterns and emitted two findings
1833
+ // for one call — CWE-78 (wrong: no shell is involved) and CWE-94 (right).
1834
+ { method: 'exec', type: 'command_injection', cwe: 'CWE-78', severity: 'high', arg_positions: [0], exclude_languages: ['python'] },
1827
1835
  // `execSync`/`spawn`/`spawnSync`/`execFile` are Node-specific — language-scope them.
1828
1836
  { method: 'execSync', type: 'command_injection', cwe: 'CWE-78', severity: 'high', arg_positions: [0], languages: ['javascript', 'typescript'] },
1829
1837
  { method: 'spawn', type: 'command_injection', cwe: 'CWE-78', severity: 'high', arg_positions: [0], languages: ['javascript', 'typescript'] },
@@ -1851,7 +1859,11 @@ export const DEFAULT_SINKS = [
1851
1859
  // Note: classless { method: 'query' } removed — too many FPs (UriComponentsBuilder.query(), etc.)
1852
1860
  // SQL query calls are covered by class-specific patterns above (Connection, Pool, Client, JdbcTemplate)
1853
1861
  // Note: `raw` is shared with Python (Django ORM) — scoped to JS+TS to avoid leaking.
1854
- { method: 'raw', type: 'sql_injection', cwe: 'CWE-89', severity: 'high', arg_positions: [0], languages: ['javascript', 'typescript'] },
1862
+ // Severity `critical` (not `high`) preserves what the JS plugin's duplicate
1863
+ // builtin emitted before the #4 follow-up consolidation removed it: the
1864
+ // plugin copy carried `critical`, which won the `findSinks` dedup slot via
1865
+ // the +0.1 critical-severity confidence boost.
1866
+ { method: 'raw', type: 'sql_injection', cwe: 'CWE-89', severity: 'critical', arg_positions: [0], languages: ['javascript', 'typescript'] },
1855
1867
  // sqlite3 (npm) — Database/Statement methods. The JS plugin resolves
1856
1868
  // `const db = new sqlite3.Database(...); db.all(sql)` to the resolution
1857
1869
  // target `Connection.all`, so class-scoped patterns matching `Connection`
@@ -2410,10 +2422,30 @@ export const DEFAULT_SINKS = [
2410
2422
  { method: 'Sprintf', class: 'fmt', type: 'format_string', cwe: 'CWE-134', severity: 'medium', arg_positions: [0], languages: ['go'] },
2411
2423
  { method: 'Printf', class: 'fmt', type: 'format_string', cwe: 'CWE-134', severity: 'medium', arg_positions: [0], languages: ['go'] },
2412
2424
  { method: 'Errorf', class: 'fmt', type: 'format_string', cwe: 'CWE-134', severity: 'medium', arg_positions: [0], languages: ['go'] },
2425
+ // `fmt.Fprintf` is deliberately dual-classified: this CWE-134 entry and the
2426
+ // Go plugin's xss (CWE-79) entry both cover arg[1], so a tainted format
2427
+ // string emits two findings. Unlike the logger case above, neither is
2428
+ // redundant — CWE-134 holds for any writer, while CWE-79 is the actionable
2429
+ // finding when the writer is an http.ResponseWriter (`fmt.Fprintf(w, taint)`
2430
+ // is the standard Go reflected-XSS shape). Collapsing it requires deciding
2431
+ // by the *type of arg[0]*, which sink patterns cannot express today: the
2432
+ // options are a Go-specific gate (mirroring isSafeGoJsonUnmarshalCall) that
2433
+ // emits xss only for a resolved ResponseWriter, or dropping one CWE and
2434
+ // accepting either lost XSS recall or a mis-classified stderr write.
2435
+ // Left as-is pending the Go corpus run — see tasks.md.
2413
2436
  { method: 'Fprintf', class: 'fmt', type: 'format_string', cwe: 'CWE-134', severity: 'medium', arg_positions: [1], languages: ['go'] },
2414
2437
  // cognium-dev #264 — Go stdlib `log` package format-string entry points.
2415
2438
  // log.Printf / Fatalf / Panicf take the format string at arg[0]; tainted
2416
2439
  // format string reaches the same fmt.Sprintf machinery internally.
2440
+ //
2441
+ // These deliberately coexist with the Go plugin's log_injection (CWE-117)
2442
+ // entries for the same methods, so such a call emits both. The Logger
2443
+ // decision note in the Java log_injection section above does NOT apply
2444
+ // here: it reasons about SLF4J-style APIs where the format string is a
2445
+ // literal with `{}` placeholders, whereas Go's `log.Printf(fmt, ...)` takes
2446
+ // a real format string that an attacker can control. CWE-117 covers the
2447
+ // forged log line, CWE-134 the format string itself. Locked by
2448
+ // tests/analysis/passes/format-string-additions.test.ts.
2417
2449
  { method: 'Printf', class: 'log', type: 'format_string', cwe: 'CWE-134', severity: 'medium', arg_positions: [0], languages: ['go'] },
2418
2450
  { method: 'Fatalf', class: 'log', type: 'format_string', cwe: 'CWE-134', severity: 'medium', arg_positions: [0], languages: ['go'] },
2419
2451
  { method: 'Panicf', class: 'log', type: 'format_string', cwe: 'CWE-134', severity: 'medium', arg_positions: [0], languages: ['go'] },
@@ -2427,6 +2459,14 @@ export const DEFAULT_SINKS = [
2427
2459
  { method: 'cookie', type: 'crlf', cwe: 'CWE-113', severity: 'medium', arg_positions: [1], languages: ['javascript', 'typescript'] },
2428
2460
  // Express: res.location(url) and res.redirect(url) — Location header.
2429
2461
  { method: 'location', type: 'crlf', cwe: 'CWE-113', severity: 'medium', arg_positions: [0], languages: ['javascript', 'typescript'] },
2462
+ // `redirect` is deliberately dual-classified: this crlf (CWE-113) entry
2463
+ // coexists with the open_redirect (CWE-601) entry for js/ts, so a tainted
2464
+ // redirect target emits two flows. That is intentional, not duplication —
2465
+ // #189 Sprint 82 shipped a sink-type-aware flow dedup specifically so both
2466
+ // survive ("res.redirect is both open_redirect AND crlf"), and #132 added a
2467
+ // crlf recall test for the bare `res.redirect(req.query.url)` shape. Locked
2468
+ // by issue-189-sprint82-open-redirect-cluster.test.ts and
2469
+ // crlf-stage8-fp.test.ts — do not collapse without revisiting both tickets.
2430
2470
  { method: 'redirect', type: 'crlf', cwe: 'CWE-113', severity: 'medium', arg_positions: [0], languages: ['javascript', 'typescript'] },
2431
2471
  // Go net/http: w.Header().Set(k, v) / Add(k, v) — first arg is the value
2432
2472
  // (Header is a map; the actual `value` is arg 1 of the call). We flag the