ship-safe 4.2.0 → 4.3.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
@@ -9,13 +9,14 @@
9
9
  <a href="https://github.com/asamassekou10/ship-safe/actions/workflows/ci.yml"><img src="https://github.com/asamassekou10/ship-safe/actions/workflows/ci.yml/badge.svg" alt="CI" /></a>
10
10
  <a href="https://nodejs.org"><img src="https://img.shields.io/node/v/ship-safe" alt="Node.js version" /></a>
11
11
  <a href="https://opensource.org/licenses/MIT"><img src="https://img.shields.io/badge/License-MIT-yellow.svg" alt="License: MIT" /></a>
12
+ <a href="https://github.com/asamassekou10/ship-safe/stargazers"><img src="https://img.shields.io/github/stars/asamassekou10/ship-safe?style=social" alt="GitHub stars" /></a>
12
13
  </p>
13
14
 
14
15
  ---
15
16
 
16
- 12 security agents. 50+ attack classes. One command.
17
+ 13 security agents. 50+ attack classes. One command.
17
18
 
18
- **Ship Safe v4.0** is an AI-powered security platform that runs 12 specialized agents against your codebase — scanning for secrets, injection vulnerabilities, auth bypass, SSRF, supply chain attacks, Docker/Terraform misconfigs, CI/CD pipeline poisoning, LLM security issues, and more. It produces a prioritized remediation plan so you know exactly what to fix first.
19
+ **Ship Safe v4.3** is an AI-powered security platform that runs 13 specialized agents in parallel against your codebase — scanning for secrets, injection vulnerabilities, auth bypass, SSRF, supply chain attacks, Supabase RLS misconfigs, Docker/Terraform/Kubernetes misconfigs, CI/CD pipeline poisoning, LLM security issues, and more. Context-aware confidence tuning reduces false positives by up to 70%. Baseline support lets teams adopt incrementally — accept existing debt, focus on not making it worse.
19
20
 
20
21
  ---
21
22
 
@@ -33,6 +34,13 @@ npx ship-safe scan .
33
34
 
34
35
  # Security health score (0-100)
35
36
  npx ship-safe score .
37
+
38
+ # Accept current findings, only report regressions
39
+ npx ship-safe baseline .
40
+ npx ship-safe audit . --baseline
41
+
42
+ # Environment diagnostics
43
+ npx ship-safe doctor
36
44
  ```
37
45
 
38
46
  ![ship-safe terminal demo](.github/assets/ship%20safe%20terminal.jpg)
@@ -49,11 +57,11 @@ npx ship-safe audit .
49
57
 
50
58
  ```
51
59
  ════════════════════════════════════════════════════════════
52
- Ship Safe v4.0 — Full Security Audit
60
+ Ship Safe v4.3 — Full Security Audit
53
61
  ════════════════════════════════════════════════════════════
54
62
 
55
63
  [Phase 1/4] Scanning for secrets... ✔ 49 found
56
- [Phase 2/4] Running 12 security agents... ✔ 103 findings
64
+ [Phase 2/4] Running 13 security agents... ✔ 103 findings
57
65
  [Phase 3/4] Auditing dependencies... ✔ 44 CVEs
58
66
  [Phase 4/4] Computing security score... ✔ 25/100 F
59
67
 
@@ -80,36 +88,44 @@ npx ship-safe audit .
80
88
 
81
89
  **What it runs:**
82
90
  1. **Secret scan** — 50+ patterns with entropy scoring (API keys, passwords, tokens)
83
- 2. **12 security agents** — injection, auth, SSRF, supply chain, config, LLM, mobile, git history, CI/CD, API
91
+ 2. **13 security agents** — run in parallel with per-agent timeouts (injection, auth, SSRF, supply chain, config, Supabase RLS, LLM, mobile, git history, CI/CD, API)
84
92
  3. **Dependency audit** — npm/pip/bundler CVE scanning
