Enterprise · Control Plane

Authoring & Customizing a Policy

A policy is a signed YAML overlay an org admin manages in the dashboard. Enrolled devices pull it on top of the bundled default policy and apply it to every AI agent action. This guide covers how a rule works, when to observe versus enforce, how to add or disable detection patterns, the security floor that keeps you safe from mistakes, and how a published policy reaches your devices.

Overview

Every Prismor device ships with a bundled default policy of 81 rules. A policy profile is a signed YAML overlay you author in the dashboard under Policy & Exemptions → Policy profiles. When you publish a profile, enrolled devices pull it (roughly every 30 seconds) and apply it on top of the defaults — you only describe what changes, not the whole rule set.

The overlay does three things: it sets a baseline mode, it can flip individual rules between observe and enforce, and — the focus of this page — it can customize the detection patterns inside a rule by adding your own and disabling defaults you don't want.

The overlay is signed. Devices verify the signature before applying it, so a tampered or unsigned profile is rejected and the device keeps running the last good policy.

How a Rule Works

A rule is a named bundle of regex patterns OR'd together. Each pattern is checked against the candidate action (for example, a shell command about to run). If any pattern matches, the rule fires — you do not need every pattern to match, just one.

A rule = many regexes, ANY match fires
rule: remote-execution
  pattern A:  curl ... | sh        ──┐
  pattern B:  sh <(curl ...)         ├─ OR  ──▶  ANY match → rule fires
  pattern C:  eval ... curl ...    ──┘

A single command only has to match ONE of these to trigger the rule.

Customizing a rule therefore means adjusting that OR-set of patterns: making it broader by adding patterns, or narrower by disabling defaults. You never rewrite the whole list.

Observe vs Enforce

When a rule fires, the resulting finding is handled in one of two modes:

observe

Detect and log the finding. The action still runs. This is the default — you start by watching, not blocking.

enforce

Block the action on the pre-action tool call. The agent is denied before the command runs. Flip a rule here once you trust its precision.

settings.default_mode sets the baseline for the whole policy, and a per-rule mode overrides it for that one rule. The recommended path is to leave the default at observe, watch the findings roll in, then flip individual rules to enforce once you're confident they won't false-positive on your team.

Two things enforce no matter what default_mode says: the self-protection rules, and the security floor (core rules, and blocking rules in a core category such as remote-execution). A per-device observe/enforce switch in the console sits above a rule's mode for everything else.

Flip one rule to enforce, keep the rest observing
settings:
  default_mode: observe        # non-floor rules detect + log

rules:
  - id: db-modification
    mode: enforce              # this one rule blocks

Adding Custom Patterns

Use add_patterns to strengthen a rule with regexes specific to your environment — an internal hostname, a deploy script you never want piped to a shell, a credential format unique to your org.

  • Patterns use the Python re flavor and are compiled with IGNORECASE + DOTALL.
  • Quote each pattern in YAML single-quotes and escape accordingly — this keeps backslashes literal so your regex survives the YAML parser intact.
  • Each pattern compiles in isolation. If one is invalid, the device drops that pattern with a warning and keeps the rest of the rule working — a bad custom pattern never breaks the rule.
add_patterns
rules:
  - id: cloud-metadata-ssrf
    add_patterns:
      - '100\.100\.100\.200'            # extend the rule's OR-set
      - 'metadata\.internal\.acme\.io'   # an internal host we want flagged

Disabling a Default Pattern

Use disable_patterns to turn off a specific default pattern that's too noisy for your workflow. You reference a default by its exact regex string — copy it verbatim from the rule's pattern list in the editor.

A string in disable_patterns that doesn't match any current default is a harmless no-op. This is deliberate: if a default pattern is renamed or removed upstream, your stale disable entry simply stops doing anything rather than silently weakening a rule. Drift always fails toward more detection, never less.

disable_patterns
rules:
  - id: disable-security-controls
    disable_patterns:
      # paste the default's regex EXACTLY as it appears in the editor
      - '\biptables\s+-F\b'

The effective pattern set for a rule is defaults − disabled + custom, de-duplicated and order-stable. You never write a patterns: key to customize a default rule — only add_patterns and disable_patterns.

The Security Floor

Customization is powerful, so two guardrails make it impossible to accidentally disarm your own protection.

Core rules are add-only

A small set of rules forms the non-negotiable core. On these, disable_patterns is rejected and their default patterns can never be replaced. You may still add_patterns to make them stronger — you just can't weaken them. On device, the same add-only floor also covers any blocking rule whose category is a core block category (for example remote-execution or credential-in-header).

The last few are Prismor's self-protection rules: they guard Prismor's own configuration and always enforce, in every mode, on every device — they are what stops an agent from editing the policy that governs it. The one sanctioned exception is the password-gated prismor unlock window a human can open on the device (3 minutes by default); admins can disable that window per device, or cap its length, from the device drawer — the setting ships in the signed policy bundle like everything else that relaxes enforcement.

