ship-safe 3.1.0 → 3.2.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +192 -22
- package/cli/bin/ship-safe.js +37 -0
- package/cli/commands/agent.js +606 -0
- package/cli/commands/deps.js +447 -0
- package/cli/commands/fix.js +3 -3
- package/cli/commands/init.js +86 -3
- package/cli/commands/mcp.js +2 -2
- package/cli/commands/remediate.js +4 -4
- package/cli/commands/rotate.js +6 -6
- package/cli/commands/scan.js +64 -23
- package/cli/commands/score.js +446 -0
- package/cli/index.js +4 -1
- package/cli/utils/entropy.js +6 -0
- package/cli/utils/output.js +42 -2
- package/cli/utils/patterns.js +393 -1
- package/package.json +2 -2
package/cli/utils/output.js
CHANGED
|
@@ -101,6 +101,21 @@ export function finding(file, line, patternName, severity, matched, description,
|
|
|
101
101
|
console.log(` ${chalk.gray('Why:')} ${description}`);
|
|
102
102
|
}
|
|
103
103
|
|
|
104
|
+
/**
|
|
105
|
+
* Print a vulnerability finding (code issue — show matched code, not masked)
|
|
106
|
+
*/
|
|
107
|
+
export function vulnerabilityFinding(file, line, patternName, severity, matched, description) {
|
|
108
|
+
const color = severityColors[severity] || chalk.white;
|
|
109
|
+
const icon = severityIcons[severity] || '';
|
|
110
|
+
const snippet = matched.length > 80 ? matched.slice(0, 80) + '…' : matched;
|
|
111
|
+
|
|
112
|
+
console.log();
|
|
113
|
+
console.log(chalk.white.bold(`${file}:${line}`));
|
|
114
|
+
console.log(` ${icon}${color(`[${severity.toUpperCase()}]`)} ${chalk.white(patternName)}`);
|
|
115
|
+
console.log(` ${chalk.gray('Code:')} ${chalk.cyan(snippet)}`);
|
|
116
|
+
console.log(` ${chalk.gray('Why:')} ${description}`);
|
|
117
|
+
}
|
|
118
|
+
|
|
104
119
|
/**
|
|
105
120
|
* Mask the middle of a secret for safe display
|
|
106
121
|
*/
|
|
@@ -113,15 +128,27 @@ export function maskSecret(secret) {
|
|
|
113
128
|
|
|
114
129
|
/**
|
|
115
130
|
* Print a summary box
|
|
131
|
+
*
|
|
132
|
+
* stats can include:
|
|
133
|
+
* total, critical, high, medium, filesScanned
|
|
134
|
+
* secretsTotal (optional), vulnsTotal (optional)
|
|
116
135
|
*/
|
|
117
136
|
export function summary(stats) {
|
|
118
137
|
console.log();
|
|
119
138
|
console.log(chalk.cyan('='.repeat(60)));
|
|
120
139
|
|
|
121
140
|
if (stats.total === 0) {
|
|
122
|
-
console.log(chalk.green.bold(' \u2714 No
|
|
141
|
+
console.log(chalk.green.bold(' \u2714 No issues detected!'));
|
|
123
142
|
} else {
|
|
124
|
-
|
|
143
|
+
const secretsTotal = stats.secretsTotal ?? stats.total;
|
|
144
|
+
const vulnsTotal = stats.vulnsTotal ?? 0;
|
|
145
|
+
|
|
146
|
+
if (secretsTotal > 0) {
|
|
147
|
+
console.log(chalk.red.bold(` \u26a0 Found ${secretsTotal} secret(s)`));
|
|
148
|
+
}
|
|
149
|
+
if (vulnsTotal > 0) {
|
|
150
|
+
console.log(chalk.yellow.bold(` \u26a0 Found ${vulnsTotal} code vulnerability/vulnerabilities`));
|
|
151
|
+
}
|
|
125
152
|
|
|
126
153
|
if (stats.critical > 0) {
|
|
127
154
|
console.log(chalk.red(` \u2022 Critical: ${stats.critical}`));
|
|
@@ -138,6 +165,19 @@ export function summary(stats) {
|
|
|
138
165
|
console.log(chalk.cyan('='.repeat(60)));
|
|
139
166
|
}
|
|
140
167
|
|
|
168
|
+
/**
|
|
169
|
+
* Print recommended actions after finding code vulnerabilities
|
|
170
|
+
*/
|
|
171
|
+
export function vulnRecommendations() {
|
|
172
|
+
console.log();
|
|
173
|
+
console.log(chalk.yellow.bold('Code Vulnerability Actions:'));
|
|
174
|
+
console.log();
|
|
175
|
+
console.log(chalk.white('1.') + ' Fix the flagged code patterns (see "Why" descriptions above)');
|
|
176
|
+
console.log(chalk.white('2.') + ' Use # ship-safe-ignore on lines that are safe (e.g. internal tools, controlled input)');
|
|
177
|
+
console.log(chalk.white('3.') + ' Run npx ship-safe checklist for a full launch-day security review');
|
|
178
|
+
console.log();
|
|
179
|
+
}
|
|
180
|
+
|
|
141
181
|
/**
|
|
142
182
|
* Print recommended actions after finding secrets
|
|
143
183
|
*/
|
package/cli/utils/patterns.js
CHANGED
|
@@ -392,6 +392,52 @@ export const SECRET_PATTERNS = [
|
|
|
392
392
|
severity: 'medium',
|
|
393
393
|
description: 'Supabase anon keys. Safe for frontend but verify RLS is enabled.'
|
|
394
394
|
},
|
|
395
|
+
{
|
|
396
|
+
name: 'Stytch Secret Key',
|
|
397
|
+
pattern: /secret-(?:live|test)-[a-zA-Z0-9]{30,}/g,
|
|
398
|
+
severity: 'critical',
|
|
399
|
+
description: 'Stytch secret keys grant full access to your authentication system.'
|
|
400
|
+
},
|
|
401
|
+
{
|
|
402
|
+
name: 'Okta API Token',
|
|
403
|
+
pattern: /(?:okta|OKTA)[_-]?(?:api[_-]?)?token["']?\s*[:=]\s*["']?(00[a-zA-Z0-9_-]{38})["']?/gi,
|
|
404
|
+
severity: 'high',
|
|
405
|
+
description: 'Okta API tokens can manage your identity provider and user directory.'
|
|
406
|
+
},
|
|
407
|
+
|
|
408
|
+
// =========================================================================
|
|
409
|
+
// CRITICAL: Additional Cloud/Infra
|
|
410
|
+
// =========================================================================
|
|
411
|
+
{
|
|
412
|
+
name: 'Azure Storage Connection String',
|
|
413
|
+
pattern: /DefaultEndpointsProtocol=https;AccountName=[^;]{1,50};AccountKey=[a-zA-Z0-9+/=]{44,}/g,
|
|
414
|
+
severity: 'critical',
|
|
415
|
+
description: 'Azure Storage connection strings contain account keys with full storage access.'
|
|
416
|
+
},
|
|
417
|
+
{
|
|
418
|
+
name: 'AWS Session Token',
|
|
419
|
+
pattern: /(?:aws_session_token|aws_security_token)[\s]*[=:][\s]*["']?([A-Za-z0-9/+=]{100,})["']?/gi,
|
|
420
|
+
severity: 'critical',
|
|
421
|
+
description: 'AWS session tokens are temporary credentials that still grant account access.'
|
|
422
|
+
},
|
|
423
|
+
{
|
|
424
|
+
name: 'PlanetScale Service Token',
|
|
425
|
+
pattern: /pscale_tkn_[a-zA-Z0-9_-]{32,}/g,
|
|
426
|
+
severity: 'critical',
|
|
427
|
+
description: 'PlanetScale service tokens grant programmatic database branch access.'
|
|
428
|
+
},
|
|
429
|
+
{
|
|
430
|
+
name: 'Shopify Admin API Access Token',
|
|
431
|
+
pattern: /shpat_[a-fA-F0-9]{32}/g,
|
|
432
|
+
severity: 'critical',
|
|
433
|
+
description: 'Shopify admin tokens grant full store management access including orders and customers.'
|
|
434
|
+
},
|
|
435
|
+
{
|
|
436
|
+
name: 'Shopify Custom App Access Token',
|
|
437
|
+
pattern: /shpca_[a-fA-F0-9]{32}/g,
|
|
438
|
+
severity: 'critical',
|
|
439
|
+
description: 'Shopify custom app tokens provide scoped store admin access.'
|
|
440
|
+
},
|
|
395
441
|
|
|
396
442
|
// =========================================================================
|
|
397
443
|
// HIGH: Productivity & SaaS
|
|
@@ -421,6 +467,66 @@ export const SECRET_PATTERNS = [
|
|
|
421
467
|
description: 'Figma PATs can access your design files and projects.'
|
|
422
468
|
},
|
|
423
469
|
|
|
470
|
+
// =========================================================================
|
|
471
|
+
// HIGH: AI/ML Providers (2025-2026 additions)
|
|
472
|
+
// =========================================================================
|
|
473
|
+
{
|
|
474
|
+
name: 'xAI (Grok) API Key',
|
|
475
|
+
pattern: /xai-[A-Za-z0-9]{52,}/g,
|
|
476
|
+
severity: 'high',
|
|
477
|
+
description: 'xAI API keys grant access to Grok models and incur usage charges on your account.'
|
|
478
|
+
},
|
|
479
|
+
{
|
|
480
|
+
name: 'Tavily API Key',
|
|
481
|
+
pattern: /tvly-[a-zA-Z0-9]{32,}/g,
|
|
482
|
+
severity: 'high',
|
|
483
|
+
description: 'Tavily API keys grant access to their AI-powered search service.'
|
|
484
|
+
},
|
|
485
|
+
{
|
|
486
|
+
name: 'Cerebras API Key',
|
|
487
|
+
pattern: /csk-[a-zA-Z0-9]{48,}/g,
|
|
488
|
+
severity: 'high',
|
|
489
|
+
description: 'Cerebras API keys provide access to fast AI inference.'
|
|
490
|
+
},
|
|
491
|
+
{
|
|
492
|
+
name: 'Pinecone API Key',
|
|
493
|
+
pattern: /pcsk_[a-zA-Z0-9]{47}_[a-zA-Z0-9]{47}/g,
|
|
494
|
+
severity: 'high',
|
|
495
|
+
description: 'Pinecone API keys grant access to your vector database indexes.'
|
|
496
|
+
},
|
|
497
|
+
{
|
|
498
|
+
name: 'ElevenLabs API Key',
|
|
499
|
+
pattern: /(?:elevenlabs|ELEVENLABS)[_-]?(?:api[_-]?)?key["']?\s*[:=]\s*["']?([a-fA-F0-9]{32})["']?/gi,
|
|
500
|
+
severity: 'high',
|
|
501
|
+
description: 'ElevenLabs API keys grant access to their voice AI cloning and synthesis service.'
|
|
502
|
+
},
|
|
503
|
+
{
|
|
504
|
+
name: 'DeepSeek API Key',
|
|
505
|
+
pattern: /(?:deepseek|DEEPSEEK)[_-]?(?:api[_-]?)?key["']?\s*[:=]\s*["']?(sk-[a-zA-Z0-9]{32,})["']?/gi,
|
|
506
|
+
severity: 'high',
|
|
507
|
+
description: 'DeepSeek API keys grant access to their language models.'
|
|
508
|
+
},
|
|
509
|
+
{
|
|
510
|
+
name: 'Voyage AI API Key',
|
|
511
|
+
pattern: /(?:voyage|VOYAGE)[_-]?(?:ai[_-]?)?(?:api[_-]?)?key["']?\s*[:=]\s*["']?([a-zA-Z0-9]{32,})["']?/gi,
|
|
512
|
+
severity: 'high',
|
|
513
|
+
requiresEntropyCheck: true,
|
|
514
|
+
description: 'Voyage AI API keys provide access to their embedding and reranking models.'
|
|
515
|
+
},
|
|
516
|
+
{
|
|
517
|
+
name: 'Fireworks AI API Key',
|
|
518
|
+
pattern: /(?:fireworks|FIREWORKS)[_-]?(?:api[_-]?)?key["']?\s*[:=]\s*["']?([a-zA-Z0-9]{32,})["']?/gi,
|
|
519
|
+
severity: 'high',
|
|
520
|
+
requiresEntropyCheck: true,
|
|
521
|
+
description: 'Fireworks AI API keys grant access to fast open-source model inference.'
|
|
522
|
+
},
|
|
523
|
+
{
|
|
524
|
+
name: 'Anyscale API Key',
|
|
525
|
+
pattern: /esecret_[a-zA-Z0-9]{32,}/g,
|
|
526
|
+
severity: 'high',
|
|
527
|
+
description: 'Anyscale API keys grant access to their managed Ray and LLM endpoints.'
|
|
528
|
+
},
|
|
529
|
+
|
|
424
530
|
// =========================================================================
|
|
425
531
|
// HIGH: Payments (Additional)
|
|
426
532
|
// =========================================================================
|
|
@@ -454,6 +560,99 @@ export const SECRET_PATTERNS = [
|
|
|
454
560
|
severity: 'high',
|
|
455
561
|
description: 'Paddle API keys can manage your subscriptions and payments.'
|
|
456
562
|
},
|
|
563
|
+
{
|
|
564
|
+
name: 'Stripe Restricted Key',
|
|
565
|
+
pattern: /rk_(?:live|test)_[a-zA-Z0-9]{24,}/g,
|
|
566
|
+
severity: 'high',
|
|
567
|
+
description: 'Stripe restricted keys have scoped permissions but still grant API access.'
|
|
568
|
+
},
|
|
569
|
+
{
|
|
570
|
+
name: 'Square Access Token',
|
|
571
|
+
pattern: /EAAAl[0-9a-zA-Z_-]{50,}/g,
|
|
572
|
+
severity: 'high',
|
|
573
|
+
description: 'Square access tokens can process payments and manage store data.'
|
|
574
|
+
},
|
|
575
|
+
{
|
|
576
|
+
name: 'Square OAuth Token',
|
|
577
|
+
pattern: /sq0[a-z]tp-[a-zA-Z0-9_-]{22}/g,
|
|
578
|
+
severity: 'high',
|
|
579
|
+
description: 'Square OAuth tokens authorize Square API access for a merchant account.'
|
|
580
|
+
},
|
|
581
|
+
{
|
|
582
|
+
name: 'Shopify Shared Secret',
|
|
583
|
+
pattern: /shpss_[a-fA-F0-9]{32}/g,
|
|
584
|
+
severity: 'high',
|
|
585
|
+
description: 'Shopify shared secrets validate webhook payload signatures.'
|
|
586
|
+
},
|
|
587
|
+
{
|
|
588
|
+
name: 'Braintree Access Token',
|
|
589
|
+
pattern: /access_token\$(?:production|sandbox)\$[a-z0-9]{16}\$[a-f0-9]{32}/g,
|
|
590
|
+
severity: 'critical',
|
|
591
|
+
description: 'Braintree access tokens grant full payment processing access.'
|
|
592
|
+
},
|
|
593
|
+
|
|
594
|
+
// =========================================================================
|
|
595
|
+
// HIGH: Realtime & Messaging
|
|
596
|
+
// =========================================================================
|
|
597
|
+
{
|
|
598
|
+
name: 'Pusher App Secret',
|
|
599
|
+
pattern: /(?:pusher|PUSHER)[_-]?(?:app[_-]?)?secret["']?\s*[:=]\s*["']?([a-f0-9]{32})["']?/gi,
|
|
600
|
+
severity: 'high',
|
|
601
|
+
description: 'Pusher app secrets authenticate private and presence channel subscriptions.'
|
|
602
|
+
},
|
|
603
|
+
{
|
|
604
|
+
name: 'Ably API Key',
|
|
605
|
+
pattern: /(?:ably|ABLY)[_-]?(?:api[_-]?)?key["']?\s*[:=]\s*["']?([a-zA-Z0-9_-]{8}\.[a-zA-Z0-9_-]{6}:[a-zA-Z0-9+/=_-]{43,})["']?/gi,
|
|
606
|
+
severity: 'high',
|
|
607
|
+
description: 'Ably API keys grant full publish and subscribe access to your realtime channels.'
|
|
608
|
+
},
|
|
609
|
+
{
|
|
610
|
+
name: 'Mapbox Access Token',
|
|
611
|
+
pattern: /pk\.eyJ1[a-zA-Z0-9._-]{40,}/g,
|
|
612
|
+
severity: 'medium',
|
|
613
|
+
description: 'Mapbox tokens can incur charges if abused. Restrict token scope and allowed URLs.'
|
|
614
|
+
},
|
|
615
|
+
|
|
616
|
+
// =========================================================================
|
|
617
|
+
// HIGH: DevOps & CI/CD
|
|
618
|
+
// =========================================================================
|
|
619
|
+
{
|
|
620
|
+
name: 'CircleCI Personal API Token',
|
|
621
|
+
pattern: /CCIPAT_[a-zA-Z0-9]{40,}/g,
|
|
622
|
+
severity: 'high',
|
|
623
|
+
description: 'CircleCI API tokens can trigger builds, read logs, and access pipeline data.'
|
|
624
|
+
},
|
|
625
|
+
{
|
|
626
|
+
name: 'Sentry Auth Token',
|
|
627
|
+
pattern: /sntrys_[a-zA-Z0-9_]{64,}/g,
|
|
628
|
+
severity: 'high',
|
|
629
|
+
description: 'Sentry auth tokens provide full API access to your error data and project settings.'
|
|
630
|
+
},
|
|
631
|
+
{
|
|
632
|
+
name: 'Terraform Cloud Token',
|
|
633
|
+
pattern: /(?:terraform|TFC)[_-]?(?:api[_-]?)?token["']?\s*[:=]\s*["']?([a-zA-Z0-9]{14}\.atlasv1\.[a-zA-Z0-9_-]{67,})["']?/gi,
|
|
634
|
+
severity: 'high',
|
|
635
|
+
description: 'Terraform Cloud tokens can read and apply infrastructure state.'
|
|
636
|
+
},
|
|
637
|
+
{
|
|
638
|
+
name: 'Cloudinary API Secret',
|
|
639
|
+
pattern: /(?:cloudinary|CLOUDINARY)[_-]?(?:api[_-]?)?secret["']?\s*[:=]\s*["']?([a-zA-Z0-9_-]{27,})["']?/gi,
|
|
640
|
+
severity: 'high',
|
|
641
|
+
requiresEntropyCheck: true,
|
|
642
|
+
description: 'Cloudinary API secrets grant access to your media library and transformations.'
|
|
643
|
+
},
|
|
644
|
+
{
|
|
645
|
+
name: 'Algolia Admin API Key',
|
|
646
|
+
pattern: /(?:algolia|ALGOLIA)[_-]?(?:admin[_-]?)?(?:api[_-]?)?key["']?\s*[:=]\s*["']?([a-f0-9]{32})["']?/gi,
|
|
647
|
+
severity: 'high',
|
|
648
|
+
description: 'Algolia admin keys can modify indices and change search configuration.'
|
|
649
|
+
},
|
|
650
|
+
{
|
|
651
|
+
name: 'LaunchDarkly SDK Key',
|
|
652
|
+
pattern: /sdk-[a-f0-9]{8}-[a-f0-9]{4}-[a-f0-9]{4}-[a-f0-9]{4}-[a-f0-9]{12}/g,
|
|
653
|
+
severity: 'high',
|
|
654
|
+
description: 'LaunchDarkly SDK keys can read all feature flags and user data.'
|
|
655
|
+
},
|
|
457
656
|
|
|
458
657
|
// =========================================================================
|
|
459
658
|
// HIGH: Analytics & Monitoring
|
|
@@ -566,7 +765,24 @@ export const SKIP_DIRS = new Set([
|
|
|
566
765
|
'jspm_packages',
|
|
567
766
|
'.vercel',
|
|
568
767
|
'.netlify',
|
|
569
|
-
'.serverless'
|
|
768
|
+
'.serverless',
|
|
769
|
+
// Additional build/tooling output
|
|
770
|
+
'.yarn',
|
|
771
|
+
'storybook-static',
|
|
772
|
+
'playwright-report',
|
|
773
|
+
'.playwright',
|
|
774
|
+
'.gradle',
|
|
775
|
+
'target', // Maven/Gradle build output
|
|
776
|
+
'.pytest_cache',
|
|
777
|
+
'.mypy_cache',
|
|
778
|
+
'.ruff_cache',
|
|
779
|
+
'.tox',
|
|
780
|
+
'site-packages',
|
|
781
|
+
'.pnpm',
|
|
782
|
+
'jspm_packages',
|
|
783
|
+
'.expo',
|
|
784
|
+
'.docusaurus',
|
|
785
|
+
'.storybook',
|
|
570
786
|
]);
|
|
571
787
|
|
|
572
788
|
export const SKIP_EXTENSIONS = new Set([
|
|
@@ -593,6 +809,182 @@ export const SKIP_EXTENSIONS = new Set([
|
|
|
593
809
|
// Maximum file size to scan (1MB)
|
|
594
810
|
export const MAX_FILE_SIZE = 1_000_000;
|
|
595
811
|
|
|
812
|
+
// =============================================================================
|
|
813
|
+
// SECURITY VULNERABILITY PATTERNS
|
|
814
|
+
// =============================================================================
|
|
815
|
+
//
|
|
816
|
+
// These patterns detect insecure code patterns (OWASP Top 10, misconfigs, etc.)
|
|
817
|
+
// They are distinct from SECRET_PATTERNS:
|
|
818
|
+
// - Secrets → move to env vars, rotate if exposed
|
|
819
|
+
// - Vulns → fix the code pattern, can't just rotate
|
|
820
|
+
//
|
|
821
|
+
// Each pattern includes category: 'vulnerability' to separate output sections.
|
|
822
|
+
|
|
823
|
+
export const SECURITY_PATTERNS = [
|
|
824
|
+
|
|
825
|
+
// =========================================================================
|
|
826
|
+
// XSS — Cross-Site Scripting
|
|
827
|
+
// =========================================================================
|
|
828
|
+
{
|
|
829
|
+
name: 'XSS: dangerouslySetInnerHTML',
|
|
830
|
+
pattern: /dangerouslySetInnerHTML\s*=\s*\{\s*\{/g,
|
|
831
|
+
severity: 'high',
|
|
832
|
+
category: 'vulnerability',
|
|
833
|
+
description: 'dangerouslySetInnerHTML can introduce XSS if the value contains user input. Sanitize with DOMPurify or restructure to avoid it.'
|
|
834
|
+
},
|
|
835
|
+
{
|
|
836
|
+
name: 'XSS: innerHTML Assignment',
|
|
837
|
+
pattern: /\.innerHTML\s*=/g,
|
|
838
|
+
severity: 'medium',
|
|
839
|
+
category: 'vulnerability',
|
|
840
|
+
description: 'innerHTML set to user-controlled data leads to XSS. Use textContent for plain text or DOMPurify to sanitize HTML.'
|
|
841
|
+
},
|
|
842
|
+
{
|
|
843
|
+
name: 'XSS: document.write',
|
|
844
|
+
pattern: /\bdocument\.write\s*\(/g,
|
|
845
|
+
severity: 'medium',
|
|
846
|
+
category: 'vulnerability',
|
|
847
|
+
description: 'document.write() is deprecated and can introduce XSS. Use DOM manipulation (createElement, appendChild) instead.'
|
|
848
|
+
},
|
|
849
|
+
|
|
850
|
+
// =========================================================================
|
|
851
|
+
// Code Injection
|
|
852
|
+
// =========================================================================
|
|
853
|
+
{
|
|
854
|
+
name: 'Code Injection: eval()',
|
|
855
|
+
pattern: /\beval\s*\(/g,
|
|
856
|
+
severity: 'high',
|
|
857
|
+
category: 'vulnerability',
|
|
858
|
+
description: 'eval() executes arbitrary JavaScript and is a serious attack vector. Replace with JSON.parse(), Function calls, or safer alternatives.'
|
|
859
|
+
},
|
|
860
|
+
{
|
|
861
|
+
name: 'Code Injection: new Function()',
|
|
862
|
+
pattern: /\bnew\s+Function\s*\(/g,
|
|
863
|
+
severity: 'high',
|
|
864
|
+
category: 'vulnerability',
|
|
865
|
+
description: 'new Function() is functionally equivalent to eval() and can execute arbitrary code. Avoid dynamic code generation.'
|
|
866
|
+
},
|
|
867
|
+
|
|
868
|
+
// =========================================================================
|
|
869
|
+
// SQL Injection
|
|
870
|
+
// =========================================================================
|
|
871
|
+
{
|
|
872
|
+
name: 'SQL Injection: Template Literal Query',
|
|
873
|
+
pattern: /`(?:SELECT|INSERT|UPDATE|DELETE|DROP\s+TABLE|ALTER\s+TABLE)[^`]*\$\{/gi,
|
|
874
|
+
severity: 'critical',
|
|
875
|
+
category: 'vulnerability',
|
|
876
|
+
description: 'SQL queries with interpolated template variables are vulnerable to injection. Use parameterized queries or a query builder.'
|
|
877
|
+
},
|
|
878
|
+
{
|
|
879
|
+
name: 'SQL Injection: String Concatenation Query',
|
|
880
|
+
pattern: /["'](?:SELECT|INSERT|UPDATE|DELETE)\s+[^"']{4,}["']\s*\+/gi,
|
|
881
|
+
severity: 'high',
|
|
882
|
+
category: 'vulnerability',
|
|
883
|
+
description: 'Building SQL with string concatenation is vulnerable to SQL injection. Use parameterized queries (?, $1) or an ORM.'
|
|
884
|
+
},
|
|
885
|
+
|
|
886
|
+
// =========================================================================
|
|
887
|
+
// Command Injection
|
|
888
|
+
// =========================================================================
|
|
889
|
+
{
|
|
890
|
+
name: 'Command Injection: exec with Template Literal',
|
|
891
|
+
pattern: /\bexec(?:Sync)?\s*\(\s*`[^`]*\$\{/g,
|
|
892
|
+
severity: 'critical',
|
|
893
|
+
category: 'vulnerability',
|
|
894
|
+
description: 'Running shell commands with interpolated values can lead to command injection. Validate all inputs or use execFile() with argument arrays.'
|
|
895
|
+
},
|
|
896
|
+
{
|
|
897
|
+
name: 'Command Injection: shell: true',
|
|
898
|
+
pattern: /\bspawn(?:Sync)?\s*\([^)]*\bshell\s*:\s*true/g,
|
|
899
|
+
severity: 'high',
|
|
900
|
+
category: 'vulnerability',
|
|
901
|
+
description: 'shell: true in spawn/spawnSync enables shell expansion and can lead to command injection. Remove shell: true and pass arguments as an array.'
|
|
902
|
+
},
|
|
903
|
+
|
|
904
|
+
// =========================================================================
|
|
905
|
+
// Weak Cryptography
|
|
906
|
+
// =========================================================================
|
|
907
|
+
{
|
|
908
|
+
name: 'Weak Crypto: MD5',
|
|
909
|
+
pattern: /createHash\s*\(\s*['"]md5['"]\s*\)/gi,
|
|
910
|
+
severity: 'medium',
|
|
911
|
+
category: 'vulnerability',
|
|
912
|
+
description: 'MD5 is cryptographically broken and must not be used for security purposes. Use SHA-256 (createHash("sha256")) or SHA-3.'
|
|
913
|
+
},
|
|
914
|
+
{
|
|
915
|
+
name: 'Weak Crypto: SHA-1',
|
|
916
|
+
pattern: /createHash\s*\(\s*['"]sha1['"]\s*\)/gi,
|
|
917
|
+
severity: 'medium',
|
|
918
|
+
category: 'vulnerability',
|
|
919
|
+
description: 'SHA-1 is cryptographically weak and collision-prone. Use SHA-256 (createHash("sha256")) or SHA-3 instead.'
|
|
920
|
+
},
|
|
921
|
+
|
|
922
|
+
// =========================================================================
|
|
923
|
+
// TLS / SSL Bypass
|
|
924
|
+
// =========================================================================
|
|
925
|
+
{
|
|
926
|
+
name: 'TLS Bypass: NODE_TLS_REJECT_UNAUTHORIZED=0',
|
|
927
|
+
pattern: /NODE_TLS_REJECT_UNAUTHORIZED\s*[=:]\s*['"]?0['"]?/g,
|
|
928
|
+
severity: 'critical',
|
|
929
|
+
category: 'vulnerability',
|
|
930
|
+
description: 'Setting NODE_TLS_REJECT_UNAUTHORIZED=0 disables TLS certificate validation and exposes your app to MITM attacks. Never use in production.'
|
|
931
|
+
},
|
|
932
|
+
{
|
|
933
|
+
name: 'TLS Bypass: rejectUnauthorized false',
|
|
934
|
+
pattern: /\brejectUnauthorized\s*:\s*false\b/g,
|
|
935
|
+
severity: 'high',
|
|
936
|
+
category: 'vulnerability',
|
|
937
|
+
description: 'rejectUnauthorized: false disables TLS certificate checking and enables man-in-the-middle attacks. Remove it or use a proper CA bundle.'
|
|
938
|
+
},
|
|
939
|
+
{
|
|
940
|
+
name: 'TLS Bypass: verify=False (Python)',
|
|
941
|
+
pattern: /\brequests\.\w+\s*\([^)]*\bverify\s*=\s*False\b/g,
|
|
942
|
+
severity: 'high',
|
|
943
|
+
category: 'vulnerability',
|
|
944
|
+
description: 'verify=False in Python requests disables SSL certificate verification. Remove this or pass verify="/path/to/ca-bundle.crt".'
|
|
945
|
+
},
|
|
946
|
+
|
|
947
|
+
// =========================================================================
|
|
948
|
+
// Unsafe Deserialization
|
|
949
|
+
// =========================================================================
|
|
950
|
+
{
|
|
951
|
+
name: 'Unsafe Deserialization: pickle.loads',
|
|
952
|
+
pattern: /\bpickle\.loads?\s*\(/g,
|
|
953
|
+
severity: 'high',
|
|
954
|
+
category: 'vulnerability',
|
|
955
|
+
description: 'pickle.loads() on untrusted data can execute arbitrary Python code (RCE). Use JSON or another safe format for data from untrusted sources.'
|
|
956
|
+
},
|
|
957
|
+
{
|
|
958
|
+
name: 'Unsafe Deserialization: yaml.load',
|
|
959
|
+
pattern: /\byaml\.load\s*\(/g,
|
|
960
|
+
severity: 'medium',
|
|
961
|
+
category: 'vulnerability',
|
|
962
|
+
description: 'yaml.load() can execute arbitrary code with certain YAML tags. Use yaml.safe_load() for untrusted input.'
|
|
963
|
+
},
|
|
964
|
+
|
|
965
|
+
// =========================================================================
|
|
966
|
+
// Security Misconfigurations
|
|
967
|
+
// =========================================================================
|
|
968
|
+
{
|
|
969
|
+
name: 'Security Config: CORS Wildcard',
|
|
970
|
+
pattern: /\borigin\s*:\s*['"]?\*['"]?/g,
|
|
971
|
+
severity: 'medium',
|
|
972
|
+
category: 'vulnerability',
|
|
973
|
+
description: 'CORS wildcard (*) allows any origin to make credentialed requests to your API. Use a specific allowlist of trusted origins.'
|
|
974
|
+
},
|
|
975
|
+
|
|
976
|
+
// =========================================================================
|
|
977
|
+
// Deprecated / Insecure Node.js APIs
|
|
978
|
+
// =========================================================================
|
|
979
|
+
{
|
|
980
|
+
name: 'Deprecated API: new Buffer()',
|
|
981
|
+
pattern: /\bnew\s+Buffer\s*\(/g,
|
|
982
|
+
severity: 'medium',
|
|
983
|
+
category: 'vulnerability',
|
|
984
|
+
description: 'new Buffer() is deprecated since Node.js 6 and has security implications. Use Buffer.from(), Buffer.alloc(), or Buffer.allocUnsafe().'
|
|
985
|
+
},
|
|
986
|
+
];
|
|
987
|
+
|
|
596
988
|
// =============================================================================
|
|
597
989
|
// TEST FILE PATTERNS (skipped by default, override with --include-tests)
|
|
598
990
|
// =============================================================================
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "ship-safe",
|
|
3
|
-
"version": "3.
|
|
3
|
+
"version": "3.2.0",
|
|
4
4
|
"description": "Security toolkit for vibe coders and indie hackers. Secure your MVP in 5 minutes.",
|
|
5
5
|
"main": "cli/index.js",
|
|
6
6
|
"bin": {
|
|
@@ -57,7 +57,7 @@
|
|
|
57
57
|
"dependencies": {
|
|
58
58
|
"chalk": "^5.3.0",
|
|
59
59
|
"commander": "^12.1.0",
|
|
60
|
-
"glob": "^
|
|
60
|
+
"fast-glob": "^3.3.3",
|
|
61
61
|
"ora": "^8.0.1",
|
|
62
62
|
"write-file-atomic": "^7.0.0"
|
|
63
63
|
}
|