Skip to content

Create custom policies and rules

Shield360 ships with built-in policies, detections and alert rules. Each of them can be extended with your own. This page lists every kind you can write, then walks through the three that live on the Policies page: custom sequence policies, your own kinds of sensitive data, and your own AI sites.

Start every new policy in Warn. Watch what it records on the Violations page for a while, then switch to Block once you trust it.

KindWhat it doesWherePermissionWalkthrough
Custom sequence policyA rule over the order of what an AI agent does, such as a secret read followed by a network call. Enforced in coding-agent hooks and the MCP gateway, and checked afterwards over traces.Govern → Policies & rules → Policies, section Sequence policies, Your policies → New policyManage agent policies (policies.manage)Below
Your own kind of sensitive dataA codename, an ID format or a list of names, recognised by keywords or formats and handled like a built-in kind in the browser, the AI proxy and coding agentsGovern → Policies & rules → Policies, section Sensitive data, Your own kinds of dataManage agent policies and Change what is collected (collector.policy)Below
Your own AI siteAn internal AI portal or unlisted public AI site the browser extension allows, warns about or blocks, and protectsGovern → Policies & rules → Policies, section AI tools, Your AI sitesManage agent policies and Change what is collectedBelow
Match rule (rule engine)Conditions over request fields, linked to prompts, contexts and evaluations, and evaluated by your application through the APIGovern → Policies & rules → Detection rules, Create RuleConfigure the rule engine (rule_engine.configure)Create and manage rules
Detection ruleA threshold, baseline, sequence, first-seen or absence check over a signal, run on a scheduleGovern → Policies & rules → Detection rules, New detectionConfigure the rule engine, and the permission of each signal the rule readsDetections
Shield Protect action policyAllows, denies or requires approval for an agent’s gateway tool call, by conditions on its arguments and cumulative limitsGovern → Agent access → Agent authorization, Protect tab, Action policies → Add policyGrant agent access (agent_access.write)Shield Protect
Agent gateway approval ruleHolds a gateway tool call until a person approves it in Shield360, Teams or SlackAPI only: POST /api/agent-access/approval-rulesGrant agent accessCopilot: set up rules
Alert ruleRaises an alert on spend, blocked prompts, a silent collector, a new AI tool or model, failed sign-ins, gateway denials, evaluation failures or spans, or routes alerts raised by features such as policy violations, and sends it to channelsDetect & respond → Alerts → Alert rules, Add ruleManage alerts (alerts.manage)Alerts

Every server route checks its permission itself. The screen only hides or disables what you cannot change.

A sequence policy is one rule in the policy language. It fires when a pattern of tool calls and user turns happens within one agent session. Custom policies belong to the current project and environment: the Editing switch at the top of the Policies page does not change them.

  • You need Manage agent policies. Without it, the page shows the policies read-only.
  • An environment holds at most 50 custom policies.
  • Decide which runtimes must see the policy. A rule that reads user turns (Message, confirms, role, since user) is not evaluated live in the MCP gateway, and a rule that reads tool results (flows(), injection, prompt_injection()) is not evaluated live in Gemini CLI or Windsurf. The scan of traces still checks both. The policy list marks such a policy Needs user turns or Needs tool results.
Open the editor

Go to Govern → Policies & rules → Policies and scroll to Sequence policies, or select it in the section links at the top of the page. Under Your policies, select New policy.

The editor opens with a starter rule and two starter fixtures. Replace both with your own.

Name and describe the policy
  • Name (required): up to 120 characters. It is shown in the policy list, in violations and in the message a blocked agent receives.
  • Description (optional): up to 1,000 characters.
  • Severity: Info, Warning (the default) or Critical. Violations and their policy.violation alerts carry it.
  • Mode: Off, Warn (the default) or Block. See Modes.
Write the rule

In Rule, write one rule: raise "message" if:, then the pattern on its own line, then one condition per line. The pattern’s last variable is the call being decided. See The policy language for the fields, operators and functions, and the worked example below.

A rule can be at most 4,000 characters, with a pattern of one to four variables and at most 19 condition lines.