85
- 4. **Score computation** — 8-category weighted scoring (0-100, A-F)
86
- 5. **Remediation plan** — prioritized fix list grouped by severity
87
- 6. **HTML report** — standalone dark-themed report with table of contents
93
+ 4. **Score computation** — confidence-weighted scoring across 8 categories (0-100, A-F)
94
+ 5. **Context-aware confidence tuning** — downgrades findings in test files, docs, and comments
95
+ 6. **Remediation plan** — prioritized fix list grouped by severity
96
+ 7. **HTML report** — standalone dark-themed report with code context
88
97
 
89
98
  **Flags:**
90
99
  - `--json` — structured JSON output (clean for piping)
91
100
  - `--sarif` — SARIF format for GitHub Code Scanning
101
+ - `--csv` — CSV export for spreadsheets
102
+ - `--md` — Markdown report
92
103
  - `--html [file]` — custom HTML report path (default: `ship-safe-report.html`)
104
+ - `--compare` — show per-category score delta vs. last scan
105
+ - `--timeout <ms>` — per-agent timeout (default: 30s)
93
106
  - `--no-deps` — skip dependency audit
94
107
  - `--no-ai` — skip AI classification
95
108
  - `--no-cache` — force full rescan (ignore cached results)
109
+ - `--baseline` — only show findings not in the baseline
110
+ - `--pdf [file]` — generate PDF report (requires Chrome/Chromium)
96
111
 
97
112
  ---
98
113
 
99
- ## 12 Security Agents
114
+ ## 13 Security Agents
100
115
 
101
116
  | Agent | Category | What It Detects |
102
117
  |-------|----------|-----------------|
103
- | **InjectionTester** | Code Vulns | SQL/NoSQL injection, command injection, code injection (eval), XSS, path traversal, XXE, ReDoS, prototype pollution |
104
- | **AuthBypassAgent** | Auth | JWT vulnerabilities (alg:none, weak secrets), cookie security, CSRF, OAuth misconfig, BOLA/IDOR, weak crypto, timing attacks, TLS bypass |
118
+ | **InjectionTester** | Code Vulns | SQL/NoSQL injection, command injection, code injection (eval), XSS, path traversal, XXE, ReDoS, prototype pollution, Python f-string SQL injection, Python subprocess shell injection |
119
+ | **AuthBypassAgent** | Auth | JWT vulnerabilities (alg:none, weak secrets), cookie security, CSRF, OAuth misconfig, BOLA/IDOR, weak crypto, timing attacks, TLS bypass, Django `DEBUG = True`, Flask hardcoded secret keys |
105
120
  | **SSRFProber** | SSRF | User input in fetch/axios, cloud metadata endpoints, internal IPs, redirect following |
106
- | **SupplyChainAudit** | Supply Chain | Typosquatting (Levenshtein distance), git/URL dependencies, wildcard versions, suspicious install scripts |
107
- | **ConfigAuditor** | Config | Dockerfile (running as root, :latest tags), Terraform (public S3, open SG), Kubernetes (privileged containers), CORS, CSP, Firebase, Nginx |
121
+ | **SupplyChainAudit** | Supply Chain | Typosquatting (Levenshtein distance), git/URL dependencies, wildcard versions, suspicious install scripts, dependency confusion, scoped packages without registry pinning |
122
+ | **ConfigAuditor** | Config | Dockerfile (running as root, :latest tags), Terraform (public S3/RDS, open SG, CloudFront HTTP, Lambda admin, S3 no versioning), Kubernetes (privileged containers, `:latest` tags, missing NetworkPolicy), CORS, CSP, Firebase, Nginx |
123
+ | **SupabaseRLSAgent** | Auth | Supabase Row Level Security — `service_role` key in client code, `CREATE TABLE` without RLS, anon key inserts, unprotected storage operations |
108
124
  | **LLMRedTeam** | AI/LLM | OWASP LLM Top 10 — prompt injection, excessive agency, system prompt leakage, unbounded consumption, RAG poisoning |
109
125
  | **MobileScanner** | Mobile | OWASP Mobile Top 10 2024 — insecure storage, WebView JS injection, HTTP endpoints, excessive permissions, debug mode |
