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.
Files changed (183) hide show
  1. package/LICENSE +21 -0
  2. package/README.md +170 -0
  3. package/ai-defense/cost-protection.md +292 -0
  4. package/ai-defense/llm-security-checklist.md +324 -0
  5. package/ai-defense/prompt-injection-patterns.js +283 -0
  6. package/ai-defense/system-prompt-armor.md +327 -0
  7. package/checklists/launch-day.md +168 -0
  8. package/cli/agents/abom-generator.js +225 -0
  9. package/cli/agents/agent-attestation-agent.js +318 -0
  10. package/cli/agents/agent-config-scanner.js +787 -0
  11. package/cli/agents/agent-telemetry-agent.js +415 -0
  12. package/cli/agents/agentic-security-agent.js +296 -0
  13. package/cli/agents/agentic-supply-chain-agent.js +463 -0
  14. package/cli/agents/ai-infra-inventory-agent.js +449 -0
  15. package/cli/agents/api-fuzzer.js +345 -0
  16. package/cli/agents/auth-bypass-agent.js +348 -0
  17. package/cli/agents/base-agent.js +280 -0
  18. package/cli/agents/cicd-scanner.js +300 -0
  19. package/cli/agents/config-auditor.js +757 -0
  20. package/cli/agents/deep-analyzer.js +776 -0
  21. package/cli/agents/endpoint-agent-abuse-agent.js +404 -0
  22. package/cli/agents/exception-handler-agent.js +187 -0
  23. package/cli/agents/git-history-scanner.js +169 -0
  24. package/cli/agents/governance-audits.js +138 -0
  25. package/cli/agents/hermes-security-agent.js +536 -0
  26. package/cli/agents/html-reporter.js +1125 -0
  27. package/cli/agents/index.js +147 -0
  28. package/cli/agents/injection-tester.js +502 -0
  29. package/cli/agents/legal-risk-agent.js +328 -0
  30. package/cli/agents/llm-redteam.js +199 -0
  31. package/cli/agents/managed-agent-scanner.js +333 -0
  32. package/cli/agents/mcp-security-agent.js +588 -0
  33. package/cli/agents/memory-poisoning-agent.js +305 -0
  34. package/cli/agents/mobile-scanner.js +231 -0
  35. package/cli/agents/model-file-scanner.js +259 -0
  36. package/cli/agents/orchestrator.js +355 -0
  37. package/cli/agents/pii-compliance-agent.js +301 -0
  38. package/cli/agents/policy-engine.js +229 -0
  39. package/cli/agents/prompt-injection-prober.js +224 -0
  40. package/cli/agents/rag-security-agent.js +204 -0
  41. package/cli/agents/recon-agent.js +207 -0
  42. package/cli/agents/sbom-generator.js +265 -0
  43. package/cli/agents/scoring-engine.js +273 -0
  44. package/cli/agents/ssrf-prober.js +130 -0
  45. package/cli/agents/stateful-watcher.js +238 -0
  46. package/cli/agents/supabase-rls-agent.js +154 -0
  47. package/cli/agents/supply-chain-agent.js +857 -0
  48. package/cli/agents/swarm-orchestrator.js +200 -0
  49. package/cli/agents/verifier-agent.js +303 -0
  50. package/cli/agents/vibe-coding-agent.js +250 -0
  51. package/cli/bin/praxis.js +866 -0
  52. package/cli/commands/abom.js +73 -0
  53. package/cli/commands/agent-fix.js +1245 -0
  54. package/cli/commands/audit.js +1180 -0
  55. package/cli/commands/autofix.js +383 -0
  56. package/cli/commands/baseline.js +193 -0
  57. package/cli/commands/benchmark.js +327 -0
  58. package/cli/commands/checklist.js +223 -0
  59. package/cli/commands/ci.js +403 -0
  60. package/cli/commands/deps.js +516 -0
  61. package/cli/commands/diff.js +200 -0
  62. package/cli/commands/doctor.js +195 -0
  63. package/cli/commands/env-audit.js +349 -0
  64. package/cli/commands/fix.js +218 -0
  65. package/cli/commands/guard.js +396 -0
  66. package/cli/commands/hooks.js +278 -0
  67. package/cli/commands/init.js +514 -0
  68. package/cli/commands/legal.js +158 -0
  69. package/cli/commands/live-advisories.js +241 -0
  70. package/cli/commands/mcp.js +660 -0
  71. package/cli/commands/openclaw.js +386 -0
  72. package/cli/commands/red-team.js +350 -0
  73. package/cli/commands/redteam.js +78 -0
  74. package/cli/commands/remediate.js +797 -0
  75. package/cli/commands/rotate.js +768 -0
  76. package/cli/commands/rules.js +196 -0
  77. package/cli/commands/scan-mcp.js +534 -0
  78. package/cli/commands/scan-skill.js +588 -0
  79. package/cli/commands/scan-standard.js +251 -0
  80. package/cli/commands/scan.js +524 -0
  81. package/cli/commands/score.js +449 -0
  82. package/cli/commands/shell.js +514 -0
  83. package/cli/commands/team-report.js +398 -0
  84. package/cli/commands/undo.js +161 -0
  85. package/cli/commands/update-intel.js +126 -0
  86. package/cli/commands/vibe-check.js +276 -0
  87. package/cli/commands/watch.js +757 -0
  88. package/cli/commands/web.js +63 -0
  89. package/cli/core/ast/guardrail-detector.js +141 -0
  90. package/cli/core/ast/index.js +22 -0
  91. package/cli/core/ast/parser.js +676 -0
  92. package/cli/core/ast/scope-tree.js +287 -0
  93. package/cli/core/ast/taint-tracker.js +158 -0
  94. package/cli/core/branding.js +37 -0
  95. package/cli/core/env.js +38 -0
  96. package/cli/core/errors.js +61 -0
  97. package/cli/core/fs.js +62 -0
  98. package/cli/core/output/compliance.js +90 -0
  99. package/cli/core/output/html-theme.js +158 -0
  100. package/cli/core/output/index.js +57 -0
  101. package/cli/core/output/json.js +48 -0
  102. package/cli/core/output/sarif.js +240 -0
  103. package/cli/core/version.js +67 -0
  104. package/cli/core/web/jobs.js +183 -0
  105. package/cli/core/web/projects.js +146 -0
  106. package/cli/core/web/server.js +439 -0
  107. package/cli/data/atlas-knowledge.json +5640 -0
  108. package/cli/data/eaa-catalog.json +39 -0
  109. package/cli/data/known-mcps.json +26 -0
  110. package/cli/data/probes/prompt-injection-corpus.json +271 -0
  111. package/cli/data/threat-intel.json +85 -0
  112. package/cli/data/threatpacks/latest.json +41 -0
  113. package/cli/hooks/patterns.js +313 -0
  114. package/cli/hooks/post-tool-use.js +140 -0
  115. package/cli/hooks/pre-tool-use.js +186 -0
  116. package/cli/index.js +90 -0
  117. package/cli/providers/llm-provider.js +766 -0
  118. package/cli/utils/autofix-rules.js +74 -0
  119. package/cli/utils/cache-manager.js +310 -0
  120. package/cli/utils/compliance-map.js +191 -0
  121. package/cli/utils/entropy.js +132 -0
  122. package/cli/utils/fix-ledger.js +127 -0
  123. package/cli/utils/hermes-tool-registry.js +252 -0
  124. package/cli/utils/intel/cache.js +61 -0
  125. package/cli/utils/intel/http.js +88 -0
  126. package/cli/utils/intel/index.js +235 -0
  127. package/cli/utils/intel/merge.js +229 -0
  128. package/cli/utils/intel/sources/epss.js +54 -0
  129. package/cli/utils/intel/sources/ghsa.js +81 -0
  130. package/cli/utils/intel/sources/gitguardian.js +40 -0
  131. package/cli/utils/intel/sources/gitleaks.js +101 -0
  132. package/cli/utils/intel/sources/kev.js +38 -0
  133. package/cli/utils/intel/sources/nvd.js +84 -0
  134. package/cli/utils/intel/sources/osv.js +132 -0
  135. package/cli/utils/intel/sources/phylum.js +44 -0
  136. package/cli/utils/intel/sources/snyk.js +46 -0
  137. package/cli/utils/intel/sources/socket.js +69 -0
  138. package/cli/utils/intel/sources/sonatype.js +84 -0
  139. package/cli/utils/intel/sources/threatpack.js +69 -0
  140. package/cli/utils/mcp-trust.js +60 -0
  141. package/cli/utils/output.js +251 -0
  142. package/cli/utils/patterns.js +1130 -0
  143. package/cli/utils/pdf-generator.js +94 -0
  144. package/cli/utils/plugin-loader.js +364 -0
  145. package/cli/utils/rule-import.js +228 -0
  146. package/cli/utils/rule-registry.js +426 -0
  147. package/cli/utils/scan-fingerprint.js +109 -0
  148. package/cli/utils/scan-playbook.js +312 -0
  149. package/cli/utils/score-history.js +119 -0
  150. package/cli/utils/secrets-verifier.js +247 -0
  151. package/cli/utils/security-memory.js +296 -0
  152. package/cli/utils/standards/atlas-knowledge.js +87 -0
  153. package/cli/utils/standards/index.js +127 -0
  154. package/cli/utils/standards/sources/avid.js +45 -0
  155. package/cli/utils/standards/sources/eu-ai-act.js +89 -0
  156. package/cli/utils/standards/sources/google-saif.js +39 -0
  157. package/cli/utils/standards/sources/iso-42001.js +94 -0
  158. package/cli/utils/standards/sources/mitre-atlas.js +54 -0
  159. package/cli/utils/standards/sources/nist-ai-600-1.js +45 -0
  160. package/cli/utils/standards/sources/owasp-llm.js +45 -0
  161. package/cli/utils/standards/sources/owasp-ml.js +45 -0
  162. package/cli/utils/threat-intel.js +265 -0
  163. package/configs/firebase/firestore-rules.txt +215 -0
  164. package/configs/firebase/security-checklist.md +236 -0
  165. package/configs/firebase/storage-rules.txt +206 -0
  166. package/configs/gitignore-template +258 -0
  167. package/configs/nextjs-security-headers.js +220 -0
  168. package/configs/praxisignore-template +50 -0
  169. package/configs/supabase/secure-client.ts +225 -0
  170. package/configs/supabase/security-checklist.md +278 -0
  171. package/docs/THIRD_PARTY_NOTICES.md +26 -0
  172. package/docs/THREAT_INTEL.md +292 -0
  173. package/docs/USAGE.md +1205 -0
  174. package/docs/design/WEB-UI.md +82 -0
  175. package/package.json +71 -0
  176. package/scripts/check-determinism.mjs +119 -0
  177. package/snippets/README.md +122 -0
  178. package/snippets/api-security/api-security-checklist.md +412 -0
  179. package/snippets/api-security/cors-config.ts +322 -0
  180. package/snippets/api-security/input-validation.ts +430 -0
  181. package/snippets/auth/jwt-checklist.md +322 -0
  182. package/snippets/rate-limiting/nextjs-middleware.ts +211 -0
  183. package/snippets/rate-limiting/upstash-ratelimit.ts +229 -0