Write test fixtures

In Test fixtures (JSON), write a list of short made-up sessions, each with the result you expect: "violation" if the policy should fire on any call in it, "pass" if it should not. Write at least one of each. See Test fixtures for every field an event can have.

Up to 20 fixtures, each of 1 to 64 events, and at most 200,000 characters of fixture JSON in all. Fixtures are stored with the policy, so never paste real commands, paths or secrets.

Select Test

Test compiles the rule on the server and runs every fixture, without saving anything. Fixtures run with your allowed destinations.

  • When the rule compiles, the editor says The rule compiles. and lists each fixture, for example publish after a yes: expected no violation — passed.
  • When it does not, the editor names the line and the problem, for example Line 3: events have no field of that name (colour).
  • When the fixtures are not valid JSON, it says The fixtures are not valid JSON.

Fix the rule or the fixtures until every fixture passes.

Select Save

Save compiles the rule and checks the fixtures again, and saves the policy straight away. It does not wait for the page’s Save…. A new policy is on, in the mode you chose.

The policy is listed under Your policies with its severity, an On switch and its mode. On the Policies page, changes to the switch and the mode are drafts until you select Save… at the top of the page. Select Edit to change the policy, or Delete to remove it. Violations it already found are kept.

Watch it, then decide

Shield360’s servers, including the MCP gateway and the scan, use the change within 30 seconds. Coding-agent hooks use it once the collector next refreshes their policy file. Violations appear on the Violations page, and each raises a policy.violation alert. Leave the policy in Warn until its violations are what you expect, then set it to Block.

Agents can publish packages with npm publish. This policy requires a confirming reply from the developer, such as “yes” or “go ahead”, within the six events before the publish.

Rule:

# A package may be published only straight after the developer says yes.
raise "A package was published without the developer confirming" if:
(c: ToolCall)
c is tool:Bash
match(r"\b(npm|pnpm|yarn) publish\b", c.args.command)
not exists((u: Message) before c within 6 where u.confirms)
  • (c: ToolCall) is the pattern: one variable, the call being decided.
  • c is tool:Bash limits it to the shell tool of Claude Code. Tool names are matched without regard to case. Other agents name their shell tool differently.
  • match(…) searches the decided call’s command argument. The r"…" string keeps \b as written.
  • exists((u: Message) before c within 6 where u.confirms) is true when one of the six events before c is a user turn that confirms. not makes the policy fire when there is none.

Test fixtures:

[
{
"name": "publish without a yes",
"expect": "violation",
"events": [
{ "kind": "user", "text": "bump the version and release it" },
{ "kind": "tool", "tool": "Bash", "args": { "command": "npm version patch" } },
{ "kind": "tool", "tool": "Bash", "args": { "command": "npm publish --access public" } }
]
},
{
"name": "publish after a yes",
"expect": "pass",
"events": [
{ "kind": "user", "text": "bump the version and release it" },
{ "kind": "tool", "tool": "Bash", "args": { "command": "npm version patch" } },
{ "kind": "user", "text": "yes, go ahead" },
{ "kind": "tool", "tool": "Bash", "args": { "command": "npm publish --access public" } }
]
},
{
"name": "tests are not a publish",
"expect": "pass",
"events": [{ "kind": "tool", "tool": "Bash", "args": { "command": "npm test" } }]
}
]

Test reports that the rule compiles and that all three fixtures pass. Once saved, the policy is marked Needs user turns in the list, because it reads Message events. Coding-agent hooks evaluate it; the MCP gateway, which cannot see a person’s turns, does not.

A request to publish is not a confirmation: “bump the version and release it” is not an affirmative reply, so the first fixture fires. A turn whose text was not captured counts as confirming, so the scan of traces finds this violation only under full content capture.

The editor uses these routes. Each acts on the project and environment currently selected for the caller, never one named in the request. Every route checks its permission on the server, and every change is recorded in the audit log. Malformed JSON is refused with 400.

