Firewall Rules
Firewall rules in Synapse provide stateless packet filtering via TC (Traffic Control) BPF programs, enabling fine-grained control over both incoming and outgoing traffic. Unlike access rules (which use XDP for inbound-only, pre-stack filtering), firewall rules operate at the TC layer to filter traffic in both directions with support for port, IP, and protocol matching.
Overview
Synapse's TC-based firewall rules allow you to:
- Filter ingress and egress traffic at the TC hook
- Match on source/destination IP, port ranges, and IP protocol
- Enforce ordered rules with a configurable default action (allow or drop)
- Distinguish IPv4 and IPv6 traffic per rule
- Distribute active rules dynamically via the Gen0Sec control plane and API feeds
How It Works
Synapse loads firewall rules into a TC/BPF program attached to network interfaces. When a packet is processed:
- TC BPF program inspects the packet at the traffic control hook
- Rules are evaluated in order (ascending
ordervalue) against the packet's IP, port, and protocol fields - First matching rule wins — the packet is allowed or dropped according to the rule's
action - Default action applies if no rule matches, as defined in the workspace
FirewallRulesConfig - Statistics are collected for monitoring and analysis
TC vs XDP
| Feature | XDP (Access Rules) | TC (Firewall Rules) |
|---|---|---|
| Hook point | Before network stack | After network stack ingress / before egress |
| Directions | Ingress only | Ingress and egress |
| Matching | IP address | IP, port, protocol |
| Latency | Sub-microsecond | Low microsecond |
| Use case | Fast IP blocklisting | Fine-grained stateless firewalling |
Configuration
Workspace Firewall Config
Each workspace has a single FirewallRulesConfig that controls global firewall behaviour:
| Field | Type | Description |
|---|---|---|
enabled | bool | When false, the BPF program passes all traffic without evaluating rules |
default_action | string | Action applied when no rule matches — "allow" or "drop" |
Firewall Rule Fields
Each FirewallRule has the following fields:
| Field | Type | Description |
|---|---|---|
id | UUID | Internal rule identifier (UUID) |
name | string | Human-readable rule name |
description | string | Optional description |
src_low_port | *int | Source port range lower bound (null = any port) |
src_high_port | *int | Source port range upper bound (null = any port) |
dst_low_port | *int | Destination port range lower bound (null = any port) |
dst_high_port | *int | Destination port range upper bound (null = any port) |
src_ip | *string | Source IP to match (null = any) |
dst_ip | *string | Destination IP to match (null = any) |
src_ip_prefix | int | Source CIDR prefix length (0 = host match, 32/128 = exact) |
dst_ip_prefix | int | Destination CIDR prefix length |
ip_version | int | 4 for IPv4, 6 for IPv6 |
ip_protocol | int | IP protocol number (e.g. 6 = TCP, 17 = UDP, 0 = any) |
action | string | "allow" or "drop" |
direction | string | "ingress" or "egress" |
order | int | Evaluation order — lower values are matched first |
is_active | bool | Only active rules are loaded into the BPF map |
status | string | Rule lifecycle status (e.g. "active", "disabled") |
created_at | timestamp | Creation time |
updated_at | timestamp | Last modification time |
API Reference
Feeds API
GET /v1/firewall/feeds
Returns the firewall configuration and active rules for the authenticated workspace, formatted for direct consumption by the Synapse agent's BPF map loader. Active rules are pre-split into ingress and egress lists, ordered by order ASC.
Authentication: API key via auth middleware (org_id, workspace_id in context)
Response (200 OK):
{
"config": {
"enabled": true,
"default_action": "drop"
},
"ingress": [
{
"id": "aaaaaaaa-aaaa-aaaa-aaaa-aaaaaaaaaaaa",
"name": "Allow HTTP from trusted range",
"src_low_port": 0,
"src_high_port": 65535,
"dst_low_port": 80,
"dst_high_port": 80,
"src_ip": "10.0.0.0",
"dst_ip": null,
"src_ip_prefix": 8,
"dst_ip_prefix": 0,
"ip_version": 4,
"ip_protocol": 6,
"action": "allow",
"order": 10,
"is_active": true
}
],
"egress": []
}
When the workspace has no FirewallRulesConfig row, the response still returns 200 OK with config: null and empty rule lists — the agent treats this as firewalling disabled.
Cache: L1 only, TTL 30 seconds. Cache hit/miss status is reported via X-Cache and X-Cache-Level response headers.
Remediation API
GET /v1/firewall-rules
Returns a paginated list of all firewall rules for the authenticated workspace.
Query Parameters:
| Parameter | Default | Maximum | Description |
|---|---|---|---|
page | 1 | — | Page number (1-indexed) |
limit | 20 | 100 | Rules per page |
Response (200 OK):
{
"success": true,
"data": [
{
"id": "aaaaaaaa-aaaa-aaaa-aaaa-aaaaaaaaaaaa",
"name": "Block outbound DNS to untrusted resolvers",
"description": "Prevents DNS exfiltration",
"src_low_port": 0,
"src_high_port": 65535,
"dst_low_port": 53,
"dst_high_port": 53,
"src_ip": null,
"dst_ip": null,
"src_ip_prefix": 0,
"dst_ip_prefix": 0,
"ip_version": 4,
"ip_protocol": 17,
"action": "drop",
"direction": "egress",
"order": 5,
"is_active": true,
"status": "active",
"created_at": "2024-01-15T10:00:00Z",
"updated_at": "2024-01-15T10:00:00Z"
}
],
"total": 1,
"page": 1,
"limit": 20
}
Cache: L1 + L2, TTL 60 seconds, keyed by org_id, workspace_id, page, and limit.
GET /v1/firewall-rules/{id}
Returns a single firewall rule by its id (UUID).
Path Parameters:
| Parameter | Description |
|---|---|
id | UUID (id field) of the firewall rule |
Response (200 OK):
{
"success": true,
"data": {
"id": "aaaaaaaa-aaaa-aaaa-aaaa-aaaaaaaaaaaa",
"name": "Allow HTTP from trusted range",
"description": "",
"src_low_port": 0,
"src_high_port": 65535,
"dst_low_port": 80,
"dst_high_port": 80,
"src_ip": "10.0.0.0",
"dst_ip": null,
"src_ip_prefix": 8,
"dst_ip_prefix": 0,
"ip_version": 4,
"ip_protocol": 6,
"action": "allow",
"direction": "ingress",
"order": 10,
"is_active": true,
"status": "active",
"created_at": "2024-01-15T10:00:00Z",
"updated_at": "2024-01-15T10:00:00Z"
}
}
Error responses: 400 if id is not a valid UUID, 404 if the rule does not exist.
Cache: L1 + L2, TTL 60 seconds, keyed by org_id and id.
GET /v1/firewall-rules/config
Returns the workspace-level firewall configuration.
Response (200 OK):
{
"success": true,
"data": {
"id": "dddddddd-dddd-dddd-dddd-dddddddddddd",
"org_id": "eeeeeeee-eeee-eeee-eeee-eeeeeeeeeeee",
"workspace_id": "ffffffff-ffff-ffff-ffff-ffffffffffff",
"enabled": true,
"default_action": "drop",
"created_at": "2024-01-15T10:00:00Z",
"updated_at": "2024-01-15T10:00:00Z"
}
}
Error responses: 404 if no config has been created for the workspace.
Cache: L1 + L2, TTL 60 seconds, keyed by org_id and workspace_id.
OpenAPI and Swagger
The firewall read endpoints are included in the generated Swagger documents for the relevant services.
- Feeds API UI:
/docs/feeds/swagger/ - Feeds API JSON:
/docs/feeds/swagger/doc.json - Remediation API UI:
/docs/remediation/swagger/ - Remediation API JSON:
/docs/remediation/swagger/doc.json
The generated service specs describe the currently implemented read routes:
GET /v1/firewall/feedsGET /v1/firewall-rulesGET /v1/firewall-rules/{id}GET /v1/firewall-rules/config
Write Path Status
The current firewall-rules HTTP implementation is also read-only.
These services currently expose:
- Agent feed retrieval from the feeds API
- Paginated rule listing from the remediation API
- Single-rule inspection from the remediation API
- Workspace config retrieval from the remediation API
The following write operations are not implemented in these HTTP services today:
- Create firewall rule
- Update firewall rule
- Delete firewall rule
- Create or update workspace firewall config
- Activate or deactivate a rule
If write operations exist in the broader control plane, they are outside the scope of these two service APIs.
Rule Evaluation
Rules are ordered by the order field (ascending). The agent inserts rules into the BPF map in this order so that lower order values have higher priority:
order=1 → evaluated first (highest priority)
order=10 → evaluated second
order=99 → evaluated last (lowest priority)
↓
default_action (fallback if no rule matches)
Only rules where is_active = true are included in the feeds response and loaded into the BPF map.
Performance Characteristics
TC Hook Benefits
- Bidirectional filtering — single BPF program handles both ingress and egress
- Rich match criteria — port, IP, CIDR, and protocol matching
- Ordered evaluation — deterministic first-match semantics
- Low overhead — BPF map lookup at ~200ns per packet
Resource Usage
- Memory: ~200 bytes per rule in BPF map
- CPU: Less than 2% for typical rule counts and traffic patterns
- Cache TTL: 30s (feeds), 60s (management API)
Best Practices
Rule Design
- Keep
ordervalues sparse (e.g. 10, 20, 30) to allow rule insertion without renumbering - Place most-specific rules first (lower
order) to avoid shadowing - Use
default_action: dropwith explicit allow rules for a deny-by-default posture - Separate ingress/egress chains — use
directionto keep rule sets focused
Security Considerations
- TC BPF requires
CAP_NET_ADMIN— ensure the Synapse agent container has this capability - Test rule ordering before deploying to production — incorrect order can allow or block unintended traffic
- Monitor the feeds endpoint cache — a 30s staleness window means rule changes propagate within ~30 seconds to agents
- Use
is_active = falseto disable a rule without deleting it, preserving audit history