@@ -0,0 +1,348 @@
1
+ /**
2
+ * AuthBypassAgent
3
+ * ================
4
+ *
5
+ * Detects authentication and authorization vulnerabilities:
6
+ * JWT misconfig, missing auth middleware, CSRF, session flaws,
7
+ * OAuth misconfig, cookie security, broken access control.
8
+ */
9
+
10
+ import path from 'path';
11
+ import { BaseAgent, createFinding } from './base-agent.js';
12
+
13
+ export const PATTERNS = [
14
+ // ── JWT Issues ─────────────────────────────────────────────────────────────
15
+ {
16
+ rule: 'JWT_ALG_NONE',
17
+ title: 'JWT Algorithm None Attack',
18
+ regex: /algorithms?\s*:\s*\[?\s*['"]none['"]/gi,
19
+ severity: 'critical',
20
+ cwe: 'CWE-327',
21
+ owasp: 'A02:2021',
22
+ description: 'Accepting alg: "none" in JWT allows forging tokens without a signature.',
23
+ fix: 'Explicitly set algorithms: ["RS256"] or ["ES256"]. Never accept "none".',
24
+ },
25
+ {
26
+ rule: 'JWT_WEAK_SECRET',
27
+ title: 'JWT Weak HMAC Secret',
28
+ regex: /jwt\.sign\s*\([^)]{0,200}?,\s*['"][^'"]{1,15}['"]/g,
29
+ severity: 'high',
30
+ cwe: 'CWE-326',
31
+ owasp: 'A02:2021',
32
+ description: 'Short JWT secret (<16 chars) is vulnerable to brute-force. Use a strong random secret.',
33
+ fix: 'Use a 256-bit (32+ char) random secret: require("crypto").randomBytes(32).toString("hex")',
34
+ },
35
+ {
36
+ rule: 'JWT_NO_EXPIRY',
37
+ title: 'JWT Without Expiration',
38
+ regex: /jwt\.sign\s*\([^)]*(?!expiresIn|exp)[^)]*\)/g,
39
+ severity: 'medium',
40
+ cwe: 'CWE-613',
41
+ owasp: 'A07:2021',
42
+ confidence: 'medium',
43
+ description: 'JWTs without expiration never expire. Set a short expiresIn (15m-1h).',
44
+ fix: 'Add { expiresIn: "15m" } to jwt.sign() options',
45
+ },
46
+ {
47
+ rule: 'JWT_VERIFY_DISABLED',
48
+ title: 'JWT Verification Disabled',
49
+ regex: /jwt\.decode\s*\(/g,
50
+ severity: 'high',
51
+ cwe: 'CWE-345',
52
+ owasp: 'A07:2021',
53
+ confidence: 'medium',
54
+ description: 'jwt.decode() does not verify the signature. Use jwt.verify() for authentication.',
55
+ fix: 'Use jwt.verify(token, secret) instead of jwt.decode(token)',
56
+ },
57
+
58
+ // ── Cookie Security ────────────────────────────────────────────────────────
59
+ {
60
+ rule: 'COOKIE_NO_HTTPONLY',
61
+ title: 'Cookie Missing httpOnly Flag',
62
+ regex: /(?:cookie|Cookie|setCookie|set-cookie)[^;]{0,100}(?:secure|domain|path|maxAge|max-age)(?![^;]*httpOnly)/gi,
63
+ severity: 'medium',
64
+ cwe: 'CWE-1004',
65
+ owasp: 'A05:2021',
66
+ confidence: 'medium',
67
+ description: 'Cookies without httpOnly can be stolen via XSS. Set httpOnly: true.',
68
+ fix: 'Add httpOnly: true to cookie options',
69
+ },
70
+ {
71
+ rule: 'COOKIE_NO_SECURE',
72
+ title: 'Cookie Missing secure Flag',
73
+ regex: /(?:res\.cookie|setCookie)\s*\([^)]*(?:httpOnly|domain)[^)]*(?!secure)/gi,
74
+ severity: 'medium',
75
+ cwe: 'CWE-614',
76
+ owasp: 'A05:2021',
77
+ confidence: 'medium',
78
+ description: 'Cookies without secure flag are sent over HTTP. Set secure: true in production.',
79
+ fix: 'Add secure: true to cookie options (ensures HTTPS-only transmission)',
80
+ },
81
+ {
82
+ rule: 'COOKIE_SAMESITE_NONE',
83
+ title: 'Cookie SameSite=None Without Secure',
84
+ regex: /sameSite\s*:\s*['"]?none['"]?/gi,
85
+ severity: 'high',
86
+ cwe: 'CWE-1275',
87
+ owasp: 'A05:2021',
88
+ description: 'SameSite=None without Secure exposes cookies to CSRF. Set Secure with SameSite=None.',
89
+ fix: 'Use sameSite: "strict" or "lax". If None is required, also set secure: true.',
90
+ },
91
+
92
+ // ── Session Security ───────────────────────────────────────────────────────
93
+ {
94
+ rule: 'SESSION_INSECURE_SECRET',
95
+ title: 'Hardcoded Session Secret',
96
+ regex: /session\s*\(\s*\{[^}]*secret\s*:\s*['"][^'"]{1,20}['"]/g,
97
+ severity: 'high',
98
+ cwe: 'CWE-798',
99
+ owasp: 'A07:2021',
100
+ description: 'Hardcoded short session secret is guessable. Use a strong random value from env.',
101
+ fix: 'Use process.env.SESSION_SECRET with a 256-bit random value',
102
+ },
103
+ {
104
+ rule: 'SESSION_NO_REGENERATE',
105
+ title: 'Session Not Regenerated After Login',
106
+ regex: /(?:login|authenticate|signIn)\s*(?:=|:)\s*(?:async\s+)?(?:function|\([^)]*\)\s*=>)/g,
107
+ severity: 'medium',
108
+ cwe: 'CWE-384',
109
+ owasp: 'A07:2021',
110
+ confidence: 'low',
111
+ description: 'Sessions should be regenerated after login to prevent session fixation.',
112
+ fix: 'Call req.session.regenerate() after successful authentication',
113
+ },
114
+
115
+ // ── CSRF ───────────────────────────────────────────────────────────────────
116
+ {
117
+ rule: 'CSRF_DISABLED',
118
+ title: 'CSRF Protection Disabled',
119
+ regex: /csrf\s*(?::\s*false|=\s*false|\.disable\(\))/gi,
120
+ severity: 'high',
121
+ cwe: 'CWE-352',
122
+ owasp: 'A01:2021',
123
+ description: 'CSRF protection is explicitly disabled. State-changing requests need CSRF tokens.',
124
+ fix: 'Enable CSRF protection or use SameSite=Strict cookies',
125
+ },
126
+
127
+ // ── OAuth / OIDC ───────────────────────────────────────────────────────────
128
+ {
129
+ rule: 'OAUTH_NO_STATE',
130
+ title: 'OAuth Missing State Parameter',
131
+ regex: /authorize_url|authorization_endpoint|auth_uri.*(?!state)/g,
132
+ severity: 'high',
133
+ cwe: 'CWE-352',
134
+ owasp: 'A07:2021',
135
+ confidence: 'low',
136
+ description: 'OAuth without state parameter is vulnerable to CSRF. Include a random state value.',
137
+ fix: 'Generate a random state parameter and verify it in the callback',
138
+ },
139
+ {
140
+ rule: 'OAUTH_WILDCARD_REDIRECT',
141
+ title: 'OAuth Permissive Redirect URI',
142
+ regex: /redirect_uri\s*[:=]\s*['"]?\*/g,
143
+ severity: 'critical',
144
+ cwe: 'CWE-601',
145
+ owasp: 'A07:2021',
146
+ description: 'Wildcard redirect URI allows OAuth token theft via open redirect.',
147
+ fix: 'Use exact redirect URIs. Never use wildcards in OAuth redirect configuration.',
148
+ },
149
+
150
+ // ── Password Security ──────────────────────────────────────────────────────
151
+ {
152
+ rule: 'WEAK_PASSWORD_HASH',
153
+ title: 'Weak Password Hashing (MD5/SHA)',
154
+ regex: /(?:createHash|hashlib\.)\s*\(\s*['"](?:md5|sha1|sha256)['"]\s*\).*(?:password|passwd)/gi,
155
+ severity: 'critical',
156
+ cwe: 'CWE-916',
157
+ owasp: 'A02:2021',
158
+ description: 'MD5/SHA are not suitable for password hashing. Use bcrypt, scrypt, or argon2.',
159
+ fix: 'Use bcrypt.hash(password, 12) or argon2.hash(password)',
160
+ },
161
+ {
162
+ rule: 'PLAINTEXT_PASSWORD_COMPARISON',
163
+ title: 'Plaintext Password Comparison',
164
+ regex: /(?:password|passwd)\s*(?:===?|==)\s*(?:req\.|request\.|body\.|query\.)/g,
165
+ severity: 'critical',
166
+ cwe: 'CWE-256',
167
+ owasp: 'A02:2021',
168
+ description: 'Comparing passwords in plaintext means they are stored unhashed. Hash passwords.',
169
+ fix: 'Use bcrypt.compare(inputPassword, hashedPassword)',
170
+ },
171
+
172
+ // ── Missing Auth Checks ────────────────────────────────────────────────────
173
+ {
174
+ rule: 'BOLA_DIRECT_ID',
175
+ title: 'Broken Object-Level Authorization (BOLA)',
176
+ regex: /(?:findById|findOne|findUnique|findByPk)\s*\(\s*(?:req\.params|req\.query|ctx\.params)/g,
177
+ severity: 'high',
178
+ cwe: 'CWE-639',
179
+ owasp: 'A01:2021',
180
+ description: 'Fetching by user-supplied ID without ownership check enables BOLA/IDOR.',
181
+ fix: 'Add ownership check: .findFirst({ where: { id: params.id, userId: session.user.id } })',
182
+ },
183
+ {
184
+ rule: 'MASS_ASSIGNMENT',
185
+ title: 'Mass Assignment Vulnerability',
186
+ regex: /(?:\.create|\.update|\.insert)\s*\(\s*(?:req\.body|request\.body|ctx\.request\.body)/g,
187
+ severity: 'high',
188
+ cwe: 'CWE-915',
189
+ owasp: 'A01:2021',
190
+ description: 'Passing full request body to create/update enables mass assignment attacks.',
191
+ fix: 'Destructure only allowed fields: const { name, email } = req.body; Model.create({ name, email })',
192
+ },
193
+
194
+ // ── Timing Attacks ─────────────────────────────────────────────────────────
195
+ {
196
+ rule: 'TIMING_ATTACK_COMPARISON',
197
+ title: 'Timing Attack: String Comparison',
198
+ regex: /(?:apiKey|api_key|token|secret|signature|hmac)\s*(?:===?|!==?)\s*/gi,
199
+ severity: 'medium',
200
+ cwe: 'CWE-208',
201
+ owasp: 'A02:2021',
202
+ confidence: 'medium',
203
+ description: 'Direct string comparison of secrets is vulnerable to timing attacks.',
204
+ fix: 'Use crypto.timingSafeEqual(Buffer.from(a), Buffer.from(b))',
205
+ },
206
+
207
+ // ── Rate Limiting ──────────────────────────────────────────────────────────
208
+ {
209
+ rule: 'NO_RATE_LIMIT_LOGIN',
210
+ title: 'No Rate Limiting on Authentication',
211
+ regex: /(?<!\w)(?:\/login|\/signin|\/auth|\/register|\/signup|\/reset-password)\s*['"]/g,
212
+ severity: 'medium',
213
+ cwe: 'CWE-307',
214
+ owasp: 'A07:2021',
215
+ confidence: 'low',
216
+ description: 'Auth endpoints without rate limiting are vulnerable to brute-force attacks.',
217
+ fix: 'Add rate limiting: express-rate-limit, @upstash/ratelimit, or Cloudflare rules',
218
+ },
219
+
220
+ // ── Weak Crypto ────────────────────────────────────────────────────────────
221
+ {
222
+ rule: 'WEAK_CRYPTO_MD5',
223
+ title: 'Weak Cryptography: MD5',
224
+ regex: /createHash\s*\(\s*['"]md5['"]\s*\)/gi,
225
+ severity: 'medium',
226
+ cwe: 'CWE-328',
227
+ owasp: 'A02:2021',
228
+ description: 'MD5 is cryptographically broken. Use SHA-256 or SHA-3 for integrity checks.',
229
+ fix: 'Use createHash("sha256") instead of createHash("md5")',
230
+ },
231
+ {
232
+ rule: 'WEAK_CRYPTO_SHA1',
233
+ title: 'Weak Cryptography: SHA-1',
234
+ regex: /createHash\s*\(\s*['"]sha1['"]\s*\)/gi,
235
+ severity: 'medium',
236
+ cwe: 'CWE-328',
237
+ owasp: 'A02:2021',
238
+ description: 'SHA-1 is collision-prone. Use SHA-256 or SHA-3.',
239
+ fix: 'Use createHash("sha256") instead of createHash("sha1")',
240
+ },
241
+ {
242
+ rule: 'WEAK_CRYPTO_ECB',
243
+ title: 'Weak Cryptography: ECB Mode',
244
+ regex: /(?:AES|DES).*ECB|createCipheriv\s*\(\s*['"](?:aes-\d+-ecb|des-ecb)['"]/gi,
245
+ severity: 'high',
246
+ cwe: 'CWE-327',
247
+ owasp: 'A02:2021',
248
+ description: 'ECB mode leaks patterns in ciphertext. Use CBC or GCM mode.',
249
+ fix: 'Use AES-256-GCM: createCipheriv("aes-256-gcm", key, iv)',
250
+ },
251
+ {
252
+ rule: 'HARDCODED_CRYPTO_KEY',
253
+ title: 'Hardcoded Encryption Key',
254
+ regex: /createCipher(?:iv)?\s*\([^,]+,\s*['"][^'"]+['"]/g,
255
+ severity: 'high',
256
+ cwe: 'CWE-321',
257
+ owasp: 'A02:2021',
258
+ description: 'Hardcoded encryption key in source code. Load from environment variables.',
259
+ fix: 'Use process.env.ENCRYPTION_KEY instead of hardcoded string',
260
+ },
261
+ {
262
+ rule: 'WEAK_RANDOM',
263
+ title: 'Insecure Random Number Generator',
264
+ regex: /Math\.random\s*\(\s*\).*(?:token|secret|key|password|salt|nonce|csrf|session)/gi,
265
+ severity: 'high',
266
+ cwe: 'CWE-338',
267
+ owasp: 'A02:2021',
268
+ description: 'Math.random() is not cryptographically secure. Use crypto.randomBytes().',
269
+ fix: 'Use crypto.randomBytes(32).toString("hex") or crypto.randomUUID()',
270
+ },
271
+
272
+ // ── Django/Flask Security ────────────────────────────────────────────────
273
+ {
274
+ rule: 'DJANGO_DEBUG_TRUE',
275
+ title: 'Django: DEBUG = True',
276
+ regex: /\bDEBUG\s*=\s*True\b/g,
277
+ severity: 'high',
278
+ cwe: 'CWE-215',
279
+ owasp: 'A05:2021',
280
+ description: 'Django DEBUG mode exposes stack traces, SQL queries, and settings to users.',
281
+ fix: 'Set DEBUG = False in production. Use environment variable: DEBUG = os.getenv("DEBUG", "False") == "True"',
282
+ },
283
+ {
284
+ rule: 'FLASK_SECRET_KEY_HARDCODED',
285
+ title: 'Flask: Hardcoded Secret Key',
286
+ regex: /app\.secret_key\s*=\s*['"][^'"]{1,30}['"]/g,
287
+ severity: 'high',
288
+ cwe: 'CWE-798',
289
+ owasp: 'A07:2021',
290
+ description: 'Flask secret key is hardcoded. Session cookies can be forged.',
291
+ fix: 'Use os.environ.get("SECRET_KEY") with a 256-bit random value',
292
+ },
293
+
294
+ // ── TLS/SSL ────────────────────────────────────────────────────────────────
295
+ {
296
+ rule: 'TLS_REJECT_UNAUTHORIZED',
297
+ title: 'TLS Certificate Verification Disabled',
298
+ regex: /NODE_TLS_REJECT_UNAUTHORIZED\s*[=:]\s*['"]?0['"]?/g,
299
+ severity: 'critical',
300
+ cwe: 'CWE-295',
301
+ owasp: 'A02:2021',
302
+ description: 'Disabling TLS verification exposes app to MITM attacks.',
303
+ fix: 'Remove NODE_TLS_REJECT_UNAUTHORIZED=0. Use proper CA certificates.', // praxis-ignore TLS_REJECT_UNAUTHORIZED — detector fix-text
304
+ },
305
+ {
306
+ rule: 'TLS_REJECT_UNAUTH_FALSE',
307
+ title: 'TLS rejectUnauthorized: false',
308
+ regex: /\brejectUnauthorized\s*:\s*false\b/g,
309
+ severity: 'high',
310
+ cwe: 'CWE-295',
311
+ owasp: 'A02:2021',
312
+ description: 'rejectUnauthorized: false disables TLS certificate checking.',
313
+ fix: 'Remove rejectUnauthorized: false, or use a proper CA bundle',
314
+ },
315
+ {
316
+ rule: 'TLS_VERIFY_FALSE_PYTHON',
317
+ title: 'TLS verify=False (Python)',
318
+ regex: /\brequests\.\w+\s*\([^)]*\bverify\s*=\s*False\b/g,
319
+ severity: 'high',
320
+ cwe: 'CWE-295',
321
+ owasp: 'A02:2021',
322
+ description: 'Python requests with verify=False disables SSL cert verification.',
323
+ fix: 'Remove verify=False, or pass verify="/path/to/ca-bundle.crt"',
324
+ },
325
+ ];
326
+
327
+ export class AuthBypassAgent extends BaseAgent {
328
+ constructor() {
329
+ super('AuthBypassAgent', 'Detect authentication and authorization vulnerabilities', 'auth');
330
+ }
331
+
332
+ async analyze(context) {
333
+ const { files } = context;
334
+ const codeFiles = files.filter(f => {
335
+ const ext = path.extname(f).toLowerCase();
336
+ return ['.js', '.jsx', '.ts', '.tsx', '.mjs', '.cjs', '.py', '.rb', '.php', '.go'].includes(ext);
337
+ });
338
+
339
+ let findings = [];
340
+ for (const file of codeFiles) {
341
+ findings = findings.concat(this.scanFileWithPatterns(file, PATTERNS));
342
+ }
343
+
344
+ return findings;
345
+ }
346
+ }
347
+
348
+ export default AuthBypassAgent;
@@ -0,0 +1,280 @@
1
+ /**
2
+ * BaseAgent — Foundation for all security scanning agents
3
+ * ========================================================
4
+ *
5
+ * Every agent in praxis extends BaseAgent. It provides:
6
+ * - Standard finding format
7
+ * - File discovery with skip-list support
8
+ * - Severity classification
9
+ * - Consistent output interface
10
+ *
11
+ * USAGE:
12
+ * class MyAgent extends BaseAgent {
13
+ * constructor() { super('MyAgent', 'Description', 'category'); }
14
+ * async analyze(context) { return [findings]; }
15
+ * }
16
+ */
17
+
18
+ import fs from 'fs';
19
+ import path from 'path';
20
+ import fg from 'fast-glob';
21
+ import { SKIP_DIRS, SKIP_EXTENSIONS, SKIP_FILENAMES, MAX_FILE_SIZE, MAX_SCAN_FILES, loadGitignorePatterns } from '../utils/patterns.js';
22
+
23
+ // =============================================================================
24
+ // FINDING FACTORY
25
+ // =============================================================================
26
+
27
+ /**
28
+ * Create a standardized finding object.
29
+ */
30
+ export function createFinding({
31
+ file,
32
+ line = 0,
33
+ column = 0,
34
+ severity = 'medium',
35
+ category = 'vulnerability',
36
+ rule,
37
+ title,
38
+ description,
39
+ matched = '',
40
+ confidence = 'high',
41
+ cwe = null,
42
+ owasp = null,
43
+ eaa = null,
44
+ fix = null,
45
+ }) {
46
+ return {
47
+ file,
48
+ line,
49
+ column,
50
+ severity,
51
+ category,
52
+ rule,
53
+ title,
54
+ description,
55
+ matched,
56
+ confidence,
57
+ cwe,
58
+ owasp,
59
+ ...(eaa ? { eaa } : {}),
60
+ fix,
61
+ };
62
+ }
63
+
64
+ // =============================================================================
65
+ // BASE AGENT CLASS
66
+ // =============================================================================
67
+
68
+ export class BaseAgent {
69
+ /**
70
+ * @param {string} name — Agent name (e.g. 'InjectionTester')
71
+ * @param {string} description — What this agent does
72
+ * @param {string} category — Finding category for scoring
73
+ */
74
+ constructor(name, description, category) {
75
+ this.name = name;
76
+ this.description = description;
77
+ this.category = category;
78
+ }
79
+
80
+ /**
81
+ * Run the agent's analysis on a codebase.
82
+ * Subclasses MUST override this method.
83
+ *
84
+ * @param {object} context — { rootPath, files, recon, options }
85
+ * @returns {Promise<object[]>} — Array of finding objects
86
+ */
87
+ async analyze(context) {
88
+ throw new Error(`${this.name}.analyze() not implemented`);
89
+ }
90
+
91
+ /**
92
+ * Whether this agent should run given the recon results.
93
+ * Override in subclasses to skip irrelevant scans.
94
+ * Default: always run.
95
+ */
96
+ shouldRun(recon) {
97
+ return true;
98
+ }
99
+
100
+ // ── Helpers available to all agents ─────────────────────────────────────────
101
+
102
+ /**
103
+ * Discover all scannable files in a directory.
104
+ * Respects SKIP_DIRS, SKIP_EXTENSIONS, and MAX_FILE_SIZE.
105
+ */
106
+ async discoverFiles(rootPath, extraGlobs = ['**/*']) {
107
+ const globIgnore = Array.from(SKIP_DIRS).map(dir => `**/${dir}/**`);
108
+
109
+ // Respect .gitignore patterns
110
+ const gitignoreGlobs = loadGitignorePatterns(rootPath);
111
+ globIgnore.push(...gitignoreGlobs);
112
+
113
+ // Load .praxisignore patterns
114
+ const ignorePatterns = this._loadIgnorePatterns(rootPath);
115
+ for (const p of ignorePatterns) {
116
+ if (p.endsWith('/')) {
117
+ globIgnore.push(`**/${p}**`);
118
+ } else {
119
+ globIgnore.push(`**/${p}`);
120
+ globIgnore.push(p);
121
+ }
122
+ }
123
+
124
+ const allFiles = await fg(extraGlobs, {
125
+ cwd: rootPath,
126
+ absolute: true,
127
+ onlyFiles: true,
128
+ ignore: globIgnore,
129
+ dot: true,
130
+ });
131
+
132
+ const capped = allFiles.length > MAX_SCAN_FILES ? allFiles.slice(0, MAX_SCAN_FILES) : allFiles;
133
+
134
+ return capped.filter(file => {
135
+ const ext = path.extname(file).toLowerCase();
136
+ if (SKIP_EXTENSIONS.has(ext)) return false;
137
+ const basename = path.basename(file);
138
+ if (SKIP_FILENAMES.has(basename)) return false;
139
+ if (basename.endsWith('.min.js') || basename.endsWith('.min.css')) return false;
140
+ try {
141
+ // Scanner hardening: refuse symlinks (a hostile repo could link to
142
+ // files outside the workspace) and cap file size.
143
+ const lstats = fs.lstatSync(file);
144
+ if (lstats.isSymbolicLink()) return false;
145
+ const stats = fs.statSync(file);
146
+ if (stats.size > MAX_FILE_SIZE) return false;
147
+ } catch {
148
+ return false;
149
+ }
150
+ return true;
151
+ });
152
+ }
153
+
154
+ /**
155
+ * Load .praxisignore patterns from the project root.
156
+ */
157
+ _loadIgnorePatterns(rootPath) {
158
+ const ignorePath = path.join(rootPath, '.praxisignore');
159
+ try {
160
+ if (!fs.existsSync(ignorePath)) return [];
161
+ return fs.readFileSync(ignorePath, 'utf-8')
162
+ .split('\n')
163
+ .map(l => l.trim())
164
+ .filter(l => l && !l.startsWith('#'));
165
+ } catch {
166
+ return [];
167
+ }
168
+ }
169
+
170
+ /**
171
+ * Get the files this agent should scan.
172
+ * If incremental scanning is active (changedFiles in context), returns only changed files.
173
+ * Otherwise returns all files. Agents that need the full file list can use context.files directly.
174
+ */
175
+ getFilesToScan(context) {
176
+ return context.changedFiles || context.files;
177
+ }
178
+
179
+ /**
180
+ * Read a file safely, returning null on failure.
181
+ */
182
+ readFile(filePath) {
183
+ try {
184
+ return fs.readFileSync(filePath, 'utf-8');
185
+ } catch {
186
+ return null;
187
+ }
188
+ }
189
+
190
+ /**
191
+ * Read a file and return its lines with line numbers.
192
+ */
193
+ readLines(filePath) {
194
+ const content = this.readFile(filePath);
195
+ if (!content) return [];
196
+ return content.split('\n');
197
+ }
198
+
199
+ /**
200
+ * Get surrounding code context for a finding.
201
+ */
202
+ getContext(filePath, lineNum, radius = 3) {
203
+ const lines = this.readLines(filePath);
204
+ if (lines.length === 0) return '';
205
+ const start = Math.max(0, lineNum - 1 - radius);
206
+ const end = Math.min(lines.length, lineNum + radius);
207
+ return lines.slice(start, end).join('\n');
208
+ }
209
+
210
+ /**
211
+ * Check if a line has the praxis-ignore suppression comment.
212
+ */
213
+ isSuppressed(line) {
214
+ return /praxis-ignore/i.test(line);
215
+ }
216
+
217
+ /**
218
+ * Scan file lines against an array of regex patterns.
219
+ * Returns findings for every match.
220
+ */
221
+ scanFileWithPatterns(filePath, patterns) {
222
+ const content = this.readFile(filePath);
223
+ if (!content) return [];
224
+
225
+ const lines = content.split('\n');
226
+ const findings = [];
227
+
228
+ for (let i = 0; i < lines.length; i++) {
229
+ const line = lines[i];
230
+ if (this.isSuppressed(line)) continue;
231
+
232
+ for (const p of patterns) {
233
+ p.regex.lastIndex = 0;
234
+ let match;
235
+ while ((match = p.regex.exec(line)) !== null) {
236
+ const finding = createFinding({
237
+ file: filePath,
238
+ line: i + 1,
239
+ column: match.index + 1,
240
+ severity: p.severity || 'medium',
241
+ category: this.category,
242
+ rule: p.rule,
243
+ title: p.title,
244
+ description: p.description,
245
+ matched: match[0],
246
+ confidence: p.confidence || 'high',
247
+ cwe: p.cwe || null,
248
+ owasp: p.owasp || null,
249
+ fix: p.fix || null,
250
+ });
251
+ // Attach surrounding code context (3 lines before/after)
252
+ const start = Math.max(0, i - 3);
253
+ const end = Math.min(lines.length, i + 4);
254
+ finding.codeContext = lines.slice(start, end).map((l, idx) => ({
255
+ line: start + idx + 1,
256
+ text: l,
257
+ highlight: (start + idx) === i,
258
+ }));
259
+ findings.push(finding);
260
+ }
261
+ }
262
+ }
263
+
264
+ return findings;
265
+ }
266
+
267
+ /**
268
+ * Check if content imports or requires a specific module.
269
+ */
270
+ hasImport(content, moduleName) {
271
+ const importRe = new RegExp(
272
+ `(?:import\\s+.*from\\s+['"]${moduleName}['"])|` +
273
+ `(?:require\\s*\\(\\s*['"]${moduleName}['"]\\s*\\))`,
274
+ 'g'
275
+ );
276
+ return importRe.test(content);
277
+ }
278
+ }
279
+
280
+ export default BaseAgent;