@fourteensystems/shipguard 0.1.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 +213 -0
- package/bin/shipguard.mjs +2 -0
- package/dist/cli/commands/baseline.d.ts +7 -0
- package/dist/cli/commands/baseline.d.ts.map +1 -0
- package/dist/cli/commands/baseline.js +22 -0
- package/dist/cli/commands/baseline.js.map +1 -0
- package/dist/cli/commands/ci.d.ts +13 -0
- package/dist/cli/commands/ci.d.ts.map +1 -0
- package/dist/cli/commands/ci.js +91 -0
- package/dist/cli/commands/ci.js.map +1 -0
- package/dist/cli/commands/explain.d.ts +2 -0
- package/dist/cli/commands/explain.d.ts.map +1 -0
- package/dist/cli/commands/explain.js +20 -0
- package/dist/cli/commands/explain.js.map +1 -0
- package/dist/cli/commands/init.d.ts +7 -0
- package/dist/cli/commands/init.d.ts.map +1 -0
- package/dist/cli/commands/init.js +91 -0
- package/dist/cli/commands/init.js.map +1 -0
- package/dist/cli/commands/rules.d.ts +2 -0
- package/dist/cli/commands/rules.d.ts.map +1 -0
- package/dist/cli/commands/rules.js +13 -0
- package/dist/cli/commands/rules.js.map +1 -0
- package/dist/cli/commands/scan.d.ts +10 -0
- package/dist/cli/commands/scan.d.ts.map +1 -0
- package/dist/cli/commands/scan.js +55 -0
- package/dist/cli/commands/scan.js.map +1 -0
- package/dist/cli/commands/waive.d.ts +8 -0
- package/dist/cli/commands/waive.d.ts.map +1 -0
- package/dist/cli/commands/waive.js +34 -0
- package/dist/cli/commands/waive.js.map +1 -0
- package/dist/cli/index.d.ts +2 -0
- package/dist/cli/index.d.ts.map +1 -0
- package/dist/cli/index.js +63 -0
- package/dist/cli/index.js.map +1 -0
- package/dist/engine/baseline.d.ts +11 -0
- package/dist/engine/baseline.d.ts.map +1 -0
- package/dist/engine/baseline.js +39 -0
- package/dist/engine/baseline.js.map +1 -0
- package/dist/engine/config.d.ts +8 -0
- package/dist/engine/config.d.ts.map +1 -0
- package/dist/engine/config.js +130 -0
- package/dist/engine/config.js.map +1 -0
- package/dist/engine/extensions/load.d.ts +11 -0
- package/dist/engine/extensions/load.d.ts.map +1 -0
- package/dist/engine/extensions/load.js +26 -0
- package/dist/engine/extensions/load.js.map +1 -0
- package/dist/engine/extensions/registry.d.ts +5 -0
- package/dist/engine/extensions/registry.d.ts.map +1 -0
- package/dist/engine/extensions/registry.js +11 -0
- package/dist/engine/extensions/registry.js.map +1 -0
- package/dist/engine/extensions/types.d.ts +51 -0
- package/dist/engine/extensions/types.d.ts.map +1 -0
- package/dist/engine/extensions/types.js +2 -0
- package/dist/engine/extensions/types.js.map +1 -0
- package/dist/engine/report.d.ts +5 -0
- package/dist/engine/report.d.ts.map +1 -0
- package/dist/engine/report.js +88 -0
- package/dist/engine/report.js.map +1 -0
- package/dist/engine/run.d.ts +9 -0
- package/dist/engine/run.d.ts.map +1 -0
- package/dist/engine/run.js +101 -0
- package/dist/engine/run.js.map +1 -0
- package/dist/engine/sarif.d.ts +3 -0
- package/dist/engine/sarif.d.ts.map +1 -0
- package/dist/engine/sarif.js +58 -0
- package/dist/engine/sarif.js.map +1 -0
- package/dist/engine/score.d.ts +13 -0
- package/dist/engine/score.d.ts.map +1 -0
- package/dist/engine/score.js +97 -0
- package/dist/engine/score.js.map +1 -0
- package/dist/engine/types.d.ts +119 -0
- package/dist/engine/types.d.ts.map +1 -0
- package/dist/engine/types.js +2 -0
- package/dist/engine/types.js.map +1 -0
- package/dist/engine/version.d.ts +5 -0
- package/dist/engine/version.d.ts.map +1 -0
- package/dist/engine/version.js +15 -0
- package/dist/engine/version.js.map +1 -0
- package/dist/engine/waivers.d.ts +9 -0
- package/dist/engine/waivers.d.ts.map +1 -0
- package/dist/engine/waivers.js +55 -0
- package/dist/engine/waivers.js.map +1 -0
- package/dist/index.d.ts +12 -0
- package/dist/index.d.ts.map +1 -0
- package/dist/index.js +11 -0
- package/dist/index.js.map +1 -0
- package/dist/next/deps.d.ts +4 -0
- package/dist/next/deps.d.ts.map +1 -0
- package/dist/next/deps.js +102 -0
- package/dist/next/deps.js.map +1 -0
- package/dist/next/detect.d.ts +10 -0
- package/dist/next/detect.d.ts.map +1 -0
- package/dist/next/detect.js +57 -0
- package/dist/next/detect.js.map +1 -0
- package/dist/next/index.d.ts +5 -0
- package/dist/next/index.d.ts.map +1 -0
- package/dist/next/index.js +41 -0
- package/dist/next/index.js.map +1 -0
- package/dist/next/middleware.d.ts +3 -0
- package/dist/next/middleware.d.ts.map +1 -0
- package/dist/next/middleware.js +33 -0
- package/dist/next/middleware.js.map +1 -0
- package/dist/next/routes.d.ts +5 -0
- package/dist/next/routes.d.ts.map +1 -0
- package/dist/next/routes.js +125 -0
- package/dist/next/routes.js.map +1 -0
- package/dist/next/server-actions.d.ts +4 -0
- package/dist/next/server-actions.d.ts.map +1 -0
- package/dist/next/server-actions.js +107 -0
- package/dist/next/server-actions.js.map +1 -0
- package/dist/next/trpc.d.ts +3 -0
- package/dist/next/trpc.d.ts.map +1 -0
- package/dist/next/trpc.js +339 -0
- package/dist/next/trpc.js.map +1 -0
- package/dist/next/types.d.ts +100 -0
- package/dist/next/types.d.ts.map +1 -0
- package/dist/next/types.js +2 -0
- package/dist/next/types.js.map +1 -0
- package/dist/rules/auth-boundary-missing.d.ts +5 -0
- package/dist/rules/auth-boundary-missing.d.ts.map +1 -0
- package/dist/rules/auth-boundary-missing.js +278 -0
- package/dist/rules/auth-boundary-missing.js.map +1 -0
- package/dist/rules/index.d.ts +12 -0
- package/dist/rules/index.d.ts.map +1 -0
- package/dist/rules/index.js +41 -0
- package/dist/rules/index.js.map +1 -0
- package/dist/rules/rate-limit-missing.d.ts +5 -0
- package/dist/rules/rate-limit-missing.d.ts.map +1 -0
- package/dist/rules/rate-limit-missing.js +230 -0
- package/dist/rules/rate-limit-missing.js.map +1 -0
- package/dist/rules/tenancy-scope-missing.d.ts +5 -0
- package/dist/rules/tenancy-scope-missing.d.ts.map +1 -0
- package/dist/rules/tenancy-scope-missing.js +149 -0
- package/dist/rules/tenancy-scope-missing.js.map +1 -0
- package/dist/util/paths.d.ts +6 -0
- package/dist/util/paths.d.ts.map +1 -0
- package/dist/util/paths.js +18 -0
- package/dist/util/paths.js.map +1 -0
- package/package.json +55 -0
|
@@ -0,0 +1,149 @@
|
|
|
1
|
+
import { readFileSync } from "node:fs";
|
|
2
|
+
import path from "node:path";
|
|
3
|
+
import fg from "fast-glob";
|
|
4
|
+
export const RULE_ID = "TENANCY-SCOPE-MISSING";
|
|
5
|
+
/**
|
|
6
|
+
* Prisma methods that modify or read data and should be tenant-scoped.
|
|
7
|
+
*/
|
|
8
|
+
const PRISMA_SCOPED_METHODS = [
|
|
9
|
+
"findUnique", "findFirst", "findMany",
|
|
10
|
+
"update", "updateMany",
|
|
11
|
+
"delete", "deleteMany",
|
|
12
|
+
"upsert",
|
|
13
|
+
];
|
|
14
|
+
export function run(index, config) {
|
|
15
|
+
// Only run if the repo uses Prisma
|
|
16
|
+
if (!index.deps.hasPrisma)
|
|
17
|
+
return [];
|
|
18
|
+
// Only run if we can confirm the repo has tenant fields
|
|
19
|
+
const orgFields = config.hints.tenancy.orgFieldNames;
|
|
20
|
+
if (!repoHasTenancy(index.rootDir))
|
|
21
|
+
return [];
|
|
22
|
+
const findings = [];
|
|
23
|
+
const severity = config.rules[RULE_ID]?.severity ?? "critical";
|
|
24
|
+
// Check for Prisma middleware that enforces tenancy globally
|
|
25
|
+
if (hasPrismaMiddlewareScoping(index.rootDir, orgFields)) {
|
|
26
|
+
// If middleware handles it, skip — or add a low-confidence informational finding
|
|
27
|
+
return [];
|
|
28
|
+
}
|
|
29
|
+
// Scan all files in include paths for Prisma calls
|
|
30
|
+
const files = fg.globSync(config.include, {
|
|
31
|
+
cwd: index.rootDir,
|
|
32
|
+
ignore: ["**/node_modules/**", ...config.exclude],
|
|
33
|
+
});
|
|
34
|
+
for (const file of files) {
|
|
35
|
+
const src = readSource(index.rootDir, file);
|
|
36
|
+
if (!src)
|
|
37
|
+
continue;
|
|
38
|
+
const unscopedCalls = findUnscopedPrismaCalls(src, orgFields);
|
|
39
|
+
for (const call of unscopedCalls) {
|
|
40
|
+
findings.push({
|
|
41
|
+
ruleId: RULE_ID,
|
|
42
|
+
severity,
|
|
43
|
+
confidence: call.confidence,
|
|
44
|
+
confidenceRationale: call.confidenceRationale,
|
|
45
|
+
message: `Prisma ${call.method}() call may lack tenant scoping`,
|
|
46
|
+
file,
|
|
47
|
+
line: call.line,
|
|
48
|
+
snippet: call.snippet,
|
|
49
|
+
evidence: call.evidence,
|
|
50
|
+
remediation: [
|
|
51
|
+
`Add ${orgFields[0] ?? "orgId"} to the where clause`,
|
|
52
|
+
"Use a tenant-aware repository helper or Prisma extension",
|
|
53
|
+
"If tenancy is enforced via Prisma middleware or RLS, add a waiver",
|
|
54
|
+
],
|
|
55
|
+
tags: ["tenancy", "prisma"],
|
|
56
|
+
});
|
|
57
|
+
}
|
|
58
|
+
}
|
|
59
|
+
return findings;
|
|
60
|
+
}
|
|
61
|
+
function findUnscopedPrismaCalls(src, orgFields) {
|
|
62
|
+
const results = [];
|
|
63
|
+
const lines = src.split("\n");
|
|
64
|
+
for (let i = 0; i < lines.length; i++) {
|
|
65
|
+
const line = lines[i];
|
|
66
|
+
for (const method of PRISMA_SCOPED_METHODS) {
|
|
67
|
+
const pattern = new RegExp(`\\.(${method})\\s*\\(`);
|
|
68
|
+
const match = pattern.exec(line);
|
|
69
|
+
if (!match)
|
|
70
|
+
continue;
|
|
71
|
+
// Look at surrounding context (current line + next 10 lines) for the where clause
|
|
72
|
+
const context = lines.slice(i, Math.min(i + 15, lines.length)).join("\n");
|
|
73
|
+
// Check if any org field appears in the where clause context
|
|
74
|
+
const hasOrgField = orgFields.some((field) => {
|
|
75
|
+
const fieldPattern = new RegExp(`\\b${field}\\b`);
|
|
76
|
+
return fieldPattern.test(context);
|
|
77
|
+
});
|
|
78
|
+
if (hasOrgField)
|
|
79
|
+
continue; // Scoped — skip
|
|
80
|
+
// Determine confidence
|
|
81
|
+
const evidence = [`prisma.*.${method}() without ${orgFields.join("/")} in where clause`];
|
|
82
|
+
let confidence;
|
|
83
|
+
let confidenceRationale;
|
|
84
|
+
if (method === "delete" || method === "deleteMany" || method === "update" || method === "updateMany") {
|
|
85
|
+
confidence = "high";
|
|
86
|
+
confidenceRationale = `High: ${method}() is a write operation without tenant scoping field in where clause`;
|
|
87
|
+
evidence.push("write operation without tenant scoping is high risk");
|
|
88
|
+
}
|
|
89
|
+
else {
|
|
90
|
+
confidence = "med";
|
|
91
|
+
confidenceRationale = `Medium: ${method}() is a read without tenant scoping (could be intentional for admin views)`;
|
|
92
|
+
}
|
|
93
|
+
const snippet = line.trim().slice(0, 120);
|
|
94
|
+
results.push({
|
|
95
|
+
method,
|
|
96
|
+
line: i + 1,
|
|
97
|
+
confidence,
|
|
98
|
+
confidenceRationale,
|
|
99
|
+
snippet,
|
|
100
|
+
evidence,
|
|
101
|
+
});
|
|
102
|
+
}
|
|
103
|
+
}
|
|
104
|
+
return results;
|
|
105
|
+
}
|
|
106
|
+
/**
|
|
107
|
+
* Check if the Prisma schema or codebase has evidence of multi-tenancy.
|
|
108
|
+
*/
|
|
109
|
+
function repoHasTenancy(rootDir) {
|
|
110
|
+
// Check Prisma schema for tenant fields
|
|
111
|
+
const schemaFiles = fg.globSync("prisma/schema.prisma", { cwd: rootDir });
|
|
112
|
+
if (schemaFiles.length > 0) {
|
|
113
|
+
const schema = readSource(rootDir, schemaFiles[0]);
|
|
114
|
+
if (schema && /orgId|tenantId|workspaceId|organizationId/i.test(schema)) {
|
|
115
|
+
return true;
|
|
116
|
+
}
|
|
117
|
+
}
|
|
118
|
+
return false;
|
|
119
|
+
}
|
|
120
|
+
/**
|
|
121
|
+
* Check if Prisma middleware enforces tenancy globally.
|
|
122
|
+
*/
|
|
123
|
+
function hasPrismaMiddlewareScoping(rootDir, orgFields) {
|
|
124
|
+
// Look for Prisma middleware or extension files
|
|
125
|
+
const candidates = fg.globSync(["**/prisma/**/*.{ts,js}", "**/lib/prisma*.{ts,js}", "**/db*.{ts,js}"], { cwd: rootDir, ignore: ["**/node_modules/**"] });
|
|
126
|
+
for (const file of candidates) {
|
|
127
|
+
const src = readSource(rootDir, file);
|
|
128
|
+
if (!src)
|
|
129
|
+
continue;
|
|
130
|
+
// Look for $use() middleware or $extends() with query extensions
|
|
131
|
+
const hasMiddleware = /\$use\s*\(/.test(src) || /\$extends\s*\(/.test(src);
|
|
132
|
+
if (!hasMiddleware)
|
|
133
|
+
continue;
|
|
134
|
+
// Check if it references org fields
|
|
135
|
+
const hasOrgFieldRef = orgFields.some((f) => src.includes(f));
|
|
136
|
+
if (hasOrgFieldRef)
|
|
137
|
+
return true;
|
|
138
|
+
}
|
|
139
|
+
return false;
|
|
140
|
+
}
|
|
141
|
+
function readSource(rootDir, file) {
|
|
142
|
+
try {
|
|
143
|
+
return readFileSync(path.join(rootDir, file), "utf8");
|
|
144
|
+
}
|
|
145
|
+
catch {
|
|
146
|
+
return null;
|
|
147
|
+
}
|
|
148
|
+
}
|
|
149
|
+
//# sourceMappingURL=tenancy-scope-missing.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"tenancy-scope-missing.js","sourceRoot":"","sources":["../../src/rules/tenancy-scope-missing.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,YAAY,EAAE,MAAM,SAAS,CAAC;AACvC,OAAO,IAAI,MAAM,WAAW,CAAC;AAC7B,OAAO,EAAE,MAAM,WAAW,CAAC;AAK3B,MAAM,CAAC,MAAM,OAAO,GAAG,uBAAuB,CAAC;AAE/C;;GAEG;AACH,MAAM,qBAAqB,GAAG;IAC5B,YAAY,EAAE,WAAW,EAAE,UAAU;IACrC,QAAQ,EAAE,YAAY;IACtB,QAAQ,EAAE,YAAY;IACtB,QAAQ;CACT,CAAC;AAEF,MAAM,UAAU,GAAG,CAAC,KAAgB,EAAE,MAAuB;IAC3D,mCAAmC;IACnC,IAAI,CAAC,KAAK,CAAC,IAAI,CAAC,SAAS;QAAE,OAAO,EAAE,CAAC;IAErC,wDAAwD;IACxD,MAAM,SAAS,GAAG,MAAM,CAAC,KAAK,CAAC,OAAO,CAAC,aAAa,CAAC;IACrD,IAAI,CAAC,cAAc,CAAC,KAAK,CAAC,OAAO,CAAC;QAAE,OAAO,EAAE,CAAC;IAE9C,MAAM,QAAQ,GAAc,EAAE,CAAC;IAC/B,MAAM,QAAQ,GAAG,MAAM,CAAC,KAAK,CAAC,OAAO,CAAC,EAAE,QAAQ,IAAI,UAAU,CAAC;IAE/D,6DAA6D;IAC7D,IAAI,0BAA0B,CAAC,KAAK,CAAC,OAAO,EAAE,SAAS,CAAC,EAAE,CAAC;QACzD,iFAAiF;QACjF,OAAO,EAAE,CAAC;IACZ,CAAC;IAED,mDAAmD;IACnD,MAAM,KAAK,GAAG,EAAE,CAAC,QAAQ,CAAC,MAAM,CAAC,OAAO,EAAE;QACxC,GAAG,EAAE,KAAK,CAAC,OAAO;QAClB,MAAM,EAAE,CAAC,oBAAoB,EAAE,GAAG,MAAM,CAAC,OAAO,CAAC;KAClD,CAAC,CAAC;IAEH,KAAK,MAAM,IAAI,IAAI,KAAK,EAAE,CAAC;QACzB,MAAM,GAAG,GAAG,UAAU,CAAC,KAAK,CAAC,OAAO,EAAE,IAAI,CAAC,CAAC;QAC5C,IAAI,CAAC,GAAG;YAAE,SAAS;QAEnB,MAAM,aAAa,GAAG,uBAAuB,CAAC,GAAG,EAAE,SAAS,CAAC,CAAC;QAC9D,KAAK,MAAM,IAAI,IAAI,aAAa,EAAE,CAAC;YACjC,QAAQ,CAAC,IAAI,CAAC;gBACZ,MAAM,EAAE,OAAO;gBACf,QAAQ;gBACR,UAAU,EAAE,IAAI,CAAC,UAAU;gBAC3B,mBAAmB,EAAE,IAAI,CAAC,mBAAmB;gBAC7C,OAAO,EAAE,UAAU,IAAI,CAAC,MAAM,iCAAiC;gBAC/D,IAAI;gBACJ,IAAI,EAAE,IAAI,CAAC,IAAI;gBACf,OAAO,EAAE,IAAI,CAAC,OAAO;gBACrB,QAAQ,EAAE,IAAI,CAAC,QAAQ;gBACvB,WAAW,EAAE;oBACX,OAAO,SAAS,CAAC,CAAC,CAAC,IAAI,OAAO,sBAAsB;oBACpD,0DAA0D;oBAC1D,mEAAmE;iBACpE;gBACD,IAAI,EAAE,CAAC,SAAS,EAAE,QAAQ,CAAC;aAC5B,CAAC,CAAC;QACL,CAAC;IACH,CAAC;IAED,OAAO,QAAQ,CAAC;AAClB,CAAC;AAWD,SAAS,uBAAuB,CAC9B,GAAW,EACX,SAAmB;IAEnB,MAAM,OAAO,GAAmB,EAAE,CAAC;IACnC,MAAM,KAAK,GAAG,GAAG,CAAC,KAAK,CAAC,IAAI,CAAC,CAAC;IAE9B,KAAK,IAAI,CAAC,GAAG,CAAC,EAAE,CAAC,GAAG,KAAK,CAAC,MAAM,EAAE,CAAC,EAAE,EAAE,CAAC;QACtC,MAAM,IAAI,GAAG,KAAK,CAAC,CAAC,CAAC,CAAC;QAEtB,KAAK,MAAM,MAAM,IAAI,qBAAqB,EAAE,CAAC;YAC3C,MAAM,OAAO,GAAG,IAAI,MAAM,CAAC,OAAO,MAAM,UAAU,CAAC,CAAC;YACpD,MAAM,KAAK,GAAG,OAAO,CAAC,IAAI,CAAC,IAAI,CAAC,CAAC;YACjC,IAAI,CAAC,KAAK;gBAAE,SAAS;YAErB,kFAAkF;YAClF,MAAM,OAAO,GAAG,KAAK,CAAC,KAAK,CAAC,CAAC,EAAE,IAAI,CAAC,GAAG,CAAC,CAAC,GAAG,EAAE,EAAE,KAAK,CAAC,MAAM,CAAC,CAAC,CAAC,IAAI,CAAC,IAAI,CAAC,CAAC;YAE1E,6DAA6D;YAC7D,MAAM,WAAW,GAAG,SAAS,CAAC,IAAI,CAAC,CAAC,KAAK,EAAE,EAAE;gBAC3C,MAAM,YAAY,GAAG,IAAI,MAAM,CAAC,MAAM,KAAK,KAAK,CAAC,CAAC;gBAClD,OAAO,YAAY,CAAC,IAAI,CAAC,OAAO,CAAC,CAAC;YACpC,CAAC,CAAC,CAAC;YAEH,IAAI,WAAW;gBAAE,SAAS,CAAC,gBAAgB;YAE3C,uBAAuB;YACvB,MAAM,QAAQ,GAAa,CAAC,YAAY,MAAM,cAAc,SAAS,CAAC,IAAI,CAAC,GAAG,CAAC,kBAAkB,CAAC,CAAC;YACnG,IAAI,UAAsB,CAAC;YAC3B,IAAI,mBAA2B,CAAC;YAEhC,IAAI,MAAM,KAAK,QAAQ,IAAI,MAAM,KAAK,YAAY,IAAI,MAAM,KAAK,QAAQ,IAAI,MAAM,KAAK,YAAY,EAAE,CAAC;gBACrG,UAAU,GAAG,MAAM,CAAC;gBACpB,mBAAmB,GAAG,SAAS,MAAM,sEAAsE,CAAC;gBAC5G,QAAQ,CAAC,IAAI,CAAC,qDAAqD,CAAC,CAAC;YACvE,CAAC;iBAAM,CAAC;gBACN,UAAU,GAAG,KAAK,CAAC;gBACnB,mBAAmB,GAAG,WAAW,MAAM,4EAA4E,CAAC;YACtH,CAAC;YAED,MAAM,OAAO,GAAG,IAAI,CAAC,IAAI,EAAE,CAAC,KAAK,CAAC,CAAC,EAAE,GAAG,CAAC,CAAC;YAE1C,OAAO,CAAC,IAAI,CAAC;gBACX,MAAM;gBACN,IAAI,EAAE,CAAC,GAAG,CAAC;gBACX,UAAU;gBACV,mBAAmB;gBACnB,OAAO;gBACP,QAAQ;aACT,CAAC,CAAC;QACL,CAAC;IACH,CAAC;IAED,OAAO,OAAO,CAAC;AACjB,CAAC;AAED;;GAEG;AACH,SAAS,cAAc,CAAC,OAAe;IACrC,wCAAwC;IACxC,MAAM,WAAW,GAAG,EAAE,CAAC,QAAQ,CAAC,sBAAsB,EAAE,EAAE,GAAG,EAAE,OAAO,EAAE,CAAC,CAAC;IAC1E,IAAI,WAAW,CAAC,MAAM,GAAG,CAAC,EAAE,CAAC;QAC3B,MAAM,MAAM,GAAG,UAAU,CAAC,OAAO,EAAE,WAAW,CAAC,CAAC,CAAC,CAAC,CAAC;QACnD,IAAI,MAAM,IAAI,4CAA4C,CAAC,IAAI,CAAC,MAAM,CAAC,EAAE,CAAC;YACxE,OAAO,IAAI,CAAC;QACd,CAAC;IACH,CAAC;IAED,OAAO,KAAK,CAAC;AACf,CAAC;AAED;;GAEG;AACH,SAAS,0BAA0B,CAAC,OAAe,EAAE,SAAmB;IACtE,gDAAgD;IAChD,MAAM,UAAU,GAAG,EAAE,CAAC,QAAQ,CAC5B,CAAC,wBAAwB,EAAE,wBAAwB,EAAE,gBAAgB,CAAC,EACtE,EAAE,GAAG,EAAE,OAAO,EAAE,MAAM,EAAE,CAAC,oBAAoB,CAAC,EAAE,CACjD,CAAC;IAEF,KAAK,MAAM,IAAI,IAAI,UAAU,EAAE,CAAC;QAC9B,MAAM,GAAG,GAAG,UAAU,CAAC,OAAO,EAAE,IAAI,CAAC,CAAC;QACtC,IAAI,CAAC,GAAG;YAAE,SAAS;QAEnB,iEAAiE;QACjE,MAAM,aAAa,GAAG,YAAY,CAAC,IAAI,CAAC,GAAG,CAAC,IAAI,gBAAgB,CAAC,IAAI,CAAC,GAAG,CAAC,CAAC;QAC3E,IAAI,CAAC,aAAa;YAAE,SAAS;QAE7B,oCAAoC;QACpC,MAAM,cAAc,GAAG,SAAS,CAAC,IAAI,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,GAAG,CAAC,QAAQ,CAAC,CAAC,CAAC,CAAC,CAAC;QAC9D,IAAI,cAAc;YAAE,OAAO,IAAI,CAAC;IAClC,CAAC;IAED,OAAO,KAAK,CAAC;AACf,CAAC;AAED,SAAS,UAAU,CAAC,OAAe,EAAE,IAAY;IAC/C,IAAI,CAAC;QACH,OAAO,YAAY,CAAC,IAAI,CAAC,IAAI,CAAC,OAAO,EAAE,IAAI,CAAC,EAAE,MAAM,CAAC,CAAC;IACxD,CAAC;IAAC,MAAM,CAAC;QACP,OAAO,IAAI,CAAC;IACd,CAAC;AACH,CAAC"}
|
|
@@ -0,0 +1,6 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Check if a file path matches any of the allowlist glob patterns.
|
|
3
|
+
* Used by rules to skip files/routes that the user has marked as exempt.
|
|
4
|
+
*/
|
|
5
|
+
export declare function isAllowlisted(filePath: string, allowlistPaths: string[]): boolean;
|
|
6
|
+
//# sourceMappingURL=paths.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"paths.d.ts","sourceRoot":"","sources":["../../src/util/paths.ts"],"names":[],"mappings":"AAAA;;;GAGG;AACH,wBAAgB,aAAa,CAAC,QAAQ,EAAE,MAAM,EAAE,cAAc,EAAE,MAAM,EAAE,GAAG,OAAO,CAGjF"}
|
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Check if a file path matches any of the allowlist glob patterns.
|
|
3
|
+
* Used by rules to skip files/routes that the user has marked as exempt.
|
|
4
|
+
*/
|
|
5
|
+
export function isAllowlisted(filePath, allowlistPaths) {
|
|
6
|
+
if (allowlistPaths.length === 0)
|
|
7
|
+
return false;
|
|
8
|
+
return allowlistPaths.some((pattern) => matchGlob(filePath, pattern));
|
|
9
|
+
}
|
|
10
|
+
function matchGlob(filePath, pattern) {
|
|
11
|
+
const regexStr = pattern
|
|
12
|
+
.replace(/[.+^${}()|[\]\\]/g, "\\$&")
|
|
13
|
+
.replace(/\*\*/g, "{{GLOBSTAR}}")
|
|
14
|
+
.replace(/\*/g, "[^/]*")
|
|
15
|
+
.replace(/\{\{GLOBSTAR\}\}/g, ".*");
|
|
16
|
+
return new RegExp(`^${regexStr}$`).test(filePath);
|
|
17
|
+
}
|
|
18
|
+
//# sourceMappingURL=paths.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"paths.js","sourceRoot":"","sources":["../../src/util/paths.ts"],"names":[],"mappings":"AAAA;;;GAGG;AACH,MAAM,UAAU,aAAa,CAAC,QAAgB,EAAE,cAAwB;IACtE,IAAI,cAAc,CAAC,MAAM,KAAK,CAAC;QAAE,OAAO,KAAK,CAAC;IAC9C,OAAO,cAAc,CAAC,IAAI,CAAC,CAAC,OAAO,EAAE,EAAE,CAAC,SAAS,CAAC,QAAQ,EAAE,OAAO,CAAC,CAAC,CAAC;AACxE,CAAC;AAED,SAAS,SAAS,CAAC,QAAgB,EAAE,OAAe;IAClD,MAAM,QAAQ,GAAG,OAAO;SACrB,OAAO,CAAC,mBAAmB,EAAE,MAAM,CAAC;SACpC,OAAO,CAAC,OAAO,EAAE,cAAc,CAAC;SAChC,OAAO,CAAC,KAAK,EAAE,OAAO,CAAC;SACvB,OAAO,CAAC,mBAAmB,EAAE,IAAI,CAAC,CAAC;IACtC,OAAO,IAAI,MAAM,CAAC,IAAI,QAAQ,GAAG,CAAC,CAAC,IAAI,CAAC,QAAQ,CAAC,CAAC;AACpD,CAAC"}
|
package/package.json
ADDED
|
@@ -0,0 +1,55 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "@fourteensystems/shipguard",
|
|
3
|
+
"version": "0.1.0",
|
|
4
|
+
"description": "CI guardrail that blocks unprotected mutation routes in Next.js SaaS",
|
|
5
|
+
"type": "module",
|
|
6
|
+
"bin": {
|
|
7
|
+
"shipguard": "./bin/shipguard.mjs"
|
|
8
|
+
},
|
|
9
|
+
"exports": {
|
|
10
|
+
".": {
|
|
11
|
+
"types": "./dist/index.d.ts",
|
|
12
|
+
"import": "./dist/index.js"
|
|
13
|
+
}
|
|
14
|
+
},
|
|
15
|
+
"files": [
|
|
16
|
+
"dist",
|
|
17
|
+
"bin"
|
|
18
|
+
],
|
|
19
|
+
"scripts": {
|
|
20
|
+
"build": "tsc",
|
|
21
|
+
"dev": "tsc --watch",
|
|
22
|
+
"test": "vitest run",
|
|
23
|
+
"test:watch": "vitest",
|
|
24
|
+
"lint": "tsc --noEmit"
|
|
25
|
+
},
|
|
26
|
+
"keywords": [
|
|
27
|
+
"nextjs",
|
|
28
|
+
"production-readiness",
|
|
29
|
+
"static-analysis",
|
|
30
|
+
"security",
|
|
31
|
+
"linter",
|
|
32
|
+
"ci"
|
|
33
|
+
],
|
|
34
|
+
"license": "MIT",
|
|
35
|
+
"repository": {
|
|
36
|
+
"type": "git",
|
|
37
|
+
"url": "https://github.com/Fourteen-Systems/shipguard.git",
|
|
38
|
+
"directory": "packages/shipguard"
|
|
39
|
+
},
|
|
40
|
+
"homepage": "https://github.com/Fourteen-Systems/shipguard",
|
|
41
|
+
"bugs": "https://github.com/Fourteen-Systems/shipguard/issues",
|
|
42
|
+
"engines": {
|
|
43
|
+
"node": ">=18"
|
|
44
|
+
},
|
|
45
|
+
"dependencies": {
|
|
46
|
+
"commander": "^13.1.0",
|
|
47
|
+
"fast-glob": "^3.3.3",
|
|
48
|
+
"picocolors": "^1.1.1"
|
|
49
|
+
},
|
|
50
|
+
"devDependencies": {
|
|
51
|
+
"@types/node": "^22.0.0",
|
|
52
|
+
"typescript": "^5.7.0",
|
|
53
|
+
"vitest": "^3.0.0"
|
|
54
|
+
}
|
|
55
|
+
}
|