circle-ir-ai 4.28.0 → 4.30.0-rc.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/CHANGELOG.md +101 -0
- package/dist/agents/mastra/workflow.d.ts +10 -2
- package/dist/agents/mastra/workflow.d.ts.map +1 -1
- package/dist/agents/mastra/workflow.js +92 -39
- package/dist/agents/mastra/workflow.js.map +1 -1
- package/dist/cache/discovery-cache.d.ts +17 -0
- package/dist/cache/discovery-cache.d.ts.map +1 -1
- package/dist/cache/discovery-cache.js +42 -7
- package/dist/cache/discovery-cache.js.map +1 -1
- package/dist/cache/tree-cache.d.ts +6 -0
- package/dist/cache/tree-cache.d.ts.map +1 -1
- package/dist/cache/tree-cache.js.map +1 -1
- package/dist/cache/verify-cache.js +1 -1
- package/dist/index.d.ts +1 -1
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +1 -1
- package/dist/index.js.map +1 -1
- package/dist/llm/ax-client.d.ts +19 -1
- package/dist/llm/ax-client.d.ts.map +1 -1
- package/dist/llm/ax-client.js +130 -32
- package/dist/llm/ax-client.js.map +1 -1
- package/dist/llm/call-logger.d.ts +7 -1
- package/dist/llm/call-logger.d.ts.map +1 -1
- package/dist/llm/call-logger.js.map +1 -1
- package/dist/llm/config.d.ts +2 -0
- package/dist/llm/config.d.ts.map +1 -1
- package/dist/llm/config.js +16 -8
- package/dist/llm/config.js.map +1 -1
- package/dist/llm/discovery.d.ts.map +1 -1
- package/dist/llm/discovery.js +2 -1
- package/dist/llm/discovery.js.map +1 -1
- package/dist/llm/llm-error-counter.d.ts.map +1 -1
- package/dist/llm/llm-error-counter.js +1 -0
- package/dist/llm/llm-error-counter.js.map +1 -1
- package/dist/llm/pre-filter.d.ts +9 -12
- package/dist/llm/pre-filter.d.ts.map +1 -1
- package/dist/llm/pre-filter.js +23 -20
- package/dist/llm/pre-filter.js.map +1 -1
- package/dist/llm/prompt-template.d.ts +10 -0
- package/dist/llm/prompt-template.d.ts.map +1 -0
- package/dist/llm/prompt-template.js +12 -0
- package/dist/llm/prompt-template.js.map +1 -0
- package/dist/llm/prompts/index.d.ts +2 -1
- package/dist/llm/prompts/index.d.ts.map +1 -1
- package/dist/llm/prompts/index.js +23 -3
- package/dist/llm/prompts/index.js.map +1 -1
- package/dist/llm/prompts/languages.d.ts +2 -0
- package/dist/llm/prompts/languages.d.ts.map +1 -1
- package/dist/llm/prompts/languages.js +26 -0
- package/dist/llm/prompts/languages.js.map +1 -1
- package/dist/llm/prompts/rust.d.ts +1 -0
- package/dist/llm/prompts/rust.d.ts.map +1 -1
- package/dist/llm/prompts/rust.js +37 -0
- package/dist/llm/prompts/rust.js.map +1 -1
- package/dist/llm/schemas.d.ts +82 -0
- package/dist/llm/schemas.d.ts.map +1 -1
- package/dist/llm/schemas.js +8 -0
- package/dist/llm/schemas.js.map +1 -1
- package/dist/llm/token-meter.d.ts +9 -1
- package/dist/llm/token-meter.d.ts.map +1 -1
- package/dist/llm/token-meter.js +14 -2
- package/dist/llm/token-meter.js.map +1 -1
- package/dist/llm/verification.d.ts +4 -1
- package/dist/llm/verification.d.ts.map +1 -1
- package/dist/llm/verification.js +110 -51
- package/dist/llm/verification.js.map +1 -1
- package/dist/secret-scan/history-patterns.d.ts.map +1 -1
- package/dist/secret-scan/history-patterns.js +47 -0
- package/dist/secret-scan/history-patterns.js.map +1 -1
- package/dist/secret-scan/scanner.d.ts.map +1 -1
- package/dist/secret-scan/scanner.js +11 -0
- package/dist/secret-scan/scanner.js.map +1 -1
- package/dist/security-scan/index.d.ts +1 -0
- package/dist/security-scan/index.d.ts.map +1 -1
- package/dist/security-scan/index.js +1 -0
- package/dist/security-scan/index.js.map +1 -1
- package/dist/security-scan/scanner.d.ts +23 -0
- package/dist/security-scan/scanner.d.ts.map +1 -1
- package/dist/security-scan/scanner.js +72 -12
- package/dist/security-scan/scanner.js.map +1 -1
- package/dist/security-scan/sink-filters.d.ts +2 -0
- package/dist/security-scan/sink-filters.d.ts.map +1 -1
- package/dist/security-scan/sink-filters.js +44 -0
- package/dist/security-scan/sink-filters.js.map +1 -1
- package/dist/security-scan/test-tree-excludes.d.ts +10 -0
- package/dist/security-scan/test-tree-excludes.d.ts.map +1 -0
- package/dist/security-scan/test-tree-excludes.js +38 -0
- package/dist/security-scan/test-tree-excludes.js.map +1 -0
- package/dist/skills/code-threat-patterns.d.ts +65 -0
- package/dist/skills/code-threat-patterns.d.ts.map +1 -1
- package/dist/skills/code-threat-patterns.js +729 -41
- package/dist/skills/code-threat-patterns.js.map +1 -1
- package/dist/utils/analyze-pool.d.ts.map +1 -1
- package/dist/utils/analyze-pool.js +6 -1
- package/dist/utils/analyze-pool.js.map +1 -1
- package/package.json +6 -6
- package/dist/coverage/index.d.ts +0 -50
- package/dist/coverage/index.d.ts.map +0 -1
- package/dist/coverage/index.js +0 -73
- package/dist/coverage/index.js.map +0 -1
package/CHANGELOG.md
CHANGED
|
@@ -7,6 +7,53 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
|
|
|
7
7
|
|
|
8
8
|
## [Unreleased]
|
|
9
9
|
|
|
10
|
+
## [4.30.0-rc.0] - 2026-10-29
|
|
11
|
+
|
|
12
|
+
Release candidate, published under the npm `next` tag for the cost gates before a final 4.30.0. It contains everything below, merged since 4.29.0 (4.29.1 and 4.29.2 were bumped in the repo but never published).
|
|
13
|
+
|
|
14
|
+
### Changed
|
|
15
|
+
- **The LLM-path dependencies are pinned to exact versions (cognium-ai#810):** `@ax-llm/ax` 25.4.5, `@mastra/core` 1.79.0, `zod` 4.6.7, the versions in the lockfile CI tests against. A range let the same release install a different LLM client depending on the install date.
|
|
16
|
+
|
|
17
|
+
### Fixed
|
|
18
|
+
- **Nested test-tree excludes (cognium-ai#801)** — restore `**/tests/**` and exclude
|
|
19
|
+
nested `.../test/...` outside `src/main/` while keeping #615 Java `test` packages
|
|
20
|
+
scannable. Shared `shouldExcludeTestTreePath` for `SecurityScanner` and cognium-ai
|
|
21
|
+
`init` / `collectFiles`.
|
|
22
|
+
|
|
23
|
+
### Changed
|
|
24
|
+
- **Combined enrichment discovery (cognium-ai#780 S3)** — `runEnrich` issues one
|
|
25
|
+
`discoverSourcesAndSinksForLanguage` LLM call per file (was two parallel calls).
|
|
26
|
+
Prompts use a cache-friendly layout: static instructions and JSON schema before
|
|
27
|
+
`[CODE START]`, per-file context after. Language-discoverer cache sentinel
|
|
28
|
+
`lang-disc-v4`.
|
|
29
|
+
|
|
30
|
+
### Fixed
|
|
31
|
+
- **Truncation retry (cognium-ai#776 S2)** — `chatJSON` retries once with a larger output cap
|
|
32
|
+
when `finish_reason=length` leaves empty `content` (up to `REASONING_MAX_TOKENS`), reads
|
|
33
|
+
`reasoning_content` when present, and no longer counts truncation toward the project-dead
|
|
34
|
+
breaker.
|
|
35
|
+
- **DeepSeek V4 reasoning budget (cognium-ai#776)** — `isThinkingModel()` now recognizes
|
|
36
|
+
`deepseek-v4*` ids (e.g. Fireworks `deepseek-v4p1-flash`) so they get the 16k completion
|
|
37
|
+
budget and 360s phase timeout instead of the 8k default.
|
|
38
|
+
- **Concurrent `chatJSON` telemetry (cognium-ai#778)** — batch verification and parallel
|
|
39
|
+
enrichment discovery each use a dedicated `AxLLMClient` (`fork()` / fresh instance) so
|
|
40
|
+
per-call `lastUsage` and `lastErrorCategory` are not clobbered across concurrent requests.
|
|
41
|
+
- **Verifier omission on thinking models (cognium-ai#777)** — when the verification-phase model
|
|
42
|
+
is a thinking/reasoning model (`isThinkingModel`), a successful batch answer with an empty
|
|
43
|
+
`pairs` array no longer labels static findings `refuted_by: 'omission'`; they stay
|
|
44
|
+
`UNVERIFIED` (`mode: attempted`) so default `{"pairs":[]}` responses do not mass-refute.
|
|
45
|
+
- **Thinking verifier explicit verdicts (cognium-ai#777 S3)** — batch verification on
|
|
46
|
+
thinking/reasoning models (`isThinkingModel`) uses a prompt contract that requires an
|
|
47
|
+
explicit TRUE_POSITIVE / FALSE_POSITIVE / UNCERTAIN entry for every submitted pair so
|
|
48
|
+
omission is not mistaken for refutation; verify-cache `PROMPT_VER` bumped to `verify-v5`.
|
|
49
|
+
- **High verifier omission telemetry (cognium-ai#777 S2)** — `ScanMeta.verification` now
|
|
50
|
+
includes `verifierModel` and sets `omissionRateElevated` (with a stderr `VERIFY_OMISSION`
|
|
51
|
+
warning) when omissionRate ≥ 99% on ≥ 20 submitted pairs.
|
|
52
|
+
- **Prompt template substitution (cognium-ai#779)** — `fillPromptTemplate()` substitutes
|
|
53
|
+
`{name}` placeholders in one pass without interpreting `$&`, `$'`, `` $` `` or `$$` in scanned
|
|
54
|
+
code. Batch/single verification, discovery `getPrompt()`, and ax-client enrichment prompts
|
|
55
|
+
now use it instead of `String.replace` with string replacements.
|
|
56
|
+
|
|
10
57
|
### Added
|
|
11
58
|
- **The ten MCP tools declare annotations** (4.18.0), each measured before it was written down.
|
|
12
59
|
All ten are `readOnlyHint: true`, `destructiveHint: false`, `idempotentHint: true`: none writes,
|
|
@@ -116,6 +163,60 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
|
|
|
116
163
|
Unchanged on 4.9.29: OWASP 1415/1415, Juliet 243/243, SecuriBench 97.2%/6.7%, Go 89.5%,
|
|
117
164
|
NodeJS 92.9%, CWE-Bench-Rust 100%, bash and HTML 100%.
|
|
118
165
|
|
|
166
|
+
## [4.29.0] - 2026-10-17
|
|
167
|
+
|
|
168
|
+
Minor release: `circle-ir` 4.15.0 → 4.16.1, LLM enrichment sinks anchored on the `scan --llm`
|
|
169
|
+
merge path, and corrected SecuriBench and C# oracle scoring. 4.16.0 was not pinned: it carried
|
|
170
|
+
four regressions, fixed upstream in 4.16.1 before this bump.
|
|
171
|
+
|
|
172
|
+
### Changed
|
|
173
|
+
- **`circle-ir` 4.15.0 → 4.16.1** (exact pin). 4.16.0 adds the navigation API (callers per
|
|
174
|
+
overload and through dispatch), `navigationTypes` extraction, `AnalyzerOptions.resources`, and
|
|
175
|
+
many recall and precision fixes across Java, C#, JS/TS, Go, Python and Rust. 4.16.1 fixes four
|
|
176
|
+
regressions found while benchmarking this bump. Java `analyze()` is back to 4.15.0 speed on
|
|
177
|
+
large classes (one NiFi file took 638 s on 4.16.0 and 0.83 s on 4.16.1; cognium-dev#806). A
|
|
178
|
+
`Character.is*` predicate on tainted text is no longer an `external_taint_escape`, which clears
|
|
179
|
+
SecuriBench Sanitizers2/6 (cognium-dev#807). Two C# false positives are fixed: a field
|
|
180
|
+
sanitized in another method (cognium-dev#808) and an `Array.IndexOf` allowlist guard
|
|
181
|
+
(cognium-dev#809). The underlying Java name-collision cause of #807 is cognium-dev#814.
|
|
182
|
+
|
|
183
|
+
Static suites, compared with 4.15.0 (packed 4.16.1, 27 runners):
|
|
184
|
+
- SecuriBench 97.2% / 6.7% → 97.7% / 6.7%;
|
|
185
|
+
- C# oracle 76.6% / 0.8% (3/367) → 77.1% / 0.3% (1/367);
|
|
186
|
+
- CWE-Bench-Java static 64 → 65 of 120;
|
|
187
|
+
- Python synthetic 72.2% → 74.0%;
|
|
188
|
+
- Vulnerability-goapp FPR 14.3% → 0.0%;
|
|
189
|
+
- the other 21 identical.
|
|
190
|
+
|
|
191
|
+
Figures before the scoring changes below.
|
|
192
|
+
|
|
193
|
+
### Fixed
|
|
194
|
+
- **LLM enrichment sinks are anchored in `runMerge`** (#330). The `scan --llm` merge path now
|
|
195
|
+
applies `resolveDiscoveryAnchor`, so an enrichment sink lands on a call node or carries
|
|
196
|
+
`llm-unanchored`, as in discovery mode. `runPatternMatch` forwards the IR calls for anchoring,
|
|
197
|
+
and LLM sinks merge on their post-anchor key.
|
|
198
|
+
- **SecurityScanner test-tree excludes (cognium-ai#615)**. The engine walker no longer uses
|
|
199
|
+
unanchored `**/test/**` / `**/tests/**`, which also matched a Java package segment named
|
|
200
|
+
`test` under main source. It now mirrors the `init` template: `test/**`, `tests/**`,
|
|
201
|
+
`**/src/test/**`, `**/__tests__/**`.
|
|
202
|
+
|
|
203
|
+
### Benchmarks
|
|
204
|
+
- **SecuriBench Micro labels.** The runner read only the `@servlet vuln_count` annotation. Where
|
|
205
|
+
that annotation contradicts both `getVulnerabilityCount()` and the `/* BAD */` markers, the
|
|
206
|
+
method and markers now win, and every disagreement is printed (`benchmarks/lib/securibench-labels.ts`).
|
|
207
|
+
- Overrides: `pred/Pred2.java` 0 → 1 (the suite's one FP was a true positive),
|
|
208
|
+
`collections/Collections10.java` 0 → 1, `inter/Inter10.java` 2 → 1.
|
|
209
|
+
- Six other disagreements keep the annotation, because the markers side with it.
|
|
210
|
+
- On circle-ir 4.16.1: **TPR 97.3%, FPR 0.0%** (107/110, 0/13). With the old labels it was
|
|
211
|
+
97.7% / 6.7% (105/108, 1/15).
|
|
212
|
+
- **C# oracle clean controls are scored against their category's taint sinks**
|
|
213
|
+
(`cleanScope()`, `benchmarks/lib/csharp-oracle-scoring.ts`).
|
|
214
|
+
- A placeholder `category: "clean"` case resolves through its paired `*Tp.cs`.
|
|
215
|
+
- 33 controls for a non-taint signal (for example `IncorrectDefaultPermsSafe.cs`, the
|
|
216
|
+
CWE-276 twin) leave the denominator.
|
|
217
|
+
- 95 general-FP or unmapped cases keep the any-flow rule.
|
|
218
|
+
- On circle-ir 4.16.1: **TPR 77.1% (340/441), FPR 0.0% (0/334)**.
|
|
219
|
+
|
|
119
220
|
## [4.28.0] - 2026-10-15
|
|
120
221
|
|
|
121
222
|
Minor release: `circle-ir` 4.14.2 → 4.15.0, discovery prompts carry line numbers, verifier
|
|
@@ -9,9 +9,10 @@
|
|
|
9
9
|
*/
|
|
10
10
|
import type { DFG, ProjectProfile, TaintSanitizer } from 'circle-ir';
|
|
11
11
|
import type { TypeShape } from '../../security-scan/source-classifier.js';
|
|
12
|
-
import type
|
|
12
|
+
import { type LLMConfig } from '../../llm/config.js';
|
|
13
13
|
import type { PackageCoordinate } from '../../cache/coordinate.js';
|
|
14
14
|
import { type ClusterVector } from '../../cluster/cluster.js';
|
|
15
|
+
import { type AnchorCallNode } from '../../llm/anchoring.js';
|
|
15
16
|
export { patternMatchStep, enrichStep, mergeStep, verifyStep, reportStep, } from './steps.js';
|
|
16
17
|
export type VerificationStrategy = 'complete' | 'sampled' | 'prioritized';
|
|
17
18
|
export interface WorkflowInput {
|
|
@@ -97,7 +98,7 @@ export interface WorkflowOutput {
|
|
|
97
98
|
}
|
|
98
99
|
/** cognium-ai#591 — why `runEnrich` made no discovery call for a file. */
|
|
99
100
|
export type EnrichmentSkipReason = 'prefilter' | 'minified';
|
|
100
|
-
export declare function runMerge(patternSources: any[], patternSinks: any[], enrichmentResult: any | null, confidenceThreshold: number, sourceCode?: string, filePath?: string, types?: ReadonlyArray<TypeShape>, language?: string): {
|
|
101
|
+
export declare function runMerge(patternSources: any[], patternSinks: any[], enrichmentResult: any | null, confidenceThreshold: number, sourceCode?: string, filePath?: string, types?: ReadonlyArray<TypeShape>, language?: string, irCalls?: ReadonlyArray<AnchorCallNode>): {
|
|
101
102
|
mergedSources: any[];
|
|
102
103
|
mergedSinks: any[];
|
|
103
104
|
};
|
|
@@ -135,6 +136,13 @@ verifyAccounting?: {
|
|
|
135
136
|
* callers omit it and keep the pre-#471 `UNVERIFIED` label.
|
|
136
137
|
*/
|
|
137
138
|
submittedPairs?: UnverifiedPair[];
|
|
139
|
+
/**
|
|
140
|
+
* cognium-ai#777 — when false, a successful verifier answer that omits every
|
|
141
|
+
* pair stays `UNVERIFIED` instead of `refuted_by: 'omission'`. Set false for
|
|
142
|
+
* thinking/reasoning verifier models that often return `pairs: []` without
|
|
143
|
+
* assessing the batch.
|
|
144
|
+
*/
|
|
145
|
+
treatOmissionAsRefutation?: boolean;
|
|
138
146
|
},
|
|
139
147
|
/**
|
|
140
148
|
* cognium-ai#460 — the rest of `generateFindings`' argument list. With
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"workflow.d.ts","sourceRoot":"","sources":["../../../src/agents/mastra/workflow.ts"],"names":[],"mappings":"AAAA;;;;;;;;GAQG;AAGH,OAAO,KAAK,EAAoC,GAAG,EAAE,cAAc,EAAE,cAAc,EAAE,MAAM,WAAW,CAAC;AAOvG,OAAO,KAAK,EACV,SAAS,EAEV,MAAM,0CAA0C,CAAC;AAKlD,OAAO,KAAK,
|
|
1
|
+
{"version":3,"file":"workflow.d.ts","sourceRoot":"","sources":["../../../src/agents/mastra/workflow.ts"],"names":[],"mappings":"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"}
|
|
@@ -8,9 +8,10 @@
|
|
|
8
8
|
* analysis pipeline using the core analysis logic.
|
|
9
9
|
*/
|
|
10
10
|
import { generateFindings, getRuleInfo } from 'circle-ir';
|
|
11
|
-
import { isSuppressedSinkShape } from '../../security-scan/sink-filters.js';
|
|
11
|
+
import { isPhantomLineSink, isSuppressedSinkShape } from '../../security-scan/sink-filters.js';
|
|
12
12
|
import { classifySource, applyClassificationGate, } from '../../security-scan/source-classifier.js';
|
|
13
13
|
import { shouldGateInterproceduralParam, classifyEntryPointTier, } from '../../analysis/entry-point-detection.js';
|
|
14
|
+
import { getDefaultLLMConfig, isThinkingModel } from '../../llm/config.js';
|
|
14
15
|
import { withLLMContext } from '../../llm/debug-context.js';
|
|
15
16
|
import { fileNeedsEnrichment } from '../../llm/pre-filter.js';
|
|
16
17
|
import { toSupportedLLMLanguage } from '../../llm/prompts/index.js';
|
|
@@ -20,6 +21,7 @@ import { resetLLMCallLimiter, isSwarmActive } from '../../llm/call-limiter.js';
|
|
|
20
21
|
import { resetLLMPerFileBreaker, resetLLMBreakerForScan } from '../../llm/ax-client.js';
|
|
21
22
|
import { getTreeCache, computeTreeCacheKey, computeRuleSetFingerprint, } from '../../cache/tree-cache.js';
|
|
22
23
|
import { extractClusterVector } from '../../cluster/cluster.js';
|
|
24
|
+
import { resolveDiscoveryAnchor, } from '../../llm/anchoring.js';
|
|
23
25
|
// Re-export the Mastra step definitions
|
|
24
26
|
export { patternMatchStep, enrichStep, mergeStep, verifyStep, reportStep, } from './steps.js';
|
|
25
27
|
// ============================================================================
|
|
@@ -145,6 +147,9 @@ coordinate) {
|
|
|
145
147
|
// pre-2.33.0 cached entries will deserialize with `clusterVector`
|
|
146
148
|
// undefined and be excluded from clustering on that scan.
|
|
147
149
|
clusterVector: extractClusterVector(result),
|
|
150
|
+
// cognium-ai#330 — forward calls so enrichment sinks can be anchored in
|
|
151
|
+
// `runMerge` the same way discovery mode does in `DiscoveryEngine`.
|
|
152
|
+
calls: (result.calls ?? []),
|
|
148
153
|
};
|
|
149
154
|
// cognium-ai#153 — populate persistent tree cache. Disk-write errors
|
|
150
155
|
// never fail the workflow. #158 Round 2 — coordinate is honored by
|
|
@@ -222,7 +227,7 @@ async function runEnrich(filePath, sourceCode, types, imports, patternSources, p
|
|
|
222
227
|
skippedBy: 'minified',
|
|
223
228
|
};
|
|
224
229
|
}
|
|
225
|
-
if (!fileNeedsEnrichment(sourceCode)) {
|
|
230
|
+
if (!fileNeedsEnrichment(sourceCode, language)) {
|
|
226
231
|
// Synthetic JSONL entries so `analyze-llm-log.ts` can surface the
|
|
227
232
|
// skip rate. Two entries (one per call we *would* have fired) keep
|
|
228
233
|
// the per-signature aggregate numerator/denominator coherent.
|
|
@@ -246,7 +251,7 @@ async function runEnrich(filePath, sourceCode, types, imports, patternSources, p
|
|
|
246
251
|
};
|
|
247
252
|
}
|
|
248
253
|
const { getAxLLMClient } = await import('../../llm/ax-client.js');
|
|
249
|
-
const
|
|
254
|
+
const discoveryClient = getAxLLMClient(llmConfig);
|
|
250
255
|
const functionNames = types[0]?.methods?.map((m) => m.name).join(', ') || '';
|
|
251
256
|
const functionName = functionNames.split(',')[0]?.trim() || 'main';
|
|
252
257
|
// cognium-ai#591 — pass the real language through. Before this, every
|
|
@@ -265,23 +270,15 @@ async function runEnrich(filePath, sourceCode, types, imports, patternSources, p
|
|
|
265
270
|
.map((s) => `${s.type}:${s.line}`)
|
|
266
271
|
.sort()
|
|
267
272
|
.join(', ');
|
|
268
|
-
//
|
|
269
|
-
const
|
|
270
|
-
|
|
271
|
-
|
|
272
|
-
|
|
273
|
-
|
|
274
|
-
|
|
275
|
-
|
|
276
|
-
|
|
277
|
-
// Sink discovery
|
|
278
|
-
client.discoverSinksForLanguage(llmLanguage, {
|
|
279
|
-
code: sourceCode,
|
|
280
|
-
functionName,
|
|
281
|
-
methodCalls: '',
|
|
282
|
-
existingSinks: canonPatternPairs(patternSinks),
|
|
283
|
-
}),
|
|
284
|
-
]));
|
|
273
|
+
// Single combined discovery call (cognium-ai#780 S3); was 2 parallel calls (#139).
|
|
274
|
+
const { additionalSources: rawSources, additionalSinks: rawSinks } = await withLLMContext({ file: filePath, method: moduleName, phase: 'enrichment' }, () => discoveryClient.discoverSourcesAndSinksForLanguage(llmLanguage, {
|
|
275
|
+
code: sourceCode,
|
|
276
|
+
functionName,
|
|
277
|
+
moduleRole: 'unknown', // Role intentionally not classified here — see runEnrich docstring
|
|
278
|
+
methodCalls: '',
|
|
279
|
+
existingSources: canonPatternPairs(patternSources),
|
|
280
|
+
existingSinks: canonPatternPairs(patternSinks),
|
|
281
|
+
}));
|
|
285
282
|
// #35: validate LLM-discovered line numbers against file bounds. Small/
|
|
286
283
|
// weak models often echo the few-shot example line literally (e.g.
|
|
287
284
|
// line=15 from the sink prompt template), producing CWE-89 sinks at
|
|
@@ -358,7 +355,17 @@ function findEnclosing(types, line) {
|
|
|
358
355
|
}
|
|
359
356
|
return {};
|
|
360
357
|
}
|
|
361
|
-
|
|
358
|
+
function methodLineBounds(types, line) {
|
|
359
|
+
const { type, method } = findEnclosing(types, line);
|
|
360
|
+
if (method) {
|
|
361
|
+
return { start: method.start_line, end: method.end_line };
|
|
362
|
+
}
|
|
363
|
+
if (type) {
|
|
364
|
+
return { start: type.start_line, end: type.end_line };
|
|
365
|
+
}
|
|
366
|
+
return undefined;
|
|
367
|
+
}
|
|
368
|
+
export function runMerge(patternSources, patternSinks, enrichmentResult, confidenceThreshold, sourceCode, filePath, types, language, irCalls) {
|
|
362
369
|
// #48: previously these maps were keyed by `line` alone, which
|
|
363
370
|
// collapsed any two sources / sinks that landed on the same source
|
|
364
371
|
// line — even when they had different types or different variables.
|
|
@@ -435,34 +442,61 @@ export function runMerge(patternSources, patternSinks, enrichmentResult, confide
|
|
|
435
442
|
}
|
|
436
443
|
if (enrichmentResult) {
|
|
437
444
|
for (const llmSink of enrichmentResult.additionalSinks) {
|
|
438
|
-
const
|
|
439
|
-
|
|
445
|
+
const llmSinkCode = sliceLine(sourceCode, llmSink.line);
|
|
446
|
+
// cognium-ai#808 — run phantom/sink-shape guards on the LLM's
|
|
447
|
+
// original line before re-anchoring. Relocating onto a real call
|
|
448
|
+
// must not bypass suppression of comment/Javadoc/blank LLM lines.
|
|
449
|
+
const originalLineTrimmed = (llmSinkCode ?? '').trim();
|
|
450
|
+
if (originalLineTrimmed) {
|
|
451
|
+
if (isPhantomLineSink(llmSinkCode)
|
|
452
|
+
|| isSuppressedSinkShape(llmSinkCode, llmSink.type, filePath, null, llmSink.line, sourceCode)) {
|
|
453
|
+
continue;
|
|
454
|
+
}
|
|
455
|
+
}
|
|
456
|
+
let sinkLine = llmSink.line;
|
|
457
|
+
let anchor;
|
|
458
|
+
const bounds = methodLineBounds(types, llmSink.line);
|
|
459
|
+
if (irCalls && bounds) {
|
|
460
|
+
const snippet = llmSinkCode && llmSinkCode.trim().length > 0
|
|
461
|
+
? llmSinkCode
|
|
462
|
+
: llmSink.method
|
|
463
|
+
? `${llmSink.method}(`
|
|
464
|
+
: '';
|
|
465
|
+
const resolved = resolveDiscoveryAnchor(llmSink.line, patternSinks, irCalls, bounds.start, bounds.end, llmSink.type, snippet);
|
|
466
|
+
sinkLine = resolved.line;
|
|
467
|
+
if (resolved.anchor !== 'ir-sink') {
|
|
468
|
+
anchor = resolved.anchor;
|
|
469
|
+
}
|
|
470
|
+
}
|
|
471
|
+
// Product enrichment path: unanchored LLM sinks are not emitted (#808).
|
|
472
|
+
if (anchor === 'llm-unanchored') {
|
|
473
|
+
continue;
|
|
474
|
+
}
|
|
475
|
+
const anchoredKey = sinkKey({ line: sinkLine, type: llmSink.type });
|
|
476
|
+
const originalKey = sinkKey({ line: llmSink.line, type: llmSink.type });
|
|
477
|
+
const existingAtAnchored = sinksByKey.get(anchoredKey);
|
|
478
|
+
const existingAtOriginal = sinksByKey.get(originalKey);
|
|
479
|
+
const existing = existingAtAnchored ?? existingAtOriginal;
|
|
480
|
+
const mergeKey = existingAtAnchored ? anchoredKey : (existingAtOriginal ? originalKey : anchoredKey);
|
|
440
481
|
if (existing) {
|
|
441
|
-
sinksByKey.set(
|
|
482
|
+
sinksByKey.set(mergeKey, {
|
|
442
483
|
...existing,
|
|
443
484
|
confidence: Math.min(1.0, (existing.confidence || 0.8) + 0.2),
|
|
444
485
|
});
|
|
445
486
|
}
|
|
446
487
|
else if (llmSink.confidence >= confidenceThreshold) {
|
|
447
|
-
|
|
448
|
-
|
|
449
|
-
// safely returns false on undefined → phantom-line findings
|
|
450
|
-
// leak through to the report. Look up the source line so the
|
|
451
|
-
// existing sink-shape filters (and the new phantom-line guard)
|
|
452
|
-
// can do their job.
|
|
453
|
-
const llmSinkCode = sliceLine(sourceCode, llmSink.line);
|
|
454
|
-
sinksByKey.set(key, {
|
|
455
|
-
line: llmSink.line,
|
|
488
|
+
sinksByKey.set(anchoredKey, {
|
|
489
|
+
line: sinkLine,
|
|
456
490
|
type: llmSink.type,
|
|
457
491
|
cwe: llmSink.cwe,
|
|
458
|
-
location: `LLM-discovered at line ${
|
|
492
|
+
location: `LLM-discovered at line ${sinkLine}`,
|
|
459
493
|
confidence: llmSink.confidence,
|
|
460
494
|
method: llmSink.method,
|
|
461
495
|
argPositions: llmSink.argPositions,
|
|
462
|
-
code: llmSinkCode,
|
|
496
|
+
code: sliceLine(sourceCode, sinkLine) ?? llmSinkCode,
|
|
463
497
|
file: filePath,
|
|
464
|
-
// #26: pure-LLM-discovered sink — see source-side comment above.
|
|
465
498
|
discoveryMethod: 'llm',
|
|
499
|
+
...(anchor ? { anchor } : {}),
|
|
466
500
|
});
|
|
467
501
|
}
|
|
468
502
|
}
|
|
@@ -552,6 +586,13 @@ language,
|
|
|
552
586
|
// rubric on library/tool repos and cut the CWE-22 hallucination
|
|
553
587
|
// bucket documented in cognium-dev#245 RC3.
|
|
554
588
|
contextProfile) {
|
|
589
|
+
const verifierModel = {
|
|
590
|
+
...getDefaultLLMConfig(),
|
|
591
|
+
...llmConfig,
|
|
592
|
+
}.phases.verification.model;
|
|
593
|
+
// cognium-ai#777 — reasoning models often return `{"pairs":[]}` without assessing
|
|
594
|
+
// pairs; treating omission as refutation over-labels real findings FALSE_POSITIVE.
|
|
595
|
+
const treatOmissionAsRefutation = !isThinkingModel(verifierModel);
|
|
555
596
|
if (!enableVerification || mergedSources.length === 0 || mergedSinks.length === 0) {
|
|
556
597
|
return {
|
|
557
598
|
verificationResults: [],
|
|
@@ -568,6 +609,7 @@ contextProfile) {
|
|
|
568
609
|
// cognium-ai#471 — nothing was sent to the verifier.
|
|
569
610
|
submittedPairs: [],
|
|
570
611
|
verificationError: undefined,
|
|
612
|
+
treatOmissionAsRefutation,
|
|
571
613
|
processingTimeMs: 0,
|
|
572
614
|
};
|
|
573
615
|
}
|
|
@@ -703,6 +745,7 @@ contextProfile) {
|
|
|
703
745
|
// "refuted by omission" from "never asked" (sampling) when it labels the
|
|
704
746
|
// static findings it keeps.
|
|
705
747
|
submittedPairs: limitedSources.flatMap((s) => limitedSinks.map((k) => ({ sourceLine: s.line, sinkLine: k.line }))),
|
|
748
|
+
treatOmissionAsRefutation,
|
|
706
749
|
processingTimeMs: batchResult.processingTimeMs,
|
|
707
750
|
};
|
|
708
751
|
}
|
|
@@ -728,6 +771,7 @@ contextProfile) {
|
|
|
728
771
|
unverifiedPairs,
|
|
729
772
|
submittedPairs: [], // #471 — nothing was answered
|
|
730
773
|
verificationError: (error instanceof Error ? error.message : String(error)) || 'verification_failed',
|
|
774
|
+
treatOmissionAsRefutation,
|
|
731
775
|
processingTimeMs: Date.now() - startTime,
|
|
732
776
|
};
|
|
733
777
|
}
|
|
@@ -1075,7 +1119,8 @@ taintContext) {
|
|
|
1075
1119
|
// sampler never sent was never asked about);
|
|
1076
1120
|
// - no contributing pair sits in a failed chunk (#564 keeps those open).
|
|
1077
1121
|
const submittedPairKeys = new Set((verifyAccounting?.submittedPairs ?? []).map((p) => `${p.sourceLine}:${p.sinkLine}`));
|
|
1078
|
-
const omissionIsRefutation =
|
|
1122
|
+
const omissionIsRefutation = (verifyAccounting?.treatOmissionAsRefutation ?? true) &&
|
|
1123
|
+
submittedPairKeys.size > 0 &&
|
|
1079
1124
|
(verifyAccounting?.pairsDroppedByBudget ?? 0) === 0 &&
|
|
1080
1125
|
(verifyAccounting?.pairsUnmapped ?? 0) === 0;
|
|
1081
1126
|
const refutedByOmission = (finding) => {
|
|
@@ -1327,7 +1372,7 @@ export async function analyzeFile(filePath, sourceCode, options) {
|
|
|
1327
1372
|
enrichResult = await runEnrich(filePath, sourceCode, patternResult.types, patternResult.imports, patternResult.patternSources, patternResult.patternSinks, enableEnrichment, language, llmConfig);
|
|
1328
1373
|
}
|
|
1329
1374
|
const mergeResult = runMerge(patternResult.patternSources, patternResult.patternSinks, enrichResult.enrichmentResult, confidenceThreshold, sourceCode, filePath, patternResult.types, // cognium-ai#128 gate
|
|
1330
|
-
language);
|
|
1375
|
+
language, patternResult.calls);
|
|
1331
1376
|
// L2 — same pattern as enrich: wrap when the operator has opted in
|
|
1332
1377
|
// via CP_VERIFY_TIMEOUT_MS.
|
|
1333
1378
|
let verifyResult;
|
|
@@ -1351,6 +1396,7 @@ export async function analyzeFile(filePath, sourceCode, options) {
|
|
|
1351
1396
|
unverifiedPairs: verifyResult.unverifiedPairs,
|
|
1352
1397
|
verificationError: verifyResult.verificationError,
|
|
1353
1398
|
submittedPairs: verifyResult.submittedPairs, // cognium-ai#471
|
|
1399
|
+
treatOmissionAsRefutation: verifyResult.treatOmissionAsRefutation,
|
|
1354
1400
|
},
|
|
1355
1401
|
// cognium-ai#460 — full generateFindings argument list.
|
|
1356
1402
|
{ language, sanitizers: patternResult.sanitizers });
|
|
@@ -1388,7 +1434,7 @@ export async function* analyzeFileStream(filePath, sourceCode, options) {
|
|
|
1388
1434
|
yield { step: 'enrich', status: 'completed', data: enrichResult };
|
|
1389
1435
|
yield { step: 'merge', status: 'running' };
|
|
1390
1436
|
const mergeResult = runMerge(patternResult.patternSources, patternResult.patternSinks, enrichResult.enrichmentResult, 0.7, sourceCode, filePath, patternResult.types, // cognium-ai#128 gate
|
|
1391
|
-
language);
|
|
1437
|
+
language, patternResult.calls);
|
|
1392
1438
|
yield { step: 'merge', status: 'completed', data: mergeResult };
|
|
1393
1439
|
yield { step: 'verify', status: 'running' };
|
|
1394
1440
|
const verifyResult = await runVerify(filePath, sourceCode, mergeResult.mergedSources, mergeResult.mergedSinks, patternResult.types, enableVerification, maxVerificationTargets, verificationStrategy, llmConfig, language, contextProfile);
|
|
@@ -1407,6 +1453,7 @@ export async function* analyzeFileStream(filePath, sourceCode, options) {
|
|
|
1407
1453
|
unverifiedPairs: verifyResult.unverifiedPairs,
|
|
1408
1454
|
verificationError: verifyResult.verificationError,
|
|
1409
1455
|
submittedPairs: verifyResult.submittedPairs, // cognium-ai#471
|
|
1456
|
+
treatOmissionAsRefutation: verifyResult.treatOmissionAsRefutation,
|
|
1410
1457
|
},
|
|
1411
1458
|
// cognium-ai#460 — full generateFindings argument list.
|
|
1412
1459
|
{ language, sanitizers: patternResult.sanitizers });
|
|
@@ -1437,6 +1484,12 @@ export function resolveVerifiedSeverity(verifierSeverity, baseSeverity, detector
|
|
|
1437
1484
|
if (detectorSeverity && (rank[detectorSeverity] ?? 0) < (rank[severity] ?? 0)) {
|
|
1438
1485
|
severity = detectorSeverity;
|
|
1439
1486
|
}
|
|
1487
|
+
// cognium-ai#811 / #672 — gated detector rejected the sink (no index entry).
|
|
1488
|
+
// Without a per-sink cap, verified findings take the rule ceiling (#264).
|
|
1489
|
+
if (detectorSeverity === undefined &&
|
|
1490
|
+
(rank[severity] ?? 0) > (rank['medium'] ?? 0)) {
|
|
1491
|
+
severity = 'medium';
|
|
1492
|
+
}
|
|
1440
1493
|
return severity;
|
|
1441
1494
|
}
|
|
1442
1495
|
//# sourceMappingURL=workflow.js.map
|