Keyboard shortcuts

Press or to navigate between chapters

Press S or / to search in the book

Press ? to show this help

Press Esc to hide this help

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 → unless exempts AMZN, so the forbid does not fire → DENY (Cedar’s default deny; no permit applies).
  • @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