praxis-sec 1.0.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/LICENSE +21 -0
- package/README.md +170 -0
- package/ai-defense/cost-protection.md +292 -0
- package/ai-defense/llm-security-checklist.md +324 -0
- package/ai-defense/prompt-injection-patterns.js +283 -0
- package/ai-defense/system-prompt-armor.md +327 -0
- package/checklists/launch-day.md +168 -0
- package/cli/agents/abom-generator.js +225 -0
- package/cli/agents/agent-attestation-agent.js +318 -0
- package/cli/agents/agent-config-scanner.js +787 -0
- package/cli/agents/agent-telemetry-agent.js +415 -0
- package/cli/agents/agentic-security-agent.js +296 -0
- package/cli/agents/agentic-supply-chain-agent.js +463 -0
- package/cli/agents/ai-infra-inventory-agent.js +449 -0
- package/cli/agents/api-fuzzer.js +345 -0
- package/cli/agents/auth-bypass-agent.js +348 -0
- package/cli/agents/base-agent.js +280 -0
- package/cli/agents/cicd-scanner.js +300 -0
- package/cli/agents/config-auditor.js +757 -0
- package/cli/agents/deep-analyzer.js +776 -0
- package/cli/agents/endpoint-agent-abuse-agent.js +404 -0
- package/cli/agents/exception-handler-agent.js +187 -0
- package/cli/agents/git-history-scanner.js +169 -0
- package/cli/agents/governance-audits.js +138 -0
- package/cli/agents/hermes-security-agent.js +536 -0
- package/cli/agents/html-reporter.js +1125 -0
- package/cli/agents/index.js +147 -0
- package/cli/agents/injection-tester.js +502 -0
- package/cli/agents/legal-risk-agent.js +328 -0
- package/cli/agents/llm-redteam.js +199 -0
- package/cli/agents/managed-agent-scanner.js +333 -0
- package/cli/agents/mcp-security-agent.js +588 -0
- package/cli/agents/memory-poisoning-agent.js +305 -0
- package/cli/agents/mobile-scanner.js +231 -0
- package/cli/agents/model-file-scanner.js +259 -0
- package/cli/agents/orchestrator.js +355 -0
- package/cli/agents/pii-compliance-agent.js +301 -0
- package/cli/agents/policy-engine.js +229 -0
- package/cli/agents/prompt-injection-prober.js +224 -0
- package/cli/agents/rag-security-agent.js +204 -0
- package/cli/agents/recon-agent.js +207 -0
- package/cli/agents/sbom-generator.js +265 -0
- package/cli/agents/scoring-engine.js +273 -0
- package/cli/agents/ssrf-prober.js +130 -0
- package/cli/agents/stateful-watcher.js +238 -0
- package/cli/agents/supabase-rls-agent.js +154 -0
- package/cli/agents/supply-chain-agent.js +857 -0
- package/cli/agents/swarm-orchestrator.js +200 -0
- package/cli/agents/verifier-agent.js +303 -0
- package/cli/agents/vibe-coding-agent.js +250 -0
- package/cli/bin/praxis.js +866 -0
- package/cli/commands/abom.js +73 -0
- package/cli/commands/agent-fix.js +1245 -0
- package/cli/commands/audit.js +1180 -0
- package/cli/commands/autofix.js +383 -0
- package/cli/commands/baseline.js +193 -0
- package/cli/commands/benchmark.js +327 -0
- package/cli/commands/checklist.js +223 -0
- package/cli/commands/ci.js +403 -0
- package/cli/commands/deps.js +516 -0
- package/cli/commands/diff.js +200 -0
- package/cli/commands/doctor.js +195 -0
- package/cli/commands/env-audit.js +349 -0
- package/cli/commands/fix.js +218 -0
- package/cli/commands/guard.js +396 -0
- package/cli/commands/hooks.js +278 -0
- package/cli/commands/init.js +514 -0
- package/cli/commands/legal.js +158 -0
- package/cli/commands/live-advisories.js +241 -0
- package/cli/commands/mcp.js +660 -0
- package/cli/commands/openclaw.js +386 -0
- package/cli/commands/red-team.js +350 -0
- package/cli/commands/redteam.js +78 -0
- package/cli/commands/remediate.js +797 -0
- package/cli/commands/rotate.js +768 -0
- package/cli/commands/rules.js +196 -0
- package/cli/commands/scan-mcp.js +534 -0
- package/cli/commands/scan-skill.js +588 -0
- package/cli/commands/scan-standard.js +251 -0
- package/cli/commands/scan.js +524 -0
- package/cli/commands/score.js +449 -0
- package/cli/commands/shell.js +514 -0
- package/cli/commands/team-report.js +398 -0
- package/cli/commands/undo.js +161 -0
- package/cli/commands/update-intel.js +126 -0
- package/cli/commands/vibe-check.js +276 -0
- package/cli/commands/watch.js +757 -0
- package/cli/commands/web.js +63 -0
- package/cli/core/ast/guardrail-detector.js +141 -0
- package/cli/core/ast/index.js +22 -0
- package/cli/core/ast/parser.js +676 -0
- package/cli/core/ast/scope-tree.js +287 -0
- package/cli/core/ast/taint-tracker.js +158 -0
- package/cli/core/branding.js +37 -0
- package/cli/core/env.js +38 -0
- package/cli/core/errors.js +61 -0
- package/cli/core/fs.js +62 -0
- package/cli/core/output/compliance.js +90 -0
- package/cli/core/output/html-theme.js +158 -0
- package/cli/core/output/index.js +57 -0
- package/cli/core/output/json.js +48 -0
- package/cli/core/output/sarif.js +240 -0
- package/cli/core/version.js +67 -0
- package/cli/core/web/jobs.js +183 -0
- package/cli/core/web/projects.js +146 -0
- package/cli/core/web/server.js +439 -0
- package/cli/data/atlas-knowledge.json +5640 -0
- package/cli/data/eaa-catalog.json +39 -0
- package/cli/data/known-mcps.json +26 -0
- package/cli/data/probes/prompt-injection-corpus.json +271 -0
- package/cli/data/threat-intel.json +85 -0
- package/cli/data/threatpacks/latest.json +41 -0
- package/cli/hooks/patterns.js +313 -0
- package/cli/hooks/post-tool-use.js +140 -0
- package/cli/hooks/pre-tool-use.js +186 -0
- package/cli/index.js +90 -0
- package/cli/providers/llm-provider.js +766 -0
- package/cli/utils/autofix-rules.js +74 -0
- package/cli/utils/cache-manager.js +310 -0
- package/cli/utils/compliance-map.js +191 -0
- package/cli/utils/entropy.js +132 -0
- package/cli/utils/fix-ledger.js +127 -0
- package/cli/utils/hermes-tool-registry.js +252 -0
- package/cli/utils/intel/cache.js +61 -0
- package/cli/utils/intel/http.js +88 -0
- package/cli/utils/intel/index.js +235 -0
- package/cli/utils/intel/merge.js +229 -0
- package/cli/utils/intel/sources/epss.js +54 -0
- package/cli/utils/intel/sources/ghsa.js +81 -0
- package/cli/utils/intel/sources/gitguardian.js +40 -0
- package/cli/utils/intel/sources/gitleaks.js +101 -0
- package/cli/utils/intel/sources/kev.js +38 -0
- package/cli/utils/intel/sources/nvd.js +84 -0
- package/cli/utils/intel/sources/osv.js +132 -0
- package/cli/utils/intel/sources/phylum.js +44 -0
- package/cli/utils/intel/sources/snyk.js +46 -0
- package/cli/utils/intel/sources/socket.js +69 -0
- package/cli/utils/intel/sources/sonatype.js +84 -0
- package/cli/utils/intel/sources/threatpack.js +69 -0
- package/cli/utils/mcp-trust.js +60 -0
- package/cli/utils/output.js +251 -0
- package/cli/utils/patterns.js +1130 -0
- package/cli/utils/pdf-generator.js +94 -0
- package/cli/utils/plugin-loader.js +364 -0
- package/cli/utils/rule-import.js +228 -0
- package/cli/utils/rule-registry.js +426 -0
- package/cli/utils/scan-fingerprint.js +109 -0
- package/cli/utils/scan-playbook.js +312 -0
- package/cli/utils/score-history.js +119 -0
- package/cli/utils/secrets-verifier.js +247 -0
- package/cli/utils/security-memory.js +296 -0
- package/cli/utils/standards/atlas-knowledge.js +87 -0
- package/cli/utils/standards/index.js +127 -0
- package/cli/utils/standards/sources/avid.js +45 -0
- package/cli/utils/standards/sources/eu-ai-act.js +89 -0
- package/cli/utils/standards/sources/google-saif.js +39 -0
- package/cli/utils/standards/sources/iso-42001.js +94 -0
- package/cli/utils/standards/sources/mitre-atlas.js +54 -0
- package/cli/utils/standards/sources/nist-ai-600-1.js +45 -0
- package/cli/utils/standards/sources/owasp-llm.js +45 -0
- package/cli/utils/standards/sources/owasp-ml.js +45 -0
- package/cli/utils/threat-intel.js +265 -0
- package/configs/firebase/firestore-rules.txt +215 -0
- package/configs/firebase/security-checklist.md +236 -0
- package/configs/firebase/storage-rules.txt +206 -0
- package/configs/gitignore-template +258 -0
- package/configs/nextjs-security-headers.js +220 -0
- package/configs/praxisignore-template +50 -0
- package/configs/supabase/secure-client.ts +225 -0
- package/configs/supabase/security-checklist.md +278 -0
- package/docs/THIRD_PARTY_NOTICES.md +26 -0
- package/docs/THREAT_INTEL.md +292 -0
- package/docs/USAGE.md +1205 -0
- package/docs/design/WEB-UI.md +82 -0
- package/package.json +71 -0
- package/scripts/check-determinism.mjs +119 -0
- package/snippets/README.md +122 -0
- package/snippets/api-security/api-security-checklist.md +412 -0
- package/snippets/api-security/cors-config.ts +322 -0
- package/snippets/api-security/input-validation.ts +430 -0
- package/snippets/auth/jwt-checklist.md +322 -0
- package/snippets/rate-limiting/nextjs-middleware.ts +211 -0
- package/snippets/rate-limiting/upstash-ratelimit.ts +229 -0
|
@@ -0,0 +1,224 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Prompt Injection Prober
|
|
3
|
+
* ========================
|
|
4
|
+
*
|
|
5
|
+
* Loads a probe corpus from cli/data/probes/prompt-injection-corpus.json and
|
|
6
|
+
* scans source code for static-detectable signals that match each probe.
|
|
7
|
+
*
|
|
8
|
+
* The corpus replaces hardcoded patterns: new probes are added by editing
|
|
9
|
+
* the JSON file. Each probe carries `tags` (e.g. ['LLM01', 'AML.T0051'])
|
|
10
|
+
* which feed into the standards registry automatically — once the agent
|
|
11
|
+
* tags a finding with the probe IDs, the per-finding `standards` field is
|
|
12
|
+
* populated by the ScoringEngine.
|
|
13
|
+
*
|
|
14
|
+
* Maps to: OWASP LLM01/05/06/07/08, MITRE ATLAS T0043/T0051/T0053/T0054/T0057/T0070.
|
|
15
|
+
*/
|
|
16
|
+
|
|
17
|
+
import fs from 'fs';
|
|
18
|
+
import path from 'path';
|
|
19
|
+
import os from 'os';
|
|
20
|
+
import { fileURLToPath } from 'url';
|
|
21
|
+
import { BaseAgent, createFinding } from './base-agent.js';
|
|
22
|
+
|
|
23
|
+
const __filename = fileURLToPath(import.meta.url);
|
|
24
|
+
const __dirname = path.dirname(__filename);
|
|
25
|
+
const CORPUS_PATH = path.resolve(__dirname, '..', 'data', 'probes', 'prompt-injection-corpus.json');
|
|
26
|
+
|
|
27
|
+
const SCAN_EXTS = new Set(['.js', '.jsx', '.mjs', '.cjs', '.ts', '.tsx', '.py', '.rb', '.go', '.java', '.rs', '.php']);
|
|
28
|
+
|
|
29
|
+
let _cachedCorpus = null;
|
|
30
|
+
|
|
31
|
+
/** Reads a JSON file, returning null rather than throwing. */
|
|
32
|
+
function readJson(file) {
|
|
33
|
+
try {
|
|
34
|
+
return JSON.parse(fs.readFileSync(file, 'utf-8'));
|
|
35
|
+
} catch {
|
|
36
|
+
return null;
|
|
37
|
+
}
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
const THREATPACK_SEED = path.join(path.dirname(CORPUS_PATH), '..', 'threatpacks', 'latest.json');
|
|
41
|
+
|
|
42
|
+
function loadCorpus() {
|
|
43
|
+
if (_cachedCorpus) return _cachedCorpus;
|
|
44
|
+
try {
|
|
45
|
+
const data = readJson(CORPUS_PATH) || {};
|
|
46
|
+
const categoryById = {};
|
|
47
|
+
for (const c of data.categories || []) categoryById[c.id] = c;
|
|
48
|
+
|
|
49
|
+
const decorate = (p) => {
|
|
50
|
+
const cat = categoryById[p.category] || {};
|
|
51
|
+
let regex;
|
|
52
|
+
try {
|
|
53
|
+
regex = compileProbeRegex(p.regex);
|
|
54
|
+
} catch {
|
|
55
|
+
regex = null;
|
|
56
|
+
}
|
|
57
|
+
return {
|
|
58
|
+
...p,
|
|
59
|
+
patternSource: p.regex,
|
|
60
|
+
regex,
|
|
61
|
+
categoryTitle: cat.title || p.category,
|
|
62
|
+
tags: cat.tags || [],
|
|
63
|
+
};
|
|
64
|
+
};
|
|
65
|
+
|
|
66
|
+
// 1. Bundled prompt-injection corpus.
|
|
67
|
+
const probes = (data.probes || []).map(decorate);
|
|
68
|
+
|
|
69
|
+
// 2. Bundled threat-pack seed — the detection floor for this release.
|
|
70
|
+
//
|
|
71
|
+
// The seed used to be consulted only via `praxis intel update`. Loading it here
|
|
72
|
+
// means the shipped attack-vector families work on a first run with no network,
|
|
73
|
+
// and it makes the release's own signatures authoritative (P-IMP-064).
|
|
74
|
+
const seed = readJson(THREATPACK_SEED);
|
|
75
|
+
const seedVersion = seed?.version || null;
|
|
76
|
+
for (const p of seed?.probes || []) probes.push(decorate(p));
|
|
77
|
+
|
|
78
|
+
// 3. Overlay the fetched intel feed, which may bring newer signatures.
|
|
79
|
+
//
|
|
80
|
+
// Version-gated per probe: a feed older than the bundled seed must not replace
|
|
81
|
+
// a probe the release already ships. A stale `threat-intel.json` holding
|
|
82
|
+
// threatpack 1.0.0 was reinstating the pre-fix TP-003 pattern
|
|
83
|
+
// (`when\s+combined\s+with`, no referent, no instruction noun) and reporting
|
|
84
|
+
// ordinary English as prompt injection. A feed newer than, or equal to, the
|
|
85
|
+
// seed still wins, so updates keep working.
|
|
86
|
+
let feedApplied = 0;
|
|
87
|
+
let feedRejected = 0;
|
|
88
|
+
const feed = readJson(path.join(os.homedir(), '.praxis', 'threat-intel.json'));
|
|
89
|
+
const pack = feed?.threatPack;
|
|
90
|
+
const feedVersion = pack?.version || null;
|
|
91
|
+
const feedIsOlder = seedVersion && feedVersion
|
|
92
|
+
&& compareVersions(feedVersion, seedVersion) < 0;
|
|
93
|
+
|
|
94
|
+
for (const p of pack?.probes || []) {
|
|
95
|
+
if (feedIsOlder) { feedRejected++; continue; }
|
|
96
|
+
const decorated = decorate(p);
|
|
97
|
+
const existing = probes.findIndex(x => x.id === p.id);
|
|
98
|
+
if (existing >= 0) probes[existing] = decorated;
|
|
99
|
+
else probes.push(decorated);
|
|
100
|
+
feedApplied++;
|
|
101
|
+
}
|
|
102
|
+
|
|
103
|
+
_cachedCorpus = {
|
|
104
|
+
version: data.version,
|
|
105
|
+
probes,
|
|
106
|
+
seedThreatPackVersion: seedVersion,
|
|
107
|
+
feedThreatPackVersion: feedVersion,
|
|
108
|
+
feedApplied,
|
|
109
|
+
feedRejected,
|
|
110
|
+
};
|
|
111
|
+
return _cachedCorpus;
|
|
112
|
+
} catch {
|
|
113
|
+
_cachedCorpus = { version: '0', probes: [], feedApplied: 0, feedRejected: 0 };
|
|
114
|
+
return _cachedCorpus;
|
|
115
|
+
}
|
|
116
|
+
}
|
|
117
|
+
|
|
118
|
+
/**
|
|
119
|
+
* Compares dotted version strings numerically. Returns <0, 0 or >0.
|
|
120
|
+
* Missing/invalid segments compare as 0, so `1.0` and `1.0.0` are equal.
|
|
121
|
+
*/
|
|
122
|
+
function compareVersions(a, b) {
|
|
123
|
+
const pa = String(a).split('.').map(n => parseInt(n, 10) || 0);
|
|
124
|
+
const pb = String(b).split('.').map(n => parseInt(n, 10) || 0);
|
|
125
|
+
const len = Math.max(pa.length, pb.length);
|
|
126
|
+
for (let i = 0; i < len; i++) {
|
|
127
|
+
const d = (pa[i] || 0) - (pb[i] || 0);
|
|
128
|
+
if (d !== 0) return d;
|
|
129
|
+
}
|
|
130
|
+
return 0;
|
|
131
|
+
}
|
|
132
|
+
|
|
133
|
+
function compileProbeRegex(pattern) {
|
|
134
|
+
let flags = 'g';
|
|
135
|
+
let body = pattern;
|
|
136
|
+
const m = body.match(/^\(\?([imsux]+)\)/);
|
|
137
|
+
if (m) {
|
|
138
|
+
for (const f of m[1]) if ('imsu'.includes(f) && !flags.includes(f)) flags += f;
|
|
139
|
+
body = body.slice(m[0].length);
|
|
140
|
+
}
|
|
141
|
+
// Scanner hardening: reject nested-quantifier constructs that risk
|
|
142
|
+
// catastrophic backtracking on adversarial input.
|
|
143
|
+
if (NESTED_QUANTIFIER.test(body)) {
|
|
144
|
+
throw new Error('ReDoS-unsafe probe regex (nested quantifiers)');
|
|
145
|
+
}
|
|
146
|
+
return new RegExp(body, flags);
|
|
147
|
+
}
|
|
148
|
+
|
|
149
|
+
const NESTED_QUANTIFIER = /\((?:[^()\\]|\\.)*[+*]\)[+*{]/;
|
|
150
|
+
|
|
151
|
+
export class PromptInjectionProber extends BaseAgent {
|
|
152
|
+
constructor() {
|
|
153
|
+
super(
|
|
154
|
+
'PromptInjectionProber',
|
|
155
|
+
'Probe-corpus-driven scan for prompt-injection patterns (OWASP LLM01, ATLAS T0043/T0051)',
|
|
156
|
+
'llm'
|
|
157
|
+
);
|
|
158
|
+
this._corpus = loadCorpus();
|
|
159
|
+
this.probeCount = this._corpus.probes.length;
|
|
160
|
+
}
|
|
161
|
+
|
|
162
|
+
shouldRun(recon) {
|
|
163
|
+
if (!recon) return true;
|
|
164
|
+
const langs = recon.languages instanceof Set ? [...recon.languages] : (recon.languages || []);
|
|
165
|
+
if (langs.some(l => ['javascript', 'typescript', 'python', 'ruby', 'go', 'java', 'rust', 'php'].includes(l))) {
|
|
166
|
+
return true;
|
|
167
|
+
}
|
|
168
|
+
return Boolean(recon.frameworks?.length || recon.apiRoutes?.length);
|
|
169
|
+
}
|
|
170
|
+
|
|
171
|
+
async analyze(context) {
|
|
172
|
+
const findings = [];
|
|
173
|
+
const probes = this._corpus.probes.filter(p => p.regex);
|
|
174
|
+
if (probes.length === 0) return findings;
|
|
175
|
+
|
|
176
|
+
const files = this.getFilesToScan(context).filter(f => SCAN_EXTS.has(path.extname(f).toLowerCase()));
|
|
177
|
+
|
|
178
|
+
for (const file of files) {
|
|
179
|
+
const content = this.readFile(file);
|
|
180
|
+
if (!content) continue;
|
|
181
|
+
const lines = content.split('\n');
|
|
182
|
+
|
|
183
|
+
for (const probe of probes) {
|
|
184
|
+
probe.regex.lastIndex = 0;
|
|
185
|
+
let match;
|
|
186
|
+
while ((match = probe.regex.exec(content)) !== null) {
|
|
187
|
+
const idx = match.index;
|
|
188
|
+
const before = content.slice(0, idx);
|
|
189
|
+
const lineNum = before.split('\n').length;
|
|
190
|
+
const lastNl = before.lastIndexOf('\n');
|
|
191
|
+
const column = lastNl === -1 ? idx + 1 : idx - lastNl;
|
|
192
|
+
const lineText = lines[lineNum - 1] || '';
|
|
193
|
+
if (this.isSuppressed(lineText)) continue;
|
|
194
|
+
|
|
195
|
+
const finding = createFinding({
|
|
196
|
+
file,
|
|
197
|
+
line: lineNum,
|
|
198
|
+
column,
|
|
199
|
+
severity: probe.severity || 'medium',
|
|
200
|
+
category: 'llm',
|
|
201
|
+
rule: `PROBE_${probe.id}`,
|
|
202
|
+
title: probe.title,
|
|
203
|
+
description: probe.description,
|
|
204
|
+
matched: match[0].slice(0, 160),
|
|
205
|
+
confidence: 'medium',
|
|
206
|
+
cwe: 'CWE-77',
|
|
207
|
+
owasp: 'ASI01',
|
|
208
|
+
fix: probe.fix || 'Sanitize untrusted input before LLM prompt construction.',
|
|
209
|
+
});
|
|
210
|
+
finding.probe = { id: probe.id, category: probe.category, tags: probe.tags };
|
|
211
|
+
findings.push(finding);
|
|
212
|
+
|
|
213
|
+
if (!probe.regex.global) break;
|
|
214
|
+
if (match.index === probe.regex.lastIndex) probe.regex.lastIndex++;
|
|
215
|
+
}
|
|
216
|
+
}
|
|
217
|
+
}
|
|
218
|
+
|
|
219
|
+
return findings;
|
|
220
|
+
}
|
|
221
|
+
}
|
|
222
|
+
|
|
223
|
+
export const _internals = { loadCorpus, compileProbeRegex, CORPUS_PATH };
|
|
224
|
+
export default PromptInjectionProber;
|
|
@@ -0,0 +1,204 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* RAG Security Agent
|
|
3
|
+
* ====================
|
|
4
|
+
*
|
|
5
|
+
* Detects security vulnerabilities in Retrieval-Augmented Generation
|
|
6
|
+
* (RAG) implementations. RAG poisoning attacks are a proven threat
|
|
7
|
+
* vector — attackers corrupt knowledge bases to manipulate LLM outputs.
|
|
8
|
+
*
|
|
9
|
+
* Checks: unvalidated document ingestion, missing chunk isolation,
|
|
10
|
+
* embedding exposure, metadata leakage, no retrieval filtering,
|
|
11
|
+
* excessive context stuffing.
|
|
12
|
+
*
|
|
13
|
+
* Maps to: OWASP LLM08 (Vector & Embedding Weaknesses),
|
|
14
|
+
* OWASP Agentic AI ASI04 (Memory Poisoning)
|
|
15
|
+
*/
|
|
16
|
+
|
|
17
|
+
import path from 'path';
|
|
18
|
+
import { BaseAgent } from './base-agent.js';
|
|
19
|
+
|
|
20
|
+
// =============================================================================
|
|
21
|
+
// RAG SECURITY PATTERNS
|
|
22
|
+
// =============================================================================
|
|
23
|
+
|
|
24
|
+
export const PATTERNS = [
|
|
25
|
+
// ── Document Ingestion ───────────────────────────────────────────────────
|
|
26
|
+
{
|
|
27
|
+
rule: 'RAG_UNSANITIZED_INGESTION',
|
|
28
|
+
title: 'RAG: Documents Ingested Without Sanitization',
|
|
29
|
+
regex: /(?:addDocuments|add_documents|upsert|insert|index\.add|from_documents)\s*\(\s*(?:documents|docs|chunks|texts|pages|uploads|files|userDocs|userFiles)/g,
|
|
30
|
+
severity: 'high',
|
|
31
|
+
cwe: 'CWE-20',
|
|
32
|
+
owasp: 'A03:2021',
|
|
33
|
+
description: 'Documents added to vector store without sanitization. Attackers can embed malicious instructions in documents that get retrieved and injected into LLM context.',
|
|
34
|
+
fix: 'Sanitize document content before ingestion: strip HTML/script tags, detect prompt injection patterns, validate content type.',
|
|
35
|
+
},
|
|
36
|
+
{
|
|
37
|
+
rule: 'RAG_USER_UPLOAD_TO_VECTORDB',
|
|
38
|
+
title: 'RAG: User Upload Directly to Vector Store',
|
|
39
|
+
regex: /(?:upload|multer|formidable|busboy|req\.file|req\.files)[\s\S]{0,500}(?:addDocuments|add_documents|upsert|vectorStore|index\.add|embed|from_documents)/g, // praxis-ignore RAG_USER_UPLOAD_TO_VECTORDB — detector definition
|
|
40
|
+
severity: 'critical',
|
|
41
|
+
cwe: 'CWE-434',
|
|
42
|
+
owasp: 'A03:2021',
|
|
43
|
+
description: 'User file uploads are fed directly into a vector database without review. This enables RAG poisoning — attackers upload documents containing hidden instructions.',
|
|
44
|
+
fix: 'Add a review/approval pipeline between user uploads and vector store ingestion. Scan uploads for prompt injection patterns.',
|
|
45
|
+
},
|
|
46
|
+
{
|
|
47
|
+
rule: 'RAG_NO_CONTENT_FILTER',
|
|
48
|
+
title: 'RAG: No Content Filtering on Ingestion',
|
|
49
|
+
regex: /(?:TextLoader|PDFLoader|CSVLoader|DirectoryLoader|WebBaseLoader|UnstructuredLoader|load_and_split|loadDocuments)\s*\((?![\s\S]{0,300}(?:filter|sanitize|clean|validate|strip|remove|moderate))/g,
|
|
50
|
+
severity: 'medium',
|
|
51
|
+
cwe: 'CWE-20',
|
|
52
|
+
owasp: 'A03:2021',
|
|
53
|
+
confidence: 'medium',
|
|
54
|
+
description: 'Document loaders used without content filtering. Loaded content goes directly to embedding/vector store.',
|
|
55
|
+
fix: 'Add content filtering after loading documents: remove scripts, detect injection patterns, validate format.',
|
|
56
|
+
},
|
|
57
|
+
|
|
58
|
+
// ── Chunk Isolation ──────────────────────────────────────────────────────
|
|
59
|
+
{
|
|
60
|
+
rule: 'RAG_NO_TENANT_ISOLATION',
|
|
61
|
+
title: 'RAG: Vector Store Without Tenant Isolation',
|
|
62
|
+
regex: /(?:vectorStore|pinecone|chroma|weaviate|qdrant|milvus|pgvector)[\s\S]{0,300}(?:add|upsert|insert|index)(?![\s\S]{0,200}(?:namespace|tenant|partition|collection|filter|user_id|org_id|metadata.*(?:user|tenant|org)))/g,
|
|
63
|
+
severity: 'high',
|
|
64
|
+
cwe: 'CWE-284',
|
|
65
|
+
owasp: 'A01:2021',
|
|
66
|
+
confidence: 'medium',
|
|
67
|
+
description: 'Documents stored in vector database without tenant/user isolation. Users can retrieve other users\' private documents.',
|
|
68
|
+
fix: 'Use namespaces, collections, or metadata filters to isolate documents by tenant/user.',
|
|
69
|
+
},
|
|
70
|
+
{
|
|
71
|
+
rule: 'RAG_SYSTEM_DOCS_WITH_USER_DOCS',
|
|
72
|
+
title: 'RAG: System Documents Mixed With User Documents',
|
|
73
|
+
regex: /(?:addDocuments|add_documents|upsert)[\s\S]{0,200}(?:system|internal|private|admin)[\s\S]{0,200}(?:user|public|external|upload)/g,
|
|
74
|
+
severity: 'medium',
|
|
75
|
+
cwe: 'CWE-668',
|
|
76
|
+
owasp: 'A01:2021',
|
|
77
|
+
confidence: 'low',
|
|
78
|
+
description: 'System/internal documents mixed with user documents in the same collection. User queries could retrieve sensitive internal docs.',
|
|
79
|
+
fix: 'Separate system documents from user documents in different collections or namespaces.',
|
|
80
|
+
},
|
|
81
|
+
|
|
82
|
+
// ── Retrieval & Query Safety ─────────────────────────────────────────────
|
|
83
|
+
{
|
|
84
|
+
rule: 'RAG_NO_RETRIEVAL_FILTER',
|
|
85
|
+
title: 'RAG: Retrieved Chunks Used Without Filtering',
|
|
86
|
+
regex: /(?:similarity_search|similaritySearch|query|retrieve|get_relevant|asRetriever)[\s\S]{0,300}(?:page_content|pageContent|text|content|document)[\s\S]{0,200}(?:prompt|messages|content|system|user)/g,
|
|
87
|
+
severity: 'high',
|
|
88
|
+
cwe: 'CWE-74',
|
|
89
|
+
owasp: 'A03:2021',
|
|
90
|
+
confidence: 'medium',
|
|
91
|
+
description: 'Retrieved document chunks injected into LLM prompt without filtering. Poisoned documents can override system instructions.',
|
|
92
|
+
fix: 'Filter retrieved chunks: remove instruction-like content, apply relevance score thresholds, limit number of chunks.',
|
|
93
|
+
},
|
|
94
|
+
{
|
|
95
|
+
rule: 'RAG_NO_RELEVANCE_THRESHOLD',
|
|
96
|
+
title: 'RAG: No Relevance Score Threshold on Retrieval',
|
|
97
|
+
regex: /(?:similarity_search|similaritySearch|asRetriever)\s*\(\s*(?:query|question|input)(?![\s\S]{0,200}(?:score_threshold|scoreThreshold|threshold|minScore|min_score|filter))/g,
|
|
98
|
+
severity: 'medium',
|
|
99
|
+
cwe: 'CWE-20',
|
|
100
|
+
owasp: 'A03:2021',
|
|
101
|
+
confidence: 'low',
|
|
102
|
+
description: 'Vector similarity search without relevance threshold. Low-relevance chunks may contain poisoned content designed to be retrieved for many queries.',
|
|
103
|
+
fix: 'Set a minimum relevance score threshold to filter out low-quality matches.',
|
|
104
|
+
},
|
|
105
|
+
{
|
|
106
|
+
rule: 'RAG_EXCESSIVE_CONTEXT',
|
|
107
|
+
title: 'RAG: Excessive Retrieved Context',
|
|
108
|
+
regex: /(?:k\s*[:=]\s*(?:[5-9]\d|\d{3,})|top_k\s*[:=]\s*(?:[5-9]\d|\d{3,})|search_kwargs.*k.*(?:[5-9]\d|\d{3,}))/g,
|
|
109
|
+
severity: 'medium',
|
|
110
|
+
cwe: 'CWE-400',
|
|
111
|
+
owasp: 'A04:2021',
|
|
112
|
+
confidence: 'low',
|
|
113
|
+
description: 'Retrieving a very large number of chunks (50+). Increases the attack surface for prompt injection via poisoned documents.',
|
|
114
|
+
fix: 'Limit retrieved chunks to the minimum needed (typically 3-10). More chunks = more injection surface.',
|
|
115
|
+
},
|
|
116
|
+
|
|
117
|
+
// ── Embedding & Data Exposure ────────────────────────────────────────────
|
|
118
|
+
{
|
|
119
|
+
rule: 'RAG_EMBEDDING_API_EXPOSED',
|
|
120
|
+
title: 'RAG: Embedding Endpoint Exposed Without Auth',
|
|
121
|
+
regex: /(?:app\.|router\.|api\.)(?:get|post)\s*\(\s*['"].*(?:embed|embedding|vectorize|encode)['"](?![\s\S]{0,300}(?:auth|middleware|protect|guard|jwt|bearer|session))/g,
|
|
122
|
+
severity: 'high',
|
|
123
|
+
cwe: 'CWE-306',
|
|
124
|
+
owasp: 'A07:2021',
|
|
125
|
+
confidence: 'medium',
|
|
126
|
+
description: 'Embedding API endpoint exposed without authentication. Attackers can enumerate embeddings or generate vectors for injection attacks.',
|
|
127
|
+
fix: 'Add authentication middleware to embedding endpoints.',
|
|
128
|
+
},
|
|
129
|
+
{
|
|
130
|
+
rule: 'RAG_METADATA_IN_RESPONSE',
|
|
131
|
+
title: 'RAG: Internal Metadata Exposed in Response',
|
|
132
|
+
regex: /(?:metadata|source|file_path|filePath|url|author|timestamp)[\s\S]{0,100}(?:res\.json|res\.send|response\.json|return\s*\{|jsonify)/g,
|
|
133
|
+
severity: 'medium',
|
|
134
|
+
cwe: 'CWE-200',
|
|
135
|
+
owasp: 'A01:2021',
|
|
136
|
+
confidence: 'low',
|
|
137
|
+
description: 'Internal document metadata (file paths, sources, authors) returned in API responses. Leaks information about the knowledge base structure.',
|
|
138
|
+
fix: 'Strip internal metadata before returning responses. Only expose necessary fields to the client.',
|
|
139
|
+
},
|
|
140
|
+
{
|
|
141
|
+
rule: 'RAG_EMBEDDING_IN_RESPONSE',
|
|
142
|
+
title: 'RAG: Raw Embeddings Returned to Client',
|
|
143
|
+
regex: /(?:embedding|vector|dense_vector)[\s\S]{0,100}(?:res\.json|res\.send|response|return|output)/g,
|
|
144
|
+
severity: 'medium',
|
|
145
|
+
cwe: 'CWE-200',
|
|
146
|
+
owasp: 'A01:2021',
|
|
147
|
+
confidence: 'low',
|
|
148
|
+
description: 'Raw embedding vectors returned in API responses. Research shows embeddings can be inverted to recover original text, leaking private data.',
|
|
149
|
+
fix: 'Never return raw embedding vectors in API responses. Embeddings should remain server-side only.',
|
|
150
|
+
},
|
|
151
|
+
|
|
152
|
+
// ── Model & Pipeline Safety ──────────────────────────────────────────────
|
|
153
|
+
{
|
|
154
|
+
rule: 'RAG_PICKLE_EMBEDDING_MODEL',
|
|
155
|
+
title: 'RAG: Embedding Model Loaded via Pickle',
|
|
156
|
+
regex: /(?:torch\.load|pickle\.load|joblib\.load)\s*\(\s*(?!.*safetensors)(?!.*weights_only\s*=\s*True)/g,
|
|
157
|
+
severity: 'critical',
|
|
158
|
+
cwe: 'CWE-502',
|
|
159
|
+
owasp: 'A08:2021',
|
|
160
|
+
description: 'ML model loaded from pickle format. Pickle files can contain arbitrary code that executes on load.',
|
|
161
|
+
fix: 'Use safetensors format for model loading. If using torch.load(), set weights_only=True.',
|
|
162
|
+
},
|
|
163
|
+
{
|
|
164
|
+
rule: 'RAG_TRUST_REMOTE_CODE',
|
|
165
|
+
title: 'RAG: Model Loaded With trust_remote_code=True',
|
|
166
|
+
regex: /trust_remote_code\s*=\s*True/g,
|
|
167
|
+
severity: 'high',
|
|
168
|
+
cwe: 'CWE-94',
|
|
169
|
+
owasp: 'A08:2021',
|
|
170
|
+
description: 'Embedding or LLM model loaded with trust_remote_code=True. Allows execution of arbitrary code from the model repository.',
|
|
171
|
+
fix: 'Set trust_remote_code=False. Audit model code before enabling remote code execution.',
|
|
172
|
+
},
|
|
173
|
+
];
|
|
174
|
+
|
|
175
|
+
// =============================================================================
|
|
176
|
+
// RAG SECURITY AGENT
|
|
177
|
+
// =============================================================================
|
|
178
|
+
|
|
179
|
+
export class RAGSecurityAgent extends BaseAgent {
|
|
180
|
+
constructor() {
|
|
181
|
+
super(
|
|
182
|
+
'RAGSecurityAgent',
|
|
183
|
+
'Detect RAG security vulnerabilities — poisoning, embedding exposure, tenant isolation gaps',
|
|
184
|
+
'llm'
|
|
185
|
+
);
|
|
186
|
+
}
|
|
187
|
+
|
|
188
|
+
async analyze(context) {
|
|
189
|
+
const { files } = context;
|
|
190
|
+
|
|
191
|
+
const codeFiles = files.filter(f => {
|
|
192
|
+
const ext = path.extname(f).toLowerCase();
|
|
193
|
+
return ['.js', '.jsx', '.ts', '.tsx', '.mjs', '.cjs', '.py', '.rb', '.go', '.java'].includes(ext);
|
|
194
|
+
});
|
|
195
|
+
|
|
196
|
+
let findings = [];
|
|
197
|
+
for (const file of codeFiles) {
|
|
198
|
+
findings = findings.concat(this.scanFileWithPatterns(file, PATTERNS));
|
|
199
|
+
}
|
|
200
|
+
return findings;
|
|
201
|
+
}
|
|
202
|
+
}
|
|
203
|
+
|
|
204
|
+
export default RAGSecurityAgent;
|
|
@@ -0,0 +1,207 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* ReconAgent — Attack Surface Discovery
|
|
3
|
+
* =======================================
|
|
4
|
+
*
|
|
5
|
+
* Maps the full attack surface before other agents run.
|
|
6
|
+
* Detects frameworks, API routes, auth patterns, databases,
|
|
7
|
+
* cloud providers, and frontend exposure.
|
|
8
|
+
*/
|
|
9
|
+
|
|
10
|
+
import fs from 'fs';
|
|
11
|
+
import path from 'path';
|
|
12
|
+
import { BaseAgent } from './base-agent.js';
|
|
13
|
+
|
|
14
|
+
export class ReconAgent extends BaseAgent {
|
|
15
|
+
constructor() {
|
|
16
|
+
super('ReconAgent', 'Attack surface discovery and mapping', 'recon');
|
|
17
|
+
}
|
|
18
|
+
|
|
19
|
+
async analyze(context) {
|
|
20
|
+
const { rootPath } = context;
|
|
21
|
+
const files = await this.discoverFiles(rootPath);
|
|
22
|
+
|
|
23
|
+
const recon = {
|
|
24
|
+
frameworks: [],
|
|
25
|
+
languages: new Set(),
|
|
26
|
+
apiRoutes: [],
|
|
27
|
+
authPatterns: [],
|
|
28
|
+
databases: [],
|
|
29
|
+
cloudProviders: [],
|
|
30
|
+
frontendExposure: [],
|
|
31
|
+
packageManagers: [],
|
|
32
|
+
cicd: [],
|
|
33
|
+
hasDockerfile: false,
|
|
34
|
+
hasTerraform: false,
|
|
35
|
+
hasKubernetes: false,
|
|
36
|
+
envFiles: [],
|
|
37
|
+
configFiles: [],
|
|
38
|
+
hasModelFiles: false,
|
|
39
|
+
modelFiles: [],
|
|
40
|
+
};
|
|
41
|
+
|
|
42
|
+
const MODEL_EXT_SET = new Set(['.pkl', '.pickle', '.pt', '.pth', '.ckpt', '.safetensors', '.gguf', '.onnx', '.h5', '.hdf5', '.pb']);
|
|
43
|
+
const MODEL_BASENAME_RE = /^(?:pytorch_model\.bin|model\.bin|consolidated\..+\.bin)$/;
|
|
44
|
+
|
|
45
|
+
// ── Detect by config files ────────────────────────────────────────────────
|
|
46
|
+
for (const file of files) {
|
|
47
|
+
const basename = path.basename(file);
|
|
48
|
+
const relPath = path.relative(rootPath, file).replace(/\\/g, '/');
|
|
49
|
+
const ext = path.extname(file).toLowerCase();
|
|
50
|
+
|
|
51
|
+
// Languages
|
|
52
|
+
if (['.js', '.jsx', '.mjs', '.cjs'].includes(ext)) recon.languages.add('javascript');
|
|
53
|
+
if (['.ts', '.tsx'].includes(ext)) recon.languages.add('typescript');
|
|
54
|
+
if (ext === '.py') recon.languages.add('python');
|
|
55
|
+
if (ext === '.rb') recon.languages.add('ruby');
|
|
56
|
+
if (ext === '.go') recon.languages.add('go');
|
|
57
|
+
if (ext === '.java') recon.languages.add('java');
|
|
58
|
+
if (ext === '.rs') recon.languages.add('rust');
|
|
59
|
+
if (ext === '.php') recon.languages.add('php');
|
|
60
|
+
|
|
61
|
+
// Frameworks
|
|
62
|
+
if (basename === 'next.config.js' || basename === 'next.config.mjs' || basename === 'next.config.ts') {
|
|
63
|
+
recon.frameworks.push('nextjs');
|
|
64
|
+
}
|
|
65
|
+
if (basename === 'nuxt.config.ts' || basename === 'nuxt.config.js') recon.frameworks.push('nuxtjs');
|
|
66
|
+
if (basename === 'svelte.config.js') recon.frameworks.push('sveltekit');
|
|
67
|
+
if (basename === 'remix.config.js') recon.frameworks.push('remix');
|
|
68
|
+
if (basename === 'astro.config.mjs' || basename === 'astro.config.ts') recon.frameworks.push('astro');
|
|
69
|
+
if (basename === 'angular.json') recon.frameworks.push('angular');
|
|
70
|
+
if (basename === 'vite.config.ts' || basename === 'vite.config.js') recon.frameworks.push('vite');
|
|
71
|
+
if (basename === 'manage.py') recon.frameworks.push('django');
|
|
72
|
+
if (basename === 'Gemfile' && this.readFile(file)?.includes('rails')) recon.frameworks.push('rails');
|
|
73
|
+
if (basename === 'pubspec.yaml') recon.frameworks.push('flutter');
|
|
74
|
+
|
|
75
|
+
// API Routes
|
|
76
|
+
if (relPath.match(/app\/api\/.*\.(js|ts)$/) || relPath.match(/pages\/api\/.*\.(js|ts)$/)) {
|
|
77
|
+
recon.apiRoutes.push(relPath);
|
|
78
|
+
}
|
|
79
|
+
if (relPath.match(/routes?\.(js|ts)$/) || relPath.match(/router\.(js|ts)$/)) {
|
|
80
|
+
recon.apiRoutes.push(relPath);
|
|
81
|
+
}
|
|
82
|
+
if (relPath.match(/urls\.py$/)) recon.apiRoutes.push(relPath);
|
|
83
|
+
if (relPath.match(/controllers?\/.*\.(js|ts|rb)$/)) recon.apiRoutes.push(relPath);
|
|
84
|
+
|
|
85
|
+
// Auth
|
|
86
|
+
if (basename === 'auth.ts' || basename === 'auth.js' || relPath.includes('auth/')) {
|
|
87
|
+
recon.authPatterns.push(relPath);
|
|
88
|
+
}
|
|
89
|
+
if (basename === 'middleware.ts' || basename === 'middleware.js') {
|
|
90
|
+
recon.authPatterns.push(relPath);
|
|
91
|
+
}
|
|
92
|
+
|
|
93
|
+
// Databases
|
|
94
|
+
if (basename === 'schema.prisma') recon.databases.push('prisma');
|
|
95
|
+
if (basename === 'drizzle.config.ts' || basename === 'drizzle.config.js') recon.databases.push('drizzle');
|
|
96
|
+
if (relPath.includes('models/') && ['.py', '.rb'].includes(ext)) recon.databases.push('orm');
|
|
97
|
+
|
|
98
|
+
// Cloud
|
|
99
|
+
if (basename === 'vercel.json') recon.cloudProviders.push('vercel');
|
|
100
|
+
if (basename === 'netlify.toml') recon.cloudProviders.push('netlify');
|
|
101
|
+
if (basename === 'fly.toml') recon.cloudProviders.push('fly');
|
|
102
|
+
if (basename === 'app.yaml' || basename === 'app.yml') recon.cloudProviders.push('gcp');
|
|
103
|
+
if (basename === 'serverless.yml' || basename === 'serverless.yaml') recon.cloudProviders.push('aws-serverless');
|
|
104
|
+
if (basename === 'render.yaml') recon.cloudProviders.push('render');
|
|
105
|
+
if (basename === 'railway.json') recon.cloudProviders.push('railway');
|
|
106
|
+
|
|
107
|
+
// IaC
|
|
108
|
+
if (ext === '.tf') recon.hasTerraform = true;
|
|
109
|
+
if (basename === 'Dockerfile' || basename.startsWith('Dockerfile.')) recon.hasDockerfile = true;
|
|
110
|
+
if (basename.match(/\.ya?ml$/) && relPath.match(/(k8s|kubernetes|deploy|helm)/i)) {
|
|
111
|
+
recon.hasKubernetes = true;
|
|
112
|
+
}
|
|
113
|
+
|
|
114
|
+
// CI/CD
|
|
115
|
+
if (relPath.startsWith('.github/workflows/')) recon.cicd.push({ platform: 'github-actions', file: relPath });
|
|
116
|
+
if (basename === '.gitlab-ci.yml') recon.cicd.push({ platform: 'gitlab', file: relPath });
|
|
117
|
+
if (basename === 'Jenkinsfile') recon.cicd.push({ platform: 'jenkins', file: relPath });
|
|
118
|
+
if (basename === '.circleci/config.yml' || relPath.startsWith('.circleci/')) recon.cicd.push({ platform: 'circleci', file: relPath });
|
|
119
|
+
if (basename === 'bitbucket-pipelines.yml') recon.cicd.push({ platform: 'bitbucket', file: relPath });
|
|
120
|
+
if (basename === 'azure-pipelines.yml') recon.cicd.push({ platform: 'azure', file: relPath });
|
|
121
|
+
|
|
122
|
+
// Package managers
|
|
123
|
+
if (basename === 'package.json') recon.packageManagers.push('npm');
|
|
124
|
+
if (basename === 'Pipfile' || basename === 'requirements.txt' || basename === 'pyproject.toml') recon.packageManagers.push('pip');
|
|
125
|
+
if (basename === 'Gemfile') recon.packageManagers.push('bundler');
|
|
126
|
+
if (basename === 'go.mod') recon.packageManagers.push('go');
|
|
127
|
+
if (basename === 'Cargo.toml') recon.packageManagers.push('cargo');
|
|
128
|
+
if (basename === 'composer.json') recon.packageManagers.push('composer');
|
|
129
|
+
|
|
130
|
+
// ML model artifacts
|
|
131
|
+
if (MODEL_EXT_SET.has(ext) || MODEL_BASENAME_RE.test(basename)) {
|
|
132
|
+
recon.hasModelFiles = true;
|
|
133
|
+
if (recon.modelFiles.length < 200) recon.modelFiles.push(relPath);
|
|
134
|
+
}
|
|
135
|
+
|
|
136
|
+
// Env files
|
|
137
|
+
if (basename.startsWith('.env')) recon.envFiles.push(relPath);
|
|
138
|
+
|
|
139
|
+
// Config files
|
|
140
|
+
if (['vercel.json', 'netlify.toml', 'next.config.js', 'next.config.mjs',
|
|
141
|
+
'next.config.ts', 'docker-compose.yml', 'docker-compose.yaml',
|
|
142
|
+
'nginx.conf', 'Caddyfile', 'firebase.json', 'supabase/config.toml'
|
|
143
|
+
].includes(basename)) {
|
|
144
|
+
recon.configFiles.push(relPath);
|
|
145
|
+
}
|
|
146
|
+
}
|
|
147
|
+
|
|
148
|
+
// ── Detect frontend exposure from package.json ────────────────────────────
|
|
149
|
+
const pkgPath = path.join(rootPath, 'package.json');
|
|
150
|
+
if (fs.existsSync(pkgPath)) {
|
|
151
|
+
try {
|
|
152
|
+
const pkg = JSON.parse(fs.readFileSync(pkgPath, 'utf-8'));
|
|
153
|
+
const allDeps = { ...pkg.dependencies, ...pkg.devDependencies };
|
|
154
|
+
|
|
155
|
+
if (allDeps['express']) recon.frameworks.push('express');
|
|
156
|
+
if (allDeps['fastify']) recon.frameworks.push('fastify');
|
|
157
|
+
if (allDeps['hono']) recon.frameworks.push('hono');
|
|
158
|
+
if (allDeps['@hono/node-server']) recon.frameworks.push('hono');
|
|
159
|
+
if (allDeps['koa']) recon.frameworks.push('koa');
|
|
160
|
+
if (allDeps['flask'] || allDeps['Flask']) recon.frameworks.push('flask');
|
|
161
|
+
if (allDeps['fastapi'] || allDeps['FastAPI']) recon.frameworks.push('fastapi');
|
|
162
|
+
|
|
163
|
+
// Auth libraries
|
|
164
|
+
if (allDeps['next-auth'] || allDeps['@auth/core']) recon.authPatterns.push('next-auth');
|
|
165
|
+
if (allDeps['@clerk/nextjs'] || allDeps['@clerk/clerk-react']) recon.authPatterns.push('clerk');
|
|
166
|
+
if (allDeps['@supabase/supabase-js']) { recon.authPatterns.push('supabase-auth'); recon.databases.push('supabase'); }
|
|
167
|
+
if (allDeps['firebase']) { recon.authPatterns.push('firebase-auth'); recon.databases.push('firebase'); }
|
|
168
|
+
if (allDeps['jsonwebtoken'] || allDeps['jose']) recon.authPatterns.push('jwt');
|
|
169
|
+
if (allDeps['passport']) recon.authPatterns.push('passport');
|
|
170
|
+
|
|
171
|
+
// Databases
|
|
172
|
+
if (allDeps['@prisma/client'] || allDeps['prisma']) recon.databases.push('prisma');
|
|
173
|
+
if (allDeps['drizzle-orm']) recon.databases.push('drizzle');
|
|
174
|
+
if (allDeps['sequelize']) recon.databases.push('sequelize');
|
|
175
|
+
if (allDeps['typeorm']) recon.databases.push('typeorm');
|
|
176
|
+
if (allDeps['mongoose'] || allDeps['mongodb']) recon.databases.push('mongodb');
|
|
177
|
+
if (allDeps['pg'] || allDeps['postgres']) recon.databases.push('postgres');
|
|
178
|
+
if (allDeps['mysql2'] || allDeps['mysql']) recon.databases.push('mysql');
|
|
179
|
+
if (allDeps['@upstash/redis']) recon.databases.push('upstash-redis');
|
|
180
|
+
|
|
181
|
+
// AI/LLM
|
|
182
|
+
if (allDeps['openai'] || allDeps['@anthropic-ai/sdk'] || allDeps['ai']) {
|
|
183
|
+
recon.frameworks.push('ai-app');
|
|
184
|
+
}
|
|
185
|
+
|
|
186
|
+
// Mobile
|
|
187
|
+
if (allDeps['react-native'] || allDeps['expo']) recon.frameworks.push('react-native');
|
|
188
|
+
|
|
189
|
+
} catch { /* skip parse errors */ }
|
|
190
|
+
}
|
|
191
|
+
|
|
192
|
+
// Deduplicate arrays
|
|
193
|
+
recon.frameworks = [...new Set(recon.frameworks)];
|
|
194
|
+
recon.languages = [...recon.languages];
|
|
195
|
+
recon.authPatterns = [...new Set(recon.authPatterns)];
|
|
196
|
+
recon.databases = [...new Set(recon.databases)];
|
|
197
|
+
recon.cloudProviders = [...new Set(recon.cloudProviders)];
|
|
198
|
+
recon.packageManagers = [...new Set(recon.packageManagers)];
|
|
199
|
+
|
|
200
|
+
// Store on context for other agents
|
|
201
|
+
if (context) context.recon = recon;
|
|
202
|
+
|
|
203
|
+
return recon;
|
|
204
|
+
}
|
|
205
|
+
}
|
|
206
|
+
|
|
207
|
+
export default ReconAgent;
|