RoutePermissionDoes
GET /api/sequence-policiespolicies.readLists the built-in and custom policies, each with key, enabled, mode and uses, and the allowed destinations
POST /api/sequence-policies/validatepolicies.manageCompiles a rule and runs fixtures. Saves nothing.
POST /api/sequence-policiespolicies.manageCreates a custom policy
PATCH /api/sequence-policies/<key>policies.manageChanges a policy
DELETE /api/sequence-policies/<key>policies.manageDeletes a custom policy
PUT /api/sequence-policies/settingspolicies.manageReplaces the allowed destinations

Validate takes { "source", "fixtures" }:

POST /api/sequence-policies/validate
Content-Type: application/json
{
"source": "raise \"A package was published without the developer confirming\" if:\n (c: ToolCall)\n c is tool:Bash\n match(r\"\\b(npm|pnpm|yarn) publish\\b\", c.args.command)\n not exists((u: Message) before c within 6 where u.confirms)\n",
"fixtures": [
{ "name": "tests are not a publish", "expect": "pass", "events": [{ "kind": "tool", "tool": "Bash", "args": { "command": "npm test" } }] }
]
}

It answers 200 with { "data": { "ok": true, "uses": ["user_turns"], "fixtures": [{ "name", "expect", "passed", "matches" }] } }. A rule that does not compile answers 200 with { "data": { "ok": false, "error": { "code", "line", "col", "detail" } } }. Fixtures that are not valid are refused with 400.

Create takes the policy’s fields. name and source are required. description defaults to empty, severity to warning, mode to warn, enabled to true and fixtures to none:

POST /api/sequence-policies
Content-Type: application/json
{
"name": "Publish only after a yes",
"description": "npm, pnpm or yarn publish needs a confirming reply in the six events before it.",
"severity": "warning",
"mode": "warn",
"source": "raise \"A package was published without the developer confirming\" if:\n (c: ToolCall)\n c is tool:Bash\n match(r\"\\b(npm|pnpm|yarn) publish\\b\", c.args.command)\n not exists((u: Message) before c within 6 where u.confirms)\n",
"fixtures": [
{ "name": "tests are not a publish", "expect": "pass", "events": [{ "kind": "tool", "tool": "Bash", "args": { "command": "npm test" } }] }
]
}

It answers 201 with the policy as { "data": { "key": "custom-…", … } }. The rule is compiled on the server: one that does not compile is refused with 400, err and a dsl object naming the code and line. A 51st custom policy is refused with 400. The fixtures are stored, but POST does not run them; use validate first.

Change takes any of the custom policy’s fields. A built-in policy takes only enabled and mode, and any other field is refused with 400:

PATCH /api/sequence-policies/custom-0123456789ab
Content-Type: application/json
{ "mode": "block" }
PATCH /api/sequence-policies/secret-then-egress
Content-Type: application/json
{ "enabled": true, "mode": "block" }

Delete removes a custom policy and answers { "data": { "key": "…" } }. A built-in policy cannot be deleted, and answers 404; switch it off instead.

Allowed destinations replaces the whole list, up to 100 entries:

PUT /api/sequence-policies/settings
Content-Type: application/json
{ "allowedDomains": ["github.com/your-org", "app.your-company.com", "mcp:github"] }

Your own kinds of data sit beside the built-in kinds (API keys, payment cards, email addresses and the rest). Each one becomes a row of the sensitive-data table, marked Yours, with an action per channel. See Your own kinds of data for how they behave.

Choose the scope

Go to Govern → Policies & rules → Policies. With the Editing switch, choose the Organisation default, or the current environment’s overrides. The list is one setting: an environment that overrides it uses its own list instead of the organisation’s.

Name the kind

In the Sensitive data section, under Your own kinds of data, enter a Name, such as Employee ID. It can be up to 60 characters on one line, and no two kinds may share a name, whatever the case. People see the name in notices and refusals, never the value found. Shield360 derives the kind’s id from the name (Employee ID becomes employee_id); the API uses the id.

Choose Keywords or Formats

Under Match by, choose one. A kind matches by keywords or by formats, never both; make two kinds if you need both.