Core ruleCovers
destructive-commandrm -rf, disk wipes, irreversible deletes
secret-exfiltrationpiping credentials to remote hosts
rce-canaryremote code execution probes
privilege-escalationsudo abuse, setuid, capability grabs
dos-resource-exhaustionfork bombs, fill-the-disk, runaway loops
tool-category-crossovera tool acting outside its declared category
audit-trail-tamperingedits to Prismor’s own session logs and evidence chain
memory-integrity-mismatchan auto-loaded instruction file changed since its approved baseline
agent-config-tamperingan agent rewriting its own hook or policy config
agent-config-tampering-patha write aimed at the agent settings or .prismor/policy.yaml file by path
prismor-self-editan agent running prismor allow/unlock/pause, or touching the unlock credential

A rule can never reach zero patterns

If your edits would leave a rule with no active patterns at all, the console blocks the save. And as a runtime backstop, a device that somehow receives a rule resolving to zero patterns falls back to the full defaults for that rule rather than running blind.

Both guardrails fail toward more protection. The worst outcome of a bad edit is that a rule keeps running its bundled defaults — never that it silently stops detecting.

Editing in the Web Console

Most editing happens visually in the policy editor. Expand a rule to see all of its default patterns:

  • Each default pattern has a Disable / Enable toggle. For the five core rules the toggle is locked — you can't turn a core default off.
  • An "add custom pattern" box lets you type a new regex with live validation. An invalid regex turns red and the Add button stays disabled until it's valid.
  • The YAML below the toggles is generated from your choices and is the source of truth. It's validated as you go — green when it parses, red when it doesn't.

Toggling a default off writes its exact regex into disable_patterns for you, and adding a custom pattern appends it to add_patterns — so the visual editor and the YAML stay in sync automatically.

Conditions: who, on what, with which arguments

A pattern says what a call looks like. An Only when… condition (when: in YAML) says who is making it, against what, and with which arguments. The rule fires only when its pattern matches and the condition holds.

Refunds of 500 or more need the finance role
- id: refund-cap
  title: Refunds of 500 or more need the finance role
  severity: HIGH
  category: custom
  event_types: [shell]
  fields: [tool_name]
  patterns: ['^refund_order$']
  when: "args.amount >= 500 and 'finance' not in principal.roles"
  action: block
  • principal.* is the end user: id, team, roles, verified. args.* is the tool call's arguments, resource.* its target, and tool.name the tool.
  • Use and or not, comparisons, in, lists and has(path). The editor checks the expression as you type, using the same grammar the device runs.
  • A value the call doesn't carry makes the condition hold, so the rule still fires. Guard optional fields with has().
  • Core rules can't take a condition, for the same reason they can't lose patterns.

A caller can say which user it is acting for, but it can't grant itself a role: roles only come from a verified identity token. A rule that relaxes on principal.id or principal.team is only as strong as that claim, so the editor offers to add not principal.verified or (…).

Try it in the playground

Policies → Playground runs a tool call against a policy's conditional rules. Pick a policy, enter the tool, its arguments, the caller and the target, and see which rules fire and why. Presets cover the common callers: a verified user, a verified finance user, a caller that only asserts who it is, and an anonymous one.

Each scenario can be copied as a prismor policy test case, so the behaviour you checked by hand is checked again in CI.

Name a condition once, reuse it

Conditions you write in several rules can be named under settings.conditions and used by name, like a derived role:

Named conditions
settings:
  conditions:
    is_finance: "'finance' in principal.roles"
    is_owner: "resource.attr.owner == principal.id"
rules:
  - id: refund-cap
    # ...
    when: "args.amount >= 500 and not is_finance"

Tests that run before every publish

In the playground, Add to this policy's tests stages the scenario into the policy's draft. From then on, Publish runs the policy's tests against the version about to ship, and a failing test blocks it until you fix the policy or tick Publish anyway. The same tests run in CI with prismor policy test --policy, with shared fixtures and one expected verdict per caller:

tests: inside the policy
fixtures:
  principals:
    bob:   {id: bob, roles: [support], verified: true}
    carol: {id: carol, roles: [finance], verified: true}
tests:
  - name: refunds over 500
    tool: refund_order
    args: {amount: 900}
    expect: {bob: block, carol: pass}

The playground simulates conditional rules only. Built-in detections, egress and the semantic judge run on the device. Full reference: Attribute conditions.

Editing the YAML Directly

You can also edit the YAML by hand. The keys you'll use:

KeyValuePurpose
settings.default_modeobserve | enforceBaseline mode applied to every rule unless a rule overrides it. Defaults to observe when unset.
rules[].idrule idWhich default rule this entry customizes. rules is a list; each entry names its rule with id.
rules[].modeobserve | enforcePer-rule override. Flip a single rule to enforce while the rest stay observe.
rules[].add_patterns[regex, ...]Extra custom regexes added to the rule. Python re flavor, IGNORECASE + DOTALL.
rules[].disable_patterns[regex, ...]Default patterns to turn off, referenced by their exact regex string. Rejected on core rules.