110
126
  | **GitHistoryScanner** | Secrets | Leaked secrets in git commit history (checks if still active in working tree) |
111
127
  | **CICDScanner** | CI/CD | OWASP CI/CD Top 10 — pipeline poisoning, unpinned actions, secret logging, self-hosted runners, script injection |
112
- | **APIFuzzer** | API | Routes without auth, missing input validation, mass assignment, unrestricted file upload, GraphQL introspection, debug endpoints |
128
+ | **APIFuzzer** | API | Routes without auth, missing input validation, mass assignment, unrestricted file upload, GraphQL introspection, debug endpoints, missing rate limiting, OpenAPI spec security issues |
113
129
  | **ReconAgent** | Recon | Attack surface discovery — frameworks, languages, auth patterns, databases, cloud providers, IaC, CI/CD pipelines |
114
130
  | **ScoringEngine** | Scoring | 8-category weighted scoring with trend tracking |
115
131
 
@@ -123,7 +139,7 @@ npx ship-safe audit .
123
139
  # Full audit with remediation plan + HTML report
124
140
  npx ship-safe audit .
125
141
 
126
- # Red team: 12 agents, 50+ attack classes
142
+ # Red team: 13 agents, 50+ attack classes
127
143
  npx ship-safe red-team .
128
144
  npx ship-safe red-team . --agents injection,auth # Run specific agents
129
145
  npx ship-safe red-team . --html report.html # HTML report
@@ -150,11 +166,35 @@ npx ship-safe agent .
150
166
 
151
167
  # Auto-fix hardcoded secrets: rewrite code + write .env
152
168
  npx ship-safe remediate .
169
+ npx ship-safe remediate . --all # Also fix agent findings (TLS, debug, XSS, etc.)
153
170
 
154
171
  # Revoke exposed keys — opens provider dashboards
155
172
  npx ship-safe rotate .
156
173
  ```
157
174
 
175
+ ### Baseline Management
176
+
177
+ ```bash
178
+ # Accept current findings as baseline
179
+ npx ship-safe baseline .
180
+
181
+ # Audit showing only new findings since baseline
182
+ npx ship-safe audit . --baseline
183
+
184
+ # Show what changed since baseline
185
+ npx ship-safe baseline --diff
186
+
187
+ # Remove baseline
188
+ npx ship-safe baseline --clear
189
+ ```
190
+
191
+ ### Diagnostics
192
+
193
+ ```bash
194
+ # Environment check — Node.js, git, npm, API keys, cache, version
195
+ npx ship-safe doctor
196
+ ```
197
+
158
198
  ### Infrastructure Commands
159
199
 
160
200
  ```bash
@@ -214,6 +254,10 @@ Ship Safe caches file hashes and findings in `.ship-safe/context.json`. On subse
214
254
 
215
255
  The cache is stored in `.ship-safe/` which is automatically excluded from scans.
216
256
 
257
+ ### LLM Response Caching
258
+
259
+ When using AI classification (`--no-ai` to disable), results are cached in `.ship-safe/llm-cache.json` with a 7-day TTL. Repeated scans reuse cached classifications — reducing API costs significantly.
260
+
217
261
  ---
218
262
 
219
263
  ## Smart `.gitignore` Handling
@@ -247,7 +291,7 @@ Auto-detected from environment variables. No API key required for scanning — A
247
291
 
248
292
  ## Scoring System
249
293
 
250
- Starts at 100. Each finding deducts points by severity and category.
294
+ Starts at 100. Each finding deducts points by severity and category, weighted by confidence level (high: 100%, medium: 60%, low: 30%) to reduce noise from heuristic patterns.
251
295
 
252
296
  **8 Categories** (with weight caps):
253
297
 
@@ -306,6 +350,9 @@ jobs:
306
350
  - name: Full security audit
307
351
  run: npx ship-safe audit . --no-ai --json
308
352
 
