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.
Files changed (105) hide show
  1. package/LICENSE +21 -0
  2. package/README.md +264 -0
  3. package/action.yml +154 -0
  4. package/dist/banner.d.ts +1 -0
  5. package/dist/banner.js +14 -0
  6. package/dist/cli.d.ts +2 -0
  7. package/dist/cli.js +103 -0
  8. package/dist/data/popular.d.ts +17 -0
  9. package/dist/data/popular.js +103 -0
  10. package/dist/index.d.ts +5 -0
  11. package/dist/index.js +4 -0
  12. package/dist/report.d.ts +15 -0
  13. package/dist/report.js +317 -0
  14. package/dist/scan.d.ts +24 -0
  15. package/dist/scan.js +81 -0
  16. package/dist/scanners/community.d.ts +2 -0
  17. package/dist/scanners/community.js +155 -0
  18. package/dist/scanners/dependencies.d.ts +21 -0
  19. package/dist/scanners/dependencies.js +549 -0
  20. package/dist/scanners/index.d.ts +8 -0
  21. package/dist/scanners/index.js +13 -0
  22. package/dist/scanners/rls.d.ts +7 -0
  23. package/dist/scanners/rls.js +531 -0
  24. package/dist/scanners/secrets.d.ts +2 -0
  25. package/dist/scanners/secrets.js +439 -0
  26. package/dist/scanners/server-actions.d.ts +2 -0
  27. package/dist/scanners/server-actions.js +466 -0
  28. package/dist/types.d.ts +65 -0
  29. package/dist/types.js +10 -0
  30. package/dist/utils/ast.d.ts +13 -0
  31. package/dist/utils/ast.js +107 -0
  32. package/dist/utils/detect.d.ts +2 -0
  33. package/dist/utils/detect.js +77 -0
  34. package/dist/utils/entropy.d.ts +7 -0
  35. package/dist/utils/entropy.js +19 -0
  36. package/dist/utils/files.d.ts +13 -0
  37. package/dist/utils/files.js +154 -0
  38. package/dist/utils/osv.d.ts +26 -0
  39. package/dist/utils/osv.js +123 -0
  40. package/dist/utils/registry.d.ts +44 -0
  41. package/dist/utils/registry.js +195 -0
  42. package/dist/utils/sql.d.ts +20 -0
  43. package/dist/utils/sql.js +157 -0
  44. package/dist/utils/suppress.d.ts +19 -0
  45. package/dist/utils/suppress.js +56 -0
  46. package/dist/utils/traverse.d.ts +2 -0
  47. package/dist/utils/traverse.js +14 -0
  48. package/dist/vendor/gitleaks/rules.d.ts +13 -0
  49. package/dist/vendor/gitleaks/rules.js +1571 -0
  50. package/dist/vendor/guardvibe/index.d.ts +11 -0
  51. package/dist/vendor/guardvibe/index.js +60 -0
  52. package/dist/vendor/guardvibe/rules/advanced-security.d.ts +2 -0
  53. package/dist/vendor/guardvibe/rules/advanced-security.js +418 -0
  54. package/dist/vendor/guardvibe/rules/ai-host-security.d.ts +2 -0
  55. package/dist/vendor/guardvibe/rules/ai-host-security.js +190 -0
  56. package/dist/vendor/guardvibe/rules/ai-security.d.ts +2 -0
  57. package/dist/vendor/guardvibe/rules/ai-security.js +413 -0
  58. package/dist/vendor/guardvibe/rules/ai-tool-runtime.d.ts +2 -0
  59. package/dist/vendor/guardvibe/rules/ai-tool-runtime.js +194 -0
  60. package/dist/vendor/guardvibe/rules/api-security.d.ts +2 -0
  61. package/dist/vendor/guardvibe/rules/api-security.js +154 -0
  62. package/dist/vendor/guardvibe/rules/auth.d.ts +2 -0
  63. package/dist/vendor/guardvibe/rules/auth.js +222 -0
  64. package/dist/vendor/guardvibe/rules/cicd.d.ts +2 -0
  65. package/dist/vendor/guardvibe/rules/cicd.js +108 -0
  66. package/dist/vendor/guardvibe/rules/core.d.ts +2 -0
  67. package/dist/vendor/guardvibe/rules/core.js +484 -0
  68. package/dist/vendor/guardvibe/rules/cve-versions.d.ts +2 -0
  69. package/dist/vendor/guardvibe/rules/cve-versions.js +1130 -0
  70. package/dist/vendor/guardvibe/rules/database.d.ts +2 -0
  71. package/dist/vendor/guardvibe/rules/database.js +173 -0
  72. package/dist/vendor/guardvibe/rules/deployment.d.ts +2 -0
  73. package/dist/vendor/guardvibe/rules/deployment.js +269 -0
  74. package/dist/vendor/guardvibe/rules/dockerfile.d.ts +2 -0
  75. package/dist/vendor/guardvibe/rules/dockerfile.js +99 -0
  76. package/dist/vendor/guardvibe/rules/firebase.d.ts +2 -0
  77. package/dist/vendor/guardvibe/rules/firebase.js +96 -0
  78. package/dist/vendor/guardvibe/rules/go.d.ts +2 -0
  79. package/dist/vendor/guardvibe/rules/go.js +85 -0
  80. package/dist/vendor/guardvibe/rules/modern-stack.d.ts +2 -0
  81. package/dist/vendor/guardvibe/rules/modern-stack.js +631 -0
  82. package/dist/vendor/guardvibe/rules/nextjs.d.ts +2 -0
  83. package/dist/vendor/guardvibe/rules/nextjs.js +233 -0
  84. package/dist/vendor/guardvibe/rules/other-services.d.ts +2 -0
  85. package/dist/vendor/guardvibe/rules/other-services.js +72 -0
  86. package/dist/vendor/guardvibe/rules/payments.d.ts +2 -0
  87. package/dist/vendor/guardvibe/rules/payments.js +123 -0
  88. package/dist/vendor/guardvibe/rules/react-native.d.ts +2 -0
  89. package/dist/vendor/guardvibe/rules/react-native.js +132 -0
  90. package/dist/vendor/guardvibe/rules/services.d.ts +2 -0
  91. package/dist/vendor/guardvibe/rules/services.js +149 -0
  92. package/dist/vendor/guardvibe/rules/shell.d.ts +2 -0
  93. package/dist/vendor/guardvibe/rules/shell.js +72 -0
  94. package/dist/vendor/guardvibe/rules/sql.d.ts +2 -0
  95. package/dist/vendor/guardvibe/rules/sql.js +60 -0
  96. package/dist/vendor/guardvibe/rules/supply-chain.d.ts +2 -0
  97. package/dist/vendor/guardvibe/rules/supply-chain.js +243 -0
  98. package/dist/vendor/guardvibe/rules/terraform.d.ts +2 -0
  99. package/dist/vendor/guardvibe/rules/terraform.js +84 -0
  100. package/dist/vendor/guardvibe/rules/types.d.ts +14 -0
  101. package/dist/vendor/guardvibe/rules/types.js +11 -0
  102. package/dist/vendor/guardvibe/rules/web-security.d.ts +2 -0
  103. package/dist/vendor/guardvibe/rules/web-security.js +258 -0
  104. package/examples/security.yml +23 -0
  105. package/package.json +70 -0
