cedar-mcp-server 1.0.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/.editorconfig +12 -0
- package/.github/workflows/ci.yml +31 -0
- package/.github/workflows/release.yml +42 -0
- package/.nvmrc +1 -0
- package/CHANGELOG.md +241 -0
- package/CONTRIBUTING.md +83 -0
- package/LICENSE +182 -0
- package/README.md +1635 -0
- package/SECURITY.md +37 -0
- package/dist/http-server.d.ts +61 -0
- package/dist/http-server.d.ts.map +1 -0
- package/dist/http-server.js +194 -0
- package/dist/http-server.js.map +1 -0
- package/dist/index.d.ts +32 -0
- package/dist/index.d.ts.map +1 -0
- package/dist/index.js +270 -0
- package/dist/index.js.map +1 -0
- package/dist/parser/policy-ast.d.ts +49 -0
- package/dist/parser/policy-ast.d.ts.map +1 -0
- package/dist/parser/policy-ast.js +311 -0
- package/dist/parser/policy-ast.js.map +1 -0
- package/dist/prompts/index.d.ts +38 -0
- package/dist/prompts/index.d.ts.map +1 -0
- package/dist/prompts/index.js +172 -0
- package/dist/prompts/index.js.map +1 -0
- package/dist/resources/ref-resolver.d.ts +23 -0
- package/dist/resources/ref-resolver.d.ts.map +1 -0
- package/dist/resources/ref-resolver.js +128 -0
- package/dist/resources/ref-resolver.js.map +1 -0
- package/dist/resources/store-manager.d.ts +64 -0
- package/dist/resources/store-manager.d.ts.map +1 -0
- package/dist/resources/store-manager.js +221 -0
- package/dist/resources/store-manager.js.map +1 -0
- package/dist/server.d.ts +18 -0
- package/dist/server.d.ts.map +1 -0
- package/dist/server.js +539 -0
- package/dist/server.js.map +1 -0
- package/dist/tools/advise/avp-rules.d.ts +49 -0
- package/dist/tools/advise/avp-rules.d.ts.map +1 -0
- package/dist/tools/advise/avp-rules.js +59 -0
- package/dist/tools/advise/avp-rules.js.map +1 -0
- package/dist/tools/advise/cedar-patterns.d.ts +24 -0
- package/dist/tools/advise/cedar-patterns.d.ts.map +1 -0
- package/dist/tools/advise/cedar-patterns.js +57 -0
- package/dist/tools/advise/cedar-patterns.js.map +1 -0
- package/dist/tools/advise/context-builder.d.ts +28 -0
- package/dist/tools/advise/context-builder.d.ts.map +1 -0
- package/dist/tools/advise/context-builder.js +89 -0
- package/dist/tools/advise/context-builder.js.map +1 -0
- package/dist/tools/advise/gotchas.d.ts +15 -0
- package/dist/tools/advise/gotchas.d.ts.map +1 -0
- package/dist/tools/advise/gotchas.js +83 -0
- package/dist/tools/advise/gotchas.js.map +1 -0
- package/dist/tools/advise.d.ts +96 -0
- package/dist/tools/advise.d.ts.map +1 -0
- package/dist/tools/advise.js +258 -0
- package/dist/tools/advise.js.map +1 -0
- package/dist/tools/authorize-batch.d.ts +35 -0
- package/dist/tools/authorize-batch.d.ts.map +1 -0
- package/dist/tools/authorize-batch.js +262 -0
- package/dist/tools/authorize-batch.js.map +1 -0
- package/dist/tools/authorize.d.ts +115 -0
- package/dist/tools/authorize.d.ts.map +1 -0
- package/dist/tools/authorize.js +373 -0
- package/dist/tools/authorize.js.map +1 -0
- package/dist/tools/check-change.d.ts +19 -0
- package/dist/tools/check-change.d.ts.map +1 -0
- package/dist/tools/check-change.js +91 -0
- package/dist/tools/check-change.js.map +1 -0
- package/dist/tools/diff-schema.d.ts +103 -0
- package/dist/tools/diff-schema.d.ts.map +1 -0
- package/dist/tools/diff-schema.js +379 -0
- package/dist/tools/diff-schema.js.map +1 -0
- package/dist/tools/diff-stores.d.ts +45 -0
- package/dist/tools/diff-stores.d.ts.map +1 -0
- package/dist/tools/diff-stores.js +222 -0
- package/dist/tools/diff-stores.js.map +1 -0
- package/dist/tools/explain.d.ts +80 -0
- package/dist/tools/explain.d.ts.map +1 -0
- package/dist/tools/explain.js +187 -0
- package/dist/tools/explain.js.map +1 -0
- package/dist/tools/format.d.ts +11 -0
- package/dist/tools/format.d.ts.map +1 -0
- package/dist/tools/format.js +20 -0
- package/dist/tools/format.js.map +1 -0
- package/dist/tools/generate-sample.d.ts +28 -0
- package/dist/tools/generate-sample.d.ts.map +1 -0
- package/dist/tools/generate-sample.js +568 -0
- package/dist/tools/generate-sample.js.map +1 -0
- package/dist/tools/link-template.d.ts +17 -0
- package/dist/tools/link-template.d.ts.map +1 -0
- package/dist/tools/link-template.js +78 -0
- package/dist/tools/link-template.js.map +1 -0
- package/dist/tools/list-template-links.d.ts +16 -0
- package/dist/tools/list-template-links.d.ts.map +1 -0
- package/dist/tools/list-template-links.js +22 -0
- package/dist/tools/list-template-links.js.map +1 -0
- package/dist/tools/list-templates.d.ts +16 -0
- package/dist/tools/list-templates.d.ts.map +1 -0
- package/dist/tools/list-templates.js +36 -0
- package/dist/tools/list-templates.js.map +1 -0
- package/dist/tools/translate.d.ts +11 -0
- package/dist/tools/translate.d.ts.map +1 -0
- package/dist/tools/translate.js +53 -0
- package/dist/tools/translate.js.map +1 -0
- package/dist/tools/validate-entities.d.ts +19 -0
- package/dist/tools/validate-entities.d.ts.map +1 -0
- package/dist/tools/validate-entities.js +88 -0
- package/dist/tools/validate-entities.js.map +1 -0
- package/dist/tools/validate-schema.d.ts +22 -0
- package/dist/tools/validate-schema.d.ts.map +1 -0
- package/dist/tools/validate-schema.js +89 -0
- package/dist/tools/validate-schema.js.map +1 -0
- package/dist/tools/validate-template.d.ts +18 -0
- package/dist/tools/validate-template.d.ts.map +1 -0
- package/dist/tools/validate-template.js +59 -0
- package/dist/tools/validate-template.js.map +1 -0
- package/dist/tools/validate.d.ts +90 -0
- package/dist/tools/validate.d.ts.map +1 -0
- package/dist/tools/validate.js +351 -0
- package/dist/tools/validate.js.map +1 -0
- package/dist/utils/format-detector.d.ts +49 -0
- package/dist/utils/format-detector.d.ts.map +1 -0
- package/dist/utils/format-detector.js +298 -0
- package/dist/utils/format-detector.js.map +1 -0
- package/examples/README.md +36 -0
- package/examples/abac-multi-tenant/README.md +150 -0
- package/examples/abac-multi-tenant/entities/users-and-docs.json +33 -0
- package/examples/abac-multi-tenant/policies/member-read-internal.cedar +9 -0
- package/examples/abac-multi-tenant/policies/owner-full-access.cedar +9 -0
- package/examples/abac-multi-tenant/policies/premium-share-guard.cedar +9 -0
- package/examples/abac-multi-tenant/policies/private-doc-guard.cedar +13 -0
- package/examples/abac-multi-tenant/run.ts +92 -0
- package/examples/abac-multi-tenant/schema.json +60 -0
- package/examples/api-gateway-path-routing/README.md +154 -0
- package/examples/api-gateway-path-routing/entities/users-and-roles.json +20 -0
- package/examples/api-gateway-path-routing/policies/admin-full-access.cedar +6 -0
- package/examples/api-gateway-path-routing/policies/developer-projects.cedar +14 -0
- package/examples/api-gateway-path-routing/policies/viewer-readonly.cedar +10 -0
- package/examples/api-gateway-path-routing/run.ts +108 -0
- package/examples/api-gateway-path-routing/schema.json +54 -0
- package/examples/rbac-document-management/README.md +167 -0
- package/examples/rbac-document-management/entities/users-and-docs.json +43 -0
- package/examples/rbac-document-management/policies/admin.cedar +6 -0
- package/examples/rbac-document-management/policies/editor.cedar +6 -0
- package/examples/rbac-document-management/policies/top-secret-forbid.cedar +13 -0
- package/examples/rbac-document-management/policies/viewer.cedar +6 -0
- package/examples/rbac-document-management/run.ts +87 -0
- package/examples/rbac-document-management/schema.json +57 -0
- package/package.json +50 -0
- package/src/http-server.ts +239 -0
- package/src/index.ts +294 -0
- package/src/parser/policy-ast.ts +345 -0
- package/src/prompts/README.md +3 -0
- package/src/prompts/index.ts +217 -0
- package/src/resources/ref-resolver.ts +134 -0
- package/src/resources/store-manager.ts +248 -0
- package/src/server.ts +711 -0
- package/src/tools/advise/avp-rules.ts +70 -0
- package/src/tools/advise/cedar-patterns.ts +73 -0
- package/src/tools/advise/context-builder.ts +109 -0
- package/src/tools/advise/gotchas.ts +92 -0
- package/src/tools/advise.ts +366 -0
- package/src/tools/authorize-batch.ts +345 -0
- package/src/tools/authorize.ts +464 -0
- package/src/tools/check-change.ts +119 -0
- package/src/tools/diff-schema.ts +510 -0
- package/src/tools/diff-stores.ts +298 -0
- package/src/tools/explain.ts +278 -0
- package/src/tools/format.ts +33 -0
- package/src/tools/generate-sample.ts +665 -0
- package/src/tools/link-template.ts +109 -0
- package/src/tools/list-template-links.ts +41 -0
- package/src/tools/list-templates.ts +55 -0
- package/src/tools/translate.ts +66 -0
- package/src/tools/validate-entities.ts +125 -0
- package/src/tools/validate-schema.ts +128 -0
- package/src/tools/validate-template.ts +72 -0
- package/src/tools/validate.ts +459 -0
- package/src/utils/format-detector.ts +356 -0
- package/test/fixtures/docmgmt.ts +121 -0
- package/test/fixtures/multitenant.ts +163 -0
- package/test/index.test.ts +96 -0
- package/test/integration/e2e/behavior.test.ts +359 -0
- package/test/integration/e2e/edge-cases.test.ts +365 -0
- package/test/integration/e2e/failure-modes.test.ts +266 -0
- package/test/integration/e2e/protocol.test.ts +252 -0
- package/test/integration/http-smoke.test.ts +588 -0
- package/test/integration/smoke.test.ts +475 -0
- package/test/prompts/prompts.test.ts +173 -0
- package/test/property/properties.test.ts +234 -0
- package/test/resources/ref-resolver.test.ts +186 -0
- package/test/resources/store-manager.test.ts +344 -0
- package/test/setup.test.ts +7 -0
- package/test/tools/advise/avp-rules.test.ts +76 -0
- package/test/tools/advise.test.ts +339 -0
- package/test/tools/authorize-batch.test.ts +459 -0
- package/test/tools/authorize.test.ts +682 -0
- package/test/tools/check-change.test.ts +104 -0
- package/test/tools/cross-fixture.test.ts +170 -0
- package/test/tools/diff-schema.test.ts +355 -0
- package/test/tools/diff-stores.test.ts +291 -0
- package/test/tools/explain.test.ts +221 -0
- package/test/tools/format.test.ts +33 -0
- package/test/tools/generate-sample.test.ts +480 -0
- package/test/tools/link-template.test.ts +90 -0
- package/test/tools/list-templates.test.ts +151 -0
- package/test/tools/translate.test.ts +89 -0
- package/test/tools/validate-entities.test.ts +178 -0
- package/test/tools/validate-schema.test.ts +86 -0
- package/test/tools/validate-template.test.ts +89 -0
- package/test/tools/validate.test.ts +331 -0
- package/test/utils/format-detector.test.ts +518 -0
- package/tsconfig.json +17 -0
- package/vitest.config.ts +13 -0
|
@@ -0,0 +1,70 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* AVP UpdatePolicy mutability rules — proven from official docs 2026-05-20:
|
|
3
|
+
* https://docs.aws.amazon.com/verifiedpermissions/latest/apireference/API_UpdatePolicy.html
|
|
4
|
+
*
|
|
5
|
+
* UpdatePolicy only updates STATIC policies. Template-linked → use UpdatePolicyTemplate.
|
|
6
|
+
*/
|
|
7
|
+
|
|
8
|
+
export type AvpUpdateMode = "in_place_via_update_policy" | "requires_delete_recreate" | "new_policy_via_create_policy";
|
|
9
|
+
|
|
10
|
+
export interface AvpChangeClassification {
|
|
11
|
+
mode: AvpUpdateMode;
|
|
12
|
+
rationale: string;
|
|
13
|
+
}
|
|
14
|
+
|
|
15
|
+
/** Classify which AVP API call a given change type requires. */
|
|
16
|
+
export function classifyAvpChange(changeField: string): AvpChangeClassification {
|
|
17
|
+
switch (changeField) {
|
|
18
|
+
case "action":
|
|
19
|
+
case "when_clause":
|
|
20
|
+
case "unless_clause":
|
|
21
|
+
case "policy_name":
|
|
22
|
+
return {
|
|
23
|
+
mode: "in_place_via_update_policy",
|
|
24
|
+
rationale: "AVP UpdatePolicy supports modifying actions, when/unless clauses, and policy name in-place.",
|
|
25
|
+
};
|
|
26
|
+
case "effect":
|
|
27
|
+
case "principal":
|
|
28
|
+
case "resource":
|
|
29
|
+
case "policy_type_conversion":
|
|
30
|
+
return {
|
|
31
|
+
mode: "requires_delete_recreate",
|
|
32
|
+
rationale: "AVP UpdatePolicy cannot change effect, principal scope, resource scope, or convert between static and template-linked policies. Delete the existing policy and create a new one.",
|
|
33
|
+
};
|
|
34
|
+
case "new_policy":
|
|
35
|
+
return {
|
|
36
|
+
mode: "new_policy_via_create_policy",
|
|
37
|
+
rationale: "New policy — use AVP CreatePolicy API.",
|
|
38
|
+
};
|
|
39
|
+
default:
|
|
40
|
+
return {
|
|
41
|
+
mode: "in_place_via_update_policy",
|
|
42
|
+
rationale: "Change type unclassified — assume in-place update is possible but verify.",
|
|
43
|
+
};
|
|
44
|
+
}
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
/** Validation error categories AVP raises — pre-detectable during authoring. */
|
|
48
|
+
export const AVP_VALIDATION_ERRORS = [
|
|
49
|
+
{ id: "UnrecognizedEntityType", description: "Policy references an entity type not declared in the schema." },
|
|
50
|
+
{ id: "UnrecognizedActionId", description: "Policy references an action not declared in the schema." },
|
|
51
|
+
{ id: "InvalidActionApplication", description: "Action does not apply to the specified principal and resource types per the schema appliesTo definition." },
|
|
52
|
+
{ id: "UnexpectedType", description: "An operand has the wrong type for the operation (e.g. comparing a String to an entity)." },
|
|
53
|
+
{ id: "IncompatibleTypes", description: "Types in a Set or if/then/else expression are incompatible." },
|
|
54
|
+
{ id: "MissingAttribute", description: "Policy accesses an attribute not declared in the schema. Add the attribute to the schema or use a has guard." },
|
|
55
|
+
{ id: "UnsafeOptionalAttributeAccess", description: "Policy accesses an optional attribute without a has guard. This causes Cedar to silently skip the policy for principals/resources missing the attribute." },
|
|
56
|
+
{ id: "ImpossiblePolicy", description: "Cedar determined the condition always evaluates to false — the policy can never match any request." },
|
|
57
|
+
{ id: "WrongNumberArguments", description: "Extension type function called with wrong number of arguments." },
|
|
58
|
+
{ id: "FunctionArgumentValidationError", description: "Argument to an extension type function could not be parsed (e.g. invalid IP address string)." },
|
|
59
|
+
] as const;
|
|
60
|
+
|
|
61
|
+
export type AvpValidationErrorId = typeof AVP_VALIDATION_ERRORS[number]["id"];
|
|
62
|
+
|
|
63
|
+
/** AVP immutability rules summary — for use in sampling prompts. */
|
|
64
|
+
export const AVP_RULES_SUMMARY = `
|
|
65
|
+
AVP UpdatePolicy constraints (verified from official API docs):
|
|
66
|
+
MUTABLE via UpdatePolicy: action scope, when/unless conditions, policy name.
|
|
67
|
+
IMMUTABLE (delete+recreate required): effect (permit↔forbid), principal scope, resource scope, static↔template-linked conversion.
|
|
68
|
+
NEW policies: use CreatePolicy API.
|
|
69
|
+
Template-linked policies: use UpdatePolicyTemplate, not UpdatePolicy.
|
|
70
|
+
`.trim();
|
|
@@ -0,0 +1,73 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Cedar design pattern definitions and classifier.
|
|
3
|
+
* Patterns proven from official docs 2026-05-20:
|
|
4
|
+
* https://docs.cedarpolicy.com/overview/patterns.html
|
|
5
|
+
*/
|
|
6
|
+
|
|
7
|
+
import type { PolicyJson } from "@cedar-policy/cedar-wasm/nodejs";
|
|
8
|
+
|
|
9
|
+
export type CedarPattern = "membership" | "relationship" | "discretionary" | "hybrid" | "unknown";
|
|
10
|
+
|
|
11
|
+
export interface PatternClassification {
|
|
12
|
+
pattern: CedarPattern;
|
|
13
|
+
confidence: "high" | "medium" | "low";
|
|
14
|
+
evidence: string;
|
|
15
|
+
}
|
|
16
|
+
|
|
17
|
+
/**
|
|
18
|
+
* Classify a single policy's primary Cedar pattern from its policyToJson AST.
|
|
19
|
+
*
|
|
20
|
+
* Membership (RBAC): principal in Role::"X" — scope uses "in" with a non-resource entity
|
|
21
|
+
* Relationship (ReBAC): principal in resource.attr — condition body has "in" with resource attribute
|
|
22
|
+
* Discretionary: principal == Service::"specific" — scope uses "==" (specific entity)
|
|
23
|
+
* Hybrid: combination of the above
|
|
24
|
+
*/
|
|
25
|
+
export function classifyPolicy(json: PolicyJson): PatternClassification {
|
|
26
|
+
const principalOp = json.principal.op;
|
|
27
|
+
const conditionsText = JSON.stringify(json.conditions);
|
|
28
|
+
|
|
29
|
+
// Relationship: condition body contains "principal in resource" — ReBAC
|
|
30
|
+
const hasReBAC = conditionsText.includes('"in"') &&
|
|
31
|
+
conditionsText.includes('"Var":"principal"') &&
|
|
32
|
+
conditionsText.includes('"Var":"resource"');
|
|
33
|
+
|
|
34
|
+
// Membership: principal scope is "in" (role/group membership, not via condition)
|
|
35
|
+
const isMembership = principalOp === "in" && !hasReBAC;
|
|
36
|
+
|
|
37
|
+
// Discretionary: principal scope is "==" (specific entity)
|
|
38
|
+
const isDiscretionary = principalOp === "==";
|
|
39
|
+
|
|
40
|
+
if (hasReBAC && isMembership) {
|
|
41
|
+
return { pattern: "hybrid", confidence: "medium", evidence: "principal scope uses 'in' (Membership) and conditions use principal-in-resource pattern (Relationship)" };
|
|
42
|
+
}
|
|
43
|
+
if (hasReBAC) {
|
|
44
|
+
return { pattern: "relationship", confidence: "high", evidence: "condition body contains principal-in-resource relationship check" };
|
|
45
|
+
}
|
|
46
|
+
if (isMembership) {
|
|
47
|
+
return { pattern: "membership", confidence: "high", evidence: `principal scope uses 'in' — group/role membership (RBAC)` };
|
|
48
|
+
}
|
|
49
|
+
if (isDiscretionary) {
|
|
50
|
+
return { pattern: "discretionary", confidence: "high", evidence: `principal scope uses '==' — specific entity grant (Discretionary)` };
|
|
51
|
+
}
|
|
52
|
+
if (principalOp === "All") {
|
|
53
|
+
return { pattern: "unknown", confidence: "low", evidence: "principal is unconstrained in scope — pattern determined entirely by conditions" };
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
return { pattern: "unknown", confidence: "low", evidence: `principal op '${principalOp}' not recognized` };
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
/** Cedar patterns summary — for use in sampling prompts. */
|
|
60
|
+
export const CEDAR_PATTERNS_SUMMARY = `
|
|
61
|
+
Cedar supports three primary design patterns (official docs):
|
|
62
|
+
|
|
63
|
+
1. MEMBERSHIP (RBAC): principal in Role::"group" — permissions follow group membership.
|
|
64
|
+
Example: permit(principal in Role::"editor", action in [...], resource);
|
|
65
|
+
|
|
66
|
+
2. RELATIONSHIP (ReBAC): principal in resource.owners — permissions follow resource relationships.
|
|
67
|
+
Example: permit(principal is User, action in [...], resource is Doc) when { principal in resource.owners };
|
|
68
|
+
|
|
69
|
+
3. DISCRETIONARY: principal == Service::"specific" — ad-hoc per-entity grants.
|
|
70
|
+
Example: permit(principal == Service::"Service-123", action == Action::"call", resource == Service::"Target");
|
|
71
|
+
|
|
72
|
+
All three can coexist in a policy store. Individual policies should follow one pattern.
|
|
73
|
+
`.trim();
|
|
@@ -0,0 +1,109 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Builds deterministic context from a policy store for cedar_advise sampling prompts.
|
|
3
|
+
* No LLM cost — pure AST walking and schema parsing.
|
|
4
|
+
*/
|
|
5
|
+
|
|
6
|
+
import { policyToJson, policySetTextToParts } from "@cedar-policy/cedar-wasm/nodejs";
|
|
7
|
+
import { storeManager, StoreManager } from "../../resources/store-manager.js";
|
|
8
|
+
import { classifyPolicy } from "./cedar-patterns.js";
|
|
9
|
+
|
|
10
|
+
export interface PolicyInventoryEntry {
|
|
11
|
+
policy_id: string;
|
|
12
|
+
pattern: string;
|
|
13
|
+
pattern_confidence: string;
|
|
14
|
+
summary: string;
|
|
15
|
+
policy_text: string;
|
|
16
|
+
}
|
|
17
|
+
|
|
18
|
+
export interface StoreContext {
|
|
19
|
+
store_name: string;
|
|
20
|
+
schema_text: string;
|
|
21
|
+
policy_inventory: PolicyInventoryEntry[];
|
|
22
|
+
policy_count: number;
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
/**
|
|
26
|
+
* Build structured context from a named policy store.
|
|
27
|
+
* Returns null if the store doesn't exist or has no content.
|
|
28
|
+
*/
|
|
29
|
+
export function buildStoreContext(storeName: string, manager: StoreManager = storeManager): StoreContext | null {
|
|
30
|
+
try {
|
|
31
|
+
const schema = manager.readSchema(storeName);
|
|
32
|
+
const inventory: PolicyInventoryEntry[] = [];
|
|
33
|
+
const policyIds = manager.listPolicies(storeName);
|
|
34
|
+
|
|
35
|
+
for (const id of policyIds) {
|
|
36
|
+
const content = manager.readPolicy(storeName, id);
|
|
37
|
+
try {
|
|
38
|
+
const parseResult = policyToJson(content.trim());
|
|
39
|
+
if (parseResult.type === "success") {
|
|
40
|
+
const classification = classifyPolicy(parseResult.json);
|
|
41
|
+
inventory.push({
|
|
42
|
+
policy_id: id,
|
|
43
|
+
pattern: classification.pattern,
|
|
44
|
+
pattern_confidence: classification.confidence,
|
|
45
|
+
summary: buildPolicySummary(id, parseResult.json.effect, classification.evidence),
|
|
46
|
+
policy_text: content.trim(),
|
|
47
|
+
});
|
|
48
|
+
} else {
|
|
49
|
+
inventory.push({
|
|
50
|
+
policy_id: id,
|
|
51
|
+
pattern: "unknown",
|
|
52
|
+
pattern_confidence: "low",
|
|
53
|
+
summary: `${id}: parse failed — ${parseResult.errors[0]?.message ?? "unknown error"}`,
|
|
54
|
+
policy_text: content.trim(),
|
|
55
|
+
});
|
|
56
|
+
}
|
|
57
|
+
} catch {
|
|
58
|
+
inventory.push({
|
|
59
|
+
policy_id: id,
|
|
60
|
+
pattern: "unknown",
|
|
61
|
+
pattern_confidence: "low",
|
|
62
|
+
summary: `${id}: could not analyze`,
|
|
63
|
+
policy_text: content.trim(),
|
|
64
|
+
});
|
|
65
|
+
}
|
|
66
|
+
}
|
|
67
|
+
|
|
68
|
+
return {
|
|
69
|
+
store_name: storeName,
|
|
70
|
+
schema_text: schema,
|
|
71
|
+
policy_inventory: inventory,
|
|
72
|
+
policy_count: policyIds.length,
|
|
73
|
+
};
|
|
74
|
+
} catch {
|
|
75
|
+
return null;
|
|
76
|
+
}
|
|
77
|
+
}
|
|
78
|
+
|
|
79
|
+
/** Resolve a store_ref to a store name. Accepts "mystore", "cedar://policies/mystore", etc. */
|
|
80
|
+
export function resolveStoreRef(storeRef: string): string {
|
|
81
|
+
if (storeRef.startsWith("cedar://")) {
|
|
82
|
+
const match = storeRef.match(/cedar:\/\/(?:policies|schema)\/([^/]+)/);
|
|
83
|
+
return match?.[1] ?? storeRef;
|
|
84
|
+
}
|
|
85
|
+
return storeRef;
|
|
86
|
+
}
|
|
87
|
+
|
|
88
|
+
function buildPolicySummary(id: string, effect: string, evidence: string): string {
|
|
89
|
+
return `${id} (${effect}, ${evidence})`;
|
|
90
|
+
}
|
|
91
|
+
|
|
92
|
+
/** Format store context as a prompt section. */
|
|
93
|
+
export function formatContextForPrompt(ctx: StoreContext): string {
|
|
94
|
+
const lines: string[] = [
|
|
95
|
+
`Store: "${ctx.store_name}" (${ctx.policy_count} policies)`,
|
|
96
|
+
"",
|
|
97
|
+
"Schema:",
|
|
98
|
+
ctx.schema_text,
|
|
99
|
+
"",
|
|
100
|
+
"Existing policies:",
|
|
101
|
+
];
|
|
102
|
+
|
|
103
|
+
for (const p of ctx.policy_inventory) {
|
|
104
|
+
lines.push(` - ${p.policy_id}: pattern=${p.pattern} (${p.pattern_confidence} confidence) — ${p.summary}`);
|
|
105
|
+
lines.push(` text: ${p.policy_text}`);
|
|
106
|
+
}
|
|
107
|
+
|
|
108
|
+
return lines.join("\n");
|
|
109
|
+
}
|
|
@@ -0,0 +1,92 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Cedar / AVP gotcha catalog.
|
|
3
|
+
* Drawn from 03-cedar-developer-guide.md and the AVP validation error categories.
|
|
4
|
+
*/
|
|
5
|
+
|
|
6
|
+
export interface Gotcha {
|
|
7
|
+
id: string;
|
|
8
|
+
severity: "high" | "medium" | "info";
|
|
9
|
+
description: string;
|
|
10
|
+
avp_error_category?: string;
|
|
11
|
+
keywords: string[]; // Used to match gotchas to intent keywords
|
|
12
|
+
}
|
|
13
|
+
|
|
14
|
+
export const GOTCHA_CATALOG: Gotcha[] = [
|
|
15
|
+
{
|
|
16
|
+
id: "optional_attribute_guard",
|
|
17
|
+
severity: "high",
|
|
18
|
+
description: "Optional schema attributes MUST be guarded with `entity has attr` before access. Without the guard, Cedar silently skips the policy for any entity missing the attribute — no error, just no match. This is one of the most common silent bugs in Cedar.",
|
|
19
|
+
avp_error_category: "UnsafeOptionalAttributeAccess",
|
|
20
|
+
keywords: ["optional", "has", "attribute", "guard", "email", "verified", "nullable"],
|
|
21
|
+
},
|
|
22
|
+
{
|
|
23
|
+
id: "forbid_overrides_permit",
|
|
24
|
+
severity: "high",
|
|
25
|
+
description: "A single matching `forbid` policy overrides ALL matching `permit` policies. There is no priority or weight system. Use `unless` to create exemptions inside the forbid itself.",
|
|
26
|
+
keywords: ["forbid", "block", "deny", "sensitive", "secret", "top_secret", "restrict"],
|
|
27
|
+
},
|
|
28
|
+
{
|
|
29
|
+
id: "avp_in_place_limitation",
|
|
30
|
+
severity: "high",
|
|
31
|
+
description: "AVP UpdatePolicy cannot change: effect (permit↔forbid), principal scope, or resource scope. These require deleting the old policy and creating a new one. Plan your deployment accordingly.",
|
|
32
|
+
avp_error_category: "ResourceNotFoundException",
|
|
33
|
+
keywords: ["change", "update", "modify", "rename", "role", "principal", "resource", "effect"],
|
|
34
|
+
},
|
|
35
|
+
{
|
|
36
|
+
id: "like_wildcard_crosses_slash",
|
|
37
|
+
severity: "medium",
|
|
38
|
+
description: "Cedar's `like` wildcard `*` matches ANY character sequence including `/`. To limit path depth, combine a positive `like` with a negated `like` for deeper patterns. Example: `resource.path like '/api/v1/*' && !(resource.path like '/api/v1/*/*')`.",
|
|
39
|
+
keywords: ["path", "like", "url", "endpoint", "api", "depth", "wildcard", "route"],
|
|
40
|
+
},
|
|
41
|
+
{
|
|
42
|
+
id: "array_containment_syntax",
|
|
43
|
+
severity: "medium",
|
|
44
|
+
description: "Cedar array containment is left-first: `[\"a\", \"b\"].contains(attr)` — the ARRAY is on the left. Writing `attr in [\"a\", \"b\"]` is an entity-hierarchy check, not a value containment check.",
|
|
45
|
+
keywords: ["contains", "list", "array", "set", "allowlist", "in"],
|
|
46
|
+
},
|
|
47
|
+
{
|
|
48
|
+
id: "schema_first_then_policy",
|
|
49
|
+
severity: "medium",
|
|
50
|
+
description: "Schema changes must be deployed BEFORE policies that reference new attributes or types. AVP validates policies against the current schema at creation/update time. Deploying a policy before its schema change causes UnrecognizedEntityType or MissingAttribute errors.",
|
|
51
|
+
avp_error_category: "MissingAttribute",
|
|
52
|
+
keywords: ["schema", "attribute", "entity", "add", "new", "field"],
|
|
53
|
+
},
|
|
54
|
+
{
|
|
55
|
+
id: "default_deny",
|
|
56
|
+
severity: "info",
|
|
57
|
+
description: "Cedar is default-deny. A request is denied unless at least one `permit` policy explicitly matches AND no `forbid` policy matches. There is no 'allow by default' mode.",
|
|
58
|
+
keywords: ["deny", "block", "default", "access"],
|
|
59
|
+
},
|
|
60
|
+
{
|
|
61
|
+
id: "rebac_migration_data",
|
|
62
|
+
severity: "medium",
|
|
63
|
+
description: "Migrating from RBAC to ReBAC (relationship-based) requires populating relationship attributes on existing resources BEFORE deploying the new policy. Without the data, the policy will deny all access for resources without the relationship attribute.",
|
|
64
|
+
keywords: ["owner", "owners", "relationship", "rebac", "migrate", "per-document", "per-resource"],
|
|
65
|
+
},
|
|
66
|
+
{
|
|
67
|
+
id: "avp_single_namespace",
|
|
68
|
+
severity: "info",
|
|
69
|
+
description: "AVP policy stores support only one namespace per schema. If your Cedar policies use multiple namespaces locally, they cannot be deployed to a single AVP policy store.",
|
|
70
|
+
keywords: ["namespace", "avp", "deploy"],
|
|
71
|
+
},
|
|
72
|
+
{
|
|
73
|
+
id: "action_groups_not_automatic",
|
|
74
|
+
severity: "medium",
|
|
75
|
+
description: "Action group entities must be included in the entity list when evaluating locally. AVP adds them automatically, but local evaluation via WASM (and this tool) does not. Pass action group entities explicitly in your entity list.",
|
|
76
|
+
keywords: ["action", "group", "batch", "evaluate", "test"],
|
|
77
|
+
},
|
|
78
|
+
];
|
|
79
|
+
|
|
80
|
+
/** Select relevant gotchas by matching intent keywords. */
|
|
81
|
+
export function selectGotchas(intent: string, maxCount = 5): Gotcha[] {
|
|
82
|
+
const lower = intent.toLowerCase();
|
|
83
|
+
const scored = GOTCHA_CATALOG.map(g => ({
|
|
84
|
+
gotcha: g,
|
|
85
|
+
score: g.keywords.filter(kw => lower.includes(kw)).length,
|
|
86
|
+
}));
|
|
87
|
+
return scored
|
|
88
|
+
.filter(s => s.score > 0)
|
|
89
|
+
.sort((a, b) => b.score - a.score || (a.gotcha.severity === "high" ? -1 : 1))
|
|
90
|
+
.slice(0, maxCount)
|
|
91
|
+
.map(s => s.gotcha);
|
|
92
|
+
}
|