diff --git a/cli/dead-code.mdx b/cli/dead-code.mdx index c8e5425..39f3a7d 100644 --- a/cli/dead-code.mdx +++ b/cli/dead-code.mdx @@ -60,7 +60,7 @@ fallow check # Hidden alias for fallow dead-code | `--re-export-cycles` | Report only re-export cycles (barrel files that re-export from each other in a loop, or self-loops). See [explanations/dead-code#re-export-cycles](/explanations/dead-code#re-export-cycles) | | `--package-cycles` | Report only package cycles (workspace packages that import each other in a loop). See [explanations/dead-code#package-cycles](/explanations/dead-code#package-cycles) | | `--boundary-violations` | Report only boundary violations | -| `--policy-violations` | Report only rule-pack policy violations (banned calls and banned imports that you declare in the `rulePacks` config key). See [explanations/dead-code#policy-violations](/explanations/dead-code#policy-violations) | +| `--policy-violations` | Report only configured rule-pack violations, including banned usages and gdp-ts proof producers outside allowed files. See [Policy violations](/explanations/dead-code#policy-violations). | | `--stale-suppressions` | Report only stale suppression comments and `@expected-unused` JSDoc tags | | `--unused-catalog-entries` | Report only unused pnpm catalog entries | | `--empty-catalog-groups` | Report only empty named pnpm catalog groups | diff --git a/cli/guard.mdx b/cli/guard.mdx index 2c6c7d8..2fe49af 100644 --- a/cli/guard.mdx +++ b/cli/guard.mdx @@ -133,6 +133,7 @@ Some projects set `boundaries.coverage.requireAllFiles`. If the path does not ma - `allowed_zones` always contains the zone of the file itself, because same-zone imports are never restricted. - `unrestricted` is `true` when no import rule applies to the file. This happens when boundaries are not configured, or when the path is outside every zone. - `policy_rules` contains only the rules in scope for that file. A rule that declares `zones` appears only for files in those zones. The array is empty when `rules.policy-violation` is `off`. +- For a `gdp-proof-producer` policy, `allowed_files` lists the globs where proof factories are permitted. `proof_kinds`, when present, lists the exact label filter. An absent filter checks every recognized factory call, including dynamic labels. The human output also shows the allowed producers and proof kinds. These fields describe permission; the rule's scope still comes from `files`, `exclude`, and `zones`. ## Suppression tokens diff --git a/configuration/overview.mdx b/configuration/overview.mdx index 0afbb4b..f9ab946 100644 --- a/configuration/overview.mdx +++ b/configuration/overview.mdx @@ -1116,7 +1116,7 @@ For presets, custom zones, examples, and output formats, see [Architecture bound -Rule packs let you ban specific calls, imports, and effects across your project. A rule pack is a standalone JSON or JSONC file with `banned-call`, `banned-import`, and `banned-effect` rules. Fallow loads packs as data only and never runs project code. Paths are relative to the project root. Fallow reports matches as `policy-violation` findings with the ID `/`. +Rule packs let you enforce project policies for calls, imports, exports, effects, and gdp-ts proof producers. A rule pack is a standalone JSON or JSONC file. Fallow loads packs as data only and never runs project code. Paths are relative to the project root. Fallow reports matches as `policy-violation` findings with the ID `/`. ```jsonc { @@ -1128,6 +1128,9 @@ A pack file declares `version: 1`, a unique `name`, and a non-empty `rules` arra - `banned-call` rules match callee paths per segment, through imports. `child_process.*` covers named, namespace, and default imports from `child_process` and `node:child_process`. - `banned-import` rules match import specifiers as written, per segment. `moment` covers `moment/locale/nl`, never `moment-timezone`. +- `banned-effect` rules match effect classes from the security catalogue, such as `network` and `database`. +- `banned-export` rules match exported names. A trailing `*` matches a name prefix. +- `gdp-proof-producer` rules restrict calls to `@gdp-ts/core.defineProof` to `allowedFiles` globs. Optional `proofKinds` labels restrict the rule to those literal proof kinds. - Optional `files` and `exclude` globs limit where a rule applies. - `"ignoreTypeOnly": true` skips type-only imports. - An optional per-rule `severity` overrides the main `rules."policy-violation"` severity (default `warn`). @@ -1138,6 +1141,42 @@ For editor autocomplete, run `fallow rule-pack-schema` to print the pack JSON Sc Keep pack files in a committed directory such as `rule-packs/`. `.fallow/` is the gitignored cache directory, so packs there are missing from the checkouts of your teammates. +#### gdp-ts proof producers + +Use a global producer rule to allow proof factories only in your authorization modules. Add a rule for a specific proof kind to require its factory to live in one owner file: + +```json title="rule-packs/gdp.json" +{ + "version": 1, + "name": "gdp", + "rules": [ + { + "id": "trusted-producers", + "kind": "gdp-proof-producer", + "allowedFiles": ["src/auth/**"], + "severity": "error" + }, + { + "id": "delete-project-owner", + "kind": "gdp-proof-producer", + "allowedFiles": ["src/auth/project.ts"], + "proofKinds": ["CanDeleteProject"], + "severity": "error" + } + ] +} +``` + +Add `"rulePacks": ["rule-packs/gdp.json"]` to your fallow config. Each matching rule reports independently. A call outside `src/auth/**` can violate both rules. A call in `src/auth/other.ts` violates only the owner rule. + +`allowedFiles` must contain at least one project-root-relative glob. It grants producer permission. `files`, `exclude`, and `zones` limit where the rule checks code. Neither `allowedFiles` nor `proofKinds` applies to other rule kinds. + +An absent or empty `proofKinds` checks every recognized factory call, including dynamic labels. A nonempty list matches exact static string labels; dynamic labels are skipped by that rule. Keep the global rule to restrict those calls too. + +The rule checks every analyzed file, including files unreachable from entry points. Files excluded from discovery are outside its scope. It follows named and namespace imports, import aliases, and unambiguous project re-export chains to the gdp-ts factory. See [Policy violations](/explanations/dead-code#policy-violations) for supported forms and limits. + +Inspect the configured permissions with `fallow rule-pack list --format json` or `fallow guard src/routes/project.ts --format json`. Use `fallow rule-pack test rule-packs/gdp.json --format json` to inspect matches while authoring the pack. This command exits successfully when it reports findings. For CI enforcement, run `fallow dead-code --policy-violations`; an error-severity finding exits with code 1. + For details on the issue type, see [Policy violations](/explanations/dead-code#policy-violations). diff --git a/configuration/rules.mdx b/configuration/rules.mdx index 0dd66d1..53ba9a1 100644 --- a/configuration/rules.mdx +++ b/configuration/rules.mdx @@ -134,7 +134,7 @@ stale-suppressions = "warn" | `re-export-cycle` | `warn` | Barrel files that re-export from each other in a loop (`kind: "multi-node"`), or one barrel that re-exports from itself (`kind: "self-loop"`). A re-export cycle does nothing and is almost always a bug. `circular-dependencies` covers runtime import loops instead. The cycle spans multiple files, so a per-file `overrides.rules.re-export-cycle` has no effect. Use the project-wide `rules` block, or `// fallow-ignore-file re-export-cycle` on any file in the cycle. Fallow also accepts the aliases `re-export-cycles`, `reexport-cycle`, and `reexport-cycles`. | | `package-cycle` | `warn` | Workspace packages that import each other in a loop, so that they cannot be built in dependency order. Built from resolved imports between packages, not from declared dependencies. Imports from test, spec, story, fixture, and tooling config files do not count. Fallow also accepts the alias `package-cycles`. See [Package cycles](/explanations/dead-code#package-cycles) | | `boundary-violation` | `error` | Imports that cross user-defined architecture zone boundaries | -| `policy-violation` | `warn` | Calls, imports, or catalogue-derived effects that a rule pack bans (the `rulePacks` config key). The default is `warn`, so a new pack does not fail CI on its first run. A per-rule `severity` in the pack overrides this value for each finding, and the exit code uses that per-finding severity. `off` turns off all rule-pack checks. | +| `policy-violation` | `warn` | Calls, imports, exports, effects, or gdp-ts proof producers that violate a rule pack (the `rulePacks` config key). A per-rule `severity` in the pack overrides this value for each finding, and the exit code uses that per-finding severity. A rule set to `error` can fail CI even when this setting is `warn`. `off` turns off all rule-pack checks. | | `invalid-client-export` | `warn` | A `"use client"` file that exports a server-only or route-config name | | `mixed-client-server-barrel` | `warn` | A barrel that re-exports both a `"use client"` module and a server-only module | | `misplaced-directive` | `warn` | A `"use client"` or `"use server"` directive that is not in the leading position and therefore has no effect | diff --git a/explanations/dead-code.mdx b/explanations/dead-code.mdx index 56559f3..32dc374 100644 --- a/explanations/dead-code.mdx +++ b/explanations/dead-code.mdx @@ -469,17 +469,27 @@ Imports that cross the architecture zone boundaries that you define. You define ### Policy violations {#policy-violations} -Calls, imports, exports, or catalogue-derived effects that match a `banned-call`, `banned-import`, `banned-effect`, or `banned-export` rule from a configured rule pack (the `rulePacks` config key). A rule pack is a standalone JSON or JSONC file with declarative policy data only. Loading a pack never executes project code. Every output format identifies a finding as `/`. +Calls, imports, exports, effects, or gdp-ts proof producers that violate a configured rule pack (the `rulePacks` config key). A rule pack is a standalone JSON or JSONC file with declarative policy data only. Loading a pack never executes project code. Every output format identifies a finding as `/`. | Severity | Default | |:---------|:--------| | Warning | Yes | -The `rules."policy-violation"` master setting defaults to `warn`, so a new pack never fails CI on its first run. A per-rule `severity` in the pack overrides the master setting for each finding. The exit code uses the effective severity of each finding, so one `error` rule fails the run even when the master setting is `warn`. `off` on the master setting turns off the whole evaluator. An invalid or missing pack makes the config load fail with exit code 2, so fallow never silently enforces nothing. +The `rules."policy-violation"` master setting defaults to `warn`. A per-rule `severity` in the pack overrides the master setting for each finding. The exit code uses the effective severity of each finding, so one `error` rule fails the run even when the master setting is `warn`. `off` on the master setting turns off the whole evaluator. An invalid or missing pack makes the config load fail with exit code 2, so fallow never silently enforces nothing. **When to act:** Replace the banned call or import with the alternative that the rule message names. If a rule fires in directories where the usage is allowed, limit the rule with `files` and `exclude` globs. To suppress one rule, use `// fallow-ignore-next-line policy-violation:/`. Use bare `policy-violation` only when you want to suppress every rule-pack finding at that line or file scope. -**Limitations:** Matching is syntactic. `banned-call` does not follow aliased or re-bound callees (`const run = cp.exec; run()`). `banned-import` matches only the raw specifier, so it does not match rewritten forms such as `npm:moment`. Fallow does not check `require()` calls and dynamic `import()`. +**Limitations:** `banned-call` does not follow aliased or re-bound callees (`const run = cp.exec; run()`). `banned-import` matches only the raw specifier, so it does not match rewritten forms such as `npm:moment`. These checks do not cover `require()` calls and dynamic `import()`. + +#### gdp-ts proof producer violations + +The opt-in `gdp-proof-producer` rule follows calls to `@gdp-ts/core.defineProof` through the module graph. It reports factory creation outside the rule's `allowedFiles`. With `proofKinds`, you can require a specific literal proof label to be created only in its owner file. Configure both rules in a [rule pack](/configuration/overview#config-fields). + +Fallow recognizes direct, renamed, and namespace imports, including unambiguous re-exports through project modules and workspace packages. A locally shadowed import name does not match. Each call site gets its own finding, with the resolved factory and a static proof label when available. This rule also checks analyzed files unreachable from entry points. + +Move the factory to an allowed authorization module and expose an operation that performs the permission check. Retain the gdp-ts ESLint or Oxlint rules, including `no-exported-prover`, and your TypeScript checks. Restricting factory locations does not prevent an allowed module from exporting a prover that callers can invoke without authorization. + +Fallow does not trace arbitrary wrappers, local value aliases, `require()`, or dynamic imports to the factory. It skips optional calls, computed member calls such as `gdp["defineProof"](...)`, and ambiguous re-export origins. When `proofKinds` contains labels, calls with dynamic labels are skipped. A global producer rule without a label filter still checks their producer locations. This analysis does not verify the authorization check or runtime permission validity. To print the pack JSON Schema for editor autocomplete, run `fallow rule-pack-schema`. For the pack file format, see the [`rulePacks` config key](/configuration/overview#config-fields). diff --git a/public-content-manifest.json b/public-content-manifest.json index b088724..067c386 100644 --- a/public-content-manifest.json +++ b/public-content-manifest.json @@ -7,7 +7,7 @@ "visibility": "public-only" }, "content": { - "sha256": "f218b84c3481fd167b14ed51299a8cb4f66962778e48920e7ac7c799426325c7", + "sha256": "a264cabeaba9e5e81e4845e8fd4ffa7c0387d4280931b5bc1e79ec2d596ce825", "files": [ { "path": "adoption.mdx", @@ -101,8 +101,8 @@ }, { "path": "cli/dead-code.mdx", - "bytes": 32389, - "sha256": "5c647161c1a549558bd6b3d46cf391105dc93bc8a22441311f4bc81456339b71" + "bytes": 32364, + "sha256": "91fd30b6e3584002d4a53f2de28a4f771893397e296973b4c5a20b11f7263f90" }, { "path": "cli/decision-surface.mdx", @@ -141,8 +141,8 @@ }, { "path": "cli/guard.mdx", - "bytes": 7433, - "sha256": "9576778f4a96b6c351a73a9a8fbbcbb9c5ee9793dfb71a38963603aa2c7d2a34" + "bytes": 7847, + "sha256": "d03527c0cc00e11786fee7797b9eb6b5559f81ae5d9d0dbbfcbb2e6cd7745f58" }, { "path": "cli/health.mdx", @@ -256,13 +256,13 @@ }, { "path": "configuration/overview.mdx", - "bytes": 73952, - "sha256": "a5ace087c0c63968470188e39f91f4b10d3484928d029ad710ce5fb1ea0e2d0e" + "bytes": 76395, + "sha256": "4573d2867daaa66fe00b792c0a4539237bcc6d72f132750fd22fca5a27cc407b" }, { "path": "configuration/rules.mdx", - "bytes": 18706, - "sha256": "1256592b36e6525144bc432cf069a8fa7b75f4aabf57674309de5f9407a24c9c" + "bytes": 18720, + "sha256": "e0b910e7f6c7eefedcc5aedb45f007daa56877c76639feaf55f78b9e09371360" }, { "path": "configuration/suppression.mdx", @@ -291,8 +291,8 @@ }, { "path": "explanations/dead-code.mdx", - "bytes": 84565, - "sha256": "124647155f3ae33b7b70f1e6e26499bd1b378139cce02116b08358b85a0f0197" + "bytes": 86001, + "sha256": "599442af48c03b82dc17df3667b7c00e20f55a0fc5ec3664d2a598f08bd5cbd1" }, { "path": "explanations/duplication.mdx",