@cosmicdrift/kumiko-guards 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 (47) hide show
  1. package/LICENSE +57 -0
  2. package/README.md +16 -0
  3. package/package.json +40 -0
  4. package/src/_lib/baseline-compare.ts +56 -0
  5. package/src/_lib/generic-reason.ts +39 -0
  6. package/src/_lib/guard-kit.ts +534 -0
  7. package/src/_lib/handler-name-forms.ts +29 -0
  8. package/src/_lib/ignore-tag.ts +24 -0
  9. package/src/_lib/primitives-access.ts +19 -0
  10. package/src/_lib/roots.ts +304 -0
  11. package/src/_lib/scan-lines.ts +25 -0
  12. package/src/_lib/scan-scope.ts +152 -0
  13. package/src/_lib/security-baseline-cli.ts +54 -0
  14. package/src/_lib/security-baseline.ts +325 -0
  15. package/src/_lib/sql-inventory.ts +267 -0
  16. package/src/guard-access-denied-test.ts +135 -0
  17. package/src/guard-admin-api.ts +134 -0
  18. package/src/guard-cross-feature-imports.ts +244 -0
  19. package/src/guard-direct-entity-writes.ts +387 -0
  20. package/src/guard-direct-fetch.ts +154 -0
  21. package/src/guard-escape-hatch-declared.ts +520 -0
  22. package/src/guard-fake-tests.ts +137 -0
  23. package/src/guard-html-escape.ts +345 -0
  24. package/src/guard-no-custom-primitives.ts +196 -0
  25. package/src/guard-no-date-api.ts +186 -0
  26. package/src/guard-no-direct-fs.ts +232 -0
  27. package/src/guard-no-direct-process-env.ts +126 -0
  28. package/src/guard-no-inline-styles.ts +58 -0
  29. package/src/guard-no-logic-in-views.ts +147 -0
  30. package/src/guard-no-raw-hooks.ts +76 -0
  31. package/src/guard-open-to-all-reason.ts +112 -0
  32. package/src/guard-pre-es-patterns.ts +199 -0
  33. package/src/guard-primitives-discipline.ts +330 -0
  34. package/src/guard-raw-classname.ts +111 -0
  35. package/src/guard-raw-interactive-elements.ts +154 -0
  36. package/src/guard-raw-sql.ts +89 -0
  37. package/src/guard-renderer-boundaries.ts +157 -0
  38. package/src/guard-restricted-symbols.ts +138 -0
  39. package/src/guard-silent-skip.ts +186 -0
  40. package/src/guard-tailwind-scan-surface.ts +588 -0
  41. package/src/guard-tenant-escalation.ts +312 -0
  42. package/src/guard-thin-wrappers.ts +422 -0
  43. package/src/guard-unsafe-json-parse.ts +86 -0
  44. package/src/index.ts +29 -0
  45. package/src/run-guards.ts +78 -0
  46. package/src/run-repo-checks.ts +22 -0
  47. package/src/run-ui-guards.ts +25 -0
