Skip to main content
Version: v5

WAF Operations and Rule Schema

Added in: v5.2.0

WAF rules are managed through dedicated Operations API operations. Generic CRUD operations cannot write the system.hdb_waf_rules system table.

All operations on this page are restricted to super_user roles. Rule writes are attributed to the authenticated user in Harper's audit records.

Operations

OperationDescription
add_waf_ruleValidates and creates a rule.
alter_waf_rulePatches and revalidates an existing rule.
drop_waf_ruleDeletes a rule.
list_waf_rulesReturns all rules.
set_waf_modeChanges the replicated mode and/or score threshold.

Rule and control changes propagate through the replicated rule table and trigger a live matcher recompile. A Harper restart is not required.

add_waf_rule

Validates and inserts the supplied rule. The id must not already exist.

When provenance is omitted, Harper records the rule as human-authored and sets provenance.approver to the authenticated username.

Request

{
"operation": "add_waf_rule",
"rule": {
"id": "block-admin-from-scanner-network",
"enabled": true,
"priority": 100,
"phase": "request",
"description": "Block a known scanner network from the admin application",
"match": {
"path": {
"prefix": "/admin/"
},
"ip": ["203.0.113.0/24"]
},
"action": "block",
"blockStatus": 403
}
}

Response

{
"message": "Added WAF rule block-admin-from-scanner-network"
}

If the ID already exists, Harper returns 409. Use alter_waf_rule to modify it.

alter_waf_rule

Patches an existing rule. Supply id plus any fields to replace. Harper combines the patch with the stored rule and validates the complete result before writing it. The rule ID cannot be changed.

Request

{
"operation": "alter_waf_rule",
"id": "block-admin-from-scanner-network",
"shadow": true,
"description": "Preview this block before enforcing it"
}

Response

{
"message": "Updated WAF rule block-admin-from-scanner-network"
}

Nested objects are replaced, not deeply patched. To change one property inside match, activation, scope, provenance, or rateLimit, send the complete replacement object.

drop_waf_rule

Deletes an existing rule.

Request

{
"operation": "drop_waf_rule",
"id": "block-admin-from-scanner-network"
}

Response

{
"message": "Dropped WAF rule block-admin-from-scanner-network"
}

list_waf_rules

Returns all stored WAF rules. The internal global-control record is not included.

Request

{
"operation": "list_waf_rules"
}

Response

[
{
"id": "log-suspicious-agent",
"enabled": true,
"priority": 200,
"phase": "request",
"match": {
"headers": [
{
"name": "user-agent",
"op": "contains",
"value": "ExampleScanner"
}
]
},
"action": "log",
"provenance": {
"origin": "human",
"approver": "admin"
}
}
]

set_waf_mode

Sets the cluster-wide enforcement mode, scoreThreshold, or both. At least one must be supplied. When only one field is supplied, Harper preserves the existing value of the other field.

The control record is stored with the WAF rules and replicates to the other nodes. It overrides node-local waf.mode and waf.scoreThreshold configuration.

Parameters

ParameterRequiredTypeDescription
operationYesstringMust be "set_waf_mode".
modeNostringenforce, monitor, or off.
scoreThresholdNonumberFinite positive score total that causes a block.

Start a monitored rollout

{
"operation": "set_waf_mode",
"mode": "monitor",
"scoreThreshold": 20
}
{
"message": "WAF mode set to monitor, scoreThreshold set to 20"
}

Enable enforcement

{
"operation": "set_waf_mode",
"mode": "enforce"
}

Disable evaluation

{
"operation": "set_waf_mode",
"mode": "off"
}

off is a pass-through kill switch. It does not delete or disable the stored rules.

Rule schema

Top-level fields

FieldRequiredTypeDefaultDescription
idYesstring-Unique rule identifier.
enabledNobooleantrueSet to false to keep the rule stored but exclude it from matching.
priorityNonumber0Evaluation order; lower numbers run first. Must be finite.
phaseYesstring-request is enforced. requestBody is reserved and skipped in v5.2.
descriptionNostring-Operator-facing description.
matchYesobject-Match conditions. At least one condition is required.
actionYesstring-block, log, or score. Reserved actions are accepted but deferred.
scoreConditionalnumber-Required and finite when action is score.
blockStatusNonumber403HTTP status for block; must be from 400 through 599.
shadowNobooleanfalseLogs a would-block result for block and score without enforcing it.
activationNoobject-Limits which nodes arm the rule.
scopeNoobject-Reserved metadata only; does not restrict matching in v5.2.
provenanceNoobject-Authorship metadata.
rateLimitNoobject-Reserved; a rule containing it is deferred in full in v5.2.

Match fields

FieldTypeDescription
ipstring | string[]IPv4, IPv6, or CIDR values. Array entries are alternatives.
methodstring[]HTTP methods. Matching is case-insensitive. Array entries are alternatives.
pathobjectexact, prefix, and/or RE2-compatible regex. Present fields are combined with AND.
headersobject[]Header name/value conditions. Header names are case-insensitive; all entries must match.
queryobject[]Query parameter conditions. Names and values are percent-decoded and case-sensitive; all entries must match.
ja4string | string[]Reserved; defers the complete rule in v5.2.
ja4hstring | string[]Reserved; defers the complete rule in v5.2.
modelobjectReserved; defers the complete rule in v5.2.
agentobjectReserved; defers the complete rule in v5.2.

ip, headers, and query arrays cannot be empty. method must be a non-empty array. Multiple top-level match fields are combined with AND.

Header and query conditions

Each headers or query entry has this shape:

{
"name": "content-type",
"op": "prefix",
"value": "application/json"
}
FieldRequiredTypeDescription
nameYesstringNon-empty header or query parameter name.
opYesstringequals, contains, prefix, regex, or exists.
valueConditionalstringRequired and non-empty for every operator except exists.

For repeated headers or query parameters, a condition succeeds if any value matches.

activation

{
"activation": {
"nodes": ["edge-1", "edge-2"],
"regions": ["us-west"],
"tags": ["canary", "public"]
}
}

Each present selector must be a non-empty string array:

  • nodes - Matches this node's configured name.
  • regions - Matches waf.region.
  • tags - Matches if any listed tag appears in waf.nodeTags.

All present selector types must succeed. For example, the rule above requires an allowed node name, the us-west region, and at least one of the two tags.

provenance

{
"provenance": {
"origin": "managed-feed",
"approver": "security-team",
"source": "vendor-rule-set-2026-07"
}
}
FieldTypeDescription
originstringhuman, managed-feed, or agent-proposed.
approverstringPerson or system that approved the rule.
sourcestringSource identifier for the rule.

Provenance is metadata and does not affect matching.

Reserved scope

The accepted shape is:

{
"scope": {
"clusters": ["production"],
"applications": ["storefront"],
"tenants": ["example"]
}
}

These values are metadata only in v5.2. They do not limit rule enforcement and do not defer the rule.

Reserved rateLimit

The accepted shape is:

{
"rateLimit": {
"key": ["ip", "session"],
"limit": 100,
"windowMs": 60000
}
}

Allowed key values are ip, ja4, session, agent, and user. limit and windowMs must be positive finite numbers.

The complete rule is deferred in v5.2. No rate limiting is enforced.