@intentius/chant 0.49.0 → 0.51.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/dist/audit/catalog.d.ts +13 -3
- package/dist/audit/catalog.d.ts.map +1 -1
- package/dist/audit/core.d.ts +9 -0
- package/dist/audit/core.d.ts.map +1 -1
- package/dist/audit/discover.d.ts +6 -0
- package/dist/audit/discover.d.ts.map +1 -1
- package/dist/audit/fetch.d.ts.map +1 -1
- package/dist/audit/report-html.d.ts.map +1 -1
- package/dist/audit/report-model.d.ts +6 -0
- package/dist/audit/report-model.d.ts.map +1 -1
- package/dist/audit/report.d.ts.map +1 -1
- package/dist/audit/rules-doc.d.ts.map +1 -1
- package/dist/audit/secrets.d.ts +95 -0
- package/dist/audit/secrets.d.ts.map +1 -0
- package/dist/audit/wrangler.d.ts +33 -0
- package/dist/audit/wrangler.d.ts.map +1 -0
- package/dist/build.d.ts.map +1 -1
- package/dist/cli/commands/audit.d.ts +7 -0
- package/dist/cli/commands/audit.d.ts.map +1 -1
- package/dist/cli/commands/build.d.ts +23 -0
- package/dist/cli/commands/build.d.ts.map +1 -1
- package/dist/cli/handlers/build.d.ts.map +1 -1
- package/dist/cli/handlers/components.d.ts +31 -0
- package/dist/cli/handlers/components.d.ts.map +1 -1
- package/dist/cli/handlers/lifecycle.d.ts +11 -0
- package/dist/cli/handlers/lifecycle.d.ts.map +1 -1
- package/dist/cli/handlers/op-progress.d.ts +57 -0
- package/dist/cli/handlers/op-progress.d.ts.map +1 -0
- package/dist/cli/handlers/operator.d.ts +32 -0
- package/dist/cli/handlers/operator.d.ts.map +1 -0
- package/dist/cli/handlers/run-client.d.ts +21 -1
- package/dist/cli/handlers/run-client.d.ts.map +1 -1
- package/dist/cli/handlers/run-report.d.ts.map +1 -1
- package/dist/cli/handlers/run.d.ts.map +1 -1
- package/dist/cli/handlers/scenario.d.ts +39 -0
- package/dist/cli/handlers/scenario.d.ts.map +1 -0
- package/dist/cli/handlers/search.d.ts +22 -0
- package/dist/cli/handlers/search.d.ts.map +1 -1
- package/dist/cli/main.d.ts.map +1 -1
- package/dist/cli/mcp/op-tools.d.ts.map +1 -1
- package/dist/cli/mcp/resource-handlers.d.ts.map +1 -1
- package/dist/cli/mcp/server.d.ts +35 -2
- package/dist/cli/mcp/server.d.ts.map +1 -1
- package/dist/cli/mcp/types.d.ts +29 -1
- package/dist/cli/mcp/types.d.ts.map +1 -1
- package/dist/cli/registry.d.ts +47 -3
- package/dist/cli/registry.d.ts.map +1 -1
- package/dist/codegen/docs-rule-scanning.d.ts.map +1 -1
- package/dist/components/capability.d.ts +17 -2
- package/dist/components/capability.d.ts.map +1 -1
- package/dist/components/cli-support.d.ts +7 -0
- package/dist/components/cli-support.d.ts.map +1 -1
- package/dist/components/component.d.ts +15 -0
- package/dist/components/component.d.ts.map +1 -1
- package/dist/components/driver.d.ts.map +1 -1
- package/dist/components/run-progress.d.ts +7 -5
- package/dist/components/run-progress.d.ts.map +1 -1
- package/dist/components/verbs/index.d.ts +6 -1
- package/dist/components/verbs/index.d.ts.map +1 -1
- package/dist/components/verbs/run-agent.d.ts +499 -0
- package/dist/components/verbs/run-agent.d.ts.map +1 -0
- package/dist/components/verbs/sign.d.ts +30 -0
- package/dist/components/verbs/sign.d.ts.map +1 -1
- package/dist/composite.d.ts +6 -1
- package/dist/composite.d.ts.map +1 -1
- package/dist/discovery/collect.d.ts.map +1 -1
- package/dist/discovery/fold-import.d.ts +15 -1
- package/dist/discovery/fold-import.d.ts.map +1 -1
- package/dist/discovery/fold-rank.d.ts +66 -0
- package/dist/discovery/fold-rank.d.ts.map +1 -0
- package/dist/discovery/index.d.ts +15 -0
- package/dist/discovery/index.d.ts.map +1 -1
- package/dist/discovery/param-deps.d.ts +17 -0
- package/dist/discovery/param-deps.d.ts.map +1 -0
- package/dist/fold/fold.d.ts +55 -2
- package/dist/fold/fold.d.ts.map +1 -1
- package/dist/fold/subset.d.ts +21 -14
- package/dist/fold/subset.d.ts.map +1 -1
- package/dist/lexicon-schema.d.ts +2 -0
- package/dist/lexicon-schema.d.ts.map +1 -1
- package/dist/lexicon.d.ts +134 -0
- package/dist/lexicon.d.ts.map +1 -1
- package/dist/lifecycle/assert-live.d.ts +77 -0
- package/dist/lifecycle/assert-live.d.ts.map +1 -0
- package/dist/lifecycle/change-set.d.ts +17 -0
- package/dist/lifecycle/change-set.d.ts.map +1 -1
- package/dist/lifecycle/converge-ledger.d.ts +90 -0
- package/dist/lifecycle/converge-ledger.d.ts.map +1 -0
- package/dist/lifecycle/deep-diff.d.ts +18 -0
- package/dist/lifecycle/deep-diff.d.ts.map +1 -1
- package/dist/lifecycle/deep-observe.d.ts +9 -1
- package/dist/lifecycle/deep-observe.d.ts.map +1 -1
- package/dist/lifecycle/disruption.d.ts +96 -0
- package/dist/lifecycle/disruption.d.ts.map +1 -0
- package/dist/lifecycle/gate-ledger.d.ts +33 -0
- package/dist/lifecycle/gate-ledger.d.ts.map +1 -0
- package/dist/lifecycle/git.d.ts +145 -21
- package/dist/lifecycle/git.d.ts.map +1 -1
- package/dist/lifecycle/index.d.ts +6 -0
- package/dist/lifecycle/index.d.ts.map +1 -1
- package/dist/lifecycle/lease.d.ts +113 -0
- package/dist/lifecycle/lease.d.ts.map +1 -0
- package/dist/lifecycle/replay.d.ts +2 -0
- package/dist/lifecycle/replay.d.ts.map +1 -1
- package/dist/lifecycle/scenario-eval.d.ts +42 -0
- package/dist/lifecycle/scenario-eval.d.ts.map +1 -0
- package/dist/lifecycle/scenario.d.ts +163 -0
- package/dist/lifecycle/scenario.d.ts.map +1 -0
- package/dist/lifecycle/symptoms.d.ts +63 -0
- package/dist/lifecycle/symptoms.d.ts.map +1 -0
- package/dist/lint/output-docs.d.ts +94 -0
- package/dist/lint/output-docs.d.ts.map +1 -0
- package/dist/lint/post-synth.d.ts +29 -0
- package/dist/lint/post-synth.d.ts.map +1 -1
- package/dist/lint/rules/__fixtures__/comp/comp003/pass/agent-turn.component.d.ts +11 -0
- package/dist/lint/rules/__fixtures__/comp/comp003/pass/agent-turn.component.d.ts.map +1 -0
- package/dist/lsp/lexicon-providers.d.ts +7 -0
- package/dist/lsp/lexicon-providers.d.ts.map +1 -1
- package/dist/op/activity-contract.d.ts +139 -0
- package/dist/op/activity-contract.d.ts.map +1 -0
- package/dist/op/builders.d.ts +42 -2
- package/dist/op/builders.d.ts.map +1 -1
- package/dist/op/converge-rule.d.ts +161 -0
- package/dist/op/converge-rule.d.ts.map +1 -0
- package/dist/op/generate-pipeline.d.ts +39 -0
- package/dist/op/generate-pipeline.d.ts.map +1 -0
- package/dist/op/index.d.ts +14 -0
- package/dist/op/index.d.ts.map +1 -1
- package/dist/op/local-executor.d.ts +7 -1
- package/dist/op/local-executor.d.ts.map +1 -1
- package/dist/op/op-verb-class.d.ts +42 -0
- package/dist/op/op-verb-class.d.ts.map +1 -0
- package/dist/op/operator.d.ts +128 -0
- package/dist/op/operator.d.ts.map +1 -0
- package/dist/op/step-output-ref.d.ts +187 -0
- package/dist/op/step-output-ref.d.ts.map +1 -0
- package/dist/op/types.d.ts +18 -1
- package/dist/op/types.d.ts.map +1 -1
- package/dist/provenance.d.ts +73 -3
- package/dist/provenance.d.ts.map +1 -1
- package/dist/runtime-adapter.d.ts +7 -1
- package/dist/runtime-adapter.d.ts.map +1 -1
- package/dist/serializer.d.ts +18 -0
- package/dist/serializer.d.ts.map +1 -1
- package/dist/testing.d.ts +23 -2
- package/dist/testing.d.ts.map +1 -1
- package/dist/toml.d.ts +40 -5
- package/dist/toml.d.ts.map +1 -1
- package/package.json +1 -1
- package/src/audit/catalog.test.ts +1 -1
- package/src/audit/catalog.ts +75 -3
- package/src/audit/core.ts +9 -0
- package/src/audit/discover.ts +29 -2
- package/src/audit/fetch.test.ts +216 -3
- package/src/audit/fetch.ts +270 -59
- package/src/audit/report-html.ts +5 -2
- package/src/audit/report-model.ts +9 -0
- package/src/audit/report.test.ts +22 -0
- package/src/audit/report.ts +3 -2
- package/src/audit/rules-doc.ts +2 -0
- package/src/audit/secrets.test.ts +303 -0
- package/src/audit/secrets.ts +406 -0
- package/src/audit/wrangler.test.ts +230 -0
- package/src/audit/wrangler.ts +290 -0
- package/src/build.ts +8 -3
- package/src/cli/command-group.ts +1 -1
- package/src/cli/commands/__fixtures__/schemas/sarif-2.1.0.schema.json +2882 -0
- package/src/cli/commands/audit.test.ts +215 -1
- package/src/cli/commands/audit.ts +86 -17
- package/src/cli/commands/build.test.ts +167 -2
- package/src/cli/commands/build.ts +114 -23
- package/src/cli/handlers/build.ts +2 -0
- package/src/cli/handlers/components.test.ts +199 -1
- package/src/cli/handlers/components.ts +160 -3
- package/src/cli/handlers/graph.test.ts +20 -0
- package/src/cli/handlers/graph.ts +10 -1
- package/src/cli/handlers/lifecycle.test.ts +90 -0
- package/src/cli/handlers/lifecycle.ts +30 -5
- package/src/cli/handlers/op-progress.test.ts +202 -0
- package/src/cli/handlers/op-progress.ts +192 -0
- package/src/cli/handlers/operator.test.ts +255 -0
- package/src/cli/handlers/operator.ts +240 -0
- package/src/cli/handlers/run-client.test.ts +82 -0
- package/src/cli/handlers/run-client.ts +85 -2
- package/src/cli/handlers/run-report.test.ts +62 -0
- package/src/cli/handlers/run-report.ts +20 -58
- package/src/cli/handlers/run.test.ts +144 -0
- package/src/cli/handlers/run.ts +40 -18
- package/src/cli/handlers/scenario.test.ts +456 -0
- package/src/cli/handlers/scenario.ts +330 -0
- package/src/cli/handlers/search-drift.test.ts +263 -0
- package/src/cli/handlers/search.ts +150 -1
- package/src/cli/main.test.ts +23 -0
- package/src/cli/main.ts +81 -1
- package/src/cli/mcp/op-tools.ts +17 -6
- package/src/cli/mcp/resource-handlers.ts +13 -5
- package/src/cli/mcp/server.test.ts +265 -2
- package/src/cli/mcp/server.ts +84 -7
- package/src/cli/mcp/types.ts +27 -1
- package/src/cli/registry.ts +47 -3
- package/src/codegen/docs-rule-scanning.test.ts +42 -0
- package/src/codegen/docs-rule-scanning.ts +25 -2
- package/src/components/README.md +7 -0
- package/src/components/capability.ts +17 -2
- package/src/components/cli-support.test.ts +17 -0
- package/src/components/cli-support.ts +13 -1
- package/src/components/component-schema.test.ts +32 -0
- package/src/components/component.schema.json +6 -0
- package/src/components/component.test.ts +21 -0
- package/src/components/component.ts +15 -0
- package/src/components/driver.ts +12 -4
- package/src/components/run-progress.ts +9 -5
- package/src/components/verbs/index.ts +6 -1
- package/src/components/verbs/run-agent.test.ts +683 -0
- package/src/components/verbs/run-agent.ts +786 -0
- package/src/components/verbs/sign.test.ts +19 -0
- package/src/components/verbs/sign.ts +34 -2
- package/src/composite.ts +31 -2
- package/src/discovery/collect.ts +11 -2
- package/src/discovery/fold-import.test.ts +54 -0
- package/src/discovery/fold-import.ts +178 -38
- package/src/discovery/fold-rank.test.ts +197 -0
- package/src/discovery/fold-rank.ts +346 -0
- package/src/discovery/index.ts +16 -1
- package/src/discovery/param-deps.test.ts +118 -0
- package/src/discovery/param-deps.ts +170 -0
- package/src/fold/fold.test.ts +6 -2
- package/src/fold/fold.ts +184 -3
- package/src/fold/subset.test.ts +82 -19
- package/src/fold/subset.ts +79 -41
- package/src/lexicon-schema.ts +3 -0
- package/src/lexicon.ts +154 -2
- package/src/lifecycle/assert-live.test.ts +125 -0
- package/src/lifecycle/assert-live.ts +154 -0
- package/src/lifecycle/change-set.ts +35 -3
- package/src/lifecycle/converge-ledger.test.ts +199 -0
- package/src/lifecycle/converge-ledger.ts +179 -0
- package/src/lifecycle/deep-diff.test.ts +79 -1
- package/src/lifecycle/deep-diff.ts +23 -0
- package/src/lifecycle/deep-observe.ts +13 -2
- package/src/lifecycle/disruption.test.ts +186 -0
- package/src/lifecycle/disruption.ts +224 -0
- package/src/lifecycle/gate-ledger.test.ts +103 -0
- package/src/lifecycle/gate-ledger.ts +140 -0
- package/src/lifecycle/git.test.ts +430 -0
- package/src/lifecycle/git.ts +446 -84
- package/src/lifecycle/index.ts +6 -0
- package/src/lifecycle/lease.test.ts +343 -0
- package/src/lifecycle/lease.ts +270 -0
- package/src/lifecycle/replay.test.ts +25 -0
- package/src/lifecycle/replay.ts +11 -3
- package/src/lifecycle/scenario-eval.test.ts +199 -0
- package/src/lifecycle/scenario-eval.ts +158 -0
- package/src/lifecycle/scenario.test.ts +195 -0
- package/src/lifecycle/scenario.ts +321 -0
- package/src/lifecycle/symptoms.test.ts +116 -0
- package/src/lifecycle/symptoms.ts +126 -0
- package/src/lint/output-docs.test.ts +220 -0
- package/src/lint/output-docs.ts +204 -0
- package/src/lint/post-synth.test.ts +97 -0
- package/src/lint/post-synth.ts +45 -0
- package/src/lint/rules/__fixtures__/comp/comp003/pass/agent-turn.component.ts +26 -0
- package/src/lint/rules/comp/comp.test.ts +49 -1
- package/src/lint/rules/evl001-non-literal-expression.test.ts +8 -3
- package/src/lint/rules/evl001-non-literal-expression.ts +6 -6
- package/src/lsp/lexicon-providers.test.ts +44 -0
- package/src/lsp/lexicon-providers.ts +11 -1
- package/src/op/activity-contract.test.ts +180 -0
- package/src/op/activity-contract.ts +278 -0
- package/src/op/builders-exports.test.ts +17 -1
- package/src/op/builders.ts +59 -5
- package/src/op/converge-rule.test.ts +179 -0
- package/src/op/converge-rule.ts +311 -0
- package/src/op/generate-pipeline.test.ts +53 -0
- package/src/op/generate-pipeline.ts +99 -0
- package/src/op/index.ts +30 -0
- package/src/op/local-executor.test.ts +92 -0
- package/src/op/local-executor.ts +52 -10
- package/src/op/local-output.ts +1 -1
- package/src/op/op-verb-class.test.ts +126 -0
- package/src/op/op-verb-class.ts +115 -0
- package/src/op/operator.test.ts +346 -0
- package/src/op/operator.ts +213 -0
- package/src/op/step-output-ref.test.ts +334 -0
- package/src/op/step-output-ref.ts +453 -0
- package/src/op/types.ts +18 -1
- package/src/provenance.test.ts +151 -4
- package/src/provenance.ts +118 -4
- package/src/runtime-adapter.ts +31 -10
- package/src/serializer.ts +18 -0
- package/src/testing.test.ts +89 -2
- package/src/testing.ts +63 -3
- package/src/toml.test.ts +157 -384
- package/src/toml.ts +371 -5
|
@@ -0,0 +1,290 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Wrangler config audit (#446) — a config format the audit engine could not
|
|
3
|
+
* parse at all before this: `wrangler.toml`, Cloudflare Workers' native
|
|
4
|
+
* declarative deploy file (routes, bindings, vars, observability). Fitting,
|
|
5
|
+
* since chant's own hosted audit path targets the same Workers runtime.
|
|
6
|
+
*
|
|
7
|
+
* Follows `secrets.ts`'s precedent for a format the issue says should ship
|
|
8
|
+
* "only a detector and a set of checks — no authoring surface, no output
|
|
9
|
+
* serializer": no lexicon plugin, no `AuditInput`/`classifyFiles` involvement,
|
|
10
|
+
* no npm package to install. `auditWranglerConfigs` scans the raw candidate
|
|
11
|
+
* files directly (the detector is `isWranglerConfigPath`) and is called
|
|
12
|
+
* alongside `scanForSecrets` from the CLI. `wrangler.toml` is also NOT the
|
|
13
|
+
* `fly` lexicon's `.fly` (this repo already has a `fly` lexicon covering the
|
|
14
|
+
* Machines/flaps REST API with its own `FLY0xx` ids) — this module owns a
|
|
15
|
+
* disjoint namespace (`WRG0xx`) and `AuditFinding.lexicon = "wrangler"`.
|
|
16
|
+
*
|
|
17
|
+
* Pure: no fs, no network, no Node-only global — Workers-safe (epic #350).
|
|
18
|
+
*/
|
|
19
|
+
|
|
20
|
+
import { parseTOML, TomlParseError } from "../toml";
|
|
21
|
+
import type { AuditFinding } from "./core";
|
|
22
|
+
|
|
23
|
+
/** The minimal file shape the scanner needs (matches `discover.ts`'s `RepoFile`). */
|
|
24
|
+
export interface ScannableFile {
|
|
25
|
+
path: string;
|
|
26
|
+
content: string;
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
/** Detector: does this path look like a Wrangler config? (native TOML only — see module doc.) */
|
|
30
|
+
export function isWranglerConfigPath(path: string): boolean {
|
|
31
|
+
const base = path.split("/").pop() ?? path;
|
|
32
|
+
return base === "wrangler.toml";
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
type JsonRecord = Record<string, unknown>;
|
|
36
|
+
|
|
37
|
+
function isRecord(v: unknown): v is JsonRecord {
|
|
38
|
+
return typeof v === "object" && v !== null && !Array.isArray(v);
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
function asArray(v: unknown): unknown[] {
|
|
42
|
+
return Array.isArray(v) ? v : [];
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
/** One config scope: the top-level document, or one `[env.<name>]` block. */
|
|
46
|
+
interface Scope {
|
|
47
|
+
/** `"top-level"`, or the env name (`"production"`, `"staging"`, …). */
|
|
48
|
+
name: string;
|
|
49
|
+
/** Whether this scope is the account's default deploy target (`wrangler deploy` with no `--env`). */
|
|
50
|
+
isTopLevel: boolean;
|
|
51
|
+
config: JsonRecord;
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
function scopesOf(doc: JsonRecord): Scope[] {
|
|
55
|
+
const scopes: Scope[] = [{ name: "top-level", isTopLevel: true, config: doc }];
|
|
56
|
+
const env = doc.env;
|
|
57
|
+
if (isRecord(env)) {
|
|
58
|
+
for (const [name, cfg] of Object.entries(env)) {
|
|
59
|
+
if (isRecord(cfg)) scopes.push({ name, isTopLevel: false, config: cfg });
|
|
60
|
+
}
|
|
61
|
+
}
|
|
62
|
+
return scopes;
|
|
63
|
+
}
|
|
64
|
+
|
|
65
|
+
const PROD_NAME_RE = /^(prod|production|live)$/i;
|
|
66
|
+
const NON_PROD_NAME_RE = /^(dev|development|staging|stage|preview|test|testing|sandbox|local)$/i;
|
|
67
|
+
|
|
68
|
+
/** Best-effort 1-based line number of the first line containing `needle`, or undefined. */
|
|
69
|
+
function lineOf(content: string, needle: string): number | undefined {
|
|
70
|
+
if (!needle) return undefined;
|
|
71
|
+
const idx = content.indexOf(needle);
|
|
72
|
+
if (idx === -1) return undefined;
|
|
73
|
+
return content.slice(0, idx).split("\n").length;
|
|
74
|
+
}
|
|
75
|
+
|
|
76
|
+
// ── WRG001: workers_dev in a production-named environment ──────────────────
|
|
77
|
+
|
|
78
|
+
function checkWorkersDevInProd(file: ScannableFile, doc: JsonRecord): AuditFinding[] {
|
|
79
|
+
const out: AuditFinding[] = [];
|
|
80
|
+
for (const scope of scopesOf(doc)) {
|
|
81
|
+
if (!PROD_NAME_RE.test(scope.name)) continue;
|
|
82
|
+
if (scope.config.workers_dev !== true) continue;
|
|
83
|
+
out.push({
|
|
84
|
+
checkId: "WRG001",
|
|
85
|
+
severity: "warning",
|
|
86
|
+
message: `Environment "${scope.name}" sets workers_dev = true — the Worker is reachable on the public *.workers.dev subdomain, bypassing whatever access control a custom domain/zone would provide.`,
|
|
87
|
+
file: file.path,
|
|
88
|
+
lexicon: "wrangler",
|
|
89
|
+
entity: scope.name,
|
|
90
|
+
line: lineOf(file.content, "workers_dev"),
|
|
91
|
+
});
|
|
92
|
+
}
|
|
93
|
+
return out;
|
|
94
|
+
}
|
|
95
|
+
|
|
96
|
+
// ── WRG002: sensitive-named key stored in [vars] instead of a secret ───────
|
|
97
|
+
|
|
98
|
+
const SENSITIVE_VAR_RE = /(SECRET|TOKEN|API[_-]?KEY|PASSWORD|PASSWD|PRIVATE[_-]?KEY|ACCESS[_-]?KEY|CLIENT[_-]?SECRET|\bAUTH\b)/i;
|
|
99
|
+
|
|
100
|
+
function checkSecretShapedVars(file: ScannableFile, doc: JsonRecord): AuditFinding[] {
|
|
101
|
+
const out: AuditFinding[] = [];
|
|
102
|
+
for (const scope of scopesOf(doc)) {
|
|
103
|
+
const vars = scope.config.vars;
|
|
104
|
+
if (!isRecord(vars)) continue;
|
|
105
|
+
for (const key of Object.keys(vars)) {
|
|
106
|
+
if (!SENSITIVE_VAR_RE.test(key)) continue;
|
|
107
|
+
out.push({
|
|
108
|
+
checkId: "WRG002",
|
|
109
|
+
severity: "error",
|
|
110
|
+
message: `"${key}" in [vars] (${scope.name}) looks like a credential name. Plaintext vars are committed to source and visible via 'wrangler dev'/the dashboard — use 'wrangler secret put ${key}' instead.`,
|
|
111
|
+
file: file.path,
|
|
112
|
+
lexicon: "wrangler",
|
|
113
|
+
entity: key,
|
|
114
|
+
line: lineOf(file.content, key),
|
|
115
|
+
});
|
|
116
|
+
}
|
|
117
|
+
}
|
|
118
|
+
return out;
|
|
119
|
+
}
|
|
120
|
+
|
|
121
|
+
// ── WRG003: observability explicitly disabled ───────────────────────────────
|
|
122
|
+
|
|
123
|
+
function checkObservabilityDisabled(file: ScannableFile, doc: JsonRecord): AuditFinding[] {
|
|
124
|
+
const out: AuditFinding[] = [];
|
|
125
|
+
for (const scope of scopesOf(doc)) {
|
|
126
|
+
const obs = scope.config.observability;
|
|
127
|
+
if (!isRecord(obs)) continue;
|
|
128
|
+
if (obs.enabled !== false) continue;
|
|
129
|
+
out.push({
|
|
130
|
+
checkId: "WRG003",
|
|
131
|
+
severity: "info",
|
|
132
|
+
message: `Environment "${scope.name}" explicitly disables observability (observability.enabled = false) — no Workers Logs are recorded for this deployment, which will slow down or block incident investigation.`,
|
|
133
|
+
file: file.path,
|
|
134
|
+
lexicon: "wrangler",
|
|
135
|
+
entity: scope.name,
|
|
136
|
+
line: lineOf(file.content, "observability"),
|
|
137
|
+
});
|
|
138
|
+
}
|
|
139
|
+
return out;
|
|
140
|
+
}
|
|
141
|
+
|
|
142
|
+
// ── WRG004: an account-wide wildcard route (unrestricted ingress) ──────────
|
|
143
|
+
|
|
144
|
+
function routePatterns(config: JsonRecord): string[] {
|
|
145
|
+
const patterns: string[] = [];
|
|
146
|
+
for (const r of asArray(config.routes)) {
|
|
147
|
+
if (typeof r === "string") patterns.push(r);
|
|
148
|
+
else if (isRecord(r) && typeof r.pattern === "string") patterns.push(r.pattern);
|
|
149
|
+
}
|
|
150
|
+
if (typeof config.route === "string") patterns.push(config.route);
|
|
151
|
+
return patterns;
|
|
152
|
+
}
|
|
153
|
+
|
|
154
|
+
/** A route pattern with no host scoping at all — matches every zone in the account, not just one. */
|
|
155
|
+
function isAccountWildcard(pattern: string): boolean {
|
|
156
|
+
const p = pattern.trim();
|
|
157
|
+
return p === "*" || p === "*/*";
|
|
158
|
+
}
|
|
159
|
+
|
|
160
|
+
function checkWildcardRoute(file: ScannableFile, doc: JsonRecord): AuditFinding[] {
|
|
161
|
+
const out: AuditFinding[] = [];
|
|
162
|
+
for (const scope of scopesOf(doc)) {
|
|
163
|
+
for (const pattern of routePatterns(scope.config)) {
|
|
164
|
+
if (!isAccountWildcard(pattern)) continue;
|
|
165
|
+
out.push({
|
|
166
|
+
checkId: "WRG004",
|
|
167
|
+
severity: "error",
|
|
168
|
+
message: `Environment "${scope.name}" declares route "${pattern}" — an unscoped wildcard that matches every zone/path on the account, not one domain. Scope it to a specific zone (e.g. "example.com/*").`,
|
|
169
|
+
file: file.path,
|
|
170
|
+
lexicon: "wrangler",
|
|
171
|
+
entity: scope.name,
|
|
172
|
+
line: lineOf(file.content, pattern),
|
|
173
|
+
});
|
|
174
|
+
}
|
|
175
|
+
}
|
|
176
|
+
return out;
|
|
177
|
+
}
|
|
178
|
+
|
|
179
|
+
// ── WRG005: a non-prod environment shares a data-store id with production ──
|
|
180
|
+
|
|
181
|
+
interface BindingSpec {
|
|
182
|
+
key: string;
|
|
183
|
+
idField: string;
|
|
184
|
+
}
|
|
185
|
+
const BINDING_KINDS: BindingSpec[] = [
|
|
186
|
+
{ key: "kv_namespaces", idField: "id" },
|
|
187
|
+
{ key: "r2_buckets", idField: "bucket_name" },
|
|
188
|
+
{ key: "d1_databases", idField: "database_id" },
|
|
189
|
+
];
|
|
190
|
+
|
|
191
|
+
function checkCrossEnvSharedResource(file: ScannableFile, doc: JsonRecord): AuditFinding[] {
|
|
192
|
+
const out: AuditFinding[] = [];
|
|
193
|
+
const scopes = scopesOf(doc);
|
|
194
|
+
// The top-level scope is the default deploy target (`wrangler deploy` with
|
|
195
|
+
// no `--env`) — treated as "production" only when the file also declares at
|
|
196
|
+
// least one dev/staging-like env, so a single-environment file (top-level
|
|
197
|
+
// only) never trips this on itself.
|
|
198
|
+
const hasNonProdEnv = scopes.some((s) => !s.isTopLevel && NON_PROD_NAME_RE.test(s.name));
|
|
199
|
+
const prodScopeNames = new Set(
|
|
200
|
+
scopes.filter((s) => (s.isTopLevel && hasNonProdEnv) || PROD_NAME_RE.test(s.name)).map((s) => s.name),
|
|
201
|
+
);
|
|
202
|
+
const nonProdScopes = scopes.filter((s) => NON_PROD_NAME_RE.test(s.name));
|
|
203
|
+
if (prodScopeNames.size === 0 || nonProdScopes.length === 0) return out;
|
|
204
|
+
|
|
205
|
+
for (const { key, idField } of BINDING_KINDS) {
|
|
206
|
+
// id -> set of scope names that declare a binding with that id
|
|
207
|
+
const byId = new Map<string, Set<string>>();
|
|
208
|
+
for (const scope of scopes) {
|
|
209
|
+
for (const binding of asArray(scope.config[key])) {
|
|
210
|
+
if (!isRecord(binding)) continue;
|
|
211
|
+
const id = binding[idField];
|
|
212
|
+
if (typeof id !== "string" || id === "") continue;
|
|
213
|
+
const owners = byId.get(id) ?? new Set<string>();
|
|
214
|
+
owners.add(scope.name);
|
|
215
|
+
byId.set(id, owners);
|
|
216
|
+
}
|
|
217
|
+
}
|
|
218
|
+
for (const [id, owners] of byId) {
|
|
219
|
+
const prodOwner = [...owners].find((n) => prodScopeNames.has(n));
|
|
220
|
+
const nonProdOwner = [...owners].find((n) => NON_PROD_NAME_RE.test(n));
|
|
221
|
+
if (!prodOwner || !nonProdOwner) continue;
|
|
222
|
+
out.push({
|
|
223
|
+
checkId: "WRG005",
|
|
224
|
+
severity: "error",
|
|
225
|
+
message: `"${nonProdOwner}" and "${prodOwner}" both bind ${key} ${idField}="${id}" — the non-production environment has full read/write access to the production data store instead of its own copy.`,
|
|
226
|
+
file: file.path,
|
|
227
|
+
lexicon: "wrangler",
|
|
228
|
+
entity: `${key}:${id}`,
|
|
229
|
+
line: lineOf(file.content, id),
|
|
230
|
+
});
|
|
231
|
+
}
|
|
232
|
+
}
|
|
233
|
+
return out;
|
|
234
|
+
}
|
|
235
|
+
|
|
236
|
+
// ── WRG006: static assets/site served from the project root ────────────────
|
|
237
|
+
|
|
238
|
+
const ROOT_DIR_RE = /^\.?\/?$/;
|
|
239
|
+
|
|
240
|
+
function checkAssetsServeRoot(file: ScannableFile, doc: JsonRecord): AuditFinding[] {
|
|
241
|
+
const out: AuditFinding[] = [];
|
|
242
|
+
for (const scope of scopesOf(doc)) {
|
|
243
|
+
const candidates: Array<{ table: string; field: string; value: unknown }> = [];
|
|
244
|
+
if (isRecord(scope.config.site)) candidates.push({ table: "site", field: "bucket", value: scope.config.site.bucket });
|
|
245
|
+
if (isRecord(scope.config.assets)) candidates.push({ table: "assets", field: "directory", value: scope.config.assets.directory });
|
|
246
|
+
for (const c of candidates) {
|
|
247
|
+
if (typeof c.value !== "string" || !ROOT_DIR_RE.test(c.value)) continue;
|
|
248
|
+
out.push({
|
|
249
|
+
checkId: "WRG006",
|
|
250
|
+
severity: "warning",
|
|
251
|
+
message: `Environment "${scope.name}" sets [${c.table}].${c.field} = "${c.value}" — the project root is served as public static assets, including any files not meant to be public (config, source maps, .git).`,
|
|
252
|
+
file: file.path,
|
|
253
|
+
lexicon: "wrangler",
|
|
254
|
+
entity: scope.name,
|
|
255
|
+
line: lineOf(file.content, c.field),
|
|
256
|
+
});
|
|
257
|
+
}
|
|
258
|
+
}
|
|
259
|
+
return out;
|
|
260
|
+
}
|
|
261
|
+
|
|
262
|
+
const CHECKS: Array<(file: ScannableFile, doc: JsonRecord) => AuditFinding[]> = [
|
|
263
|
+
checkWorkersDevInProd,
|
|
264
|
+
checkSecretShapedVars,
|
|
265
|
+
checkObservabilityDisabled,
|
|
266
|
+
checkWildcardRoute,
|
|
267
|
+
checkCrossEnvSharedResource,
|
|
268
|
+
checkAssetsServeRoot,
|
|
269
|
+
];
|
|
270
|
+
|
|
271
|
+
/**
|
|
272
|
+
* Audit every `wrangler.toml` in `files`. Pure with respect to the filesystem
|
|
273
|
+
* and network. A file that fails to parse contributes no findings — the audit
|
|
274
|
+
* contract is "runs against any repo," never a crash on odd/partial config.
|
|
275
|
+
*/
|
|
276
|
+
export function auditWranglerConfigs(files: ScannableFile[]): AuditFinding[] {
|
|
277
|
+
const findings: AuditFinding[] = [];
|
|
278
|
+
for (const file of files) {
|
|
279
|
+
if (!isWranglerConfigPath(file.path)) continue;
|
|
280
|
+
let doc: JsonRecord;
|
|
281
|
+
try {
|
|
282
|
+
doc = parseTOML(file.content);
|
|
283
|
+
} catch (err) {
|
|
284
|
+
if (err instanceof TomlParseError) continue;
|
|
285
|
+
throw err;
|
|
286
|
+
}
|
|
287
|
+
for (const check of CHECKS) findings.push(...check(file, doc));
|
|
288
|
+
}
|
|
289
|
+
return findings;
|
|
290
|
+
}
|
package/src/build.ts
CHANGED
|
@@ -8,6 +8,7 @@ import { DiscoveryError, BuildError as BuildErrorClass } from "./errors";
|
|
|
8
8
|
import { LexiconOutput, isLexiconOutput } from "./lexicon-output";
|
|
9
9
|
import { isSecretDeclaration } from "./secret-provenance";
|
|
10
10
|
import { splitReceiptEntities } from "./effect-receipt";
|
|
11
|
+
import { isScenario } from "./lifecycle/scenario";
|
|
11
12
|
import { AttrRef } from "./attrref";
|
|
12
13
|
import { isAttrRefLike } from "./utils";
|
|
13
14
|
import { isChildProject, type ChildProjectInstance } from "./child-project";
|
|
@@ -114,9 +115,10 @@ export function computeStackGraph(
|
|
|
114
115
|
// Dependency map: node → producers it depends on.
|
|
115
116
|
const nodes = lexiconNames.length
|
|
116
117
|
? [...lexiconNames]
|
|
117
|
-
: // Secret provenance declarations (#1828)
|
|
118
|
-
// pseudo-lexicon has no serializer and must not
|
|
119
|
-
|
|
118
|
+
: // Secret provenance declarations (#1828) and plan scenarios (#1292) never
|
|
119
|
+
// form a stack — their pseudo-lexicon has no serializer and must not
|
|
120
|
+
// appear in the manifest.
|
|
121
|
+
[...new Set([...entities.values()].filter((e) => !isSecretDeclaration(e) && !isScenario(e)).map((e) => e.lexicon))];
|
|
120
122
|
const deps = new Map<string, Set<string>>();
|
|
121
123
|
for (const n of nodes) deps.set(n, new Set());
|
|
122
124
|
for (const { from, to } of edges) {
|
|
@@ -303,6 +305,9 @@ export function partitionByLexicon(
|
|
|
303
305
|
// them out of every partition means no serializer sees them and no
|
|
304
306
|
// "No serializer found" warning fires for their pseudo-lexicon.
|
|
305
307
|
if (isSecretDeclaration(entity)) continue;
|
|
308
|
+
// Plan scenarios (#1292) are serializer-neutral the same way: a checkable
|
|
309
|
+
// expectation the CLI reads off the entity map, never output.
|
|
310
|
+
if (isScenario(entity)) continue;
|
|
306
311
|
const lexicon = entity.lexicon;
|
|
307
312
|
if (!partitions.has(lexicon)) {
|
|
308
313
|
partitions.set(lexicon, new Map());
|
package/src/cli/command-group.ts
CHANGED
|
@@ -71,7 +71,7 @@ export interface CommandGroup {
|
|
|
71
71
|
export const RESERVED_COMMAND_NAMES: ReadonlySet<string> = new Set([
|
|
72
72
|
"build", "lint", "list", "describe", "import", "audit", "migrate", "carve",
|
|
73
73
|
"init", "update", "doctor", "dev", "run", "graph", "vendor", "lifecycle",
|
|
74
|
-
"lc", "components", "emulator", "serve",
|
|
74
|
+
"lc", "components", "emulator", "serve", "operator", "approve",
|
|
75
75
|
]);
|
|
76
76
|
|
|
77
77
|
/**
|