@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.
Files changed (139) hide show
  1. package/README.md +213 -0
  2. package/bin/shipguard.mjs +2 -0
  3. package/dist/cli/commands/baseline.d.ts +7 -0
  4. package/dist/cli/commands/baseline.d.ts.map +1 -0
  5. package/dist/cli/commands/baseline.js +22 -0
  6. package/dist/cli/commands/baseline.js.map +1 -0
  7. package/dist/cli/commands/ci.d.ts +13 -0
  8. package/dist/cli/commands/ci.d.ts.map +1 -0
  9. package/dist/cli/commands/ci.js +91 -0
  10. package/dist/cli/commands/ci.js.map +1 -0
  11. package/dist/cli/commands/explain.d.ts +2 -0
  12. package/dist/cli/commands/explain.d.ts.map +1 -0
  13. package/dist/cli/commands/explain.js +20 -0
  14. package/dist/cli/commands/explain.js.map +1 -0
  15. package/dist/cli/commands/init.d.ts +7 -0
  16. package/dist/cli/commands/init.d.ts.map +1 -0
  17. package/dist/cli/commands/init.js +91 -0
  18. package/dist/cli/commands/init.js.map +1 -0
  19. package/dist/cli/commands/rules.d.ts +2 -0
  20. package/dist/cli/commands/rules.d.ts.map +1 -0
  21. package/dist/cli/commands/rules.js +13 -0
  22. package/dist/cli/commands/rules.js.map +1 -0
  23. package/dist/cli/commands/scan.d.ts +10 -0
  24. package/dist/cli/commands/scan.d.ts.map +1 -0
  25. package/dist/cli/commands/scan.js +55 -0
  26. package/dist/cli/commands/scan.js.map +1 -0
  27. package/dist/cli/commands/waive.d.ts +8 -0
  28. package/dist/cli/commands/waive.d.ts.map +1 -0
  29. package/dist/cli/commands/waive.js +34 -0
  30. package/dist/cli/commands/waive.js.map +1 -0
  31. package/dist/cli/index.d.ts +2 -0
  32. package/dist/cli/index.d.ts.map +1 -0
  33. package/dist/cli/index.js +63 -0
  34. package/dist/cli/index.js.map +1 -0
  35. package/dist/engine/baseline.d.ts +11 -0
  36. package/dist/engine/baseline.d.ts.map +1 -0
  37. package/dist/engine/baseline.js +39 -0
  38. package/dist/engine/baseline.js.map +1 -0
  39. package/dist/engine/config.d.ts +8 -0
  40. package/dist/engine/config.d.ts.map +1 -0
  41. package/dist/engine/config.js +130 -0
  42. package/dist/engine/config.js.map +1 -0
  43. package/dist/engine/extensions/load.d.ts +11 -0
  44. package/dist/engine/extensions/load.d.ts.map +1 -0
  45. package/dist/engine/extensions/load.js +26 -0
  46. package/dist/engine/extensions/load.js.map +1 -0
  47. package/dist/engine/extensions/registry.d.ts +5 -0
  48. package/dist/engine/extensions/registry.d.ts.map +1 -0
  49. package/dist/engine/extensions/registry.js +11 -0
  50. package/dist/engine/extensions/registry.js.map +1 -0
  51. package/dist/engine/extensions/types.d.ts +51 -0
  52. package/dist/engine/extensions/types.d.ts.map +1 -0
  53. package/dist/engine/extensions/types.js +2 -0
  54. package/dist/engine/extensions/types.js.map +1 -0
  55. package/dist/engine/report.d.ts +5 -0
  56. package/dist/engine/report.d.ts.map +1 -0
  57. package/dist/engine/report.js +88 -0
  58. package/dist/engine/report.js.map +1 -0
  59. package/dist/engine/run.d.ts +9 -0
  60. package/dist/engine/run.d.ts.map +1 -0
  61. package/dist/engine/run.js +101 -0
  62. package/dist/engine/run.js.map +1 -0
  63. package/dist/engine/sarif.d.ts +3 -0
  64. package/dist/engine/sarif.d.ts.map +1 -0
  65. package/dist/engine/sarif.js +58 -0
  66. package/dist/engine/sarif.js.map +1 -0
  67. package/dist/engine/score.d.ts +13 -0
  68. package/dist/engine/score.d.ts.map +1 -0
  69. package/dist/engine/score.js +97 -0
  70. package/dist/engine/score.js.map +1 -0
  71. package/dist/engine/types.d.ts +119 -0
  72. package/dist/engine/types.d.ts.map +1 -0
  73. package/dist/engine/types.js +2 -0
  74. package/dist/engine/types.js.map +1 -0
  75. package/dist/engine/version.d.ts +5 -0
  76. package/dist/engine/version.d.ts.map +1 -0
  77. package/dist/engine/version.js +15 -0
  78. package/dist/engine/version.js.map +1 -0
  79. package/dist/engine/waivers.d.ts +9 -0
  80. package/dist/engine/waivers.d.ts.map +1 -0
  81. package/dist/engine/waivers.js +55 -0
  82. package/dist/engine/waivers.js.map +1 -0
  83. package/dist/index.d.ts +12 -0
  84. package/dist/index.d.ts.map +1 -0
  85. package/dist/index.js +11 -0
  86. package/dist/index.js.map +1 -0
  87. package/dist/next/deps.d.ts +4 -0
  88. package/dist/next/deps.d.ts.map +1 -0
  89. package/dist/next/deps.js +102 -0
  90. package/dist/next/deps.js.map +1 -0
  91. package/dist/next/detect.d.ts +10 -0
  92. package/dist/next/detect.d.ts.map +1 -0
  93. package/dist/next/detect.js +57 -0
  94. package/dist/next/detect.js.map +1 -0
  95. package/dist/next/index.d.ts +5 -0
  96. package/dist/next/index.d.ts.map +1 -0
  97. package/dist/next/index.js +41 -0
  98. package/dist/next/index.js.map +1 -0
  99. package/dist/next/middleware.d.ts +3 -0
  100. package/dist/next/middleware.d.ts.map +1 -0
  101. package/dist/next/middleware.js +33 -0
  102. package/dist/next/middleware.js.map +1 -0
  103. package/dist/next/routes.d.ts +5 -0
  104. package/dist/next/routes.d.ts.map +1 -0
  105. package/dist/next/routes.js +125 -0
  106. package/dist/next/routes.js.map +1 -0
  107. package/dist/next/server-actions.d.ts +4 -0
  108. package/dist/next/server-actions.d.ts.map +1 -0
  109. package/dist/next/server-actions.js +107 -0
  110. package/dist/next/server-actions.js.map +1 -0
  111. package/dist/next/trpc.d.ts +3 -0
  112. package/dist/next/trpc.d.ts.map +1 -0
  113. package/dist/next/trpc.js +339 -0
  114. package/dist/next/trpc.js.map +1 -0
  115. package/dist/next/types.d.ts +100 -0
  116. package/dist/next/types.d.ts.map +1 -0
  117. package/dist/next/types.js +2 -0
  118. package/dist/next/types.js.map +1 -0
  119. package/dist/rules/auth-boundary-missing.d.ts +5 -0
  120. package/dist/rules/auth-boundary-missing.d.ts.map +1 -0
  121. package/dist/rules/auth-boundary-missing.js +278 -0
  122. package/dist/rules/auth-boundary-missing.js.map +1 -0
  123. package/dist/rules/index.d.ts +12 -0
  124. package/dist/rules/index.d.ts.map +1 -0
  125. package/dist/rules/index.js +41 -0
  126. package/dist/rules/index.js.map +1 -0
  127. package/dist/rules/rate-limit-missing.d.ts +5 -0
  128. package/dist/rules/rate-limit-missing.d.ts.map +1 -0
  129. package/dist/rules/rate-limit-missing.js +230 -0
  130. package/dist/rules/rate-limit-missing.js.map +1 -0
  131. package/dist/rules/tenancy-scope-missing.d.ts +5 -0
  132. package/dist/rules/tenancy-scope-missing.d.ts.map +1 -0
  133. package/dist/rules/tenancy-scope-missing.js +149 -0
  134. package/dist/rules/tenancy-scope-missing.js.map +1 -0
  135. package/dist/util/paths.d.ts +6 -0
  136. package/dist/util/paths.d.ts.map +1 -0
  137. package/dist/util/paths.js +18 -0
  138. package/dist/util/paths.js.map +1 -0
  139. 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
+ }