circle-ir 3.151.0 → 3.153.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.
@@ -0,0 +1,89 @@
1
+ /**
2
+ * LibraryProfileSinkGatePass — cognium-dev #232
3
+ *
4
+ * Sink-side companion to `LibraryProfileSourceGatePass` (#236,
5
+ * shipped 3.151.0). Under the `library/*` project profile, drops
6
+ * sinks whose entire vulnerability class is off-topic for library
7
+ * code — currently just `log_injection` (CWE-117).
8
+ *
9
+ * Motivation:
10
+ *
11
+ * `log_injection` requires a downstream log-viewer that interprets
12
+ * attacker-controlled log content — the exploit ("log forging",
13
+ * HTML/ANSI injection into a log renderer, log-based privilege
14
+ * escalation) is exercised at the application-integration boundary,
15
+ * not inside a library. A library that calls `Logger.info(x)` where
16
+ * `x` originated from an HTTP parameter has not committed a defect:
17
+ * the consuming application decides where those log records are
18
+ * rendered and how their content is escaped.
19
+ *
20
+ * Empirically, `log_injection` was ~10% of H+C findings on the
21
+ * Tier 2 8-repo library cohort (402 findings in the audit
22
+ * summarised in cognium-ai#189 §1). The signal is systemically
23
+ * noisy for library code and provides no actionable
24
+ * application-security value.
25
+ *
26
+ * Where #236 dropped speculative `interprocedural_param` sources
27
+ * (which removed the entire `external_taint_escape` Scenario-B
28
+ * synthesis path), this pass drops the sinks themselves. `log_injection`
29
+ * has real, non-speculative sources (`http_param`, `env_input`,
30
+ * `db_input`, …) that flow into concrete sink calls (`Logger.info`,
31
+ * `logging.info`, `console.log`, …); the source-side gate does not
32
+ * remove them. The *sink class* is what is off-topic here.
33
+ *
34
+ * Pipeline slot: runs after `CliMainReflectionSuppressPass` (so
35
+ * every sink-side categorisation / suppression pass fires first)
36
+ * and before `TaintPropagationPass` (so no dropped sink ever
37
+ * reaches the flow generators).
38
+ *
39
+ * Guardrails:
40
+ * - Pass is a no-op when `graph.ir.meta.projectProfile` is absent,
41
+ * `'unknown'`, or does not start with `library/`. Callers that do
42
+ * not opt in to profile detection get the unmodified sink list.
43
+ * - Only `log_injection` is eligible in 3.152.0. Every other
44
+ * `SinkType` (`sql_injection`, `command_injection`, `xss`,
45
+ * `path_traversal`, `deserialization`, …) is preserved
46
+ * unconditionally. Extending the drop set is a deliberate
47
+ * one-line change in `DROPPED_SINK_TYPES`.
48
+ * - Guarded on `disabledPasses.has('library-profile-sink-gate')`
49
+ * at the pipeline registration site.
50
+ *
51
+ * Scope note (Rust log macros): `LanguageSourcesPass` emits
52
+ * `rule_id: 'log_injection'` findings directly for Rust log macros
53
+ * (`info!`, `println!`, `eprintln!`, …). Those findings bypass the
54
+ * sink pipeline entirely and therefore bypass this pass. Deferred
55
+ * to a follow-up if the harness rerun shows material Rust
56
+ * residuals; the Tier 2 8-repo cohort is Java-heavy.
57
+ */
58
+ import type { AnalysisPass, PassContext } from '../../graph/analysis-pass.js';
59
+ import type { ProjectProfile, SinkType } from '../../types/index.js';
60
+ export interface LibraryProfileSinkGateResult {
61
+ /**
62
+ * Resolved `ProjectProfile` observed on `graph.ir.meta.projectProfile`
63
+ * at the time this pass ran. `undefined` when no profile was
64
+ * supplied by the caller.
65
+ */
66
+ profile: ProjectProfile | undefined;
67
+ /**
68
+ * Whether the profile matched the library-shape trigger and the
69
+ * gate was applied. `false` for every non-library shape and for
70
+ * `'unknown'` / absent profiles.
71
+ */
72
+ applied: boolean;
73
+ /**
74
+ * Number of sinks removed from the authoritative sink list. Zero
75
+ * when `applied === false`.
76
+ */
77
+ dropped: number;
78
+ /**
79
+ * Breakdown of drops by `SinkType`. Empty object when
80
+ * `applied === false`.
81
+ */
82
+ droppedByType: Partial<Record<SinkType, number>>;
83
+ }
84
+ export declare class LibraryProfileSinkGatePass implements AnalysisPass<LibraryProfileSinkGateResult> {
85
+ readonly name = "library-profile-sink-gate";
86
+ readonly category: "security";
87
+ run(ctx: PassContext): LibraryProfileSinkGateResult;
88
+ }
89
+ //# sourceMappingURL=library-profile-sink-gate-pass.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"library-profile-sink-gate-pass.d.ts","sourceRoot":"","sources":["../../../src/analysis/passes/library-profile-sink-gate-pass.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GAwDG;AAEH,OAAO,KAAK,EAAE,YAAY,EAAE,WAAW,EAAE,MAAM,8BAA8B,CAAC;AAC9E,OAAO,KAAK,EAAE,cAAc,EAAE,QAAQ,EAAa,MAAM,sBAAsB,CAAC;AAYhF,MAAM,WAAW,4BAA4B;IAC3C;;;;OAIG;IACH,OAAO,EAAE,cAAc,GAAG,SAAS,CAAC;IACpC;;;;OAIG;IACH,OAAO,EAAE,OAAO,CAAC;IACjB;;;OAGG;IACH,OAAO,EAAE,MAAM,CAAC;IAChB;;;OAGG;IACH,aAAa,EAAE,OAAO,CAAC,MAAM,CAAC,QAAQ,EAAE,MAAM,CAAC,CAAC,CAAC;CAClD;AAaD,qBAAa,0BACX,YAAW,YAAY,CAAC,4BAA4B,CAAC;IAErD,QAAQ,CAAC,IAAI,+BAA+B;IAC5C,QAAQ,CAAC,QAAQ,EAAG,UAAU,CAAU;IAExC,GAAG,CAAC,GAAG,EAAE,WAAW,GAAG,4BAA4B;CA0DpD"}
@@ -0,0 +1,132 @@
1
+ /**
2
+ * LibraryProfileSinkGatePass — cognium-dev #232
3
+ *
4
+ * Sink-side companion to `LibraryProfileSourceGatePass` (#236,
5
+ * shipped 3.151.0). Under the `library/*` project profile, drops
6
+ * sinks whose entire vulnerability class is off-topic for library
7
+ * code — currently just `log_injection` (CWE-117).
8
+ *
9
+ * Motivation:
10
+ *
11
+ * `log_injection` requires a downstream log-viewer that interprets
12
+ * attacker-controlled log content — the exploit ("log forging",
13
+ * HTML/ANSI injection into a log renderer, log-based privilege
14
+ * escalation) is exercised at the application-integration boundary,
15
+ * not inside a library. A library that calls `Logger.info(x)` where
16
+ * `x` originated from an HTTP parameter has not committed a defect:
17
+ * the consuming application decides where those log records are
18
+ * rendered and how their content is escaped.
19
+ *
20
+ * Empirically, `log_injection` was ~10% of H+C findings on the
21
+ * Tier 2 8-repo library cohort (402 findings in the audit
22
+ * summarised in cognium-ai#189 §1). The signal is systemically
23
+ * noisy for library code and provides no actionable
24
+ * application-security value.
25
+ *
26
+ * Where #236 dropped speculative `interprocedural_param` sources
27
+ * (which removed the entire `external_taint_escape` Scenario-B
28
+ * synthesis path), this pass drops the sinks themselves. `log_injection`
29
+ * has real, non-speculative sources (`http_param`, `env_input`,
30
+ * `db_input`, …) that flow into concrete sink calls (`Logger.info`,
31
+ * `logging.info`, `console.log`, …); the source-side gate does not
32
+ * remove them. The *sink class* is what is off-topic here.
33
+ *
34
+ * Pipeline slot: runs after `CliMainReflectionSuppressPass` (so
35
+ * every sink-side categorisation / suppression pass fires first)
36
+ * and before `TaintPropagationPass` (so no dropped sink ever
37
+ * reaches the flow generators).
38
+ *
39
+ * Guardrails:
40
+ * - Pass is a no-op when `graph.ir.meta.projectProfile` is absent,
41
+ * `'unknown'`, or does not start with `library/`. Callers that do
42
+ * not opt in to profile detection get the unmodified sink list.
43
+ * - Only `log_injection` is eligible in 3.152.0. Every other
44
+ * `SinkType` (`sql_injection`, `command_injection`, `xss`,
45
+ * `path_traversal`, `deserialization`, …) is preserved
46
+ * unconditionally. Extending the drop set is a deliberate
47
+ * one-line change in `DROPPED_SINK_TYPES`.
48
+ * - Guarded on `disabledPasses.has('library-profile-sink-gate')`
49
+ * at the pipeline registration site.
50
+ *
51
+ * Scope note (Rust log macros): `LanguageSourcesPass` emits
52
+ * `rule_id: 'log_injection'` findings directly for Rust log macros
53
+ * (`info!`, `println!`, `eprintln!`, …). Those findings bypass the
54
+ * sink pipeline entirely and therefore bypass this pass. Deferred
55
+ * to a follow-up if the harness rerun shows material Rust
56
+ * residuals; the Tier 2 8-repo cohort is Java-heavy.
57
+ */
58
+ /**
59
+ * Sink types eligible for the library-profile drop. Seeded with
60
+ * `log_injection` (CWE-117) in 3.152.0. Extending this set is a
61
+ * deliberate, reviewable change.
62
+ */
63
+ const DROPPED_SINK_TYPES = new Set([
64
+ 'log_injection',
65
+ ]);
66
+ /**
67
+ * Returns true when the resolved profile begins with `library/`
68
+ * (i.e. any `library/production`, `library/dev`, `library/sample`,
69
+ * `library/benchmark`, `library/test` environment binding).
70
+ * `'unknown'` and non-library shapes return false.
71
+ */
72
+ function isLibraryShape(profile) {
73
+ if (!profile || profile === 'unknown')
74
+ return false;
75
+ return profile.startsWith('library/');
76
+ }
77
+ export class LibraryProfileSinkGatePass {
78
+ name = 'library-profile-sink-gate';
79
+ category = 'security';
80
+ run(ctx) {
81
+ const { graph } = ctx;
82
+ const profile = graph.ir.meta.projectProfile;
83
+ if (!isLibraryShape(profile)) {
84
+ return {
85
+ profile,
86
+ applied: false,
87
+ dropped: 0,
88
+ droppedByType: {},
89
+ };
90
+ }
91
+ // Authoritative sink list mirrors the fetch pattern in
92
+ // `SinkSemanticsPass`: prefer `SinkFilterResult.sinks` (what
93
+ // `analyzer.ts` assembles the final `taint.sinks` from), fall
94
+ // back to `graph.ir.taint.sinks` for stand-alone unit tests
95
+ // that don't run `SinkFilterPass`.
96
+ const sinks = ctx.hasResult('sink-filter')
97
+ ? ctx.getResult('sink-filter').sinks
98
+ : graph.ir.taint.sinks;
99
+ if (sinks.length === 0) {
100
+ return {
101
+ profile,
102
+ applied: true,
103
+ dropped: 0,
104
+ droppedByType: {},
105
+ };
106
+ }
107
+ const droppedByType = {};
108
+ const kept = [];
109
+ for (const sink of sinks) {
110
+ if (DROPPED_SINK_TYPES.has(sink.type)) {
111
+ droppedByType[sink.type] = (droppedByType[sink.type] ?? 0) + 1;
112
+ continue;
113
+ }
114
+ kept.push(sink);
115
+ }
116
+ const dropped = sinks.length - kept.length;
117
+ // Mutate the array in place so downstream passes see the
118
+ // filtered list. Preserves array identity for any consumer that
119
+ // captured a reference before this pass ran.
120
+ if (dropped > 0) {
121
+ sinks.length = 0;
122
+ sinks.push(...kept);
123
+ }
124
+ return {
125
+ profile,
126
+ applied: true,
127
+ dropped,
128
+ droppedByType,
129
+ };
130
+ }
131
+ }
132
+ //# sourceMappingURL=library-profile-sink-gate-pass.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"library-profile-sink-gate-pass.js","sourceRoot":"","sources":["../../../src/analysis/passes/library-profile-sink-gate-pass.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GAwDG;AAMH;;;;GAIG;AACH,MAAM,kBAAkB,GAA0B,IAAI,GAAG,CAAW;IAClE,eAAe;CAChB,CAAC,CAAC;AA2BH;;;;;GAKG;AACH,SAAS,cAAc,CAAC,OAAmC;IACzD,IAAI,CAAC,OAAO,IAAI,OAAO,KAAK,SAAS;QAAE,OAAO,KAAK,CAAC;IACpD,OAAO,OAAO,CAAC,UAAU,CAAC,UAAU,CAAC,CAAC;AACxC,CAAC;AAED,MAAM,OAAO,0BAA0B;IAG5B,IAAI,GAAG,2BAA2B,CAAC;IACnC,QAAQ,GAAG,UAAmB,CAAC;IAExC,GAAG,CAAC,GAAgB;QAClB,MAAM,EAAE,KAAK,EAAE,GAAG,GAAG,CAAC;QACtB,MAAM,OAAO,GAAG,KAAK,CAAC,EAAE,CAAC,IAAI,CAAC,cAAc,CAAC;QAE7C,IAAI,CAAC,cAAc,CAAC,OAAO,CAAC,EAAE,CAAC;YAC7B,OAAO;gBACL,OAAO;gBACP,OAAO,EAAE,KAAK;gBACd,OAAO,EAAE,CAAC;gBACV,aAAa,EAAE,EAAE;aAClB,CAAC;QACJ,CAAC;QAED,uDAAuD;QACvD,6DAA6D;QAC7D,8DAA8D;QAC9D,4DAA4D;QAC5D,mCAAmC;QACnC,MAAM,KAAK,GAAgB,GAAG,CAAC,SAAS,CAAC,aAAa,CAAC;YACrD,CAAC,CAAC,GAAG,CAAC,SAAS,CAAmB,aAAa,CAAC,CAAC,KAAK;YACtD,CAAC,CAAC,KAAK,CAAC,EAAE,CAAC,KAAK,CAAC,KAAK,CAAC;QAEzB,IAAI,KAAK,CAAC,MAAM,KAAK,CAAC,EAAE,CAAC;YACvB,OAAO;gBACL,OAAO;gBACP,OAAO,EAAE,IAAI;gBACb,OAAO,EAAE,CAAC;gBACV,aAAa,EAAE,EAAE;aAClB,CAAC;QACJ,CAAC;QAED,MAAM,aAAa,GAAsC,EAAE,CAAC;QAC5D,MAAM,IAAI,GAAgB,EAAE,CAAC;QAC7B,KAAK,MAAM,IAAI,IAAI,KAAK,EAAE,CAAC;YACzB,IAAI,kBAAkB,CAAC,GAAG,CAAC,IAAI,CAAC,IAAI,CAAC,EAAE,CAAC;gBACtC,aAAa,CAAC,IAAI,CAAC,IAAI,CAAC,GAAG,CAAC,aAAa,CAAC,IAAI,CAAC,IAAI,CAAC,IAAI,CAAC,CAAC,GAAG,CAAC,CAAC;gBAC/D,SAAS;YACX,CAAC;YACD,IAAI,CAAC,IAAI,CAAC,IAAI,CAAC,CAAC;QAClB,CAAC;QAED,MAAM,OAAO,GAAG,KAAK,CAAC,MAAM,GAAG,IAAI,CAAC,MAAM,CAAC;QAE3C,yDAAyD;QACzD,gEAAgE;QAChE,6CAA6C;QAC7C,IAAI,OAAO,GAAG,CAAC,EAAE,CAAC;YAChB,KAAK,CAAC,MAAM,GAAG,CAAC,CAAC;YACjB,KAAK,CAAC,IAAI,CAAC,GAAG,IAAI,CAAC,CAAC;QACtB,CAAC;QAED,OAAO;YACL,OAAO;YACP,OAAO,EAAE,IAAI;YACb,OAAO;YACP,aAAa;SACd,CAAC;IACJ,CAAC;CACF"}
@@ -0,0 +1,91 @@
1
+ /**
2
+ * Require-entry-path anchor (cognium-dev#234, ships 3.153.0).
3
+ *
4
+ * Post-pipeline, project-level helper that:
5
+ * 1. Annotates high+critical (H+C) taint findings with an
6
+ * `entryPath[]` chain of methods traversed from a classified
7
+ * Tier-1 entry point (Spring MVC handler, JAX-RS resource,
8
+ * Servlet lifecycle method, Netty channel handler, `main(String[])`,
9
+ * …) down to the finding's sink method.
10
+ * 2. Drops H+C findings under `application` / `server` / `cli` /
11
+ * `plugin` / `unknown` project profiles when the reverse-BFS
12
+ * conclusively returns no such chain — i.e. the finding lives on a
13
+ * method that no HTTP / RPC / lifecycle entry point in the scan can
14
+ * reach.
15
+ *
16
+ * # Why
17
+ *
18
+ * cognium-ai#189 §1 (2026-07 Tier-2 Java cohort — hutool, Sentinel,
19
+ * plantuml, mockserver) surfaced 1942 H+C findings on hutool with
20
+ * *zero* classified HTTP/RPC entry point and no reachable path from
21
+ * `main`. #236 (source-side profile gate, 3.151.0) and #232
22
+ * (sink-side profile gate, 3.152.0) each attacked the problem via
23
+ * profile-conditional per-file drops, but both leave the residual
24
+ * signal on files whose profile is `application/*` or `unknown` even
25
+ * though the enclosing method is manifestly unreachable from any
26
+ * classified boundary.
27
+ *
28
+ * This helper closes that hole: no entry point → no path from an
29
+ * entry point → no H+C finding. Every remaining H+C finding under
30
+ * `application/*` carries a demonstrable call chain from a real
31
+ * boundary as evidence, materialised on `entryPath[]` for consumers
32
+ * (CLI, SARIF, cognium-ai) to display.
33
+ *
34
+ * # Scope
35
+ *
36
+ * - Java only (relies on `classifyEntryPointTier`, which is Java-primary).
37
+ * Non-Java files: pass-through, no annotation, no drop.
38
+ * - Project-level only. Per-file `analyze()` never runs this helper —
39
+ * the reachability question is meaningful only across a full scan.
40
+ * - Only H+C findings from taint passes are candidates for drop. Metric
41
+ * findings, `medium` / `low` taint findings, and findings without a
42
+ * resolved containing method are always preserved.
43
+ *
44
+ * # Interaction with #236 / #232
45
+ *
46
+ * Both #236 and #232 fire when `projectProfile` starts with `library/`:
47
+ * the source / sink is dropped BEFORE the flow is materialised. This
48
+ * helper therefore no-ops under `library/*` (an already-dropped flow
49
+ * never reaches us) but still ANNOTATES findings with `entryPath[]`
50
+ * whenever a chain is available, so downstream consumers can see the
51
+ * anchor regardless of the drop decision.
52
+ *
53
+ * # Reference
54
+ *
55
+ * - cognium-dev#234 — this ticket.
56
+ * - cognium-dev#128 — entry-point tier classifier.
57
+ * - cognium-dev#236 (3.151.0) — source-side library-profile gate.
58
+ * - cognium-dev#232 (3.152.0) — sink-side library-profile gate.
59
+ * - cognium-ai#189 — Tier-2 Java cohort audit.
60
+ * - `docs/ARCHITECTURE.md` ADR-010.
61
+ */
62
+ import type { CircleIR, ProjectProfile } from '../types/index.js';
63
+ /** Rule identifier used for `disabledPasses` lookups. */
64
+ export declare const RULE_ID_REQUIRE_ENTRY_PATH = "require-entry-path";
65
+ export interface ApplyRequireEntryPathOptions {
66
+ /**
67
+ * Caller-resolved profile for each file. Same semantics as
68
+ * `AnalyzerOptions.projectProfile`. When absent, the helper treats
69
+ * every file as `'unknown'` — i.e. drops still apply.
70
+ */
71
+ projectProfile?: ProjectProfile | Map<string, ProjectProfile>;
72
+ /**
73
+ * Set of disabled pass ids (from `AnalyzerOptions.disabledPasses`).
74
+ * If it contains `'require-entry-path'`, the helper is a full no-op
75
+ * (no annotation, no drop).
76
+ */
77
+ disabledPasses?: ReadonlySet<string> | ReadonlyArray<string>;
78
+ }
79
+ /**
80
+ * Apply the entry-path gate + annotation to every file's findings in
81
+ * `fileAnalyses`, mutating each `analysis.findings` array in place.
82
+ *
83
+ * The mutation is idempotent — running twice produces the same result
84
+ * (BFS is deterministic; annotation always overwrites prior fields
85
+ * with the same values).
86
+ */
87
+ export declare function applyRequireEntryPath(fileAnalyses: ReadonlyArray<{
88
+ file: string;
89
+ analysis: CircleIR;
90
+ }>, options?: ApplyRequireEntryPathOptions): void;
91
+ //# sourceMappingURL=require-entry-path.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"require-entry-path.d.ts","sourceRoot":"","sources":["../../src/analysis/require-entry-path.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GA4DG;AAEH,OAAO,KAAK,EACV,QAAQ,EAER,cAAc,EAIf,MAAM,mBAAmB,CAAC;AAO3B,yDAAyD;AACzD,eAAO,MAAM,0BAA0B,uBAAuB,CAAC;AAe/D,MAAM,WAAW,4BAA4B;IAC3C;;;;OAIG;IACH,cAAc,CAAC,EAAE,cAAc,GAAG,GAAG,CAAC,MAAM,EAAE,cAAc,CAAC,CAAC;IAC9D;;;;OAIG;IACH,cAAc,CAAC,EAAE,WAAW,CAAC,MAAM,CAAC,GAAG,aAAa,CAAC,MAAM,CAAC,CAAC;CAC9D;AAED;;;;;;;GAOG;AACH,wBAAgB,qBAAqB,CACnC,YAAY,EAAE,aAAa,CAAC;IAAE,IAAI,EAAE,MAAM,CAAC;IAAC,QAAQ,EAAE,QAAQ,CAAA;CAAE,CAAC,EACjE,OAAO,GAAE,4BAAiC,GACzC,IAAI,CA6CN"}