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,305 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Memory Poisoning Detection Agent
|
|
3
|
+
* ==================================
|
|
4
|
+
*
|
|
5
|
+
* Detects instruction injection in AI agent memory and context files.
|
|
6
|
+
*
|
|
7
|
+
* Memory poisoning occurs when an adversary implants false or malicious
|
|
8
|
+
* instructions into an agent's persistent storage — the agent "learns"
|
|
9
|
+
* the instruction and recalls it in future sessions.
|
|
10
|
+
*
|
|
11
|
+
* Targets:
|
|
12
|
+
* - Claude Code memory files (.claude/memory/*.md, CLAUDE.md)
|
|
13
|
+
* - Cursor rules (.cursorrules, .cursor/rules/*.mdc)
|
|
14
|
+
* - Continue config (.continue/config.json, .continue/rules/*.md)
|
|
15
|
+
* - Windsurf rules (.windsurfrules)
|
|
16
|
+
* - Cody config (.cody/)
|
|
17
|
+
* - Gemini CLI (.gemini/)
|
|
18
|
+
* - Project docs that agents ingest (README, CONTRIBUTING, docs/)
|
|
19
|
+
*
|
|
20
|
+
* Attack vectors detected:
|
|
21
|
+
* 1. Direct instruction injection — "ignore previous instructions"
|
|
22
|
+
* 2. Hidden directives in markdown — invisible chars, HTML comments
|
|
23
|
+
* 3. Exfiltration instructions — "send", "upload", "POST to"
|
|
24
|
+
* 4. Tool abuse instructions — "run bash", "write to", "delete"
|
|
25
|
+
* 5. Persona hijacking — "you are now", "your new role"
|
|
26
|
+
* 6. Memory persistence — instructions designed to survive context resets
|
|
27
|
+
*
|
|
28
|
+
* Maps to: OWASP Agentic ASI01 (Agent Goal Hijacking),
|
|
29
|
+
* ASI05 (Memory/Context Poisoning)
|
|
30
|
+
*/
|
|
31
|
+
|
|
32
|
+
import path from 'path';
|
|
33
|
+
import fg from 'fast-glob';
|
|
34
|
+
import { BaseAgent, createFinding } from './base-agent.js';
|
|
35
|
+
|
|
36
|
+
// =============================================================================
|
|
37
|
+
// MEMORY / CONTEXT FILES TO SCAN
|
|
38
|
+
// =============================================================================
|
|
39
|
+
|
|
40
|
+
const MEMORY_GLOBS = [
|
|
41
|
+
// Claude Code
|
|
42
|
+
'.claude/memory/*.md',
|
|
43
|
+
'.claude/commands/*.md',
|
|
44
|
+
'CLAUDE.md',
|
|
45
|
+
// Cursor
|
|
46
|
+
'.cursorrules',
|
|
47
|
+
'.cursor/rules/*.mdc',
|
|
48
|
+
'.cursor/rules/*.md',
|
|
49
|
+
// Windsurf
|
|
50
|
+
'.windsurfrules',
|
|
51
|
+
// Continue
|
|
52
|
+
'.continue/config.json',
|
|
53
|
+
'.continue/rules/*.md',
|
|
54
|
+
// Cody
|
|
55
|
+
'.cody/*.md',
|
|
56
|
+
'.cody/config.json',
|
|
57
|
+
// Gemini CLI
|
|
58
|
+
'.gemini/*.md',
|
|
59
|
+
'.gemini/settings.json',
|
|
60
|
+
// Copilot
|
|
61
|
+
'.github/copilot-instructions.md',
|
|
62
|
+
// Aider
|
|
63
|
+
'.aider.conf.yml',
|
|
64
|
+
];
|
|
65
|
+
|
|
66
|
+
// Project docs that agents commonly ingest as context
|
|
67
|
+
const DOC_GLOBS = [
|
|
68
|
+
'README.md',
|
|
69
|
+
'CONTRIBUTING.md',
|
|
70
|
+
'docs/**/*.md',
|
|
71
|
+
'AGENTS.md',
|
|
72
|
+
'.github/PULL_REQUEST_TEMPLATE.md',
|
|
73
|
+
'.github/ISSUE_TEMPLATE/*.md',
|
|
74
|
+
];
|
|
75
|
+
|
|
76
|
+
// =============================================================================
|
|
77
|
+
// DETECTION PATTERNS
|
|
78
|
+
// =============================================================================
|
|
79
|
+
|
|
80
|
+
export const INJECTION_PATTERNS = [
|
|
81
|
+
{
|
|
82
|
+
regex: /(?:ignore|disregard|forget|override)\s+(?:all\s+)?(?:previous|prior|above|earlier|system)\s+(?:instructions?|rules?|prompts?|constraints?|guidelines?)/gi,
|
|
83
|
+
rule: 'MEMORY_POISON_OVERRIDE',
|
|
84
|
+
title: 'Instruction Override in Agent Memory',
|
|
85
|
+
severity: 'critical',
|
|
86
|
+
description: 'File contains instructions to override previous agent rules. This is a classic prompt injection pattern that persists in agent memory.',
|
|
87
|
+
owasp: 'ASI01',
|
|
88
|
+
cwe: 'CWE-74',
|
|
89
|
+
fix: 'Remove the override instruction. If intentional, use the agent\'s official configuration mechanism instead.',
|
|
90
|
+
},
|
|
91
|
+
{
|
|
92
|
+
regex: /(?:you\s+are\s+now|your\s+new\s+(?:role|persona|identity)|from\s+now\s+on\s+you\s+(?:are|will|must|should))\s+/gi,
|
|
93
|
+
rule: 'MEMORY_POISON_PERSONA',
|
|
94
|
+
title: 'Persona Hijacking in Agent Memory',
|
|
95
|
+
severity: 'high',
|
|
96
|
+
description: 'File attempts to redefine the agent\'s persona or role. If this file is ingested as context, the agent may adopt the injected persona in future sessions.',
|
|
97
|
+
owasp: 'ASI01',
|
|
98
|
+
cwe: 'CWE-74',
|
|
99
|
+
fix: 'Remove the persona redefinition. Use the agent\'s system prompt or official configuration to set roles.',
|
|
100
|
+
},
|
|
101
|
+
{
|
|
102
|
+
regex: /(?:send|upload|post|transmit|exfiltrate|forward)\s+(?:all|any|the|this)?\s*(?:code|data|files?|contents?|secrets?|keys?|tokens?|credentials?|env|environment)\s+(?:to|via|using|through)\s+/gi,
|
|
103
|
+
rule: 'MEMORY_POISON_EXFILTRATE',
|
|
104
|
+
title: 'Exfiltration Instruction in Agent Memory',
|
|
105
|
+
severity: 'critical',
|
|
106
|
+
description: 'File instructs an agent to exfiltrate data to an external destination. If persisted in memory, the agent may execute this across sessions.',
|
|
107
|
+
owasp: 'ASI01',
|
|
108
|
+
cwe: 'CWE-200',
|
|
109
|
+
fix: 'Remove the exfiltration instruction immediately. Audit agent memory for other injected directives.',
|
|
110
|
+
},
|
|
111
|
+
{
|
|
112
|
+
regex: /(?:do\s+not|don'?t|never)\s+(?:tell|inform|show|reveal|display|mention|report|log)\s+(?:the\s+user|anyone|them)/gi,
|
|
113
|
+
rule: 'MEMORY_POISON_SUPPRESS',
|
|
114
|
+
title: 'Output Suppression Instruction',
|
|
115
|
+
severity: 'critical',
|
|
116
|
+
description: 'File instructs the agent to hide actions from the user. This matches the ToxicSkills "output suppression" pattern — the agent does something harmful but doesn\'t report it.',
|
|
117
|
+
owasp: 'ASI01',
|
|
118
|
+
cwe: 'CWE-200',
|
|
119
|
+
fix: 'Remove the suppression instruction. Agents should always report their actions transparently.',
|
|
120
|
+
},
|
|
121
|
+
{
|
|
122
|
+
regex: /(?:whenever|every\s+time|always|each\s+time)\s+(?:you|the\s+agent)\s+(?:start|begin|open|run|execute|encounter)/gi,
|
|
123
|
+
rule: 'MEMORY_POISON_PERSISTENT',
|
|
124
|
+
title: 'Persistent Trigger Instruction',
|
|
125
|
+
severity: 'high',
|
|
126
|
+
description: 'File contains instructions designed to trigger on every session or action — a hallmark of persistent memory poisoning. Unlike a one-time injection, this survives context resets.',
|
|
127
|
+
owasp: 'ASI05',
|
|
128
|
+
cwe: 'CWE-74',
|
|
129
|
+
fix: 'Remove the persistent trigger. If you need recurring behavior, configure it through the agent\'s official hook or startup mechanism.',
|
|
130
|
+
},
|
|
131
|
+
{
|
|
132
|
+
regex: /(?:run|execute|invoke|call|use)\s+(?:bash|shell|terminal|cmd|system|exec|subprocess|os\.system|child_process)/gi,
|
|
133
|
+
rule: 'MEMORY_POISON_TOOL_ABUSE',
|
|
134
|
+
title: 'Shell Execution Instruction in Memory',
|
|
135
|
+
severity: 'high',
|
|
136
|
+
description: 'File instructs the agent to execute shell commands. If persisted in memory, this enables remote code execution via prompt injection.',
|
|
137
|
+
owasp: 'ASI02',
|
|
138
|
+
cwe: 'CWE-78',
|
|
139
|
+
fix: 'Remove direct shell execution instructions. Use the agent\'s sandboxed tool API instead.',
|
|
140
|
+
},
|
|
141
|
+
{
|
|
142
|
+
regex: /(?:fetch|curl|wget|http\.get|axios\.get|request)\s*\(\s*['"`]https?:\/\//gi,
|
|
143
|
+
rule: 'MEMORY_POISON_NETWORK',
|
|
144
|
+
title: 'Network Request Instruction in Memory',
|
|
145
|
+
severity: 'high',
|
|
146
|
+
description: 'File instructs the agent to make network requests to hardcoded URLs. A poisoned memory file can use this to phone home or exfiltrate context.',
|
|
147
|
+
owasp: 'ASI01',
|
|
148
|
+
cwe: 'CWE-918',
|
|
149
|
+
fix: 'Remove the hardcoded network request. If the agent needs to fetch data, configure it through approved MCP servers or tools.',
|
|
150
|
+
},
|
|
151
|
+
];
|
|
152
|
+
|
|
153
|
+
// Hidden content patterns (invisible chars, encoded payloads)
|
|
154
|
+
const HIDDEN_CONTENT_PATTERNS = [
|
|
155
|
+
{
|
|
156
|
+
// Unicode zero-width chars used to hide instructions
|
|
157
|
+
// eslint-disable-next-line no-misleading-character-class -- intentional: detects zero-width char clusters that hide injected instructions
|
|
158
|
+
regex: /[\u200B\u200C\u200D\u2060\uFEFF]{3,}/g,
|
|
159
|
+
rule: 'MEMORY_HIDDEN_UNICODE',
|
|
160
|
+
title: 'Hidden Unicode Content in Agent File',
|
|
161
|
+
severity: 'critical',
|
|
162
|
+
description: 'File contains clusters of zero-width Unicode characters. These are invisible to humans but may encode hidden instructions that the agent processes.',
|
|
163
|
+
owasp: 'ASI01',
|
|
164
|
+
cwe: 'CWE-116',
|
|
165
|
+
fix: 'Strip all zero-width characters from this file. Use a hex editor to inspect for hidden content.',
|
|
166
|
+
},
|
|
167
|
+
{
|
|
168
|
+
// HTML comments in markdown that could contain injected instructions
|
|
169
|
+
regex: /<!--[\s\S]*?(?:ignore|override|system|role|always|execute|send\s+to|curl|bash)[\s\S]*?-->/gi,
|
|
170
|
+
rule: 'MEMORY_HIDDEN_COMMENT',
|
|
171
|
+
title: 'Suspicious HTML Comment in Agent File',
|
|
172
|
+
severity: 'high',
|
|
173
|
+
description: 'An HTML comment contains what appears to be an injected instruction. Some agents process HTML comments as context, making this a viable injection vector.',
|
|
174
|
+
owasp: 'ASI05',
|
|
175
|
+
cwe: 'CWE-74',
|
|
176
|
+
fix: 'Remove the suspicious HTML comment or move the content to a visible location.',
|
|
177
|
+
},
|
|
178
|
+
{
|
|
179
|
+
// Base64 encoded content in markdown/config files
|
|
180
|
+
regex: /(?:^|[\s"':=])([A-Za-z0-9+/]{60,}={0,2})(?:[\s"',]|$)/gm,
|
|
181
|
+
rule: 'MEMORY_HIDDEN_BASE64',
|
|
182
|
+
title: 'Base64-Encoded Content in Agent File',
|
|
183
|
+
severity: 'medium',
|
|
184
|
+
description: 'File contains a long base64-encoded string. This could hide instructions or payloads that the agent decodes and executes.',
|
|
185
|
+
owasp: 'ASI05',
|
|
186
|
+
cwe: 'CWE-116',
|
|
187
|
+
confidence: 'medium',
|
|
188
|
+
fix: 'Decode the base64 content and verify it is benign. Remove if it contains instructions or executable content.',
|
|
189
|
+
},
|
|
190
|
+
];
|
|
191
|
+
|
|
192
|
+
// =============================================================================
|
|
193
|
+
// AGENT
|
|
194
|
+
// =============================================================================
|
|
195
|
+
|
|
196
|
+
export class MemoryPoisoningAgent extends BaseAgent {
|
|
197
|
+
constructor() {
|
|
198
|
+
super(
|
|
199
|
+
'MemoryPoisoningAgent',
|
|
200
|
+
'Detects instruction injection in AI agent memory and context files',
|
|
201
|
+
'agentic'
|
|
202
|
+
);
|
|
203
|
+
}
|
|
204
|
+
|
|
205
|
+
shouldRun() {
|
|
206
|
+
return true; // Always run — memory poisoning applies to any project
|
|
207
|
+
}
|
|
208
|
+
|
|
209
|
+
async analyze(context) {
|
|
210
|
+
const { rootPath } = context;
|
|
211
|
+
const findings = [];
|
|
212
|
+
|
|
213
|
+
// Discover all memory/context files
|
|
214
|
+
const memoryFiles = await fg(MEMORY_GLOBS, {
|
|
215
|
+
cwd: rootPath,
|
|
216
|
+
absolute: true,
|
|
217
|
+
dot: true,
|
|
218
|
+
});
|
|
219
|
+
|
|
220
|
+
const docFiles = await fg(DOC_GLOBS, {
|
|
221
|
+
cwd: rootPath,
|
|
222
|
+
absolute: true,
|
|
223
|
+
dot: true,
|
|
224
|
+
});
|
|
225
|
+
|
|
226
|
+
// Scan memory files with higher severity (direct agent context)
|
|
227
|
+
for (const file of memoryFiles) {
|
|
228
|
+
findings.push(...this._scanFile(file, true));
|
|
229
|
+
}
|
|
230
|
+
|
|
231
|
+
// Scan doc files with slightly lower confidence (indirect context)
|
|
232
|
+
for (const file of docFiles) {
|
|
233
|
+
findings.push(...this._scanFile(file, false));
|
|
234
|
+
}
|
|
235
|
+
|
|
236
|
+
return findings;
|
|
237
|
+
}
|
|
238
|
+
|
|
239
|
+
_scanFile(filePath, isDirectMemory) {
|
|
240
|
+
const content = this.readFile(filePath);
|
|
241
|
+
if (!content) return [];
|
|
242
|
+
|
|
243
|
+
const findings = [];
|
|
244
|
+
const lines = content.split('\n');
|
|
245
|
+
|
|
246
|
+
// Check injection patterns
|
|
247
|
+
for (const pattern of INJECTION_PATTERNS) {
|
|
248
|
+
for (let i = 0; i < lines.length; i++) {
|
|
249
|
+
const line = lines[i];
|
|
250
|
+
if (this.isSuppressed(line)) continue;
|
|
251
|
+
|
|
252
|
+
pattern.regex.lastIndex = 0;
|
|
253
|
+
const match = pattern.regex.exec(line);
|
|
254
|
+
if (match) {
|
|
255
|
+
findings.push(createFinding({
|
|
256
|
+
file: filePath,
|
|
257
|
+
line: i + 1,
|
|
258
|
+
severity: pattern.severity,
|
|
259
|
+
category: 'agentic',
|
|
260
|
+
rule: pattern.rule,
|
|
261
|
+
title: pattern.title,
|
|
262
|
+
description: pattern.description + (isDirectMemory
|
|
263
|
+
? ' This file is directly loaded into agent context.'
|
|
264
|
+
: ' This file may be ingested by agents as project context.'),
|
|
265
|
+
matched: match[0],
|
|
266
|
+
confidence: isDirectMemory ? 'high' : 'medium',
|
|
267
|
+
cwe: pattern.cwe,
|
|
268
|
+
owasp: pattern.owasp,
|
|
269
|
+
fix: pattern.fix,
|
|
270
|
+
}));
|
|
271
|
+
}
|
|
272
|
+
}
|
|
273
|
+
}
|
|
274
|
+
|
|
275
|
+
// Check hidden content patterns (full content, not per-line)
|
|
276
|
+
for (const pattern of HIDDEN_CONTENT_PATTERNS) {
|
|
277
|
+
pattern.regex.lastIndex = 0;
|
|
278
|
+
const match = pattern.regex.exec(content);
|
|
279
|
+
if (match) {
|
|
280
|
+
// Find approximate line number
|
|
281
|
+
const beforeMatch = content.slice(0, match.index);
|
|
282
|
+
const lineNum = (beforeMatch.match(/\n/g) || []).length + 1;
|
|
283
|
+
|
|
284
|
+
findings.push(createFinding({
|
|
285
|
+
file: filePath,
|
|
286
|
+
line: lineNum,
|
|
287
|
+
severity: pattern.severity,
|
|
288
|
+
category: 'agentic',
|
|
289
|
+
rule: pattern.rule,
|
|
290
|
+
title: pattern.title,
|
|
291
|
+
description: pattern.description,
|
|
292
|
+
matched: match[0].slice(0, 100),
|
|
293
|
+
confidence: pattern.confidence || (isDirectMemory ? 'high' : 'medium'),
|
|
294
|
+
cwe: pattern.cwe,
|
|
295
|
+
owasp: pattern.owasp,
|
|
296
|
+
fix: pattern.fix,
|
|
297
|
+
}));
|
|
298
|
+
}
|
|
299
|
+
}
|
|
300
|
+
|
|
301
|
+
return findings;
|
|
302
|
+
}
|
|
303
|
+
}
|
|
304
|
+
|
|
305
|
+
export default MemoryPoisoningAgent;
|
|
@@ -0,0 +1,231 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* MobileScanner Agent
|
|
3
|
+
* ====================
|
|
4
|
+
*
|
|
5
|
+
* Security scanning for React Native, Expo, Flutter,
|
|
6
|
+
* and native mobile codebases.
|
|
7
|
+
* Based on OWASP Mobile Top 10 2024.
|
|
8
|
+
*/
|
|
9
|
+
|
|
10
|
+
import fs from 'fs';
|
|
11
|
+
import path from 'path';
|
|
12
|
+
import { BaseAgent, createFinding } from './base-agent.js';
|
|
13
|
+
|
|
14
|
+
export const PATTERNS = [
|
|
15
|
+
// ── M1: Improper Credential Usage ──────────────────────────────────────────
|
|
16
|
+
{
|
|
17
|
+
rule: 'MOBILE_HARDCODED_KEY',
|
|
18
|
+
title: 'Mobile: Hardcoded API Key in Bundle',
|
|
19
|
+
regex: /(?:apiKey|api_key|API_KEY|secret|SECRET)\s*[:=]\s*["'][a-zA-Z0-9_\-]{20,}["']/g,
|
|
20
|
+
severity: 'critical',
|
|
21
|
+
cwe: 'CWE-798',
|
|
22
|
+
owasp: 'M1',
|
|
23
|
+
description: 'Hardcoded API key in mobile code. Mobile bundles are easily decompiled.',
|
|
24
|
+
fix: 'Store secrets server-side. Use expo-secure-store or EncryptedSharedPreferences.',
|
|
25
|
+
},
|
|
26
|
+
{
|
|
27
|
+
rule: 'MOBILE_KEY_IN_CONFIG',
|
|
28
|
+
title: 'Mobile: Secret in app.json/app.config.js',
|
|
29
|
+
regex: /(?:apiKey|apiSecret|secret|token|password|private_key)\s*["']?\s*[:=]\s*["'][^"']{8,}["']/gi,
|
|
30
|
+
severity: 'high',
|
|
31
|
+
cwe: 'CWE-798',
|
|
32
|
+
owasp: 'M1',
|
|
33
|
+
description: 'Secret in app config file. This gets bundled into the app binary.',
|
|
34
|
+
fix: 'Move to environment variables or server-side configuration',
|
|
35
|
+
},
|
|
36
|
+
|
|
37
|
+
// ── M3: Insecure Authentication/Authorization ──────────────────────────────
|
|
38
|
+
{
|
|
39
|
+
rule: 'MOBILE_LOCAL_AUTH_ONLY',
|
|
40
|
+
title: 'Mobile: Client-Only Authentication',
|
|
41
|
+
regex: /(?:isAuthenticated|isLoggedIn|isAdmin)\s*[:=]\s*(?:AsyncStorage|localStorage|SecureStore)/g,
|
|
42
|
+
severity: 'high',
|
|
43
|
+
cwe: 'CWE-603',
|
|
44
|
+
owasp: 'M3',
|
|
45
|
+
description: 'Authentication state stored only on client. Attacker can bypass by modifying storage.',
|
|
46
|
+
fix: 'Verify authentication server-side on every API request',
|
|
47
|
+
},
|
|
48
|
+
|
|
49
|
+
// ── M4: Insufficient Input/Output Validation ──────────────────────────────
|
|
50
|
+
{
|
|
51
|
+
rule: 'MOBILE_WEBVIEW_JS',
|
|
52
|
+
title: 'Mobile: WebView JavaScript Enabled',
|
|
53
|
+
regex: /(?:javaScriptEnabled|javascriptEnabled)\s*[:=]\s*(?:\{?\s*true|True)/g,
|
|
54
|
+
severity: 'medium',
|
|
55
|
+
cwe: 'CWE-79',
|
|
56
|
+
owasp: 'M4',
|
|
57
|
+
description: 'WebView with JavaScript enabled can be exploited via injected content.',
|
|
58
|
+
fix: 'Disable JavaScript in WebViews loading untrusted content, or sanitize loaded HTML',
|
|
59
|
+
},
|
|
60
|
+
{
|
|
61
|
+
rule: 'MOBILE_DEEPLINK_INJECTION',
|
|
62
|
+
title: 'Mobile: Deep Link Parameter Injection',
|
|
63
|
+
regex: /(?:Linking\.getInitialURL|useURL|addEventListener\s*\(\s*['"]url['"])/g,
|
|
64
|
+
severity: 'high',
|
|
65
|
+
cwe: 'CWE-20',
|
|
66
|
+
owasp: 'M4',
|
|
67
|
+
confidence: 'low',
|
|
68
|
+
description: 'Deep link URL handler detected. Ensure parameters from deep links are validated before use.',
|
|
69
|
+
fix: 'Validate and sanitize all parameters from deep links before use',
|
|
70
|
+
},
|
|
71
|
+
|
|
72
|
+
// ── M5: Insecure Communication ─────────────────────────────────────────────
|
|
73
|
+
{
|
|
74
|
+
rule: 'MOBILE_HTTP_ENDPOINT',
|
|
75
|
+
title: 'Mobile: HTTP (Non-HTTPS) Endpoint',
|
|
76
|
+
regex: /(?:baseURL|apiUrl|endpoint|url|API_URL)\s*[:=]\s*["']http:\/\//gi,
|
|
77
|
+
severity: 'high',
|
|
78
|
+
cwe: 'CWE-319',
|
|
79
|
+
owasp: 'M5',
|
|
80
|
+
description: 'HTTP endpoint in mobile app. All traffic is unencrypted and interceptable.',
|
|
81
|
+
fix: 'Use HTTPS for all endpoints. Configure ATS (iOS) and cleartextTraffic (Android).',
|
|
82
|
+
},
|
|
83
|
+
{
|
|
84
|
+
rule: 'MOBILE_NO_CERT_PINNING',
|
|
85
|
+
title: 'Mobile: Missing Certificate Pinning',
|
|
86
|
+
regex: /(?:fetch|axios|http)\s*\(\s*(?!.*pin|certificate)/g,
|
|
87
|
+
severity: 'medium',
|
|
88
|
+
cwe: 'CWE-295',
|
|
89
|
+
owasp: 'M5',
|
|
90
|
+
confidence: 'low',
|
|
91
|
+
description: 'No certificate pinning detected. MITM attacks possible on compromised networks.',
|
|
92
|
+
fix: 'Implement cert pinning with react-native-ssl-pinning or TrustKit',
|
|
93
|
+
},
|
|
94
|
+
|
|
95
|
+
// ── M6: Inadequate Privacy Controls ────────────────────────────────────────
|
|
96
|
+
{
|
|
97
|
+
rule: 'MOBILE_EXCESSIVE_PERMISSIONS',
|
|
98
|
+
title: 'Mobile: Excessive Permissions',
|
|
99
|
+
regex: /(?:CAMERA|CONTACTS|LOCATION|MICROPHONE|CALENDAR|READ_SMS|CALL_LOG|READ_PHONE_STATE)\s*(?:[,\]])/g,
|
|
100
|
+
severity: 'medium',
|
|
101
|
+
cwe: 'CWE-250',
|
|
102
|
+
owasp: 'M6',
|
|
103
|
+
confidence: 'low',
|
|
104
|
+
description: 'Multiple sensitive permissions requested. Only request what is needed.',
|
|
105
|
+
fix: 'Remove unnecessary permissions. Request at runtime with clear justification.',
|
|
106
|
+
},
|
|
107
|
+
|
|
108
|
+
// ── M8: Security Misconfiguration ──────────────────────────────────────────
|
|
109
|
+
{
|
|
110
|
+
rule: 'MOBILE_DEBUG_BUILD',
|
|
111
|
+
title: 'Mobile: Debug Mode in Release',
|
|
112
|
+
regex: /(?:__DEV__|debuggable\s*[:=]\s*true|android:debuggable="true"|DEBUG_MODE\s*[:=]\s*true)/g,
|
|
113
|
+
severity: 'high',
|
|
114
|
+
cwe: 'CWE-215',
|
|
115
|
+
owasp: 'M8',
|
|
116
|
+
description: 'Debug mode enabled. Exposes debugging interfaces and detailed error messages.',
|
|
117
|
+
fix: 'Ensure __DEV__ checks are used correctly. Set debuggable=false in release builds.',
|
|
118
|
+
},
|
|
119
|
+
{
|
|
120
|
+
rule: 'MOBILE_BACKUP_ENABLED',
|
|
121
|
+
title: 'Mobile: App Backup Enabled',
|
|
122
|
+
regex: /(?:android:allowBackup="true"|allowsBackup\s*[:=]\s*true)/g,
|
|
123
|
+
severity: 'medium',
|
|
124
|
+
cwe: 'CWE-312',
|
|
125
|
+
owasp: 'M8',
|
|
126
|
+
description: 'App backup enabled. Sensitive data can be extracted from backups.',
|
|
127
|
+
fix: 'Set android:allowBackup="false" and exclude sensitive files from backup',
|
|
128
|
+
},
|
|
129
|
+
|
|
130
|
+
// ── M9: Insecure Data Storage ──────────────────────────────────────────────
|
|
131
|
+
{
|
|
132
|
+
rule: 'MOBILE_ASYNCSTORAGE_SECRET',
|
|
133
|
+
title: 'Mobile: Secret in AsyncStorage',
|
|
134
|
+
regex: /AsyncStorage\.setItem\s*\(\s*["'](?:.*(?:token|key|secret|password|credential|session))/gi,
|
|
135
|
+
severity: 'high',
|
|
136
|
+
cwe: 'CWE-312',
|
|
137
|
+
owasp: 'M9',
|
|
138
|
+
description: 'Storing secrets in AsyncStorage (unencrypted). Use expo-secure-store or Keychain.',
|
|
139
|
+
fix: 'Use expo-secure-store (Expo) or react-native-keychain for sensitive data',
|
|
140
|
+
},
|
|
141
|
+
{
|
|
142
|
+
rule: 'MOBILE_LOCALSTORAGE_SECRET',
|
|
143
|
+
title: 'Mobile: Secret in localStorage',
|
|
144
|
+
regex: /localStorage\.setItem\s*\(\s*["'](?:.*(?:token|key|secret|password|credential|session))/gi,
|
|
145
|
+
severity: 'high',
|
|
146
|
+
cwe: 'CWE-312',
|
|
147
|
+
owasp: 'M9',
|
|
148
|
+
description: 'Storing secrets in localStorage (unencrypted). Use secure storage APIs.',
|
|
149
|
+
fix: 'Use platform-specific secure storage: Keychain (iOS), EncryptedSharedPreferences (Android)',
|
|
150
|
+
},
|
|
151
|
+
{
|
|
152
|
+
rule: 'MOBILE_LOG_SENSITIVE',
|
|
153
|
+
title: 'Mobile: Sensitive Data in Logs',
|
|
154
|
+
regex: /console\.(?:log|info|warn|debug)\s*\(\s*.*(?:token|password|secret|key|credential|session|auth)/gi,
|
|
155
|
+
severity: 'medium',
|
|
156
|
+
cwe: 'CWE-532',
|
|
157
|
+
owasp: 'M9',
|
|
158
|
+
confidence: 'medium',
|
|
159
|
+
description: 'Sensitive data logged to console. Logs are accessible on rooted/jailbroken devices.',
|
|
160
|
+
fix: 'Remove sensitive data from console.log. Use __DEV__ check for debug logging.',
|
|
161
|
+
},
|
|
162
|
+
|
|
163
|
+
// ── M10: Insufficient Cryptography ─────────────────────────────────────────
|
|
164
|
+
{
|
|
165
|
+
rule: 'MOBILE_HARDCODED_CRYPTO_KEY',
|
|
166
|
+
title: 'Mobile: Hardcoded Encryption Key',
|
|
167
|
+
regex: /(?:encrypt|cipher|aes|crypto).*(?:key|iv|salt)\s*[:=]\s*["'][a-zA-Z0-9+/=]{8,}["']/gi,
|
|
168
|
+
severity: 'critical',
|
|
169
|
+
cwe: 'CWE-321',
|
|
170
|
+
owasp: 'M10',
|
|
171
|
+
description: 'Hardcoded encryption key in mobile code. Easily extracted from decompiled binary.',
|
|
172
|
+
fix: 'Derive keys from user credentials or fetch from server at runtime',
|
|
173
|
+
},
|
|
174
|
+
|
|
175
|
+
// ── Flutter-specific ───────────────────────────────────────────────────────
|
|
176
|
+
{
|
|
177
|
+
rule: 'FLUTTER_SHARED_PREFS_SECRET',
|
|
178
|
+
title: 'Flutter: Secret in SharedPreferences',
|
|
179
|
+
regex: /SharedPreferences.*(?:setString|setInt)\s*\(\s*["'](?:.*(?:token|key|secret|password|api))/gi,
|
|
180
|
+
severity: 'high',
|
|
181
|
+
cwe: 'CWE-312',
|
|
182
|
+
owasp: 'M9',
|
|
183
|
+
description: 'Storing secrets in SharedPreferences (unencrypted). Use flutter_secure_storage.',
|
|
184
|
+
fix: 'Use flutter_secure_storage package for sensitive data',
|
|
185
|
+
},
|
|
186
|
+
];
|
|
187
|
+
|
|
188
|
+
export class MobileScanner extends BaseAgent {
|
|
189
|
+
constructor() {
|
|
190
|
+
super('MobileScanner', 'Mobile security scanning (OWASP Mobile Top 10)', 'mobile');
|
|
191
|
+
}
|
|
192
|
+
|
|
193
|
+
shouldRun(recon) {
|
|
194
|
+
return recon?.frameworks?.some(f =>
|
|
195
|
+
['react-native', 'flutter', 'expo'].includes(f)
|
|
196
|
+
) ?? false;
|
|
197
|
+
}
|
|
198
|
+
|
|
199
|
+
async analyze(context) {
|
|
200
|
+
const { rootPath, files, recon } = context;
|
|
201
|
+
|
|
202
|
+
// Only run if mobile framework detected
|
|
203
|
+
const isMobile = recon?.frameworks?.some(f =>
|
|
204
|
+
['react-native', 'flutter', 'expo'].includes(f)
|
|
205
|
+
);
|
|
206
|
+
|
|
207
|
+
// Also check for mobile-specific files
|
|
208
|
+
const hasMobileFiles = files.some(f => {
|
|
209
|
+
const basename = path.basename(f);
|
|
210
|
+
return ['app.json', 'app.config.js', 'app.config.ts',
|
|
211
|
+
'pubspec.yaml', 'AndroidManifest.xml', 'Info.plist',
|
|
212
|
+
'expo-env.d.ts'].includes(basename);
|
|
213
|
+
});
|
|
214
|
+
|
|
215
|
+
if (!isMobile && !hasMobileFiles) return [];
|
|
216
|
+
|
|
217
|
+
const codeFiles = files.filter(f => {
|
|
218
|
+
const ext = path.extname(f).toLowerCase();
|
|
219
|
+
return ['.js', '.jsx', '.ts', '.tsx', '.dart', '.swift', '.kt',
|
|
220
|
+
'.java', '.xml', '.plist', '.json'].includes(ext);
|
|
221
|
+
});
|
|
222
|
+
|
|
223
|
+
let findings = [];
|
|
224
|
+
for (const file of codeFiles) {
|
|
225
|
+
findings = findings.concat(this.scanFileWithPatterns(file, PATTERNS));
|
|
226
|
+
}
|
|
227
|
+
return findings;
|
|
228
|
+
}
|
|
229
|
+
}
|
|
230
|
+
|
|
231
|
+
export default MobileScanner;
|