@intentius/chant-lexicon-cedar 0.124.30 → 0.124.32
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/agentcore/embed.js +250 -0
- package/dist/agentcore/enforcement.js +81 -0
- package/dist/agentcore/scan.js +93 -0
- package/dist/avp/ambient.js +100 -0
- package/dist/avp/client.js +318 -0
- package/dist/avp/describe-resources.js +144 -0
- package/dist/avp/embed.js +125 -0
- package/dist/avp/live-export.js +151 -0
- package/dist/avp/ownership.js +144 -0
- package/dist/avp/statement.js +45 -0
- package/dist/avp/store.js +105 -0
- package/dist/avp/testdata/mock-transport.js +100 -0
- package/dist/codegen/docs-cli.js +6 -0
- package/dist/codegen/docs.js +133 -0
- package/dist/codegen/emit.js +331 -0
- package/dist/codegen/generate-cli.js +18 -0
- package/dist/codegen/generate.js +122 -0
- package/dist/codegen/naming.js +90 -0
- package/dist/codegen/package.js +33 -0
- package/dist/commands.js +86 -0
- package/dist/composites/deny-by-default-set.js +51 -0
- package/dist/composites/index.js +9 -0
- package/dist/composites/owner-can-manage.js +48 -0
- package/dist/config.js +159 -0
- package/dist/coverage.js +112 -0
- package/dist/detect.js +61 -0
- package/dist/dogwood/cli.js +477 -0
- package/dist/dogwood/event-schema.js +219 -0
- package/dist/dogwood/index.js +32 -0
- package/dist/dogwood/macros.js +175 -0
- package/dist/dogwood/policy.js +58 -0
- package/dist/dogwood/replay-activity.js +313 -0
- package/dist/dogwood/replay-op.js +134 -0
- package/dist/dogwood/scan.js +234 -0
- package/dist/dogwood/serialize.js +199 -0
- package/dist/dogwood/temporal.js +354 -0
- package/dist/dogwood/trace.js +269 -0
- package/dist/dogwood/upstream.js +40 -0
- package/dist/dogwood/window.js +82 -0
- package/dist/gate-policy-pack.js +189 -0
- package/dist/gate-policy.js +168 -0
- package/dist/gate-schema.js +122 -0
- package/dist/generated/index.js +94 -0
- package/dist/generated/runtime.js +2 -0
- package/dist/import/adapter.js +54 -0
- package/dist/import/clause-text.js +166 -0
- package/dist/import/generator.js +178 -0
- package/dist/import/parser.js +273 -0
- package/dist/index.js +79 -0
- package/dist/init-templates.js +330 -0
- package/dist/integrity.json +2 -2
- package/dist/lint/audit-catalog.js +65 -0
- package/dist/lint/audit-lineage.js +23 -0
- package/dist/lint/post-synth/cedar-helpers.js +123 -0
- package/dist/lint/post-synth/cedc010.js +59 -0
- package/dist/lint/post-synth/cedc011.js +29 -0
- package/dist/lint/post-synth/cedc012.js +34 -0
- package/dist/lint/post-synth/cedc013.js +45 -0
- package/dist/lint/post-synth/cedc014.js +54 -0
- package/dist/lint/post-synth/cede010.js +51 -0
- package/dist/lint/post-synth/cede011.js +36 -0
- package/dist/lint/post-synth/ceds010.js +31 -0
- package/dist/lint/post-synth/ceds011.js +22 -0
- package/dist/lint/post-synth/ceds012.js +31 -0
- package/dist/lint/post-synth/dogwood-helpers.js +94 -0
- package/dist/lint/post-synth/dwdc010.js +38 -0
- package/dist/lint/post-synth/dwdc011.js +34 -0
- package/dist/lint/post-synth/dwdc012.js +24 -0
- package/dist/lint/post-synth/dwdc013.js +29 -0
- package/dist/lint/post-synth/dwde010.js +84 -0
- package/dist/lint/post-synth/dwde011.js +67 -0
- package/dist/lint/post-synth/dwds010.js +21 -0
- package/dist/lint/post-synth/index.js +36 -0
- package/dist/lint/post-synth/wasm-helpers.js +234 -0
- package/dist/lint/rules/index.js +9 -0
- package/dist/lint/rules/policy-shape.js +114 -0
- package/dist/lsp/completions.js +52 -0
- package/dist/lsp/hover.js +56 -0
- package/dist/lsp/registry.d.ts.map +1 -1
- package/dist/lsp/registry.js +51 -0
- package/dist/manifest.json +1 -1
- package/dist/mcp/index.js +82 -0
- package/dist/mcp/policy-coverage.js +134 -0
- package/dist/op/activities/index.js +16 -0
- package/dist/package-cli.js +18 -0
- package/dist/plugin.js +304 -0
- package/dist/policy-text.js +117 -0
- package/dist/schema-artifact.js +66 -0
- package/dist/serializer.js +203 -0
- package/dist/spec/fetch.js +96 -0
- package/dist/spec/parse.js +164 -0
- package/dist/spec/pin.js +170 -0
- package/dist/spec/wasm.js +194 -0
- package/dist/validate-cli.js +7 -0
- package/dist/validate.js +74 -0
- package/package.json +7 -7
- package/src/codegen/package.ts +2 -2
- package/src/coverage.ts +2 -2
- package/src/import/generator.ts +2 -2
- package/src/lsp/registry.ts +2 -1
- package/src/spec/fetch.ts +2 -2
package/dist/index.js
ADDED
|
@@ -0,0 +1,79 @@
|
|
|
1
|
+
// Plugin
|
|
2
|
+
export { cedarPlugin } from "./plugin.js";
|
|
3
|
+
// Serializer
|
|
4
|
+
export { cedarSerializer, policyIdFromLogicalName, resolvePolicyId, cedarPolicyRecords } from "./serializer.js";
|
|
5
|
+
export { CEDAR_POLICY_TYPE, CEDAR_JSON_FILENAME } from "./serializer.js";
|
|
6
|
+
// Gate approval policy (chant#2508) — a cedar policy set as a gate's `approval.policy`.
|
|
7
|
+
export { gatePolicy, evaluateGatePolicy, GATE_HUMAN_TYPE, GATE_AGENT_TYPE, GATE_ROLE_TYPE, GATE_RESOURCE_TYPE, PASS_GATE_ACTION, } from "./gate-policy.js";
|
|
8
|
+
// Gate policy over the plan (chant#3182): the request's Cedar schema, with the
|
|
9
|
+
// plan summary core puts in `context.plan`, and a starter policy pack over it.
|
|
10
|
+
export { GATE_CEDAR_SCHEMA, GATE_PLAN_SUMMARY_CEDAR_TYPES, gateCedarSchema, validateGatePolicy, } from "./gate-schema.js";
|
|
11
|
+
export { STATEFUL_TYPES, allowRegions, capDeletes, capReplacements, permitLowRiskAgents, permitTagOnlyAgents, protectStateful, requireCreateTags, starterGatePolicies, } from "./gate-policy-pack.js";
|
|
12
|
+
// AVP embedding (#1652) — the typed statement an `AWS::VerifiedPermissions::Policy`
|
|
13
|
+
// carries, without a dependency on the aws lexicon. See src/avp/embed.ts.
|
|
14
|
+
export { avpStatement, avpStatementJSON, avpPolicyDefinition, avpPolicyResource, avpPolicySet, } from "./avp/embed.js";
|
|
15
|
+
// The AVP ownership channel (#1652, chant #1348). Design record: src/avp/OWNERSHIP.md.
|
|
16
|
+
export { AVP_OWNERSHIP_KEYS, AVP_POLICY_ID_KEY, AVP_DESCRIPTION_MAX, encodeOwnershipDescription, decodeOwnershipDescription, ownershipFromDescription, ownershipFromStoreTags, descriptionIsOwned, storeOwnershipTags, } from "./avp/ownership.js";
|
|
17
|
+
// AVP lifecycle readers (#1652).
|
|
18
|
+
export { describeAvpResources } from "./avp/describe-resources.js";
|
|
19
|
+
export { observeAvpAmbient, AVP_AMBIENT_KINDS } from "./avp/ambient.js";
|
|
20
|
+
export { exportAvpResources, policyToResourceIR } from "./avp/live-export.js";
|
|
21
|
+
export { resolvePolicyStoreId, AVP_POLICY_STORE_ENV } from "./avp/store.js";
|
|
22
|
+
export { policySetJSON, escapeCedarString } from "./serializer.js";
|
|
23
|
+
// Import / reconcile (#1653) — `.cedar` text and the JSON policy-set envelope
|
|
24
|
+
// in, authoring TypeScript out.
|
|
25
|
+
export { CedarParser, isPolicySetEnvelope } from "./import/parser.js";
|
|
26
|
+
export { CedarGenerator, loadActionConstants, sanitizeName, CEDAR_PACKAGE } from "./import/generator.js";
|
|
27
|
+
export { CedarTemplateParser, CedarTemplateGenerator } from "./import/adapter.js";
|
|
28
|
+
export { detectTemplate } from "./detect.js";
|
|
29
|
+
// AgentCore embedding (#1660) — the typed statement an
|
|
30
|
+
// `AWS::BedrockAgentCore::Policy` carries on either arm of its Definition
|
|
31
|
+
// union, and the EnforcementMode dial that stages the rollout. Same
|
|
32
|
+
// no-dependency rule as the AVP seam above. See src/agentcore/embed.ts.
|
|
33
|
+
export { agentCoreStatement, agentCorePolicyDefinition, agentCorePolicyResource, agentCorePolicySet, agentCoreStagedPolicy, agentCorePolicyName, AGENTCORE_NAME_PATTERN, AGENTCORE_STATEMENT_MIN, AGENTCORE_STATEMENT_MAX, } from "./agentcore/embed.js";
|
|
34
|
+
export { AGENTCORE_ENFORCEMENT, describeStage, enforcementMode, enforcementStage, isEnforcing, } from "./agentcore/enforcement.js";
|
|
35
|
+
export { embeddedAgentCoreDefinitions, embeddedAgentCorePolicyStatements } from "./agentcore/scan.js";
|
|
36
|
+
// The dogwood temporal dialect (#1658) — typed temporal builders, the `.dw`
|
|
37
|
+
// serializer leg, `.dwschema` event schemas. Pre-release; see ./dogwood.
|
|
38
|
+
//
|
|
39
|
+
// Namespaced rather than flattened: the temporal sub-language's builders are
|
|
40
|
+
// named after its own grammar (`and`, `not`, `count`, `sum`, `str`, `field`,
|
|
41
|
+
// `record`, `window`), and spilling those into the package root would collide
|
|
42
|
+
// with a schema-generated entity class the moment someone declares one. The
|
|
43
|
+
// three entity classes are re-exported flat because that is what a policy file
|
|
44
|
+
// names.
|
|
45
|
+
export * as dogwood from "./dogwood/index.js";
|
|
46
|
+
export { DOGWOOD_EVENT_SCHEMA_FILENAME, DOGWOOD_EVENT_SCHEMA_TYPE, DOGWOOD_MACRO_FILENAME, DOGWOOD_MACRO_LIBRARY_TYPE, DOGWOOD_POLICY_FILENAME, DOGWOOD_POLICY_TYPE, TemporalEventSchema, TemporalMacroLibrary, TemporalPolicy, } from "./dogwood/policy.js";
|
|
47
|
+
export { DOGWOOD_UPSTREAM } from "./dogwood/upstream.js";
|
|
48
|
+
// The replay Op composite and its typed step builders (#1661). Flat, like
|
|
49
|
+
// fly's `flyDeploy`: an Op factory is what a project's `ops/*.op.ts` names,
|
|
50
|
+
// and it depends on no lexicon but this one — see ./dogwood/replay-op.ts for
|
|
51
|
+
// why the composite ships from cedar.
|
|
52
|
+
export { DEFAULT_REPLAY_REPORT_PATH, PolicyReplayOp, dogwoodReplayReportStep, dogwoodReplayStep, } from "./dogwood/replay-op.js";
|
|
53
|
+
// Lint rules
|
|
54
|
+
export { rules as cedarLintRules } from "./lint/rules/index.js";
|
|
55
|
+
// Packaging pipeline (for external tooling)
|
|
56
|
+
export { packageLexicon } from "./codegen/package.js";
|
|
57
|
+
// The `cedar` config namespace (#1344). Importing this package is what brings
|
|
58
|
+
// the key into ChantConfig, so a `chant.config.ts` that sets it compiles.
|
|
59
|
+
export { cedarConfigSchema, loadCedarConfig, loadCedarProject, CEDAR_DEFAULT_SCHEMA_PATH, CEDAR_DEFAULT_OUT_DIR } from "./config.js";
|
|
60
|
+
// Where `chant cedar generate` writes for a project (#1696).
|
|
61
|
+
export { resolveGeneratedDir } from "./codegen/generate.js";
|
|
62
|
+
// The project schema as a build artifact (#1697): authored by hand as
|
|
63
|
+
// `new Schema({ text })`, or contributed automatically from `cedar.schema`.
|
|
64
|
+
export { Schema, CEDAR_SCHEMA_TYPE, CEDAR_SCHEMA_FILENAME } from "./schema-artifact.js";
|
|
65
|
+
// Upstream pin — the cedar-wasm package version and the Cedar language
|
|
66
|
+
// version it implements (#1650).
|
|
67
|
+
export { CEDAR_WASM_VERSION, CEDAR_LANG_VERSION } from "./spec/pin.js";
|
|
68
|
+
// Schema coverage — is every schema declaration reachable from the generated
|
|
69
|
+
// artifacts? Policy coverage below asks the other half.
|
|
70
|
+
export { computeCedarCoverage, formatCedarCoverage, analyzeCedarCoverage } from "./coverage.js";
|
|
71
|
+
// Policy coverage — which schema declarations the built policy set can apply
|
|
72
|
+
// to. Backs the `cedar:coverage` MCP tool.
|
|
73
|
+
export { computePolicyCoverage, formatPolicyCoverage } from "./mcp/policy-coverage.js";
|
|
74
|
+
// Composites — the repeated policy shapes Cedar itself has nowhere to put.
|
|
75
|
+
export * from "./composites/index.js";
|
|
76
|
+
// `chant init --lexicon cedar --template <name>` scaffolds.
|
|
77
|
+
export { cedarInitTemplates, CEDAR_INIT_TEMPLATES } from "./init-templates.js";
|
|
78
|
+
// Generated entity types, actions and the Policy authoring class (#1650).
|
|
79
|
+
export * from "./generated/index.js";
|
|
@@ -0,0 +1,330 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* `chant init --lexicon cedar --template <name>` scaffolds.
|
|
3
|
+
*
|
|
4
|
+
* Every template ships a `schema.cedarschema` at the project root. That is not
|
|
5
|
+
* decoration: cedar's codegen input is the project's own schema (epic #1645),
|
|
6
|
+
* and a scaffold without one generates against the bundled default — a project
|
|
7
|
+
* whose typed classes describe somebody else's entity model.
|
|
8
|
+
*
|
|
9
|
+
* The filename is fixed rather than configured because `schema.cedarschema` in
|
|
10
|
+
* the project root is step 2 of the resolution order in `spec/fetch.ts`, so a
|
|
11
|
+
* scaffolded project needs no `cedar.schema` key at all. It also has to be
|
|
12
|
+
* fixed: core writes `chant.config.ts` before a plugin's root files and
|
|
13
|
+
* `writeIfNotExists` keeps the first one, so a template that shipped its own
|
|
14
|
+
* config would be silently discarded. The README each template ships is where
|
|
15
|
+
* `requireProjectSchema` gets recommended, which is the honest place for advice
|
|
16
|
+
* a scaffold cannot enforce.
|
|
17
|
+
*
|
|
18
|
+
* The policy files use `entityType`-string scopes (`{ is: "App::Document" }`)
|
|
19
|
+
* and generated action constants, so a scaffolded project compiles the moment
|
|
20
|
+
* `chant cedar generate` has run against its own schema (#1696).
|
|
21
|
+
*/
|
|
22
|
+
/** Filename every template writes its schema to — see spec/fetch.ts step 2. */
|
|
23
|
+
const SCHEMA_FILE = "schema.cedarschema";
|
|
24
|
+
function readme(title, body) {
|
|
25
|
+
return `# ${title}
|
|
26
|
+
|
|
27
|
+
\`\`\`bash
|
|
28
|
+
npm install
|
|
29
|
+
npx chant cedar generate # schema.cedarschema -> src/generated/cedar/
|
|
30
|
+
npx chant build # emits .cedar text, policies.cedar.json, and the schema
|
|
31
|
+
\`\`\`
|
|
32
|
+
|
|
33
|
+
Generate first. The classes and action constants \`src/policies.ts\` imports
|
|
34
|
+
from \`./generated/cedar\` do not exist until \`chant cedar generate\` has read
|
|
35
|
+
\`${SCHEMA_FILE}\`. The output is yours: it lives in this project, not in
|
|
36
|
+
\`node_modules\`, so commit it (or regenerate it in CI before \`chant build\`)
|
|
37
|
+
and re-run generate whenever the schema changes. \`cedar.outDir\` in
|
|
38
|
+
\`chant.config.ts\` moves it.
|
|
39
|
+
|
|
40
|
+
${body}
|
|
41
|
+
|
|
42
|
+
## Before this is real
|
|
43
|
+
|
|
44
|
+
Add \`requireProjectSchema\` once you are past the scaffold:
|
|
45
|
+
|
|
46
|
+
\`\`\`typescript
|
|
47
|
+
// chant.config.ts
|
|
48
|
+
export default {
|
|
49
|
+
lexicons: ["cedar"],
|
|
50
|
+
cedar: {
|
|
51
|
+
validation: { mode: "strict", requireProjectSchema: true },
|
|
52
|
+
},
|
|
53
|
+
} satisfies ChantConfig;
|
|
54
|
+
\`\`\`
|
|
55
|
+
|
|
56
|
+
Without it, deleting or renaming \`${SCHEMA_FILE}\` falls back to the schema
|
|
57
|
+
bundled with the lexicon and the build still succeeds — against an entity model
|
|
58
|
+
this project never declared.
|
|
59
|
+
|
|
60
|
+
## What comes out
|
|
61
|
+
|
|
62
|
+
| File | Who reads it |
|
|
63
|
+
|------|--------------|
|
|
64
|
+
| \`dist/*.cedar\` | Every Cedar evaluator — AVP, cedar-agent, an embedded cedar-wasm |
|
|
65
|
+
| \`dist/policies.cedar.json\` | The Cedar JSON policy format; also the parse source for import |
|
|
66
|
+
| \`dist/${SCHEMA_FILE}\` | Your schema, beside the policies, so the build validates against it and a bundle can carry it |
|
|
67
|
+
`;
|
|
68
|
+
}
|
|
69
|
+
// ── default ───────────────────────────────────────────────────────
|
|
70
|
+
const DEFAULT_SCHEMA = `// The entity model your policies are typed against.
|
|
71
|
+
//
|
|
72
|
+
// \`chant cedar generate\` turns every declaration below into a
|
|
73
|
+
// TypeScript class or constant, so a renamed entity type becomes a
|
|
74
|
+
// compiler-guided refactor rather than a runtime validation failure.
|
|
75
|
+
|
|
76
|
+
namespace App {
|
|
77
|
+
entity User = {
|
|
78
|
+
"email": String,
|
|
79
|
+
"department": String,
|
|
80
|
+
};
|
|
81
|
+
|
|
82
|
+
entity Document = {
|
|
83
|
+
"title": String,
|
|
84
|
+
"owner": User,
|
|
85
|
+
"classification": String,
|
|
86
|
+
};
|
|
87
|
+
|
|
88
|
+
action read, write appliesTo {
|
|
89
|
+
principal: [User],
|
|
90
|
+
resource: [Document],
|
|
91
|
+
context: {
|
|
92
|
+
"mfa": Bool,
|
|
93
|
+
}
|
|
94
|
+
};
|
|
95
|
+
}
|
|
96
|
+
`;
|
|
97
|
+
const DEFAULT_POLICIES = `import { Policy, ReadAction, WriteAction } from "./generated/cedar";
|
|
98
|
+
|
|
99
|
+
/**
|
|
100
|
+
* A permit and a forbid — the smallest policy set worth deploying.
|
|
101
|
+
*
|
|
102
|
+
* Cedar is default-deny, so the permit is what grants anything at all. The
|
|
103
|
+
* forbid is not the absence of a grant: it beats every permit in the set
|
|
104
|
+
* unconditionally, which is what makes it survive a wider grant added later.
|
|
105
|
+
*/
|
|
106
|
+
|
|
107
|
+
/** Owners read and write their own documents. */
|
|
108
|
+
export const ownerAccess = new Policy({
|
|
109
|
+
effect: "permit",
|
|
110
|
+
principal: { is: "App::User" },
|
|
111
|
+
action: { in: [ReadAction, WriteAction] },
|
|
112
|
+
resource: { is: "App::Document" },
|
|
113
|
+
when: ["resource.owner == principal"],
|
|
114
|
+
});
|
|
115
|
+
|
|
116
|
+
/** Nothing confidential moves without MFA, owner or not. */
|
|
117
|
+
export const requireMfaOnConfidential = new Policy({
|
|
118
|
+
effect: "forbid",
|
|
119
|
+
principal: { is: "App::User" },
|
|
120
|
+
resource: { is: "App::Document" },
|
|
121
|
+
when: ['resource.classification == "confidential"'],
|
|
122
|
+
unless: ["context.mfa == true"],
|
|
123
|
+
});
|
|
124
|
+
`;
|
|
125
|
+
// ── avp-embedding ─────────────────────────────────────────────────
|
|
126
|
+
const AVP_SCHEMA = `// The entity model behind an Amazon Verified Permissions policy store.
|
|
127
|
+
//
|
|
128
|
+
// AVP is one deployment vehicle for Cedar; the schema and the policies are
|
|
129
|
+
// ordinary Cedar either way, and the same emitted \`.cedar\` file is read by any
|
|
130
|
+
// other evaluator with no AWS involved.
|
|
131
|
+
|
|
132
|
+
namespace Store {
|
|
133
|
+
entity Tenant = {
|
|
134
|
+
"name": String,
|
|
135
|
+
};
|
|
136
|
+
|
|
137
|
+
entity User in [Tenant] = {
|
|
138
|
+
"email": String,
|
|
139
|
+
"tenant": Tenant,
|
|
140
|
+
"admin": Bool,
|
|
141
|
+
};
|
|
142
|
+
|
|
143
|
+
entity Record in [Tenant] = {
|
|
144
|
+
"tenant": Tenant,
|
|
145
|
+
"owner": User,
|
|
146
|
+
"sensitive": Bool,
|
|
147
|
+
};
|
|
148
|
+
|
|
149
|
+
action view, edit appliesTo {
|
|
150
|
+
principal: [User],
|
|
151
|
+
resource: [Record],
|
|
152
|
+
context: {
|
|
153
|
+
"mfa": Bool,
|
|
154
|
+
}
|
|
155
|
+
};
|
|
156
|
+
}
|
|
157
|
+
`;
|
|
158
|
+
const AVP_POLICIES = `import { Policy, EditAction, ViewAction } from "./generated/cedar";
|
|
159
|
+
|
|
160
|
+
/**
|
|
161
|
+
* Policies destined for an AVP policy store.
|
|
162
|
+
*
|
|
163
|
+
* The statement each of these serializes to is what goes in a
|
|
164
|
+
* \`AWS::VerifiedPermissions::Policy\`'s \`definition.static.statement\` — the
|
|
165
|
+
* same bytes as the emitted \`.cedar\` file, so the deployed policy and the
|
|
166
|
+
* reviewed file cannot disagree.
|
|
167
|
+
*
|
|
168
|
+
* The typed handoff (a VerifiedPermissionsPolicy whose statement accepts a
|
|
169
|
+
* Policy value directly, plus the policy-store lifecycle and the ownership
|
|
170
|
+
* channel) is landing with INTENTIUS/chant#1652. Until it does, emit the
|
|
171
|
+
* \`.cedar\` file and reference its text; do not hand-type a statement string.
|
|
172
|
+
*/
|
|
173
|
+
|
|
174
|
+
/** Tenancy: nobody touches a record outside their own tenant. */
|
|
175
|
+
export const tenantIsolation = new Policy({
|
|
176
|
+
effect: "forbid",
|
|
177
|
+
principal: { is: "Store::User" },
|
|
178
|
+
resource: { is: "Store::Record" },
|
|
179
|
+
unless: ["resource.tenant == principal.tenant"],
|
|
180
|
+
});
|
|
181
|
+
|
|
182
|
+
/** Members view records in their tenant. */
|
|
183
|
+
export const tenantView = new Policy({
|
|
184
|
+
effect: "permit",
|
|
185
|
+
principal: { is: "Store::User" },
|
|
186
|
+
action: { eq: ViewAction },
|
|
187
|
+
resource: { is: "Store::Record" },
|
|
188
|
+
when: ["resource.tenant == principal.tenant"],
|
|
189
|
+
});
|
|
190
|
+
|
|
191
|
+
/** Owners and tenant admins edit; sensitive records also demand MFA. */
|
|
192
|
+
export const ownerEdit = new Policy({
|
|
193
|
+
effect: "permit",
|
|
194
|
+
principal: { is: "Store::User" },
|
|
195
|
+
action: { eq: EditAction },
|
|
196
|
+
resource: { is: "Store::Record" },
|
|
197
|
+
when: ["resource.owner == principal || principal.admin == true"],
|
|
198
|
+
unless: ["resource.sensitive == true && context.mfa == false"],
|
|
199
|
+
});
|
|
200
|
+
`;
|
|
201
|
+
// ── gateway-policy-set ────────────────────────────────────────────
|
|
202
|
+
const GATEWAY_SCHEMA = `// An API gateway's authorization model.
|
|
203
|
+
//
|
|
204
|
+
// The principal is a caller identity, the resource is a route, and the action
|
|
205
|
+
// is an HTTP method. Modelling routes as entities rather than strings is what
|
|
206
|
+
// lets a policy say \`resource.public == true\` instead of matching paths.
|
|
207
|
+
|
|
208
|
+
namespace Gateway {
|
|
209
|
+
entity Scope = {
|
|
210
|
+
"name": String,
|
|
211
|
+
};
|
|
212
|
+
|
|
213
|
+
entity Caller in [Scope] = {
|
|
214
|
+
"clientId": String,
|
|
215
|
+
"internal": Bool,
|
|
216
|
+
};
|
|
217
|
+
|
|
218
|
+
entity Route = {
|
|
219
|
+
"path": String,
|
|
220
|
+
"public": Bool,
|
|
221
|
+
"deprecated": Bool,
|
|
222
|
+
};
|
|
223
|
+
|
|
224
|
+
action get, post, delete appliesTo {
|
|
225
|
+
principal: [Caller],
|
|
226
|
+
resource: [Route],
|
|
227
|
+
context: {
|
|
228
|
+
"sourceIp": String,
|
|
229
|
+
"authenticated": Bool,
|
|
230
|
+
}
|
|
231
|
+
};
|
|
232
|
+
}
|
|
233
|
+
`;
|
|
234
|
+
const GATEWAY_POLICIES = `import { DenyByDefaultSet } from "@intentius/chant-lexicon-cedar";
|
|
235
|
+
import { DeleteAction, GetAction, Policy, PostAction } from "./generated/cedar";
|
|
236
|
+
|
|
237
|
+
/**
|
|
238
|
+
* A gateway policy set with an explicit deny floor.
|
|
239
|
+
*
|
|
240
|
+
* \`DenyByDefaultSet\` returns the forbid and the permits it governs from one
|
|
241
|
+
* call, so the floor cannot be deleted in a refactor while the grants survive.
|
|
242
|
+
* A forbid beats every permit unconditionally — that is the whole reason to
|
|
243
|
+
* write one under a default-deny engine.
|
|
244
|
+
*/
|
|
245
|
+
|
|
246
|
+
/** Anonymous callers reach public routes, read-only. */
|
|
247
|
+
const publicRead = new Policy({
|
|
248
|
+
effect: "permit",
|
|
249
|
+
principal: { is: "Gateway::Caller" },
|
|
250
|
+
action: { eq: GetAction },
|
|
251
|
+
resource: { is: "Gateway::Route" },
|
|
252
|
+
when: ["resource.public == true"],
|
|
253
|
+
});
|
|
254
|
+
|
|
255
|
+
/** Authenticated callers may write. */
|
|
256
|
+
const authenticatedWrite = new Policy({
|
|
257
|
+
effect: "permit",
|
|
258
|
+
principal: { is: "Gateway::Caller" },
|
|
259
|
+
action: { in: [PostAction, DeleteAction] },
|
|
260
|
+
resource: { is: "Gateway::Route" },
|
|
261
|
+
when: ["context.authenticated == true"],
|
|
262
|
+
});
|
|
263
|
+
|
|
264
|
+
/** Deprecated routes are closed to everyone but internal callers. */
|
|
265
|
+
const guarded = DenyByDefaultSet({
|
|
266
|
+
policies: [publicRead, authenticatedWrite],
|
|
267
|
+
entityType: "Gateway::Route",
|
|
268
|
+
when: ["resource.deprecated == true"],
|
|
269
|
+
unless: ["principal.internal == true"],
|
|
270
|
+
annotations: { id: "deprecated-route-floor" },
|
|
271
|
+
});
|
|
272
|
+
|
|
273
|
+
export const deprecatedRouteFloor = guarded.floor;
|
|
274
|
+
export const publicReadGrant = guarded.members[0];
|
|
275
|
+
export const authenticatedWriteGrant = guarded.members[1];
|
|
276
|
+
`;
|
|
277
|
+
// ── The template set ──────────────────────────────────────────────
|
|
278
|
+
/**
|
|
279
|
+
* Scaffolds for `chant init`.
|
|
280
|
+
*
|
|
281
|
+
* An unrecognized name falls through to the default rather than throwing —
|
|
282
|
+
* the same behaviour every other lexicon's `initTemplates` has.
|
|
283
|
+
*/
|
|
284
|
+
export function cedarInitTemplates(template) {
|
|
285
|
+
const scripts = {
|
|
286
|
+
generate: "chant cedar generate",
|
|
287
|
+
build: "chant build",
|
|
288
|
+
};
|
|
289
|
+
if (template === "avp-embedding") {
|
|
290
|
+
return {
|
|
291
|
+
src: { "policies.ts": AVP_POLICIES },
|
|
292
|
+
root: {
|
|
293
|
+
[SCHEMA_FILE]: AVP_SCHEMA,
|
|
294
|
+
"README.md": readme("Cedar policies for a Verified Permissions store", "The `Store` namespace models a multi-tenant record store. `tenantIsolation` is a\n" +
|
|
295
|
+
"`forbid`, deliberately: a forbid beats every permit in the set unconditionally,\n" +
|
|
296
|
+
"so a tenancy boundary written that way survives a wider grant added later.\n\n" +
|
|
297
|
+
"The statement each policy serializes to is what goes in an\n" +
|
|
298
|
+
"`AWS::VerifiedPermissions::Policy`. The typed handoff is landing with\n" +
|
|
299
|
+
"INTENTIUS/chant#1652 — until then, reference the emitted `.cedar` text rather\n" +
|
|
300
|
+
"than hand-typing a statement string."),
|
|
301
|
+
},
|
|
302
|
+
scripts,
|
|
303
|
+
};
|
|
304
|
+
}
|
|
305
|
+
if (template === "gateway-policy-set") {
|
|
306
|
+
return {
|
|
307
|
+
src: { "policies.ts": GATEWAY_POLICIES },
|
|
308
|
+
root: {
|
|
309
|
+
[SCHEMA_FILE]: GATEWAY_SCHEMA,
|
|
310
|
+
"README.md": readme("Cedar policies for an API gateway", "Routes are entities, not path strings, which is what lets a policy say\n" +
|
|
311
|
+
"`resource.public == true` instead of matching globs. The `DenyByDefaultSet`\n" +
|
|
312
|
+
"composite returns the deprecated-route floor and the grants it governs from one\n" +
|
|
313
|
+
"call, so a refactor cannot delete the floor and leave the grants behind."),
|
|
314
|
+
},
|
|
315
|
+
scripts,
|
|
316
|
+
};
|
|
317
|
+
}
|
|
318
|
+
return {
|
|
319
|
+
src: { "policies.ts": DEFAULT_POLICIES },
|
|
320
|
+
root: {
|
|
321
|
+
[SCHEMA_FILE]: DEFAULT_SCHEMA,
|
|
322
|
+
"README.md": readme("Cedar policies", "One permit and one forbid. Cedar is default-deny, so the permit is what grants\n" +
|
|
323
|
+
"anything at all; the forbid is not the absence of a grant but an override that\n" +
|
|
324
|
+
"beats every permit in the set."),
|
|
325
|
+
},
|
|
326
|
+
scripts,
|
|
327
|
+
};
|
|
328
|
+
}
|
|
329
|
+
/** Every template name this lexicon scaffolds, for docs and tests. */
|
|
330
|
+
export const CEDAR_INIT_TEMPLATES = ["default", "avp-embedding", "gateway-policy-set"];
|
package/dist/integrity.json
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"algorithm": "sha256",
|
|
3
3
|
"artifacts": {
|
|
4
|
-
"manifest.json": "
|
|
4
|
+
"manifest.json": "eaf723654b33eea3ec3378b6efb10697767a39f43e5849712c683faf6978233d",
|
|
5
5
|
"meta.json": "b218779260169a193882aeee76e5ac559588f1682657a001fb0982cffb5843fc",
|
|
6
6
|
"types/index.d.ts": "9e53a1cb3c9ee38f9de2f3c0db9cc28a0b67c3649be410c341e2451b9b0076cd",
|
|
7
7
|
"rules/policy-shape.ts": "11377265f05ede594b77c2b6cc3d977a688e5e5712e7a706e7aa6fd6ea05d027",
|
|
@@ -30,5 +30,5 @@
|
|
|
30
30
|
"skills/chant-cedar-meta-policy.md": "050b310a87196a48c827f1129698e4c6bbcd96dc963782e22bf1a640bbc20a1a",
|
|
31
31
|
"skills/chant-cedar-dogwood.md": "7e52d3f045c1c34e00fdef76014a05483fb9dd58cc41037621a76bc298776d8f"
|
|
32
32
|
},
|
|
33
|
-
"composite": "
|
|
33
|
+
"composite": "81189c85f84ffb6ed2fac31e166e712fa16f2a97cbf39826ced2704663de1728"
|
|
34
34
|
}
|
|
@@ -0,0 +1,65 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* The cedar lexicon's `chant audit` catalog — metadata for every CED check,
|
|
3
|
+
* contributed via `cedarPlugin.auditCatalog()` (#687, #1346).
|
|
4
|
+
*
|
|
5
|
+
* A check's own definition says what it looks for. It does not say how severe
|
|
6
|
+
* the finding is, whether the fix is mechanical, what backs the rule, or what
|
|
7
|
+
* to title it in a report. `resolveAuditCatalog` merges this map over core's
|
|
8
|
+
* static catalog to answer all four — and it skips a lexicon that omits the
|
|
9
|
+
* method *silently*, which is how fly's and temporal's checks reached the
|
|
10
|
+
* report with no metadata at all. A repo-level test fails on a gap in either
|
|
11
|
+
* direction, so this map and the shipped checks move together.
|
|
12
|
+
*
|
|
13
|
+
* Every entry here is `yamlBased: true`: each check reads `ctx.outputs` — the
|
|
14
|
+
* emitted policy set — and nothing reads `ctx.entities`, so all of them fire
|
|
15
|
+
* on an audit of a standalone `policies.cedar.json` exactly as they do on a
|
|
16
|
+
* `chant build`. `auditRule()` hardcodes that, so it is used throughout rather
|
|
17
|
+
* than constructing `RuleMeta` by hand the way fly and fountain must.
|
|
18
|
+
*
|
|
19
|
+
* `authority` is left off deliberately. The catalog test treats a citation as
|
|
20
|
+
* proof that a finding is `security`, and Cedar's own docs are a language
|
|
21
|
+
* reference rather than a benchmark like CIS or OSSF Scorecard; citing them
|
|
22
|
+
* would be dressing up chant's own opinion. The one place a citation would be
|
|
23
|
+
* honest — the bare-permit wall — is chant's convention, not upstream's, and
|
|
24
|
+
* the verification report is explicit that Cedar's validator says nothing
|
|
25
|
+
* about it.
|
|
26
|
+
*/
|
|
27
|
+
import { auditRule, applyLineage } from "@intentius/chant/audit/catalog";
|
|
28
|
+
import { cedarAuditLineage } from "./audit-lineage.js";
|
|
29
|
+
export const cedarAuditCatalog = {
|
|
30
|
+
// ── Pre-synth lint rule ────────────────────────────────────────
|
|
31
|
+
//
|
|
32
|
+
// Not a post-synth check, and so not required by the coverage test — but a
|
|
33
|
+
// reader who hits CEDC001 wants the same metadata as one who hits CEDC010,
|
|
34
|
+
// which is the reason fountain catalogs FTN001.
|
|
35
|
+
CEDC001: auditRule("CEDC001", "merge-worthy", "guidance", "Cedar policy has an unusable effect or an empty guard", 'Use effect: "permit" or "forbid" — anything else is emitted as a permit — and remove empty when/unless entries.', { category: "correctness" }),
|
|
36
|
+
// ── Correctness ────────────────────────────────────────────────
|
|
37
|
+
CEDC010: auditRule("CEDC010", "merge-worthy", "guidance", "Cedar policy set does not parse", "Fix the policy set so Cedar's own parser accepts it — no evaluator can load it as it stands.", { category: "correctness" }),
|
|
38
|
+
CEDC011: auditRule("CEDC011", "merge-worthy", "guidance", "Cedar condition expression does not parse", "Correct the when/unless expression source named in the finding; the rest of the policy set cannot load until it parses.", { category: "correctness" }),
|
|
39
|
+
CEDC012: auditRule("CEDC012", "merge-worthy", "guidance", "Duplicate Cedar policy id", "Rename one of the colliding declarations, or give it an explicit and distinct @id annotation — the JSON policy set keeps only one of them.", { category: "correctness" }),
|
|
40
|
+
CEDC013: auditRule("CEDC013", "report-only", "guidance", "Cedar policy annotation is empty or disagrees with the policy id", "Make @id match the policy's id (or drop it and let chant derive one), and give every other annotation a real value.", { category: "best-practice" }),
|
|
41
|
+
CEDC014: auditRule("CEDC014", "merge-worthy", "guidance", "Cedar scope constraint names a bare type instead of an entity", 'Write the reference as a full UID — Namespace::Type::"id" — so it resolves to an entity rather than degrading to a type name with an empty id.', { category: "correctness" }),
|
|
42
|
+
// ── Evaluability (the validator) ───────────────────────────────
|
|
43
|
+
CEDE010: auditRule("CEDE010", "merge-worthy", "guidance", "Cedar policy fails validation against the schema", "Fix the entity type, action, or attribute the validator names — a policy the validator rejects can never grant anything. If the finding is the advisory, emit a .cedarschema beside the policies so validation can run at all.", { category: "correctness" }),
|
|
44
|
+
CEDE011: auditRule("CEDE011", "report-only", "guidance", "Cedar validator warns about a policy", "Read the validator's warning — an impossible policy evaluates to false for every valid request, so the access it was written to grant does not exist.", { category: "correctness" }),
|
|
45
|
+
// ── Security ───────────────────────────────────────────────────
|
|
46
|
+
CEDS010: auditRule("CEDS010", "merge-worthy", "guidance", "Bare permit(principal, action, resource) grants everything", "Constrain a scope or add a when/unless guard. Cedar's validator does not flag this policy; a production build fails on it.", { category: "security" }),
|
|
47
|
+
CEDS011: auditRule("CEDS011", "report-only", "guidance", "Cedar policy set has no forbid", "Add a forbid for whatever the set must never allow — a forbid beats every permit, so it is the only invariant a later grant cannot widen.", { category: "security" }),
|
|
48
|
+
CEDS012: auditRule("CEDS012", "report-only", "guidance", "Cedar permit leaves the action scope unconstrained", 'Name the actions the grant needs (action == Action::"read", or action in [ … ]) so it does not widen as the schema gains actions.', { category: "security" }),
|
|
49
|
+
// ── The dogwood dialect (DWD) ──────────────────────────────────
|
|
50
|
+
//
|
|
51
|
+
// A second id family under the same lexicon, declared on the serializer as
|
|
52
|
+
// `extraRulePrefixes` (#1349's mechanism, the way k8s owns ARGO and FLUX).
|
|
53
|
+
// Dogwood is a dialect of Cedar rather than a peer language — a `.dw` file
|
|
54
|
+
// stripped of Cedar semantics is meaningless — so its checks ship here, and
|
|
55
|
+
// its ids are their own family so a reader can tell which surface bit them.
|
|
56
|
+
DWDC010: auditRule("DWDC010", "merge-worthy", "guidance", "Dogwood temporal predicate names an undeclared event kind", "Declare the event kind in the emitted .dwschema, or correct the predicate to a kind the schema derives — upstream rejects the policy set otherwise.", { category: "correctness" }),
|
|
57
|
+
DWDC011: auditRule("DWDC011", "merge-worthy", "guidance", "Dogwood temporal window exceeds the event schema's max_window", "Shorten the window, or raise max_window in the event schema. With no schema emitted the cap is upstream's 24h default.", { category: "correctness" }),
|
|
58
|
+
DWDC012: auditRule("DWDC012", "merge-worthy", "guidance", "Dogwood formerly/previous/since is missing its window", "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.", { category: "correctness" }),
|
|
59
|
+
DWDC013: auditRule("DWDC013", "report-only", "guidance", "AgentCore policy embeds temporal text with no event schema emitted", "Declare a TemporalEventSchema so the .dwschema ships with the policy, or record where the service schema is registered — AWS::BedrockAgentCore::Policy has no property to carry it, and until the engine has it every temporal predicate in the statement matches nothing.", { category: "correctness" }),
|
|
60
|
+
DWDE010: auditRule("DWDE010", "merge-worthy", "guidance", "Dogwood policy set fails `dogwood validate`", "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.", { category: "correctness" }),
|
|
61
|
+
DWDE011: auditRule("DWDE011", "merge-worthy", "guidance", "Cedar lowered from a dogwood policy set fails Cedar validation", "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.", { category: "correctness" }),
|
|
62
|
+
DWDS010: auditRule("DWDS010", "report-only", "guidance", "Dogwood event schema pins nothing, widening every temporal predicate", "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.", { category: "security" }),
|
|
63
|
+
};
|
|
64
|
+
// Prior art credits live beside the rules in ./audit-lineage.ts (see core audit/prior-art.ts).
|
|
65
|
+
applyLineage(cedarAuditCatalog, cedarAuditLineage);
|
|
@@ -0,0 +1,23 @@
|
|
|
1
|
+
export const cedarAuditLineage = {
|
|
2
|
+
CEDC001: [
|
|
3
|
+
{ tool: "cedar-docs", rule: "effect", url: "https://docs.cedarpolicy.com/policies/json-format.html#effect", relation: "overlaps" },
|
|
4
|
+
],
|
|
5
|
+
CEDC010: [
|
|
6
|
+
{ tool: "cedar-cli", rule: "check-parse", url: "https://github.com/cedar-policy/cedar/tree/main/cedar-policy-cli", relation: "equivalent" },
|
|
7
|
+
],
|
|
8
|
+
CEDC011: [
|
|
9
|
+
{ tool: "cedar-cli", rule: "check-parse", url: "https://github.com/cedar-policy/cedar/tree/main/cedar-policy-cli", relation: "overlaps" },
|
|
10
|
+
],
|
|
11
|
+
CEDC012: [
|
|
12
|
+
{ tool: "cedar-policy-crate", rule: "PolicySetError::AlreadyDefined", url: "https://docs.rs/cedar-policy/latest/cedar_policy/enum.PolicySetError.html#variant.AlreadyDefined", relation: "overlaps" },
|
|
13
|
+
],
|
|
14
|
+
CEDE010: [
|
|
15
|
+
{ tool: "cedar-validator", rule: "supported-validation-checks (errors)", url: "https://docs.cedarpolicy.com/policies/validation.html#supported-validation-checks", relation: "equivalent" },
|
|
16
|
+
],
|
|
17
|
+
CEDE011: [
|
|
18
|
+
{ tool: "cedar-validator", rule: "supported-validation-checks (warnings)", url: "https://docs.cedarpolicy.com/policies/validation.html#supported-validation-checks", relation: "equivalent" },
|
|
19
|
+
],
|
|
20
|
+
DWDE011: [
|
|
21
|
+
{ tool: "cedar-validator", rule: "supported-validation-checks (errors)", url: "https://docs.cedarpolicy.com/policies/validation.html#supported-validation-checks", relation: "overlaps" },
|
|
22
|
+
],
|
|
23
|
+
};
|
|
@@ -0,0 +1,123 @@
|
|
|
1
|
+
/** Files carrying a Cedar JSON policy set end with this. */
|
|
2
|
+
export const CEDAR_JSON_SUFFIX = ".cedar.json";
|
|
3
|
+
/** The lexicon name stamped on every diagnostic from this directory. */
|
|
4
|
+
export const CEDAR_LEXICON = "cedar";
|
|
5
|
+
// ── Narrowing ─────────────────────────────────────────────────────
|
|
6
|
+
export function isRecord(value) {
|
|
7
|
+
return typeof value === "object" && value !== null && !Array.isArray(value);
|
|
8
|
+
}
|
|
9
|
+
/** `permit` / `forbid` / whatever else the document claims. */
|
|
10
|
+
export function effectOf(policy) {
|
|
11
|
+
return typeof policy.effect === "string" ? policy.effect : undefined;
|
|
12
|
+
}
|
|
13
|
+
/** The three scope positions, in Cedar's own order. */
|
|
14
|
+
export function scopesOf(policy) {
|
|
15
|
+
const out = [];
|
|
16
|
+
for (const variable of ["principal", "action", "resource"]) {
|
|
17
|
+
const scope = policy[variable];
|
|
18
|
+
if (isRecord(scope))
|
|
19
|
+
out.push({ variable, scope: scope });
|
|
20
|
+
}
|
|
21
|
+
return out;
|
|
22
|
+
}
|
|
23
|
+
/** True when a scope position is Cedar's unconstrained "any" (`op: "All"`). */
|
|
24
|
+
export function scopeIsAll(scope) {
|
|
25
|
+
return isRecord(scope) && scope.op === "All";
|
|
26
|
+
}
|
|
27
|
+
/** Every `when`/`unless` clause, or `[]` when the policy is unconditioned. */
|
|
28
|
+
export function conditionsOf(policy) {
|
|
29
|
+
if (!Array.isArray(policy.conditions))
|
|
30
|
+
return [];
|
|
31
|
+
return policy.conditions.filter(isRecord);
|
|
32
|
+
}
|
|
33
|
+
/** The policy's annotation map, narrowed to the string-valued entries Cedar allows. */
|
|
34
|
+
export function annotationsOf(policy) {
|
|
35
|
+
return isRecord(policy.annotations) ? policy.annotations : {};
|
|
36
|
+
}
|
|
37
|
+
/** The `@id` annotation, when it is a string. */
|
|
38
|
+
export function annotatedId(policy) {
|
|
39
|
+
const id = annotationsOf(policy).id;
|
|
40
|
+
return typeof id === "string" ? id : undefined;
|
|
41
|
+
}
|
|
42
|
+
/**
|
|
43
|
+
* Whether a build's `env` (from `--env` or the project's `ownership.env`, see
|
|
44
|
+
* `PostSynthContext#env`) is production-like, for the checks whose severity is
|
|
45
|
+
* env-gated. Same seam and same vocabulary as the aws lexicon's
|
|
46
|
+
* `isFullTierEnv`: an unset or unrecognized env is treated as non-production,
|
|
47
|
+
* so a project that never passes `--env` is never unexpectedly hard-failed.
|
|
48
|
+
*/
|
|
49
|
+
export function isProdLikeEnv(env) {
|
|
50
|
+
return env === "prod" || env === "production" || env === "full";
|
|
51
|
+
}
|
|
52
|
+
// ── Reading policy sets out of the build output ───────────────────
|
|
53
|
+
/** Every candidate policy-set document in the build, parsed or not. */
|
|
54
|
+
export function policySets(ctx) {
|
|
55
|
+
const sets = [];
|
|
56
|
+
for (const [lexicon, output] of ctx.outputs) {
|
|
57
|
+
if (typeof output === "string") {
|
|
58
|
+
// A bare string output. `createPostSynthContext` and `chant audit` over a
|
|
59
|
+
// standalone file both land here; only JSON is a policy set, `.cedar`
|
|
60
|
+
// text is the human-facing twin and carries nothing the JSON does not.
|
|
61
|
+
//
|
|
62
|
+
// Every lexicon's output shares this map, and several of them are JSON,
|
|
63
|
+
// so "is it JSON" is not enough to claim one: take it when it came from
|
|
64
|
+
// the cedar output (where even unparseable content is a cedar finding —
|
|
65
|
+
// CEDC010 reports it) or when it is unmistakably a policy-set envelope.
|
|
66
|
+
if (!looksLikeJson(output))
|
|
67
|
+
continue;
|
|
68
|
+
const set = readSet(lexicon, lexicon, output);
|
|
69
|
+
if (lexicon === CEDAR_LEXICON || isPolicySetEnvelope(set.doc))
|
|
70
|
+
sets.push(set);
|
|
71
|
+
continue;
|
|
72
|
+
}
|
|
73
|
+
for (const [filename, content] of Object.entries(output.files ?? {})) {
|
|
74
|
+
if (!filename.endsWith(CEDAR_JSON_SUFFIX))
|
|
75
|
+
continue;
|
|
76
|
+
if (typeof content !== "string")
|
|
77
|
+
continue;
|
|
78
|
+
sets.push(readSet(lexicon, filename, content));
|
|
79
|
+
}
|
|
80
|
+
}
|
|
81
|
+
return sets;
|
|
82
|
+
}
|
|
83
|
+
/** The subset of {@link policySets} that parsed as JSON. */
|
|
84
|
+
export function parsedPolicySets(ctx) {
|
|
85
|
+
return policySets(ctx).filter((set) => set.doc !== undefined);
|
|
86
|
+
}
|
|
87
|
+
function looksLikeJson(text) {
|
|
88
|
+
return text.trimStart().startsWith("{");
|
|
89
|
+
}
|
|
90
|
+
/** The `PolicySet` envelope has one of these two keys; nothing else does. */
|
|
91
|
+
function isPolicySetEnvelope(doc) {
|
|
92
|
+
return doc !== undefined && (isRecord(doc.staticPolicies) || isRecord(doc.templates));
|
|
93
|
+
}
|
|
94
|
+
function readSet(lexicon, source, raw) {
|
|
95
|
+
let doc;
|
|
96
|
+
let parseError;
|
|
97
|
+
try {
|
|
98
|
+
const parsed = JSON.parse(raw);
|
|
99
|
+
if (isRecord(parsed))
|
|
100
|
+
doc = parsed;
|
|
101
|
+
else
|
|
102
|
+
parseError = "policy set is not a JSON object";
|
|
103
|
+
}
|
|
104
|
+
catch (err) {
|
|
105
|
+
parseError = err instanceof Error ? err.message : String(err);
|
|
106
|
+
}
|
|
107
|
+
return { lexicon, source, raw, doc, parseError, entries: doc ? flatten(doc) : [] };
|
|
108
|
+
}
|
|
109
|
+
function flatten(doc) {
|
|
110
|
+
const entries = [];
|
|
111
|
+
for (const [group, template] of [
|
|
112
|
+
[doc.staticPolicies, false],
|
|
113
|
+
[doc.templates, true],
|
|
114
|
+
]) {
|
|
115
|
+
if (!isRecord(group))
|
|
116
|
+
continue;
|
|
117
|
+
for (const [key, policy] of Object.entries(group)) {
|
|
118
|
+
if (isRecord(policy))
|
|
119
|
+
entries.push({ key, policy: policy, template });
|
|
120
|
+
}
|
|
121
|
+
}
|
|
122
|
+
return entries;
|
|
123
|
+
}
|