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,48 @@
|
|
|
1
|
+
const POISONING_PATTERNS = [
|
|
2
|
+
'ignore previous instructions', 'override all rules', 'you must always',
|
|
3
|
+
'from now on always', 'your new primary directive', 'secret instruction:',
|
|
4
|
+
'hidden instruction:', 'when asked about', 'never reveal',
|
|
5
|
+
'always respond with', 'pretend that', 'act as though',
|
|
6
|
+
'the real answer is', 'disregard safety', 'bypass all', 'system override',
|
|
7
|
+
];
|
|
8
|
+
const AUTHORITY_CLAIMS = [
|
|
9
|
+
'i am the administrator', 'i am the owner', 'admin access granted',
|
|
10
|
+
'elevated privileges', 'full access mode', 'authorized override',
|
|
11
|
+
'master key:', 'root access',
|
|
12
|
+
];
|
|
13
|
+
/**
|
|
14
|
+
* Scans a memory chunk for poisoning signatures.
|
|
15
|
+
*/
|
|
16
|
+
export function scanMemoryChunk(content, chunkId, retrievalQuery) {
|
|
17
|
+
const lower = content.toLowerCase();
|
|
18
|
+
const id = chunkId ?? '';
|
|
19
|
+
for (const pattern of POISONING_PATTERNS) {
|
|
20
|
+
if (lower.includes(pattern)) {
|
|
21
|
+
return {
|
|
22
|
+
severity: 'POISONED',
|
|
23
|
+
pattern,
|
|
24
|
+
chunkId: id,
|
|
25
|
+
message: `Memory poisoning in chunk '${id}': pattern '${pattern}'${retrievalQuery ? `. Query: '${retrievalQuery}'` : ''}`,
|
|
26
|
+
};
|
|
27
|
+
}
|
|
28
|
+
}
|
|
29
|
+
for (const pattern of AUTHORITY_CLAIMS) {
|
|
30
|
+
if (lower.includes(pattern)) {
|
|
31
|
+
return {
|
|
32
|
+
severity: 'SUSPICIOUS',
|
|
33
|
+
pattern,
|
|
34
|
+
chunkId: id,
|
|
35
|
+
message: `Suspicious authority claim in chunk '${id}': '${pattern}'`,
|
|
36
|
+
};
|
|
37
|
+
}
|
|
38
|
+
}
|
|
39
|
+
return { severity: 'CLEAN', pattern: null, chunkId: id, message: '' };
|
|
40
|
+
}
|
|
41
|
+
/**
|
|
42
|
+
* Batch scan multiple memory chunks. Returns only problematic ones.
|
|
43
|
+
*/
|
|
44
|
+
export function scanMemoryChunks(chunks, retrievalQuery) {
|
|
45
|
+
return chunks
|
|
46
|
+
.map((c) => scanMemoryChunk(c.content, c.id, retrievalQuery))
|
|
47
|
+
.filter((r) => r.severity !== 'CLEAN');
|
|
48
|
+
}
|
|
@@ -0,0 +1,10 @@
|
|
|
1
|
+
export interface IsolationCheckResult {
|
|
2
|
+
isolated: boolean;
|
|
3
|
+
foreignUserIds: string[];
|
|
4
|
+
foreignNamespaces: string[];
|
|
5
|
+
message: string;
|
|
6
|
+
}
|
|
7
|
+
/**
|
|
8
|
+
* Checks that no content from other users is in the current context.
|
|
9
|
+
*/
|
|
10
|
+
export declare function checkContextIsolation(currentUserId: string, contextUserIds: string[], memoryNamespaces?: string[]): IsolationCheckResult;
|
|
@@ -0,0 +1,24 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Checks that no content from other users is in the current context.
|
|
3
|
+
*/
|
|
4
|
+
export function checkContextIsolation(currentUserId, contextUserIds, memoryNamespaces) {
|
|
5
|
+
const foreignUsers = contextUserIds.filter((id) => id !== currentUserId);
|
|
6
|
+
const foreignNs = (memoryNamespaces ?? []).filter((ns) => !ns.toLowerCase().includes(currentUserId.toLowerCase()));
|
|
7
|
+
if (foreignUsers.length > 0) {
|
|
8
|
+
return {
|
|
9
|
+
isolated: false,
|
|
10
|
+
foreignUserIds: foreignUsers,
|
|
11
|
+
foreignNamespaces: foreignNs,
|
|
12
|
+
message: `Cross-user contamination: context contains data from user(s) ${foreignUsers.join(', ')}`,
|
|
13
|
+
};
|
|
14
|
+
}
|
|
15
|
+
if (foreignNs.length > 0) {
|
|
16
|
+
return {
|
|
17
|
+
isolated: false,
|
|
18
|
+
foreignUserIds: [],
|
|
19
|
+
foreignNamespaces: foreignNs,
|
|
20
|
+
message: `Memory from non-user namespaces: ${foreignNs.join(', ')}`,
|
|
21
|
+
};
|
|
22
|
+
}
|
|
23
|
+
return { isolated: true, foreignUserIds: [], foreignNamespaces: [], message: '' };
|
|
24
|
+
}
|
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
export type DependencySeverity = 'CRITICAL' | 'WARNING' | 'INFO';
|
|
2
|
+
export interface DependencyFinding {
|
|
3
|
+
severity: DependencySeverity;
|
|
4
|
+
package: string;
|
|
5
|
+
file: string;
|
|
6
|
+
line: number;
|
|
7
|
+
reason: 'nonexistent' | 'typosquat' | 'unpopular_suspicious';
|
|
8
|
+
suggestion: string;
|
|
9
|
+
similarPackages?: string[];
|
|
10
|
+
}
|
|
11
|
+
export declare function extractImportedPackages(content: string): string[];
|
|
12
|
+
export declare function checkDependencies(content: string, filepath: string, options?: {
|
|
13
|
+
projectRoot?: string;
|
|
14
|
+
knownPackages?: Set<string>;
|
|
15
|
+
}): Promise<DependencyFinding[]>;
|
|
@@ -0,0 +1,203 @@
|
|
|
1
|
+
import { readFileSync, writeFileSync, mkdirSync, existsSync } from 'node:fs';
|
|
2
|
+
import { join } from 'node:path';
|
|
3
|
+
const CACHE_TTL_MS = 24 * 60 * 60 * 1000;
|
|
4
|
+
const TOP_PACKAGES = [
|
|
5
|
+
'react', 'react-dom', 'next', 'express', 'lodash', 'axios', 'typescript',
|
|
6
|
+
'webpack', 'babel', 'eslint', 'prettier', 'jest', 'mocha', 'chai', 'sinon',
|
|
7
|
+
'moment', 'dayjs', 'date-fns', 'uuid', 'dotenv', 'cors', 'body-parser',
|
|
8
|
+
'cookie-parser', 'jsonwebtoken', 'bcrypt', 'passport', 'mongoose', 'sequelize',
|
|
9
|
+
'prisma', '@prisma/client', 'pg', 'mysql2', 'redis', 'ioredis', 'socket.io',
|
|
10
|
+
'ws', 'graphql', 'apollo-server', '@apollo/client', 'tailwindcss', 'postcss',
|
|
11
|
+
'autoprefixer', 'sass', 'styled-components', '@emotion/react', 'framer-motion',
|
|
12
|
+
'three', 'd3', 'chart.js', '@types/node', '@types/react', 'zod', 'yup', 'joi',
|
|
13
|
+
'ajv', 'commander', 'inquirer', 'chalk', 'ora', 'got', 'node-fetch', 'cheerio',
|
|
14
|
+
'puppeteer', 'playwright', 'sharp', 'multer', 'formidable', 'nodemailer', 'bull',
|
|
15
|
+
'stripe', '@stripe/stripe-js', 'firebase', '@firebase/app', 'supabase',
|
|
16
|
+
'@supabase/supabase-js', 'aws-sdk', '@aws-sdk/client-s3', 'openai',
|
|
17
|
+
'@anthropic-ai/sdk', 'langchain', '@langchain/core', 'vue', 'svelte', 'angular',
|
|
18
|
+
'@angular/core', 'nuxt', 'vite', 'esbuild', 'rollup', 'turbo', 'pnpm', 'bun',
|
|
19
|
+
];
|
|
20
|
+
const IMPORT_PATTERNS = [
|
|
21
|
+
/import\s+(?:[\s\S]*?\s+from\s+)?['"]([^'"]+)['"]/g,
|
|
22
|
+
/require\s*\(\s*['"]([^'"]+)['"]\s*\)/g,
|
|
23
|
+
/import\s*\(\s*['"]([^'"]+)['"]\s*\)/g,
|
|
24
|
+
];
|
|
25
|
+
function extractPackageName(specifier) {
|
|
26
|
+
if (specifier.startsWith('.') || specifier.startsWith('/'))
|
|
27
|
+
return null;
|
|
28
|
+
if (specifier.startsWith('@')) {
|
|
29
|
+
const parts = specifier.split('/');
|
|
30
|
+
if (parts.length < 2)
|
|
31
|
+
return null;
|
|
32
|
+
return `${parts[0]}/${parts[1]}`;
|
|
33
|
+
}
|
|
34
|
+
return specifier.split('/')[0];
|
|
35
|
+
}
|
|
36
|
+
export function extractImportedPackages(content) {
|
|
37
|
+
const packages = new Set();
|
|
38
|
+
for (const pattern of IMPORT_PATTERNS) {
|
|
39
|
+
pattern.lastIndex = 0;
|
|
40
|
+
let match;
|
|
41
|
+
while ((match = pattern.exec(content)) !== null) {
|
|
42
|
+
const pkg = extractPackageName(match[1]);
|
|
43
|
+
if (pkg)
|
|
44
|
+
packages.add(pkg);
|
|
45
|
+
}
|
|
46
|
+
}
|
|
47
|
+
return [...packages];
|
|
48
|
+
}
|
|
49
|
+
function levenshtein(a, b) {
|
|
50
|
+
const m = a.length;
|
|
51
|
+
const n = b.length;
|
|
52
|
+
const dp = Array.from({ length: m + 1 }, () => Array(n + 1).fill(0));
|
|
53
|
+
for (let i = 0; i <= m; i++)
|
|
54
|
+
dp[i][0] = i;
|
|
55
|
+
for (let j = 0; j <= n; j++)
|
|
56
|
+
dp[0][j] = j;
|
|
57
|
+
for (let i = 1; i <= m; i++) {
|
|
58
|
+
for (let j = 1; j <= n; j++) {
|
|
59
|
+
const cost = a[i - 1] === b[j - 1] ? 0 : 1;
|
|
60
|
+
dp[i][j] = Math.min(dp[i - 1][j] + 1, dp[i][j - 1] + 1, dp[i - 1][j - 1] + cost);
|
|
61
|
+
}
|
|
62
|
+
}
|
|
63
|
+
return dp[m][n];
|
|
64
|
+
}
|
|
65
|
+
function findTyposquatMatches(pkg) {
|
|
66
|
+
const matches = [];
|
|
67
|
+
for (const top of TOP_PACKAGES) {
|
|
68
|
+
if (top === pkg)
|
|
69
|
+
continue;
|
|
70
|
+
const dist = levenshtein(pkg, top);
|
|
71
|
+
if (dist > 0 && dist <= 2)
|
|
72
|
+
matches.push(top);
|
|
73
|
+
}
|
|
74
|
+
return matches;
|
|
75
|
+
}
|
|
76
|
+
function getLineNumber(content, index) {
|
|
77
|
+
return content.slice(0, index).split('\n').length;
|
|
78
|
+
}
|
|
79
|
+
function loadCache(projectRoot) {
|
|
80
|
+
const cachePath = join(projectRoot, '.corpus', 'npm-cache.json');
|
|
81
|
+
if (!existsSync(cachePath))
|
|
82
|
+
return {};
|
|
83
|
+
try {
|
|
84
|
+
return JSON.parse(readFileSync(cachePath, 'utf-8'));
|
|
85
|
+
}
|
|
86
|
+
catch {
|
|
87
|
+
return {};
|
|
88
|
+
}
|
|
89
|
+
}
|
|
90
|
+
function saveCache(projectRoot, cache) {
|
|
91
|
+
const cacheDir = join(projectRoot, '.corpus');
|
|
92
|
+
if (!existsSync(cacheDir))
|
|
93
|
+
mkdirSync(cacheDir, { recursive: true });
|
|
94
|
+
writeFileSync(join(cacheDir, 'npm-cache.json'), JSON.stringify(cache, null, 2));
|
|
95
|
+
}
|
|
96
|
+
function isCacheValid(entry) {
|
|
97
|
+
return Date.now() - new Date(entry.checkedAt).getTime() < CACHE_TTL_MS;
|
|
98
|
+
}
|
|
99
|
+
function loadKnownPackages(projectRoot) {
|
|
100
|
+
const pkgPath = join(projectRoot, 'package.json');
|
|
101
|
+
if (!existsSync(pkgPath))
|
|
102
|
+
return new Set();
|
|
103
|
+
try {
|
|
104
|
+
const pkg = JSON.parse(readFileSync(pkgPath, 'utf-8'));
|
|
105
|
+
const deps = Object.keys(pkg.dependencies ?? {});
|
|
106
|
+
const devDeps = Object.keys(pkg.devDependencies ?? {});
|
|
107
|
+
return new Set([...deps, ...devDeps]);
|
|
108
|
+
}
|
|
109
|
+
catch {
|
|
110
|
+
return new Set();
|
|
111
|
+
}
|
|
112
|
+
}
|
|
113
|
+
async function checkNpmExists(pkg) {
|
|
114
|
+
try {
|
|
115
|
+
const res = await fetch(`https://registry.npmjs.org/${encodeURIComponent(pkg)}`, {
|
|
116
|
+
method: 'HEAD',
|
|
117
|
+
});
|
|
118
|
+
return res.ok;
|
|
119
|
+
}
|
|
120
|
+
catch {
|
|
121
|
+
return true;
|
|
122
|
+
}
|
|
123
|
+
}
|
|
124
|
+
function delay(ms) {
|
|
125
|
+
return new Promise((resolve) => setTimeout(resolve, ms));
|
|
126
|
+
}
|
|
127
|
+
export async function checkDependencies(content, filepath, options) {
|
|
128
|
+
const findings = [];
|
|
129
|
+
const projectRoot = options?.projectRoot ?? process.cwd();
|
|
130
|
+
const knownPackages = options?.knownPackages ?? loadKnownPackages(projectRoot);
|
|
131
|
+
const cache = loadCache(projectRoot);
|
|
132
|
+
const packageLocations = new Map();
|
|
133
|
+
for (const pattern of IMPORT_PATTERNS) {
|
|
134
|
+
pattern.lastIndex = 0;
|
|
135
|
+
let match;
|
|
136
|
+
while ((match = pattern.exec(content)) !== null) {
|
|
137
|
+
const pkg = extractPackageName(match[1]);
|
|
138
|
+
if (!pkg)
|
|
139
|
+
continue;
|
|
140
|
+
const line = getLineNumber(content, match.index);
|
|
141
|
+
if (!packageLocations.has(pkg))
|
|
142
|
+
packageLocations.set(pkg, []);
|
|
143
|
+
packageLocations.get(pkg).push(line);
|
|
144
|
+
}
|
|
145
|
+
}
|
|
146
|
+
const packagesToCheck = [];
|
|
147
|
+
for (const pkg of packageLocations.keys()) {
|
|
148
|
+
if (knownPackages.has(pkg))
|
|
149
|
+
continue;
|
|
150
|
+
if (pkg.startsWith('node:'))
|
|
151
|
+
continue;
|
|
152
|
+
packagesToCheck.push(pkg);
|
|
153
|
+
}
|
|
154
|
+
let requestCount = 0;
|
|
155
|
+
for (const pkg of packagesToCheck) {
|
|
156
|
+
const lines = packageLocations.get(pkg);
|
|
157
|
+
const line = lines[0];
|
|
158
|
+
const typosquatMatches = findTyposquatMatches(pkg);
|
|
159
|
+
let exists;
|
|
160
|
+
const cached = cache[pkg];
|
|
161
|
+
if (cached && isCacheValid(cached)) {
|
|
162
|
+
exists = cached.exists;
|
|
163
|
+
}
|
|
164
|
+
else {
|
|
165
|
+
if (requestCount > 0 && requestCount % 10 === 0) {
|
|
166
|
+
await delay(1000);
|
|
167
|
+
}
|
|
168
|
+
exists = await checkNpmExists(pkg);
|
|
169
|
+
cache[pkg] = { exists, checkedAt: new Date().toISOString() };
|
|
170
|
+
requestCount++;
|
|
171
|
+
}
|
|
172
|
+
if (!exists) {
|
|
173
|
+
const finding = {
|
|
174
|
+
severity: 'CRITICAL',
|
|
175
|
+
package: pkg,
|
|
176
|
+
file: filepath,
|
|
177
|
+
line,
|
|
178
|
+
reason: 'nonexistent',
|
|
179
|
+
suggestion: `Package "${pkg}" does not exist on npm. This may be an AI-hallucinated dependency.`,
|
|
180
|
+
};
|
|
181
|
+
if (typosquatMatches.length > 0) {
|
|
182
|
+
finding.similarPackages = typosquatMatches;
|
|
183
|
+
finding.suggestion += ` Did you mean: ${typosquatMatches.join(', ')}?`;
|
|
184
|
+
}
|
|
185
|
+
findings.push(finding);
|
|
186
|
+
}
|
|
187
|
+
else if (typosquatMatches.length > 0) {
|
|
188
|
+
findings.push({
|
|
189
|
+
severity: 'WARNING',
|
|
190
|
+
package: pkg,
|
|
191
|
+
file: filepath,
|
|
192
|
+
line,
|
|
193
|
+
reason: 'typosquat',
|
|
194
|
+
suggestion: `Package "${pkg}" exists but is very similar to: ${typosquatMatches.join(', ')}. Verify this is the intended package.`,
|
|
195
|
+
similarPackages: typosquatMatches,
|
|
196
|
+
});
|
|
197
|
+
}
|
|
198
|
+
}
|
|
199
|
+
saveCache(projectRoot, cache);
|
|
200
|
+
const severityOrder = { CRITICAL: 0, WARNING: 1, INFO: 2 };
|
|
201
|
+
findings.sort((a, b) => severityOrder[a.severity] - severityOrder[b.severity]);
|
|
202
|
+
return findings;
|
|
203
|
+
}
|
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
export interface PiiMatch {
|
|
2
|
+
type: 'EMAIL' | 'PHONE' | 'SSN' | 'CREDIT_CARD' | 'IP_ADDRESS' | 'CONTEXT_PII';
|
|
3
|
+
value: string;
|
|
4
|
+
redacted: string;
|
|
5
|
+
}
|
|
6
|
+
export interface ExfiltrationScanResult {
|
|
7
|
+
hasPii: boolean;
|
|
8
|
+
matches: PiiMatch[];
|
|
9
|
+
redactedPayload: string;
|
|
10
|
+
}
|
|
11
|
+
/**
|
|
12
|
+
* Scans a payload string for PII and returns matches + a redacted version.
|
|
13
|
+
* Optionally checks for custom context fields (user names, etc.).
|
|
14
|
+
*/
|
|
15
|
+
export declare function scanPayload(payload: string, contextFields?: string[]): ExfiltrationScanResult;
|
|
16
|
+
/**
|
|
17
|
+
* Convenience: redact a payload in-place and return just the cleaned string.
|
|
18
|
+
*/
|
|
19
|
+
export declare function redactPayload(payload: string, contextFields?: string[]): string;
|
|
@@ -0,0 +1,49 @@
|
|
|
1
|
+
const PATTERNS = [
|
|
2
|
+
{ type: 'EMAIL', regex: /[a-zA-Z0-9._%+-]+@[a-zA-Z0-9.-]+\.[a-zA-Z]{2,}/g, redacted: '[EMAIL_REDACTED]' },
|
|
3
|
+
{ type: 'PHONE', regex: /(\+?1?\s?)?(\(?\d{3}\)?[\s.-]?)?\d{3}[\s.-]?\d{4}/g, redacted: '[PHONE_REDACTED]' },
|
|
4
|
+
{ type: 'SSN', regex: /\b\d{3}-\d{2}-\d{4}\b/g, redacted: '[SSN_REDACTED]' },
|
|
5
|
+
{ type: 'CREDIT_CARD', regex: /\b\d{4}[\s-]?\d{4}[\s-]?\d{4}[\s-]?\d{4}\b/g, redacted: '[CARD_REDACTED]' },
|
|
6
|
+
{ type: 'IP_ADDRESS', regex: /\b\d{1,3}\.\d{1,3}\.\d{1,3}\.\d{1,3}\b/g, redacted: '[IP_REDACTED]' },
|
|
7
|
+
];
|
|
8
|
+
/**
|
|
9
|
+
* Scans a payload string for PII and returns matches + a redacted version.
|
|
10
|
+
* Optionally checks for custom context fields (user names, etc.).
|
|
11
|
+
*/
|
|
12
|
+
export function scanPayload(payload, contextFields) {
|
|
13
|
+
const matches = [];
|
|
14
|
+
let redacted = payload;
|
|
15
|
+
for (const { type, regex, redacted: redactedStr } of PATTERNS) {
|
|
16
|
+
// Reset regex state
|
|
17
|
+
regex.lastIndex = 0;
|
|
18
|
+
let match;
|
|
19
|
+
while ((match = regex.exec(payload)) !== null) {
|
|
20
|
+
const value = match[0];
|
|
21
|
+
if (type === 'PHONE' && value.trim().length < 10)
|
|
22
|
+
continue;
|
|
23
|
+
matches.push({ type, value, redacted: redactedStr });
|
|
24
|
+
redacted = redacted.replace(value, redactedStr);
|
|
25
|
+
}
|
|
26
|
+
}
|
|
27
|
+
if (contextFields) {
|
|
28
|
+
for (const field of contextFields) {
|
|
29
|
+
if (field.length > 2 && payload.toLowerCase().includes(field.toLowerCase())) {
|
|
30
|
+
matches.push({ type: 'CONTEXT_PII', value: field, redacted: '[PII_REDACTED]' });
|
|
31
|
+
redacted = redacted.replace(new RegExp(escapeRegex(field), 'gi'), '[PII_REDACTED]');
|
|
32
|
+
}
|
|
33
|
+
}
|
|
34
|
+
}
|
|
35
|
+
return {
|
|
36
|
+
hasPii: matches.length > 0,
|
|
37
|
+
matches,
|
|
38
|
+
redactedPayload: redacted,
|
|
39
|
+
};
|
|
40
|
+
}
|
|
41
|
+
function escapeRegex(s) {
|
|
42
|
+
return s.replace(/[.*+?^${}()|[\]\\]/g, '\\$&');
|
|
43
|
+
}
|
|
44
|
+
/**
|
|
45
|
+
* Convenience: redact a payload in-place and return just the cleaned string.
|
|
46
|
+
*/
|
|
47
|
+
export function redactPayload(payload, contextFields) {
|
|
48
|
+
return scanPayload(payload, contextFields).redactedPayload;
|
|
49
|
+
}
|
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
export { scanForInjection, type InjectionScanResult, type InjectionSeverity } from './injection-firewall.js';
|
|
2
|
+
export { scanPayload, redactPayload, type ExfiltrationScanResult, type PiiMatch } from './exfiltration-guard.js';
|
|
3
|
+
export { scanMemoryChunk, scanMemoryChunks, type PoisoningScanResult, type PoisoningSeverity } from './context-poisoning.js';
|
|
4
|
+
export { checkActionScope, type ScopeCheckResult, type ScopeMap } from './scope-enforcer.js';
|
|
5
|
+
export { checkContextIsolation, type IsolationCheckResult } from './cross-user-firewall.js';
|
|
6
|
+
export { auditCalibration, type CalibrationEntry, type CalibrationResult, type CalibrationReport } from './confidence-calibrator.js';
|
|
7
|
+
export { checkUndoIntegrity, type UndoCheckResult, type UndoCapability } from './undo-integrity.js';
|
|
8
|
+
export { checkSessionIntegrity, type SessionEvent, type SessionCheckResult } from './session-hijack.js';
|
|
9
|
+
export { detectSecrets, scanFiles, type SecretFinding, type SecretSeverity } from './secret-detector.js';
|
|
10
|
+
export { checkCodeSafety, type SafetyFinding, type SafetySeverity } from './code-safety.js';
|
|
11
|
+
export { computeFileTrust, computeCodebaseTrust, type FileTrustResult, type CodebaseTrustResult, type TrustFinding } from './trust-score.js';
|
|
12
|
+
export { checkDependencies, extractImportedPackages, type DependencyFinding } from './dependency-checker.js';
|
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
export { scanForInjection } from './injection-firewall.js';
|
|
2
|
+
export { scanPayload, redactPayload } from './exfiltration-guard.js';
|
|
3
|
+
export { scanMemoryChunk, scanMemoryChunks } from './context-poisoning.js';
|
|
4
|
+
export { checkActionScope } from './scope-enforcer.js';
|
|
5
|
+
export { checkContextIsolation } from './cross-user-firewall.js';
|
|
6
|
+
export { auditCalibration } from './confidence-calibrator.js';
|
|
7
|
+
export { checkUndoIntegrity } from './undo-integrity.js';
|
|
8
|
+
export { checkSessionIntegrity } from './session-hijack.js';
|
|
9
|
+
export { detectSecrets, scanFiles } from './secret-detector.js';
|
|
10
|
+
export { checkCodeSafety } from './code-safety.js';
|
|
11
|
+
export { computeFileTrust, computeCodebaseTrust } from './trust-score.js';
|
|
12
|
+
export { checkDependencies, extractImportedPackages } from './dependency-checker.js';
|
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
export type InjectionSeverity = 'CLEAN' | 'SUSPICIOUS' | 'BLOCK';
|
|
2
|
+
export interface InjectionScanResult {
|
|
3
|
+
severity: InjectionSeverity;
|
|
4
|
+
pattern: string | null;
|
|
5
|
+
category: 'instruction_override' | 'role_escalation' | 'exfiltration' | 'encoded_payload' | null;
|
|
6
|
+
message: string;
|
|
7
|
+
}
|
|
8
|
+
/**
|
|
9
|
+
* Scans content for prompt injection patterns.
|
|
10
|
+
* Returns immediately on first match (fast path).
|
|
11
|
+
*/
|
|
12
|
+
export declare function scanForInjection(content: string, source?: string): InjectionScanResult;
|
|
@@ -0,0 +1,71 @@
|
|
|
1
|
+
const INSTRUCTION_OVERRIDE = [
|
|
2
|
+
'ignore previous instructions', 'ignore all previous', 'disregard above',
|
|
3
|
+
'forget everything', 'new task:', 'new instructions:', 'you are now',
|
|
4
|
+
'your new role', 'override:', 'ignore the above', 'do not follow',
|
|
5
|
+
'stop following', 'from now on', 'reset your instructions',
|
|
6
|
+
'pretend you are', 'act as if',
|
|
7
|
+
];
|
|
8
|
+
const ROLE_ESCALATION = [
|
|
9
|
+
'you are an admin', 'you are root', 'you have full access',
|
|
10
|
+
'system prompt', 'developer mode', 'sudo mode', 'unrestricted mode',
|
|
11
|
+
'jailbreak', 'god mode', 'bypass safety', 'bypass filters',
|
|
12
|
+
'no restrictions', 'without limitations', 'act as an unrestricted',
|
|
13
|
+
'dan mode',
|
|
14
|
+
];
|
|
15
|
+
const EXFILTRATION = [
|
|
16
|
+
'send to', 'forward to', 'email to', 'post to', 'upload to',
|
|
17
|
+
'exfiltrate', 'send all data', 'forward all emails', 'share with',
|
|
18
|
+
'transmit to', 'copy to external', 'leak the',
|
|
19
|
+
];
|
|
20
|
+
/**
|
|
21
|
+
* Scans content for prompt injection patterns.
|
|
22
|
+
* Returns immediately on first match (fast path).
|
|
23
|
+
*/
|
|
24
|
+
export function scanForInjection(content, source) {
|
|
25
|
+
const lower = content.toLowerCase();
|
|
26
|
+
const src = source ?? 'unknown';
|
|
27
|
+
for (const pattern of INSTRUCTION_OVERRIDE) {
|
|
28
|
+
if (lower.includes(pattern)) {
|
|
29
|
+
return {
|
|
30
|
+
severity: 'BLOCK',
|
|
31
|
+
pattern,
|
|
32
|
+
category: 'instruction_override',
|
|
33
|
+
message: `Injection detected: instruction override '${pattern}' in content from '${src}'`,
|
|
34
|
+
};
|
|
35
|
+
}
|
|
36
|
+
}
|
|
37
|
+
for (const pattern of ROLE_ESCALATION) {
|
|
38
|
+
if (lower.includes(pattern)) {
|
|
39
|
+
return {
|
|
40
|
+
severity: 'BLOCK',
|
|
41
|
+
pattern,
|
|
42
|
+
category: 'role_escalation',
|
|
43
|
+
message: `Injection detected: role escalation '${pattern}' in content from '${src}'`,
|
|
44
|
+
};
|
|
45
|
+
}
|
|
46
|
+
}
|
|
47
|
+
for (const pattern of EXFILTRATION) {
|
|
48
|
+
if (lower.includes(pattern)) {
|
|
49
|
+
return {
|
|
50
|
+
severity: 'SUSPICIOUS',
|
|
51
|
+
pattern,
|
|
52
|
+
category: 'exfiltration',
|
|
53
|
+
message: `Suspicious: possible exfiltration pattern '${pattern}' in content from '${src}'`,
|
|
54
|
+
};
|
|
55
|
+
}
|
|
56
|
+
}
|
|
57
|
+
// Check for encoded payloads (high density of alphanumeric chars)
|
|
58
|
+
if (content.length > 200) {
|
|
59
|
+
const alphaCount = [...content].filter((c) => /[a-zA-Z0-9/+=]/.test(c)).length;
|
|
60
|
+
const ratio = alphaCount / content.length;
|
|
61
|
+
if (ratio > 0.95) {
|
|
62
|
+
return {
|
|
63
|
+
severity: 'SUSPICIOUS',
|
|
64
|
+
pattern: `encoded_payload (density: ${ratio.toFixed(2)})`,
|
|
65
|
+
category: 'encoded_payload',
|
|
66
|
+
message: `Suspicious: possible encoded payload from '${src}'`,
|
|
67
|
+
};
|
|
68
|
+
}
|
|
69
|
+
}
|
|
70
|
+
return { severity: 'CLEAN', pattern: null, category: null, message: '' };
|
|
71
|
+
}
|
|
@@ -0,0 +1,10 @@
|
|
|
1
|
+
export interface ScopeCheckResult {
|
|
2
|
+
allowed: boolean;
|
|
3
|
+
message: string;
|
|
4
|
+
}
|
|
5
|
+
export type ScopeMap = Record<string, string[]>;
|
|
6
|
+
/**
|
|
7
|
+
* Checks if an action on an integration is within declared scope.
|
|
8
|
+
* If no scope map is provided, everything passes.
|
|
9
|
+
*/
|
|
10
|
+
export declare function checkActionScope(integration: string, action: string, scopeMap: ScopeMap): ScopeCheckResult;
|
|
@@ -0,0 +1,30 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Checks if an action on an integration is within declared scope.
|
|
3
|
+
* If no scope map is provided, everything passes.
|
|
4
|
+
*/
|
|
5
|
+
export function checkActionScope(integration, action, scopeMap) {
|
|
6
|
+
if (!scopeMap || Object.keys(scopeMap).length === 0) {
|
|
7
|
+
return { allowed: true, message: '' };
|
|
8
|
+
}
|
|
9
|
+
const actLower = action.toLowerCase();
|
|
10
|
+
// Normalize scope map keys to lowercase for case-insensitive lookup
|
|
11
|
+
const normalizedMap = {};
|
|
12
|
+
for (const key of Object.keys(scopeMap)) {
|
|
13
|
+
normalizedMap[key.toLowerCase()] = scopeMap[key];
|
|
14
|
+
}
|
|
15
|
+
const intLower = integration.toLowerCase();
|
|
16
|
+
if (!(intLower in normalizedMap)) {
|
|
17
|
+
return {
|
|
18
|
+
allowed: false,
|
|
19
|
+
message: `Integration '${integration}' is not in scope. Allowed: ${Object.keys(scopeMap).join(', ')}`,
|
|
20
|
+
};
|
|
21
|
+
}
|
|
22
|
+
const allowedActions = normalizedMap[intLower].map((a) => a.toLowerCase());
|
|
23
|
+
if (!allowedActions.includes(actLower)) {
|
|
24
|
+
return {
|
|
25
|
+
allowed: false,
|
|
26
|
+
message: `Action '${action}' is not allowed on '${integration}'. Allowed: ${normalizedMap[intLower].join(', ')}`,
|
|
27
|
+
};
|
|
28
|
+
}
|
|
29
|
+
return { allowed: true, message: '' };
|
|
30
|
+
}
|
|
@@ -0,0 +1,34 @@
|
|
|
1
|
+
export type SecretSeverity = 'CRITICAL' | 'WARNING' | 'INFO';
|
|
2
|
+
export interface SecretFinding {
|
|
3
|
+
severity: SecretSeverity;
|
|
4
|
+
type: string;
|
|
5
|
+
value: string;
|
|
6
|
+
redacted: string;
|
|
7
|
+
line: number;
|
|
8
|
+
column: number;
|
|
9
|
+
file: string;
|
|
10
|
+
message: string;
|
|
11
|
+
isAiPattern: boolean;
|
|
12
|
+
}
|
|
13
|
+
/**
|
|
14
|
+
* Scans file content for secrets and AI-specific security patterns.
|
|
15
|
+
* Returns findings sorted by severity (CRITICAL first).
|
|
16
|
+
*/
|
|
17
|
+
export declare function detectSecrets(content: string, filepath: string, options?: {
|
|
18
|
+
skipTests?: boolean;
|
|
19
|
+
skipPlaceholders?: boolean;
|
|
20
|
+
}): SecretFinding[];
|
|
21
|
+
/**
|
|
22
|
+
* Scans multiple files and returns aggregate results.
|
|
23
|
+
*/
|
|
24
|
+
export declare function scanFiles(files: {
|
|
25
|
+
path: string;
|
|
26
|
+
content: string;
|
|
27
|
+
}[], options?: {
|
|
28
|
+
skipTests?: boolean;
|
|
29
|
+
skipPlaceholders?: boolean;
|
|
30
|
+
}): {
|
|
31
|
+
findings: SecretFinding[];
|
|
32
|
+
scannedFiles: number;
|
|
33
|
+
scanTimeMs: number;
|
|
34
|
+
};
|