@intentius/chant-lexicon-cedar 0.44.8 → 0.44.9
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/README.md +58 -0
- package/dist/codegen/package.d.ts.map +1 -1
- package/dist/config.d.ts +25 -0
- package/dist/config.d.ts.map +1 -1
- package/dist/dogwood/cli.d.ts +206 -0
- package/dist/dogwood/cli.d.ts.map +1 -0
- package/dist/dogwood/event-schema.d.ts +161 -0
- package/dist/dogwood/event-schema.d.ts.map +1 -0
- package/dist/dogwood/index.d.ts +33 -0
- package/dist/dogwood/index.d.ts.map +1 -0
- package/dist/dogwood/macros.d.ts +96 -0
- package/dist/dogwood/macros.d.ts.map +1 -0
- package/dist/dogwood/policy.d.ts +120 -0
- package/dist/dogwood/policy.d.ts.map +1 -0
- package/dist/dogwood/scan.d.ts +109 -0
- package/dist/dogwood/scan.d.ts.map +1 -0
- package/dist/dogwood/serialize.d.ts +46 -0
- package/dist/dogwood/serialize.d.ts.map +1 -0
- package/dist/dogwood/temporal.d.ts +259 -0
- package/dist/dogwood/temporal.d.ts.map +1 -0
- package/dist/dogwood/upstream.d.ts +41 -0
- package/dist/dogwood/upstream.d.ts.map +1 -0
- package/dist/dogwood/window.d.ts +73 -0
- package/dist/dogwood/window.d.ts.map +1 -0
- package/dist/index.d.ts +4 -0
- package/dist/index.d.ts.map +1 -1
- package/dist/integrity.json +13 -3
- package/dist/lint/audit-catalog.d.ts.map +1 -1
- package/dist/lint/post-synth/dogwood-helpers.d.ts +63 -0
- package/dist/lint/post-synth/dogwood-helpers.d.ts.map +1 -0
- package/dist/lint/post-synth/dwdc010.d.ts +25 -0
- package/dist/lint/post-synth/dwdc010.d.ts.map +1 -0
- package/dist/lint/post-synth/dwdc011.d.ts +19 -0
- package/dist/lint/post-synth/dwdc011.d.ts.map +1 -0
- package/dist/lint/post-synth/dwdc012.d.ts +21 -0
- package/dist/lint/post-synth/dwdc012.d.ts.map +1 -0
- package/dist/lint/post-synth/dwde010.d.ts +32 -0
- package/dist/lint/post-synth/dwde010.d.ts.map +1 -0
- package/dist/lint/post-synth/dwde011.d.ts +33 -0
- package/dist/lint/post-synth/dwde011.d.ts.map +1 -0
- package/dist/lint/post-synth/dwds010.d.ts +24 -0
- package/dist/lint/post-synth/dwds010.d.ts.map +1 -0
- package/dist/lint/post-synth/index.d.ts.map +1 -1
- package/dist/manifest.json +1 -1
- package/dist/okf/index.md +6 -0
- package/dist/okf/rules/DWDC010.md +11 -0
- package/dist/okf/rules/DWDC011.md +11 -0
- package/dist/okf/rules/DWDC012.md +11 -0
- package/dist/okf/rules/DWDE010.md +11 -0
- package/dist/okf/rules/DWDE011.md +11 -0
- package/dist/okf/rules/DWDS010.md +11 -0
- package/dist/policy-text.d.ts +53 -0
- package/dist/policy-text.d.ts.map +1 -0
- package/dist/rules/dogwood-helpers.ts +139 -0
- package/dist/rules/dwdc010.ts +62 -0
- package/dist/rules/dwdc011.ts +61 -0
- package/dist/rules/dwdc012.ts +46 -0
- package/dist/rules/dwde010.ts +130 -0
- package/dist/rules/dwde011.ts +108 -0
- package/dist/rules/dwds010.ts +46 -0
- package/dist/serializer.d.ts +10 -18
- package/dist/serializer.d.ts.map +1 -1
- package/dist/skills/chant-cedar-authoring.md +180 -0
- package/dist/skills/chant-cedar-avp-embedding.md +125 -0
- package/dist/skills/chant-cedar-meta-policy.md +119 -0
- package/package.json +2 -2
- package/src/codegen/package.ts +3 -2
- package/src/config.test.ts +12 -0
- package/src/config.ts +28 -0
- package/src/dogwood/cli.test.ts +392 -0
- package/src/dogwood/cli.ts +545 -0
- package/src/dogwood/event-schema.test.ts +218 -0
- package/src/dogwood/event-schema.ts +318 -0
- package/src/dogwood/index.ts +198 -0
- package/src/dogwood/macros.test.ts +104 -0
- package/src/dogwood/macros.ts +229 -0
- package/src/dogwood/policy.test.ts +94 -0
- package/src/dogwood/policy.ts +141 -0
- package/src/dogwood/scan.ts +287 -0
- package/src/dogwood/serialize.test.ts +331 -0
- package/src/dogwood/serialize.ts +209 -0
- package/src/dogwood/temporal.test.ts +272 -0
- package/src/dogwood/temporal.ts +592 -0
- package/src/dogwood/testdata/custom-kinds.dwschema +17 -0
- package/src/dogwood/testdata/default-macros.dw +23 -0
- package/src/dogwood/testdata/lowered-read-after-login.json +13 -0
- package/src/dogwood/testdata/max-window-raised.dwschema +25 -0
- package/src/dogwood/testdata/pinned.dwschema +31 -0
- package/src/dogwood/testdata/read-after-login.cedarschema +20 -0
- package/src/dogwood/testdata/read-after-login.dw +17 -0
- package/src/dogwood/testdata/temporal-policies.dw +53 -0
- package/src/dogwood/upstream.ts +41 -0
- package/src/dogwood/window.ts +124 -0
- package/src/index.ts +24 -0
- package/src/lint/audit-catalog.ts +56 -0
- package/src/lint/post-synth/dogwood-helpers.ts +139 -0
- package/src/lint/post-synth/dwd-post-synth.test.ts +256 -0
- package/src/lint/post-synth/dwdc010.ts +62 -0
- package/src/lint/post-synth/dwdc011.ts +61 -0
- package/src/lint/post-synth/dwdc012.ts +46 -0
- package/src/lint/post-synth/dwde-post-synth.test.ts +368 -0
- package/src/lint/post-synth/dwde010.ts +130 -0
- package/src/lint/post-synth/dwde011.ts +108 -0
- package/src/lint/post-synth/dwds010.ts +46 -0
- package/src/lint/post-synth/index.ts +12 -0
- package/src/lint/post-synth/post-synth.test.ts +7 -3
- package/src/policy-text.ts +128 -0
- package/src/serializer.ts +71 -109
|
@@ -0,0 +1,31 @@
|
|
|
1
|
+
// Adapted from dogwood-policy/dogwood@5063bcc2d6d6cf5024d1b0498e6cc8ef52cbcf0c (Apache-2.0):
|
|
2
|
+
// dogwood-language/configuration/event-schemas/pinned.dwschema
|
|
3
|
+
// Upstream's shape; the bytes are what chant's typed builders emit.
|
|
4
|
+
|
|
5
|
+
// The default event-schema shape: request/response/error, each correlated to
|
|
6
|
+
// the deciding request's principal.
|
|
7
|
+
|
|
8
|
+
decision event <A>::request {
|
|
9
|
+
...inputs(A),
|
|
10
|
+
pin callerPrincipal: principalType(A) = principal,
|
|
11
|
+
callerResource: resourceType(A),
|
|
12
|
+
requestId: String,
|
|
13
|
+
sessionId: String,
|
|
14
|
+
}
|
|
15
|
+
|
|
16
|
+
event <A>::response {
|
|
17
|
+
...inputs(A),
|
|
18
|
+
...outputs(A),
|
|
19
|
+
pin callerPrincipal: principalType(A) = principal,
|
|
20
|
+
callerResource: resourceType(A),
|
|
21
|
+
requestId: String,
|
|
22
|
+
sessionId: String,
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
event <A>::error {
|
|
26
|
+
...inputs(A),
|
|
27
|
+
pin callerPrincipal: principalType(A) = principal,
|
|
28
|
+
callerResource: resourceType(A),
|
|
29
|
+
requestId: String,
|
|
30
|
+
sessionId: String,
|
|
31
|
+
}
|
|
@@ -0,0 +1,20 @@
|
|
|
1
|
+
// Adapted from dogwood-policy/dogwood@5063bcc2d6d6cf5024d1b0498e6cc8ef52cbcf0c (Apache-2.0):
|
|
2
|
+
// dogwood-docs/examples/read_after_login/schema.cedarschema
|
|
3
|
+
// The action schema half — what `dogwood validate --policy-schema` reads.
|
|
4
|
+
|
|
5
|
+
namespace Drupe {
|
|
6
|
+
type LoginInput = { user: String };
|
|
7
|
+
type ReadInput = { user: String };
|
|
8
|
+
entity Gateway;
|
|
9
|
+
entity OAuthUser = { id: String } tags String;
|
|
10
|
+
action "Login" appliesTo {
|
|
11
|
+
principal: [OAuthUser],
|
|
12
|
+
resource: [Gateway],
|
|
13
|
+
context: { input: LoginInput }
|
|
14
|
+
};
|
|
15
|
+
action "Read" appliesTo {
|
|
16
|
+
principal: [OAuthUser],
|
|
17
|
+
resource: [Gateway],
|
|
18
|
+
context: { input: ReadInput }
|
|
19
|
+
};
|
|
20
|
+
}
|
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
// Adapted from dogwood-policy/dogwood@5063bcc2d6d6cf5024d1b0498e6cc8ef52cbcf0c (Apache-2.0):
|
|
2
|
+
// dogwood-docs/examples/read_after_login/policy.dw
|
|
3
|
+
// The bundle the CLI-gated DWDE checks are exercised against (#1659).
|
|
4
|
+
|
|
5
|
+
// Permit Read only if the same user successfully logged in within the last
|
|
6
|
+
// hour. The `when temporal { … }` clause reads the accumulated event history;
|
|
7
|
+
// the `{ input.user: context.input.user }` pin correlates the past login's
|
|
8
|
+
// user with the current request's user.
|
|
9
|
+
@id("read_after_login")
|
|
10
|
+
permit (
|
|
11
|
+
principal,
|
|
12
|
+
action == Drupe::Action::"Read",
|
|
13
|
+
resource
|
|
14
|
+
)
|
|
15
|
+
when temporal {
|
|
16
|
+
formerly within 1h Drupe::Action::"Login"::response{ input.user: context.input.user }
|
|
17
|
+
};
|
|
@@ -0,0 +1,53 @@
|
|
|
1
|
+
// Adapted from dogwood-policy/dogwood@5063bcc2d6d6cf5024d1b0498e6cc8ef52cbcf0c (Apache-2.0):
|
|
2
|
+
// dogwood-docs/examples/read_after_login/policy.dw
|
|
3
|
+
// dogwood-docs/examples/temporal_sum_formerly_transfer/policy.dw
|
|
4
|
+
// dogwood-docs/examples/read_heartbeat_since_login_30s/policy.dw
|
|
5
|
+
// dogwood-docs/examples/temporal_count_formerly_login/policy.dw
|
|
6
|
+
// Upstream's shape; the bytes are what chant's typed builders emit.
|
|
7
|
+
|
|
8
|
+
// Sums the numeric value `?a` over occurrences of `?body` within window `?w`.
|
|
9
|
+
def temporal sum_formerly(?a, ?w, ?body) {
|
|
10
|
+
sum ?a for (?a: Long), ($t: Timepoint). where formerly within ?w (?body && tp($t))
|
|
11
|
+
};
|
|
12
|
+
|
|
13
|
+
@id("read_after_login")
|
|
14
|
+
permit (
|
|
15
|
+
principal,
|
|
16
|
+
action == Drupe::Action::"Read",
|
|
17
|
+
resource
|
|
18
|
+
)
|
|
19
|
+
when temporal {
|
|
20
|
+
formerly within 1h Drupe::Action::"Login"::response{ input.user: context.input.user }
|
|
21
|
+
};
|
|
22
|
+
|
|
23
|
+
@id("transfer_sum_over_100")
|
|
24
|
+
permit (
|
|
25
|
+
principal,
|
|
26
|
+
action == Drupe::Action::"Alert",
|
|
27
|
+
resource
|
|
28
|
+
)
|
|
29
|
+
when temporal {
|
|
30
|
+
exists (total: Long). (sum_formerly(a, 1h, Drupe::Action::"Transfer"::request{ input.user: _, input.amount: a })) == total && total > 100
|
|
31
|
+
};
|
|
32
|
+
|
|
33
|
+
@id("no_tool_after_sensitive_read")
|
|
34
|
+
forbid (
|
|
35
|
+
principal,
|
|
36
|
+
action == Drupe::Action::"Invoke",
|
|
37
|
+
resource
|
|
38
|
+
)
|
|
39
|
+
when guardrails { context.input.tool != "audit" }
|
|
40
|
+
when temporal {
|
|
41
|
+
Drupe::Action::"Read"::response{ output.classification: c } since within 30m Drupe::Action::"Login"::request{}
|
|
42
|
+
}
|
|
43
|
+
unless { principal in Drupe::Group::"breakglass" };
|
|
44
|
+
|
|
45
|
+
@id("rate_limited")
|
|
46
|
+
permit (
|
|
47
|
+
principal,
|
|
48
|
+
action == Drupe::Action::"Transfer",
|
|
49
|
+
resource
|
|
50
|
+
)
|
|
51
|
+
when temporal {
|
|
52
|
+
(count for (t: Timepoint). where formerly within 15m (Drupe::Action::"Transfer"::request{} && tp(t))) < 5
|
|
53
|
+
};
|
|
@@ -0,0 +1,41 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* The dogwood upstream pin.
|
|
3
|
+
*
|
|
4
|
+
* Not a `upstreamPin` on the plugin: that slot belongs to the cedar grammar
|
|
5
|
+
* (`@cedar-policy/cedar-wasm`, see `../spec/pin.ts`), and self-upgrade tooling
|
|
6
|
+
* bumps exactly one thing per lexicon. Dogwood's pin is a git SHA recorded by
|
|
7
|
+
* #1657, because upstream has no version to key off — zero tags, zero
|
|
8
|
+
* releases, and a workspace version of `1.0.0` that is an Amazon-internal
|
|
9
|
+
* package artifact every future sync will also report.
|
|
10
|
+
*
|
|
11
|
+
* The blob hashes are the narrow content pin the #1657 report recommends. The
|
|
12
|
+
* whole tree hash moves on every sync including docs-only ones, which makes it
|
|
13
|
+
* too noisy to gate on; these seven files are the surface chant actually
|
|
14
|
+
* depends on. The three `.pest` grammars pin the language, `default_macros.dw`
|
|
15
|
+
* pins the standard aggregate library (which is a file a sync can edit and a
|
|
16
|
+
* caller can replace with `--macros`), and the three `dogwood-cli/src` files
|
|
17
|
+
* pin the JSON report structs that are #1659's integration surface.
|
|
18
|
+
*
|
|
19
|
+
* Movement risk is structural, not incidental: content arrives as squashed
|
|
20
|
+
* "Sync from internal source" commits from a publish bot, against a repository
|
|
21
|
+
* nobody outside Amazon can see, with no changelog and no version bump.
|
|
22
|
+
*/
|
|
23
|
+
|
|
24
|
+
/** The pinned upstream revision and the files chant's surface is built against. */
|
|
25
|
+
export const DOGWOOD_UPSTREAM = {
|
|
26
|
+
owner: "dogwood-policy",
|
|
27
|
+
repo: "dogwood",
|
|
28
|
+
/** Default-branch SHA verified in #1657 (committed 2026-08-06). */
|
|
29
|
+
revision: "5063bcc2d6d6cf5024d1b0498e6cc8ef52cbcf0c",
|
|
30
|
+
license: "Apache-2.0",
|
|
31
|
+
/** Git blob hashes at {@link revision} — the narrow content pin. */
|
|
32
|
+
contents: {
|
|
33
|
+
"dogwood-language/src/parser/grammar.pest": "adb0801303ee1b735585fe22a5d85175ecf6772c",
|
|
34
|
+
"dogwood-language/src/extension/temporal/grammar.pest": "dc31fe1634b5c37173f0a999619e9834069b7a22",
|
|
35
|
+
"dogwood-language/src/event_schema/grammar.pest": "bd3b064447dc0d09cc2f8eb28a7c657e746cc8a2",
|
|
36
|
+
"dogwood-language/configuration/default_macros.dw": "ecd67e5edcd566aa66b05ad51bc472afbaf22a7a",
|
|
37
|
+
"dogwood-cli/src/ops.rs": "2924c7eb0e569a745ff19c2d84cbba782fc56502",
|
|
38
|
+
"dogwood-cli/src/main.rs": "bfd68c9ff05883ed48e5738f658b5925542d817d",
|
|
39
|
+
"dogwood-cli/src/error.rs": "43f12afbbf25ad94e25130c83227eefd86f8d516",
|
|
40
|
+
},
|
|
41
|
+
} as const;
|
|
@@ -0,0 +1,124 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Temporal windows — the one thing dogwood's past-only operators cannot omit.
|
|
3
|
+
*
|
|
4
|
+
* `formerly`, `previous` and `since` all take a mandatory `within <n><unit>`
|
|
5
|
+
* clause; the unit is one of `s`, `m`, `h`, `d` and nothing else. Upstream's
|
|
6
|
+
* `temporal/grammar.pest` spells that out:
|
|
7
|
+
*
|
|
8
|
+
* ```
|
|
9
|
+
* within = { "within" ~ within_payload }
|
|
10
|
+
* within_payload = { param_ref | (integer ~ time_unit) }
|
|
11
|
+
* time_unit = { "s" | "m" | "h" | "d" }
|
|
12
|
+
* ```
|
|
13
|
+
*
|
|
14
|
+
* Typing the window as a value rather than as free text is what makes
|
|
15
|
+
* "a `formerly` without a window" unrepresentable in the builders (#1658's
|
|
16
|
+
* third lint wall) — you cannot call {@link formerly} without one.
|
|
17
|
+
*
|
|
18
|
+
* The `param_ref` arm (`within ?w`) is legal only inside a macro body; it is
|
|
19
|
+
* modelled by {@link MacroWindow} rather than by this type, so an ordinary
|
|
20
|
+
* policy cannot accidentally emit a sigil the expander would reject.
|
|
21
|
+
*/
|
|
22
|
+
|
|
23
|
+
/** The four units upstream's grammar admits. Anything else does not parse. */
|
|
24
|
+
export type TimeUnit = "s" | "m" | "h" | "d";
|
|
25
|
+
|
|
26
|
+
/** A `within` payload: a positive integer and one of the four units. */
|
|
27
|
+
export interface TemporalWindow {
|
|
28
|
+
readonly value: number;
|
|
29
|
+
readonly unit: TimeUnit;
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
/**
|
|
33
|
+
* A window as written in a `.dw` file (`"1h"`, `"30m"`, `"7d"`).
|
|
34
|
+
*
|
|
35
|
+
* The template-literal arm means `formerly("1h", …)` type-checks and
|
|
36
|
+
* `formerly("1w", …)` does not, without anyone writing a runtime check first.
|
|
37
|
+
*/
|
|
38
|
+
export type WindowLike = TemporalWindow | `${number}${TimeUnit}`;
|
|
39
|
+
|
|
40
|
+
/**
|
|
41
|
+
* `within ?w` — a window a macro takes as a parameter.
|
|
42
|
+
*
|
|
43
|
+
* The other arm of upstream's `within_payload`. Legal only inside a macro
|
|
44
|
+
* body; the expander resolves it against the call site's bare interval
|
|
45
|
+
* argument (`once(1h, …)`), which is why the call side has no `within`.
|
|
46
|
+
*/
|
|
47
|
+
export interface WindowParam {
|
|
48
|
+
readonly param: string;
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
/** Either a concrete window or a macro parameter standing in for one. */
|
|
52
|
+
export type WindowValue = TemporalWindow | WindowParam;
|
|
53
|
+
|
|
54
|
+
const WINDOW_TEXT = /^(\d+)([smhd])$/;
|
|
55
|
+
const PARAM_TEXT = /^\?[A-Za-z_][A-Za-z0-9_]*$/;
|
|
56
|
+
|
|
57
|
+
/** True when the payload is `?w` rather than `1h`. */
|
|
58
|
+
export function isWindowParam(value: WindowValue): value is WindowParam {
|
|
59
|
+
return "param" in value;
|
|
60
|
+
}
|
|
61
|
+
|
|
62
|
+
/**
|
|
63
|
+
* `within ?w` inside a macro body.
|
|
64
|
+
*
|
|
65
|
+
* Exported from `./macros.ts` too, which is where the sigil rules are
|
|
66
|
+
* documented and where a reader looking for it will be.
|
|
67
|
+
*/
|
|
68
|
+
export function windowParam(param: string): WindowParam {
|
|
69
|
+
if (!PARAM_TEXT.test(param)) {
|
|
70
|
+
throw new Error(`dogwood: a window parameter carries the "?" sigil — got "${param}"`);
|
|
71
|
+
}
|
|
72
|
+
return { param };
|
|
73
|
+
}
|
|
74
|
+
|
|
75
|
+
/** Seconds in one of each unit — the scale `max_window` comparisons run on. */
|
|
76
|
+
const UNIT_SECONDS: Record<TimeUnit, number> = { s: 1, m: 60, h: 3600, d: 86400 };
|
|
77
|
+
|
|
78
|
+
/** Narrow a {@link WindowLike} to the structured form, validating as it goes. */
|
|
79
|
+
export function window(value: WindowLike): TemporalWindow {
|
|
80
|
+
if (typeof value !== "string") {
|
|
81
|
+
if (!Number.isInteger(value.value) || value.value < 0) {
|
|
82
|
+
throw new Error(`dogwood: a temporal window must be a non-negative integer, got ${String(value.value)}`);
|
|
83
|
+
}
|
|
84
|
+
if (!(value.unit in UNIT_SECONDS)) {
|
|
85
|
+
throw new Error(`dogwood: a temporal window unit must be s, m, h or d, got "${String(value.unit)}"`);
|
|
86
|
+
}
|
|
87
|
+
return { value: value.value, unit: value.unit };
|
|
88
|
+
}
|
|
89
|
+
|
|
90
|
+
const match = WINDOW_TEXT.exec(value);
|
|
91
|
+
if (!match) {
|
|
92
|
+
throw new Error(`dogwood: "${value}" is not a temporal window — write an integer and one of s, m, h, d (for example "1h")`);
|
|
93
|
+
}
|
|
94
|
+
return { value: Number(match[1]), unit: match[2] as TimeUnit };
|
|
95
|
+
}
|
|
96
|
+
|
|
97
|
+
/** `1h` — the surface form, in a `within` clause or as a macro-call argument. */
|
|
98
|
+
export function renderWindow(value: WindowLike): string {
|
|
99
|
+
const w = window(value);
|
|
100
|
+
return `${w.value}${w.unit}`;
|
|
101
|
+
}
|
|
102
|
+
|
|
103
|
+
/** A `within` payload: `1h`, or the `?w` a macro body defers to its call site. */
|
|
104
|
+
export function renderWindowValue(value: WindowValue): string {
|
|
105
|
+
return isWindowParam(value) ? value.param : renderWindow(value);
|
|
106
|
+
}
|
|
107
|
+
|
|
108
|
+
/** Narrow a window argument, letting a `?w` parameter through unchanged. */
|
|
109
|
+
export function windowValue(value: WindowLike | WindowParam): WindowValue {
|
|
110
|
+
return typeof value === "object" && "param" in value ? windowParam(value.param) : window(value);
|
|
111
|
+
}
|
|
112
|
+
|
|
113
|
+
/** The window in seconds, so `90m` and `1h` are comparable to a `max_window`. */
|
|
114
|
+
export function windowSeconds(value: WindowLike): number {
|
|
115
|
+
const w = window(value);
|
|
116
|
+
return w.value * UNIT_SECONDS[w.unit];
|
|
117
|
+
}
|
|
118
|
+
|
|
119
|
+
/**
|
|
120
|
+
* Upstream's fallback cap. `ServiceSchema::defaults()` and an event schema
|
|
121
|
+
* with no `max_window` directive both look back at most 24 hours, per the
|
|
122
|
+
* comment atop `event_schema/grammar.pest` ("absent → a 24h default").
|
|
123
|
+
*/
|
|
124
|
+
export const DEFAULT_MAX_WINDOW: TemporalWindow = { value: 24, unit: "h" };
|
package/src/index.ts
CHANGED
|
@@ -54,6 +54,30 @@ export type { CedarGenerateOptions, CedarGenerateResult } from "./import/generat
|
|
|
54
54
|
export { CedarTemplateParser, CedarTemplateGenerator } from "./import/adapter";
|
|
55
55
|
export { detectTemplate } from "./detect";
|
|
56
56
|
|
|
57
|
+
// The dogwood temporal dialect (#1658) — typed temporal builders, the `.dw`
|
|
58
|
+
// serializer leg, `.dwschema` event schemas. Pre-release; see ./dogwood.
|
|
59
|
+
//
|
|
60
|
+
// Namespaced rather than flattened: the temporal sub-language's builders are
|
|
61
|
+
// named after its own grammar (`and`, `not`, `count`, `sum`, `str`, `field`,
|
|
62
|
+
// `record`, `window`), and spilling those into the package root would collide
|
|
63
|
+
// with a schema-generated entity class the moment someone declares one. The
|
|
64
|
+
// three entity classes are re-exported flat because that is what a policy file
|
|
65
|
+
// names.
|
|
66
|
+
export * as dogwood from "./dogwood/index";
|
|
67
|
+
export {
|
|
68
|
+
DOGWOOD_EVENT_SCHEMA_FILENAME,
|
|
69
|
+
DOGWOOD_EVENT_SCHEMA_TYPE,
|
|
70
|
+
DOGWOOD_MACRO_FILENAME,
|
|
71
|
+
DOGWOOD_MACRO_LIBRARY_TYPE,
|
|
72
|
+
DOGWOOD_POLICY_FILENAME,
|
|
73
|
+
DOGWOOD_POLICY_TYPE,
|
|
74
|
+
TemporalEventSchema,
|
|
75
|
+
TemporalMacroLibrary,
|
|
76
|
+
TemporalPolicy,
|
|
77
|
+
} from "./dogwood/policy";
|
|
78
|
+
export type { EventSchemaProps, MacroLibraryProps, TemporalPolicyProps } from "./dogwood/policy";
|
|
79
|
+
export { DOGWOOD_UPSTREAM } from "./dogwood/upstream";
|
|
80
|
+
|
|
57
81
|
// Lint rules
|
|
58
82
|
export { rules as cedarLintRules } from "./lint/rules";
|
|
59
83
|
|
|
@@ -127,4 +127,60 @@ export const cedarAuditCatalog: Record<string, RuleMeta> = {
|
|
|
127
127
|
'Name the actions the grant needs (action == Action::"read", or action in [ … ]) so it does not widen as the schema gains actions.',
|
|
128
128
|
{ category: "security" },
|
|
129
129
|
),
|
|
130
|
+
|
|
131
|
+
// ── The dogwood dialect (DWD) ──────────────────────────────────
|
|
132
|
+
//
|
|
133
|
+
// A second id family under the same lexicon, declared on the serializer as
|
|
134
|
+
// `extraRulePrefixes` (#1349's mechanism, the way k8s owns ARGO and FLUX).
|
|
135
|
+
// Dogwood is a dialect of Cedar rather than a peer language — a `.dw` file
|
|
136
|
+
// stripped of Cedar semantics is meaningless — so its checks ship here, and
|
|
137
|
+
// its ids are their own family so a reader can tell which surface bit them.
|
|
138
|
+
DWDC010: auditRule(
|
|
139
|
+
"DWDC010",
|
|
140
|
+
"merge-worthy",
|
|
141
|
+
"guidance",
|
|
142
|
+
"Dogwood temporal predicate names an undeclared event kind",
|
|
143
|
+
"Declare the event kind in the emitted .dwschema, or correct the predicate to a kind the schema derives — upstream rejects the policy set otherwise.",
|
|
144
|
+
{ category: "correctness" },
|
|
145
|
+
),
|
|
146
|
+
DWDC011: auditRule(
|
|
147
|
+
"DWDC011",
|
|
148
|
+
"merge-worthy",
|
|
149
|
+
"guidance",
|
|
150
|
+
"Dogwood temporal window exceeds the event schema's max_window",
|
|
151
|
+
"Shorten the window, or raise max_window in the event schema. With no schema emitted the cap is upstream's 24h default.",
|
|
152
|
+
{ category: "correctness" },
|
|
153
|
+
),
|
|
154
|
+
DWDC012: auditRule(
|
|
155
|
+
"DWDC012",
|
|
156
|
+
"merge-worthy",
|
|
157
|
+
"guidance",
|
|
158
|
+
"Dogwood formerly/previous/since is missing its window",
|
|
159
|
+
"Give the operator a `within <n><s|m|h|d>` window — all three past-only operators require one. The typed builders take it as an argument.",
|
|
160
|
+
{ category: "correctness" },
|
|
161
|
+
),
|
|
162
|
+
DWDE010: auditRule(
|
|
163
|
+
"DWDE010",
|
|
164
|
+
"merge-worthy",
|
|
165
|
+
"guidance",
|
|
166
|
+
"Dogwood policy set fails `dogwood validate`",
|
|
167
|
+
"Fix what upstream's frontend names — a macro, a temporal type error, or a Cedar body the action schema rejects. If the finding is the advisory, install the dogwood binary (or set cedar.dogwood.binary) so full .dw validation can run at all; without it only the DWDC walls apply.",
|
|
168
|
+
{ category: "correctness" },
|
|
169
|
+
),
|
|
170
|
+
DWDE011: auditRule(
|
|
171
|
+
"DWDE011",
|
|
172
|
+
"merge-worthy",
|
|
173
|
+
"guidance",
|
|
174
|
+
"Cedar lowered from a dogwood policy set fails Cedar validation",
|
|
175
|
+
"Fix the entity type, action, or attribute Cedar's validator names in the lowered body — the temporal clauses became context slots, so everything the finding points at is ordinary Cedar the policy wrote.",
|
|
176
|
+
{ category: "correctness" },
|
|
177
|
+
),
|
|
178
|
+
DWDS010: auditRule(
|
|
179
|
+
"DWDS010",
|
|
180
|
+
"report-only",
|
|
181
|
+
"guidance",
|
|
182
|
+
"Dogwood event schema pins nothing, widening every temporal predicate",
|
|
183
|
+
"Pin a field to the deciding request (pin callerPrincipal: principalType(A) = principal), or record that cross-principal correlation is the intent — supplying any event schema opts out of upstream's default pin.",
|
|
184
|
+
{ category: "security" },
|
|
185
|
+
),
|
|
130
186
|
};
|
|
@@ -0,0 +1,139 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Turning a build's emitted files into `dogwood` CLI invocations (#1659).
|
|
3
|
+
*
|
|
4
|
+
* The DWDE checks both need the same thing: which of the emitted `.dw` files
|
|
5
|
+
* is a policy set, which is a macro library, which `.cedarschema` backs
|
|
6
|
+
* `--policy-schema`, and which `.dwschema` backs `--event-schema`. Planning
|
|
7
|
+
* that once here keeps the two checks from disagreeing about what they ran —
|
|
8
|
+
* DWDE010 validates a bundle and DWDE011 lowers the *same* bundle, and a
|
|
9
|
+
* finding from one that names different inputs than the other would be a
|
|
10
|
+
* puzzle rather than a report.
|
|
11
|
+
*
|
|
12
|
+
* Excluded from check auto-discovery by the "helper" filename filter.
|
|
13
|
+
*/
|
|
14
|
+
import type { PostSynthContext } from "@intentius/chant/lint/post-synth";
|
|
15
|
+
import { blankComments, dogwoodPolicyFiles, dogwoodSchemaFiles, type DogwoodArtifact } from "../../dogwood/scan";
|
|
16
|
+
import type { DogwoodBundle } from "../../dogwood/cli";
|
|
17
|
+
|
|
18
|
+
/** One planned invocation: the files, and the names to blame in a finding. */
|
|
19
|
+
export interface PreparedBundle {
|
|
20
|
+
lexicon: string;
|
|
21
|
+
/** The `.dw` policy set filename. */
|
|
22
|
+
source: string;
|
|
23
|
+
/** The `.cedarschema` filename passed as `--policy-schema`. */
|
|
24
|
+
policySchemaSource: string;
|
|
25
|
+
/** The `.dwschema` filename passed as `--event-schema`, when one was emitted. */
|
|
26
|
+
eventSchemaSource?: string;
|
|
27
|
+
bundle: DogwoodBundle;
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
/** What the CLI-gated checks should do with this build. */
|
|
31
|
+
export interface DogwoodRunPlan {
|
|
32
|
+
/** True when the build emitted a `.dw` policy set at all. */
|
|
33
|
+
hasPolicies: boolean;
|
|
34
|
+
/** Every invocation to make. Empty when {@link blocked} is set. */
|
|
35
|
+
bundles: PreparedBundle[];
|
|
36
|
+
/** Why nothing can be run, when policies exist but a required input does not. */
|
|
37
|
+
blocked?: string;
|
|
38
|
+
/** The lexicon the `.dw` output came from, for a finding that names no file. */
|
|
39
|
+
lexicon?: string;
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
/**
|
|
43
|
+
* A `.dw` file holding only `def` declarations is a macro library, not a
|
|
44
|
+
* policy set.
|
|
45
|
+
*
|
|
46
|
+
* By structure rather than by filename: the serializer's default is
|
|
47
|
+
* `macros.dw`, but `MacroLibraryProps.filename` overrides it, and `chant
|
|
48
|
+
* audit` runs over trees chant never wrote. A policy set is a file with a
|
|
49
|
+
* `permit` or `forbid` head in it — upstream's policy grammar has no third
|
|
50
|
+
* effect — and a set with inlined macros still has one.
|
|
51
|
+
*/
|
|
52
|
+
export function looksLikeMacroLibrary(text: string): boolean {
|
|
53
|
+
return !/\b(permit|forbid)\s*\(/.test(blankComments(text));
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
function byName(a: DogwoodArtifact, b: DogwoodArtifact): number {
|
|
57
|
+
return a.source.localeCompare(b.source);
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
/**
|
|
61
|
+
* The Cedar action schema the CLI needs, as text.
|
|
62
|
+
*
|
|
63
|
+
* `--policy-schema` reads a human-readable `.cedarschema`; a `.cedarschema.json`
|
|
64
|
+
* is not a substitute, so it is not offered as one. Sorted by filename so a
|
|
65
|
+
* build emitting several picks the same one on every run.
|
|
66
|
+
*/
|
|
67
|
+
function findPolicySchema(ctx: PostSynthContext): DogwoodArtifact | undefined {
|
|
68
|
+
const found: DogwoodArtifact[] = [];
|
|
69
|
+
for (const [lexicon, output] of ctx.outputs) {
|
|
70
|
+
if (typeof output === "string") continue;
|
|
71
|
+
for (const [source, text] of Object.entries(output.files ?? {})) {
|
|
72
|
+
if (typeof text !== "string") continue;
|
|
73
|
+
if (!source.endsWith(".cedarschema")) continue;
|
|
74
|
+
found.push({ lexicon, source, text });
|
|
75
|
+
}
|
|
76
|
+
}
|
|
77
|
+
return found.sort(byName)[0];
|
|
78
|
+
}
|
|
79
|
+
|
|
80
|
+
/**
|
|
81
|
+
* Plan every `dogwood` invocation this build calls for.
|
|
82
|
+
*
|
|
83
|
+
* One bundle per policy set per emitted event schema. A build with several
|
|
84
|
+
* `.dwschema` files means several services, and `--event-schema` takes exactly
|
|
85
|
+
* one file, so the set is run against each and every finding names the schema
|
|
86
|
+
* it was found under — the alternative is picking one silently and reporting
|
|
87
|
+
* against a schema the reader did not choose. Macro libraries do combine: a
|
|
88
|
+
* library is a sequence of `def`s, so several concatenate into the one file
|
|
89
|
+
* `--macros` accepts.
|
|
90
|
+
*/
|
|
91
|
+
export function planDogwoodRuns(ctx: PostSynthContext): DogwoodRunPlan {
|
|
92
|
+
const files = dogwoodPolicyFiles(ctx).sort(byName);
|
|
93
|
+
const policySets = files.filter((f) => !looksLikeMacroLibrary(f.text));
|
|
94
|
+
if (policySets.length === 0) return { hasPolicies: false, bundles: [] };
|
|
95
|
+
|
|
96
|
+
const lexicon = policySets[0].lexicon;
|
|
97
|
+
const libraries = files.filter((f) => looksLikeMacroLibrary(f.text));
|
|
98
|
+
const macros = libraries.length > 0 ? libraries.map((f) => f.text).join("\n") : undefined;
|
|
99
|
+
|
|
100
|
+
const policySchema = findPolicySchema(ctx);
|
|
101
|
+
if (!policySchema) {
|
|
102
|
+
return {
|
|
103
|
+
hasPolicies: true,
|
|
104
|
+
bundles: [],
|
|
105
|
+
lexicon,
|
|
106
|
+
blocked:
|
|
107
|
+
"no Cedar action schema (.cedarschema) was emitted beside them, and `dogwood validate` requires one via --policy-schema",
|
|
108
|
+
};
|
|
109
|
+
}
|
|
110
|
+
|
|
111
|
+
const eventSchemas = dogwoodSchemaFiles(ctx).sort(byName);
|
|
112
|
+
const bundles: PreparedBundle[] = [];
|
|
113
|
+
|
|
114
|
+
for (const set of policySets) {
|
|
115
|
+
for (const events of eventSchemas.length > 0 ? eventSchemas : [undefined]) {
|
|
116
|
+
bundles.push({
|
|
117
|
+
lexicon: set.lexicon,
|
|
118
|
+
source: set.source,
|
|
119
|
+
policySchemaSource: policySchema.source,
|
|
120
|
+
...(events ? { eventSchemaSource: events.source } : {}),
|
|
121
|
+
bundle: {
|
|
122
|
+
policies: set.text,
|
|
123
|
+
policySchema: policySchema.text,
|
|
124
|
+
...(events ? { eventSchema: events.text } : {}),
|
|
125
|
+
...(macros !== undefined ? { macros } : {}),
|
|
126
|
+
},
|
|
127
|
+
});
|
|
128
|
+
}
|
|
129
|
+
}
|
|
130
|
+
|
|
131
|
+
return { hasPolicies: true, bundles, lexicon };
|
|
132
|
+
}
|
|
133
|
+
|
|
134
|
+
/** How a finding names the inputs a run was made with. */
|
|
135
|
+
export function describeBundle(prepared: PreparedBundle): string {
|
|
136
|
+
const parts = [`"${prepared.source}"`, `against "${prepared.policySchemaSource}"`];
|
|
137
|
+
if (prepared.eventSchemaSource) parts.push(`with "${prepared.eventSchemaSource}"`);
|
|
138
|
+
return parts.join(" ");
|
|
139
|
+
}
|