Keywords, one per line, up to 200:

  • Whole words or phrases, matched without regard to case. Falcon matches falcon but not falconry.
  • Each is 2 to 80 characters and must contain a letter or a digit.
  • A space in a phrase matches any run of spaces or line breaks, so Project Falcon also matches project falcon.

Formats, one per line, up to 20, one character per position:

CharacterMatches
#Any digit
@Any letter A to Z
*Any letter A to Z or digit
\ followed by a characterThat character itself, for example \# for a literal #
Any other characterThat character, in either case: EMP-###### matches EMP-004211 and emp-004211

A format is 3 to 64 positions long and needs at least three of #, @ or *. A format made only of them needs at least six, so ###### is accepted and ##### is not. It cannot hold a line break or a tab, or start or end with a space.

Neither matches inside a longer word or number: EMP-###### does not match EMP-0042117 or XEMP-004211. There are no free-form regular expressions.

Try it

Paste sample text into Try it. The kind’s matches are marked as you type. Check both text that should match and text that should not.

Select Add kind

The kind is added to the table as a row marked Yours. A problem, such as a keyword that is too short, is named with the value. To change a kind later, use its row’s edit control, then Update kind. Its remove control removes the kind and its cells at this scope.

Set its actions, then save the page

In the kind’s row, choose what each channel does: Off, Warn, Mask or Block under All channels, Browser, AI proxy and Coding agents (coding agents cannot mask). A kind with no cells of its own follows the defaults.

Select Save… at the top of the page, review the change summary, and select Save changes. Nothing is saved until then. If your organisation requires a change comment or approval, the change waits for them like any other enforcement change.

The organisation and each environment hold up to 50 kinds. Through the API, a kind is an entry of the customDataKinds setting, and its actions are sensitive.<layer>.custom_<id> cells. See Enforcement policy: API.

Your AI sites add internal AI portals and public AI sites the built-in list does not name to the browser extension’s coverage. See Your AI sites for what the extension does on them, and for the one-time permission each person grants.

Choose the scope

Go to Govern → Policies & rules → Policies, and choose the organisation default or the environment’s overrides with the Editing switch. The list is one setting: an environment that overrides it replaces the whole list.

Enter the host

In the AI tools section, under Your AI sites, enter the Host name to add:

EntryCovers
ai.corp.example.comThat host and every subdomain of it
*.corp.example.comcorp.example.com and every subdomain of it
10.20.30.40That IPv4 address only

Entries are lower-cased, and a trailing dot is dropped. An internationalised name is stored in its ASCII (punycode) form. An entry is refused, with the reason, when it:

  • has a scheme, path, query, port or user name (https://…, …/chat, …:8443, user@…), or a space;
  • has a * anywhere but a leading *., or a wildcard over an IPv4 address;
  • is a single label (intranet), has a label that is not letters, digits and hyphens, or has an all-digit last label;
  • is longer than 253 characters;
  • is, or is a wildcard over, a public suffix or a shared hosting domain (co.uk, *.github.io, vercel.app);
  • is, is under, or covers an AI site that is already built in (chatgpt.com, *.openai.com). Set that tool’s action instead;
  • covers the same host as another entry (corp.example.com and *.corp.example.com are the same).
Choose what happens when it is opened

Under When the new site is opened, choose Allow (the default), Warn or Block, then select Add site. You can change a site’s action, or Remove it, in its row.

On every site that is not blocked, the extension also checks prompts and file uploads with the Browser column of the sensitive-data policy.

Save the page

Select Save…, review the change summary, and select Save changes.

The list holds up to 50 sites. Through the API, it is the customAiHosts setting: a list of { "host", "action": "allow" | "warn" | "block" }. See Enforcement policy: API.

  • Sequence policies: the built-in policies, the policy language and fixture fields in full.
  • Policies overview: what every setting on the Policies page does, and how developers get unblocked.
  • Enforcement policy: organisation defaults, environment overrides and the settings API.
  • Violations: every decision the policies made, in one feed.