@cosmicdrift/kumiko-guards 0.288.0 → 0.289.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/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@cosmicdrift/kumiko-guards",
3
- "version": "0.288.0",
3
+ "version": "0.289.0",
4
4
  "description": "AST-based security guards for Kumiko repos: direct-fs/fetch, tenant escalation, admin-API, escape hatches and related checks, run over a shared ts-morph project.",
5
5
  "license": "BUSL-1.1",
6
6
  "author": "Marc Frost <marc@cosmicdriftgamestudio.com>",
@@ -27,7 +27,7 @@
27
27
  "kumiko-guards": "./src/cli.ts"
28
28
  },
29
29
  "dependencies": {
30
- "@cosmicdrift/kumiko-repo-manifest": "0.288.0",
30
+ "@cosmicdrift/kumiko-repo-manifest": "0.289.0",
31
31
  "ts-morph": "^28.0.0"
32
32
  },
33
33
  "publishConfig": {
@@ -11,7 +11,7 @@
11
11
  * I/O: Bun.Glob + Bun.file; directoryExists uses node:fs (same as roots.ts).
12
12
  */
13
13
  import { existsSync, statSync } from "node:fs";
14
- import type { RepoRoot } from "./roots";
14
+ import { type RepoRoot, sourceRootDirs } from "./roots";
15
15
 
16
16
  /** POSIX path join without Node path module. */
17
17
  function joinPath(base: string, ...segments: string[]): string {
@@ -107,20 +107,10 @@ export const BLOCKING_SQL_KINDS: ReadonlyArray<SqlInventoryKind> = ["unsafe", "a
107
107
 
108
108
  const TS_GLOB = new Bun.Glob("**/*.{ts,tsx}");
109
109
 
110
- export type SqlScanLayout = "multi-package" | "flat" | "none";
111
-
112
- // kumiko-platform deliberately returns "none" (0 files) — its docs-samples tree needs its own allowlist review before this guard scans it (follow-up issue).
113
- export function sqlScanLayoutFor(root: Pick<RepoRoot, "name" | "kind">): SqlScanLayout {
114
- if (root.name === "kumiko-platform") return "none";
115
- if (root.kind === "framework" || root.kind === "library") return "multi-package";
116
- return "flat";
117
- }
118
-
119
- /** Scan dirs per repo layout — multi-package keeps the packages/samples/scripts/bin layout, flat is a bare src/ + bin/. */
120
- function scanDirsFor(layout: SqlScanLayout): readonly string[] {
121
- if (layout === "multi-package") return ["packages", "samples", "scripts", "bin"];
122
- if (layout === "none") return [];
123
- return ["src", "bin"];
110
+ // kumiko-platform deliberately returns no scan dirs (0 files) — its docs-samples tree needs its own allowlist review before this guard scans it (follow-up issue).
111
+ export function sqlScanDirsFor(root: RepoRoot): readonly string[] {
112
+ if (root.name === "kumiko-platform") return [];
113
+ return sourceRootDirs(root).map((dir) => dir.slice(root.absPath.length).replace(/^\/+/, ""));
124
114
  }
125
115
 
126
116
  function normalizePathForMatch(filePath: string): string {
@@ -155,9 +145,9 @@ function directoryExists(path: string): boolean {
155
145
  }
156
146
  }
157
147
 
158
- async function collectTsFiles(repoRoot: string, layout: SqlScanLayout): Promise<string[]> {
148
+ async function collectTsFiles(repoRoot: string, scanDirs: readonly string[]): Promise<string[]> {
159
149
  const out: string[] = [];
160
- for (const sub of scanDirsFor(layout)) {
150
+ for (const sub of scanDirs) {
161
151
  const cwd = joinPath(repoRoot, sub);
162
152
  if (!directoryExists(cwd)) continue;
163
153
  for await (const rel of TS_GLOB.scan({ cwd, onlyFiles: true })) {
@@ -226,9 +216,9 @@ function scanFileText(relPath: string, text: string, hits: SqlInventoryHit[]): v
226
216
 
227
217
  export async function scanRepo(
228
218
  repoRoot: string,
229
- layout: SqlScanLayout,
219
+ scanDirs: readonly string[],
230
220
  ): Promise<SqlInventoryReport> {
231
- const relFiles = await collectTsFiles(repoRoot, layout);
221
+ const relFiles = await collectTsFiles(repoRoot, scanDirs);
232
222
  const hits: SqlInventoryHit[] = [];
233
223
 
234
224
  for (const rel of relFiles) {
@@ -63,7 +63,7 @@ import {
63
63
  runRepoChecks,
64
64
  } from "./_lib/guard-kit";
65
65
  import { frameworkTsConfigPath, type RepoRoot, resolveRepoRoots } from "./_lib/roots";
66
- import { type ScanSpec, scanFiles } from "./_lib/scan-scope";
66
+ import { type ScanSpec, scanFiles, scanRoots } from "./_lib/scan-scope";
67
67
  import {
68
68
  classify,
69
69
  computeClientReachablePaths,
@@ -166,6 +166,12 @@ export const check: RepoCheck = {
166
166
  run(roots) {
167
167
  const root = roots[0];
168
168
  if (!root) return { violations: [], matchedFiles: 0, notApplicable: true };
169
+ // The kind filter (scan-scope.ts keepsRootKind) excludes "tooling" roots by
170
+ // default — that's a deliberate scope choice, not a vacuous scan, so it must
171
+ // report notApplicable instead of matchedFiles: 0.
172
+ if (scanRoots(SCAN, [root]).length === 0) {
173
+ return { violations: [], matchedFiles: 0, notApplicable: true };
174
+ }
169
175
  const rootAbsPath = root.absPath;
170
176
 
171
177
  const { allViolations, outsideRoot, scannedFiles } = scanRoot(root);
@@ -28,13 +28,12 @@ import { type RepoCheck, reportResults, runRepoChecks } from "./_lib/guard-kit";
28
28
  import { type RepoRoot, resolveRepoRoots } from "./_lib/roots";
29
29
  import { scanLinesForPredicate } from "./_lib/scan-lines";
30
30
 
31
- // Server-side only: apps/server (not apps/mobile — client code, no server secrets).
32
- const SCAN_PATTERNS: ReadonlyArray<string> = [
33
- "bin/**/*.ts",
34
- "src/**/*.ts",
35
- "apps/server/src/**/*.ts",
36
- "packages/*/src/**/*.ts",
37
- ];
31
+ // Server-side only: a declared sourceRoot under a mobile/client app has no server secrets to leak.
32
+ const CLIENT_SOURCE_ROOT = /(?:^|\/)mobile(?:\/|$)/;
33
+
34
+ function serverSourceRoots(root: RepoRoot): readonly string[] {
35
+ return root.manifest.sourceRoots.filter((sourceRoot) => !CLIENT_SOURCE_ROOT.test(sourceRoot));
36
+ }
38
37
 
39
38
  const EXCLUDE_DIR = /(?:^|\/)(?:node_modules|dist|__tests__)\//;
40
39
  const IS_TEST = /\.(?:test|integration)\.tsx?$/;
@@ -75,8 +74,8 @@ async function scanRoot(
75
74
  ): Promise<{ readonly findings: SecretLiteralFinding[]; readonly scannedFiles: number }> {
76
75
  const findings: SecretLiteralFinding[] = [];
77
76
  let scannedFiles = 0;
78
- for (const pattern of SCAN_PATTERNS) {
79
- for (const rel of new Glob(pattern).scanSync({ cwd: root.absPath })) {
77
+ for (const sourceRoot of serverSourceRoots(root)) {
78
+ for (const rel of new Glob(`${sourceRoot}/**/*.ts`).scanSync({ cwd: root.absPath })) {
80
79
  if (EXCLUDE_DIR.test(`/${rel}`) || IS_TEST.test(rel) || DEV_ENTRYPOINT.test(rel)) {
81
80
  continue;
82
81
  }
@@ -14,7 +14,7 @@
14
14
 
15
15
  import { type RepoCheck, reportResults, runRepoChecks } from "./_lib/guard-kit";
16
16
  import { type RepoRoot, resolveRepoRoots } from "./_lib/roots";
17
- import { BLOCKING_SQL_KINDS, scanRepo, sqlScanLayoutFor } from "./_lib/sql-inventory";
17
+ import { BLOCKING_SQL_KINDS, scanRepo, sqlScanDirsFor } from "./_lib/sql-inventory";
18
18
 
19
19
  export type RawSqlFinding = {
20
20
  readonly repo: string;
@@ -32,7 +32,7 @@ async function scanAllRepos(roots: readonly RepoRoot[]): Promise<{
32
32
  let scannedFiles = 0;
33
33
 
34
34
  for (const root of roots) {
35
- const report = await scanRepo(root.absPath, sqlScanLayoutFor(root));
35
+ const report = await scanRepo(root.absPath, sqlScanDirsFor(root));
36
36
  scannedFiles += report.scannedFiles;
37
37
 
38
38
  for (const hit of report.hits) {
@@ -66,7 +66,7 @@ export const check: RepoCheck = {
66
66
  "// kumiko-lint-ignore raw-sql <reason> on the line or the line above.",
67
67
  async run(roots) {
68
68
  // kumiko-platform's deliberate empty scan-dir list must not read as vacuous (infra#610).
69
- const applicableRoots = roots.filter((r) => sqlScanLayoutFor(r) !== "none");
69
+ const applicableRoots = roots.filter((r) => sqlScanDirsFor(r).length > 0);
70
70
  if (applicableRoots.length === 0) {
71
71
  return { violations: [], matchedFiles: 0, notApplicable: true };
72
72
  }
@@ -39,7 +39,7 @@ import {
39
39
  } from "ts-morph";
40
40
  import { type RepoCheck, reportResults, runRepoChecks } from "./_lib/guard-kit";
41
41
  import { frameworkTsConfigPath } from "./_lib/roots";
42
- import { type ScanSpec, scanFiles } from "./_lib/scan-scope";
42
+ import { type ScanSpec, scanFiles, scanRoots } from "./_lib/scan-scope";
43
43
 
44
44
  const ROOT = process.cwd();
45
45
 
@@ -385,6 +385,13 @@ const SCAN: ScanSpec = {
385
385
  export const check: RepoCheck = {
386
386
  name: "Thin-Wrappers Guard",
387
387
  run(roots) {
388
+ // The kind filter (scan-scope.ts keepsRootKind) excludes "tooling" roots by
389
+ // default — that's a deliberate scope choice, not a vacuous scan, so it must
390
+ // report notApplicable instead of matchedFiles: 0.
391
+ if (scanRoots(SCAN, roots).length === 0) {
392
+ return { violations: [], matchedFiles: 0, notApplicable: true };
393
+ }
394
+
388
395
  const project = new Project({
389
396
  tsConfigFilePath: frameworkTsConfigPath(),
390
397
  skipAddingFilesFromTsConfig: true,
@@ -416,7 +423,7 @@ export const check: RepoCheck = {
416
423
  violations: [],
417
424
  warnings: warnings.length > 0 ? warnings : undefined,
418
425
  matchedFiles: scanned,
419
- notApplicable: roots.length === 0,
426
+ notApplicable: false,
420
427
  };
421
428
  },
422
429
  };