Problem
When an administrator reviews a WAF event in the log viewer and identifies it as a false positive, creating the corresponding RuleExclusion is a multi-step manual process:
- Open the log detail and read the rule ID, request URI, and matched variable.
- Switch to the policy tuning page.
- Manually fill in the exclusion form: rule ID, target type, target value, scope path.
- Remember or copy the exact variable name from the log.
This is slow, error-prone, and requires the admin to mentally translate Coraza audit output into Guard Proxy exclusion fields.
Goal
Add a "Create Exclusion" action directly inside the log detail view. Clicking it should open a pre-filled exclusion form populated from the event's data, so the admin only needs to review and confirm.
Proposed Flow
- Admin opens the Log Detail Modal (already exists).
- Clicks a new button "Create Exclusion".
- A modal or slide-over opens with the
RuleExclusionCreate form pre-filled:
rule_id — from the log's rule_id field.
target_type — parsed from the matched variable in raw_context (e.g. ARGS → args, REQUEST_HEADERS → request_headers).
target_value — parsed from the matched variable (e.g. ARGS:token → token).
scope_path — extracted from the log's request_uri (path prefix only, no query string).
comment — pre-filled with something like "Generated from log #<id>" (editable).
- Admin can adjust any field before saving.
- On save, a
RuleExclusion is created and the admin is prompted to run /config/apply.
Backend Changes
New endpoint
POST /logs/{log_id}/suggest-exclusion
Returns a RuleExclusionCreate-shaped object with fields pre-filled from the log.
Parsing logic
The endpoint must:
- Fetch the log row by
log_id.
- Parse
raw_context (the original Coraza audit JSON) to find the exact matched variable.
- Map the Coraza variable name to Guard Proxy's
TargetType enum.
- Extract the variable value (the argument/header name).
- Derive
scope_path from request_uri by stripping the query string.
The log-shipper already parses Coraza audit JSON (src/log-shipper/app/mapping.py). The same parsing helpers can be reused or adapted in the backend.
Example Coraza audit snippet:
{
"messages": [{
"message": "SQL Injection Attack Detected via libinjection",
"details": {
"match": "Matched \"...\" at ARGS:token"
}
}]
}
From "at ARGS:token" the endpoint derives:
target_type: args
target_value: "token"
Edge cases to handle
- Log has no
raw_context → return 422 with clear message.
- Multiple messages in
raw_context → use the first one that contains a rule-bearing message.
- Variable cannot be parsed → return the form with
rule_id and scope_path pre-filled, leaving target_type and target_value empty for manual entry.
- Log belongs to a vhost with no bound policy → return
422 because exclusions need a policy.
Frontend Changes
- Add a "Create Exclusion" button inside
LogDetailModal (visible only for events with rule_id and when the user is an admin).
- Re-use the existing exclusion creation form or extract it into a shared component.
- Call
POST /logs/{id}/suggest-exclusion to get pre-filled data.
- After save, show a success toast with a link/button to trigger
/config/apply.
Acceptance Criteria
Out of Scope
Related
Estimated effort: ~150–250 lines across backend + frontend. Small, focused PR.
Problem
When an administrator reviews a WAF event in the log viewer and identifies it as a false positive, creating the corresponding
RuleExclusionis a multi-step manual process:This is slow, error-prone, and requires the admin to mentally translate Coraza audit output into Guard Proxy exclusion fields.
Goal
Add a "Create Exclusion" action directly inside the log detail view. Clicking it should open a pre-filled exclusion form populated from the event's data, so the admin only needs to review and confirm.
Proposed Flow
RuleExclusionCreateform pre-filled:rule_id— from the log'srule_idfield.target_type— parsed from the matched variable inraw_context(e.g.ARGS→args,REQUEST_HEADERS→request_headers).target_value— parsed from the matched variable (e.g.ARGS:token→token).scope_path— extracted from the log'srequest_uri(path prefix only, no query string).comment— pre-filled with something like"Generated from log #<id>"(editable).RuleExclusionis created and the admin is prompted to run/config/apply.Backend Changes
New endpoint
Returns a
RuleExclusionCreate-shaped object with fields pre-filled from the log.Parsing logic
The endpoint must:
log_id.raw_context(the original Coraza audit JSON) to find the exact matched variable.TargetTypeenum.scope_pathfromrequest_uriby stripping the query string.The log-shipper already parses Coraza audit JSON (
src/log-shipper/app/mapping.py). The same parsing helpers can be reused or adapted in the backend.Example Coraza audit snippet:
{ "messages": [{ "message": "SQL Injection Attack Detected via libinjection", "details": { "match": "Matched \"...\" at ARGS:token" } }] }From
"at ARGS:token"the endpoint derives:target_type:argstarget_value:"token"Edge cases to handle
raw_context→ return422with clear message.raw_context→ use the first one that contains a rule-bearing message.rule_idandscope_pathpre-filled, leavingtarget_typeandtarget_valueempty for manual entry.422because exclusions need a policy.Frontend Changes
LogDetailModal(visible only for events withrule_idand when the user is an admin).POST /logs/{id}/suggest-exclusionto get pre-filled data./config/apply.Acceptance Criteria
RuleExclusionunder the correct policy.raw_contextcannot be parsed, the form opens with whatever fields could be extracted.pnpm run type-checkandpnpm run lint.Out of Scope
Related
README.waf-rules.md— tuning documentationEstimated effort: ~150–250 lines across backend + frontend. Small, focused PR.