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
package/dist/memory.js
ADDED
|
@@ -0,0 +1,261 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Corpus Immune Memory -- Backboard.io Integration
|
|
3
|
+
*
|
|
4
|
+
* Stores behavioral baselines and health snapshots persistently across sessions.
|
|
5
|
+
* The longer you use Corpus, the smarter it gets. Memory powers:
|
|
6
|
+
* - "This function was flagged 3 times before"
|
|
7
|
+
* - Cross-session pattern recognition
|
|
8
|
+
* - Behavioral drift detection over time
|
|
9
|
+
*
|
|
10
|
+
* Falls back to local .corpus/memory.json when Backboard is unavailable.
|
|
11
|
+
*/
|
|
12
|
+
import { existsSync, readFileSync, writeFileSync, mkdirSync } from 'fs';
|
|
13
|
+
import path from 'path';
|
|
14
|
+
// ── Backboard.io Client ──────────────────────────────────────────────────────
|
|
15
|
+
const BACKBOARD_BASE_URL = 'https://app.backboard.io/api';
|
|
16
|
+
class BackboardClient {
|
|
17
|
+
apiKey;
|
|
18
|
+
assistantId = null;
|
|
19
|
+
constructor(apiKey) {
|
|
20
|
+
this.apiKey = apiKey;
|
|
21
|
+
}
|
|
22
|
+
async request(method, endpoint, options) {
|
|
23
|
+
const url = new URL(`${BACKBOARD_BASE_URL}/${endpoint.replace(/^\//, '')}`);
|
|
24
|
+
if (options?.params) {
|
|
25
|
+
for (const [key, val] of Object.entries(options.params)) {
|
|
26
|
+
url.searchParams.set(key, String(val));
|
|
27
|
+
}
|
|
28
|
+
}
|
|
29
|
+
const headers = {
|
|
30
|
+
'X-API-Key': this.apiKey,
|
|
31
|
+
'User-Agent': 'corpus/1.0',
|
|
32
|
+
};
|
|
33
|
+
let body;
|
|
34
|
+
if (options?.json) {
|
|
35
|
+
headers['Content-Type'] = 'application/json';
|
|
36
|
+
body = JSON.stringify(options.json);
|
|
37
|
+
}
|
|
38
|
+
const controller = new AbortController();
|
|
39
|
+
const timeout = setTimeout(() => controller.abort(), 30000);
|
|
40
|
+
try {
|
|
41
|
+
const res = await fetch(url.toString(), {
|
|
42
|
+
method,
|
|
43
|
+
headers,
|
|
44
|
+
body,
|
|
45
|
+
signal: controller.signal,
|
|
46
|
+
});
|
|
47
|
+
if (!res.ok) {
|
|
48
|
+
const text = await res.text().catch(() => '<no body>');
|
|
49
|
+
throw new Error(`Backboard API ${res.status}: ${text}`);
|
|
50
|
+
}
|
|
51
|
+
// DELETE responses may have no body
|
|
52
|
+
if (res.status === 204 || res.headers.get('content-length') === '0') {
|
|
53
|
+
return undefined;
|
|
54
|
+
}
|
|
55
|
+
return (await res.json());
|
|
56
|
+
}
|
|
57
|
+
finally {
|
|
58
|
+
clearTimeout(timeout);
|
|
59
|
+
}
|
|
60
|
+
}
|
|
61
|
+
async ensureAssistant(name) {
|
|
62
|
+
if (this.assistantId)
|
|
63
|
+
return this.assistantId;
|
|
64
|
+
try {
|
|
65
|
+
// Try to find existing assistant
|
|
66
|
+
const list = await this.request('GET', '/assistants', { params: { skip: 0, limit: 100 } });
|
|
67
|
+
const existing = (Array.isArray(list) ? list : []).find((a) => a.name === name);
|
|
68
|
+
if (existing) {
|
|
69
|
+
this.assistantId = existing.assistant_id;
|
|
70
|
+
return existing.assistant_id;
|
|
71
|
+
}
|
|
72
|
+
// Create new
|
|
73
|
+
const created = await this.request('POST', '/assistants', {
|
|
74
|
+
json: {
|
|
75
|
+
name,
|
|
76
|
+
description: 'Corpus immune memory for codebase health tracking',
|
|
77
|
+
system_prompt: 'You are the memory layer for Corpus, tracking codebase health patterns.',
|
|
78
|
+
},
|
|
79
|
+
});
|
|
80
|
+
this.assistantId = created.assistant_id;
|
|
81
|
+
return created.assistant_id;
|
|
82
|
+
}
|
|
83
|
+
catch (e) {
|
|
84
|
+
throw new Error(`Failed to initialize Backboard assistant: ${e}`);
|
|
85
|
+
}
|
|
86
|
+
}
|
|
87
|
+
async addMemory(assistantId, content, metadata) {
|
|
88
|
+
return this.request('POST', `/assistants/${assistantId}/memories`, {
|
|
89
|
+
json: { content, metadata },
|
|
90
|
+
});
|
|
91
|
+
}
|
|
92
|
+
async getMemories(assistantId) {
|
|
93
|
+
return this.request('GET', `/assistants/${assistantId}/memories`);
|
|
94
|
+
}
|
|
95
|
+
async deleteMemory(assistantId, memoryId) {
|
|
96
|
+
await this.request('DELETE', `/assistants/${assistantId}/memories/${memoryId}`);
|
|
97
|
+
}
|
|
98
|
+
}
|
|
99
|
+
// ── Local Memory Fallback ────────────────────────────────────────────────────
|
|
100
|
+
function getLocalMemoryPath(projectRoot) {
|
|
101
|
+
return path.join(projectRoot, '.corpus', 'memory.json');
|
|
102
|
+
}
|
|
103
|
+
function loadLocalMemory(projectRoot) {
|
|
104
|
+
const memPath = getLocalMemoryPath(projectRoot);
|
|
105
|
+
if (existsSync(memPath)) {
|
|
106
|
+
try {
|
|
107
|
+
return JSON.parse(readFileSync(memPath, 'utf-8'));
|
|
108
|
+
}
|
|
109
|
+
catch {
|
|
110
|
+
// Corrupted, start fresh
|
|
111
|
+
}
|
|
112
|
+
}
|
|
113
|
+
return {
|
|
114
|
+
entries: [],
|
|
115
|
+
stats: {
|
|
116
|
+
totalEntries: 0,
|
|
117
|
+
totalViolations: 0,
|
|
118
|
+
totalFixes: 0,
|
|
119
|
+
sessionsTracked: 0,
|
|
120
|
+
lastUpdated: new Date().toISOString(),
|
|
121
|
+
},
|
|
122
|
+
};
|
|
123
|
+
}
|
|
124
|
+
function saveLocalMemory(projectRoot, memory) {
|
|
125
|
+
const corpusDir = path.join(projectRoot, '.corpus');
|
|
126
|
+
if (!existsSync(corpusDir))
|
|
127
|
+
mkdirSync(corpusDir, { recursive: true });
|
|
128
|
+
writeFileSync(getLocalMemoryPath(projectRoot), JSON.stringify(memory, null, 2));
|
|
129
|
+
}
|
|
130
|
+
// ── Public API ───────────────────────────────────────────────────────────────
|
|
131
|
+
let backboardClient = null;
|
|
132
|
+
let backboardAssistantId = null;
|
|
133
|
+
function getBackboardClient() {
|
|
134
|
+
if (backboardClient)
|
|
135
|
+
return backboardClient;
|
|
136
|
+
const apiKey = process.env.BACKBOARD_API_KEY;
|
|
137
|
+
if (!apiKey)
|
|
138
|
+
return null;
|
|
139
|
+
backboardClient = new BackboardClient(apiKey);
|
|
140
|
+
return backboardClient;
|
|
141
|
+
}
|
|
142
|
+
/**
|
|
143
|
+
* Record a memory entry (violation, fix, baseline, pattern).
|
|
144
|
+
* Stores in Backboard.io if available, always stores locally.
|
|
145
|
+
*/
|
|
146
|
+
export async function recordMemory(projectRoot, entry) {
|
|
147
|
+
const memory = loadLocalMemory(projectRoot);
|
|
148
|
+
const fullEntry = {
|
|
149
|
+
...entry,
|
|
150
|
+
id: `mem_${Date.now()}_${Math.random().toString(36).slice(2, 8)}`,
|
|
151
|
+
timestamp: new Date().toISOString(),
|
|
152
|
+
};
|
|
153
|
+
memory.entries.push(fullEntry);
|
|
154
|
+
if (entry.type === 'violation')
|
|
155
|
+
memory.stats.totalViolations++;
|
|
156
|
+
if (entry.type === 'fix')
|
|
157
|
+
memory.stats.totalFixes++;
|
|
158
|
+
memory.stats.totalEntries++;
|
|
159
|
+
memory.stats.lastUpdated = fullEntry.timestamp;
|
|
160
|
+
saveLocalMemory(projectRoot, memory);
|
|
161
|
+
// Also store in Backboard.io if available
|
|
162
|
+
const client = getBackboardClient();
|
|
163
|
+
if (client) {
|
|
164
|
+
try {
|
|
165
|
+
if (!backboardAssistantId) {
|
|
166
|
+
const projectName = path.basename(projectRoot);
|
|
167
|
+
backboardAssistantId = await client.ensureAssistant(`corpus-${projectName}`);
|
|
168
|
+
}
|
|
169
|
+
await client.addMemory(backboardAssistantId, fullEntry.content, {
|
|
170
|
+
type: fullEntry.type,
|
|
171
|
+
file: fullEntry.file,
|
|
172
|
+
functionName: fullEntry.functionName,
|
|
173
|
+
...fullEntry.metadata,
|
|
174
|
+
});
|
|
175
|
+
}
|
|
176
|
+
catch {
|
|
177
|
+
// Backboard unavailable, local memory is the fallback
|
|
178
|
+
}
|
|
179
|
+
}
|
|
180
|
+
}
|
|
181
|
+
/**
|
|
182
|
+
* Get the flag count for a specific function across all sessions.
|
|
183
|
+
* "This function was flagged 3 times before."
|
|
184
|
+
*/
|
|
185
|
+
export function getFlagCount(projectRoot, file, functionName) {
|
|
186
|
+
const memory = loadLocalMemory(projectRoot);
|
|
187
|
+
return memory.entries.filter(e => e.type === 'violation' && e.file === file && e.functionName === functionName).length;
|
|
188
|
+
}
|
|
189
|
+
/**
|
|
190
|
+
* Get recent violations for the dashboard.
|
|
191
|
+
*/
|
|
192
|
+
export function getRecentViolations(projectRoot, limit = 20) {
|
|
193
|
+
const memory = loadLocalMemory(projectRoot);
|
|
194
|
+
return memory.entries
|
|
195
|
+
.filter(e => e.type === 'violation')
|
|
196
|
+
.sort((a, b) => new Date(b.timestamp).getTime() - new Date(a.timestamp).getTime())
|
|
197
|
+
.slice(0, limit);
|
|
198
|
+
}
|
|
199
|
+
/**
|
|
200
|
+
* Get immune memory stats for display.
|
|
201
|
+
*/
|
|
202
|
+
export function getMemoryStats(projectRoot) {
|
|
203
|
+
const memory = loadLocalMemory(projectRoot);
|
|
204
|
+
return memory.stats;
|
|
205
|
+
}
|
|
206
|
+
/**
|
|
207
|
+
* Get all memories (for syncing to Backboard.io or display).
|
|
208
|
+
*/
|
|
209
|
+
export function getAllMemories(projectRoot) {
|
|
210
|
+
return loadLocalMemory(projectRoot);
|
|
211
|
+
}
|
|
212
|
+
/**
|
|
213
|
+
* Get memories stored in Backboard.io for this project.
|
|
214
|
+
*/
|
|
215
|
+
export async function getBackboardMemories(projectRoot) {
|
|
216
|
+
const client = getBackboardClient();
|
|
217
|
+
if (!client)
|
|
218
|
+
return null;
|
|
219
|
+
const projectName = path.basename(projectRoot);
|
|
220
|
+
try {
|
|
221
|
+
const assistantId = await client.ensureAssistant(`corpus-${projectName}`);
|
|
222
|
+
return await client.getMemories(assistantId);
|
|
223
|
+
}
|
|
224
|
+
catch {
|
|
225
|
+
return null;
|
|
226
|
+
}
|
|
227
|
+
}
|
|
228
|
+
/**
|
|
229
|
+
* Sync local memory to Backboard.io (for initial setup or recovery).
|
|
230
|
+
*/
|
|
231
|
+
export async function syncToBackboard(projectRoot) {
|
|
232
|
+
const client = getBackboardClient();
|
|
233
|
+
if (!client)
|
|
234
|
+
return { synced: 0, errors: 0 };
|
|
235
|
+
const memory = loadLocalMemory(projectRoot);
|
|
236
|
+
const projectName = path.basename(projectRoot);
|
|
237
|
+
try {
|
|
238
|
+
const assistantId = await client.ensureAssistant(`corpus-${projectName}`);
|
|
239
|
+
let synced = 0;
|
|
240
|
+
let errors = 0;
|
|
241
|
+
for (const entry of memory.entries) {
|
|
242
|
+
try {
|
|
243
|
+
await client.addMemory(assistantId, entry.content, {
|
|
244
|
+
type: entry.type,
|
|
245
|
+
file: entry.file,
|
|
246
|
+
functionName: entry.functionName,
|
|
247
|
+
originalTimestamp: entry.timestamp,
|
|
248
|
+
...entry.metadata,
|
|
249
|
+
});
|
|
250
|
+
synced++;
|
|
251
|
+
}
|
|
252
|
+
catch {
|
|
253
|
+
errors++;
|
|
254
|
+
}
|
|
255
|
+
}
|
|
256
|
+
return { synced, errors };
|
|
257
|
+
}
|
|
258
|
+
catch {
|
|
259
|
+
return { synced: 0, errors: memory.entries.length };
|
|
260
|
+
}
|
|
261
|
+
}
|
|
@@ -0,0 +1,82 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Corpus Pattern Learner
|
|
3
|
+
*
|
|
4
|
+
* Analyzes findings across multiple repos to learn which patterns
|
|
5
|
+
* are real issues vs false positives. Builds a statistical model
|
|
6
|
+
* that evolves as more repos are scanned.
|
|
7
|
+
*
|
|
8
|
+
* This is the "learning" part of the immune system.
|
|
9
|
+
*/
|
|
10
|
+
export interface PatternSignature {
|
|
11
|
+
type: string;
|
|
12
|
+
totalOccurrences: number;
|
|
13
|
+
inTestFiles: number;
|
|
14
|
+
inProductionFiles: number;
|
|
15
|
+
inBuildTools: number;
|
|
16
|
+
falsePositiveRate: number;
|
|
17
|
+
severity: 'CRITICAL' | 'WARNING' | 'INFO';
|
|
18
|
+
adjustedSeverity: 'CRITICAL' | 'WARNING' | 'INFO' | 'SUPPRESSED';
|
|
19
|
+
description: string;
|
|
20
|
+
examples: {
|
|
21
|
+
repo: string;
|
|
22
|
+
file: string;
|
|
23
|
+
}[];
|
|
24
|
+
repoCount: number;
|
|
25
|
+
repoPrevalence: number;
|
|
26
|
+
contextBreakdown: Record<string, number>;
|
|
27
|
+
coOccursWith: Array<{
|
|
28
|
+
pattern: string;
|
|
29
|
+
correlation: number;
|
|
30
|
+
combinedRisk: 'ELEVATED' | 'NORMAL';
|
|
31
|
+
}>;
|
|
32
|
+
linkedCVEs: string[];
|
|
33
|
+
categoryWeights: Record<string, number>;
|
|
34
|
+
}
|
|
35
|
+
export interface LearnedPatterns {
|
|
36
|
+
version: 1;
|
|
37
|
+
learnedFrom: number;
|
|
38
|
+
totalFindings: number;
|
|
39
|
+
patterns: PatternSignature[];
|
|
40
|
+
lastUpdated: string;
|
|
41
|
+
knownPackages: string[];
|
|
42
|
+
repoCategories: Record<string, string>;
|
|
43
|
+
}
|
|
44
|
+
/**
|
|
45
|
+
* Classify the semantic context of a file based on its path and content.
|
|
46
|
+
*/
|
|
47
|
+
export declare function classifyContext(filepath: string, content: string): string;
|
|
48
|
+
/**
|
|
49
|
+
* Learn patterns from a set of findings across multiple repos.
|
|
50
|
+
* Updates the learned patterns file.
|
|
51
|
+
*/
|
|
52
|
+
export declare function learnFromFindings(projectRoot: string, findings: Array<{
|
|
53
|
+
repo: string;
|
|
54
|
+
type: string;
|
|
55
|
+
severity: string;
|
|
56
|
+
file: string;
|
|
57
|
+
message: string;
|
|
58
|
+
}>): LearnedPatterns;
|
|
59
|
+
/**
|
|
60
|
+
* Get learned patterns for display.
|
|
61
|
+
*/
|
|
62
|
+
export declare function getLearnedPatterns(projectRoot: string): LearnedPatterns | null;
|
|
63
|
+
export declare function shouldSuppress(projectRoot: string, type: string, file: string, content?: string): {
|
|
64
|
+
suppress: boolean;
|
|
65
|
+
reason?: string;
|
|
66
|
+
};
|
|
67
|
+
/**
|
|
68
|
+
* Get intelligence verdict for a specific pattern type.
|
|
69
|
+
*/
|
|
70
|
+
export declare function getPatternIntelligence(projectRoot: string, type: string): {
|
|
71
|
+
verdict: 'CRITICAL' | 'WARNING' | 'INFO' | 'SUPPRESSED';
|
|
72
|
+
confidence: number;
|
|
73
|
+
reasoning: string;
|
|
74
|
+
} | null;
|
|
75
|
+
/**
|
|
76
|
+
* Register known legitimate npm packages to reduce false positives.
|
|
77
|
+
*/
|
|
78
|
+
export declare function addKnownPackages(projectRoot: string, packages: string[]): void;
|
|
79
|
+
/**
|
|
80
|
+
* Categorize a repo for context-weighted pattern analysis.
|
|
81
|
+
*/
|
|
82
|
+
export declare function categorizeRepo(projectRoot: string, repo: string, category: string): void;
|