docs · rule format
Rule format.
Every detection rule is a YAML document. The schema is small and stable. Rule packs are tar archives of these YAML files plus a manifest, signed end-to-end with Ed25519. The daemon refuses to load any rule pack whose signature does not verify against the embedded public key.
the schema · a single rule
Example: R1 subcommand-chain bypass.
# rules/v1/R1_subcommand_chain.yaml
id: R1
name: subcommand-chain bypass
version: 1
authors:
- oknek-core
references:
- url: https://adversa.ai/blog/claude-code-security-bypass-deny-rules-disabled/
label: Adversa CC-643 (April 2026)
- url: https://www.theregister.com/2026/04/01/claude_code_rule_cap_raises/
label: The Register coverage
attack_techniques:
- mitre_atlas: AML.T0051
- owasp_asi: ASI05
kind: exec_observed
match:
any_of:
- chain_depth: { gte: 8 }
threshold:
default: 8
configurable: true
action:
default: block
configurable: true
options: [ block, warn, allow ]
evidence:
capture:
- bash_command
- chain_depth
- chain_breakdown
- agent_identifier
- parent_pid
retain_for: 90d
field by field
Schema reference.
| Field | Type | Required | Notes |
|---|---|---|---|
id | string · R{n} | yes | Globally unique. R1–R7 reserved for core pack. Authors use B{n} for business-only or X{n} for custom packs. |
name | string | yes | Human-readable, lowercase. Appears in oknek logs and the dashboard. |
version | integer | yes | Incremented on every revision. Daemon refuses to downgrade. |
kind | enum | yes | One of: exec_observed, file_opened, socket_connect, file_changed, baseline_drift, file_scanned, mcp_endpoint. Determines which kernel hook fires the rule. |
match | predicate | yes | Boolean expression: any_of / all_of / none_of with leaf predicates per kind. See predicates table below. |
threshold | object | no | Numeric default + whether the user can override via /etc/oknek/oknek.yaml. |
action | object | yes | What to do when the rule matches. Options: block (SIGSTOP the process), warn (log + alert, allow through), allow (effectively disabled but still logged). |
evidence | object | yes | What to capture when the rule fires. capture lists field names. retain_for sets local retention. |
references | array | no | External citations — CVE URLs, research papers, blog posts. |
attack_techniques | array | no | MITRE ATLAS and OWASP ASI alignment. |
how rule packs are distributed
Pack structure + signing.
oknek-rules-v1.0.5.tar.gz
├── manifest.yaml # pack metadata · timestamps · rule list · prev pack hash
├── rules/
│ ├── R1_subcommand_chain.yaml
│ ├── R2_settings_json_flip.yaml
│ ├── R3_credential_read.yaml
│ ├── R4_mcp_url_drift.yaml
│ ├── R5_egress_allowlist.yaml
│ ├── R6_claudemd_injection.yaml
│ └── R7_behavioral_drift.yaml
└── signature.ed25519 # Ed25519 sig over sha256(manifest.yaml || all rules)
The public key is embedded in the oknekd binary at build time, never fetched at runtime. To rotate it we ship a new oknekd binary — not a new rule pack. This makes the rule-pack channel non-trust-bootstrappable in a way malicious rule packs cannot hijack.
custom rules · business + enterprise tiers
Writing your own.
Business tier and above can author site-local rules. Drop them in /etc/oknek/rules.d/ with an X-prefixed ID. The daemon picks them up on the next reload (oknek update --reload-local).
# /etc/oknek/rules.d/X100_no_aws_secrets_outside_business_hours.yaml
id: X100
name: aws credential read outside business hours
version: 1
kind: file_opened
match:
all_of:
- path: { glob: "~/.aws/credentials" }
- process_name: { match: "claude" }
- wall_clock_hour: { not_in: [9, 10, 11, 12, 13, 14, 15, 16, 17] }
- wall_clock_dow: { not_in: [0, 6] }
action:
default: warn
evidence:
capture: [ path, agent_identifier, wall_clock, baseline_distance ]
retain_for: 30d
Next: architecture.
See how a rule actually fires — the five-stage funnel from agent action to verdict.