@kernlang/review 2.0.0 → 3.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/dist/concept-rules/boundary-mutation.d.ts +13 -0
- package/dist/concept-rules/boundary-mutation.js +40 -0
- package/dist/concept-rules/boundary-mutation.js.map +1 -0
- package/dist/concept-rules/ignored-error.d.ts +13 -0
- package/dist/concept-rules/ignored-error.js +40 -0
- package/dist/concept-rules/ignored-error.js.map +1 -0
- package/dist/concept-rules/illegal-dependency.d.ts +13 -0
- package/dist/concept-rules/illegal-dependency.js +49 -0
- package/dist/concept-rules/illegal-dependency.js.map +1 -0
- package/dist/concept-rules/index.d.ts +15 -0
- package/dist/concept-rules/index.js +27 -0
- package/dist/concept-rules/index.js.map +1 -0
- package/dist/concept-rules/unguarded-effect.d.ts +13 -0
- package/dist/concept-rules/unguarded-effect.js +58 -0
- package/dist/concept-rules/unguarded-effect.js.map +1 -0
- package/dist/concept-rules/unrecovered-effect.d.ts +13 -0
- package/dist/concept-rules/unrecovered-effect.js +61 -0
- package/dist/concept-rules/unrecovered-effect.js.map +1 -0
- package/dist/confidence.d.ts +92 -0
- package/dist/confidence.js +263 -0
- package/dist/confidence.js.map +1 -0
- package/dist/differ.js +4 -2
- package/dist/differ.js.map +1 -1
- package/dist/external-tools.js +7 -3
- package/dist/external-tools.js.map +1 -1
- package/dist/file-role.d.ts +10 -0
- package/dist/file-role.js +80 -0
- package/dist/file-role.js.map +1 -0
- package/dist/graph.d.ts +11 -0
- package/dist/graph.js +152 -0
- package/dist/graph.js.map +1 -0
- package/dist/index.d.ts +46 -3
- package/dist/index.js +313 -27
- package/dist/index.js.map +1 -1
- package/dist/inferrer.js +123 -25
- package/dist/inferrer.js.map +1 -1
- package/dist/kern-lint.d.ts +18 -0
- package/dist/kern-lint.js +24 -0
- package/dist/kern-lint.js.map +1 -0
- package/dist/llm-bridge.d.ts +42 -0
- package/dist/llm-bridge.js +176 -0
- package/dist/llm-bridge.js.map +1 -0
- package/dist/llm-review.d.ts +8 -1
- package/dist/llm-review.js +20 -7
- package/dist/llm-review.js.map +1 -1
- package/dist/mappers/ts-concepts.d.ts +9 -0
- package/dist/mappers/ts-concepts.js +518 -0
- package/dist/mappers/ts-concepts.js.map +1 -0
- package/dist/quality-rules.d.ts +3 -3
- package/dist/quality-rules.js +3 -11
- package/dist/quality-rules.js.map +1 -1
- package/dist/reporter.d.ts +19 -3
- package/dist/reporter.js +232 -20
- package/dist/reporter.js.map +1 -1
- package/dist/rules/base.js +167 -15
- package/dist/rules/base.js.map +1 -1
- package/dist/rules/confidence.d.ts +37 -0
- package/dist/rules/confidence.js +159 -0
- package/dist/rules/confidence.js.map +1 -0
- package/dist/rules/dead-logic.d.ts +13 -0
- package/dist/rules/dead-logic.js +393 -0
- package/dist/rules/dead-logic.js.map +1 -0
- package/dist/rules/express.js +69 -2
- package/dist/rules/express.js.map +1 -1
- package/dist/rules/ground-layer.d.ts +23 -0
- package/dist/rules/ground-layer.js +132 -0
- package/dist/rules/ground-layer.js.map +1 -0
- package/dist/rules/index.d.ts +1 -1
- package/dist/rules/index.js +8 -2
- package/dist/rules/index.js.map +1 -1
- package/dist/rules/kern-source.d.ts +16 -0
- package/dist/rules/kern-source.js +726 -0
- package/dist/rules/kern-source.js.map +1 -0
- package/dist/rules/nextjs.js +38 -10
- package/dist/rules/nextjs.js.map +1 -1
- package/dist/rules/null-safety.d.ts +12 -0
- package/dist/rules/null-safety.js +123 -0
- package/dist/rules/null-safety.js.map +1 -0
- package/dist/rules/react.js +64 -1
- package/dist/rules/react.js.map +1 -1
- package/dist/rules/security-v2.d.ts +12 -0
- package/dist/rules/security-v2.js +415 -0
- package/dist/rules/security-v2.js.map +1 -0
- package/dist/rules/security-v3.d.ts +12 -0
- package/dist/rules/security-v3.js +397 -0
- package/dist/rules/security-v3.js.map +1 -0
- package/dist/rules/security-v4.d.ts +22 -0
- package/dist/rules/security-v4.js +688 -0
- package/dist/rules/security-v4.js.map +1 -0
- package/dist/rules/security.d.ts +12 -0
- package/dist/rules/security.js +286 -0
- package/dist/rules/security.js.map +1 -0
- package/dist/rules/utils.d.ts +7 -0
- package/dist/rules/utils.js +21 -0
- package/dist/rules/utils.js.map +1 -0
- package/dist/rules/vue.js +1 -1
- package/dist/rules/vue.js.map +1 -1
- package/dist/spec-checker.d.ts +83 -0
- package/dist/spec-checker.js +405 -0
- package/dist/spec-checker.js.map +1 -0
- package/dist/suppression/apply-suppression.d.ts +17 -0
- package/dist/suppression/apply-suppression.js +94 -0
- package/dist/suppression/apply-suppression.js.map +1 -0
- package/dist/suppression/index.d.ts +6 -0
- package/dist/suppression/index.js +6 -0
- package/dist/suppression/index.js.map +1 -0
- package/dist/suppression/parse-directives.d.ts +25 -0
- package/dist/suppression/parse-directives.js +161 -0
- package/dist/suppression/parse-directives.js.map +1 -0
- package/dist/suppression/types.d.ts +32 -0
- package/dist/suppression/types.js +5 -0
- package/dist/suppression/types.js.map +1 -0
- package/dist/taint.d.ts +115 -0
- package/dist/taint.js +1052 -0
- package/dist/taint.js.map +1 -0
- package/dist/types.d.ts +71 -0
- package/dist/types.js.map +1 -1
- package/package.json +7 -4
|
@@ -0,0 +1,161 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Parse kern-ignore directives from source text.
|
|
3
|
+
*
|
|
4
|
+
* Supported syntax:
|
|
5
|
+
* // kern-ignore <rule-id>[, <rule-id>...] — suppress on same or next non-comment line
|
|
6
|
+
* // kern-ignore-file <rule-id>[, <rule-id>...] — suppress for entire file (first 5 lines)
|
|
7
|
+
* # kern-ignore <rule-id>[, <rule-id>...] — Python variant
|
|
8
|
+
* # kern-ignore-file <rule-id>[, <rule-id>...] — Python variant
|
|
9
|
+
*/
|
|
10
|
+
import { createFingerprint } from '../types.js';
|
|
11
|
+
/** Known concept rule IDs — these only support file-level suppression */
|
|
12
|
+
const CONCEPT_RULE_IDS = new Set([
|
|
13
|
+
'unguarded-effect',
|
|
14
|
+
'unrecovered-effect',
|
|
15
|
+
'ignored-error',
|
|
16
|
+
'boundary-mutation',
|
|
17
|
+
'illegal-dependency',
|
|
18
|
+
]);
|
|
19
|
+
/** Matches: // kern-ignore[-file] rule1, rule2 */
|
|
20
|
+
const TS_DIRECTIVE = /\/\/\s*kern-ignore(?:-(file))?\s+([\w-][\w,-\s]*)/;
|
|
21
|
+
/** Matches: # kern-ignore[-file] rule1, rule2 */
|
|
22
|
+
const PY_DIRECTIVE = /#\s*kern-ignore(?:-(file))?\s+([\w-][\w,-\s]*)/;
|
|
23
|
+
/** Matches bare: // kern-ignore (no rule IDs) */
|
|
24
|
+
const TS_BARE = /\/\/\s*kern-ignore\s*$/;
|
|
25
|
+
const PY_BARE = /#\s*kern-ignore\s*$/;
|
|
26
|
+
export function isConceptRule(ruleId) {
|
|
27
|
+
return CONCEPT_RULE_IDS.has(ruleId);
|
|
28
|
+
}
|
|
29
|
+
/**
|
|
30
|
+
* Parse all suppression directives from source text.
|
|
31
|
+
* Returns directives + any warnings (e.g., bare kern-ignore, concept rule on line-level).
|
|
32
|
+
*/
|
|
33
|
+
export function parseDirectives(source, filePath) {
|
|
34
|
+
const lines = source.split('\n');
|
|
35
|
+
const isPython = filePath.endsWith('.py');
|
|
36
|
+
const directivePattern = isPython ? PY_DIRECTIVE : TS_DIRECTIVE;
|
|
37
|
+
const barePattern = isPython ? PY_BARE : TS_BARE;
|
|
38
|
+
const isCommentLine = isPython
|
|
39
|
+
? (line) => line.trimStart().startsWith('#')
|
|
40
|
+
: (line) => line.trimStart().startsWith('//');
|
|
41
|
+
const directives = [];
|
|
42
|
+
const warnings = [];
|
|
43
|
+
for (let i = 0; i < lines.length; i++) {
|
|
44
|
+
const line = lines[i];
|
|
45
|
+
const lineNum = i + 1;
|
|
46
|
+
// Check for bare kern-ignore (no rule ID) — emit warning
|
|
47
|
+
if (barePattern.test(line)) {
|
|
48
|
+
warnings.push({
|
|
49
|
+
source: 'kern',
|
|
50
|
+
ruleId: 'kern-ignore-bare',
|
|
51
|
+
severity: 'warning',
|
|
52
|
+
category: 'style',
|
|
53
|
+
message: `Bare kern-ignore without rule ID — specify rules: // kern-ignore <rule-id>`,
|
|
54
|
+
primarySpan: { file: filePath, startLine: lineNum, startCol: 1, endLine: lineNum, endCol: line.length },
|
|
55
|
+
fingerprint: createFingerprint('kern-ignore-bare', lineNum, 1),
|
|
56
|
+
});
|
|
57
|
+
continue;
|
|
58
|
+
}
|
|
59
|
+
const match = directivePattern.exec(line);
|
|
60
|
+
if (!match)
|
|
61
|
+
continue;
|
|
62
|
+
const isFileLevel = match[1] === 'file';
|
|
63
|
+
const ruleIds = match[2].split(',').map(r => r.trim()).filter(Boolean);
|
|
64
|
+
if (ruleIds.length === 0)
|
|
65
|
+
continue;
|
|
66
|
+
// File-level directives must appear in the first 5 lines
|
|
67
|
+
if (isFileLevel && lineNum > 5) {
|
|
68
|
+
warnings.push({
|
|
69
|
+
source: 'kern',
|
|
70
|
+
ruleId: 'kern-ignore-position',
|
|
71
|
+
severity: 'warning',
|
|
72
|
+
category: 'style',
|
|
73
|
+
message: `kern-ignore-file must appear in the first 5 lines of the file (found at line ${lineNum})`,
|
|
74
|
+
primarySpan: { file: filePath, startLine: lineNum, startCol: 1, endLine: lineNum, endCol: line.length },
|
|
75
|
+
fingerprint: createFingerprint('kern-ignore-position', lineNum, 1),
|
|
76
|
+
});
|
|
77
|
+
continue;
|
|
78
|
+
}
|
|
79
|
+
// Concept rules with line-level suppression — emit warning, suggest file-level
|
|
80
|
+
if (!isFileLevel) {
|
|
81
|
+
const conceptRules = ruleIds.filter(isConceptRule);
|
|
82
|
+
if (conceptRules.length > 0) {
|
|
83
|
+
warnings.push({
|
|
84
|
+
source: 'kern',
|
|
85
|
+
ruleId: 'kern-ignore-concept',
|
|
86
|
+
severity: 'warning',
|
|
87
|
+
category: 'style',
|
|
88
|
+
message: `'${conceptRules.join(', ')}' ${conceptRules.length === 1 ? 'is a' : 'are'} concept rule${conceptRules.length === 1 ? '' : 's'} — use '// kern-ignore-file ${conceptRules.join(', ')}' at the top of the file instead`,
|
|
89
|
+
primarySpan: { file: filePath, startLine: lineNum, startCol: 1, endLine: lineNum, endCol: line.length },
|
|
90
|
+
fingerprint: createFingerprint('kern-ignore-concept', lineNum, 1),
|
|
91
|
+
});
|
|
92
|
+
// Still process non-concept rules on this line
|
|
93
|
+
const nonConcept = ruleIds.filter(r => !isConceptRule(r));
|
|
94
|
+
if (nonConcept.length === 0)
|
|
95
|
+
continue;
|
|
96
|
+
// Fall through with non-concept rules only
|
|
97
|
+
ruleIds.length = 0;
|
|
98
|
+
ruleIds.push(...nonConcept);
|
|
99
|
+
}
|
|
100
|
+
}
|
|
101
|
+
if (isFileLevel) {
|
|
102
|
+
directives.push({
|
|
103
|
+
type: 'file',
|
|
104
|
+
ruleIds,
|
|
105
|
+
file: filePath,
|
|
106
|
+
source: 'inline',
|
|
107
|
+
commentLine: lineNum,
|
|
108
|
+
});
|
|
109
|
+
}
|
|
110
|
+
else {
|
|
111
|
+
// Same-line: check if the directive is on a line with actual code
|
|
112
|
+
// If the line is comment-only, it applies to the next non-comment, non-blank line
|
|
113
|
+
const trimmed = line.trimStart();
|
|
114
|
+
const isCommentOnly = isPython
|
|
115
|
+
? trimmed.startsWith('#') && !trimmed.replace(/#.*/, '').trim()
|
|
116
|
+
: trimmed.startsWith('//');
|
|
117
|
+
let targetLine;
|
|
118
|
+
if (isCommentOnly) {
|
|
119
|
+
// Find next non-comment, non-blank line
|
|
120
|
+
targetLine = lineNum; // fallback
|
|
121
|
+
for (let j = i + 1; j < lines.length; j++) {
|
|
122
|
+
const nextLine = lines[j].trim();
|
|
123
|
+
if (nextLine === '')
|
|
124
|
+
continue;
|
|
125
|
+
if (isCommentLine(nextLine))
|
|
126
|
+
continue;
|
|
127
|
+
targetLine = j + 1;
|
|
128
|
+
break;
|
|
129
|
+
}
|
|
130
|
+
}
|
|
131
|
+
else {
|
|
132
|
+
// Inline comment on same line as code — suppress this line
|
|
133
|
+
targetLine = lineNum;
|
|
134
|
+
}
|
|
135
|
+
directives.push({
|
|
136
|
+
type: 'line',
|
|
137
|
+
ruleIds,
|
|
138
|
+
file: filePath,
|
|
139
|
+
line: targetLine,
|
|
140
|
+
source: 'inline',
|
|
141
|
+
commentLine: lineNum,
|
|
142
|
+
});
|
|
143
|
+
}
|
|
144
|
+
}
|
|
145
|
+
return { directives, warnings };
|
|
146
|
+
}
|
|
147
|
+
/**
|
|
148
|
+
* Create config-level suppression directives from disabledRules.
|
|
149
|
+
* These apply to all files (file field is '*').
|
|
150
|
+
*/
|
|
151
|
+
export function configDirectives(disabledRules) {
|
|
152
|
+
if (disabledRules.length === 0)
|
|
153
|
+
return [];
|
|
154
|
+
return [{
|
|
155
|
+
type: 'file',
|
|
156
|
+
ruleIds: disabledRules,
|
|
157
|
+
file: '*',
|
|
158
|
+
source: 'config',
|
|
159
|
+
}];
|
|
160
|
+
}
|
|
161
|
+
//# sourceMappingURL=parse-directives.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"parse-directives.js","sourceRoot":"","sources":["../../src/suppression/parse-directives.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,32 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Types for the review suppression system.
|
|
3
|
+
*/
|
|
4
|
+
import type { ReviewFinding } from '../types.js';
|
|
5
|
+
/** A parsed suppression directive from source comments or config */
|
|
6
|
+
export interface SuppressionDirective {
|
|
7
|
+
/** 'line' = suppress on a specific line, 'file' = suppress entire file */
|
|
8
|
+
type: 'line' | 'file';
|
|
9
|
+
/** Rule IDs to suppress (always explicit — no blanket suppression) */
|
|
10
|
+
ruleIds: string[];
|
|
11
|
+
/** File this directive applies to */
|
|
12
|
+
file: string;
|
|
13
|
+
/** Target line (for 'line' type — the line whose findings are suppressed) */
|
|
14
|
+
line?: number;
|
|
15
|
+
/** Where this directive came from */
|
|
16
|
+
source: 'inline' | 'config';
|
|
17
|
+
/** The raw line number where the comment was found (for unused-directive warnings) */
|
|
18
|
+
commentLine?: number;
|
|
19
|
+
}
|
|
20
|
+
/** Result of applying suppression to a set of findings */
|
|
21
|
+
export interface SuppressionResult {
|
|
22
|
+
/** Findings that passed (not suppressed) */
|
|
23
|
+
findings: ReviewFinding[];
|
|
24
|
+
/** Findings that were suppressed */
|
|
25
|
+
suppressed: ReviewFinding[];
|
|
26
|
+
/** All parsed directives */
|
|
27
|
+
directives: SuppressionDirective[];
|
|
28
|
+
/** Directives that matched no findings (potential stale comments) */
|
|
29
|
+
unusedDirectives: SuppressionDirective[];
|
|
30
|
+
}
|
|
31
|
+
/** Strict mode levels for CI */
|
|
32
|
+
export type StrictMode = false | 'inline' | 'all';
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"types.js","sourceRoot":"","sources":["../../src/suppression/types.ts"],"names":[],"mappings":"AAAA;;GAEG"}
|
package/dist/taint.d.ts
ADDED
|
@@ -0,0 +1,115 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Taint Tracking — source→sink analysis on KERN IR.
|
|
3
|
+
*
|
|
4
|
+
* Phase 2 of the security pipeline. Works on InferResult[] handler bodies.
|
|
5
|
+
*
|
|
6
|
+
* Two modes:
|
|
7
|
+
* analyzeTaint() — intra-procedural (single file)
|
|
8
|
+
* analyzeTaintCrossFile() — inter-procedural (follows imports across files)
|
|
9
|
+
*
|
|
10
|
+
* Also validates sanitizer sufficiency: parseInt stops SQL injection on
|
|
11
|
+
* numeric values but NOT command injection. DOMPurify stops XSS but
|
|
12
|
+
* NOT SQL injection. The sufficiency matrix catches these mismatches.
|
|
13
|
+
*/
|
|
14
|
+
import { type SourceFile } from 'ts-morph';
|
|
15
|
+
import type { InferResult, ReviewFinding } from './types.js';
|
|
16
|
+
export interface TaintSource {
|
|
17
|
+
name: string;
|
|
18
|
+
origin: string;
|
|
19
|
+
line?: number;
|
|
20
|
+
}
|
|
21
|
+
export interface TaintSink {
|
|
22
|
+
name: string;
|
|
23
|
+
category: 'command' | 'fs' | 'sql' | 'redirect' | 'eval' | 'template';
|
|
24
|
+
taintedArg: string;
|
|
25
|
+
line?: number;
|
|
26
|
+
}
|
|
27
|
+
export interface TaintPath {
|
|
28
|
+
source: TaintSource;
|
|
29
|
+
sink: TaintSink;
|
|
30
|
+
sanitized: boolean;
|
|
31
|
+
sanitizer?: string;
|
|
32
|
+
insufficientSanitizer?: string;
|
|
33
|
+
}
|
|
34
|
+
export interface TaintResult {
|
|
35
|
+
fnName: string;
|
|
36
|
+
filePath: string;
|
|
37
|
+
startLine: number;
|
|
38
|
+
paths: TaintPath[];
|
|
39
|
+
}
|
|
40
|
+
type SinkCategory = TaintSink['category'];
|
|
41
|
+
/**
|
|
42
|
+
* Check if a sanitizer is actually sufficient for a given sink category.
|
|
43
|
+
* Returns true if the sanitizer protects against the sink, false if it's
|
|
44
|
+
* a mismatch (e.g., parseInt used to "sanitize" command injection).
|
|
45
|
+
*/
|
|
46
|
+
export declare function isSanitizerSufficient(sanitizerName: string, sinkCategory: SinkCategory): boolean;
|
|
47
|
+
export interface CrossFileTaintResult {
|
|
48
|
+
callerFile: string;
|
|
49
|
+
callerFn: string;
|
|
50
|
+
callerLine: number;
|
|
51
|
+
calleeFile: string;
|
|
52
|
+
calleeFn: string;
|
|
53
|
+
taintedArgs: string[];
|
|
54
|
+
sinkInCallee: TaintSink;
|
|
55
|
+
source: TaintSource;
|
|
56
|
+
}
|
|
57
|
+
/** Map of exported function names → file path + param info */
|
|
58
|
+
export interface ExportedFunction {
|
|
59
|
+
filePath: string;
|
|
60
|
+
fnName: string;
|
|
61
|
+
params: string;
|
|
62
|
+
hasSink: boolean;
|
|
63
|
+
sinks: TaintSink[];
|
|
64
|
+
}
|
|
65
|
+
/**
|
|
66
|
+
* Run taint analysis on all fn nodes in inferred results.
|
|
67
|
+
* When sourceFile is provided, uses AST-based analysis (more accurate).
|
|
68
|
+
* Falls back to regex-based analysis when no SourceFile available.
|
|
69
|
+
*/
|
|
70
|
+
export declare function analyzeTaint(inferred: InferResult[], filePath: string, sourceFile?: SourceFile): TaintResult[];
|
|
71
|
+
/**
|
|
72
|
+
* Multi-hop taint propagation using worklist algorithm.
|
|
73
|
+
* Propagates until fixed point or configurable depth limit.
|
|
74
|
+
*
|
|
75
|
+
* Handles all assignment patterns:
|
|
76
|
+
* - const b = a
|
|
77
|
+
* - const b = a.trim()
|
|
78
|
+
* - const {x} = obj
|
|
79
|
+
* - let b; b = a
|
|
80
|
+
*
|
|
81
|
+
* @param code - Handler code string
|
|
82
|
+
* @param initialTainted - Set of initially tainted variable names
|
|
83
|
+
* @param maxDepth - Maximum propagation depth (default: 3)
|
|
84
|
+
* @returns Set of all tainted variable names after fixed point or depth limit
|
|
85
|
+
*/
|
|
86
|
+
export declare function propagateTaintMultiHop(code: string, initialTainted: Set<string>, maxDepth?: number): Set<string>;
|
|
87
|
+
/**
|
|
88
|
+
* Convert taint results into ReviewFinding[] for the unified pipeline.
|
|
89
|
+
*/
|
|
90
|
+
export declare function taintToFindings(results: TaintResult[]): ReviewFinding[];
|
|
91
|
+
/**
|
|
92
|
+
* Build a map of exported functions across all files.
|
|
93
|
+
* Maps "filePath::fnName" → ExportedFunction with sink info.
|
|
94
|
+
*/
|
|
95
|
+
export declare function buildExportMap(inferredPerFile: Map<string, InferResult[]>): Map<string, ExportedFunction>;
|
|
96
|
+
/**
|
|
97
|
+
* Build import→function resolution map.
|
|
98
|
+
* Maps "importingFile::importedName" → absolute file path of the definition.
|
|
99
|
+
*/
|
|
100
|
+
export declare function buildImportMap(inferredPerFile: Map<string, InferResult[]>, graphImports: Map<string, string[]>): Map<string, string>;
|
|
101
|
+
/**
|
|
102
|
+
* Cross-file taint analysis.
|
|
103
|
+
*
|
|
104
|
+
* For each handler function with tainted params:
|
|
105
|
+
* 1. Find calls to imported functions in the handler body
|
|
106
|
+
* 2. Check if tainted data is passed as an argument
|
|
107
|
+
* 3. Look up the target function — does it have a dangerous sink?
|
|
108
|
+
* 4. If yes and no sanitizer in between → cross-file taint path
|
|
109
|
+
*/
|
|
110
|
+
export declare function analyzeTaintCrossFile(inferredPerFile: Map<string, InferResult[]>, graphImports: Map<string, string[]>): CrossFileTaintResult[];
|
|
111
|
+
/**
|
|
112
|
+
* Convert cross-file taint results into ReviewFinding[].
|
|
113
|
+
*/
|
|
114
|
+
export declare function crossFileTaintToFindings(results: CrossFileTaintResult[]): ReviewFinding[];
|
|
115
|
+
export {};
|