cleartoship 0.4.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/LICENSE +21 -0
- package/README.md +264 -0
- package/action.yml +154 -0
- package/dist/banner.d.ts +1 -0
- package/dist/banner.js +14 -0
- package/dist/cli.d.ts +2 -0
- package/dist/cli.js +103 -0
- package/dist/data/popular.d.ts +17 -0
- package/dist/data/popular.js +103 -0
- package/dist/index.d.ts +5 -0
- package/dist/index.js +4 -0
- package/dist/report.d.ts +15 -0
- package/dist/report.js +317 -0
- package/dist/scan.d.ts +24 -0
- package/dist/scan.js +81 -0
- package/dist/scanners/community.d.ts +2 -0
- package/dist/scanners/community.js +155 -0
- package/dist/scanners/dependencies.d.ts +21 -0
- package/dist/scanners/dependencies.js +549 -0
- package/dist/scanners/index.d.ts +8 -0
- package/dist/scanners/index.js +13 -0
- package/dist/scanners/rls.d.ts +7 -0
- package/dist/scanners/rls.js +531 -0
- package/dist/scanners/secrets.d.ts +2 -0
- package/dist/scanners/secrets.js +439 -0
- package/dist/scanners/server-actions.d.ts +2 -0
- package/dist/scanners/server-actions.js +466 -0
- package/dist/types.d.ts +65 -0
- package/dist/types.js +10 -0
- package/dist/utils/ast.d.ts +13 -0
- package/dist/utils/ast.js +107 -0
- package/dist/utils/detect.d.ts +2 -0
- package/dist/utils/detect.js +77 -0
- package/dist/utils/entropy.d.ts +7 -0
- package/dist/utils/entropy.js +19 -0
- package/dist/utils/files.d.ts +13 -0
- package/dist/utils/files.js +154 -0
- package/dist/utils/osv.d.ts +26 -0
- package/dist/utils/osv.js +123 -0
- package/dist/utils/registry.d.ts +44 -0
- package/dist/utils/registry.js +195 -0
- package/dist/utils/sql.d.ts +20 -0
- package/dist/utils/sql.js +157 -0
- package/dist/utils/suppress.d.ts +19 -0
- package/dist/utils/suppress.js +56 -0
- package/dist/utils/traverse.d.ts +2 -0
- package/dist/utils/traverse.js +14 -0
- package/dist/vendor/gitleaks/rules.d.ts +13 -0
- package/dist/vendor/gitleaks/rules.js +1571 -0
- package/dist/vendor/guardvibe/index.d.ts +11 -0
- package/dist/vendor/guardvibe/index.js +60 -0
- package/dist/vendor/guardvibe/rules/advanced-security.d.ts +2 -0
- package/dist/vendor/guardvibe/rules/advanced-security.js +418 -0
- package/dist/vendor/guardvibe/rules/ai-host-security.d.ts +2 -0
- package/dist/vendor/guardvibe/rules/ai-host-security.js +190 -0
- package/dist/vendor/guardvibe/rules/ai-security.d.ts +2 -0
- package/dist/vendor/guardvibe/rules/ai-security.js +413 -0
- package/dist/vendor/guardvibe/rules/ai-tool-runtime.d.ts +2 -0
- package/dist/vendor/guardvibe/rules/ai-tool-runtime.js +194 -0
- package/dist/vendor/guardvibe/rules/api-security.d.ts +2 -0
- package/dist/vendor/guardvibe/rules/api-security.js +154 -0
- package/dist/vendor/guardvibe/rules/auth.d.ts +2 -0
- package/dist/vendor/guardvibe/rules/auth.js +222 -0
- package/dist/vendor/guardvibe/rules/cicd.d.ts +2 -0
- package/dist/vendor/guardvibe/rules/cicd.js +108 -0
- package/dist/vendor/guardvibe/rules/core.d.ts +2 -0
- package/dist/vendor/guardvibe/rules/core.js +484 -0
- package/dist/vendor/guardvibe/rules/cve-versions.d.ts +2 -0
- package/dist/vendor/guardvibe/rules/cve-versions.js +1130 -0
- package/dist/vendor/guardvibe/rules/database.d.ts +2 -0
- package/dist/vendor/guardvibe/rules/database.js +173 -0
- package/dist/vendor/guardvibe/rules/deployment.d.ts +2 -0
- package/dist/vendor/guardvibe/rules/deployment.js +269 -0
- package/dist/vendor/guardvibe/rules/dockerfile.d.ts +2 -0
- package/dist/vendor/guardvibe/rules/dockerfile.js +99 -0
- package/dist/vendor/guardvibe/rules/firebase.d.ts +2 -0
- package/dist/vendor/guardvibe/rules/firebase.js +96 -0
- package/dist/vendor/guardvibe/rules/go.d.ts +2 -0
- package/dist/vendor/guardvibe/rules/go.js +85 -0
- package/dist/vendor/guardvibe/rules/modern-stack.d.ts +2 -0
- package/dist/vendor/guardvibe/rules/modern-stack.js +631 -0
- package/dist/vendor/guardvibe/rules/nextjs.d.ts +2 -0
- package/dist/vendor/guardvibe/rules/nextjs.js +233 -0
- package/dist/vendor/guardvibe/rules/other-services.d.ts +2 -0
- package/dist/vendor/guardvibe/rules/other-services.js +72 -0
- package/dist/vendor/guardvibe/rules/payments.d.ts +2 -0
- package/dist/vendor/guardvibe/rules/payments.js +123 -0
- package/dist/vendor/guardvibe/rules/react-native.d.ts +2 -0
- package/dist/vendor/guardvibe/rules/react-native.js +132 -0
- package/dist/vendor/guardvibe/rules/services.d.ts +2 -0
- package/dist/vendor/guardvibe/rules/services.js +149 -0
- package/dist/vendor/guardvibe/rules/shell.d.ts +2 -0
- package/dist/vendor/guardvibe/rules/shell.js +72 -0
- package/dist/vendor/guardvibe/rules/sql.d.ts +2 -0
- package/dist/vendor/guardvibe/rules/sql.js +60 -0
- package/dist/vendor/guardvibe/rules/supply-chain.d.ts +2 -0
- package/dist/vendor/guardvibe/rules/supply-chain.js +243 -0
- package/dist/vendor/guardvibe/rules/terraform.d.ts +2 -0
- package/dist/vendor/guardvibe/rules/terraform.js +84 -0
- package/dist/vendor/guardvibe/rules/types.d.ts +14 -0
- package/dist/vendor/guardvibe/rules/types.js +11 -0
- package/dist/vendor/guardvibe/rules/web-security.d.ts +2 -0
- package/dist/vendor/guardvibe/rules/web-security.js +258 -0
- package/examples/security.yml +23 -0
- package/package.json +70 -0
|
@@ -0,0 +1,108 @@
|
|
|
1
|
+
/*
|
|
2
|
+
* Vendored from GuardVibe — https://github.com/goklab/guardvibe
|
|
3
|
+
* Copyright 2026 GokLab. Licensed under the Apache License, Version 2.0.
|
|
4
|
+
* Full licence: LICENSES/guardvibe-Apache-2.0.txt
|
|
5
|
+
*
|
|
6
|
+
* Modifications by ClearToShip: import paths rewritten for this package
|
|
7
|
+
* layout. Rule content is unchanged; rules that duplicate ClearToShip's own
|
|
8
|
+
* AST checks are disabled at runtime in src/scanners/community.ts rather
|
|
9
|
+
* than deleted here, so this file stays a faithful copy of upstream.
|
|
10
|
+
*/
|
|
11
|
+
export const cicdRules = [
|
|
12
|
+
{
|
|
13
|
+
id: "VG210",
|
|
14
|
+
name: "Secrets interpolated in run step",
|
|
15
|
+
severity: "critical",
|
|
16
|
+
owasp: "A01:2025 Broken Access Control",
|
|
17
|
+
description: "GitHub Actions secrets interpolated directly in run steps can leak via process logs or error messages.",
|
|
18
|
+
pattern: /run:\s*.*\$\{\{\s*secrets\./gi,
|
|
19
|
+
languages: ["yaml"],
|
|
20
|
+
fix: "Pass secrets as environment variables instead of interpolating in run steps.",
|
|
21
|
+
fixCode: "# Pass secrets via env, not interpolation\nsteps:\n - run: echo \"deploying\"\n env:\n MY_SECRET: ${{ secrets.MY_SECRET }}",
|
|
22
|
+
compliance: ["SOC2:CC6.1", "PCI-DSS:Req2.3"],
|
|
23
|
+
},
|
|
24
|
+
{
|
|
25
|
+
id: "VG211",
|
|
26
|
+
name: "pull_request_target with checkout",
|
|
27
|
+
severity: "critical",
|
|
28
|
+
owasp: "A01:2025 Broken Access Control",
|
|
29
|
+
description: "Using pull_request_target with actions/checkout allows untrusted PR code to access secrets.",
|
|
30
|
+
pattern: /pull_request_target[\s\S]*?actions\/checkout/gi,
|
|
31
|
+
languages: ["yaml"],
|
|
32
|
+
fix: "Use pull_request trigger instead, or avoid checking out PR code with pull_request_target.",
|
|
33
|
+
fixCode: "# Use pull_request instead of pull_request_target\non:\n pull_request:\n branches: [main]",
|
|
34
|
+
compliance: ["SOC2:CC6.1", "SOC2:CC6.6"],
|
|
35
|
+
},
|
|
36
|
+
{
|
|
37
|
+
id: "VG212",
|
|
38
|
+
name: "Unpinned action version",
|
|
39
|
+
severity: "medium",
|
|
40
|
+
owasp: "A03:2025 Software Supply Chain Failures",
|
|
41
|
+
description: "Using @main or @master for GitHub Actions allows untested code changes to affect your pipeline.",
|
|
42
|
+
pattern: /uses:\s*\S+@(?:main|master)\s/gi,
|
|
43
|
+
languages: ["yaml"],
|
|
44
|
+
fix: "Pin actions to a specific commit SHA or version tag.",
|
|
45
|
+
fixCode: "# Pin to specific version or SHA\nuses: actions/checkout@v4\n# Or pin to commit SHA:\n# uses: actions/checkout@abc123def456",
|
|
46
|
+
compliance: ["SOC2:CC7.1"],
|
|
47
|
+
},
|
|
48
|
+
{
|
|
49
|
+
id: "VG213",
|
|
50
|
+
name: "Overly permissive permissions",
|
|
51
|
+
severity: "high",
|
|
52
|
+
owasp: "A01:2025 Broken Access Control",
|
|
53
|
+
description: "write-all permissions give the workflow full access to the repository. Use least-privilege permissions.",
|
|
54
|
+
pattern: /permissions:\s*write-all/gi,
|
|
55
|
+
languages: ["yaml"],
|
|
56
|
+
fix: "Specify minimum required permissions for each job.",
|
|
57
|
+
fixCode: "# Use least-privilege permissions\npermissions:\n contents: read\n pull-requests: write",
|
|
58
|
+
compliance: ["SOC2:CC6.1", "PCI-DSS:Req8"],
|
|
59
|
+
},
|
|
60
|
+
{
|
|
61
|
+
id: "VG214",
|
|
62
|
+
name: "GitHub Actions Expression Injection",
|
|
63
|
+
severity: "critical",
|
|
64
|
+
owasp: "A02:2025 Injection",
|
|
65
|
+
description: "Untrusted input from github.event context (issue title, PR body, comment body, head ref) is interpolated in a run step. Attackers can inject arbitrary shell commands via crafted issue titles or PR descriptions. This caused CVE-2025-53104 and compromised thousands of repos.",
|
|
66
|
+
pattern: /run:\s*.*\$\{\{\s*github\.event\.(?:issue|pull_request|comment|discussion|review|head_commit)\.(?:title|body|head\.ref|label\.name|message)/gi,
|
|
67
|
+
languages: ["yaml"],
|
|
68
|
+
fix: "Never interpolate github.event data in run steps. Pass it through an environment variable instead.",
|
|
69
|
+
fixCode: '# BAD: direct interpolation\n- run: echo "${{ github.event.issue.title }}"\n\n# GOOD: pass through env\n- run: echo "$ISSUE_TITLE"\n env:\n ISSUE_TITLE: ${{ github.event.issue.title }}',
|
|
70
|
+
compliance: ["SOC2:CC7.1"],
|
|
71
|
+
},
|
|
72
|
+
{
|
|
73
|
+
id: "VG215",
|
|
74
|
+
name: "GitHub Actions Tag Reference Without SHA Pinning",
|
|
75
|
+
severity: "high",
|
|
76
|
+
owasp: "A03:2025 Software Supply Chain Failures",
|
|
77
|
+
description: "Third-party GitHub Action is referenced by a mutable tag (e.g., @v4) instead of a commit SHA. Tags can be force-pushed to point at malicious code — this is exactly how the tj-actions/changed-files attack (CVE-2025-30066) compromised 23,000+ repositories.",
|
|
78
|
+
pattern: /uses:\s*(?!actions\/|github\/)[^\s]+@v\d+\s/gi,
|
|
79
|
+
languages: ["yaml"],
|
|
80
|
+
fix: "Pin third-party actions to a full commit SHA. Use Dependabot or Renovate to keep SHA pins updated.",
|
|
81
|
+
fixCode: '# BAD: mutable tag\n- uses: someorg/action@v4\n\n# GOOD: SHA-pinned\n- uses: someorg/action@a1b2c3d4e5f6a1b2c3d4e5f6a1b2c3d4e5f6a1b2 # v4',
|
|
82
|
+
compliance: ["SOC2:CC7.1"],
|
|
83
|
+
},
|
|
84
|
+
{
|
|
85
|
+
id: "VG216",
|
|
86
|
+
name: "CI Pipeline Executes Untrusted PR Code",
|
|
87
|
+
severity: "high",
|
|
88
|
+
owasp: "A01:2025 Broken Access Control",
|
|
89
|
+
description: "Workflow triggered by pull_request_target runs make, npm test, or other commands from the PR branch. Since pull_request_target has access to secrets but runs untrusted code, attackers can exfiltrate secrets via malicious Makefiles or test scripts (Poisoned Pipeline Execution, OWASP CICD-SEC-4).",
|
|
90
|
+
pattern: /pull_request_target[\s\S]*?run:\s*(?:make\s|npm\s+(?:test|run)|yarn\s+(?:test|run)|pnpm\s+(?:test|run)|pytest|jest|eslint|cargo\s+test)/gi,
|
|
91
|
+
languages: ["yaml"],
|
|
92
|
+
fix: "Use pull_request trigger (no secret access) for testing untrusted code. If pull_request_target is needed, do NOT checkout or run the PR's code.",
|
|
93
|
+
fixCode: '# Use pull_request for untrusted code\non:\n pull_request:\n branches: [main]\nsteps:\n - uses: actions/checkout@v4\n - run: npm test # safe: runs YOUR code, not PR code',
|
|
94
|
+
compliance: ["SOC2:CC7.1"],
|
|
95
|
+
},
|
|
96
|
+
{
|
|
97
|
+
id: "VG1070",
|
|
98
|
+
name: "CI npm install/ci Without Supply-Chain Hardening Flag (--expect-provenance / --ignore-scripts)",
|
|
99
|
+
severity: "medium",
|
|
100
|
+
owasp: "A08:2025 Software & Data Integrity Failures",
|
|
101
|
+
description: "A CI workflow runs `npm install` or `npm ci` without `--expect-provenance` (npm 10.2+, requires every installed package to ship an SLSA provenance attestation signed against the npm registry) or `--ignore-scripts` (skips lifecycle scripts that typosquats and compromised maintainers use as the execution beachhead). One of the two should be on every CI install step. The 2026 @tanstack mass-malware wave, the 2022 node-ipc protestware, and the long tail of post-install crypto-miners all execute through lifecycle scripts the first time the package lands on a build runner — once that command runs, the runner's secrets are reachable. `--expect-provenance` raises the bar further by refusing unsigned packages entirely; pair it with `--ignore-scripts` for packages whose maintainers have not yet published provenance.",
|
|
102
|
+
pattern: /(?:^|\n)\s*(?:-\s+)?(?:run|cmd|shell):\s*[|>-]?\s*["'`]?[^"'`\n]*\bnpm\s+(?:ci|install|i)\b(?![^\n"'`]*--(?:expect-provenance|ignore-scripts))[^\n"'`]*/gi,
|
|
103
|
+
languages: ["yaml"],
|
|
104
|
+
fix: "Add `--expect-provenance` (recommended for new pipelines) or `--ignore-scripts` (broadest compatibility) to every `npm install` / `npm ci` invocation in CI. `--expect-provenance` will fail the install if any package lacks a signed SLSA attestation — combine with `--ignore-scripts` while upstream packages catch up to provenance. For deployments that must run `postinstall` (e.g. native binary build), narrow the allowlist instead of disabling the flag globally.",
|
|
105
|
+
fixCode: "# BAD — no supply-chain gate\n- run: npm ci\n\n# GOOD — strict\n- run: npm ci --expect-provenance --ignore-scripts\n\n# GOOD — minimal\n- run: npm ci --ignore-scripts",
|
|
106
|
+
compliance: ["SOC2:CC7.1", "SOC2:CC8.1", "PCI-DSS:Req6.2"],
|
|
107
|
+
},
|
|
108
|
+
];
|
|
@@ -0,0 +1,484 @@
|
|
|
1
|
+
/*
|
|
2
|
+
* Vendored from GuardVibe — https://github.com/goklab/guardvibe
|
|
3
|
+
* Copyright 2026 GokLab. Licensed under the Apache License, Version 2.0.
|
|
4
|
+
* Full licence: LICENSES/guardvibe-Apache-2.0.txt
|
|
5
|
+
*
|
|
6
|
+
* Modifications by ClearToShip: import paths rewritten for this package
|
|
7
|
+
* layout. Rule content is unchanged; rules that duplicate ClearToShip's own
|
|
8
|
+
* AST checks are disabled at runtime in src/scanners/community.ts rather
|
|
9
|
+
* than deleted here, so this file stays a faithful copy of upstream.
|
|
10
|
+
*/
|
|
11
|
+
// Security detection patterns - these RegExps match VULNERABLE code patterns
|
|
12
|
+
// that GuardVibe flags to users. The patterns themselves are safe detectors.
|
|
13
|
+
export const coreRules = [
|
|
14
|
+
{
|
|
15
|
+
id: "VG001",
|
|
16
|
+
name: "Hardcoded credentials",
|
|
17
|
+
severity: "critical",
|
|
18
|
+
owasp: "A01:2025 Broken Access Control",
|
|
19
|
+
description: "Hardcoded passwords, API keys, or secrets detected in source code.",
|
|
20
|
+
pattern: /(?:secret_?key|api_?key|api_?secret|private_?key|access_?key|password|passwd|pwd|auth_?token|jwt_?secret|app_?secret|master_?key|signing_?key|encryption_?key)\w*\s*[:=]\s*['"][^'"\n]{3,}['"]/gi,
|
|
21
|
+
languages: ["javascript", "typescript", "python", "go"],
|
|
22
|
+
fix: "Use environment variables (process.env.SECRET) or a secrets manager. Never commit credentials to source code.",
|
|
23
|
+
fixCode: "// Use environment variables instead\nconst password = process.env.DB_PASSWORD;\nconst apiKey = process.env.API_KEY;",
|
|
24
|
+
compliance: ["SOC2:CC6.1", "PCI-DSS:Req2.3", "PCI-DSS:Req8", "HIPAA:§164.312(a)"],
|
|
25
|
+
},
|
|
26
|
+
{
|
|
27
|
+
id: "VG003",
|
|
28
|
+
name: "Cloud provider API key",
|
|
29
|
+
severity: "critical",
|
|
30
|
+
owasp: "A01:2025 Broken Access Control",
|
|
31
|
+
description: "Cloud provider API key or token pattern detected in source code (AWS, GitHub, OpenAI, Stripe).",
|
|
32
|
+
pattern: /(?:AKIA[0-9A-Z]{16}|(?:ghp|gho|ghu|ghs|ghr)_[A-Za-z0-9_]{36,}|sk-(?:proj-)?[A-Za-z0-9\-_]{20,}|sk_live_[A-Za-z0-9]{5,}|rk_live_[A-Za-z0-9]{5,})/g,
|
|
33
|
+
languages: ["javascript", "typescript", "python", "go", "html", "shell"],
|
|
34
|
+
fix: "Remove hardcoded keys immediately. Use environment variables or a secrets manager (AWS Secrets Manager, Vault). Rotate any compromised keys.",
|
|
35
|
+
fixCode: "// Store keys in environment variables\nconst awsKey = process.env.AWS_ACCESS_KEY_ID;\nconst githubToken = process.env.GITHUB_TOKEN;",
|
|
36
|
+
compliance: ["SOC2:CC6.1", "PCI-DSS:Req2.3", "HIPAA:§164.312(a)"],
|
|
37
|
+
},
|
|
38
|
+
{
|
|
39
|
+
id: "VG002",
|
|
40
|
+
name: "Missing authentication check",
|
|
41
|
+
severity: "high",
|
|
42
|
+
owasp: "A01:2025 Broken Access Control",
|
|
43
|
+
description: "API route handler without authentication middleware or auth check.",
|
|
44
|
+
pattern: /(?:app|router)\.(get|post|put|delete|patch)\s*\(\s*['"](?!(?:\/(?:api\/)?)?(?:health|status|ping|ready|live|metrics|public|favicon|robots|sitemap)\b)[^'"]*['"]\s*,\s*(?:async\s+)?\(?\b(?:req|request)\b/gi,
|
|
45
|
+
languages: ["javascript", "typescript"],
|
|
46
|
+
fix: "Add authentication middleware before route handlers: app.get('/api/data', authMiddleware, handler). Use frameworks like Passport.js, Clerk, or Auth0.",
|
|
47
|
+
fixCode: "// Add auth middleware before handler\napp.get('/api/data', authMiddleware, async (req, res) => {\n // handler code\n});",
|
|
48
|
+
compliance: ["SOC2:CC6.6", "PCI-DSS:Req6.5.10", "HIPAA:§164.312(d)"],
|
|
49
|
+
},
|
|
50
|
+
{
|
|
51
|
+
id: "VG005",
|
|
52
|
+
name: "Missing authentication check",
|
|
53
|
+
severity: "high",
|
|
54
|
+
owasp: "A01:2025 Broken Access Control",
|
|
55
|
+
description: "Python API route without authentication dependency or decorator.",
|
|
56
|
+
pattern: /@app\.(?:get|post|put|delete|patch)\s*\(\s*['"]\/(?:api|users|admin|account|dashboard|settings|login)/gi,
|
|
57
|
+
languages: ["python"],
|
|
58
|
+
fix: "Add authentication dependency: async def route(user = Depends(get_current_user)). Use FastAPI's Depends() or Flask-Login for auth checks.",
|
|
59
|
+
fixCode: "# Add auth dependency\n@app.get('/api/data')\nasync def route(user = Depends(get_current_user)):\n pass",
|
|
60
|
+
compliance: ["SOC2:CC6.6", "PCI-DSS:Req6.5.10", "HIPAA:§164.312(d)"],
|
|
61
|
+
},
|
|
62
|
+
{
|
|
63
|
+
id: "VG010",
|
|
64
|
+
name: "SQL injection risk",
|
|
65
|
+
severity: "critical",
|
|
66
|
+
owasp: "A02:2025 Injection",
|
|
67
|
+
description: "String concatenation, template literals, or f-strings used in SQL queries — whether inline in the DB call or assembled in a variable/return first. This allows SQL injection attacks.",
|
|
68
|
+
pattern: /(?:query|execute|raw|sql|all|run|get|exec|prepare|literal|QueryRow|QueryContext)\s*\(\s*(?:`[^`]*\$\{|(?:"[^"]*"|'[^']*')\s*\+\s*|f"[^"]*\{|f'[^']*\{|['"][^'"]*['"]\s*%\s*|['"][^'"]*['"]\s*\.format\s*\(|['"][^'"]*['"]\s*,\s*(?:req\.|request\.|params\.|body\.|args))|(?:=|return)\s*(?:`\s*(?:SELECT|INSERT|UPDATE|DELETE)\b[^`]*\b(?:FROM|INTO|SET|WHERE|VALUES)\b[^`]*\$\{|['"]\s*(?:SELECT|INSERT|UPDATE|DELETE)\b[^\n]*?\b(?:FROM|INTO|SET|WHERE|VALUES)\b[^\n]*?['"]\s*\+\s*\w)/gi,
|
|
69
|
+
languages: ["javascript", "typescript", "python", "go"],
|
|
70
|
+
fix: "Use parameterized queries: db.query('SELECT * FROM users WHERE id = $1', [userId]). Python: cursor.execute('SELECT * FROM users WHERE id = %s', (user_id,)). Never concatenate user input into SQL strings.",
|
|
71
|
+
fixCode: "// Use parameterized queries\ndb.query('SELECT * FROM users WHERE id = $1', [userId]);\n// Python: cursor.execute('SELECT * FROM users WHERE id = %s', (user_id,))",
|
|
72
|
+
compliance: ["SOC2:CC7.1", "PCI-DSS:Req6.5.1", "HIPAA:§164.312(a)"],
|
|
73
|
+
},
|
|
74
|
+
{
|
|
75
|
+
id: "VG011",
|
|
76
|
+
name: "Command injection risk",
|
|
77
|
+
severity: "critical",
|
|
78
|
+
owasp: "A02:2025 Injection",
|
|
79
|
+
description: "User input passed to shell command functions. This allows arbitrary command execution.",
|
|
80
|
+
pattern: /(?:(?:child_process|cp)[\s\S]*?(?:exec|execSync|spawn|spawnSync)|\.exec(?:Sync)?\s*\(|\.spawn(?:Sync)?\s*\(|os\.system|os\.popen|subprocess\.(?:call|run|Popen)|shell_exec)\s*\(\s*(?:`[^`]*\$\{|['"][^'"]*['"]\s*\+|f['"][^'"]*\{|.*(?:req\.|request\.|params\.|body\.|input|argv))/gi,
|
|
81
|
+
languages: ["javascript", "typescript", "python", "go", "shell"],
|
|
82
|
+
fix: "Avoid shell commands with user input. Use allowlists and input validation. Prefer spawn() with array arguments. Python: use subprocess.run([...]) with list arguments, never shell=True with user input.",
|
|
83
|
+
fixCode: "// Use spawn with array arguments (no shell)\nimport { spawn } from 'child_process';\nspawn('ls', ['-la', directory]);",
|
|
84
|
+
compliance: ["SOC2:CC7.1", "PCI-DSS:Req6.5.1"],
|
|
85
|
+
},
|
|
86
|
+
{
|
|
87
|
+
id: "VG012",
|
|
88
|
+
name: "XSS via innerHTML",
|
|
89
|
+
severity: "high",
|
|
90
|
+
owasp: "A02:2025 Injection",
|
|
91
|
+
description: "Setting innerHTML with dynamic content enables Cross-Site Scripting (XSS) attacks.",
|
|
92
|
+
pattern: /(?:innerHTML|outerHTML)\s*(?:=|:)\s*(?!['"]<)/gi,
|
|
93
|
+
languages: ["javascript", "typescript", "html"],
|
|
94
|
+
fix: "Use textContent instead of innerHTML. Sanitize with DOMPurify if HTML rendering is needed. In React, avoid dangerouslySetInnerHTML.",
|
|
95
|
+
// fixCode: added via concatenation to avoid false-positive hook trigger on DOMPurify example
|
|
96
|
+
fixCode: "// Use textContent instead of innerHTML\nelement.textContent = userInput;\n// If HTML needed, sanitize first:\nimport DOMPurify from 'dompurify';\n" + "element.innerHTML = DOMPurify.sanitize(html);",
|
|
97
|
+
compliance: ["SOC2:CC7.1", "PCI-DSS:Req6.5.7"],
|
|
98
|
+
},
|
|
99
|
+
{
|
|
100
|
+
id: "VG015",
|
|
101
|
+
name: "XSS via server response",
|
|
102
|
+
severity: "high",
|
|
103
|
+
owasp: "A02:2025 Injection",
|
|
104
|
+
description: "User input embedded in HTML response via template literals or string concatenation enables Cross-Site Scripting.",
|
|
105
|
+
pattern: /res\.(?:send|write|end)\s*\(\s*(?:`[^`]*\$\{|['"][^'"]*['"]\s*\+)/gi,
|
|
106
|
+
languages: ["javascript", "typescript"],
|
|
107
|
+
fix: "Use a template engine with auto-escaping (EJS, Handlebars), or sanitize output with the 'escape-html' package. Never embed user input directly in HTML responses.",
|
|
108
|
+
fixCode: "// Use a template engine with auto-escaping\nimport escapeHtml from 'escape-html';\nres.send(escapeHtml(userInput));",
|
|
109
|
+
compliance: ["SOC2:CC7.1", "PCI-DSS:Req6.5.7"],
|
|
110
|
+
},
|
|
111
|
+
{
|
|
112
|
+
id: "VG013",
|
|
113
|
+
name: "ORM/NoSQL query injection risk",
|
|
114
|
+
severity: "high",
|
|
115
|
+
owasp: "A02:2025 Injection",
|
|
116
|
+
description: "User input passed directly into an ORM/NoSQL query filter object — a MongoDB/Mongoose .find()/.findOne() or a SQL-ORM where clause (Sequelize .find({where}), TypeORM). When the value is an object instead of a scalar, an attacker can inject query operators (MongoDB $ne/$gt/$where, or Sequelize string-operator aliases like $gt/$ne in v4) to bypass authentication or filters. Express parses req.query via qs into nested objects, so query-param values reach the filter as objects unless coerced.",
|
|
117
|
+
pattern: /(?:find|findOne|updateOne|deleteOne|aggregate)\s*\(\s*\{[^}]*(?:req\.|request\.|body\.|params\.)/gi,
|
|
118
|
+
languages: ["javascript", "typescript"],
|
|
119
|
+
fix: "Coerce filter values to scalars before querying and reject objects where strings are expected: const id = typeof req.params.id === 'string' ? req.params.id : ''. For Mongoose use schema validation; for Sequelize wrap values (String(req.query.id)) and never spread raw req objects into a where clause.",
|
|
120
|
+
fixCode: "// Coerce to a scalar before using in any ORM/NoSQL query filter\nconst id = typeof req.params.id === 'string' ? req.params.id : '';\n// Mongoose: await collection.findOne({ _id: new ObjectId(id) });\n// Sequelize: await Model.findOne({ where: { id: String(req.query.id) } });",
|
|
121
|
+
compliance: ["SOC2:CC7.1", "PCI-DSS:Req6.5.1"],
|
|
122
|
+
},
|
|
123
|
+
{
|
|
124
|
+
id: "VG014",
|
|
125
|
+
name: "Dynamic code execution",
|
|
126
|
+
severity: "critical",
|
|
127
|
+
owasp: "A02:2025 Injection",
|
|
128
|
+
description: "Dynamic code execution function detected. This can run arbitrary code and is a major security risk.",
|
|
129
|
+
pattern: /(?:\beval\s*\(|new\s+Function\s*\(|\bvm\s*\.\s*(?:runInNewContext|runInContext|runInThisContext|compileFunction)\s*\(|new\s+vm\s*\.\s*Script\s*\()/gi,
|
|
130
|
+
languages: ["javascript", "typescript", "python"],
|
|
131
|
+
fix: "Avoid dynamic code execution. Use JSON.parse() for JSON data. Use a sandboxed environment if absolutely required.",
|
|
132
|
+
fixCode: "// Use JSON.parse for data\nconst data = JSON.parse(input);\n// Alternatives: use a proper parser for your data format\n// const fn = new " + "Function('x', 'return x * 2'); // only if absolutely needed",
|
|
133
|
+
compliance: ["SOC2:CC7.1", "PCI-DSS:Req6.5.1"],
|
|
134
|
+
},
|
|
135
|
+
{
|
|
136
|
+
id: "VG020",
|
|
137
|
+
name: "Wildcard dependency version",
|
|
138
|
+
severity: "medium",
|
|
139
|
+
owasp: "A03:2025 Software Supply Chain Failures",
|
|
140
|
+
description: "Using '*' or overly broad version ranges in package.json allows untested dependency updates.",
|
|
141
|
+
pattern: /["']\w+["']\s*:\s*["']\*["']|["']\w+["']\s*:\s*["']>=\d/gi,
|
|
142
|
+
languages: ["json"],
|
|
143
|
+
fix: "Pin dependencies to specific versions or use caret ranges (^1.2.3). Run npm audit regularly.",
|
|
144
|
+
fixCode: "// Pin to specific version\n\"lodash\": \"^4.17.21\"\n// Run: npm audit to check for vulnerabilities",
|
|
145
|
+
compliance: ["SOC2:CC7.1"],
|
|
146
|
+
},
|
|
147
|
+
{
|
|
148
|
+
id: "VG030",
|
|
149
|
+
name: "Missing rate limiting",
|
|
150
|
+
severity: "medium",
|
|
151
|
+
owasp: "A04:2025 Insecure Design",
|
|
152
|
+
description: "Authentication or API endpoints without rate limiting are vulnerable to brute force attacks.",
|
|
153
|
+
pattern: /(?:app|router)\.\s*(?:get|post|put|delete|patch|use)\s*\(\s*['"](?:\/login|\/auth|\/signin|\/register|\/signup|\/forgot-password)['"]\s*,(?!\s*(?:\w*[Ll]imiter|\w*[Tt]hrottle|\w*[Rr]ate[Ll]imit|\w*[Bb]rute|\w*[Ss]low[Dd]own))/gi,
|
|
154
|
+
languages: ["javascript", "typescript", "python", "go"],
|
|
155
|
+
fix: "Add rate limiting middleware. Express: npm install express-rate-limit. FastAPI: use slowapi. Apply stricter limits on auth endpoints (e.g. 5 requests/minute).",
|
|
156
|
+
fixCode: "// Express rate limiting\nimport rateLimit from 'express-rate-limit';\napp.use('/api/', rateLimit({ windowMs: 15 * 60 * 1000, max: 100 }));",
|
|
157
|
+
compliance: ["SOC2:CC7.1"],
|
|
158
|
+
},
|
|
159
|
+
{
|
|
160
|
+
id: "VG040",
|
|
161
|
+
name: "CORS wildcard",
|
|
162
|
+
severity: "high",
|
|
163
|
+
owasp: "A05:2025 Security Misconfiguration",
|
|
164
|
+
description: "CORS configured with wildcard (*) origin allows any website to make requests to your API.",
|
|
165
|
+
pattern: /(?:(?:cors|Access-Control-Allow-Origin)['"]?\]?\s*[:=(]\s*['"]?\s*\*|origin\s*:\s*['"]?\s*\*\s*['"]?|CORS_ORIGINS['"]?\]?\s*=\s*['"]?\s*\*)/gi,
|
|
166
|
+
languages: ["javascript", "typescript", "python", "go"],
|
|
167
|
+
fix: "Set specific allowed origins: cors({ origin: ['https://myapp.com'] }). Never use wildcard with authentication.",
|
|
168
|
+
fixCode: "// Specify allowed origins\nimport cors from 'cors';\napp.use(cors({ origin: ['https://myapp.com'] }));",
|
|
169
|
+
compliance: ["SOC2:CC6.6", "PCI-DSS:Req6.5.10"],
|
|
170
|
+
},
|
|
171
|
+
{
|
|
172
|
+
id: "VG041",
|
|
173
|
+
name: "Debug mode in production",
|
|
174
|
+
severity: "medium",
|
|
175
|
+
owasp: "A05:2025 Security Misconfiguration",
|
|
176
|
+
description: "Debug mode or verbose error messages exposed in production.",
|
|
177
|
+
pattern: /(?:DEBUG\s*[:=]\s*['"]?(?:true|\*)|console\.log\(.*(?:password|secret_?key|api_?key|private_?key|auth_?token)\s*[\),}])/gi,
|
|
178
|
+
languages: ["javascript", "typescript", "python"],
|
|
179
|
+
fix: "Disable debug mode in production. Never expose stack traces to users.",
|
|
180
|
+
fixCode: "// Use environment-based config\nconst DEBUG = process.env.NODE_ENV !== 'production';",
|
|
181
|
+
compliance: ["SOC2:CC6.1"],
|
|
182
|
+
},
|
|
183
|
+
{
|
|
184
|
+
id: "VG042",
|
|
185
|
+
name: "Missing security headers",
|
|
186
|
+
severity: "medium",
|
|
187
|
+
owasp: "A05:2025 Security Misconfiguration",
|
|
188
|
+
description: "Express app without security headers (helmet).",
|
|
189
|
+
pattern: /(?:express\(\))(?![\s\S]{0,500}?helmet\s*\()|(?:createServer\s*\()(?![\s\S]{0,500}?helmet\s*\()/gi,
|
|
190
|
+
languages: ["javascript", "typescript"],
|
|
191
|
+
fix: "Use helmet middleware: npm install helmet, then app.use(helmet()).",
|
|
192
|
+
fixCode: "// Add helmet for security headers\nimport helmet from 'helmet';\napp.use(helmet());",
|
|
193
|
+
compliance: ["SOC2:CC6.1"],
|
|
194
|
+
},
|
|
195
|
+
{
|
|
196
|
+
id: "VG060",
|
|
197
|
+
name: "Weak password hashing",
|
|
198
|
+
severity: "critical",
|
|
199
|
+
owasp: "A07:2025 Auth Failures",
|
|
200
|
+
description: "Using MD5 or SHA-1 for password hashing. These are fast hashes, not designed for passwords.",
|
|
201
|
+
pattern: /(?:createHash\s*\(\s*['"](?:md5|sha1)['"]\s*\)|(?:md5|sha1)\s*\.\s*(?:new|update|digest|hexdigest|Sum|New)\s*\(|import\s+(?:md5|sha1)|require\s*\(\s*['"](?:md5|sha1)['"]\s*\)|hashlib\.(?:md5|sha1)\s*\()/gi,
|
|
202
|
+
languages: ["javascript", "typescript", "python", "go"],
|
|
203
|
+
fix: "Use bcrypt, scrypt, or argon2 for password hashing. Use at least 12 salt rounds.",
|
|
204
|
+
fixCode: "// Use bcrypt for password hashing\nimport bcrypt from 'bcrypt';\nconst hash = await bcrypt.hash(password, 12);\nconst valid = await bcrypt.compare(input, hash);",
|
|
205
|
+
compliance: ["SOC2:CC6.1", "PCI-DSS:Req3.4", "PCI-DSS:Req8.2.1", "HIPAA:§164.312(a)"],
|
|
206
|
+
},
|
|
207
|
+
{
|
|
208
|
+
id: "VG061",
|
|
209
|
+
name: "JWT without expiry",
|
|
210
|
+
severity: "high",
|
|
211
|
+
owasp: "A07:2025 Auth Failures",
|
|
212
|
+
description: "JWT token created without expiration time.",
|
|
213
|
+
pattern: /jwt\.sign\s*\((?:(?!\bexpiresIn\b)[^)])*\)/gi,
|
|
214
|
+
languages: ["javascript", "typescript"],
|
|
215
|
+
fix: "Always set token expiration: jwt.sign(payload, secret, { expiresIn: '15m' }).",
|
|
216
|
+
fixCode: "// Always set expiration\nconst token = jwt.sign(payload, secret, { expiresIn: '15m' });",
|
|
217
|
+
compliance: ["SOC2:CC6.1", "PCI-DSS:Req8"],
|
|
218
|
+
},
|
|
219
|
+
{
|
|
220
|
+
id: "VG062",
|
|
221
|
+
name: "Hardcoded secret in variable",
|
|
222
|
+
severity: "critical",
|
|
223
|
+
owasp: "A07:2025 Auth Failures",
|
|
224
|
+
description: "Variable named secret, password, or apiKey assigned a string literal. Secrets should come from environment variables or a secrets manager, never hardcoded in source.",
|
|
225
|
+
pattern: /(?:(?:const|let|var|export)\s+)?(?:secret|password|passwd|apiKey|api_key|privateKey|private_key|signingKey|signing_key|encryptionKey|encryption_key|masterKey|master_key|dbPassword|db_password)\s*(?::\s*string\s*)?=\s*["'][^"'\n]{8,}["']/gi,
|
|
226
|
+
languages: ["javascript", "typescript", "python"],
|
|
227
|
+
fix: "Use environment variables: const secret = process.env.MY_SECRET. Never hardcode secrets in source code.",
|
|
228
|
+
fixCode: "// Use environment variables\nconst secret = process.env.JWT_SECRET;\nconst apiKey = process.env.API_KEY;\n\n// In .env.local (never commit this file)\nJWT_SECRET=your-secret-here",
|
|
229
|
+
compliance: ["SOC2:CC6.1", "PCI-DSS:Req2.3", "HIPAA:§164.312(a)"],
|
|
230
|
+
},
|
|
231
|
+
{
|
|
232
|
+
id: "VG070",
|
|
233
|
+
name: "Insecure deserialization",
|
|
234
|
+
severity: "high",
|
|
235
|
+
owasp: "A08:2025 Data Integrity Failures",
|
|
236
|
+
description: "Deserializing untrusted data can lead to remote code execution.",
|
|
237
|
+
pattern: /(?:JSON\.parse\s*\(\s*(?:req\.|request\.|body)|pickle\.loads?\s*\(|yaml\.(?:load|unsafe_load)\s*\(|\bunserialize\s*\(|\bfuncster\s*\.|\bcryo\s*\.\s*parse\s*\()/gi,
|
|
238
|
+
languages: ["javascript", "typescript", "python"],
|
|
239
|
+
fix: "Validate all deserialized data with a schema (zod, joi) before processing.",
|
|
240
|
+
fixCode: "// Validate with schema after parsing\nimport { z } from 'zod';\nconst schema = z.object({ name: z.string() });\nconst data = schema.parse(JSON.parse(req.body));",
|
|
241
|
+
compliance: ["SOC2:CC7.1", "PCI-DSS:Req6.5.1"],
|
|
242
|
+
},
|
|
243
|
+
{
|
|
244
|
+
id: "VG080",
|
|
245
|
+
name: "Sensitive data in logs",
|
|
246
|
+
severity: "medium",
|
|
247
|
+
owasp: "A09:2025 Logging Failures",
|
|
248
|
+
description: "Logging sensitive information like passwords, tokens, or personal data.",
|
|
249
|
+
pattern: /(?:console\.log|logger\.\w+|print)\s*\([^)]*(?:(?:password|token|secret|ssn|credit.?card|api.?key)\s*[,\)}\]:+]|(?:password|token|secret|ssn|credit.?card|api.?key)\s*=)/gi,
|
|
250
|
+
languages: ["javascript", "typescript", "python", "go"],
|
|
251
|
+
fix: "Never log sensitive data. Redact or mask sensitive fields before logging.",
|
|
252
|
+
fixCode: "// Redact sensitive fields\nconst safeUser = { ...user, password: '[REDACTED]' };\nconsole.log('User:', safeUser);",
|
|
253
|
+
compliance: ["SOC2:CC6.1", "PCI-DSS:Req3.4", "HIPAA:§164.312(a)"],
|
|
254
|
+
},
|
|
255
|
+
{
|
|
256
|
+
id: "VG090",
|
|
257
|
+
name: "SSRF risk",
|
|
258
|
+
severity: "high",
|
|
259
|
+
owasp: "A10:2025 SSRF",
|
|
260
|
+
description: "User-supplied URLs passed to fetch/request functions can be used for SSRF attacks.",
|
|
261
|
+
pattern: /(?:fetch|axios|request|http\.get|urllib|requests\.get)\s*\(\s*(?:req\.(?:body|query|params)\.|request\.(?:body|query)\.|body\.\w+|params\.\w+|query\.\w+)/gi,
|
|
262
|
+
languages: ["javascript", "typescript", "python", "go"],
|
|
263
|
+
fix: "Validate and allowlist URLs before making requests. Block internal IP ranges.",
|
|
264
|
+
fixCode: "// Validate URL against allowlist\nconst allowed = ['https://api.example.com'];\nconst url = new URL(input);\nif (!allowed.some(a => url.origin === a)) throw new Error('Blocked');",
|
|
265
|
+
compliance: ["SOC2:CC7.1", "PCI-DSS:Req6.5.1"],
|
|
266
|
+
},
|
|
267
|
+
{
|
|
268
|
+
id: "VG100",
|
|
269
|
+
name: "Insecure cookie configuration",
|
|
270
|
+
severity: "medium",
|
|
271
|
+
owasp: "A05:2025 Security Misconfiguration",
|
|
272
|
+
description: "Cookies set without secure, httpOnly, or sameSite flags.",
|
|
273
|
+
pattern: /(?:\bcookie|setCookie|set-cookie|res\.cookie)[ \t]*\([^)]*(?!(?:.*secure|.*httpOnly|.*sameSite))/gi,
|
|
274
|
+
languages: ["javascript", "typescript"],
|
|
275
|
+
fix: "Set all security flags: { secure: true, httpOnly: true, sameSite: 'strict' }.",
|
|
276
|
+
fixCode: "res.cookie('session', token, {\n secure: true,\n httpOnly: true,\n sameSite: 'strict',\n maxAge: 3600000\n});",
|
|
277
|
+
compliance: ["SOC2:CC6.1", "PCI-DSS:Req6.5.10"],
|
|
278
|
+
},
|
|
279
|
+
{
|
|
280
|
+
id: "VG101",
|
|
281
|
+
name: "Unvalidated redirect",
|
|
282
|
+
severity: "medium",
|
|
283
|
+
owasp: "A01:2025 Broken Access Control",
|
|
284
|
+
description: "Redirect URL taken from user input without validation.",
|
|
285
|
+
pattern: /(?:redirect|location\.href|window\.location)\s*(?:=|\()\s*(?:req\.|request\.|params\.|query\.|body\.)/gi,
|
|
286
|
+
languages: ["javascript", "typescript"],
|
|
287
|
+
fix: "Validate redirect URLs against an allowlist. Use relative paths for internal redirects.",
|
|
288
|
+
fixCode: "// Validate redirect against allowlist\nconst allowedPaths = ['/dashboard', '/profile'];\nconst target = req.query.redirect;\nif (allowedPaths.includes(target)) res.redirect(target);",
|
|
289
|
+
compliance: ["SOC2:CC7.1", "PCI-DSS:Req6.5.10"],
|
|
290
|
+
},
|
|
291
|
+
{
|
|
292
|
+
id: "VG102",
|
|
293
|
+
name: "File path traversal risk",
|
|
294
|
+
severity: "high",
|
|
295
|
+
owasp: "A01:2025 Broken Access Control",
|
|
296
|
+
description: "User input used in file paths without sanitization.",
|
|
297
|
+
pattern: /(?:readFile|readFileSync|createReadStream|open|sendFile|download|path\.join|path\.resolve)\s*\([^)]*(?:req\.|request\.|params\.|body\.|query\.)/gi,
|
|
298
|
+
languages: ["javascript", "typescript", "python", "go"],
|
|
299
|
+
fix: "Sanitize file paths: remove ../ sequences, verify the result is within the expected directory.",
|
|
300
|
+
fixCode: "import path from 'path';\nconst safePath = path.resolve('/uploads', filename);\nif (!safePath.startsWith('/uploads/')) throw new Error('Invalid path');",
|
|
301
|
+
compliance: ["SOC2:CC7.1", "PCI-DSS:Req6.5.1"],
|
|
302
|
+
},
|
|
303
|
+
{
|
|
304
|
+
id: "VG103",
|
|
305
|
+
name: "Prototype pollution risk",
|
|
306
|
+
severity: "high",
|
|
307
|
+
owasp: "A02:2025 Injection",
|
|
308
|
+
description: "Deep merge or object assignment from user input can lead to prototype pollution.",
|
|
309
|
+
pattern: /(?:(?:Object\.assign|\bmerge\b|deepMerge|\bextend\b|(?:_|lodash)\.set(?:With)?|objectPath\.set|dotProp\.set)\s*\([^)]*(?:req\.|request\.|\bbody\b|\bparams\b)|\[\s*(?:req|request)\.(?:body|params|query)\.[\w.]+\s*\]\s*=(?!=))/gi,
|
|
310
|
+
languages: ["javascript", "typescript"],
|
|
311
|
+
fix: "Use Object.create(null) for lookup objects. Validate that keys don't include __proto__, constructor, or prototype.",
|
|
312
|
+
fixCode: "// Use Object.create(null) for lookups\nconst lookup = Object.create(null);\n// Validate keys\nconst forbidden = ['__proto__', 'constructor', 'prototype'];\nif (forbidden.includes(key)) throw new Error('Invalid key');",
|
|
313
|
+
compliance: ["SOC2:CC7.1", "PCI-DSS:Req6.5.1"],
|
|
314
|
+
},
|
|
315
|
+
{
|
|
316
|
+
id: "VG104",
|
|
317
|
+
name: "CORS Origin Reflection",
|
|
318
|
+
severity: "high",
|
|
319
|
+
owasp: "A05:2025 Security Misconfiguration",
|
|
320
|
+
description: "The server reflects the request's Origin header back as Access-Control-Allow-Origin. This is worse than a wildcard — combined with credentials:true, it allows any website to make authenticated requests to your API and read responses. Consistently a top HackerOne finding.",
|
|
321
|
+
pattern: /(?:Access-Control-Allow-Origin|origin)\s*[:=]\s*(?:req\.headers\.origin|req\.header\s*\(\s*['"]origin['"]\)|request\.headers\.get\s*\(\s*['"]origin['"]|event\.headers\.origin)/gi,
|
|
322
|
+
languages: ["javascript", "typescript"],
|
|
323
|
+
fix: "Use an explicit allowlist of origins instead of reflecting the request origin.",
|
|
324
|
+
fixCode: '// Use an allowlist\nconst ALLOWED_ORIGINS = ["https://myapp.com", "https://staging.myapp.com"];\nconst origin = req.headers.origin;\nif (ALLOWED_ORIGINS.includes(origin)) {\n res.setHeader("Access-Control-Allow-Origin", origin);\n}',
|
|
325
|
+
compliance: ["SOC2:CC6.6", "PCI-DSS:Req6.5.10"],
|
|
326
|
+
},
|
|
327
|
+
{
|
|
328
|
+
id: "VG105",
|
|
329
|
+
name: "JWT Algorithm None Attack",
|
|
330
|
+
severity: "critical",
|
|
331
|
+
owasp: "A02:2025 Cryptographic Failures",
|
|
332
|
+
description: "JWT verification does not specify allowed algorithms or explicitly allows 'none'. Attackers can forge tokens by setting alg:none in the header, bypassing signature verification entirely.",
|
|
333
|
+
pattern: /(?:jwt\.verify|jwtVerify|verifyToken)\s*\(\s*[^,]+,\s*[^,]+(?:\s*\)|\s*,\s*\{(?:(?!algorithms)[\s\S]){0,200}?\})|algorithms\s*:\s*\[\s*['"]none['"]/gi,
|
|
334
|
+
languages: ["javascript", "typescript"],
|
|
335
|
+
fix: "Always specify allowed algorithms explicitly in jwt.verify(). Never allow 'none'.",
|
|
336
|
+
fixCode: '// Always specify algorithms\nconst payload = jwt.verify(token, secret, {\n algorithms: ["HS256"], // explicit allowlist\n});\n\n// NEVER: algorithms: ["none"]',
|
|
337
|
+
compliance: ["SOC2:CC6.1", "PCI-DSS:Req8"],
|
|
338
|
+
},
|
|
339
|
+
{
|
|
340
|
+
id: "VG106",
|
|
341
|
+
name: "Timing-Unsafe Secret Comparison",
|
|
342
|
+
severity: "medium",
|
|
343
|
+
owasp: "A02:2025 Cryptographic Failures",
|
|
344
|
+
description: "Secret values (tokens, API keys, webhook signatures, HMAC digests) are compared using === or ==. String comparison short-circuits on the first different byte, allowing attackers to guess secrets one character at a time via timing side-channels.",
|
|
345
|
+
pattern: /(?:secret|token|apiKey|api_key|signature|hmac|hash|webhook|digest)\w*\s*(?:===|!==|==|!=)\s*/gi,
|
|
346
|
+
languages: ["javascript", "typescript"],
|
|
347
|
+
fix: "Use crypto.timingSafeEqual() for all secret comparisons.",
|
|
348
|
+
fixCode: 'import { timingSafeEqual } from "crypto";\n\nfunction safeCompare(a: string, b: string): boolean {\n const bufA = Buffer.from(a);\n const bufB = Buffer.from(b);\n if (bufA.length !== bufB.length) return false;\n return timingSafeEqual(bufA, bufB);\n}',
|
|
349
|
+
compliance: ["SOC2:CC6.1"],
|
|
350
|
+
},
|
|
351
|
+
{
|
|
352
|
+
id: "VG107",
|
|
353
|
+
name: "ReDoS via User-Controlled RegExp",
|
|
354
|
+
severity: "high",
|
|
355
|
+
owasp: "A04:2023 Unrestricted Resource Consumption",
|
|
356
|
+
description: "User input is passed directly to new RegExp() constructor. Crafted regex patterns with catastrophic backtracking (e.g., (a+)+$) can freeze the event loop for minutes, causing denial of service.",
|
|
357
|
+
pattern: /new\s+RegExp\s*\(\s*(?:req\.|request\.|body\.|params\.|query\.|input|userInput|search|filter|pattern|term)/gi,
|
|
358
|
+
languages: ["javascript", "typescript"],
|
|
359
|
+
fix: "Never pass user input directly to RegExp. Use string methods (includes, startsWith) or sanitize regex special characters.",
|
|
360
|
+
fixCode: '// BAD: user controls the regex\nconst re = new RegExp(req.query.search);\n\n// GOOD: escape regex special chars\nfunction escapeRegex(s: string) {\n return s.replace(/[.*+?^${}()|[\\]\\\\]/g, "\\\\$&");\n}\nconst re = new RegExp(escapeRegex(req.query.search));\n\n// BETTER: use string methods\nconst results = items.filter(i => i.name.includes(query));',
|
|
361
|
+
compliance: ["SOC2:CC7.1"],
|
|
362
|
+
},
|
|
363
|
+
{
|
|
364
|
+
id: "VG108",
|
|
365
|
+
name: "Vue v-html Directive with User Data",
|
|
366
|
+
severity: "high",
|
|
367
|
+
owasp: "A07:2025 Cross-Site Scripting",
|
|
368
|
+
description: "Vue's v-html directive renders raw HTML without sanitization, equivalent to innerHTML. If user-controlled data is bound via v-html, attackers can inject arbitrary scripts for stored or reflected XSS.",
|
|
369
|
+
pattern: /v-html\s*=\s*["'](?!['"])\w/gi,
|
|
370
|
+
languages: ["html", "javascript", "typescript"],
|
|
371
|
+
fix: "Avoid v-html with user data. Use text interpolation {{ }} or sanitize with DOMPurify before rendering.",
|
|
372
|
+
fixCode: '<!-- BAD: raw HTML rendering -->\n<!-- <div v-html="userComment"></div> -->\n\n<!-- GOOD: text interpolation (auto-escaped) -->\n<div>{{ userComment }}</div>\n\n<!-- If HTML needed: sanitize first -->\n<div v-html="DOMPurify.sanitize(userComment)"></div>',
|
|
373
|
+
compliance: ["SOC2:CC7.1"],
|
|
374
|
+
},
|
|
375
|
+
{
|
|
376
|
+
id: "VG109",
|
|
377
|
+
name: "Angular innerHTML Binding with User Data",
|
|
378
|
+
severity: "high",
|
|
379
|
+
owasp: "A07:2025 Cross-Site Scripting",
|
|
380
|
+
description: "Angular's [innerHTML] property binding renders HTML content. While Angular's built-in sanitizer strips scripts, it can be bypassed with bypassSecurityTrustHtml() or via CSS/SVG-based XSS vectors.",
|
|
381
|
+
pattern: /(?:\[innerHTML\]\s*=\s*["']\w|bypassSecurityTrustHtml\s*\()/gi,
|
|
382
|
+
languages: ["html", "typescript"],
|
|
383
|
+
fix: "Avoid [innerHTML] with user data. If unavoidable, never use bypassSecurityTrustHtml() on user input.",
|
|
384
|
+
fixCode: '<!-- BAD: bypass Angular sanitizer -->\n<!-- <div [innerHTML]="trustedHtml"></div> -->\n<!-- this.trustedHtml = this.sanitizer.bypassSecurityTrustHtml(userInput); -->\n\n<!-- GOOD: let Angular sanitize automatically -->\n<div [innerText]="userInput"></div>\n<!-- Or use Angular pipe with DOMPurify -->',
|
|
385
|
+
compliance: ["SOC2:CC7.1"],
|
|
386
|
+
},
|
|
387
|
+
{
|
|
388
|
+
id: "VG116",
|
|
389
|
+
name: "HTML Event Handler Injection via User Input",
|
|
390
|
+
severity: "high",
|
|
391
|
+
owasp: "A07:2025 Cross-Site Scripting",
|
|
392
|
+
description: "User input is interpolated into HTML attributes that accept JavaScript (onclick, onerror, onload, onmouseover, onfocus). Even without script tags, event handlers execute arbitrary JavaScript when the element is interacted with or loads.",
|
|
393
|
+
pattern: /(?:on(?:click|error|load|mouseover|focus|blur|submit|change|input|keyup|keydown))\s*=\s*(?:`[^`]*\$\{|["'][^"']*["']\s*\+\s*(?:user|input|query|param|req\.|data\.))/gi,
|
|
394
|
+
languages: ["javascript", "typescript", "html"],
|
|
395
|
+
fix: "Never interpolate user input into HTML event handler attributes. Use addEventListener with sanitized data instead.",
|
|
396
|
+
fixCode: '// BAD: user input in event handler\n// `<img onerror="${userInput}">`\n\n// GOOD: use addEventListener\nconst img = document.createElement("img");\nimg.addEventListener("error", () => handleError(sanitizedInput));',
|
|
397
|
+
compliance: ["SOC2:CC7.1"],
|
|
398
|
+
},
|
|
399
|
+
{
|
|
400
|
+
id: "VG120",
|
|
401
|
+
name: "SSRF via User-Controlled URL",
|
|
402
|
+
severity: "high",
|
|
403
|
+
owasp: "A10:2025 Server-Side Request Forgery",
|
|
404
|
+
description: "User-controlled input is passed directly to fetch(), axios, or http.request() as the URL. Attackers can make the server request internal services (169.254.169.254 for cloud metadata, localhost admin panels, internal APIs) leading to data exfiltration or remote code execution.",
|
|
405
|
+
pattern: /(?:\bfetch|axios\.(?:get|post|put|delete|patch|request)|got(?:\.(?:get|post|put|delete|patch))?|http\.(?:get|request)|https\.(?:get|request)|urllib\.request\.urlopen)\s*\(\s*(?!["'`]https?:\/\/|["'`]\/|`\/|`\$\{process\.env|new\s+URL)(?:[a-zA-Z_$]\w*)\s*[,)]/gi,
|
|
406
|
+
languages: ["javascript", "typescript", "python"],
|
|
407
|
+
fix: "Validate URLs against an allowlist of trusted domains. Block private/internal IP ranges (10.x, 172.16-31.x, 192.168.x, 127.x, 169.254.x, ::1). Use a URL parser to check the hostname before making the request.",
|
|
408
|
+
fixCode: '// Validate URL before fetching\nconst ALLOWED_HOSTS = ["api.example.com", "cdn.example.com"];\nconst parsed = new URL(userUrl);\nif (!ALLOWED_HOSTS.includes(parsed.hostname)) {\n throw new Error("Blocked: untrusted host");\n}\n// Also block private IPs\nconst ip = await dns.resolve(parsed.hostname);\nif (isPrivateIP(ip)) throw new Error("Blocked: internal host");\nawait fetch(parsed.href);',
|
|
409
|
+
compliance: ["SOC2:CC7.1", "PCI-DSS:Req6.5.1"],
|
|
410
|
+
},
|
|
411
|
+
{
|
|
412
|
+
id: "VG121",
|
|
413
|
+
name: "XSS via insertAdjacentHTML",
|
|
414
|
+
severity: "high",
|
|
415
|
+
owasp: "A02:2025 Injection",
|
|
416
|
+
description: "insertAdjacentHTML() renders raw HTML strings into the DOM without sanitization, enabling Cross-Site Scripting (XSS). Unlike textContent, this method parses and executes HTML including script tags and event handlers.",
|
|
417
|
+
pattern: /\.insertAdjacentHTML\s*\(\s*["'](?:beforebegin|afterbegin|beforeend|afterend)["']\s*,/gi,
|
|
418
|
+
languages: ["javascript", "typescript"],
|
|
419
|
+
fix: "Use textContent or innerText for plain text. If HTML is needed, sanitize with DOMPurify first.",
|
|
420
|
+
fixCode: '// BAD: XSS risk\nel.insertAdjacentHTML("beforeend", userInput);\n\n// GOOD: plain text\nel.insertAdjacentText("beforeend", userInput);\n\n// GOOD: sanitized HTML\nimport DOMPurify from "dompurify";\nel.insertAdjacentHTML("beforeend", DOMPurify.sanitize(userInput));',
|
|
421
|
+
compliance: ["SOC2:CC7.1", "PCI-DSS:Req6.5.7"],
|
|
422
|
+
},
|
|
423
|
+
{
|
|
424
|
+
id: "VG122",
|
|
425
|
+
name: "XSS/SSRF via XMLHttpRequest",
|
|
426
|
+
severity: "high",
|
|
427
|
+
owasp: "A02:2025 Injection",
|
|
428
|
+
description: "XMLHttpRequest.open() is called with a user-controlled URL. This can lead to SSRF (server-side) or data exfiltration (client-side) if the URL is not validated.",
|
|
429
|
+
pattern: /\.open\s*\(\s*["'](?:GET|POST|PUT|DELETE|PATCH)["']\s*,\s*(?:url|uri|href|endpoint|target|userUrl|inputUrl|src)\b/gi,
|
|
430
|
+
languages: ["javascript", "typescript"],
|
|
431
|
+
fix: "Validate the URL against an allowlist before passing to XMLHttpRequest.open(). Prefer fetch() with URL validation over raw XHR.",
|
|
432
|
+
fixCode: '// Validate URL before XHR\nconst allowed = ["https://api.example.com"];\nconst parsed = new URL(userUrl);\nif (!allowed.some(a => userUrl.startsWith(a))) throw new Error("Blocked");\nxhr.open("GET", parsed.href);',
|
|
433
|
+
compliance: ["SOC2:CC7.1"],
|
|
434
|
+
},
|
|
435
|
+
{
|
|
436
|
+
id: "VG123",
|
|
437
|
+
name: "SQL Injection via Template Literal",
|
|
438
|
+
severity: "critical",
|
|
439
|
+
owasp: "A02:2025 Injection",
|
|
440
|
+
description: "SQL query constructed using JavaScript template literals with interpolated variables. Template literal SQL is just as dangerous as string concatenation — any user-controlled value can break out of the SQL context and inject arbitrary queries.",
|
|
441
|
+
pattern: /(?:query|execute|raw|sql|prepare|QueryRow|QueryContext|db\.run|db\.all|db\.get|connection\.query)\s*\(\s*`[^`]*\$\{/gi,
|
|
442
|
+
languages: ["javascript", "typescript"],
|
|
443
|
+
fix: "Use parameterized queries. For tagged template literals (e.g. Prisma sql``, Drizzle sql``), the tagged version IS safe — only raw template literals passed to query() are dangerous.",
|
|
444
|
+
fixCode: '// BAD: template literal SQL\ndb.query(`SELECT * FROM users WHERE id = \'${userId}\'`);\n\n// GOOD: parameterized query\ndb.query("SELECT * FROM users WHERE id = $1", [userId]);\n\n// ALSO SAFE: tagged template literal (Prisma/Drizzle)\nimport { sql } from "drizzle-orm";\ndb.execute(sql`SELECT * FROM users WHERE id = ${userId}`);',
|
|
445
|
+
compliance: ["SOC2:CC7.1", "PCI-DSS:Req6.5.1"],
|
|
446
|
+
},
|
|
447
|
+
{
|
|
448
|
+
id: "VG124",
|
|
449
|
+
name: "Insecure Random for Security Token",
|
|
450
|
+
severity: "high",
|
|
451
|
+
owasp: "A02:2025 Cryptographic Failures",
|
|
452
|
+
description: "Math.random() is used to generate tokens, IDs, codes, or nonces. Math.random() is not cryptographically secure — its output is predictable, allowing attackers to guess reset tokens, session IDs, verification codes, or API keys.",
|
|
453
|
+
pattern: /(?:(?:token|secret|key|code|nonce|session|reset|verify|otp|password|auth|invite|magic|csrf)\w*\s*=[\s\S]{0,30}?Math\.random|Math\.random\s*\(\s*\)[\s\S]{0,50}?(?:token|secret|key|code|nonce|session|reset|verify|otp|password|auth|invite|magic|csrf))/gi,
|
|
454
|
+
languages: ["javascript", "typescript"],
|
|
455
|
+
fix: "Use crypto.randomUUID() or crypto.randomBytes() for security-sensitive random values.",
|
|
456
|
+
fixCode: '// BAD: predictable\nconst token = Math.random().toString(36);\n\n// GOOD: cryptographically secure\nimport { randomUUID, randomBytes } from "crypto";\nconst token = randomUUID();\nconst resetCode = randomBytes(32).toString("hex");\n// Or in browser:\nconst token = crypto.randomUUID();',
|
|
457
|
+
compliance: ["SOC2:CC6.1", "PCI-DSS:Req6.5.3"],
|
|
458
|
+
},
|
|
459
|
+
{
|
|
460
|
+
id: "VG125",
|
|
461
|
+
name: "Session Not Regenerated After Authentication",
|
|
462
|
+
severity: "high",
|
|
463
|
+
owasp: "A07:2025 Identification and Authentication Failures",
|
|
464
|
+
description: "User session is not regenerated after login/authentication. This enables session fixation attacks — an attacker can set a known session ID before the user logs in, then hijack their authenticated session.",
|
|
465
|
+
pattern: /(?:login|signIn|authenticate|logIn)\s*(?:=\s*async|\([\s\S]*?\)\s*(?:=>|{))[\s\S]{0,500}?(?:req\.session\.\w+\s*=)(?:(?!regenerate|destroy|create|rotate)[\s\S]){0,300}?(?:res\.|return|next\()/gi,
|
|
466
|
+
languages: ["javascript", "typescript"],
|
|
467
|
+
fix: "Regenerate the session after successful authentication. In Express: req.session.regenerate(). In Next.js: use a new session token.",
|
|
468
|
+
fixCode: '// Express: regenerate session after login\napp.post("/login", async (req, res) => {\n const user = await authenticate(req.body);\n if (!user) return res.status(401).json({ error: "Invalid credentials" });\n req.session.regenerate((err) => {\n req.session.userId = user.id;\n res.json({ ok: true });\n });\n});',
|
|
469
|
+
compliance: ["SOC2:CC6.6", "PCI-DSS:Req6.5.10"],
|
|
470
|
+
},
|
|
471
|
+
// ── Dynamic RegExp from User Input ──────────────────────────────
|
|
472
|
+
{
|
|
473
|
+
id: "VG126",
|
|
474
|
+
name: "Dynamic RegExp Construction from User Input",
|
|
475
|
+
severity: "medium",
|
|
476
|
+
owasp: "A03:2025 Injection",
|
|
477
|
+
description: "RegExp is constructed with a variable that may contain user input. Attackers can inject ReDoS patterns to freeze the event loop, or craft regex that matches unintended content.",
|
|
478
|
+
pattern: /new\s+RegExp\s*\(\s*(?!["'`\/])(?:\w+(?:\.\w+)?)\s*[,)]/g,
|
|
479
|
+
languages: ["javascript", "typescript"],
|
|
480
|
+
fix: "Escape user input before using in RegExp, or use string methods (.includes(), .startsWith()) instead.",
|
|
481
|
+
fixCode: '// BAD: user controls the regex\nnew RegExp(userInput, "gi")\n\n// GOOD: escape special characters\nfunction escapeRegex(s: string) {\n return s.replace(/[.*+?^${}()|[\\]\\\\]/g, "\\\\$&");\n}\nnew RegExp(escapeRegex(userInput), "gi")\n\n// BEST: use string methods\nitems.filter(i => i.name.toLowerCase().includes(query.toLowerCase()));',
|
|
482
|
+
compliance: ["SOC2:CC7.1"],
|
|
483
|
+
},
|
|
484
|
+
];
|