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

heartbeat_scope_alias

formerly with scope-alias correlation. Permit an Alert only if a Heartbeat for the same server and the same request scope fired within the last hour (formerly within 1h).

The scope aliases context.principal / context.resource resolve to the current request’s scope entities and are pinned against the reserved event fields callerPrincipal / callerResource, so a heartbeat only counts if it came from the same principal on the same resource — not just any heartbeat for that server.

The trace shows both outcomes:

  • @0machine-a sends a Heartbeat for s1 (history-only event; no Alert permit applies, so the decision is a deny).
  • @100machine-a raises an Alert for s1allow (its own heartbeat, same server, same principal/resource, is within the window).
  • @200machine-b raises an Alert for s1deny: the server matches, but the scope-alias pins on callerPrincipal fail (the only heartbeat came from machine-a).

Referenced by guide/04-temporal-expressions.md.

Policy

// Scope correlation: `principal`/`resource` are the request's scope entities
// (first-class temporal term roots, matching Cedar) and pin against the
// reserved event fields callerPrincipal / callerResource. Permit an Alert
// only if a Heartbeat for the same server, principal, and resource fired within
// the last hour.
@id("alert_recent_heartbeat")
permit (
    principal,
    action == Drupe::Action::"Alert",
    resource
)
when temporal {
    formerly within 1h Drupe::Action::"Heartbeat"::request{
        input.server: context.input.server,
        callerPrincipal: principal,
        callerResource: resource
    }
};

Schema

namespace Drupe {
  type AlertInput = {
    level: Long,
    server: String
  };

  type AlertOutput = {  };

  type ContentFilterFinding = {
    severityScore: decimal
  };

  type HeartbeatInput = {
    server: String
  };

  type HeartbeatOutput = {  };

  type LoginInput = {
    server: String,
    user: String
  };

  type LoginOutput = {  };

  type PromptAttackFinding = {
    severityScore: decimal
  };

  type ReadInput = {
    document: String,
    user: String
  };

  type ReadOutput = {  };

  type SensitiveInfoFinding = {
    confidenceScore: decimal
  };

  type SystemContext = {
    now: datetime
  };

  entity Gateway;

  entity IamEntity = {
    id: String
  };

  entity OAuthUser = {
    id: String
  } tags String;

  entity UnauthenticatedUser;

  action "Alert" in [Action::"CallTool"] appliesTo {
    principal: [IamEntity, OAuthUser, UnauthenticatedUser],
    resource: [Gateway],
    context: {
      input: AlertInput,
      output?: AlertOutput,
      system: SystemContext
    }
  };

  action "CallTool" in [Action::"Mcp"] appliesTo {
    principal: [OAuthUser, IamEntity, UnauthenticatedUser],
    resource: [Gateway],
    context: {
      system: SystemContext
    }
  };

  action "Heartbeat" in [Action::"CallTool"] appliesTo {
    principal: [IamEntity, OAuthUser, UnauthenticatedUser],
    resource: [Gateway],
    context: {
      input: HeartbeatInput,
      output?: HeartbeatOutput,
      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 "Login" in [Action::"CallTool"] appliesTo {
    principal: [IamEntity, OAuthUser, UnauthenticatedUser],
    resource: [Gateway],
    context: {
      input: LoginInput,
      output?: LoginOutput,
      system: SystemContext
    }
  };

  action "Mcp" appliesTo {
    principal: [OAuthUser, IamEntity, UnauthenticatedUser],
    resource: [Gateway],
    context: {
      system: SystemContext
    }
  };

  action "Read" in [Action::"CallTool"] appliesTo {
    principal: [IamEntity, OAuthUser, UnauthenticatedUser],
    resource: [Gateway],
    context: {
      input: ReadInput,
      output?: ReadOutput,
      system: SystemContext
    }
  };

  action "UnknownTool" in [Action::"CallTool"] appliesTo {
    principal: [OAuthUser, IamEntity, UnauthenticatedUser],
    resource: [Gateway],
    context: {
      system: SystemContext
    }
  };
}

Trace

@0 scope(principal: Drupe::OAuthUser::"machine-a", resource: Drupe::Gateway::"gw1") request_context(input: { server: "s1" }) Drupe::Action::"Heartbeat"::request(input: { server: "s1" }, callerPrincipal: Drupe::OAuthUser::"machine-a", callerResource: Drupe::Gateway::"gw1", requestId: "u1")
@100 scope(principal: Drupe::OAuthUser::"machine-a", resource: Drupe::Gateway::"gw1") request_context(input: { level: 3, server: "s1" }) Drupe::Action::"Alert"::request(input: { level: 3, server: "s1" }, callerPrincipal: Drupe::OAuthUser::"machine-a", callerResource: Drupe::Gateway::"gw1", requestId: "u2")
@200 scope(principal: Drupe::OAuthUser::"machine-b", resource: Drupe::Gateway::"gw1") request_context(input: { level: 2, server: "s1" }) Drupe::Action::"Alert"::request(input: { level: 2, server: "s1" }, callerPrincipal: Drupe::OAuthUser::"machine-b", callerResource: Drupe::Gateway::"gw1", requestId: "u3")

Expected Output

@0 (time point 0): DENY
@100 (time point 1): ALLOW  [rules: 0]
@200 (time point 2): DENY