Skip to content

Rule Authoring

Capsem rules are profile-owned and evaluated by the Security Engine over typed Security Events. The old policy.<type>.<rule_name> runtime and raw request.* authoring path are gone.

Use this page for the shared authoring vocabulary. Use Enforcement for synchronous allow/ask/block/rewrite behavior and Detection Format for Sigma-compatible finding rules.

FamilyRuntime effectAPI groupAdmin workflow
Enforcementallow, ask, block, or rewrite at a synchronous boundary/enforcement/*capsem-admin enforcement ...
DetectionAttach findings to the resolved event; never blocks by itself/detection/*capsem-admin detection ...

Detection and enforcement may use similar canonical fields, but they are not the same semantic surface. Detection is evidence and hunting. Enforcement is a transport decision.

Authored rules target high-level typed roots. Do not author rules against internal event.*, raw subject.*, or provider-specific JSON paths.

Event familyExample roots
HTTPhttp.request.host, http.request.url, http.request.path, http.request.method, http.request.header("authorization"), http.request.body.text, http.response.status, http.response.body.text
DNSdns.request.qname, dns.request.qtype, dns.response.rcode, dns.response.answers
MCPmcp.request.server_name, mcp.request.tool_name, mcp.request.arguments, mcp.response.result_status, mcp.response.content
Modelmodel.request.provider, model.request.name, model.request.messages, model.request.tool_calls, model.response.output_text, model.response.tool_calls
Filefile.activity.path, file.activity.path_class, file.activity.operation, file.activity.snapshot_id
Processprocess.exec.argv, process.exec.cwd, process.exec.env_keys, process.exec.exit_code

Examples:

http.request.host.contains("google")
http.request.url.contains("admin")
http.request.path.startsWith("/admin")
http.request.header("authorization").exists()
http.request.body.text.contains("secret")
mcp.request.tool_name == "github__get_file_contents"
model.request.provider == "google" && model.request.name.contains("gemini")
[security.rules.http.block_metadata]
on = "http.request"
if = 'http.request.host == "169.254.169.254"'
decision = "block"
priority = 10
reason = "metadata endpoints are not reachable from corp VMs"
FieldRequiredDescription
onyesSynchronous boundary, such as http.request or mcp.request.
ifyesCEL expression over canonical roots.
decisionyesallow, ask, block, or rewrite.
priorityyesLower numbers run first.
reasonnoShort audit string stored with the resolved event.
DecisionBehavior
allowContinue through the boundary.
askCreate an approval challenge and fail closed unless approved.
blockStop at the boundary and return a denial response.
rewriteApply validated declarative mutations, then continue.

warn is not an enforcement decision.

Plugins and rules declare mutations; Rust validates and applies them to the real request, response, model payload, MCP payload, or file/process event.

{
"op": "strip_header",
"path": "http.request.headers.authorization"
}

Each event type has an allowlist of legal rewrite targets. Rewrites outside the allowlist fail closed before the transport body is changed.

RangeOwnerNotes
-1000 to -1Corp-exclusiveOnly valid in corp profiles or corp directives.
0System/toggle-derivedUsed by generated provider/MCP capability rules.
1 to 999User-authoredRecommended interactive range.
1000Catch-allSystem-emitted only.

Rules are evaluated in ascending priority. Lower number means earlier decision.

Corp directives that add or replace rule values must use the corp window [-1000, 0]. Catch-all priority 1000 is reserved for system-emitted defaults and is rejected for hand-authored rules.

Resolved rules carry ownership metadata so UI, CLI, and audit logs can explain why a rule exists and whether it is editable:

FieldMeaning
owner_setting_pathDotted setting path that produced the rule, such as ai.providers.google.enabled.
owner_setting_labelHuman-readable label for “managed by” UI copy.
editablefalse for setting-derived rules; direct mutations must target the owning setting.

Ownership classes:

ClassEditableExample
Hand-authored ruleyessecurity.rules.http.allow_corp
Capability-derived rulenosecurity.capabilities.network_egress
Toggle-derived rulenoai.providers.google.enabled
Corp-directive replacementyescorp_directives[0]

If a caller edits a non-editable rule directly, the mutation gate returns Forbidden { owner_setting_path }. The fix is to edit the owning setting or profile directive.

Rules can live at top level or under the setting that owns them. Nesting keeps provenance close to the control it describes:

[ai.providers.google]
enabled = true
[ai.providers.google.rules.http.allow_gemini]
on = "http.request"
if = 'http.request.host == "generativelanguage.googleapis.com"'
decision = "allow"
priority = 0

The resolver tags the emitted rule with owner_setting_path = "ai.providers.google".

HTTP request rules can use broad http.request callbacks or the read/write split used by catch-all generation:

CallbackMethods
http.readGET, HEAD, OPTIONS
http.writePOST, PUT, PATCH, DELETE

For example, a read-only profile can emit an allow catch-all for http.read and a block catch-all for http.write.

The resolver emits one catch-all per rule type at priority 1000. Catch-alls run only when no earlier rule matched.

CapabilityGenerated catch-alls
security.capabilities.network_egressdns.default, http.default_read, http.default_write, model.default
security.capabilities.mcp_toolsmcp.default

The old hardcoded default allow/block lists are not migrated into profile rules. Hosts that should be reachable must be represented by explicit corp or user rules. The old http_upstream_ports allowlist also exits with the removed NetworkPolicy runtime.

Both enforcement and detection support backtests. Backtests return aggregate counts plus up to 100 diverse matched evidence rows by default. Local evidence is not redacted for a user with access to the session; exported telemetry keeps bounded/redacted summaries.

The Security Engine emits a resolved event before telemetry, audit logging, and detection export projections. The resolved event carries the final decision, findings, matched rules, mutations, trace/profile/VM/user attribution, and evidence refs. VM status and OpenTelemetry summaries are derived from those typed events, not from ad hoc policy tables.