Active Response
RAVEN's Phase 3 capabilities move beyond observation into automated response. The Event Engine evaluates configurable rules on every posture change and triggers actions: webhooks, logs, and Flowspec mitigation rules.
Event Engine
The Event Engine runs continuously alongside the validation pipeline. On every posture change it evaluates all configured rules and fires matching actions.
Rule Structure
events:
rules:
- name: "alert-origin-invalid"
trigger:
type: posture_change
postures: ["origin-invalid"]
cooldown: 60s
actions:
- type: log
level: warn
- type: webhook
url: "https://hooks.example.com/raven"
- type: flowspec
dry_run: true
ttl: 5m
Trigger: what causes the rule to fire. posture_change fires when a
route transitions to one of the listed postures. The trigger also fires for
new routes arriving with a matching posture.
Cooldown: minimum time between firings for the same prefix+peer combination. Prevents alert storms during route flapping. Default: 60s.
Actions: what happens when the rule fires. Multiple actions can be listed; they execute in order. If one action fails, subsequent actions still run.
Trigger Types
| Type | Required fields | Description |
|---|---|---|
posture |
postures |
Route currently has one of the listed postures (steady-state) |
posture_change |
postures |
Route posture transitions to one of the listed postures |
prefix |
prefix |
Route prefix falls within (or equals) the configured supernet |
asn |
asns |
Route involves one of the listed ASNs |
protected_asn |
asns |
Origin-invalid route hijacks a prefix authorised for one of the listed ASNs |
cache_unhealthy |
- | RTR cache becomes unreachable |
compound |
- | AND/OR composition of any of the above trigger types |
posture evaluates current state on every event; it fires even if the posture has not changed. posture_change fires only on transitions: when old_posture != new_posture and the new posture matches. Use posture_change for alerting (avoids re-firing on every update for a route that is already in a bad state); use posture inside compound triggers when you need to combine a steady-state check with another condition.
asn trigger - fires whenever a route's origin ASN (default) or any ASN in
its full AS path (asn_match: path) matches one in the asns list. Use this to
track routing events involving a specific network.
protected_asn trigger - fires when a route is origin-invalid and a
covering VRP names one of the listed ASNs as the authorised originator. This
detects prefix hijacks targeting prefixes you own: the hijacking route does not
contain your ASN in its path, but the RPKI record says only your ASN is allowed
to originate it.
Compound Triggers
A compound trigger combines multiple sub-triggers with boolean AND or OR logic. Use it to narrow a broad trigger to a specific prefix scope, or to merge several independent conditions into a single rule.
trigger:
type: compound
operator: and # "and": all must match, "or": any must match
triggers:
- type: posture_change
postures: ["origin-invalid"]
- type: prefix
prefix: "192.0.2.0/24"
This fires only when a route that falls within 192.0.2.0/24 transitions to origin-invalid. A hijack on an unrelated prefix leaves this rule silent.
Operator semantics:
| Operator | Fires when |
|---|---|
and |
Every sub-trigger matches (intersection) |
or |
At least one sub-trigger matches (union) |
Sub-triggers can themselves be compound; nesting is fully supported:
trigger:
type: compound
operator: or
triggers:
- type: posture_change
postures: ["origin-invalid"]
- type: compound
operator: and
triggers:
- type: posture_change
postures: ["path-suspect"]
- type: prefix
prefix: "10.0.0.0/8"
Fires on any origin-invalid event, or when a path-suspect event lands within 10.0.0.0/8.
Recipes
Alert only on your own address space:
- name: "alert-own-prefix-hijack"
trigger:
type: compound
operator: and
triggers:
- type: posture_change
postures: ["origin-invalid"]
- type: prefix
prefix: "203.0.113.0/24"
cooldown: 60s
actions:
- type: log
level: error
- type: webhook
url: "https://hooks.example.com/critical"
Fan-in: alert on route threat or RPKI cache failure under one rule:
- name: "alert-any-threat"
trigger:
type: compound
operator: or
triggers:
- type: posture_change
postures: ["origin-invalid", "path-suspect"]
- type: cache_unhealthy
cooldown: 30s
actions:
- type: webhook
url: "https://hooks.example.com/raven"
Scoped Flowspec: auto-mitigate only within a critical range:
- name: "mitigate-critical-range"
trigger:
type: compound
operator: and
triggers:
- type: posture_change
postures: ["origin-invalid"]
- type: prefix
prefix: "198.51.100.0/24"
cooldown: 60s
actions:
- type: flowspec
gobgp_address: "localhost:50051"
dry_run: true
ttl: 30m
rule_action: drop
Recommended Rules
Alert on hijacks (origin-invalid):
- name: "alert-hijack"
trigger:
type: posture_change
postures: ["origin-invalid"]
cooldown: 60s
actions:
- type: log
level: warn
- type: webhook
url: "https://your-pagerduty-or-slack-endpoint"
Alert on route leaks (path-suspect):
- name: "alert-route-leak"
trigger:
type: posture_change
postures: ["path-suspect"]
cooldown: 60s
actions:
- type: log
level: warn
- type: webhook
url: "https://your-pagerduty-or-slack-endpoint"
Flowspec mitigation on hijacks:
- name: "mitigate-hijack"
trigger:
type: posture_change
postures: ["origin-invalid"]
cooldown: 60s
actions:
- type: flowspec
gobgp_address: "localhost:50051"
dry_run: true
ttl: 1h
rule_action: drop
max_rules: 50
Alert when any route involves a specific ASN (origin only):
- name: "track-as64496-origin"
trigger:
type: asn
asns: [64496]
cooldown: 300s
actions:
- type: log
level: info
Alert when any route traverses a specific ASN (full path):
- name: "track-as64496-transit"
trigger:
type: asn
asns: [64496]
asn_match: path
cooldown: 300s
actions:
- type: webhook
url: "https://your-endpoint"
Alert when your own prefixes are hijacked (protected_asn):
- name: "alert-my-prefix-hijacked"
trigger:
type: protected_asn
asns: [64511]
cooldown: 60s
actions:
- type: log
level: warn
- type: webhook
url: "https://your-pagerduty-or-slack-endpoint"
This rule fires only when an origin-invalid route covers a prefix for which
a VRP names your ASN as the sole authorised originator — a strong signal that
your prefix is being hijacked.
Webhooks
The webhook action sends an HTTP POST to your endpoint when a rule fires.
Payload Format
{
"id": "b65b80a6-1234-5678-abcd-ef0123456789",
"timestamp": "2026-05-06T14:23:01Z",
"type": "posture_change",
"rule_name": "alert-my-prefix-hijacked",
"prefix": "192.0.2.0/24",
"peer_addr": "10.0.0.1",
"peer_asn": 65000,
"origin_asn": 64496,
"protected_asns": [64511],
"old_posture": "",
"new_posture": "origin-invalid",
"rov_state": "Invalid",
"aspa_state": "Unknown",
"router_id": "10.0.0.1"
}
rule_name identifies which rule fired the webhook. protected_asns lists the
ASNs named as authorised originators in VRPs that cover the affected prefix — only
present when covering VRPs exist.
HMAC Signing
Set secret in the webhook config to enable HMAC-SHA256 signing. RAVEN
adds an X-RAVEN-Signature header to every request:
X-RAVEN-Signature: sha256=
The signature is computed over the raw JSON body. Verify it on your webhook receiver to confirm the request is from RAVEN.
actions:
- type: webhook
url: "https://hooks.example.com/raven"
secret: "your-shared-secret"
max_attempts: 3
timeout: 5s
Retry Behaviour
Failed webhook deliveries are retried with exponential backoff up to
max_attempts times. If all attempts fail, the event is logged to the
dead-letter log at warn level. RAVEN never blocks the event pipeline on
webhook failures.
Integrations
The webhook payload is compatible with:
- PagerDuty: use the Events API v2 with a custom transformer
- Slack: use an incoming webhook URL; transform the payload with a small proxy function
- ServiceNow: POST to the Table API endpoint for your incident table
- Alertmanager: use a webhook receiver with a custom template
Flowspec Lifecycle
The Flowspec action automates the full mitigation lifecycle: detect origin-invalid route ↓ generate Flowspec drop rule (dry-run) ↓ raven flowspec list ← review ↓ raven flowspec toggle ← inject live into GoBGP ↓ auto-expire after TTL ← or toggle again to withdraw early
Prerequisites
Flowspec injection requires a GoBGP instance with gRPC enabled. In the
demo lab, a GoBGP sidecar container handles this. In production, point
gobgp_address at your GoBGP instance.
- type: flowspec
gobgp_address: "your-gobgp:50051"
dry_run: true
ttl: 1h
rule_action: drop
max_rules: 50
reaper_interval: 30s
Dry-run Mode
Always start with dry_run: true. Rules are tracked in RAVEN's registry
and logged but not injected into GoBGP. This lets you validate that the
correct rules are being generated before enabling live injection.
# Review generated rules
raven flowspec list
# Example output
KEY ACTION DRY-RUN TTL INSERTED
192.0.2.0/24|drop drop yes 4m50s 2026-05-06 14:23:01
Live Injection
Toggle a specific rule from dry-run to live:
RAVEN calls GoBGP's AddPath API. The rule propagates to any BGP peers configured on the GoBGP instance. Verify with:
Toggle again to withdraw:
TTL and Auto-expiry
Every rule has a TTL (default: 1 hour). The reaper goroutine runs every
reaper_interval and withdraws rules whose TTL has expired. This ensures
Flowspec rules don't persist indefinitely if RAVEN restarts or the
operator forgets to withdraw manually.
Approval Webhook
For human-in-the-loop automation, configure an approval webhook. RAVEN POSTs the proposed rule and waits for a 200 response before injecting:
- type: flowspec
gobgp_address: "localhost:50051"
dry_run: false
approval_webhook: "https://change-mgmt.example.com/approve"
ttl: 1h
The approval endpoint receives the same JSON payload as a regular webhook
plus a proposed_rule field. Return 200 to approve, any other status to
reject.
Warning
Max rules (max_rules) is a hard cap. When the registry is full, new
rules are dropped and logged. Set this conservatively: a runaway
event loop generating hundreds of Flowspec rules is worse than missing
a few.
raven audit
raven audit generates a read-only security posture report for any router
or peer in your BMP feed. It summarises ROV/ASPA coverage, flags top
offending ASNs, and provides actionable recommendations.
# Audit a specific router by peer address
raven audit --router 10.0.0.1
# JSON output for automation
raven audit --router 10.0.0.1 --format json
# Markdown output for incident reports
raven audit --router 10.0.0.1 --format markdown
Example Output
Router: 10.0.0.1 Total Routes: 6 ROV Coverage: 83.3% ASPA Coverage: 0.0% POSTURE COUNT origin-only 3 (50%) unverified 1 (17%) origin-invalid 2 (33%) TOP OFFENDERS AS64496 1 route origin-invalid 192.0.2.0/24 AS65001 1 route origin-invalid 10.10.0.0/24 RECOMMENDATIONS
2 origin-invalid routes: run raven what-if --reject-invalid ASPA coverage 0%: register ASPA objects at your RIR Consider enabling reject-invalid after reviewing what-if output
Automation
Pipe audit output to your ticketing or reporting pipeline:
# Weekly cron job
0 9 * * 1 raven audit --router 10.0.0.1 --format json \
| curl -s -X POST https://your-api/security-report \
-H "Content-Type: application/json" -d @-
Warm-start Persistence
On clean shutdown, RAVEN saves the route table and RPKI cache to a snapshot file. On restart, it restores from the snapshot before reconnecting to BMP and RTR, eliminating the 30–90 second blind window of a cold start.
Enable in config:
Restored routes are marked stale. If BMP doesn't refresh a route within
stale_timeout, it is evicted. This prevents stale routes from persisting
if the topology changed during the downtime.
Note
The snapshot uses JSON serialisation. When buf/protoc become available in your build environment, swap to protobuf for faster save/restore on large route tables. The interface is unchanged.