A complete example: an observe baseline, one non-core rule that adds custom patterns and disables a default, and one core rule that adds patterns only.

policy.yaml - full example
# Org policy overlay - applied on top of the bundled default policy.
# You never write a 'patterns:' key here. To customize a default rule,
# use add_patterns (strengthen) and/or disable_patterns (turn off).

settings:
  default_mode: observe        # non-floor rules detect + log unless flipped to enforce

rules:                         # a list: each entry names the rule it customizes by id

  # --- A non-core rule: strengthen it and turn off one noisy default ---
  - id: disable-security-controls
    mode: enforce              # block instead of just logging
    add_patterns:
      - 'spctl\s+--master-disable'             # macOS Gatekeeper off
      - 'systemctl\s+stop\s+osqueryd'         # our fleet agent
    disable_patterns:
      # Copy these strings VERBATIM from the rule's default pattern list.
      - '\biptables\s+-F\b'                  # our CI image flushes iptables on boot; drop the noise

  # --- A core rule: add-only. disable_patterns here is REJECTED. ---
  - id: destructive-command
    add_patterns:
      - 'shred\s+-[a-z]*u'                     # our extra: catch shred -u
      - 'dd\s+if=/dev/zero\s+of=/dev/sd[a-z]'

Layering & Precedence

Policy is resolved from four layers, lowest to highest precedence:

LayerPrecedenceSource
defaultlowestThe bundled default policy every device ships with
projectlowPer-project .prismor/policy.yaml in the workspace
remote (org)middleThe signed org policy profile managed in the dashboard
repo-exemptionhighestAdmin-granted, repo-scoped exemptions layered on top

For pattern customization, the key rule is that add_patterns and disable_patterns are unioned across all layers. A later layer can add patterns and disable defaults, but it cannot wipe an earlier layer's additions — once a custom pattern is added at any layer, it stays in the effective set.

To see which layer governs a given action on a device, run prismor check '<command>' --explain. Each finding shows defined by: (the default, project, remote or exemption layer that last wrote the rule) and a mode: line naming the lever that decided whether it blocks or only reports.

Because adds are unioned and never wiped, layering is purely additive for detection. The most a higher layer can do is loosen mode (via an exemption) or disable a non-core default — it can never remove protection another layer contributed.

Validation & Troubleshooting

JavaScript validation vs Python execution

The console validates your custom regex with JavaScript's RegExp, but the device compiles it with Python's re. The two flavors overlap heavily, but they aren't identical. If a pattern is valid in the browser but invalid in Python, the device drops just that one pattern (with a warning) and keeps the rest of the rule running. So a pattern that looked green in the editor can still be skipped on-device — check the device warnings if a custom pattern never seems to fire.

A disable that does nothing

If a disable_patterns entry doesn't turn anything off, the string didn't match a current default exactly. Re-copy the regex verbatim from the expanded rule in the editor — a single different character (or an extra escape) means no match, and the entry is treated as a harmless no-op.

Save blocked: zero patterns

If the console won't let you save, you've likely disabled every default in a rule without adding a replacement. Either re-enable at least one default or add a custom pattern so the rule has something to match. Remember core rules can't be emptied at all — their disable toggles are locked.

How It Reaches Devices

Once you publish a profile, delivery is automatic:

Publish → pull → apply
1. Admin saves & publishes the profile in Policy & Exemptions.
   The control plane signs the YAML overlay.

2. Each enrolled device polls (~30s) and pulls the signed overlay.

3. The device verifies the signature, then layers the overlay on top
   of the bundled defaults:
      effective patterns = defaults − disabled + custom   (per rule)

4. If a rule would resolve to zero patterns, the device falls back to
   that rule's full defaults instead of running blind.

5. The new policy takes effect on the next agent action - no restart.

Because the pull interval is short, an edit you publish is live across your fleet within roughly half a minute — you can flip a rule to enforce, watch the telemetry, and roll it back just as quickly if needed.

Frequently asked questions

What is the difference between observe mode and enforce mode?

Observe mode logs what a rule would have blocked without actually blocking it, so you can measure impact before turning enforcement on. Enforce mode blocks the matching tool call. The usual rollout is to run in observe first, review what it flags, then switch to enforce.

How do I add a custom detection pattern to my Prismor policy?

Use add_patterns in policy.yaml to define your own regex patterns alongside the built-in rules. You can author this in the web console or edit the YAML directly, then publish it so enrolled devices pick it up.

Can I disable a default Prismor detection pattern?

Yes, with disable_patterns, except for anything in the core security floor. The security floor is the set of protections that cannot be disabled by a project overlay, so a local override cannot weaken the organization baseline.

How does policy layering and precedence work?

Prismor layers an org-level policy floor with project-level overlays and time-boxed exemptions. The org floor sets the minimum that projects cannot relax, overlays add stricter or project-specific rules on top, and precedence rules decide which wins when they conflict.

How does a published policy reach enrolled devices?

Policies are signed and distributed to enrolled devices, which pull the signed policy from the control plane. The signature is what lets a device trust that the policy really came from your organization.