353
+ - name: Score delta vs. last scan
354
+ run: npx ship-safe audit . --no-ai --compare
355
+
309
356
  - name: Upload SARIF to GitHub Security tab
310
357
  run: npx ship-safe audit . --no-ai --sarif > results.sarif
311
358
 
@@ -314,6 +361,8 @@ jobs:
314
361
  sarif_file: results.sarif
315
362
  ```
316
363
 
364
+ **Export formats:** `--json`, `--sarif`, `--csv`, `--md`, `--html`, `--pdf`
365
+
317
366
  ---
318
367
 
319
368
  ## Suppress False Positives
@@ -493,4 +493,230 @@ describe('Orchestrator', async () => {
493
493
  const deduped = orchestrator.deduplicate(findings);
494
494
  assert.equal(deduped.length, 2);
495
495
  });
496
+
497
+ it('tunes confidence for test files', () => {
498
+ const orchestrator = new Orchestrator();
499
+ const findings = [
500
+ { file: '/project/__tests__/foo.test.js', line: 1, rule: 'R1', severity: 'high', confidence: 'high', matched: 'eval(x)' },
501
+ { file: '/project/src/app.js', line: 1, rule: 'R2', severity: 'high', confidence: 'high', matched: 'eval(x)' },
502
+ ];
503
+ const tuned = orchestrator.tuneConfidence(findings);
504
+ assert.equal(tuned[0].confidence, 'low'); // test file → low
505
+ assert.equal(tuned[1].confidence, 'high'); // src file → unchanged
506
+ });
507
+
508
+ it('tunes confidence for doc files', () => {
509
+ const orchestrator = new Orchestrator();
510
+ const findings = [
511
+ { file: '/project/README.md', line: 5, rule: 'R1', severity: 'high', confidence: 'high', matched: 'password = "test"' },
512
+ ];
513
+ const tuned = orchestrator.tuneConfidence(findings);
514
+ assert.equal(tuned[0].confidence, 'low');
515
+ });
516
+
517
+ it('tunes confidence for example paths', () => {
518
+ const orchestrator = new Orchestrator();
519
+ const findings = [
520
+ { file: '/project/examples/demo.js', line: 1, rule: 'R1', severity: 'high', confidence: 'high', matched: 'eval(x)' },
521
+ ];
522
+ const tuned = orchestrator.tuneConfidence(findings);
523
+ assert.equal(tuned[0].confidence, 'medium');
524
+ });
525
+ });
526
+
527
+ // =============================================================================
528
+ // SUPABASE RLS AGENT
529
+ // =============================================================================
530
+
531
+ describe('SupabaseRLSAgent', () => {
532
+ it('detects service_role key in client code', async () => {
533
+ const { SupabaseRLSAgent } = await import('../agents/supabase-rls-agent.js');
534
+ const agent = new SupabaseRLSAgent();
535
+ const { dir, file } = writeTempFile(`const supabase = createClient(url, SUPABASE_SERVICE_ROLE_KEY);`);
536
+ try {
537
+ const findings = agent.scanFileWithPatterns(file, [
538
+ { rule: 'SUPABASE_SERVICE_KEY_CLIENT', regex: /SUPABASE_SERVICE_ROLE_KEY|service_role_key|serviceRoleKey|supabaseAdmin/g, severity: 'critical', title: 'test', description: 'test' }
539
+ ]);
540
+ assert.ok(findings.some(f => f.rule === 'SUPABASE_SERVICE_KEY_CLIENT'));
541
+ } finally { cleanup(dir); }
542
+ });
543
+
544
+ it('detects missing RLS on table', async () => {
545
+ const { SupabaseRLSAgent } = await import('../agents/supabase-rls-agent.js');
546
+ const agent = new SupabaseRLSAgent();
547
+
548
+ const dir = fs.mkdtempSync(path.join(os.tmpdir(), 'shipsafe-rls-'));
549
+ const sqlFile = path.join(dir, 'migration.sql');
550
+ fs.writeFileSync(sqlFile, `CREATE TABLE users (id uuid PRIMARY KEY, name text);\nCREATE TABLE posts (id uuid PRIMARY KEY);`);
551
+ const jsFile = path.join(dir, 'app.js');
552
+ fs.writeFileSync(jsFile, 'const x = 1;');
553
+
554
+ try {
555
+ const findings = await agent.analyze({
556
+ rootPath: dir,
557
+ files: [sqlFile, jsFile],
558
+ recon: {},
559
+ options: {},
560
+ });
561
+ // Should flag both tables as missing RLS
562
+ const rlsFindings = findings.filter(f => f.rule === 'SUPABASE_NO_RLS_POLICY');
563
+ assert.ok(rlsFindings.length >= 2, `Expected >=2 RLS findings, got ${rlsFindings.length}`);
564
+ } finally { cleanup(dir); }
565
+ });
566
+
567
+ it('does not flag tables with RLS enabled', async () => {
568
+ const { SupabaseRLSAgent } = await import('../agents/supabase-rls-agent.js');
569
+ const agent = new SupabaseRLSAgent();
570
+
571
+ const dir = fs.mkdtempSync(path.join(os.tmpdir(), 'shipsafe-rls2-'));
572
+ const sqlFile = path.join(dir, 'migration.sql');
573
+ fs.writeFileSync(sqlFile, `CREATE TABLE users (id uuid PRIMARY KEY);\nALTER TABLE users ENABLE ROW LEVEL SECURITY;`);
574
+
575
+ try {
576
+ const findings = await agent.analyze({
577
+ rootPath: dir,
578
+ files: [sqlFile],
579
+ recon: {},
580
+ options: {},
581
+ });
582
+ const rlsFindings = findings.filter(f => f.rule === 'SUPABASE_NO_RLS_POLICY');
583
+ assert.equal(rlsFindings.length, 0);
584
+ } finally { cleanup(dir); }
585
+ });
586
+ });
587
+
588
+ // =============================================================================
589
+ // CONFIG AUDITOR — NEW TERRAFORM/K8S PATTERNS
590
+ // =============================================================================
591
+
592
+ describe('ConfigAuditor (v4.3 patterns)', () => {
593
+ it('detects publicly accessible RDS', async () => {
594
+ const { ConfigAuditor } = await import('../agents/config-auditor.js');
595
+ const agent = new ConfigAuditor();
596
+ const { dir, file } = writeTempFile(`resource "aws_db_instance" "main" {\n publicly_accessible = true\n}`, '.tf');
597
+ try {
598
+ const findings = await agent.analyze({ rootPath: dir, files: [file], recon: {}, options: {} });
599
+ assert.ok(findings.some(f => f.rule === 'TERRAFORM_RDS_PUBLIC'));
600
+ } finally { cleanup(dir); }
601
+ });
602
+
603
+ it('detects CloudFront allowing HTTP', async () => {
604
+ const { ConfigAuditor } = await import('../agents/config-auditor.js');
605
+ const agent = new ConfigAuditor();
606
+ const { dir, file } = writeTempFile(`viewer_protocol_policy = "allow-all"`, '.tf');
607
+ try {
608
+ const findings = await agent.analyze({ rootPath: dir, files: [file], recon: {}, options: {} });
609
+ assert.ok(findings.some(f => f.rule === 'TERRAFORM_CLOUDFRONT_HTTP'));
610
+ } finally { cleanup(dir); }
611
+ });
612
+
613
+ it('detects K8s :latest image tag', async () => {
614
+ const { ConfigAuditor } = await import('../agents/config-auditor.js');
615
+ const agent = new ConfigAuditor();
616
+ const dir = fs.mkdtempSync(path.join(os.tmpdir(), 'shipsafe-k8s-'));
617
+ const k8sDir = path.join(dir, 'k8s');
618
+ fs.mkdirSync(k8sDir);
619
+ const file = path.join(k8sDir, 'deployment.yaml');
620
+ fs.writeFileSync(file, `kind: Deployment\nspec:\n containers:\n - image: nginx:latest`);
621
+ try {
622
+ const findings = await agent.analyze({ rootPath: dir, files: [file], recon: {}, options: {} });
623
+ assert.ok(findings.some(f => f.rule === 'K8S_LATEST_IMAGE'));
624
+ } finally { cleanup(dir); }
625
+ });
626
+ });
627
+
628
+ // =============================================================================
629
+ // API FUZZER — RATE LIMITING & OPENAPI
630
+ // =============================================================================
631
+
632
+ describe('APIFuzzer (v4.3 patterns)', () => {
633
+ it('detects missing rate limiting in Express app', async () => {
634
+ const { APIFuzzer } = await import('../agents/api-fuzzer.js');
635
+ const agent = new APIFuzzer();
636
+
637
+ const dir = fs.mkdtempSync(path.join(os.tmpdir(), 'shipsafe-api-'));
638
+ const file = path.join(dir, 'app.js');
639
+ fs.writeFileSync(file, `import express from 'express';\nconst app = express();\napp.listen(3000);`);
640
+
641
+ try {
642
+ const findings = await agent.analyze({ rootPath: dir, files: [file], recon: {}, options: {} });
643
+ assert.ok(findings.some(f => f.rule === 'API_NO_RATE_LIMIT'));
644
+ } finally { cleanup(dir); }
645
+ });
646
+
647
+ it('does not flag when rate limiter is present', async () => {
648
+ const { APIFuzzer } = await import('../agents/api-fuzzer.js');
649
+ const agent = new APIFuzzer();
650
+
651
+ const dir = fs.mkdtempSync(path.join(os.tmpdir(), 'shipsafe-api2-'));
652
+ const file = path.join(dir, 'app.js');
653
+ fs.writeFileSync(file, `import express from 'express';\nimport rateLimit from 'express-rate-limit';\nconst app = express();\napp.listen(3000);`);
654
+
655
+ try {
656
+ const findings = await agent.analyze({ rootPath: dir, files: [file], recon: {}, options: {} });
657
+ assert.ok(!findings.some(f => f.rule === 'API_NO_RATE_LIMIT'));
658
+ } finally { cleanup(dir); }
659
+ });
660
+
661
+ it('detects secrets in OpenAPI examples', async () => {
662
+ const { APIFuzzer } = await import('../agents/api-fuzzer.js');
663
+ const agent = new APIFuzzer();
664
+
665
+ const dir = fs.mkdtempSync(path.join(os.tmpdir(), 'shipsafe-oas-'));
666
+ const file = path.join(dir, 'openapi.yaml');
667
+ fs.writeFileSync(file, `openapi: 3.0.0\npaths:\n /users:\n get:\n parameters:\n - name: token\n example: sk-proj-abc123xyz`);
668
+
669
+ try {
670
+ const findings = await agent.analyze({ rootPath: dir, files: [file], recon: {}, options: {} });
671
+ assert.ok(findings.some(f => f.rule === 'OPENAPI_EXAMPLE_SECRETS' || f.rule === 'OPENAPI_NO_SECURITY'));
672
+ } finally { cleanup(dir); }
673
+ });
674
+ });
675
+
676
+ // =============================================================================
677
+ // AUTOFIX RULES
678
+ // =============================================================================
679
+
680
+ describe('Autofix Rules', () => {
681
+ it('fixes rejectUnauthorized: false', async () => {
682
+ const { applyAutofix } = await import('../utils/autofix-rules.js');
683
+ const line = ' rejectUnauthorized: false,';
684
+ const fixed = applyAutofix('TLS_REJECT_UNAUTHORIZED', line);
685
+ assert.ok(fixed.includes('rejectUnauthorized: true'));
686
+ assert.ok(!fixed.includes('false'));
687
+ });
688
+
689
+ it('fixes DEBUG = true', async () => {
690
+ const { applyAutofix } = await import('../utils/autofix-rules.js');
691
+ assert.ok(applyAutofix('DEBUG_MODE_PRODUCTION', 'DEBUG = true').includes('false'));
692
+ assert.ok(applyAutofix('DEBUG_MODE_PRODUCTION', 'DEBUG = True').includes('False'));
693
+ });
694
+
695
+ it('fixes shell: true', async () => {
696
+ const { applyAutofix } = await import('../utils/autofix-rules.js');
697
+ const fixed = applyAutofix('CMD_INJECTION_SHELL_TRUE', ' shell: true');
698
+ assert.ok(fixed.includes('shell: false'));
699
+ });
700
+ });
701
+
702
+ // =============================================================================
703
+ // CODE CONTEXT
704
+ // =============================================================================
705
+
706
+ describe('Code Context in Findings', () => {
707
+ it('attaches codeContext to findings from scanFileWithPatterns', async () => {
708
+ const { InjectionTester } = await import('../agents/injection-tester.js');
709
+ const agent = new InjectionTester();
710
+ const code = `const x = 1;\nconst y = 2;\nconst z = eval(userInput);\nconst w = 3;\nconst v = 4;`;
711
+ const { dir, file } = writeTempFile(code);
712
+ try {
713
+ const findings = await agent.analyze({ rootPath: dir, files: [file], recon: {}, options: {} });
714
+ const evalFinding = findings.find(f => f.rule && f.rule.includes('EVAL'));
715
+ if (evalFinding) {
716
+ assert.ok(evalFinding.codeContext, 'Finding should have codeContext');
717
+ assert.ok(Array.isArray(evalFinding.codeContext));
718
+ assert.ok(evalFinding.codeContext.some(c => c.highlight === true));
719
+ }
720
+ } finally { cleanup(dir); }
721
+ });
496
722
  });
@@ -209,6 +209,49 @@ const PATTERNS = [
209
209
  description: 'No body size limit configured. Large payloads can cause memory exhaustion.',
210
210
  fix: 'Set limit: express.json({ limit: "1mb" })',
211
211
  },
212
+ {
213
+ rule: 'API_NO_PAGINATION',
214
+ title: 'API: Query Without Limit',
215
+ regex: /\.find\s*\(\s*\{?\s*\}\s*\)/g,
216
+ severity: 'low',
217
+ cwe: 'CWE-400',
218
+ confidence: 'low',
219
+ description: 'Database query returns all records without limit. Can exhaust memory on large tables.',
220
+ fix: 'Add pagination: .find({}).limit(50).skip(offset)',
221
+ },
222
+ ];
223
+
224
+ // OpenAPI/Swagger spec patterns
225
+ const OPENAPI_PATTERNS = [
226
+ {
227
+ rule: 'OPENAPI_NO_SECURITY',
228
+ title: 'OpenAPI: No Security Scheme Defined',
229
+ regex: /(?:openapi|swagger)\s*:\s*["']?[23]\./g,
230
+ severity: 'high',
231
+ cwe: 'CWE-306',
232
+ owasp: 'A07:2021',
233
+ confidence: 'medium',
234
+ description: 'OpenAPI spec detected without security schemes. API endpoints may be unprotected.',
235
+ fix: 'Add securityDefinitions/securitySchemes to your OpenAPI spec',
236
+ },
237
+ {
238
+ rule: 'OPENAPI_HTTP_SERVER',
239
+ title: 'OpenAPI: Non-HTTPS Server URL',
240
+ regex: /url\s*:\s*["']?http:\/\//g,
241
+ severity: 'high',
242
+ cwe: 'CWE-319',
243
+ description: 'OpenAPI spec defines an HTTP (non-HTTPS) server URL. Use HTTPS in production.',
244
+ fix: 'Change server URL to use https://',
245
+ },
246
+ {
247
+ rule: 'OPENAPI_EXAMPLE_SECRETS',
248
+ title: 'OpenAPI: Secrets in Examples',
249
+ regex: /example\s*:\s*['"]?(?:sk-|sk_live_|ghp_|gho_|AKIA[0-9A-Z]|Bearer\s+ey[A-Za-z0-9])/g,
250
+ severity: 'critical',
251
+ cwe: 'CWE-798',
252
+ description: 'Real API keys or tokens found in OpenAPI spec examples.',
253
+ fix: 'Replace real secrets with placeholder values in examples',
254
+ },
212
255
  ];
213
256
 
214
257
  export class APIFuzzer extends BaseAgent {
@@ -227,6 +270,74 @@ export class APIFuzzer extends BaseAgent {
227
270
  for (const file of codeFiles) {
228
271
  findings = findings.concat(this.scanFileWithPatterns(file, PATTERNS));
229
272
  }
273
+
274
+ // ── Project-level: Rate limiting check ────────────────────────────────────
275
+ let hasExpressApp = false;
276
+ let hasRateLimiter = false;
277
+ let expressAppFile = null;
278
+
279
+ for (const file of codeFiles) {
280
+ const content = this.readFile(file);
281
+ if (!content) continue;
282
+ if (/(?:express\s*\(\)|app\.listen|createServer)\s*/.test(content)) {
283
+ hasExpressApp = true;
284
+ if (!expressAppFile) expressAppFile = file;
285
+ }
286
+ if (/(?:express-rate-limit|rate-limiter-flexible|@upstash\/ratelimit|limiter|bottleneck|rateLimit)/i.test(content)) {
287
+ hasRateLimiter = true;
288
+ }
289
+ }
290
+
291
+ if (hasExpressApp && !hasRateLimiter && expressAppFile) {
292
+ findings.push(createFinding({
293
+ file: expressAppFile,
294
+ line: 0,
295
+ severity: 'medium',
296
+ category: 'api',
297
+ rule: 'API_NO_RATE_LIMIT',
298
+ title: 'API: No Rate Limiting Detected',
299
+ description: 'HTTP server detected without any rate-limiting library. APIs without rate limits are vulnerable to brute-force and DoS attacks.',
300
+ matched: 'No rate-limiting middleware found',
301
+ confidence: 'medium',
302
+ cwe: 'CWE-307',
303
+ owasp: 'A07:2021',
304
+ fix: 'Add rate limiting: npm i express-rate-limit && app.use(rateLimit({ windowMs: 15*60*1000, max: 100 }))',
305
+ }));
306
+ }
307
+
308
+ // ── OpenAPI/Swagger spec scanning ─────────────────────────────────────────
309
+ const specFiles = files.filter(f => {
310
+ const basename = path.basename(f).toLowerCase();
311
+ return /(?:openapi|swagger)\.(json|ya?ml)$/i.test(basename);
312
+ });
313
+
314
+ for (const file of specFiles) {
315
+ const specFindings = this.scanFileWithPatterns(file, OPENAPI_PATTERNS);
316
+ // Check if spec has securitySchemes
317
+ const content = this.readFile(file);
318
+ if (content && /(?:openapi|swagger)\s*:\s*["']?[23]\./.test(content)) {
319
+ if (!/securitySchemes|securityDefinitions/i.test(content)) {
320
+ findings.push(createFinding({
321
+ file,
322
+ line: 0,
323
+ severity: 'high',
324
+ category: 'api',
325
+ rule: 'OPENAPI_NO_SECURITY',
326
+ title: 'OpenAPI: No Security Scheme Defined',
327
+ description: 'OpenAPI spec has no securitySchemes/securityDefinitions. API endpoints may be unprotected.',
328
+ matched: 'Missing securitySchemes',
329
+ confidence: 'high',
330
+ cwe: 'CWE-306',
331
+ fix: 'Add securitySchemes with Bearer token, API key, or OAuth2',
332
+ }));
333
+ }
334
+ }
335
+ // Add other OpenAPI pattern matches (HTTP server, example secrets)
336
+ findings = findings.concat(
337
+ specFindings.filter(f => f.rule !== 'OPENAPI_NO_SECURITY')
338
+ );
339
+ }
340
+
230
341
  return findings;
231
342
  }
232
343
  }