corpus-core 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/dist/autofix.d.ts +41 -0
- package/dist/autofix.js +159 -0
- package/dist/constants.d.ts +9 -0
- package/dist/constants.js +9 -0
- package/dist/cve-database.json +396 -0
- package/dist/cve-patterns.d.ts +54 -0
- package/dist/cve-patterns.js +124 -0
- package/dist/engine.d.ts +6 -0
- package/dist/engine.js +71 -0
- package/dist/graph-engine.d.ts +56 -0
- package/dist/graph-engine.js +412 -0
- package/dist/index.d.ts +17 -0
- package/dist/index.js +17 -0
- package/dist/log.d.ts +7 -0
- package/dist/log.js +33 -0
- package/dist/logger.d.ts +1 -0
- package/dist/logger.js +12 -0
- package/dist/memory.d.ts +67 -0
- package/dist/memory.js +261 -0
- package/dist/pattern-learner.d.ts +82 -0
- package/dist/pattern-learner.js +420 -0
- package/dist/scanners/code-safety.d.ts +13 -0
- package/dist/scanners/code-safety.js +114 -0
- package/dist/scanners/confidence-calibrator.d.ts +25 -0
- package/dist/scanners/confidence-calibrator.js +58 -0
- package/dist/scanners/context-poisoning.d.ts +18 -0
- package/dist/scanners/context-poisoning.js +48 -0
- package/dist/scanners/cross-user-firewall.d.ts +10 -0
- package/dist/scanners/cross-user-firewall.js +24 -0
- package/dist/scanners/dependency-checker.d.ts +15 -0
- package/dist/scanners/dependency-checker.js +203 -0
- package/dist/scanners/exfiltration-guard.d.ts +19 -0
- package/dist/scanners/exfiltration-guard.js +49 -0
- package/dist/scanners/index.d.ts +12 -0
- package/dist/scanners/index.js +12 -0
- package/dist/scanners/injection-firewall.d.ts +12 -0
- package/dist/scanners/injection-firewall.js +71 -0
- package/dist/scanners/scope-enforcer.d.ts +10 -0
- package/dist/scanners/scope-enforcer.js +30 -0
- package/dist/scanners/secret-detector.d.ts +34 -0
- package/dist/scanners/secret-detector.js +188 -0
- package/dist/scanners/session-hijack.d.ts +16 -0
- package/dist/scanners/session-hijack.js +53 -0
- package/dist/scanners/trust-score.d.ts +34 -0
- package/dist/scanners/trust-score.js +164 -0
- package/dist/scanners/undo-integrity.d.ts +9 -0
- package/dist/scanners/undo-integrity.js +38 -0
- package/dist/subprocess.d.ts +10 -0
- package/dist/subprocess.js +103 -0
- package/dist/types.d.ts +117 -0
- package/dist/types.js +16 -0
- package/dist/yaml-evaluator.d.ts +12 -0
- package/dist/yaml-evaluator.js +105 -0
- package/package.json +36 -0
- package/src/cve-database.json +396 -0
|
@@ -0,0 +1,54 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Corpus CVE Pattern Detector
|
|
3
|
+
*
|
|
4
|
+
* Loads a curated database of CVE-linked vulnerability patterns and
|
|
5
|
+
* scans source code for matches. Each finding includes the CVE ID,
|
|
6
|
+
* severity, matched code, and remediation guidance.
|
|
7
|
+
*
|
|
8
|
+
* This is the "known threat" layer of the immune system — it catches
|
|
9
|
+
* patterns linked to real-world CVEs before they ship.
|
|
10
|
+
*/
|
|
11
|
+
export interface CVEPattern {
|
|
12
|
+
id: string;
|
|
13
|
+
name: string;
|
|
14
|
+
pattern: RegExp;
|
|
15
|
+
description: string;
|
|
16
|
+
severity: 'CRITICAL' | 'HIGH' | 'MEDIUM';
|
|
17
|
+
ecosystem: string;
|
|
18
|
+
affectedPackages: string[];
|
|
19
|
+
references: string[];
|
|
20
|
+
codeExample: string;
|
|
21
|
+
fixExample: string;
|
|
22
|
+
}
|
|
23
|
+
export interface CVEFinding {
|
|
24
|
+
cveId: string;
|
|
25
|
+
name: string;
|
|
26
|
+
severity: 'CRITICAL' | 'HIGH' | 'MEDIUM';
|
|
27
|
+
line: number;
|
|
28
|
+
file: string;
|
|
29
|
+
matchedCode: string;
|
|
30
|
+
description: string;
|
|
31
|
+
fixExample: string;
|
|
32
|
+
references: string[];
|
|
33
|
+
}
|
|
34
|
+
/**
|
|
35
|
+
* Load and compile all CVE patterns from the JSON database.
|
|
36
|
+
* Results are cached in module scope so the file is read only once.
|
|
37
|
+
*/
|
|
38
|
+
export declare function loadCVEPatterns(): CVEPattern[];
|
|
39
|
+
/**
|
|
40
|
+
* Scan file content for CVE-linked vulnerability patterns.
|
|
41
|
+
*
|
|
42
|
+
* Returns an array of findings with line numbers, matched code snippets,
|
|
43
|
+
* and remediation guidance. Test files are skipped automatically.
|
|
44
|
+
*/
|
|
45
|
+
export declare function checkForCVEs(content: string, filepath: string): CVEFinding[];
|
|
46
|
+
/**
|
|
47
|
+
* Return a summary of the loaded CVE database.
|
|
48
|
+
*/
|
|
49
|
+
export declare function getCVESummary(): {
|
|
50
|
+
total: number;
|
|
51
|
+
critical: number;
|
|
52
|
+
high: number;
|
|
53
|
+
medium: number;
|
|
54
|
+
};
|
|
@@ -0,0 +1,124 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Corpus CVE Pattern Detector
|
|
3
|
+
*
|
|
4
|
+
* Loads a curated database of CVE-linked vulnerability patterns and
|
|
5
|
+
* scans source code for matches. Each finding includes the CVE ID,
|
|
6
|
+
* severity, matched code, and remediation guidance.
|
|
7
|
+
*
|
|
8
|
+
* This is the "known threat" layer of the immune system — it catches
|
|
9
|
+
* patterns linked to real-world CVEs before they ship.
|
|
10
|
+
*/
|
|
11
|
+
import { readFileSync } from 'fs';
|
|
12
|
+
import path from 'path';
|
|
13
|
+
import { fileURLToPath } from 'url';
|
|
14
|
+
// ---------------------------------------------------------------------------
|
|
15
|
+
// Module-level cache
|
|
16
|
+
// ---------------------------------------------------------------------------
|
|
17
|
+
const __dirname = path.dirname(fileURLToPath(import.meta.url));
|
|
18
|
+
const DATABASE_PATH = path.join(__dirname, 'cve-database.json');
|
|
19
|
+
let cachedPatterns = null;
|
|
20
|
+
// ---------------------------------------------------------------------------
|
|
21
|
+
// Test-file heuristic (shared with other Corpus scanners)
|
|
22
|
+
// ---------------------------------------------------------------------------
|
|
23
|
+
const TEST_INDICATORS = [
|
|
24
|
+
'test',
|
|
25
|
+
'spec',
|
|
26
|
+
'__tests__',
|
|
27
|
+
'__mocks__',
|
|
28
|
+
'fixture',
|
|
29
|
+
'mock',
|
|
30
|
+
'stub',
|
|
31
|
+
'e2e',
|
|
32
|
+
'integration-test',
|
|
33
|
+
'.test.',
|
|
34
|
+
'.spec.',
|
|
35
|
+
'.stories.',
|
|
36
|
+
];
|
|
37
|
+
function isTestFile(filepath) {
|
|
38
|
+
const lower = filepath.toLowerCase();
|
|
39
|
+
return TEST_INDICATORS.some((t) => lower.includes(t));
|
|
40
|
+
}
|
|
41
|
+
// ---------------------------------------------------------------------------
|
|
42
|
+
// Core API
|
|
43
|
+
// ---------------------------------------------------------------------------
|
|
44
|
+
/**
|
|
45
|
+
* Load and compile all CVE patterns from the JSON database.
|
|
46
|
+
* Results are cached in module scope so the file is read only once.
|
|
47
|
+
*/
|
|
48
|
+
export function loadCVEPatterns() {
|
|
49
|
+
if (cachedPatterns)
|
|
50
|
+
return cachedPatterns;
|
|
51
|
+
const raw = JSON.parse(readFileSync(DATABASE_PATH, 'utf-8'));
|
|
52
|
+
cachedPatterns = raw.patterns.map((entry) => ({
|
|
53
|
+
id: entry.id,
|
|
54
|
+
name: entry.name,
|
|
55
|
+
pattern: new RegExp(entry.pattern, entry.flags),
|
|
56
|
+
description: entry.description,
|
|
57
|
+
severity: entry.severity,
|
|
58
|
+
ecosystem: entry.ecosystem,
|
|
59
|
+
affectedPackages: entry.affectedPackages,
|
|
60
|
+
references: entry.references,
|
|
61
|
+
codeExample: entry.codeExample,
|
|
62
|
+
fixExample: entry.fixExample,
|
|
63
|
+
}));
|
|
64
|
+
return cachedPatterns;
|
|
65
|
+
}
|
|
66
|
+
/**
|
|
67
|
+
* Scan file content for CVE-linked vulnerability patterns.
|
|
68
|
+
*
|
|
69
|
+
* Returns an array of findings with line numbers, matched code snippets,
|
|
70
|
+
* and remediation guidance. Test files are skipped automatically.
|
|
71
|
+
*/
|
|
72
|
+
export function checkForCVEs(content, filepath) {
|
|
73
|
+
// Skip test files — same heuristic used by other Corpus scanners
|
|
74
|
+
if (isTestFile(filepath))
|
|
75
|
+
return [];
|
|
76
|
+
const patterns = loadCVEPatterns();
|
|
77
|
+
const lines = content.split('\n');
|
|
78
|
+
const findings = [];
|
|
79
|
+
for (const cve of patterns) {
|
|
80
|
+
// Reset lastIndex for stateful regexes (global flag)
|
|
81
|
+
cve.pattern.lastIndex = 0;
|
|
82
|
+
for (let i = 0; i < lines.length; i++) {
|
|
83
|
+
const line = lines[i];
|
|
84
|
+
cve.pattern.lastIndex = 0;
|
|
85
|
+
if (cve.pattern.test(line)) {
|
|
86
|
+
findings.push({
|
|
87
|
+
cveId: cve.id,
|
|
88
|
+
name: cve.name,
|
|
89
|
+
severity: cve.severity,
|
|
90
|
+
line: i + 1,
|
|
91
|
+
file: filepath,
|
|
92
|
+
matchedCode: line.trim(),
|
|
93
|
+
description: cve.description,
|
|
94
|
+
fixExample: cve.fixExample,
|
|
95
|
+
references: cve.references,
|
|
96
|
+
});
|
|
97
|
+
}
|
|
98
|
+
}
|
|
99
|
+
}
|
|
100
|
+
return findings;
|
|
101
|
+
}
|
|
102
|
+
/**
|
|
103
|
+
* Return a summary of the loaded CVE database.
|
|
104
|
+
*/
|
|
105
|
+
export function getCVESummary() {
|
|
106
|
+
const patterns = loadCVEPatterns();
|
|
107
|
+
let critical = 0;
|
|
108
|
+
let high = 0;
|
|
109
|
+
let medium = 0;
|
|
110
|
+
for (const p of patterns) {
|
|
111
|
+
switch (p.severity) {
|
|
112
|
+
case 'CRITICAL':
|
|
113
|
+
critical++;
|
|
114
|
+
break;
|
|
115
|
+
case 'HIGH':
|
|
116
|
+
high++;
|
|
117
|
+
break;
|
|
118
|
+
case 'MEDIUM':
|
|
119
|
+
medium++;
|
|
120
|
+
break;
|
|
121
|
+
}
|
|
122
|
+
}
|
|
123
|
+
return { total: patterns.length, critical, high, medium };
|
|
124
|
+
}
|
package/dist/engine.d.ts
ADDED
package/dist/engine.js
ADDED
|
@@ -0,0 +1,71 @@
|
|
|
1
|
+
import path from 'path';
|
|
2
|
+
import { fileURLToPath } from 'url';
|
|
3
|
+
import { evaluateYamlPolicies, loadPolicyFile } from './yaml-evaluator.js';
|
|
4
|
+
import { runJacWalker } from './subprocess.js';
|
|
5
|
+
import { DEFAULT_POLICY_EVAL_TIMEOUT_MS } from './constants.js';
|
|
6
|
+
const __dirname = path.dirname(fileURLToPath(import.meta.url));
|
|
7
|
+
// In source: src/ -> packages/core/ -> packages/ -> repo root -> policies/builtin
|
|
8
|
+
// In dist: dist/ -> packages/core/ -> packages/ -> repo root -> policies/builtin
|
|
9
|
+
const BUILTIN_POLICY_DIR = path.resolve(__dirname, '..', '..', '..', 'policies', 'builtin');
|
|
10
|
+
export async function evaluatePolicies(input, config) {
|
|
11
|
+
const start = Date.now();
|
|
12
|
+
const timeout = config.timeouts?.policyEvalMs ?? DEFAULT_POLICY_EVAL_TIMEOUT_MS;
|
|
13
|
+
// Load the developer's policy file
|
|
14
|
+
const policyFile = await loadPolicyFile(config.policyPath);
|
|
15
|
+
// Step 1: Evaluate YAML rules (developer-defined, fast, synchronous)
|
|
16
|
+
const yamlResult = evaluateYamlPolicies(input, policyFile);
|
|
17
|
+
if (yamlResult.verdict !== 'PASS') {
|
|
18
|
+
return { result: yamlResult, durationMs: Date.now() - start };
|
|
19
|
+
}
|
|
20
|
+
// Step 2: Built-in action safety walker (hard rules, always runs)
|
|
21
|
+
const safetyResult = await runJacWalker(path.join(BUILTIN_POLICY_DIR, 'action_safety.jac'), 'CheckActionSafety', {
|
|
22
|
+
action_type: input.actionType,
|
|
23
|
+
user_approved: input.userApproved ?? false,
|
|
24
|
+
}, timeout);
|
|
25
|
+
if (safetyResult.verdict !== 'PASS') {
|
|
26
|
+
return { result: safetyResult, durationMs: Date.now() - start };
|
|
27
|
+
}
|
|
28
|
+
// Step 3: Scope guard (only if policy declares allowed_actions)
|
|
29
|
+
if (policyFile.allowedActions && policyFile.allowedActions.length > 0) {
|
|
30
|
+
const scopeResult = await runJacWalker(path.join(BUILTIN_POLICY_DIR, 'scope_guard.jac'), 'CheckScope', {
|
|
31
|
+
action_type: input.actionType,
|
|
32
|
+
allowed_actions: policyFile.allowedActions,
|
|
33
|
+
intent_context: String(input.context?.intent ?? ''),
|
|
34
|
+
}, timeout);
|
|
35
|
+
if (scopeResult.verdict !== 'PASS') {
|
|
36
|
+
return { result: scopeResult, durationMs: Date.now() - start };
|
|
37
|
+
}
|
|
38
|
+
}
|
|
39
|
+
// Step 4: Rate guard (only if policy declares rate limits)
|
|
40
|
+
if (policyFile.rateLimits) {
|
|
41
|
+
const limit = policyFile.rateLimits[input.actionType] ?? policyFile.rateLimits['*'];
|
|
42
|
+
if (limit) {
|
|
43
|
+
const rateResult = await runJacWalker(path.join(BUILTIN_POLICY_DIR, 'rate_guard.jac'), 'CheckRateLimit', {
|
|
44
|
+
action_type: input.actionType,
|
|
45
|
+
limit: limit.count,
|
|
46
|
+
window_seconds: limit.windowSeconds,
|
|
47
|
+
current_epoch: Math.floor(Date.now() / 1000),
|
|
48
|
+
}, timeout);
|
|
49
|
+
if (rateResult.verdict !== 'PASS') {
|
|
50
|
+
return { result: rateResult, durationMs: Date.now() - start };
|
|
51
|
+
}
|
|
52
|
+
}
|
|
53
|
+
}
|
|
54
|
+
// Step 5: Custom Jac extension (if declared in policy file)
|
|
55
|
+
if (policyFile.jacExtension) {
|
|
56
|
+
const customResult = await runJacWalker(policyFile.jacExtension, 'CheckCustom', { action_type: input.actionType, context: input.context ?? {} }, timeout);
|
|
57
|
+
if (customResult.verdict !== 'PASS') {
|
|
58
|
+
return { result: customResult, durationMs: Date.now() - start };
|
|
59
|
+
}
|
|
60
|
+
}
|
|
61
|
+
return {
|
|
62
|
+
result: {
|
|
63
|
+
verdict: 'PASS',
|
|
64
|
+
blockReason: null,
|
|
65
|
+
message: '',
|
|
66
|
+
policyName: '__pass__',
|
|
67
|
+
requiresConfirmation: false,
|
|
68
|
+
},
|
|
69
|
+
durationMs: Date.now() - start,
|
|
70
|
+
};
|
|
71
|
+
}
|
|
@@ -0,0 +1,56 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Corpus Graph Engine
|
|
3
|
+
*
|
|
4
|
+
* Auto-scans a TypeScript/JavaScript codebase and builds a structural graph.
|
|
5
|
+
* Nodes = functions, modules, classes. Edges = calls, imports, exports.
|
|
6
|
+
* This is the "immune system memory" -- Corpus learns what your codebase looks like.
|
|
7
|
+
*
|
|
8
|
+
* No manual configuration. No YAML to write. It figures it out.
|
|
9
|
+
*/
|
|
10
|
+
export interface GraphNode {
|
|
11
|
+
id: string;
|
|
12
|
+
type: 'function' | 'class' | 'module' | 'variable';
|
|
13
|
+
name: string;
|
|
14
|
+
file: string;
|
|
15
|
+
line: number;
|
|
16
|
+
exported: boolean;
|
|
17
|
+
params: string[];
|
|
18
|
+
returnType: string | null;
|
|
19
|
+
guards: string[];
|
|
20
|
+
health: 'verified' | 'violates' | 'uncertain';
|
|
21
|
+
trustScore: number;
|
|
22
|
+
}
|
|
23
|
+
export interface GraphEdge {
|
|
24
|
+
source: string;
|
|
25
|
+
target: string;
|
|
26
|
+
type: 'calls' | 'imports' | 'exports' | 'extends' | 'implements';
|
|
27
|
+
}
|
|
28
|
+
export interface CodebaseGraph {
|
|
29
|
+
version: 1;
|
|
30
|
+
created: string;
|
|
31
|
+
updated: string;
|
|
32
|
+
projectRoot: string;
|
|
33
|
+
nodes: GraphNode[];
|
|
34
|
+
edges: GraphEdge[];
|
|
35
|
+
stats: {
|
|
36
|
+
totalFiles: number;
|
|
37
|
+
totalFunctions: number;
|
|
38
|
+
totalExports: number;
|
|
39
|
+
healthScore: number;
|
|
40
|
+
};
|
|
41
|
+
}
|
|
42
|
+
export declare function buildGraph(projectRoot: string): CodebaseGraph;
|
|
43
|
+
export interface GraphDiff {
|
|
44
|
+
added: GraphNode[];
|
|
45
|
+
removed: GraphNode[];
|
|
46
|
+
modified: {
|
|
47
|
+
before: GraphNode;
|
|
48
|
+
after: GraphNode;
|
|
49
|
+
changes: string[];
|
|
50
|
+
}[];
|
|
51
|
+
verdict: 'VERIFIED' | 'VIOLATES' | 'UNCERTAIN';
|
|
52
|
+
violations: string[];
|
|
53
|
+
}
|
|
54
|
+
export declare function diffFile(graph: CodebaseGraph, filePath: string, newContent: string): GraphDiff;
|
|
55
|
+
export declare function saveGraph(graph: CodebaseGraph, projectRoot: string): string;
|
|
56
|
+
export declare function loadGraph(projectRoot: string): CodebaseGraph | null;
|