redgun-security 1.0.0 → 1.2.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/README.md CHANGED
@@ -10,25 +10,24 @@
10
10
  </p>
11
11
 
12
12
  <p align="center">
13
- <strong>Black-box & white-box security auditor for web applications — HackTricks Enhanced.</strong>
13
+ <strong>Black-box & white-box security auditor for web applications — Enhanced.</strong>
14
14
  </p>
15
15
 
16
16
  <p align="center">
17
17
  <a href="https://github.com/aloc999/redgun/blob/main/LICENSE"><img src="https://img.shields.io/badge/license-MIT-blue" alt="License"></a>
18
18
  <img src="https://img.shields.io/badge/node-%3E%3D18-green" alt="Node">
19
- <img src="https://img.shields.io/badge/modules-39-ff4444" alt="Modules">
20
- <img src="https://img.shields.io/badge/HackTricks-Enhanced-critical" alt="HackTricks">
19
+ <img src="https://img.shields.io/badge/modules-51-ff4444" alt="Modules">
21
20
  </p>
22
21
 
23
22
  <br>
24
23
 
25
24
  ## What is RedGun?
26
25
 
27
- RedGun is a security auditing CLI tool that finds vulnerabilities in your web applications. It includes **39 security modules** covering techniques from [HackTricks](https://book.hacktricks.wiki). Two modes:
26
+ RedGun is a security auditing CLI tool that finds vulnerabilities in your web applications. It includes **51 security modules** covering techniques from [HackTricks](https://book.hacktricks.wiki), [PortSwigger Web Security Academy](https://portswigger.net/web-security), [Katana](https://github.com/projectdiscovery/katana), and [httpx](https://github.com/projectdiscovery/httpx). Two modes:
28
27
 
29
- **Remote scan** (black-box): Give it a URL. It tests your site from the outside — XSS, SQLi, SSRF, CORS, CRLF injection, cache poisoning, host header injection, HTTP request smuggling, GraphQL introspection, path traversal, NoSQL injection, and more.
28
+ **Remote scan** (black-box): Give it a URL. It crawls with Katana-style JS parsing, fingerprints with httpx-style probing, then tests — XSS, SQLi, SSRF, CORS, XXE, OAuth, IDOR, cache deception, DOM-based, HTTP smuggling, CRLF, parameter pollution, file upload, and more.
30
29
 
31
- **Local audit** (white-box): Point it at your project directory. It reads your source code checking for secrets, SSTI, insecure deserialization, prototype pollution, JWT vulnerabilities, command injection, weak crypto, path traversal, and more.
30
+ **Local audit** (white-box): Point it at your project directory. It reads your source code checking for secrets, SSTI, XXE, insecure deserialization, prototype pollution, JWT attacks, OAuth flaws, IDOR, business logic, command injection, weak crypto, and more.
32
31
 
33
32
  <br>
34
33
 
@@ -51,10 +50,12 @@ redgun modules # List all modules
51
50
 
52
51
  <br>
53
52
 
54
- ## Remote Scan Modules (25 — Black-box)
53
+ ## Remote Scan Modules (33 — Black-box)
55
54
 
56
55
  | Module | What it tests | Source |
57
56
  |---|---|---|
57
+ | **Probe & Fingerprint** | Status code, title, technologies (40+), CDN/WAF detection, favicon hash, response time, virtual host discovery | httpx |
58
+ | **Crawl & Extract** | JS file parsing, endpoint extraction, form discovery, parameter mining, email harvesting, secret detection in bundles | Katana |
58
59
  | **HTTP Headers** | Missing CSP, HSTS, X-Frame-Options, X-Content-Type-Options, Referrer-Policy, Permissions-Policy, COOP, CORP, COEP | OWASP |
59
60
  | **Exposed Files** | `.env`, `.git/config`, `package.json`, `.DS_Store`, source maps, actuator, swagger, phpinfo, Docker files, backups | HackTricks |
60
61
  | **Secrets Detection** | API keys (AWS, Stripe, Firebase, Supabase, OpenAI, Anthropic), tokens, passwords in page source | HackTricks |
@@ -80,10 +81,18 @@ redgun modules # List all modules
80
81
  | **WebSocket Security** | Origin validation, authentication checks | HackTricks |
81
82
  | **Cache Poisoning** | Unkeyed headers (X-Forwarded-Host, X-Forwarded-Scheme, X-Original-URL) | HackTricks |
82
83
  | **Race Conditions** | Detection guidance for concurrent request attacks | HackTricks |
84
+ | **XXE Injection** | XML entity injection at upload/import/SOAP endpoints | PortSwigger |
85
+ | **OAuth Misconfiguration** | redirect_uri validation, OIDC config exposure, implicit flow detection | PortSwigger |
86
+ | **Access Control Bypass** | Admin panel exposure, 403 bypass via X-Original-URL/X-Forwarded-For, robots.txt disclosure | PortSwigger |
87
+ | **Web Cache Deception** | Static extension cache deception, path normalization inconsistency | PortSwigger |
88
+ | **Parameter Pollution** | HTTP Parameter Pollution, null byte truncation, duplicate params | PortSwigger |
89
+ | **File Upload Testing** | Upload endpoint discovery, OPTIONS probing | PortSwigger |
90
+ | **DOM-Based Vulnerabilities** | DOM sinks (document.write, innerHTML, eval, postMessage), source-to-sink flow | PortSwigger |
91
+ | **HTTP/2 Attacks** | H2.CL/H2.TE smuggling indicators, HPACK injection surface | PortSwigger |
83
92
 
84
93
  <br>
85
94
 
86
- ## Local Audit Modules (14 — White-box)
95
+ ## Local Audit Modules (18 — White-box)
87
96
 
88
97
  | Module | What it checks | Source |
89
98
  |---|---|---|
@@ -101,6 +110,10 @@ redgun modules # List all modules
101
110
  | **Path Traversal / LFI** | User input in file paths, readFile, sendFile, include/require | HackTricks |
102
111
  | **Command Injection** | exec, spawn, child_process, system, subprocess with user input, shell interpolation | HackTricks |
103
112
  | **Weak Cryptography** | MD5, SHA1, DES, RC4, ECB mode, Math.random, hardcoded keys/IVs | HackTricks |
113
+ | **XXE Detection** | XML parsers without entity disabled, DOMParser, lxml, simplexml with user input | PortSwigger |
114
+ | **Access Control / IDOR** | Direct object reference, role from user input, admin headers, ownership checks | PortSwigger |
115
+ | **OAuth / OIDC Flaws** | redirect_uri manipulation, missing state, client_secret exposure, token storage | PortSwigger |
116
+ | **Business Logic** | Price manipulation, negative quantity, workflow step skipping, race conditions, referral abuse | PortSwigger |
104
117
 
105
118
  <br>
106
119
 
@@ -209,9 +222,9 @@ Create a `.redgunignore` file to exclude files from local audit:
209
222
 
210
223
  <br>
211
224
 
212
- ## HackTricks Techniques Included
225
+ ## Techniques & Sources
213
226
 
214
- RedGun integrates techniques documented in [HackTricks](https://book.hacktricks.wiki):
227
+ ### HackTricks Techniques
215
228
 
216
229
  - **SSRF** — AWS/GCP metadata, internal IP bypass, DNS rebinding indicators
217
230
  - **SSTI** — Template engine detection and exploitation patterns
@@ -231,6 +244,37 @@ RedGun integrates techniques documented in [HackTricks](https://book.hacktricks.
231
244
  - **Subdomain Takeover** — Dangling CNAME detection
232
245
  - **Weak Cryptography** — Deprecated algorithms and hardcoded key detection
233
246
 
247
+ ### PortSwigger Web Security Academy Techniques
248
+
249
+ - **XXE (XML External Entity)** — Entity injection, DTD-based file read, blind OOB XXE, parameter entities
250
+ - **Access Control** — Horizontal/vertical privilege escalation, IDOR, 403 bypass via headers (X-Original-URL, X-Rewrite-URL), referer-based control
251
+ - **OAuth 2.0 Vulnerabilities** — redirect_uri manipulation, state CSRF, implicit flow token theft, client_secret exposure, PKCE bypass
252
+ - **Business Logic** — Price manipulation, negative quantity, workflow step skipping, race condition exploitation, referral abuse, trial abuse
253
+ - **Web Cache Deception** — Static extension deception, path normalization inconsistency, cache key manipulation
254
+ - **DOM-Based Vulnerabilities** — Source-to-sink analysis, document.write, innerHTML, eval, postMessage hijacking
255
+ - **HTTP Parameter Pollution** — Duplicate parameters, server-side truncation, null byte injection
256
+ - **File Upload** — Unrestricted upload, extension bypass, content-type manipulation, polyglot files
257
+ - **HTTP/2 Attacks** — H2.CL smuggling, H2.TE smuggling, HPACK header injection, request tunneling
258
+
259
+ ### Katana (ProjectDiscovery) Techniques
260
+
261
+ - **JavaScript Crawling** — Parse all JS bundles including lazy-loaded chunks for endpoints
262
+ - **Endpoint Extraction** — API routes, fetch/axios calls, URL patterns from source
263
+ - **Form Discovery** — Automatic form detection with CSRF and sensitive field analysis
264
+ - **Parameter Mining** — Extract all parameters from URLs, forms, and JS source
265
+ - **Secret Extraction** — API keys, tokens, and credentials from JS bundles
266
+ - **Email Harvesting** — Email addresses from page source for social engineering
267
+
268
+ ### httpx (ProjectDiscovery) Techniques
269
+
270
+ - **Technology Detection** — 40+ technologies fingerprinted (frameworks, CMS, servers, BaaS, analytics)
271
+ - **CDN/WAF Detection** — Cloudflare, AWS CloudFront, Fastly, Akamai, Imperva, Sucuri, Azure, Vercel, Netlify
272
+ - **WAF Fingerprinting** — ModSecurity, Wordfence, F5 BIG-IP, Cloudflare WAF, Incapsula
273
+ - **Favicon Hashing** — MD5 hash for Shodan-style fingerprinting
274
+ - **Virtual Host Discovery** — Host header manipulation to find hidden vhosts
275
+ - **Response Analysis** — Status codes, content-length, response time, title extraction
276
+ - **TLS/Certificate Info** — Protocol detection, certificate validation
277
+
234
278
  <br>
235
279
 
236
280
  ## Project Structure
@@ -247,7 +291,7 @@ redgun/
247
291
  │ │ ├── console.js # Terminal output
248
292
  │ │ ├── json.js # JSON + SARIF export
249
293
  │ │ └── html.js # HTML report
250
- │ ├── local/ # White-box modules (14)
294
+ │ ├── local/ # White-box modules (18)
251
295
  │ │ ├── index.js # Module orchestrator
252
296
  │ │ ├── secrets.js # Source code secrets
253
297
  │ │ ├── env.js # .env audit
@@ -255,18 +299,26 @@ redgun/
255
299
  │ │ ├── code-vulnerabilities.js # SQLi, XSS, eval, ReDoS
256
300
  │ │ ├── auth.js # Auth & middleware
257
301
  │ │ ├── headers-config.js # CSP/HSTS config
258
- │ │ ├── ssrf.js # SSRF detection
259
- │ │ ├── ssti.js # SSTI detection
260
- │ │ ├── deserialization.js # Insecure deserialization
261
- │ │ ├── prototype-pollution.js # Prototype pollution
262
- │ │ ├── jwt.js # JWT vulnerabilities
263
- │ │ ├── path-traversal.js # LFI/path traversal
264
- │ │ ├── command-injection.js # OS command injection
265
- │ │ └── crypto.js # Weak cryptography
302
+ │ │ ├── ssrf.js # SSRF (HackTricks)
303
+ │ │ ├── ssti.js # SSTI (HackTricks)
304
+ │ │ ├── deserialization.js # Insecure deserialization (HackTricks)
305
+ │ │ ├── prototype-pollution.js # Prototype pollution (HackTricks)
306
+ │ │ ├── jwt.js # JWT vulnerabilities (HackTricks)
307
+ │ │ ├── path-traversal.js # LFI/path traversal (HackTricks)
308
+ │ │ ├── command-injection.js # OS command injection (HackTricks)
309
+ │ │ ├── crypto.js # Weak cryptography (HackTricks)
310
+ │ │ ├── xxe.js # XXE detection (PortSwigger)
311
+ │ │ ├── access-control.js # IDOR/access control (PortSwigger)
312
+ │ │ ├── oauth.js # OAuth/OIDC flaws (PortSwigger)
313
+ │ │ └── business-logic.js # Business logic (PortSwigger)
314
+ │ ├── remote/ # Black-box enhanced modules
315
+ │ │ ├── crawler.js # Katana-style JS crawler
316
+ │ │ ├── probe.js # httpx-style fingerprinting
317
+ │ │ └── portswigger.js # PortSwigger remote tests
266
318
  │ └── utils/
267
319
  │ ├── fetch.js # HTTP with timeout
268
320
  │ └── patterns.js # Shared regex patterns
269
- ├── scan.js # Remote scan engine (25 modules)
321
+ ├── scan.js # Remote scan engine (33 modules)
270
322
  ├── action.yml # GitHub Action definition
271
323
  ├── .github/workflows/security.yml
272
324
  └── package.json
package/bin/redgun.js CHANGED
@@ -19,7 +19,7 @@ const program = new Command();
19
19
 
20
20
  program
21
21
  .name('redgun')
22
- .description('Black-box & white-box security auditor for web applications (HackTricks Enhanced)')
22
+ .description('Black-box & white-box security auditor for web applications (Enhanced)')
23
23
  .version('1.0.0');
24
24
 
25
25
  program
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "redgun-security",
3
- "version": "1.0.0",
3
+ "version": "1.2.0",
4
4
  "description": "Black-box & white-box security auditor for web applications with HackTricks techniques",
5
5
  "type": "module",
6
6
  "main": "scan.js",
@@ -20,10 +20,16 @@
20
20
  "vulnerability",
21
21
  "audit",
22
22
  "hacktricks",
23
+ "portswigger",
24
+ "katana",
25
+ "httpx",
23
26
  "xss",
24
27
  "sqli",
25
28
  "ssrf",
26
29
  "ssti",
30
+ "xxe",
31
+ "idor",
32
+ "oauth",
27
33
  "web-security"
28
34
  ],
29
35
  "author": "aloc999",
package/scan.js CHANGED
@@ -1,6 +1,10 @@
1
1
  import { addFinding } from './src/core/findings.js';
2
2
  import { fetchText, checkUrl } from './src/utils/fetch.js';
3
3
  import { EXPOSED_FILES, HEADER_CHECKS, COMMON_SUBDOMAINS, COMMON_PORTS, SECRET_PATTERNS } from './src/utils/patterns.js';
4
+ import { runCrawler } from './src/remote/crawler.js';
5
+ import { runProbe } from './src/remote/probe.js';
6
+ import { scanXxeRemote, scanOauthRemote, scanAccessControlRemote, scanWebCacheDeception, scanParameterPollution, scanFileUpload, scanDomBased, scanHttp2 } from './src/remote/portswigger.js';
7
+ import { scanSamlRemote, scanLdapRemote, scanMfaBypass, scanWebsocketReplay, scanPasswordReset, scanCsrfRemote, scanDanglingDns, scanCloudRemote } from './src/remote/advanced.js';
4
8
 
5
9
  export async function runRemoteScan(url, spinner, modules = null) {
6
10
  const target = new URL(url);
@@ -8,6 +12,8 @@ export async function runRemoteScan(url, spinner, modules = null) {
8
12
  const origin = target.origin;
9
13
 
10
14
  const allModules = [
15
+ { name: 'Probe & Fingerprint (httpx)', value: 'probe', fn: () => runProbe(origin, spinner) },
16
+ { name: 'Crawl & Extract (Katana)', value: 'crawl', fn: () => runCrawler(origin, spinner) },
11
17
  { name: 'HTTP Headers', value: 'headers', fn: () => scanHeaders(origin, spinner) },
12
18
  { name: 'Exposed Files & Paths', value: 'files', fn: () => scanExposedFiles(origin, spinner) },
13
19
  { name: 'Secrets in JS Bundles', value: 'secrets', fn: () => scanSecrets(origin, spinner) },
@@ -33,6 +39,22 @@ export async function runRemoteScan(url, spinner, modules = null) {
33
39
  { name: 'WebSocket Security (HackTricks)', value: 'websocket', fn: () => scanWebsocket(origin, spinner) },
34
40
  { name: 'Cache Poisoning (HackTricks)', value: 'cache', fn: () => scanCachePoisoning(origin, spinner) },
35
41
  { name: 'Race Condition Detection (HackTricks)', value: 'race', fn: () => scanRaceCondition(origin, spinner) },
42
+ { name: 'XXE Injection (PortSwigger)', value: 'xxe', fn: () => scanXxeRemote(origin, spinner) },
43
+ { name: 'OAuth Misconfiguration (PortSwigger)', value: 'oauth', fn: () => scanOauthRemote(origin, spinner) },
44
+ { name: 'Access Control Bypass (PortSwigger)', value: 'acl', fn: () => scanAccessControlRemote(origin, spinner) },
45
+ { name: 'Web Cache Deception (PortSwigger)', value: 'wcd', fn: () => scanWebCacheDeception(origin, spinner) },
46
+ { name: 'Parameter Pollution (PortSwigger)', value: 'hpp', fn: () => scanParameterPollution(origin, spinner) },
47
+ { name: 'File Upload Testing (PortSwigger)', value: 'upload', fn: () => scanFileUpload(origin, spinner) },
48
+ { name: 'DOM-Based Vulnerabilities (PortSwigger)', value: 'dom', fn: () => scanDomBased(origin, spinner) },
49
+ { name: 'HTTP/2 Attacks (PortSwigger)', value: 'h2', fn: () => scanHttp2(origin, spinner) },
50
+ { name: 'SAML/SSO Attacks', value: 'saml', fn: () => scanSamlRemote(origin, spinner) },
51
+ { name: 'LDAP Injection', value: 'ldap', fn: () => scanLdapRemote(origin, spinner) },
52
+ { name: 'MFA Bypass Testing', value: 'mfa', fn: () => scanMfaBypass(origin, spinner) },
53
+ { name: 'WebSocket Replay/CSWSH', value: 'wshijack', fn: () => scanWebsocketReplay(origin, spinner) },
54
+ { name: 'Password Reset Security', value: 'pwdreset', fn: () => scanPasswordReset(origin, spinner) },
55
+ { name: 'CSRF Token Analysis (remote)', value: 'csrf', fn: () => scanCsrfRemote(origin, spinner) },
56
+ { name: 'Subdomain Takeover (Dangling DNS)', value: 'takeover', fn: () => scanDanglingDns(hostname, spinner) },
57
+ { name: 'Cloud Metadata SSRF', value: 'cloudmeta', fn: () => scanCloudRemote(origin, spinner) },
36
58
  ];
37
59
 
38
60
  const toRun = modules ? allModules.filter((m) => modules.includes(m.value)) : allModules;
@@ -19,7 +19,7 @@ export function printBanner() {
19
19
  ██║ ██║███████╗██████╔╝╚██████╔╝╚██████╔╝██║ ╚████║
20
20
  ╚═╝ ╚═╝╚══════╝╚═════╝ ╚═════╝ ╚═════╝ ╚═╝ ╚═══╝
21
21
  `));
22
- console.log(chalk.gray(' Black-box & white-box security auditor | HackTricks Enhanced\n'));
22
+ console.log(chalk.gray(' Black-box & white-box security auditor | Enhanced\n'));
23
23
  }
24
24
 
25
25
  export function printResults() {
@@ -89,7 +89,7 @@ export function exportHtml(outputDir = './scans') {
89
89
  <h2>Findings (${findings.length} total)</h2>
90
90
  ${findingsHtml}
91
91
  <div class="footer">
92
- <p>RedGun Security Scanner v1.0.0 | HackTricks Enhanced</p>
92
+ <p>RedGun Security Scanner v1.1.0 | Enhanced</p>
93
93
  </div>
94
94
  </div>
95
95
  </body>
@@ -0,0 +1,64 @@
1
+ import { readFileSync, readdirSync, statSync } from 'fs';
2
+ import { join, extname } from 'path';
3
+ import { addFinding } from '../core/findings.js';
4
+
5
+ const SCAN_EXTENSIONS = ['.js', '.ts', '.jsx', '.tsx', '.mjs', '.py', '.rb', '.php', '.go', '.java'];
6
+ const IGNORE_DIRS = ['node_modules', '.git', 'dist', 'build', '__pycache__', 'vendor'];
7
+
8
+ export async function auditAccessControl(projectPath, spinner) {
9
+ spinner.text = 'Scanning for access control vulnerabilities (PortSwigger)...';
10
+ const files = getFiles(projectPath);
11
+
12
+ for (const file of files) {
13
+ try {
14
+ const content = readFileSync(file, 'utf-8');
15
+ const relativePath = file.replace(projectPath, '.');
16
+ const lines = content.split('\n');
17
+
18
+ const patterns = [
19
+ { pattern: /(?:req|request)\.(?:params|query|body)\.\s*(?:user_?id|userId|uid|owner_?id|account_?id)/gi, name: 'IDOR - user ID from request parameters', severity: 'HIGH' },
20
+ { pattern: /(?:isAdmin|is_admin|role)\s*[:=]\s*(?:req|request|params|query|body)/gi, name: 'Role/privilege from user input', severity: 'CRITICAL' },
21
+ { pattern: /(?:if|when)\s*\(\s*(?:req|request)\.(?:headers|query|params)\[?\s*['"]?(?:x-admin|admin|role|is[-_]?admin)/gi, name: 'Admin check via client-controlled header/param', severity: 'CRITICAL' },
22
+ { pattern: /\.findById\s*\(\s*(?:req|params|query|body)\./gi, name: 'Direct object reference without ownership check', severity: 'HIGH' },
23
+ { pattern: /\.findOne\s*\(\s*\{\s*(?:_id|id)\s*:\s*(?:req|params|query|body)\./gi, name: 'Database lookup by user-supplied ID (potential IDOR)', severity: 'HIGH' },
24
+ { pattern: /\.delete\s*\(\s*['"`]\/[^'"`]*:id/gi, name: 'DELETE route with :id param (check authorization)', severity: 'MEDIUM' },
25
+ { pattern: /\.put\s*\(\s*['"`]\/[^'"`]*:id/gi, name: 'PUT route with :id param (check authorization)', severity: 'MEDIUM' },
26
+ { pattern: /(?:admin|dashboard|manage|internal).*(?:app\.get|router\.get|@app\.route)/gi, name: 'Admin route - verify middleware protection', severity: 'MEDIUM' },
27
+ { pattern: /res\.json\s*\(\s*(?:users|accounts|orders|payments|transactions)/gi, name: 'Bulk data exposure without filtering', severity: 'MEDIUM' },
28
+ { pattern: /X-Original-URL|X-Rewrite-URL/gi, name: 'URL override headers (access control bypass)', severity: 'HIGH' },
29
+ { pattern: /referer\s*(?:===?|!==?|includes|match)/gi, name: 'Referer-based access control (easily spoofed)', severity: 'HIGH' },
30
+ { pattern: /(?:hidden|type=['"]hidden['"])\s*.*(?:role|admin|privilege|permission)/gi, name: 'Hidden form field for access control', severity: 'HIGH' },
31
+ ];
32
+
33
+ for (const { pattern, name, severity } of patterns) {
34
+ let match;
35
+ while ((match = pattern.exec(content)) !== null) {
36
+ const lineNum = content.substring(0, match.index).split('\n').length;
37
+ addFinding(
38
+ severity,
39
+ 'Access Control (PortSwigger)',
40
+ name,
41
+ `File: ${relativePath}:${lineNum}\nCode: ${lines[lineNum - 1]?.trim().substring(0, 120)}`,
42
+ 'Implement server-side authorization checks. Verify object ownership on every request. Use middleware for role-based access control. Never trust client-supplied IDs without verifying the requesting user owns that resource.'
43
+ );
44
+ }
45
+ }
46
+ } catch {}
47
+ }
48
+ }
49
+
50
+ function getFiles(dir, files = []) {
51
+ try {
52
+ const entries = readdirSync(dir);
53
+ for (const entry of entries) {
54
+ if (IGNORE_DIRS.includes(entry) || entry.startsWith('.')) continue;
55
+ const fullPath = join(dir, entry);
56
+ try {
57
+ const stat = statSync(fullPath);
58
+ if (stat.isDirectory()) getFiles(fullPath, files);
59
+ else if (SCAN_EXTENSIONS.includes(extname(entry).toLowerCase()) && stat.size < 512 * 1024) files.push(fullPath);
60
+ } catch {}
61
+ }
62
+ } catch {}
63
+ return files;
64
+ }
@@ -0,0 +1,72 @@
1
+ import { readFileSync, readdirSync, statSync } from 'fs';
2
+ import { join, extname } from 'path';
3
+ import { addFinding } from '../core/findings.js';
4
+
5
+ const SCAN_EXTENSIONS = ['.js', '.ts', '.jsx', '.tsx', '.py', '.rb', '.php', '.go', '.java'];
6
+ const IGNORE_DIRS = ['node_modules', '.git', 'dist', 'build', '__pycache__', 'vendor'];
7
+
8
+ export async function auditAto(projectPath, spinner) {
9
+ spinner.text = 'Scanning for Account Takeover patterns...';
10
+ const files = getFiles(projectPath);
11
+
12
+ for (const file of files) {
13
+ try {
14
+ const content = readFileSync(file, 'utf-8');
15
+ const relativePath = file.replace(projectPath, '.');
16
+ const lines = content.split('\n');
17
+
18
+ const patterns = [
19
+ { pattern: /(?:password|pwd).*reset.*token\s*=\s*(?:Math\.random|Date\.now|uuid|random)/gi, name: 'Password reset token from predictable source', severity: 'CRITICAL' },
20
+ { pattern: /reset.*token\s*=\s*(?:hash|md5|sha1)\s*\(/gi, name: 'Reset token hashed with weak algorithm', severity: 'MEDIUM' },
21
+ { pattern: /reset.*token\s*.*(?:url|link|href).*\$\{.*token/gi, name: 'Reset token exposed in URL (referer leakage)', severity: 'HIGH' },
22
+ { pattern: /(?:email|phone)\s*.*\s*change.*\s*\((?!.*confirm|verify|reauth|password)/gi, name: 'Email/phone change without re-authentication', severity: 'HIGH' },
23
+ { pattern: /logout\s*\([^)]*\)\s*\{[^}]*(?:session\.destroy|clearCookie|cookie.*clear)/gi, name: 'Logout implementation (check server-side invalidation)', severity: 'INFO' },
24
+ { pattern: /(?:session|token|jwt)\s*.*\s*(?:invalidation|revoke|blacklist|expire|expiry)/gi, name: 'Session/token revocation', severity: 'INFO' },
25
+ { pattern: /otp\s*.*generate.*\s*=\s*(?:Math\.floor|Math\.random|random)/gi, name: 'OTP from predictable source', severity: 'CRITICAL' },
26
+ { pattern: /otp.*length\s*[<=]?\s*[0-5]/gi, name: 'Short OTP length (< 6 digits)', severity: 'HIGH' },
27
+ { pattern: /otp.*(?:expir|ttl|validity|valid.*for).*[>=]?\s*(?:60\d|7\d|8\d|9\d)\d*/gi, name: 'Long OTP validity (> 10 min)', severity: 'MEDIUM' },
28
+ { pattern: /(?:login|auth|signin)\s*.*\s*attempt.*\s*=\s*0/gi, name: 'Rate limit on login (good)', severity: 'INFO' },
29
+ { pattern: /(?:password|credential|account|login).*(?:lock|throttle|attempt|brute|restrict|limit)/gi, name: 'Account lockout/throttling detected', severity: 'INFO' },
30
+ { pattern: /(?:social|oauth|google|github|facebook).*(?:link|connect|attach).*(?!.*confirm|verify|reauth)/gi, name: 'Social account linking without re-auth', severity: 'HIGH' },
31
+ { pattern: /(?:MFA|2FA|two.?factor|multi.?factor).*(?:skip|disabled?|bypass|false)/gi, name: 'MFA skip/bypass condition', severity: 'CRITICAL' },
32
+ { pattern: /(?:MFA|2FA).*\..*secret.*\s*=.*['"][a-zA-Z0-9]{10,}['"]/gi, name: 'MFA secret hardcoded', severity: 'CRITICAL' },
33
+ { pattern: /(?:register|signup|create).*(?:duplicate|exist|unique).*email|username/gi, name: 'User enumeration via register endpoint', severity: 'MEDIUM' },
34
+ { pattern: /(?:login|signin).*(?:incorrect|wrong|invalid|not found).*(?:password|email)/gi, name: 'User enumeration via verbose login errors', severity: 'MEDIUM' },
35
+ { pattern: /res\.sendStatus\s*\(\s*401|res\.json.*invalid.*credentials/gi, name: 'Generic error handling (good for preventing enumeration)', severity: 'INFO' },
36
+ ];
37
+
38
+ for (const { pattern, name, severity } of patterns) {
39
+ let match;
40
+ while ((match = pattern.exec(content)) !== null) {
41
+ const lineNum = content.substring(0, match.index).split('\n').length;
42
+ const line = lines[lineNum - 1]?.trim() || '';
43
+ if (line.startsWith('//') || line.startsWith('#') || line.startsWith('*')) continue;
44
+
45
+ addFinding(
46
+ severity,
47
+ 'Account Takeover (ATO)',
48
+ name,
49
+ `File: ${relativePath}:${lineNum}\nCode: ${line.substring(0, 120)}`,
50
+ 'Use crypto.randomBytes for reset tokens. Expire tokens quickly (< 15 min). Require current password to change email/password or link social accounts. Use TOTP-based MFA (not SMS). Use generic error messages. Implement proper rate limiting and account lockout (not just IP-based).'
51
+ );
52
+ }
53
+ }
54
+ } catch {}
55
+ }
56
+ }
57
+
58
+ function getFiles(dir, files = []) {
59
+ try {
60
+ const entries = readdirSync(dir);
61
+ for (const entry of entries) {
62
+ if (IGNORE_DIRS.includes(entry) || entry.startsWith('.')) continue;
63
+ const fullPath = join(dir, entry);
64
+ try {
65
+ const stat = statSync(fullPath);
66
+ if (stat.isDirectory()) getFiles(fullPath, files);
67
+ else if (SCAN_EXTENSIONS.includes(extname(entry).toLowerCase()) && stat.size < 512 * 1024) files.push(fullPath);
68
+ } catch {}
69
+ }
70
+ } catch {}
71
+ return files;
72
+ }
@@ -0,0 +1,67 @@
1
+ import { readFileSync, readdirSync, statSync } from 'fs';
2
+ import { join, extname } from 'path';
3
+ import { addFinding } from '../core/findings.js';
4
+
5
+ const SCAN_EXTENSIONS = ['.js', '.ts', '.jsx', '.tsx', '.mjs', '.py', '.rb', '.php', '.go', '.java'];
6
+ const IGNORE_DIRS = ['node_modules', '.git', 'dist', 'build', '__pycache__', 'vendor'];
7
+
8
+ export async function auditBusinessLogic(projectPath, spinner) {
9
+ spinner.text = 'Scanning for business logic flaws (PortSwigger)...';
10
+ const files = getFiles(projectPath);
11
+
12
+ for (const file of files) {
13
+ try {
14
+ const content = readFileSync(file, 'utf-8');
15
+ const relativePath = file.replace(projectPath, '.');
16
+ const lines = content.split('\n');
17
+
18
+ const patterns = [
19
+ { pattern: /(?:price|amount|total|cost|quantity|qty)\s*[:=]\s*(?:req|params|query|body|request)\./gi, name: 'Price/amount from client input (price manipulation)', severity: 'CRITICAL' },
20
+ { pattern: /(?:discount|coupon|promo|voucher).*(?:apply|use|redeem)/gi, name: 'Discount/coupon logic (test for race conditions & reuse)', severity: 'MEDIUM' },
21
+ { pattern: /(?:quantity|qty)\s*[:=]\s*(?:parseInt|Number)\s*\(\s*(?:req|body)/gi, name: 'Quantity from user input (negative quantity attack)', severity: 'HIGH' },
22
+ { pattern: /(?:transfer|withdraw|send)\s*.*(?:amount|value)\s*[:=]/gi, name: 'Financial transfer logic (check for negative amounts, overflow)', severity: 'HIGH' },
23
+ { pattern: /(?:limit|max|threshold)\s*.*(?:parseInt|Number|parseFloat)\s*\(\s*(?:req|body|query)/gi, name: 'Limit/threshold from user input', severity: 'MEDIUM' },
24
+ { pattern: /(?:step|stage|phase|state)\s*[:=]\s*(?:req|params|query|body)/gi, name: 'Workflow step from user input (step skipping)', severity: 'HIGH' },
25
+ { pattern: /(?:if|when).*(?:balance|credits?|points?)\s*(?:>=?|<=?|>|<)\s*(?:0|amount|price)/gi, name: 'Balance check (test for race conditions)', severity: 'MEDIUM' },
26
+ { pattern: /(?:verify|validate|check).*(?:email|phone|identity)\s*.*(?:skip|bypass|false)/gi, name: 'Verification bypass flag', severity: 'HIGH' },
27
+ { pattern: /(?:free[-_]?trial|trial[-_]?period).*(?:extend|reset|create)/gi, name: 'Trial logic (test for unlimited trial abuse)', severity: 'MEDIUM' },
28
+ { pattern: /(?:referral|invite|bonus).*(?:credit|reward|points)/gi, name: 'Referral/bonus logic (test for self-referral abuse)', severity: 'MEDIUM' },
29
+ { pattern: /parseInt\s*\(\s*(?:req|body|query).*10\s*\)|Number\s*\(\s*(?:req|body|query)/gi, name: 'Numeric input parsing (test integer overflow, NaN, Infinity)', severity: 'LOW' },
30
+ { pattern: /(?:vote|like|upvote|rate|review).*(?:req|body|params)/gi, name: 'Voting/rating logic (test for multiple votes)', severity: 'MEDIUM' },
31
+ ];
32
+
33
+ for (const { pattern, name, severity } of patterns) {
34
+ let match;
35
+ while ((match = pattern.exec(content)) !== null) {
36
+ const lineNum = content.substring(0, match.index).split('\n').length;
37
+ const line = lines[lineNum - 1]?.trim() || '';
38
+ if (line.startsWith('//') || line.startsWith('#')) continue;
39
+
40
+ addFinding(
41
+ severity,
42
+ 'Business Logic (PortSwigger)',
43
+ name,
44
+ `File: ${relativePath}:${lineNum}\nCode: ${line.substring(0, 120)}`,
45
+ 'Never trust client-supplied values for prices, quantities, or workflow states. Validate all business rules server-side. Use database transactions with proper isolation levels for financial operations. Implement idempotency keys to prevent double-processing.'
46
+ );
47
+ }
48
+ }
49
+ } catch {}
50
+ }
51
+ }
52
+
53
+ function getFiles(dir, files = []) {
54
+ try {
55
+ const entries = readdirSync(dir);
56
+ for (const entry of entries) {
57
+ if (IGNORE_DIRS.includes(entry) || entry.startsWith('.')) continue;
58
+ const fullPath = join(dir, entry);
59
+ try {
60
+ const stat = statSync(fullPath);
61
+ if (stat.isDirectory()) getFiles(fullPath, files);
62
+ else if (SCAN_EXTENSIONS.includes(extname(entry).toLowerCase()) && stat.size < 512 * 1024) files.push(fullPath);
63
+ } catch {}
64
+ }
65
+ } catch {}
66
+ return files;
67
+ }
@@ -0,0 +1,69 @@
1
+ import { readFileSync, readdirSync, statSync } from 'fs';
2
+ import { join, extname } from 'path';
3
+ import { addFinding } from '../core/findings.js';
4
+
5
+ const SCAN_EXTENSIONS = ['.yml', '.yaml', '.js', '.ts', '.sh', '.bash'];
6
+ const IGNORE_DIRS = ['node_modules', '.git', 'dist', 'build', '__pycache__', 'vendor'];
7
+
8
+ export async function auditCicd(projectPath, spinner) {
9
+ spinner.text = 'Scanning for CI/CD pipeline vulnerabilities...';
10
+ const files = getFiles(projectPath);
11
+
12
+ for (const file of files) {
13
+ try {
14
+ const content = readFileSync(file, 'utf-8');
15
+ const relativePath = file.replace(projectPath, '.');
16
+ const lines = content.split('\n');
17
+
18
+ const patterns = [
19
+ { pattern: /pull_request_target/gi, name: 'pull_request_target event (injection risk)', severity: 'CRITICAL' },
20
+ { pattern: /pull_request_target.*(?:checkout|run:|script:|exec|execute)/gi, name: 'pull_request_target with code execution', severity: 'CRITICAL' },
21
+ { pattern: /on:\s*pull_request_target.*workflow_run/gi, name: 'workflow_run from fork PR (injection)', severity: 'CRITICAL' },
22
+ { pattern: /\$\{\{\s*github\.event\.pull_request\.(?:title|body|head\.ref)\s*\}\}/gi, name: 'GitHub Actions - PR body/title used unsafely', severity: 'CRITICAL' },
23
+ { pattern: /run:.*\${{.*github\.event\.|run:.*\${{.*inputs\./gi, name: 'GitHub Actions - user input in run step', severity: 'CRITICAL' },
24
+ { pattern: /secrets\.\w+\s*:\s*\$\{\{\s*inputs\./gi, name: 'Secrets exposed via workflow inputs', severity: 'HIGH' },
25
+ { pattern: /(?:ACCESS_TOKEN|API_KEY|SECRET|TOKEN|PASSWORD|CREDENTIALS)\s*:\s*\$\{\{\s*secrets\./gi, name: 'Secrets in workflow environment', severity: 'INFO' },
26
+ { pattern: /actions\/checkout@v\d\s*$|[^/]checkout\s*:/gi, name: 'Repository checkout', severity: 'INFO' },
27
+ { pattern: /jenkins.*pipeline|Jenkinsfile/gi, name: 'Jenkins pipeline', severity: 'INFO' },
28
+ { pattern: /stage\s*\(\s*['"]Deploy|publish|release/gi, name: 'CI deployment stage', severity: 'INFO' },
29
+ { pattern: /docker.*build.*push|docker.*image|container.*registry/gi, name: 'Container build and push', severity: 'INFO' },
30
+ { pattern: /gitlab-ci\.yml|\.gitlab-ci/gi, name: 'GitLab CI configuration', severity: 'INFO' },
31
+ { pattern: /(?:npm|pip|maven|gradle|docker|helm)\s*(?:publish|push|deploy)/gi, name: 'Package/docker publish step', severity: 'INFO' },
32
+ { pattern: /needs:\s*\[.*build.*\]\s*$|if:\s*github\.ref\s*==\s*'refs\/heads\/(?:main|master)'/gi, name: 'CI gate on main branch', severity: 'INFO' },
33
+ { pattern: /write\s*:\s*\[\s*['"]id-token['"]\s*\]|id-token\s*:\s*write/gi, name: 'OIDC token write permission', severity: 'MEDIUM' },
34
+ { pattern: /actions\s*:\s*read.*contents:\s*write|actions: write/gi, name: 'Elevated pipeline permissions', severity: 'HIGH' },
35
+ { pattern: /(?:fetch-depth|filter)\s*:\s*0/gi, name: 'Full git history fetched', severity: 'LOW' },
36
+ ];
37
+
38
+ for (const { pattern, name, severity } of patterns) {
39
+ let match;
40
+ while ((match = pattern.exec(content)) !== null) {
41
+ const lineNum = content.substring(0, match.index).split('\n').length;
42
+ addFinding(
43
+ severity,
44
+ 'CI/CD Pipeline',
45
+ name,
46
+ `File: ${relativePath}:${lineNum}\nCode: ${lines[lineNum - 1]?.trim().substring(0, 120)}`,
47
+ 'Never use pull_request_target with untrusted code execution. Sanitize all github.event fields used in scripts. Use OIDC instead of long-lived secrets. Apply least-privilege GitHub token permissions. Use environment protection rules for deployments.'
48
+ );
49
+ }
50
+ }
51
+ } catch {}
52
+ }
53
+ }
54
+
55
+ function getFiles(dir, files = []) {
56
+ try {
57
+ const entries = readdirSync(dir);
58
+ for (const entry of entries) {
59
+ if (IGNORE_DIRS.includes(entry) || entry.startsWith('.')) continue;
60
+ const fullPath = join(dir, entry);
61
+ try {
62
+ const stat = statSync(fullPath);
63
+ if (stat.isDirectory()) getFiles(fullPath, files);
64
+ else if (SCAN_EXTENSIONS.includes(extname(entry).toLowerCase()) && stat.size < 256 * 1024) files.push(fullPath);
65
+ } catch {}
66
+ }
67
+ } catch {}
68
+ return files;
69
+ }
@@ -0,0 +1,73 @@
1
+ import { readFileSync, readdirSync, statSync } from 'fs';
2
+ import { join, extname } from 'path';
3
+ import { addFinding } from '../core/findings.js';
4
+
5
+ const SCAN_EXTENSIONS = ['.js', '.ts', '.jsx', '.tsx', '.py', '.rb', '.tf', '.hcl', '.yml', '.yaml', '.json'];
6
+ const IGNORE_DIRS = ['node_modules', '.git', 'dist', 'build', '__pycache__', 'vendor'];
7
+
8
+ export async function auditCloud(projectPath, spinner) {
9
+ spinner.text = 'Scanning for cloud misconfigurations...';
10
+ const files = getFiles(projectPath);
11
+
12
+ for (const file of files) {
13
+ try {
14
+ const content = readFileSync(file, 'utf-8');
15
+ const relativePath = file.replace(projectPath, '.');
16
+ const lines = content.split('\n');
17
+
18
+ const patterns = [
19
+ { pattern: /"Effect"\s*:\s*"Allow"\s*,\s*"Principal"\s*:\s*"\*"/gi, name: 'S3 bucket policy - public access (Principal: *)', severity: 'CRITICAL' },
20
+ { pattern: /"Effect"\s*:\s*"Allow"\s*,\s*"Principal"\s*:\s*{?\s*"AWS"\s*:\s*"\*"/gi, name: 'AWS IAM policy - public access', severity: 'CRITICAL' },
21
+ { pattern: /s3:GetObject.*Principal.*\*/gi, name: 'S3 GetObject public access', severity: 'CRITICAL' },
22
+ { pattern: /s3:PutObject.*Principal.*\*/gi, name: 'S3 PutObject public access (critical!)', severity: 'CRITICAL' },
23
+ { pattern: /block_public_acls\s*=\s*false|blockPublicPolicy\s*:\s*false/gi, name: 'S3 public access block disabled', severity: 'CRITICAL' },
24
+ { pattern: /access_key\s*=\s*['"][A-Za-z0-9+\/=]{20}['"]/gi, name: 'AWS access key in config', severity: 'CRITICAL' },
25
+ { pattern: /secret_key\s*=\s*['"][A-Za-z0-9+\/=]{40}['"]/gi, name: 'AWS secret key in config', severity: 'CRITICAL' },
26
+ { pattern: /primary_access_key|primary_secret_key/gi, name: 'Cloud access keys in source', severity: 'CRITICAL' },
27
+ { pattern: /metadata.*169\.254|instance.?metadata/gi, name: 'Cloud metadata endpoint reference', severity: 'INFO' },
28
+ { pattern: /"Action"\s*:\s*"\*:"\s*,\s*"Resource"\s*:\s*"\*"/gi, name: 'IAM policy - wildcard action + resource', severity: 'CRITICAL' },
29
+ { pattern: /"NotAction"|"NotResource"/gi, name: 'IAM NotAction/NotResource (check for over-permissiveness)', severity: 'MEDIUM' },
30
+ { pattern: /GCP_CREDENTIALS|GOOGLE_APPLICATION_CREDENTIALS|gcp\.json|service\.account/gi, name: 'GCP service account credentials', severity: 'CRITICAL' },
31
+ { pattern: /AZURE_STORAGE_CONNECTION_STRING|AZURE_CLIENT_SECRET|ARM_CLIENT_SECRET/gi, name: 'Azure credentials in source', severity: 'CRITICAL' },
32
+ { pattern: /terraform\.tfstate|backend\.tf/gi, name: 'Terraform state reference', severity: 'HIGH' },
33
+ { pattern: /(?:bucket|container)\s*.*acl\s*.*public-read/gi, name: 'Storage bucket with public-read ACL', severity: 'CRITICAL' },
34
+ { pattern: /(?:lambda|function_url).*auth_type\s*=\s*"NONE"/gi, name: 'AWS Lambda function URL without auth', severity: 'HIGH' },
35
+ { pattern: /(?:publicly_readable|publicly_writable)\s*=\s*true/gi, name: 'GCP bucket publicly accessible', severity: 'CRITICAL' },
36
+ { pattern: /(?:monitoring|logging|audit.?log)\s*.*(?:disabled?|false)/gi, name: 'Cloud logging/monitoring disabled', severity: 'MEDIUM' },
37
+ ];
38
+
39
+ for (const { pattern, name, severity } of patterns) {
40
+ let match;
41
+ while ((match = pattern.exec(content)) !== null) {
42
+ const lineNum = content.substring(0, match.index).split('\n').length;
43
+ const line = lines[lineNum - 1]?.trim() || '';
44
+ if (line.startsWith('//') || line.startsWith('#') || line.startsWith('*')) continue;
45
+
46
+ addFinding(
47
+ severity,
48
+ 'Cloud Misconfig (S3/IAM)',
49
+ name,
50
+ `File: ${relativePath}:${lineNum}\nCode: ${line.substring(0, 120)}`,
51
+ 'Follow least privilege principle. Never use wildcard (*) for IAM actions/resources. Block all public S3 bucket access by default. Use IAM roles, not access keys. Enable CloudTrail logging. Store secrets in AWS Secrets Manager / GCP Secret Manager / Azure Key Vault.'
52
+ );
53
+ }
54
+ }
55
+ } catch {}
56
+ }
57
+ }
58
+
59
+ function getFiles(dir, files = []) {
60
+ try {
61
+ const entries = readdirSync(dir);
62
+ for (const entry of entries) {
63
+ if (IGNORE_DIRS.includes(entry) || entry.startsWith('.')) continue;
64
+ const fullPath = join(dir, entry);
65
+ try {
66
+ const stat = statSync(fullPath);
67
+ if (stat.isDirectory()) getFiles(fullPath, files);
68
+ else if (SCAN_EXTENSIONS.includes(extname(entry).toLowerCase()) && stat.size < 512 * 1024) files.push(fullPath);
69
+ } catch {}
70
+ }
71
+ } catch {}
72
+ return files;
73
+ }