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

provider_enum_two_namespaces

The same Vendor::ThreatScan provider and Vendor::Severity enum as provider_enum_severity_forbid, with an action schema that has actions in two namespaces, App and Audit. One unscoped guardrail covers both:

@id("forbid_severe")
forbid (principal, action, resource) when {
    Vendor::ThreatScan(context.input.document).severity
        in [Vendor::Severity::"HIGH", Vendor::Severity::"CRITICAL"]
};

The enum is exported once, into the first namespace (App::Vendor_Severity), and Audit’s actions refer to it by its full name. So the forbid fires on both namespaces:

  • @0 App::Read "hello" → ALLOW
  • @10 App::Read "malware here" → DENY
  • @20 Audit::Inspect "hello" → ALLOW
  • @30 Audit::Inspect "malware here" → DENY

Referenced by guide/05-information-providers.md.

Policy

@id("permit_all")
permit (principal, action, resource);

// One unscoped guardrail for every action, in both namespaces.
@id("forbid_severe")
forbid (principal, action, resource)
when {
    Vendor::ThreatScan(context.input.document).severity
        in [Vendor::Severity::"HIGH", Vendor::Severity::"CRITICAL"]
};

Schema

namespace App {
  type ReadInput = { document: String };
  entity User;
  entity Doc;
  action "Read" appliesTo { principal: [User], resource: [Doc], context: { input: ReadInput } };
}

namespace Audit {
  type ReadInput = { document: String };
  entity User;
  entity Doc;
  action "Inspect" appliesTo { principal: [User], resource: [Doc], context: { input: ReadInput } };
}

Trace

@0 scope(principal: App::User::"alice", resource: App::Doc::"d1") request_context(input: { document: "hello" }) App::Action::"Read"::request(input: { document: "hello" }, callerPrincipal: App::User::"alice", callerResource: App::Doc::"d1", requestId: "u1")
@10 scope(principal: App::User::"alice", resource: App::Doc::"d1") request_context(input: { document: "malware here" }) App::Action::"Read"::request(input: { document: "malware here" }, callerPrincipal: App::User::"alice", callerResource: App::Doc::"d1", requestId: "u2")
@20 scope(principal: Audit::User::"bob", resource: Audit::Doc::"d2") request_context(input: { document: "hello" }) Audit::Action::"Inspect"::request(input: { document: "hello" }, callerPrincipal: Audit::User::"bob", callerResource: Audit::Doc::"d2", requestId: "u3")
@30 scope(principal: Audit::User::"bob", resource: Audit::Doc::"d2") request_context(input: { document: "malware here" }) Audit::Action::"Inspect"::request(input: { document: "malware here" }, callerPrincipal: Audit::User::"bob", callerResource: Audit::Doc::"d2", requestId: "u4")

Expected Output

@0 (time point 0): ALLOW  [rules: 0]
@10 (time point 1): DENY  [rules: 1]
@20 (time point 2): ALLOW  [rules: 0]
@30 (time point 3): DENY  [rules: 1]

Provider Declarations

{
  "entityTypes": {
    "Vendor::Severity": {
      "enum": [
        "LOW",
        "MEDIUM",
        "HIGH",
        "CRITICAL"
      ]
    }
  },
  "availableProviders": {
    "Vendor::ThreatScan": {
      "argumentTypes": [
        {
          "paramType": "string"
        }
      ],
      "outputType": {
        "paramType": "record",
        "fields": {
          "severity": {
            "paramType": "entity",
            "entityType": "Vendor::Severity"
          }
        },
        "required": [
          "severity"
        ]
      },
      "implementation": {
        "kind": "rhai",
        "script": "// Vendor::ThreatScan(text) -> { severity: Vendor::Severity }.\n// Returns the bare enum id; Dogwood converts it to the enum entity and denies\n// the request if the id isn't one of the declared values.\nfn evaluate(text) {\n    // A provider may be evaluated for ANY decision event, so the argument may\n    // be absent (unit). Return the most restrictive value.\n    if type_of(text) == \"()\" { return #{ severity: \"CRITICAL\" }; }\n    let t = to_lower(text);\n    if t.contains(\"ransomware\") { #{ severity: \"CRITICAL\" } }\n    else if t.contains(\"malware\") { #{ severity: \"HIGH\" } }\n    else if t.contains(\"scan\") { #{ severity: \"MEDIUM\" } }\n    else if t.contains(\"garbled\") { #{ severity: \"SEVERE\" } }   // not a declared value\n    else { #{ severity: \"LOW\" } }\n}\n"
      }
    }
  }
}