forbid_large_except_amzn
Mixing when and unless on a forbid rule: block large SellShares
(context.input.shares > 100), but carve out an exemption for AMZN
(unless { context.input.stock == "AMZN" }).
Because this bundle has only a forbid rule and no permit, every request is
denied — there is nothing that can produce an allow. What the trace shows is
why each request is denied:
@0— alice sells 500 MSFT → the forbid fires (large, not AMZN) →DENY [rules: 0](actively blocked by the rule).@100— alice sells 500 AMZN →unlessexempts AMZN, so the forbid does not fire →DENY(Cedar’s default deny; nopermitapplies).@200— bob sells 50 MSFT →when { shares > 100 }is false, so the forbid does not fire →DENY(below the threshold; again default deny).
The [rules: …] annotation distinguishes an actively forbidden request from
one that simply falls through to the default deny.
Referenced by guide/02-policy-language.md — The Policy Language.
Policy
// Forbid large sells except for AMZN (when + unless on a forbid rule).
@id("forbid_large_except_amzn")
forbid ( principal, action == Drupe::Action::"SellShares", resource )
when { context.input.shares > 100 }
unless { context.input.stock == "AMZN" };
Schema
namespace Drupe {
type ApproveSaleInput = {
shares: Long,
stock: String
};
type ApproveSaleOutput = {
approved: Bool
};
type ContentFilterFinding = {
severityScore: decimal
};
type GetStockInfoInput = {
stock: String
};
type GetStockInfoOutput = {
info: String
};
type PromptAttackFinding = {
severityScore: decimal
};
type SellSharesInput = {
shares: Long,
stock: String
};
type SellSharesOutput = {
proceeds: decimal
};
type SensitiveInfoFinding = {
confidenceScore: decimal
};
type SystemContext = {
now: datetime
};
entity Gateway;
entity IamEntity = {
id: String
};
entity OAuthUser = {
id: String
} tags String;
entity UnauthenticatedUser;
action "ApproveSale" in [Action::"CallTool"] appliesTo {
principal: [IamEntity, OAuthUser, UnauthenticatedUser],
resource: [Gateway],
context: {
input: ApproveSaleInput,
output?: ApproveSaleOutput,
system: SystemContext
}
};
action "CallTool" in [Action::"Mcp"] appliesTo {
principal: [OAuthUser, IamEntity, UnauthenticatedUser],
resource: [Gateway],
context: {
system: SystemContext
}
};
action "GetStockInfo" in [Action::"CallTool"] appliesTo {
principal: [IamEntity, OAuthUser, UnauthenticatedUser],
resource: [Gateway],
context: {
input: GetStockInfoInput,
output?: GetStockInfoOutput,
system: SystemContext
}
};
action "Http" appliesTo {
principal: [OAuthUser, IamEntity, UnauthenticatedUser],
resource: [Gateway],
context: {
system: SystemContext
}
};
action "InvokeAgent" in [Action::"Http"] appliesTo {
principal: [OAuthUser, IamEntity, UnauthenticatedUser],
resource: [Gateway],
context: {
input?: { },
system: SystemContext
}
};
action "InvokeLLM" in [Action::"Http"] appliesTo {
principal: [OAuthUser, IamEntity, UnauthenticatedUser],
resource: [Gateway],
context: {
input?: { },
system: SystemContext
}
};
action "Mcp" appliesTo {
principal: [OAuthUser, IamEntity, UnauthenticatedUser],
resource: [Gateway],
context: {
system: SystemContext
}
};
action "SellShares" in [Action::"CallTool"] appliesTo {
principal: [IamEntity, OAuthUser, UnauthenticatedUser],
resource: [Gateway],
context: {
input: SellSharesInput,
output?: SellSharesOutput,
system: SystemContext
}
};
action "UnknownTool" in [Action::"CallTool"] appliesTo {
principal: [OAuthUser, IamEntity, UnauthenticatedUser],
resource: [Gateway],
context: {
system: SystemContext
}
};
}
Trace
@0 scope(principal: Drupe::OAuthUser::"alice", resource: Drupe::Gateway::"gw1") request_context(input: { shares: 500, stock: "MSFT" }) Drupe::Action::"SellShares"::request(input: { shares: 500, stock: "MSFT" }, callerPrincipal: Drupe::OAuthUser::"alice", callerResource: Drupe::Gateway::"gw1", requestId: "u1")
@100 scope(principal: Drupe::OAuthUser::"alice", resource: Drupe::Gateway::"gw1") request_context(input: { shares: 500, stock: "AMZN" }) Drupe::Action::"SellShares"::request(input: { shares: 500, stock: "AMZN" }, callerPrincipal: Drupe::OAuthUser::"alice", callerResource: Drupe::Gateway::"gw1", requestId: "u2")
@200 scope(principal: Drupe::OAuthUser::"bob", resource: Drupe::Gateway::"gw1") request_context(input: { shares: 50, stock: "MSFT" }) Drupe::Action::"SellShares"::request(input: { shares: 50, stock: "MSFT" }, callerPrincipal: Drupe::OAuthUser::"bob", callerResource: Drupe::Gateway::"gw1", requestId: "u3")
Expected Output
@0 (time point 0): DENY [rules: 0]
@100 (time point 1): DENY
@200 (time point 2): DENY