package/LICENSE ADDED
@@ -0,0 +1,57 @@
1
+ Business Source License 1.1
2
+
3
+ Parameters
4
+
5
+ Licensor: Marc Frost
6
+
7
+ Licensed Work: @cosmicdrift/kumiko-guards
8
+ The Licensed Work is © 2026 Marc Frost.
9
+
10
+ Additional Use Grant:
11
+ You may use the Licensed Work in production for any purpose, including
12
+ commercially, EXCEPT for the Restricted Use.
13
+
14
+ "Restricted Use" is defined as using the Licensed Work to provide a platform
15
+ or service to third parties that allows them to host, deploy, or run their
16
+ own applications built with the Licensed Work. This includes, but is not
17
+ limited to: managed hosting services, software-as-a-service (SaaS) platforms,
18
+ platform-as-a-service (PaaS), developer platforms, or any multi-tenant
19
+ managed offering of the Licensed Work.
20
+
21
+ This restriction does not apply to the Licensor, any entity controlled by,
22
+ controlling, or under common control with the Licensor ("Affiliates"), or
23
+ contractors acting on their behalf. The Licensor remains free to use the
24
+ Licensed Work for any purpose, including for the operation of kumiko.rocks.
25
+
26
+ Change Date: 2030-05-05
27
+ Change License: Apache License, Version 2.0
28
+
29
+
30
+ Terms
31
+
32
+ The Licensor hereby grants you the right to copy, modify, create derivative works,
33
+ redistribute, and make non-production use of the Licensed Work. The Licensor may
34
+ make an Additional Use Grant, above, permitting limited production use.
35
+
36
+ Effective on the Change Date, or the fourth anniversary of the first publicly
37
+ available distribution of the Licensed Work under this License, whichever comes
38
+ first, this License will convert to the Change License.
39
+
40
+ This Business Source License governs use of the Licensed Work in all cases, except
41
+ as to any use that is explicitly granted in the Additional Use Grant above or
42
+ under the Change License after the Change Date.
43
+
44
+ If your use of the Licensed Work does not comply with the requirements of this
45
+ License, you must cease use of the Licensed Work immediately.
46
+
47
+ All copies of the Licensed Work, and all derivative works thereof, must include
48
+ this License.
49
+
50
+ This License does not grant you any right, title, or interest in any trademark,
51
+ logo, or branding of the Licensor, except as required to comply with this License.
52
+
53
+ TO THE EXTENT PERMITTED BY APPLICABLE LAW, THE LICENSED WORK IS PROVIDED ON AN
54
+ “AS IS” BASIS. LICENSOR DISCLAIMS ALL WARRANTIES, EXPRESS OR IMPLIED, INCLUDING
55
+ WITHOUT LIMITATION WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE,
56
+ TITLE, AND NON-INFRINGEMENT. IN NO EVENT WILL LICENSOR BE LIABLE FOR ANY DAMAGES
57
+ ARISING OUT OF OR RELATED TO THIS LICENSE OR THE USE OF THE LICENSED WORK.
package/README.md ADDED
@@ -0,0 +1,16 @@
1
+ # @cosmicdrift/kumiko-guards
2
+
3
+ AST-based security guards for a single Kumiko repo: direct filesystem
4
+ access, direct fetch, direct entity writes, admin-API misuse, tenant
5
+ escalation, undeclared escape hatches, `openToAll` without a reason, and
6
+ missing access-denied tests. Every finding must clear the repo's own
7
+ security baseline — there is no per-guard skip flag.
8
+
9
+ ```
10
+ bun node_modules/@cosmicdrift/kumiko-guards/src/run-guards.ts [--explain|--write-security-baseline]
11
+ ```
12
+
13
+ The baseline lives at `.kumiko-security-baseline.json` in the consumer
14
+ repo's root. `--explain` prints the resolved repo and per-guard scan scope
15
+ without running any guard; `--write-security-baseline` freezes current
16
+ findings into the baseline file.
package/package.json ADDED
@@ -0,0 +1,40 @@
1
+ {
2
+ "name": "@cosmicdrift/kumiko-guards",
3
+ "version": "0.1.0",
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
+ "license": "BUSL-1.1",
6
+ "author": "Marc Frost <marc@cosmicdriftgamestudio.com>",
7
+ "repository": {
8
+ "type": "git",
9
+ "url": "git+https://github.com/CosmicDriftGameStudio/kumiko-framework.git",
10
+ "directory": "packages/guards"
11
+ },
12
+ "bugs": {
13
+ "url": "https://github.com/CosmicDriftGameStudio/kumiko-framework/issues"
14
+ },
15
+ "homepage": "https://kumiko.rocks",
16
+ "type": "module",
17
+ "kumiko": {
18
+ "runtime": "dev"
19
+ },
20
+ "exports": {
21
+ ".": {
22
+ "types": "./src/index.ts",
23
+ "default": "./src/index.ts"
24
+ }
25
+ },
26
+ "dependencies": {
27
+ "@cosmicdrift/kumiko-repo-manifest": "0.1.0",
28
+ "ts-morph": "^28.0.0"
29
+ },
30
+ "publishConfig": {
31
+ "registry": "https://registry.npmjs.org",
32
+ "access": "public"
33
+ },
34
+ "files": [
35
+ "src",
36
+ "!src/**/__tests__/**",
37
+ "README.md",
38
+ "LICENSE"
39
+ ]
40
+ }
@@ -0,0 +1,56 @@
1
+ // Shared by baseline-ratchet guards (check-complexity, guard-pii-
2
+ // annotations) and the security-baseline module: per-file current-vs-baseline
3
+ // count diff. More than baseline is a regression; less is allowed but doesn't
4
+ // auto-update the baseline.
5
+ export interface BaselineRegression {
6
+ readonly file: string;
7
+ readonly baseline: number;
8
+ readonly current: number;
9
+ }
10
+
11
+ export function compareToBaseline(
12
+ current: Readonly<Record<string, number>>,
13
+ baseline: Readonly<Record<string, number>>,
14
+ ): {
15
+ regressions: BaselineRegression[];
16
+ reduced: number;
17
+ reductions: BaselineRegression[];
18
+ } {
19
+ const regressions: BaselineRegression[] = [];
20
+ const reductions: BaselineRegression[] = [];
21
+ let reduced = 0;
22
+ for (const file of new Set([...Object.keys(current), ...Object.keys(baseline)])) {
23
+ const expected = baseline[file] ?? 0;
24
+ const actual = current[file] ?? 0;
25
+ if (actual > expected) {
26
+ regressions.push({ file, baseline: expected, current: actual });
27
+ } else if (actual < expected) {
28
+ reduced += expected - actual;
29
+ reductions.push({ file, baseline: expected, current: actual });
30
+ }
31
+ }
32
+ regressions.sort((a, b) => a.file.localeCompare(b.file));
33
+ reductions.sort((a, b) => a.file.localeCompare(b.file));
34
+ return { regressions, reduced, reductions };
35
+ }
36
+
37
+ // Shared by the import/symbol-restriction guards (guard-no-direct-fs,
38
+ // guard-restricted-symbols): relativize an absolute file path against the
39
+ // real repo root so ALLOWLIST/EXCLUDE regexes match a stable, anchored path
40
+ // instead of an arbitrary substring. resolveRepoRoots() finds the repo the
41
+ // path actually lives under; the marker-slice below is a fallback ONLY for
42
+ // ts-morph's in-memory test filesystem, whose paths never match a real repo
43
+ // root (e.g. "/packages/framework/src/x.ts" in a unit test). Unresolvable
44
+ // paths throw — returning the absolute path re-opens ancestor-segment
45
+ // EXCLUDE false-negatives (tools/bin matching above the repo root).
46
+ export function findRepoRootFor<T extends { readonly absPath: string }>(
47
+ filePath: string,
48
+ roots: ReadonlyArray<T>,
49
+ ): T | undefined {
50
+ for (const root of roots) {
51
+ if (filePath === root.absPath || filePath.startsWith(`${root.absPath}/`)) {
52
+ return root;
53
+ }
54
+ }
55
+ return undefined;
56
+ }
@@ -0,0 +1,39 @@
1
+ import { type Node, SyntaxKind } from "ts-morph";
2
+
3
+ const GENERIC_REASONS = new Set([
4
+ "",
5
+ "todo",
6
+ "tbd",
7
+ "fixme",
8
+ "legacy",
9
+ "temp",
10
+ "temporary",
11
+ "hack",
12
+ "wip",
13
+ "n/a",
14
+ "na",
15
+ "none",
16
+ "-",
17
+ "?",
18
+ "reason",
19
+ "xxx",
20
+ ]);
21
+
22
+ const GENERIC_PREFIXES = ["todo:", "fixme:", "tbd:"];
23
+
24
+ // undefined = not statically judgeable (template with substitutions, identifier, call), not "generic".
25
+ export function literalReasonText(node: Node | undefined): string | undefined {
26
+ if (!node) return undefined;
27
+ if (node.isKind(SyntaxKind.StringLiteral)) return node.getLiteralValue();
28
+ if (node.isKind(SyntaxKind.NoSubstitutionTemplateLiteral)) return node.getLiteralValue();
29
+ return undefined;
30
+ }
31
+
32
+ export function isGenericReason(text: string): boolean {
33
+ const lowered = text.trim().toLowerCase();
34
+ if (GENERIC_PREFIXES.some((prefix) => lowered.startsWith(prefix))) {
35
+ return true;
36
+ }
37
+ const stripped = lowered.replace(/[.!:;,]+$/, "").trim();
38
+ return GENERIC_REASONS.has(stripped);
39
+ }