@@ -0,0 +1,84 @@
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 terraformRules = [
12
+ {
13
+ id: "VG300",
14
+ name: "Public S3 bucket",
15
+ severity: "critical",
16
+ owasp: "A01:2025 Broken Access Control",
17
+ description: "S3 bucket configured with public access. This exposes data to the entire internet.",
18
+ pattern: /(?:acl\s*=\s*["']public|public_access_block_configuration[\s\S]*?block_public_acls\s*=\s*false)/gi,
19
+ languages: ["terraform"],
20
+ fix: "Set ACL to private and enable public access blocking.",
21
+ fixCode: 'resource "aws_s3_bucket" "example" {\n bucket = "my-bucket"\n}\n\nresource "aws_s3_bucket_public_access_block" "example" {\n bucket = aws_s3_bucket.example.id\n block_public_acls = true\n block_public_policy = true\n}',
22
+ compliance: ["SOC2:CC6.1", "PCI-DSS:Req3.4", "HIPAA:§164.312(a)"],
23
+ },
24
+ {
25
+ id: "VG301",
26
+ name: "Open security group ingress",
27
+ severity: "critical",
28
+ owasp: "A05:2025 Security Misconfiguration",
29
+ description: "Security group allows inbound traffic from 0.0.0.0/0 (entire internet).",
30
+ pattern: /cidr_blocks\s*=\s*\[?\s*["']0\.0\.0\.0\/0["']/gi,
31
+ languages: ["terraform"],
32
+ fix: "Restrict cidr_blocks to specific IP ranges needed for access.",
33
+ fixCode: 'ingress {\n from_port = 443\n to_port = 443\n protocol = "tcp"\n cidr_blocks = ["10.0.0.0/8"] # Internal only\n}',
34
+ compliance: ["SOC2:CC6.6", "PCI-DSS:Req6.5.10"],
35
+ },
36
+ {
37
+ id: "VG302",
38
+ name: "Unencrypted RDS instance",
39
+ severity: "high",
40
+ owasp: "A02:2025 Cryptographic Failures",
41
+ description: "RDS database instance without storage encryption. Data at rest is unprotected.",
42
+ pattern: /resource\s*["']aws_db_instance["'][^{]*\{[^}]*storage_encrypted\s*=\s*false/gi,
43
+ languages: ["terraform"],
44
+ fix: "Add storage_encrypted = true to all RDS instances.",
45
+ fixCode: 'resource "aws_db_instance" "example" {\n storage_encrypted = true\n # ...\n}',
46
+ compliance: ["SOC2:CC6.1", "PCI-DSS:Req3.4", "HIPAA:§164.312(a)"],
47
+ },
48
+ {
49
+ id: "VG303",
50
+ name: "IAM wildcard policy",
51
+ severity: "critical",
52
+ owasp: "A01:2025 Broken Access Control",
53
+ description: "IAM policy with wildcard (*) Action or Resource grants excessive permissions.",
54
+ pattern: /(?:Action|Resource)\s*=\s*["']\*["']/gi,
55
+ languages: ["terraform"],
56
+ fix: "Follow least-privilege: specify exact actions and resources needed.",
57
+ fixCode: 'statement {\n actions = ["s3:GetObject", "s3:PutObject"]\n resources = ["arn:aws:s3:::my-bucket/*"]\n}',
58
+ compliance: ["SOC2:CC6.1", "PCI-DSS:Req8"],
59
+ },
60
+ {
61
+ id: "VG304",
62
+ name: "Hardcoded secrets in Terraform",
63
+ severity: "critical",
64
+ owasp: "A01:2025 Broken Access Control",
65
+ description: "Secrets or credentials hardcoded in Terraform configuration files.",
66
+ pattern: /(?:password|secret_key|access_key|api_key|token)\s*=\s*["'][^"']{8,}["']/gi,
67
+ languages: ["terraform"],
68
+ fix: "Use Terraform variables with sensitive = true or reference a secrets manager.",
69
+ fixCode: 'variable "db_password" {\n type = string\n sensitive = true\n}\n\nresource "aws_db_instance" "example" {\n password = var.db_password\n}',
70
+ compliance: ["SOC2:CC6.1", "PCI-DSS:Req2.3", "HIPAA:§164.312(a)"],
71
+ },
72
+ {
73
+ id: "VG305",
74
+ name: "Terraform State File Tracked in Git",
75
+ severity: "critical",
76
+ owasp: "A01:2025 Broken Access Control",
77
+ description: "Terraform state file (*.tfstate) contains all infrastructure secrets in plaintext — database passwords, API keys, private IPs. If committed to git, these secrets are exposed in the repository history permanently.",
78
+ pattern: /(?:terraform\.tfstate|\.tfstate)/gi,
79
+ languages: ["terraform", "shell"],
80
+ fix: "Add *.tfstate and *.tfstate.* to .gitignore. Use remote backends (S3, GCS, Terraform Cloud) with encryption enabled.",
81
+ fixCode: '# .gitignore\n*.tfstate\n*.tfstate.*\n\n# Use encrypted remote backend\nterraform {\n backend "s3" {\n bucket = "my-tf-state"\n key = "prod/terraform.tfstate"\n encrypt = true\n }\n}',
82
+ compliance: ["SOC2:CC6.1", "PCI-DSS:Req2.3", "HIPAA:§164.312(a)"],
83
+ },
84
+ ];
@@ -0,0 +1,14 @@
1
+ export interface SecurityRule {
2
+ id: string;
3
+ name: string;
4
+ severity: "critical" | "high" | "medium" | "low" | "info";
5
+ owasp: string;
6
+ description: string;
7
+ pattern: RegExp;
8
+ languages: string[];
9
+ fix: string;
10
+ fixCode?: string;
11
+ compliance?: string[];
12
+ exploit?: string;
13
+ audit?: string;
14
+ }
@@ -0,0 +1,11 @@
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 {};
@@ -0,0 +1,2 @@
1
+ import type { SecurityRule } from "./types.js";
2
+ export declare const webSecurityRules: SecurityRule[];
@@ -0,0 +1,258 @@
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 webSecurityRules = [
12
+ // Webhook Security
13
+ {
14
+ id: "VG650",
15
+ name: "Webhook Missing Signature Verification",
16
+ severity: "high",
17
+ owasp: "A01:2025 Broken Access Control",
18
+ description: "Webhook endpoint processes incoming events without verifying the request signature. Attackers can send forged events.",
19
+ pattern: /(?:\/api\/webhook|\/webhook)[\s\S]*?export\s+(?:async\s+)?function\s+POST\s*\([^)]*\)\s*\{(?:(?!verify|signature|hmac|crypto|constructEvent|svix|webhookSecret)[\s\S])*?\}/g,
20
+ languages: ["javascript", "typescript"],
21
+ fix: "Always verify webhook signatures before processing events.",
22
+ fixCode: "// Verify webhook signature\nimport crypto from 'crypto';\nconst sig = request.headers.get('x-webhook-signature');\nconst expected = crypto.createHmac('sha256', process.env.WEBHOOK_SECRET!)\n .update(body).digest('hex');\nif (sig !== expected) return new Response('Unauthorized', { status: 401 });",
23
+ compliance: ["SOC2:CC6.6"],
24
+ },
25
+ {
26
+ id: "VG651",
27
+ name: "Webhook Secret Hardcoded",
28
+ severity: "critical",
29
+ owasp: "A07:2025 Sensitive Data Exposure",
30
+ description: "Webhook signing secret hardcoded in source code.",
31
+ pattern: /(?:webhook_?secret|signing_?secret|whsec_)\s*[:=]\s*["'][A-Za-z0-9_\-]{12,}["']/gi,
32
+ languages: ["javascript", "typescript"],
33
+ fix: "Use environment variables for webhook secrets.",
34
+ fixCode: "// Use environment variable\nconst webhookSecret = process.env.WEBHOOK_SECRET!;\n\n// .env.local\nWEBHOOK_SECRET=whsec_your_secret_here",
35
+ compliance: ["SOC2:CC6.1"],
36
+ },
37
+ // .env Security
38
+ {
39
+ id: "VG655",
40
+ name: "Sensitive Env Var in NEXT_PUBLIC",
41
+ severity: "critical",
42
+ owasp: "A07:2025 Sensitive Data Exposure",
43
+ description: "A sensitive service credential is exposed via NEXT_PUBLIC_ prefix. NEXT_PUBLIC_ variables are embedded in the client JavaScript bundle.",
44
+ pattern: /NEXT_PUBLIC_\w*(?:SECRET|PRIVATE|SERVICE_ROLE|API_KEY|ACCESS_TOKEN|AUTH_TOKEN|SIGNING|WEBHOOK)\w*\s*=/gi,
45
+ languages: ["shell", "javascript", "typescript"],
46
+ fix: "Remove NEXT_PUBLIC_ prefix from sensitive credentials. Access them only in server-side code.",
47
+ fixCode: "# .env.local — WRONG\n# NEXT_PUBLIC_API_KEY=sk_live_xxx\n\n# CORRECT — server-side only\nAPI_KEY=sk_live_xxx\n# Access via process.env.API_KEY in Server Components/Actions",
48
+ compliance: ["SOC2:CC6.1", "PCI-DSS:Req2.3"],
49
+ },
50
+ {
51
+ id: "VG656",
52
+ name: ".env File Committed to Git",
53
+ severity: "critical",
54
+ owasp: "A07:2025 Sensitive Data Exposure",
55
+ description: ".env file with secrets appears to be tracked by git. Secrets will be visible in repository history.",
56
+ pattern: /^(?:SUPABASE_SERVICE_ROLE_KEY|STRIPE_SECRET_KEY|DATABASE_URL|RESEND_API_KEY|OPENAI_API_KEY|ANTHROPIC_API_KEY|CLERK_SECRET_KEY|AUTH_SECRET|NEXTAUTH_SECRET)\s*=\s*\S{10,}/gm,
57
+ languages: ["shell"],
58
+ fix: "Add .env* to .gitignore immediately. Rotate any exposed secrets.",
59
+ fixCode: "# .gitignore\n.env\n.env.*\n.env.local\n!.env.example",
60
+ compliance: ["SOC2:CC6.1", "PCI-DSS:Req2.3", "HIPAA:§164.312(a)"],
61
+ },
62
+ {
63
+ id: "VG657",
64
+ name: ".env.example Contains Real Secrets",
65
+ severity: "high",
66
+ owasp: "A07:2025 Sensitive Data Exposure",
67
+ description: ".env.example file contains what appears to be real secret values instead of placeholders.",
68
+ pattern: /(?:SECRET|KEY|TOKEN|PASSWORD)\w*\s*=\s*(?:sk_live_|sk_test_|re_|whsec_|phx_|AKIA|ghp_|gho_)[A-Za-z0-9]{10,}/g,
69
+ languages: ["shell"],
70
+ fix: "Replace real values in .env.example with placeholders.",
71
+ fixCode: "# .env.example — use placeholders\nSTRIPE_SECRET_KEY=sk_test_your_key_here\nRESEND_API_KEY=re_your_key_here",
72
+ compliance: ["SOC2:CC6.1"],
73
+ },
74
+ // SEO / Meta Security
75
+ {
76
+ id: "VG660",
77
+ name: "Open Redirect in Meta Tags",
78
+ severity: "medium",
79
+ owasp: "A01:2025 Broken Access Control",
80
+ description: "Dynamic user input used in meta refresh or og:url tags. Can be used for phishing via open redirect.",
81
+ pattern: /(?:meta.*?(?:refresh|og:url)|(?:openGraph|twitter)[\s\S]{0,200}?url)\s*[:=]\s*(?:params|searchParams|query|req\.|request\.)/gi,
82
+ languages: ["javascript", "typescript"],
83
+ fix: "Validate and sanitize URLs used in meta tags. Use allowlists for domains.",
84
+ fixCode: '// Validate URL before using in meta tags\nconst ALLOWED_HOSTS = ["example.com"];\nconst url = new URL(input, "https://example.com");\nif (!ALLOWED_HOSTS.includes(url.hostname)) url.href = "https://example.com";\n\nexport const metadata = { openGraph: { url: url.href } };',
85
+ compliance: ["SOC2:CC6.6"],
86
+ },
87
+ {
88
+ id: "VG661",
89
+ name: "Sensitive Path in robots.txt",
90
+ severity: "medium",
91
+ owasp: "A05:2025 Security Misconfiguration",
92
+ description: "robots.txt disallows sensitive paths, revealing their existence to attackers. Disallow does not prevent access.",
93
+ pattern: /Disallow:\s*\/(?:admin|dashboard|internal|staging|debug|phpMyAdmin|\.env|backup|api\/internal)/gi,
94
+ languages: ["shell"],
95
+ fix: "Don't rely on robots.txt for security. Use authentication to protect sensitive paths. robots.txt is publicly readable.",
96
+ fixCode: "# robots.txt — keep it simple, don't list sensitive paths\nUser-agent: *\nDisallow:\n\n# Protect paths with authentication instead\n# middleware.ts → clerkMiddleware() for /admin/*",
97
+ compliance: ["SOC2:CC6.6"],
98
+ },
99
+ {
100
+ id: "VG662",
101
+ name: "Source Map Publicly Accessible",
102
+ severity: "medium",
103
+ owasp: "A05:2025 Security Misconfiguration",
104
+ description: "Source maps are generated for production builds, exposing original source code.",
105
+ pattern: /productionBrowserSourceMaps\s*:\s*true/g,
106
+ languages: ["javascript", "typescript"],
107
+ fix: "Set productionBrowserSourceMaps to false in next.config.",
108
+ fixCode: "// next.config.ts\nmodule.exports = {\n productionBrowserSourceMaps: false,\n};",
109
+ compliance: ["SOC2:CC6.1"],
110
+ },
111
+ // GitHub / Git Security
112
+ {
113
+ id: "VG665",
114
+ name: "GitHub Token Hardcoded",
115
+ severity: "critical",
116
+ owasp: "A07:2025 Sensitive Data Exposure",
117
+ description: "GitHub personal access token or app token hardcoded in source code.",
118
+ pattern: /(?:github_?token|gh_?token|GITHUB_TOKEN)\s*[:=]\s*["'](?:ghp_|gho_|ghu_|ghs_|ghr_|github_pat_)[A-Za-z0-9_]{10,}["']/gi,
119
+ languages: ["javascript", "typescript", "python", "shell"],
120
+ fix: "Use environment variables for GitHub tokens.",
121
+ fixCode: "// Use environment variable\nconst token = process.env.GITHUB_TOKEN;\n\n// .env.local\nGITHUB_TOKEN=ghp_your_token_here",
122
+ compliance: ["SOC2:CC6.1"],
123
+ },
124
+ // Cloudflare
125
+ {
126
+ id: "VG670",
127
+ name: "Cloudflare API Token Client Exposure",
128
+ severity: "critical",
129
+ owasp: "A07:2025 Sensitive Data Exposure",
130
+ description: "Cloudflare API token or key exposed in client-side code.",
131
+ pattern: /["']use client["'][\s\S]{0,500}?(?:CLOUDFLARE_API_TOKEN|CF_API_TOKEN|CLOUDFLARE_API_KEY)/g,
132
+ languages: ["javascript", "typescript"],
133
+ fix: "Use Cloudflare API tokens only in server-side code.",
134
+ fixCode: "// Server-side only (API route or Server Action)\nconst cf = new Cloudflare({ apiToken: process.env.CLOUDFLARE_API_TOKEN! });",
135
+ compliance: ["SOC2:CC6.1"],
136
+ },
137
+ {
138
+ id: "VG671",
139
+ name: "NEXT_PUBLIC Cloudflare Credentials",
140
+ severity: "critical",
141
+ owasp: "A07:2025 Sensitive Data Exposure",
142
+ description: "Cloudflare API credentials exposed via NEXT_PUBLIC_ prefix.",
143
+ pattern: /NEXT_PUBLIC_\w*(?:CLOUDFLARE|CF)\w*(?:API|TOKEN|KEY|SECRET)\s*=/gi,
144
+ languages: ["javascript", "typescript", "shell"],
145
+ fix: "Remove NEXT_PUBLIC_ prefix from Cloudflare credentials.",
146
+ fixCode: "# .env.local — WRONG\n# NEXT_PUBLIC_CF_API_TOKEN=xxx\n\n# CORRECT\nCLOUDFLARE_API_TOKEN=xxx",
147
+ compliance: ["SOC2:CC6.1"],
148
+ },
149
+ // OpenAI / AI Keys
150
+ {
151
+ id: "VG675",
152
+ name: "AI API Key Client Exposure",
153
+ severity: "critical",
154
+ owasp: "A07:2025 Sensitive Data Exposure",
155
+ description: "AI provider API key (OpenAI, Anthropic, Google AI) exposed in client-side code. These keys have direct cost implications.",
156
+ pattern: /["']use client["'][\s\S]{0,500}?(?:OPENAI_API_KEY|ANTHROPIC_API_KEY|GOOGLE_AI_API_KEY|GOOGLE_GENERATIVE_AI_API_KEY)/g,
157
+ languages: ["javascript", "typescript"],
158
+ fix: "Use AI API keys only in server-side code. Use API routes to proxy AI requests.",
159
+ fixCode: "// Server-side only (API route)\nimport OpenAI from 'openai';\nconst openai = new OpenAI(); // reads OPENAI_API_KEY from env",
160
+ compliance: ["SOC2:CC6.1"],
161
+ },
162
+ {
163
+ id: "VG676",
164
+ name: "NEXT_PUBLIC AI API Key",
165
+ severity: "critical",
166
+ owasp: "A07:2025 Sensitive Data Exposure",
167
+ description: "AI provider API key exposed via NEXT_PUBLIC_ prefix. Anyone can use your AI credits.",
168
+ pattern: /NEXT_PUBLIC_\w*(?:OPENAI|ANTHROPIC|GOOGLE_AI|GEMINI|COHERE|REPLICATE)\w*(?:KEY|TOKEN|SECRET)\s*=/gi,
169
+ languages: ["javascript", "typescript", "shell"],
170
+ fix: "Remove NEXT_PUBLIC_ prefix from AI API keys. Route AI requests through server-side API routes.",
171
+ fixCode: "# .env.local — WRONG\n# NEXT_PUBLIC_OPENAI_API_KEY=sk-xxx\n\n# CORRECT — server-side only\nOPENAI_API_KEY=sk-xxx\n# Use in API route: const openai = new OpenAI();",
172
+ compliance: ["SOC2:CC6.1"],
173
+ },
174
+ {
175
+ id: "VG677",
176
+ name: "Hardcoded AI API Key",
177
+ severity: "critical",
178
+ owasp: "A07:2025 Sensitive Data Exposure",
179
+ description: "AI provider API key hardcoded in source code.",
180
+ pattern: /(?:openai|OpenAI|anthropic|Anthropic)\s*\(\s*\{\s*apiKey\s*:\s*["'](?:sk-|sk-ant-)[A-Za-z0-9\-]{10,}["']/g,
181
+ languages: ["javascript", "typescript"],
182
+ fix: "Use environment variables for AI API keys.",
183
+ fixCode: "// Reads from OPENAI_API_KEY env automatically\nconst openai = new OpenAI();",
184
+ compliance: ["SOC2:CC6.1"],
185
+ },
186
+ {
187
+ id: "VG678",
188
+ name: "Missing X-Content-Type-Options Header",
189
+ severity: "high",
190
+ owasp: "A05:2021 Security Misconfiguration",
191
+ description: "Response serving user-uploaded files does not set X-Content-Type-Options: nosniff. Browsers may MIME-sniff the content and execute uploaded files as HTML/JavaScript, enabling stored XSS via file uploads.",
192
+ pattern: /(?:res\.sendFile|res\.download|createReadStream|getSignedUrl|getPublicUrl|\.pipe\s*\(\s*res)[\s\S]{0,500}?(?:(?!X-Content-Type-Options|nosniff)[\s\S]){10,}?(?:res\.end|\.pipe|return|response)/gi,
193
+ languages: ["javascript", "typescript"],
194
+ fix: "Set X-Content-Type-Options: nosniff on all responses serving user-uploaded content.",
195
+ fixCode: '// Set nosniff header for uploaded file responses\nres.setHeader("X-Content-Type-Options", "nosniff");\nres.setHeader("Content-Disposition", "attachment"); // force download for unknown types\nres.sendFile(filePath);',
196
+ compliance: ["SOC2:CC6.1"],
197
+ },
198
+ {
199
+ id: "VG1080",
200
+ name: "DOM XSS via document.write()",
201
+ severity: "high",
202
+ owasp: "A03:2025 Injection",
203
+ description: "document.write()/document.writeln() called with user-controlled or concatenated/interpolated content. document.write parses its argument as HTML, so attacker-influenced input (location, query params, cookies, window.name) leads to DOM-based cross-site scripting.",
204
+ pattern: /document\.write(?:ln)?\s*\(\s*(?:[^)]*?(?:location|document\.(?:URL|cookie|referrer)|searchParams|req\.|request\.|params\.|query\.|window\.name|\binput\b)|`[^`]*\$\{|["'][^"']*["']\s*\+)/gi,
205
+ languages: ["javascript", "typescript"],
206
+ fix: "Never build HTML with document.write from untrusted input. Use safe DOM APIs (textContent, createElement) or sanitize with DOMPurify before inserting.",
207
+ fixCode: "// BAD: document.write('<div>' + location.hash + '</div>')\n// GOOD:\nconst el = document.createElement('div');\nel.textContent = userValue; // auto-escaped\ncontainer.appendChild(el);",
208
+ compliance: ["SOC2:CC7.1", "PCI-DSS:Req6.5.7"],
209
+ },
210
+ {
211
+ id: "VG1081",
212
+ name: "Insecure Block Cipher Mode (ECB / deprecated createCipher)",
213
+ severity: "high",
214
+ owasp: "A02:2025 Cryptographic Failures",
215
+ description: "AES/DES used in ECB mode (createCipheriv with an *-ecb algorithm), or the deprecated crypto.createCipher() which derives a key/IV insecurely. ECB encrypts identical plaintext blocks to identical ciphertext blocks, leaking structure; createCipher is password-derived and IV-less. Both are cryptographically broken for confidentiality.",
216
+ pattern: /(?:createCipheriv\s*\(\s*["'][^"']*-ecb["']|createDecipheriv\s*\(\s*["'][^"']*-ecb["']|crypto\s*\.\s*createCipher\s*\(\s*["'])/gi,
217
+ languages: ["javascript", "typescript"],
218
+ fix: "Use an authenticated mode: aes-256-gcm with a random 12-byte IV per message (crypto.randomBytes), or aes-256-cbc with a random IV and a separate MAC. Never use ECB; replace createCipher with createCipheriv.",
219
+ fixCode: "// GOOD: AES-256-GCM with a random IV\nconst iv = crypto.randomBytes(12);\nconst cipher = crypto.createCipheriv('aes-256-gcm', key, iv);\nconst enc = Buffer.concat([cipher.update(data), cipher.final()]);\nconst tag = cipher.getAuthTag();",
220
+ compliance: ["SOC2:CC6.1", "PCI-DSS:Req3.5", "HIPAA:§164.312(a)(2)(iv)"],
221
+ },
222
+ {
223
+ id: "VG1082",
224
+ name: "Server-Side Template Injection (SSTI)",
225
+ severity: "critical",
226
+ owasp: "A03:2025 Injection",
227
+ description: "A template engine compiles/renders a user-controlled template SOURCE (not just user data bound into a fixed template). Handlebars.compile, ejs.render/compile, pug, nunjucks.renderString, or lodash _.template on attacker-influenced input allows server-side template injection — often a path to remote code execution.",
228
+ pattern: /(?:Handlebars\.compile|ejs\.(?:render|compile)|pug\.(?:compile|render)|nunjucks\.(?:renderString|compile)|_\.template|lodash\.template|dot\.template)\s*\(\s*(?:[^,)]*?(?:req\.|request\.|\bbody\b|\bparams\b|\bquery\b|userInput|\binput\b)|`[^`]*\$\{|[^,)]*\+)/gi,
229
+ languages: ["javascript", "typescript"],
230
+ fix: "Never compile a template from user input. Keep template sources static/server-owned and pass user values only as DATA to a precompiled template. If user-authored templates are required, use a sandboxed engine with no access to globals.",
231
+ fixCode: "// BAD: ejs.render(req.body.template, data)\n// GOOD: fixed template, user value as data only\nconst tpl = ejs.compile(STATIC_TEMPLATE);\nres.send(tpl({ name: req.body.name }));",
232
+ compliance: ["SOC2:CC7.1", "PCI-DSS:Req6.5.1"],
233
+ },
234
+ {
235
+ id: "VG1083",
236
+ name: "JWT Verification Bypass (decode/none-algorithm)",
237
+ severity: "critical",
238
+ owasp: "A07:2025 Auth Failures",
239
+ description: "A JWT is trusted without a real signature check: jwt.decode() of a request-supplied token returns the payload WITHOUT verifying the signature (any forged token is accepted), or jwt.verify() is called with algorithms including 'none' (algorithm-confusion / signature-stripping). Either lets an attacker mint arbitrary identities/claims.",
240
+ pattern: /(?:jwt\.verify\s*\([^;]{0,200}?algorithms\s*:\s*\[[^\]]*["']none["']|(?:jwt|jsonwebtoken|jose)\s*\.\s*decode\s*\(\s*(?:req\.|request\.|token\b|authToken|bearerToken|accessToken|authorization\b|headers\b))/gi,
241
+ languages: ["javascript", "typescript"],
242
+ fix: "Always verify the signature with an explicit algorithm allowlist: jwt.verify(token, secret, { algorithms: ['HS256'] }). Never use jwt.decode() for authentication/authorization, and never include 'none' in the algorithms list.",
243
+ fixCode: "// BAD: const user = jwt.decode(req.headers.authorization);\n// GOOD:\nconst user = jwt.verify(token, process.env.JWT_SECRET, { algorithms: ['HS256'] });",
244
+ compliance: ["SOC2:CC6.6", "PCI-DSS:Req6.5.10", "HIPAA:§164.312(d)"],
245
+ },
246
+ {
247
+ id: "VG1084",
248
+ name: "DOM XSS via jQuery HTML insertion",
249
+ severity: "high",
250
+ owasp: "A03:2025 Injection",
251
+ description: "jQuery DOM-insertion methods (.html(), .append(), .prepend(), .after(), .before(), .replaceWith(), .wrap*) parse their argument as HTML. Passing user-controlled or concatenated/interpolated content (location, query params, .val(), .data()) into them causes DOM-based cross-site scripting.",
252
+ pattern: /\$\([^)]*\)(?:\.\w+\([^)]*\))*?\.(?:html|append|prepend|after|before|replaceWith|wrap|wrapAll|wrapInner)\s*\(\s*(?:[^)]*?(?:location|document\.(?:URL|cookie|referrer)|searchParams|req\.|request\.|params\.|query\.|window\.name|\.val\s*\(\s*\)|\.data\s*\()|`[^`]*\$\{|["'][^"']*["']\s*\+)/gi,
253
+ languages: ["javascript", "typescript"],
254
+ fix: "Use .text() instead of .html() for untrusted content, or sanitize with DOMPurify before insertion. Build elements with $('<div>').text(value) rather than concatenating HTML strings.",
255
+ fixCode: "// BAD: $('#out').html(location.hash)\n// GOOD:\n$('#out').text(userValue); // auto-escaped\n// or: $('#out').html(DOMPurify.sanitize(html));",
256
+ compliance: ["SOC2:CC7.1", "PCI-DSS:Req6.5.7"],
257
+ },
258
+ ];
@@ -0,0 +1,23 @@
1
+ # Copy to .github/workflows/security.yml in your own repository.
2
+ name: ClearToShip
3
+
4
+ on:
5
+ pull_request:
6
+ push:
7
+ branches: [main]
8
+
9
+ permissions:
10
+ contents: read
11
+ pull-requests: write # for the summary comment
12
+ security-events: write # for the optional SARIF upload
13
+
14
+ jobs:
15
+ preflight:
16
+ runs-on: ubuntu-latest
17
+ steps:
18
+ - uses: actions/checkout@v4
19
+ - uses: murtazaozdemir/cleartoship@v0.3.0
20
+ with:
21
+ fail-on: critical # block the PR only on criticals
22
+ comment: true # post a summary comment on the PR
23
+ sarif: true # also upload to the Security tab
package/package.json ADDED
@@ -0,0 +1,70 @@
1
+ {
2
+ "name": "cleartoship",
3
+ "version": "0.4.0",
4
+ "description": "The 30-second pre-launch security clearance for AI-built & vibe-coded apps. Catches missing Server Action auth, Supabase RLS holes, hallucinated npm packages and leaked keys.",
5
+ "keywords": [
6
+ "security",
7
+ "sast",
8
+ "nextjs",
9
+ "server-actions",
10
+ "supabase",
11
+ "rls",
12
+ "slopsquatting",
13
+ "hallucinated-packages",
14
+ "vibe-coding",
15
+ "ai-code-review"
16
+ ],
17
+ "license": "MIT",
18
+ "type": "module",
19
+ "bin": {
20
+ "cleartoship": "dist/cli.js"
21
+ },
22
+ "main": "dist/index.js",
23
+ "types": "dist/index.d.ts",
24
+ "exports": {
25
+ ".": {
26
+ "types": "./dist/index.d.ts",
27
+ "import": "./dist/index.js"
28
+ }
29
+ },
30
+ "files": [
31
+ "dist",
32
+ "README.md",
33
+ "LICENSE",
34
+ "action.yml",
35
+ "examples"
36
+ ],
37
+ "engines": {
38
+ "node": ">=18.18"
39
+ },
40
+ "scripts": {
41
+ "build": "tsc -p tsconfig.json && chmod +x dist/cli.js",
42
+ "dev": "tsc -p tsconfig.json --watch",
43
+ "test": "node --test test/*.test.js",
44
+ "prepublishOnly": "npm run build"
45
+ },
46
+ "dependencies": {
47
+ "@babel/parser": "^7.25.0",
48
+ "@babel/traverse": "^7.25.0",
49
+ "@babel/types": "^7.25.0",
50
+ "commander": "^12.1.0",
51
+ "picocolors": "^1.1.0"
52
+ },
53
+ "devDependencies": {
54
+ "@types/babel__traverse": "^7.20.6",
55
+ "@types/node": "^22.5.0",
56
+ "typescript": "^5.6.0"
57
+ },
58
+ "optionalDependencies": {
59
+ "pg": "^8.13.0"
60
+ },
61
+ "repository": {
62
+ "type": "git",
63
+ "url": "git+https://github.com/murtazaozdemir/cleartoship.git"
64
+ },
65
+ "bugs": {
66
+ "url": "https://github.com/murtazaozdemir/cleartoship/issues"
67
+ },
68
+ "homepage": "https://cleartoship.app",
69
+ "author": "Murtaza Ozdemir"
70
+ }