ship-safe 1.0.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +201 -0
- package/ai-defense/system-prompt-armor.md +327 -0
- package/checklists/launch-day.md +168 -0
- package/cli/bin/ship-safe.js +97 -0
- package/cli/commands/checklist.js +223 -0
- package/cli/commands/init.js +265 -0
- package/cli/commands/scan.js +261 -0
- package/cli/index.js +12 -0
- package/cli/utils/output.js +177 -0
- package/cli/utils/patterns.js +265 -0
- package/configs/nextjs-security-headers.js +220 -0
- package/package.json +54 -0
- package/snippets/README.md +58 -0
|
@@ -0,0 +1,265 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Secret Detection Patterns
|
|
3
|
+
* =========================
|
|
4
|
+
*
|
|
5
|
+
* These regex patterns detect common secret formats.
|
|
6
|
+
* Each pattern includes:
|
|
7
|
+
* - name: Human-readable identifier
|
|
8
|
+
* - pattern: Regular expression
|
|
9
|
+
* - severity: 'critical' | 'high' | 'medium'
|
|
10
|
+
* - description: Why this matters
|
|
11
|
+
*
|
|
12
|
+
* MAINTENANCE NOTES:
|
|
13
|
+
* - Patterns should have low false-positive rates
|
|
14
|
+
* - Test new patterns against real codebases before adding
|
|
15
|
+
* - Order doesn't matter (all patterns are checked)
|
|
16
|
+
*/
|
|
17
|
+
|
|
18
|
+
export const SECRET_PATTERNS = [
|
|
19
|
+
// =========================================================================
|
|
20
|
+
// CRITICAL: These are almost always real secrets
|
|
21
|
+
// =========================================================================
|
|
22
|
+
{
|
|
23
|
+
name: 'AWS Access Key ID',
|
|
24
|
+
pattern: /AKIA[0-9A-Z]{16}/g,
|
|
25
|
+
severity: 'critical',
|
|
26
|
+
description: 'AWS Access Keys can access your entire AWS account. Rotate immediately if exposed.'
|
|
27
|
+
},
|
|
28
|
+
{
|
|
29
|
+
name: 'AWS Secret Access Key',
|
|
30
|
+
pattern: /(?:aws_secret_access_key|aws_secret_key)[\s]*[=:][\s]*["']?([A-Za-z0-9/+=]{40})["']?/gi,
|
|
31
|
+
severity: 'critical',
|
|
32
|
+
description: 'AWS Secret Keys paired with Access Keys grant full AWS access.'
|
|
33
|
+
},
|
|
34
|
+
{
|
|
35
|
+
name: 'GitHub Personal Access Token',
|
|
36
|
+
pattern: /ghp_[a-zA-Z0-9]{36}/g,
|
|
37
|
+
severity: 'critical',
|
|
38
|
+
description: 'GitHub PATs can access repositories, create commits, and manage settings.'
|
|
39
|
+
},
|
|
40
|
+
{
|
|
41
|
+
name: 'GitHub OAuth Token',
|
|
42
|
+
pattern: /gho_[a-zA-Z0-9]{36}/g,
|
|
43
|
+
severity: 'critical',
|
|
44
|
+
description: 'GitHub OAuth tokens grant authorized application access.'
|
|
45
|
+
},
|
|
46
|
+
{
|
|
47
|
+
name: 'GitHub App Token',
|
|
48
|
+
pattern: /ghu_[a-zA-Z0-9]{36}|ghs_[a-zA-Z0-9]{36}/g,
|
|
49
|
+
severity: 'critical',
|
|
50
|
+
description: 'GitHub App tokens have installation-level access to repositories.'
|
|
51
|
+
},
|
|
52
|
+
{
|
|
53
|
+
name: 'Stripe Live Secret Key',
|
|
54
|
+
pattern: /sk_live_[a-zA-Z0-9]{24,}/g,
|
|
55
|
+
severity: 'critical',
|
|
56
|
+
description: 'Stripe live keys can process real payments and access customer data.'
|
|
57
|
+
},
|
|
58
|
+
{
|
|
59
|
+
name: 'Stripe Live Publishable Key',
|
|
60
|
+
pattern: /pk_live_[a-zA-Z0-9]{24,}/g,
|
|
61
|
+
severity: 'high',
|
|
62
|
+
description: 'Stripe publishable keys are less sensitive but should not be in server code.'
|
|
63
|
+
},
|
|
64
|
+
{
|
|
65
|
+
name: 'Private Key Block',
|
|
66
|
+
pattern: /-----BEGIN\s+(RSA\s+|EC\s+|DSA\s+|OPENSSH\s+)?PRIVATE\s+KEY-----/g,
|
|
67
|
+
severity: 'critical',
|
|
68
|
+
description: 'Private keys enable impersonation and decryption. Never commit these.'
|
|
69
|
+
},
|
|
70
|
+
|
|
71
|
+
// =========================================================================
|
|
72
|
+
// HIGH: Very likely to be secrets
|
|
73
|
+
// =========================================================================
|
|
74
|
+
{
|
|
75
|
+
name: 'OpenAI API Key',
|
|
76
|
+
pattern: /sk-[a-zA-Z0-9]{20,}/g,
|
|
77
|
+
severity: 'high',
|
|
78
|
+
description: 'OpenAI keys can rack up API charges and access your usage history.'
|
|
79
|
+
},
|
|
80
|
+
{
|
|
81
|
+
name: 'Anthropic API Key',
|
|
82
|
+
pattern: /sk-ant-[a-zA-Z0-9-]{32,}/g,
|
|
83
|
+
severity: 'high',
|
|
84
|
+
description: 'Anthropic API keys grant access to Claude and your usage quota.'
|
|
85
|
+
},
|
|
86
|
+
{
|
|
87
|
+
name: 'Slack Token',
|
|
88
|
+
pattern: /xox[baprs]-[0-9]{10,13}-[0-9]{10,13}[a-zA-Z0-9-]*/g,
|
|
89
|
+
severity: 'high',
|
|
90
|
+
description: 'Slack tokens can read messages, post content, and access workspace data.'
|
|
91
|
+
},
|
|
92
|
+
{
|
|
93
|
+
name: 'Slack Webhook',
|
|
94
|
+
pattern: /https:\/\/hooks\.slack\.com\/services\/T[a-zA-Z0-9_]+\/B[a-zA-Z0-9_]+\/[a-zA-Z0-9_]+/g,
|
|
95
|
+
severity: 'high',
|
|
96
|
+
description: 'Slack webhooks allow posting messages to channels.'
|
|
97
|
+
},
|
|
98
|
+
{
|
|
99
|
+
name: 'Discord Webhook',
|
|
100
|
+
pattern: /https:\/\/discord(?:app)?\.com\/api\/webhooks\/[0-9]+\/[a-zA-Z0-9_-]+/g,
|
|
101
|
+
severity: 'high',
|
|
102
|
+
description: 'Discord webhooks allow posting messages to channels.'
|
|
103
|
+
},
|
|
104
|
+
{
|
|
105
|
+
name: 'Discord Bot Token',
|
|
106
|
+
pattern: /[MN][A-Za-z\d]{23,}\.[\w-]{6}\.[\w-]{27}/g,
|
|
107
|
+
severity: 'high',
|
|
108
|
+
description: 'Discord bot tokens grant full control over your bot.'
|
|
109
|
+
},
|
|
110
|
+
{
|
|
111
|
+
name: 'Twilio API Key',
|
|
112
|
+
pattern: /SK[a-f0-9]{32}/g,
|
|
113
|
+
severity: 'high',
|
|
114
|
+
description: 'Twilio keys can send SMS/calls and access account data.'
|
|
115
|
+
},
|
|
116
|
+
{
|
|
117
|
+
name: 'SendGrid API Key',
|
|
118
|
+
pattern: /SG\.[a-zA-Z0-9_-]{22}\.[a-zA-Z0-9_-]{43}/g,
|
|
119
|
+
severity: 'high',
|
|
120
|
+
description: 'SendGrid keys can send emails from your account.'
|
|
121
|
+
},
|
|
122
|
+
{
|
|
123
|
+
name: 'Mailgun API Key',
|
|
124
|
+
pattern: /key-[a-zA-Z0-9]{32}/g,
|
|
125
|
+
severity: 'high',
|
|
126
|
+
description: 'Mailgun keys can send emails and access logs.'
|
|
127
|
+
},
|
|
128
|
+
{
|
|
129
|
+
name: 'Firebase/Google Service Account',
|
|
130
|
+
pattern: /"type":\s*"service_account"/g,
|
|
131
|
+
severity: 'high',
|
|
132
|
+
description: 'Service account JSON files grant broad GCP/Firebase access.'
|
|
133
|
+
},
|
|
134
|
+
{
|
|
135
|
+
name: 'Supabase Service Role Key',
|
|
136
|
+
pattern: /eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9\.[a-zA-Z0-9_-]+\.[a-zA-Z0-9_-]+/g,
|
|
137
|
+
severity: 'high',
|
|
138
|
+
description: 'Supabase service role keys bypass Row Level Security. Keep server-side only.'
|
|
139
|
+
},
|
|
140
|
+
{
|
|
141
|
+
name: 'Vercel Token',
|
|
142
|
+
pattern: /vercel_[a-zA-Z0-9]{24}/gi,
|
|
143
|
+
severity: 'high',
|
|
144
|
+
description: 'Vercel tokens can deploy and manage your projects.'
|
|
145
|
+
},
|
|
146
|
+
{
|
|
147
|
+
name: 'NPM Token',
|
|
148
|
+
pattern: /npm_[a-zA-Z0-9]{36}/g,
|
|
149
|
+
severity: 'high',
|
|
150
|
+
description: 'NPM tokens can publish packages under your account.'
|
|
151
|
+
},
|
|
152
|
+
{
|
|
153
|
+
name: 'Heroku API Key',
|
|
154
|
+
pattern: /[hH]eroku.*[a-f0-9]{8}-[a-f0-9]{4}-[a-f0-9]{4}-[a-f0-9]{4}-[a-f0-9]{12}/g,
|
|
155
|
+
severity: 'high',
|
|
156
|
+
description: 'Heroku API keys can manage apps and dynos.'
|
|
157
|
+
},
|
|
158
|
+
{
|
|
159
|
+
name: 'DigitalOcean Token',
|
|
160
|
+
pattern: /dop_v1_[a-f0-9]{64}/g,
|
|
161
|
+
severity: 'high',
|
|
162
|
+
description: 'DigitalOcean tokens can manage droplets and resources.'
|
|
163
|
+
},
|
|
164
|
+
|
|
165
|
+
// =========================================================================
|
|
166
|
+
// MEDIUM: Likely secrets, but may have false positives
|
|
167
|
+
// =========================================================================
|
|
168
|
+
{
|
|
169
|
+
name: 'Generic API Key',
|
|
170
|
+
pattern: /["']?(?:api[_-]?key|apikey)["']?\s*[:=]\s*["']([a-zA-Z0-9_\-]{16,})["']/gi,
|
|
171
|
+
severity: 'medium',
|
|
172
|
+
description: 'Hardcoded API keys should be moved to environment variables.'
|
|
173
|
+
},
|
|
174
|
+
{
|
|
175
|
+
name: 'Generic Secret',
|
|
176
|
+
pattern: /["']?(?:secret|secret[_-]?key)["']?\s*[:=]\s*["']([a-zA-Z0-9_\-]{16,})["']/gi,
|
|
177
|
+
severity: 'medium',
|
|
178
|
+
description: 'Hardcoded secrets should be moved to environment variables.'
|
|
179
|
+
},
|
|
180
|
+
{
|
|
181
|
+
name: 'Password Assignment',
|
|
182
|
+
pattern: /["']?password["']?\s*[:=]\s*["']([^"']{8,})["']/gi,
|
|
183
|
+
severity: 'medium',
|
|
184
|
+
description: 'Hardcoded passwords are a critical vulnerability.'
|
|
185
|
+
},
|
|
186
|
+
{
|
|
187
|
+
name: 'Database URL with Credentials',
|
|
188
|
+
pattern: /(mongodb|postgres|postgresql|mysql|redis):\/\/[^:]+:[^@]+@[^\s"']+/gi,
|
|
189
|
+
severity: 'medium',
|
|
190
|
+
description: 'Database URLs with embedded passwords expose your database.'
|
|
191
|
+
},
|
|
192
|
+
{
|
|
193
|
+
name: 'Bearer Token',
|
|
194
|
+
pattern: /["']Bearer\s+[a-zA-Z0-9_\-\.=]{20,}["']/gi,
|
|
195
|
+
severity: 'medium',
|
|
196
|
+
description: 'Hardcoded bearer tokens should not be in source code.'
|
|
197
|
+
},
|
|
198
|
+
{
|
|
199
|
+
name: 'JWT Token',
|
|
200
|
+
pattern: /["']?jwt["']?\s*[:=]\s*["']?(eyJ[a-zA-Z0-9_-]*\.eyJ[a-zA-Z0-9_-]*\.[a-zA-Z0-9_-]*)["']?/gi,
|
|
201
|
+
severity: 'medium',
|
|
202
|
+
description: 'JWTs in source code may be test tokens, but verify they\'re not production.'
|
|
203
|
+
},
|
|
204
|
+
{
|
|
205
|
+
name: 'Basic Auth Header',
|
|
206
|
+
pattern: /["']Basic\s+[A-Za-z0-9+/=]{10,}["']/gi,
|
|
207
|
+
severity: 'medium',
|
|
208
|
+
description: 'Basic auth headers contain base64-encoded credentials.'
|
|
209
|
+
}
|
|
210
|
+
];
|
|
211
|
+
|
|
212
|
+
// =============================================================================
|
|
213
|
+
// FILES AND DIRECTORIES TO SKIP
|
|
214
|
+
// =============================================================================
|
|
215
|
+
|
|
216
|
+
export const SKIP_DIRS = new Set([
|
|
217
|
+
'node_modules',
|
|
218
|
+
'.git',
|
|
219
|
+
'venv',
|
|
220
|
+
'env',
|
|
221
|
+
'.venv',
|
|
222
|
+
'__pycache__',
|
|
223
|
+
'.next',
|
|
224
|
+
'.nuxt',
|
|
225
|
+
'dist',
|
|
226
|
+
'build',
|
|
227
|
+
'out',
|
|
228
|
+
'.output',
|
|
229
|
+
'coverage',
|
|
230
|
+
'.nyc_output',
|
|
231
|
+
'vendor',
|
|
232
|
+
'.bundle',
|
|
233
|
+
'.cache',
|
|
234
|
+
'.parcel-cache',
|
|
235
|
+
'.turbo',
|
|
236
|
+
'bower_components',
|
|
237
|
+
'jspm_packages',
|
|
238
|
+
'.vercel',
|
|
239
|
+
'.netlify',
|
|
240
|
+
'.serverless'
|
|
241
|
+
]);
|
|
242
|
+
|
|
243
|
+
export const SKIP_EXTENSIONS = new Set([
|
|
244
|
+
// Images
|
|
245
|
+
'.png', '.jpg', '.jpeg', '.gif', '.svg', '.ico', '.webp', '.bmp', '.tiff',
|
|
246
|
+
// Fonts
|
|
247
|
+
'.woff', '.woff2', '.ttf', '.eot', '.otf',
|
|
248
|
+
// Media
|
|
249
|
+
'.mp3', '.mp4', '.wav', '.avi', '.mov', '.webm', '.ogg',
|
|
250
|
+
// Archives
|
|
251
|
+
'.zip', '.tar', '.gz', '.rar', '.7z', '.bz2',
|
|
252
|
+
// Documents
|
|
253
|
+
'.pdf', '.doc', '.docx', '.xls', '.xlsx', '.ppt', '.pptx',
|
|
254
|
+
// Lock files (usually very large and auto-generated)
|
|
255
|
+
'.lock',
|
|
256
|
+
// Minified files
|
|
257
|
+
'.min.js', '.min.css',
|
|
258
|
+
// Binaries
|
|
259
|
+
'.exe', '.dll', '.so', '.dylib', '.bin', '.o', '.a',
|
|
260
|
+
// Maps
|
|
261
|
+
'.map'
|
|
262
|
+
]);
|
|
263
|
+
|
|
264
|
+
// Maximum file size to scan (1MB)
|
|
265
|
+
export const MAX_FILE_SIZE = 1_000_000;
|
|
@@ -0,0 +1,220 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Ship Safe - Next.js Security Headers Configuration
|
|
3
|
+
* ===================================================
|
|
4
|
+
*
|
|
5
|
+
* Drop this into your next.config.js to add essential security headers.
|
|
6
|
+
*
|
|
7
|
+
* WHY THESE HEADERS MATTER:
|
|
8
|
+
* Without security headers, your app is vulnerable to:
|
|
9
|
+
* - Clickjacking (your site loaded in malicious iframes)
|
|
10
|
+
* - XSS attacks (malicious scripts injected into your pages)
|
|
11
|
+
* - MIME sniffing (browsers misinterpreting file types)
|
|
12
|
+
* - Protocol downgrade attacks (HTTPS -> HTTP)
|
|
13
|
+
*
|
|
14
|
+
* HOW TO USE:
|
|
15
|
+
* 1. Copy this file to your Next.js project root
|
|
16
|
+
* 2. Import and spread into your next.config.js (see example at bottom)
|
|
17
|
+
*
|
|
18
|
+
* TEST YOUR HEADERS:
|
|
19
|
+
* After deploying, check your score at: https://securityheaders.com
|
|
20
|
+
*/
|
|
21
|
+
|
|
22
|
+
const securityHeaders = [
|
|
23
|
+
// ==========================================================================
|
|
24
|
+
// CLICKJACKING PROTECTION
|
|
25
|
+
// ==========================================================================
|
|
26
|
+
{
|
|
27
|
+
key: 'X-Frame-Options',
|
|
28
|
+
value: 'DENY'
|
|
29
|
+
// WHAT: Prevents your site from being embedded in iframes
|
|
30
|
+
// WHY: Attackers could overlay invisible buttons on your site (clickjacking)
|
|
31
|
+
// OPTIONS:
|
|
32
|
+
// 'DENY' - Never allow framing (most secure)
|
|
33
|
+
// 'SAMEORIGIN' - Only allow framing by your own site
|
|
34
|
+
// NOTE: Use 'SAMEORIGIN' if you need iframes for your own features
|
|
35
|
+
},
|
|
36
|
+
|
|
37
|
+
// ==========================================================================
|
|
38
|
+
// XSS PROTECTION
|
|
39
|
+
// ==========================================================================
|
|
40
|
+
{
|
|
41
|
+
key: 'X-XSS-Protection',
|
|
42
|
+
value: '1; mode=block'
|
|
43
|
+
// WHAT: Enables browser's built-in XSS filter
|
|
44
|
+
// WHY: Adds a layer of defense against reflected XSS attacks
|
|
45
|
+
// NOTE: Modern browsers use CSP instead, but this helps older browsers
|
|
46
|
+
},
|
|
47
|
+
{
|
|
48
|
+
key: 'X-Content-Type-Options',
|
|
49
|
+
value: 'nosniff'
|
|
50
|
+
// WHAT: Prevents browsers from MIME-sniffing (guessing file types)
|
|
51
|
+
// WHY: Attackers could trick browsers into executing malicious content
|
|
52
|
+
// by serving scripts with incorrect MIME types
|
|
53
|
+
},
|
|
54
|
+
|
|
55
|
+
// ==========================================================================
|
|
56
|
+
// HTTPS ENFORCEMENT
|
|
57
|
+
// ==========================================================================
|
|
58
|
+
{
|
|
59
|
+
key: 'Strict-Transport-Security',
|
|
60
|
+
value: 'max-age=31536000; includeSubDomains; preload'
|
|
61
|
+
// WHAT: Forces browsers to always use HTTPS for your site
|
|
62
|
+
// WHY: Prevents protocol downgrade attacks and cookie hijacking
|
|
63
|
+
// BREAKDOWN:
|
|
64
|
+
// max-age=31536000 - Remember for 1 year (in seconds)
|
|
65
|
+
// includeSubDomains - Apply to all subdomains too
|
|
66
|
+
// preload - Allow inclusion in browser's HSTS preload list
|
|
67
|
+
// WARNING: Only enable 'preload' if you're CERTAIN all subdomains support HTTPS
|
|
68
|
+
},
|
|
69
|
+
|
|
70
|
+
// ==========================================================================
|
|
71
|
+
// REFERRER POLICY
|
|
72
|
+
// ==========================================================================
|
|
73
|
+
{
|
|
74
|
+
key: 'Referrer-Policy',
|
|
75
|
+
value: 'strict-origin-when-cross-origin'
|
|
76
|
+
// WHAT: Controls how much referrer info is sent to other sites
|
|
77
|
+
// WHY: Prevents leaking sensitive URLs (with tokens, user IDs, etc.)
|
|
78
|
+
// THIS SETTING: Send origin only (not full URL) for cross-origin requests
|
|
79
|
+
// OPTIONS:
|
|
80
|
+
// 'no-referrer' - Never send referrer (most private, may break analytics)
|
|
81
|
+
// 'strict-origin' - Only send origin, never on HTTP downgrade
|
|
82
|
+
// 'strict-origin-when-cross-origin' - Full URL same-site, origin cross-site
|
|
83
|
+
},
|
|
84
|
+
|
|
85
|
+
// ==========================================================================
|
|
86
|
+
// PERMISSIONS POLICY (formerly Feature-Policy)
|
|
87
|
+
// ==========================================================================
|
|
88
|
+
{
|
|
89
|
+
key: 'Permissions-Policy',
|
|
90
|
+
value: 'camera=(), microphone=(), geolocation=(), interest-cohort=()'
|
|
91
|
+
// WHAT: Disables browser features you don't need
|
|
92
|
+
// WHY: Reduces attack surface; malicious scripts can't access these APIs
|
|
93
|
+
// THIS SETTING: Disables camera, mic, location, and FLoC tracking
|
|
94
|
+
// CUSTOMIZE: If you need camera/mic (e.g., for video chat), update this:
|
|
95
|
+
// camera=(self) - Allow camera only from your own origin
|
|
96
|
+
// microphone=(self "https://trusted-site.com") - Allow from specific origins
|
|
97
|
+
},
|
|
98
|
+
|
|
99
|
+
// ==========================================================================
|
|
100
|
+
// CONTENT SECURITY POLICY (CSP)
|
|
101
|
+
// ==========================================================================
|
|
102
|
+
// CSP is your most powerful defense against XSS attacks.
|
|
103
|
+
// It tells the browser exactly which resources are allowed to load.
|
|
104
|
+
//
|
|
105
|
+
// WARNING: CSP can break your site if misconfigured!
|
|
106
|
+
// Start with 'Content-Security-Policy-Report-Only' to test without blocking.
|
|
107
|
+
{
|
|
108
|
+
key: 'Content-Security-Policy',
|
|
109
|
+
value: [
|
|
110
|
+
// DEFAULT: Block everything not explicitly allowed
|
|
111
|
+
"default-src 'self'",
|
|
112
|
+
|
|
113
|
+
// SCRIPTS: Only from your domain + inline scripts (needed for Next.js)
|
|
114
|
+
// NOTE: 'unsafe-inline' is needed for Next.js. For maximum security,
|
|
115
|
+
// use nonces instead (requires custom server setup)
|
|
116
|
+
"script-src 'self' 'unsafe-inline' 'unsafe-eval'",
|
|
117
|
+
|
|
118
|
+
// STYLES: Your domain + inline styles (needed for most CSS-in-JS)
|
|
119
|
+
"style-src 'self' 'unsafe-inline'",
|
|
120
|
+
|
|
121
|
+
// IMAGES: Your domain + data URIs + common CDNs
|
|
122
|
+
// ADD your image CDN here if needed (e.g., images.unsplash.com)
|
|
123
|
+
"img-src 'self' data: blob: https:",
|
|
124
|
+
|
|
125
|
+
// FONTS: Your domain + Google Fonts
|
|
126
|
+
"font-src 'self' https://fonts.gstatic.com",
|
|
127
|
+
|
|
128
|
+
// CONNECTIONS (fetch, WebSocket, etc.): Your domain + your API
|
|
129
|
+
// ADD your API domains here
|
|
130
|
+
"connect-src 'self' https://api.openai.com https://api.anthropic.com",
|
|
131
|
+
|
|
132
|
+
// FRAMES: Block all iframes by default
|
|
133
|
+
// Change to 'self' if you need iframes from your own domain
|
|
134
|
+
"frame-src 'none'",
|
|
135
|
+
|
|
136
|
+
// PREVENT your site from being framed
|
|
137
|
+
"frame-ancestors 'none'",
|
|
138
|
+
|
|
139
|
+
// FORMS: Only submit to your own domain
|
|
140
|
+
"form-action 'self'",
|
|
141
|
+
|
|
142
|
+
// BASE URI: Prevent base tag injection
|
|
143
|
+
"base-uri 'self'",
|
|
144
|
+
|
|
145
|
+
// UPGRADE insecure requests to HTTPS
|
|
146
|
+
"upgrade-insecure-requests",
|
|
147
|
+
].join('; ')
|
|
148
|
+
//
|
|
149
|
+
// DEBUGGING CSP:
|
|
150
|
+
// 1. Open browser DevTools > Console
|
|
151
|
+
// 2. Look for "Refused to load..." errors
|
|
152
|
+
// 3. Add the blocked domain to the appropriate directive
|
|
153
|
+
//
|
|
154
|
+
// COMMON ADDITIONS:
|
|
155
|
+
// - Google Analytics: Add to script-src and connect-src
|
|
156
|
+
// script-src: https://www.googletagmanager.com
|
|
157
|
+
// connect-src: https://www.google-analytics.com
|
|
158
|
+
// - Stripe:
|
|
159
|
+
// script-src: https://js.stripe.com
|
|
160
|
+
// frame-src: https://js.stripe.com
|
|
161
|
+
},
|
|
162
|
+
];
|
|
163
|
+
|
|
164
|
+
/**
|
|
165
|
+
* Export the headers configuration for next.config.js
|
|
166
|
+
*
|
|
167
|
+
* USAGE IN next.config.js:
|
|
168
|
+
*
|
|
169
|
+
* const { securityHeadersConfig } = require('./nextjs-security-headers');
|
|
170
|
+
*
|
|
171
|
+
* module.exports = {
|
|
172
|
+
* ...securityHeadersConfig,
|
|
173
|
+
* // your other config options...
|
|
174
|
+
* };
|
|
175
|
+
*
|
|
176
|
+
* OR for ES modules (next.config.mjs):
|
|
177
|
+
*
|
|
178
|
+
* import { securityHeadersConfig } from './nextjs-security-headers.js';
|
|
179
|
+
*
|
|
180
|
+
* export default {
|
|
181
|
+
* ...securityHeadersConfig,
|
|
182
|
+
* // your other config options...
|
|
183
|
+
* };
|
|
184
|
+
*/
|
|
185
|
+
const securityHeadersConfig = {
|
|
186
|
+
async headers() {
|
|
187
|
+
return [
|
|
188
|
+
{
|
|
189
|
+
// Apply to all routes
|
|
190
|
+
source: '/:path*',
|
|
191
|
+
headers: securityHeaders,
|
|
192
|
+
},
|
|
193
|
+
];
|
|
194
|
+
},
|
|
195
|
+
};
|
|
196
|
+
|
|
197
|
+
// =============================================================================
|
|
198
|
+
// COMPLETE EXAMPLE: next.config.js
|
|
199
|
+
// =============================================================================
|
|
200
|
+
/*
|
|
201
|
+
// next.config.js (CommonJS)
|
|
202
|
+
const { securityHeadersConfig } = require('./nextjs-security-headers');
|
|
203
|
+
|
|
204
|
+
module.exports = {
|
|
205
|
+
...securityHeadersConfig,
|
|
206
|
+
reactStrictMode: true,
|
|
207
|
+
// ... your other config
|
|
208
|
+
};
|
|
209
|
+
|
|
210
|
+
// OR next.config.mjs (ES Modules)
|
|
211
|
+
import { securityHeadersConfig } from './nextjs-security-headers.js';
|
|
212
|
+
|
|
213
|
+
export default {
|
|
214
|
+
...securityHeadersConfig,
|
|
215
|
+
reactStrictMode: true,
|
|
216
|
+
// ... your other config
|
|
217
|
+
};
|
|
218
|
+
*/
|
|
219
|
+
|
|
220
|
+
module.exports = { securityHeaders, securityHeadersConfig };
|
package/package.json
ADDED
|
@@ -0,0 +1,54 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "ship-safe",
|
|
3
|
+
"version": "1.0.0",
|
|
4
|
+
"description": "Security toolkit for vibe coders and indie hackers. Secure your MVP in 5 minutes.",
|
|
5
|
+
"main": "cli/index.js",
|
|
6
|
+
"bin": {
|
|
7
|
+
"ship-safe": "cli/bin/ship-safe.js"
|
|
8
|
+
},
|
|
9
|
+
"type": "module",
|
|
10
|
+
"scripts": {
|
|
11
|
+
"test": "node --test",
|
|
12
|
+
"lint": "eslint cli/",
|
|
13
|
+
"ship-safe": "node cli/bin/ship-safe.js"
|
|
14
|
+
},
|
|
15
|
+
"keywords": [
|
|
16
|
+
"security",
|
|
17
|
+
"secrets",
|
|
18
|
+
"scanner",
|
|
19
|
+
"devops",
|
|
20
|
+
"devsecops",
|
|
21
|
+
"api-keys",
|
|
22
|
+
"gitignore",
|
|
23
|
+
"indie-hacker",
|
|
24
|
+
"vibe-coding",
|
|
25
|
+
"mvp",
|
|
26
|
+
"cli"
|
|
27
|
+
],
|
|
28
|
+
"author": "",
|
|
29
|
+
"license": "MIT",
|
|
30
|
+
"repository": {
|
|
31
|
+
"type": "git",
|
|
32
|
+
"url": "git+https://github.com/asamassekou10/ship-safe.git"
|
|
33
|
+
},
|
|
34
|
+
"bugs": {
|
|
35
|
+
"url": "https://github.com/asamassekou10/ship-safe/issues"
|
|
36
|
+
},
|
|
37
|
+
"homepage": "https://github.com/asamassekou10/ship-safe#readme",
|
|
38
|
+
"engines": {
|
|
39
|
+
"node": ">=18.0.0"
|
|
40
|
+
},
|
|
41
|
+
"files": [
|
|
42
|
+
"cli/",
|
|
43
|
+
"checklists/",
|
|
44
|
+
"configs/",
|
|
45
|
+
"snippets/",
|
|
46
|
+
"ai-defense/"
|
|
47
|
+
],
|
|
48
|
+
"dependencies": {
|
|
49
|
+
"chalk": "^5.3.0",
|
|
50
|
+
"commander": "^12.1.0",
|
|
51
|
+
"glob": "^10.3.10",
|
|
52
|
+
"ora": "^8.0.1"
|
|
53
|
+
}
|
|
54
|
+
}
|
|
@@ -0,0 +1,58 @@
|
|
|
1
|
+
# Security Snippets
|
|
2
|
+
|
|
3
|
+
**Copy-paste code blocks for common security patterns.**
|
|
4
|
+
|
|
5
|
+
This folder contains drop-in code snippets for securing your application. Each snippet is heavily commented to explain *why* it works.
|
|
6
|
+
|
|
7
|
+
---
|
|
8
|
+
|
|
9
|
+
## Coming Soon
|
|
10
|
+
|
|
11
|
+
- **Rate Limiting**
|
|
12
|
+
- Express.js middleware
|
|
13
|
+
- Next.js API route wrapper
|
|
14
|
+
- Upstash Redis implementation
|
|
15
|
+
|
|
16
|
+
- **Authentication**
|
|
17
|
+
- Secure session configuration
|
|
18
|
+
- JWT validation middleware
|
|
19
|
+
- OAuth state parameter handling
|
|
20
|
+
|
|
21
|
+
- **Input Validation**
|
|
22
|
+
- Zod schemas for common patterns
|
|
23
|
+
- SQL injection prevention
|
|
24
|
+
- XSS sanitization
|
|
25
|
+
|
|
26
|
+
- **API Security**
|
|
27
|
+
- CORS configuration
|
|
28
|
+
- API key validation
|
|
29
|
+
- Webhook signature verification (Stripe, GitHub)
|
|
30
|
+
|
|
31
|
+
---
|
|
32
|
+
|
|
33
|
+
## Contributing
|
|
34
|
+
|
|
35
|
+
Have a security snippet that saved your app? Add it here!
|
|
36
|
+
|
|
37
|
+
1. Create a new file: `snippets/your-snippet-name.{js,ts,py}`
|
|
38
|
+
2. Add extensive comments explaining:
|
|
39
|
+
- What attack this prevents
|
|
40
|
+
- How to integrate it
|
|
41
|
+
- Common gotchas
|
|
42
|
+
3. Open a PR
|
|
43
|
+
|
|
44
|
+
---
|
|
45
|
+
|
|
46
|
+
## Usage Pattern
|
|
47
|
+
|
|
48
|
+
Each snippet follows this format:
|
|
49
|
+
|
|
50
|
+
```
|
|
51
|
+
// =============================================================================
|
|
52
|
+
// WHAT: Brief description
|
|
53
|
+
// WHY: What attack/vulnerability this prevents
|
|
54
|
+
// HOW: Integration instructions
|
|
55
|
+
// =============================================================================
|
|
56
|
+
|
|
57
|
+
[actual code with inline comments]
|
|
58
|
+
```
|