@ship-safe/cli 1.1.5 → 1.1.7
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/dist/index.js +37 -9
- package/package.json +1 -1
package/dist/index.js
CHANGED
|
@@ -4211,7 +4211,7 @@ var NEXTJS_RULES = [
|
|
|
4211
4211
|
],
|
|
4212
4212
|
excludePatterns: [
|
|
4213
4213
|
{ regex: "(?:auth|session|token|clerk|getAuth|currentUser|requireAuth|protect|webhook|public|health)", type: "context_line" },
|
|
4214
|
-
{ regex: "(?:webhook|callback|health|cron|stripe|checkout|unsubscribe|contact|export|badge)", type: "file_path" }
|
|
4214
|
+
{ regex: "(?:webhook|callback|health|cron|stripe|checkout|unsubscribe|contact|export|badge|cli\\/)", type: "file_path" }
|
|
4215
4215
|
],
|
|
4216
4216
|
fix: { description: "Add authentication to your API route. Use middleware or check the session at the start of each handler.", suggestion: "const { userId } = auth();\nif (!userId) return new Response('Unauthorized', { status: 401 });" }
|
|
4217
4217
|
},
|
|
@@ -4715,7 +4715,10 @@ var SECRET_RULES = [
|
|
|
4715
4715
|
owasp: "A07:2021",
|
|
4716
4716
|
languages: ["*"],
|
|
4717
4717
|
patterns: [{ regex: `(?:password|passwd|pwd)\\s*[:=]\\s*["']([^\\s"']{8,})["']`, type: "match" }],
|
|
4718
|
-
excludePatterns: [
|
|
4718
|
+
excludePatterns: [
|
|
4719
|
+
{ regex: "(?:example|test|fake|dummy|placeholder|password123|changeme|process\\.env|import\\.meta\\.env|type|interface|schema|validation|zod|yup)", type: "context_line" },
|
|
4720
|
+
{ regex: "(?:admin123|admin|default|sample|secret123|qwerty|letmein|welcome|monkey|dragon|master|1234|abcd)", type: "context_line" }
|
|
4721
|
+
],
|
|
4719
4722
|
fix: { description: "Remove hardcoded passwords. Use environment variables or a secrets manager." }
|
|
4720
4723
|
},
|
|
4721
4724
|
{
|
|
@@ -4896,7 +4899,7 @@ var XSS_RULES = [
|
|
|
4896
4899
|
patterns: [{ regex: "dangerouslySetInnerHTML", type: "match" }],
|
|
4897
4900
|
excludePatterns: [
|
|
4898
4901
|
{ regex: "(?:json-ld|jsonLd|structured-data|schema\\.org|ld\\+json|localStorage|theme|nonce|suppressHydration|DOMPurify|sanitize)", type: "context_line" },
|
|
4899
|
-
{ regex: "(?:json-ld|jsonld|structured-data)", type: "file_path" }
|
|
4902
|
+
{ regex: "(?:json-ld|jsonld|structured-data|layout\\.tsx|layout\\.jsx|_document\\.tsx|_document\\.jsx)", type: "file_path" }
|
|
4900
4903
|
],
|
|
4901
4904
|
fix: { description: "Avoid dangerouslySetInnerHTML. If you must use it, sanitize the HTML with DOMPurify or a similar library first." }
|
|
4902
4905
|
},
|
|
@@ -4910,6 +4913,10 @@ var XSS_RULES = [
|
|
|
4910
4913
|
owasp: "A03:2021",
|
|
4911
4914
|
languages: ["javascript", "typescript"],
|
|
4912
4915
|
patterns: [{ regex: "document\\.write\\s*\\(", type: "match" }],
|
|
4916
|
+
excludePatterns: [
|
|
4917
|
+
{ regex: "(?:pdf|print|export|report|window\\.open)", type: "context_line" },
|
|
4918
|
+
{ regex: "(?:pdf|print|export)", type: "file_path" }
|
|
4919
|
+
],
|
|
4913
4920
|
fix: { description: "Replace document.write() with DOM manipulation methods like createElement() and appendChild()." }
|
|
4914
4921
|
},
|
|
4915
4922
|
{
|
|
@@ -5039,6 +5046,10 @@ var PII_RULES = [
|
|
|
5039
5046
|
owasp: "A09:2021",
|
|
5040
5047
|
languages: ["javascript", "typescript"],
|
|
5041
5048
|
patterns: [{ regex: "console\\.log\\s*\\(.*(?:email|user\\.email|userEmail)", type: "match" }],
|
|
5049
|
+
excludePatterns: [
|
|
5050
|
+
{ regex: "(?:chalk|ora|spinner|ink|inquirer|readline|prompt|display|print|show)", type: "context_line" },
|
|
5051
|
+
{ regex: "(?:cli\\/|commands\\/|bin\\/)", type: "file_path" }
|
|
5052
|
+
],
|
|
5042
5053
|
fix: { description: "Avoid logging email addresses or other PII. If needed for debugging, mask the email." }
|
|
5043
5054
|
},
|
|
5044
5055
|
{
|
|
@@ -5051,6 +5062,10 @@ var PII_RULES = [
|
|
|
5051
5062
|
owasp: "A09:2021",
|
|
5052
5063
|
languages: ["javascript", "typescript"],
|
|
5053
5064
|
patterns: [{ regex: "console\\.(?:log|info|debug|warn)\\s*\\(.*(?:password|token|secret|creditCard|ssn|socialSecurity)", type: "match" }],
|
|
5065
|
+
excludePatterns: [
|
|
5066
|
+
{ regex: "(?:not set|missing|undefined|required|invalid|expired|failed|error|skipping)", type: "context_line" },
|
|
5067
|
+
{ regex: "(?:cli\\/|commands\\/|bin\\/)", type: "file_path" }
|
|
5068
|
+
],
|
|
5054
5069
|
fix: { description: "Remove console logging of sensitive data before deploying to production." }
|
|
5055
5070
|
},
|
|
5056
5071
|
{
|
|
@@ -5096,7 +5111,10 @@ var AUTHZ_RULES = [
|
|
|
5096
5111
|
owasp: "A01:2021",
|
|
5097
5112
|
languages: ["javascript", "typescript"],
|
|
5098
5113
|
patterns: [{ regex: `(?:isAdmin|role\\s*===?\\s*["']admin|user\\.role|hasPermission|isAuthorized)\\s*(?:\\)|&&|;|\\?)`, type: "match" }],
|
|
5099
|
-
excludePatterns: [
|
|
5114
|
+
excludePatterns: [
|
|
5115
|
+
{ regex: "(?:middleware|server|api|route\\.ts|route\\.js|\\bGET\\b|\\bPOST\\b|\\bPUT\\b|\\bDELETE\\b)", type: "file_path" },
|
|
5116
|
+
{ regex: "(?:useQuery|useConvex|useMutation|convex|trpc|graphql|useSWR|useSession|getServerSession|className|disabled|hidden|opacity|hidden|display|render|show|visible)", type: "context_line" }
|
|
5117
|
+
],
|
|
5100
5118
|
fix: { description: "Always enforce admin/role checks on the server side (API routes or middleware), not just in the frontend. Client-side checks are for UI only." }
|
|
5101
5119
|
},
|
|
5102
5120
|
{
|
|
@@ -5566,7 +5584,10 @@ var HEADERS_RULES = [
|
|
|
5566
5584
|
{ regex: "(?:login|signin|sign-in|signup|sign-up|register|reset-password|forgot-password|verify-email).*(?:POST|post|handler|action)", type: "match" },
|
|
5567
5585
|
{ regex: "(?:POST|post|handler|action).*(?:login|signin|sign-in|signup|sign-up|register|reset-password|forgot-password)", type: "match" }
|
|
5568
5586
|
],
|
|
5569
|
-
excludePatterns: [
|
|
5587
|
+
excludePatterns: [
|
|
5588
|
+
{ regex: "(?:rateLimit|rateLimiter|throttle|limiter|upstash|slowDown|brute|attempts)", type: "context_line" },
|
|
5589
|
+
{ regex: "(?:cli\\/|commands\\/|bin\\/|desktop\\/|electron\\/)", type: "file_path" }
|
|
5590
|
+
],
|
|
5570
5591
|
fix: { description: "Add rate limiting to all authentication endpoints. Limit login attempts per IP and per account to prevent brute force attacks.", suggestion: "const limiter = rateLimit({ windowMs: 15 * 60 * 1000, max: 5 })" }
|
|
5571
5592
|
},
|
|
5572
5593
|
{
|
|
@@ -5723,7 +5744,10 @@ var CLIENT_RULES = [
|
|
|
5723
5744
|
{ regex: "(?:res|response)\\.(?:json|send|status)\\s*\\([^)]*(?:error\\.stack|error\\.message|err\\.stack|err\\.message|e\\.stack)", type: "match" },
|
|
5724
5745
|
{ regex: "NextResponse\\.json\\s*\\(\\s*\\{[^}]*(?:error\\.stack|error\\.message|err\\.stack|err\\.message)", type: "match" }
|
|
5725
5746
|
],
|
|
5726
|
-
excludePatterns: [
|
|
5747
|
+
excludePatterns: [
|
|
5748
|
+
{ regex: "(?:development|dev|NODE_ENV|process\\.env)", type: "context_line" },
|
|
5749
|
+
{ regex: "(?:startsWith|includes|===|!==|Quota|limit|Unauthorized|Invalid)", type: "context_line" }
|
|
5750
|
+
],
|
|
5727
5751
|
fix: { description: "Return a generic error message to users (e.g. 'Something went wrong'). Log the full error server-side with console.error() for debugging. Never send stack traces or internal error messages in API responses." }
|
|
5728
5752
|
},
|
|
5729
5753
|
{
|
|
@@ -5756,7 +5780,7 @@ var CLIENT_RULES = [
|
|
|
5756
5780
|
],
|
|
5757
5781
|
excludePatterns: [
|
|
5758
5782
|
{ regex: "(?:csrf|xsrf|_token|csrfToken|validateToken|SameSite|clerk|auth\\(\\)|getAuth|currentUser|getSession)", type: "context_line" },
|
|
5759
|
-
{ regex: "(?:api/webhook|api/stripe|api/clerk|api/cron|api/internal)", type: "file_path" }
|
|
5783
|
+
{ regex: "(?:api/webhook|api/stripe|api/clerk|api/cron|api/internal|api/cli|api/checkout|api/contact|api/unsubscribe)", type: "file_path" }
|
|
5760
5784
|
],
|
|
5761
5785
|
fix: { description: "Add CSRF protection: use SameSite=Strict cookies, verify a CSRF token header, or use a framework that handles this automatically (e.g. Next.js Server Actions have built-in CSRF protection)." }
|
|
5762
5786
|
}
|
|
@@ -5846,6 +5870,10 @@ function matchRule(rule, file) {
|
|
|
5846
5870
|
return [];
|
|
5847
5871
|
}
|
|
5848
5872
|
const isSecretRule = rule.id.startsWith("secrets/");
|
|
5873
|
+
if (!isSecretRule) {
|
|
5874
|
+
const docPaths = /(?:\/docs\/|\/examples\/|\/fixtures\/|__tests__\/fixtures)/i;
|
|
5875
|
+
if (docPaths.test(file.path)) return [];
|
|
5876
|
+
}
|
|
5849
5877
|
const findings = [];
|
|
5850
5878
|
const lines = file.content.split("\n");
|
|
5851
5879
|
for (const pattern of rule.patterns) {
|
|
@@ -7500,10 +7528,10 @@ import { homedir as homedir2 } from "os";
|
|
|
7500
7528
|
import { join as join3 } from "path";
|
|
7501
7529
|
var HISTORY_DIR = join3(homedir2(), ".shipsafe", "scans");
|
|
7502
7530
|
function fingerprint(f) {
|
|
7503
|
-
return createHash2("
|
|
7531
|
+
return createHash2("sha256").update(`${f.ruleId}:${f.file}`).digest("hex");
|
|
7504
7532
|
}
|
|
7505
7533
|
function historyPath(scanPath) {
|
|
7506
|
-
const hash = createHash2("
|
|
7534
|
+
const hash = createHash2("sha256").update(scanPath).digest("hex");
|
|
7507
7535
|
return join3(HISTORY_DIR, `${hash}.json`);
|
|
7508
7536
|
}
|
|
7509
7537
|
function diffWithPrevious(scanPath, currentFindings) {
|