Add decision log levels and the fields needed to review an allow #9

Merged
james merged 1 commit from decision-log into main 2026-10-08 23:21:52 +00:00
Owner

Problem

The log recorded refusals and failures only, so a false allow left no trace. A request the gate passed with a WAF score, or one the model cleared by a narrow margin, was invisible. Since v0.1.8 most hosts produce no log lines at all.

Change

New setting CENTINELA_LOG_DECISIONS:

Level Logged
blocked (default) refusals and failures, as before
escalated also every allowed request that showed a signal: anything past the gate, and anything the gate passed with a WAF score above 0
all every request, bypassed and clean ones included

An unknown value fails at startup. Rate-limited requests stay at one line per client per window at every level.

New fields on each decision line:

Field Meaning
id per request; a review-queued allow and its late review verdict share it
scores the model's three strongest hazards and its severity, whether or not any reached a threshold; kept with cached verdicts
rules up to 12 matched CRS rule ids
ms time since the request arrived; for a late verdict, how long after

Existing fields are unchanged. Query strings, headers and bodies are still never logged.

Example at escalated:

{"level":"INFO","msg":"decision","id":"9f2c41d07a3e","verdict":"allow","source":"model","reasons":null,"scores":["sqli=0.31","recon=0.12","xss=0.02","severity=0.40"],"waf_score":5,"rules":[942100,949110],"ms":148,"client":"198.51.100.9","method":"GET","host":"blog.example","path":"/search"}

Notes

  • Log volume: escalated adds a line for every scored or model-judged allow; all adds one per request.
  • policy.Decision gains Scores, and request.Request gains ID and Received (neither is sent to the model or the training log).

Tests

  • Which of bypass, clean, under-the-floor, model-allowed and hostile requests are logged at each level.
  • Field contents on a model allow and on the cache hit that follows; the query value is absent from the log.
  • A review-queued allow and its late verdict share an id.
  • Config default, valid value, and rejection of an unknown level.

make vet, make build and make test (race detector) pass.

Merging to main runs build-and-push, which tags and publishes the next patch version.

## Problem The log recorded refusals and failures only, so a false allow left no trace. A request the gate passed with a WAF score, or one the model cleared by a narrow margin, was invisible. Since v0.1.8 most hosts produce no log lines at all. ## Change New setting `CENTINELA_LOG_DECISIONS`: | Level | Logged | |---|---| | `blocked` (default) | refusals and failures, as before | | `escalated` | also every allowed request that showed a signal: anything past the gate, and anything the gate passed with a WAF score above 0 | | `all` | every request, bypassed and clean ones included | An unknown value fails at startup. Rate-limited requests stay at one line per client per window at every level. New fields on each `decision` line: | Field | Meaning | |---|---| | `id` | per request; a `review-queued` allow and its late `review` verdict share it | | `scores` | the model's three strongest hazards and its severity, whether or not any reached a threshold; kept with cached verdicts | | `rules` | up to 12 matched CRS rule ids | | `ms` | time since the request arrived; for a late verdict, how long after | Existing fields are unchanged. Query strings, headers and bodies are still never logged. Example at `escalated`: ```json {"level":"INFO","msg":"decision","id":"9f2c41d07a3e","verdict":"allow","source":"model","reasons":null,"scores":["sqli=0.31","recon=0.12","xss=0.02","severity=0.40"],"waf_score":5,"rules":[942100,949110],"ms":148,"client":"198.51.100.9","method":"GET","host":"blog.example","path":"/search"} ``` ## Notes - Log volume: `escalated` adds a line for every scored or model-judged allow; `all` adds one per request. - `policy.Decision` gains `Scores`, and `request.Request` gains `ID` and `Received` (neither is sent to the model or the training log). ## Tests - Which of bypass, clean, under-the-floor, model-allowed and hostile requests are logged at each level. - Field contents on a model allow and on the cache hit that follows; the query value is absent from the log. - A `review-queued` allow and its late verdict share an id. - Config default, valid value, and rejection of an unknown level. `make vet`, `make build` and `make test` (race detector) pass. Merging to `main` runs `build-and-push`, which tags and publishes the next patch version.
Add decision log levels and the fields needed to review an allow
All checks were successful
security-scan / security-scan (pull_request) Successful in 28s
test / go (pull_request) Successful in 4m49s
a46413f0df
The log recorded refusals and failures only, so a false allow left no
trace: a request the gate passed with a WAF score, or one the model
cleared by a narrow margin, was invisible.

CENTINELA_LOG_DECISIONS selects which decisions get a line:

  blocked    refusals and failures (the default, as before)
  escalated  also every allowed request that showed a signal: anything
             past the gate, and anything the gate passed with a WAF
             score above 0
  all        every request

Each line gains:

  id      per request, shared by a review-queued allow and its late
          verdict
  scores  the model's three strongest hazards and severity, whether or
          not any reached a threshold; kept with cached verdicts
  rules   up to 12 matched CRS rule ids
  ms      time since the request arrived

Query strings, headers and bodies are still never logged.

🔎 ojo scan results

Severity Count
🟠 HIGH 2
🟡 MEDIUM 4
🟢 LOW 1
⚪ UNKNOWN 5
Details (12)
Type Severity ID/Rule Location Description
vuln 🟠 HIGH GHSA-6gcq-wc29-5xf2 github.com/corazawaf/coraza/[email protected] Coraza JSON body processor: argument-limit truncation reopens an unbounded-depth gjson.Valid stack overflow (process crash)
vuln 🟠 HIGH CVE-2026-41510 github.com/corazawaf/coraza/[email protected] Coraza: Silent argument drop at ArgumentLimit allows bypass of ARGS-targeted rules via parameter flooding
vuln 🟡 MEDIUM GHSA-3wr7-993q-jrff github.com/corazawaf/coraza/[email protected] Coraza: Multipart filename* (RFC 5987) charset restriction lets a decoy filename bypass FILES-based rules
vuln 🟡 MEDIUM GHSA-5gj4-9gm7-2fx2 github.com/corazawaf/coraza/[email protected] Coraza body processor has a JSON key collision that allows unauthenticated attackers to bypass OWASP CRS inspection
vuln 🟡 MEDIUM GHSA-g4qm-m288-5cp9 github.com/corazawaf/coraza/[email protected] Coraza has Cookie Parser Confusion
vuln 🟡 MEDIUM GHSA-w253-m66g-rx24 github.com/corazawaf/coraza/[email protected] Coraza: URL-encoded form Content-Type parameters bypass Coraza body inspection
misconfig 🟢 LOW dockerfile-no-healthcheck Dockerfile:1 image has no HEALTHCHECK
vuln ⚪ UNKNOWN CVE-2026-78659 golang.org/x/[email protected] HTTP/2 server memory exhaustion due to Trailer headers in net/http
vuln ⚪ UNKNOWN CVE-2026-78660 golang.org/x/[email protected] HTTP/2 transport accepts malformed framing-related headers in net/http
vuln ⚪ UNKNOWN CVE-2026-78669 golang.org/x/[email protected] Excessive CPU consumption from repeated initial window changes in net/http
vuln ⚪ UNKNOWN CVE-2026-78663 golang.org/x/[email protected] Double flow control refund on HTTP/2 server streams in net/http
vuln ⚪ UNKNOWN CVE-2026-97032 golang.org/x/[email protected] HTTP/2 server crash due to HPACK encoder race in net/http
<!-- ojo-scan-summary --> ### 🔎 ojo scan results | Severity | Count | |---|---| | 🟠 HIGH | 2 | | 🟡 MEDIUM | 4 | | 🟢 LOW | 1 | | ⚪ UNKNOWN | 5 | <details><summary>Details (12)</summary> | Type | Severity | ID/Rule | Location | Description | |---|---|---|---|---| | vuln | 🟠 HIGH | <a href="https://github.com/corazawaf/coraza/security/advisories/GHSA-6gcq-wc29-5xf2" target="_blank" rel="noopener noreferrer">GHSA-6gcq-wc29-5xf2</a> | github.com/corazawaf/coraza/[email protected] | Coraza JSON body processor: argument-limit truncation reopens an unbounded-depth gjson.Valid stack overflow (process crash) | | vuln | 🟠 HIGH | <a href="https://github.com/corazawaf/coraza/security/advisories/GHSA-6r3q-mjv7-xr8m" target="_blank" rel="noopener noreferrer">CVE-2026-41510</a> | github.com/corazawaf/coraza/[email protected] | Coraza: Silent argument drop at ArgumentLimit allows bypass of ARGS-targeted rules via parameter flooding | | vuln | 🟡 MEDIUM | <a href="https://github.com/corazawaf/coraza/security/advisories/GHSA-3wr7-993q-jrff" target="_blank" rel="noopener noreferrer">GHSA-3wr7-993q-jrff</a> | github.com/corazawaf/coraza/[email protected] | Coraza: Multipart filename* (RFC 5987) charset restriction lets a decoy filename bypass FILES-based rules | | vuln | 🟡 MEDIUM | <a href="https://github.com/corazawaf/coraza/security/advisories/GHSA-5gj4-9gm7-2fx2" target="_blank" rel="noopener noreferrer">GHSA-5gj4-9gm7-2fx2</a> | github.com/corazawaf/coraza/[email protected] | Coraza body processor has a JSON key collision that allows unauthenticated attackers to bypass OWASP CRS inspection | | vuln | 🟡 MEDIUM | <a href="https://github.com/corazawaf/coraza/security/advisories/GHSA-g4qm-m288-5cp9" target="_blank" rel="noopener noreferrer">GHSA-g4qm-m288-5cp9</a> | github.com/corazawaf/coraza/[email protected] | Coraza has Cookie Parser Confusion | | vuln | 🟡 MEDIUM | <a href="https://github.com/corazawaf/coraza/security/advisories/GHSA-w253-m66g-rx24" target="_blank" rel="noopener noreferrer">GHSA-w253-m66g-rx24</a> | github.com/corazawaf/coraza/[email protected] | Coraza: URL-encoded form Content-Type parameters bypass Coraza body inspection | | misconfig | 🟢 LOW | dockerfile-no-healthcheck | <a href="https://git.colibrisec.org/ColibriSec/centinela/src/commit/a46413f0df1469a0a21c0c8d682a125a0dde0819/Dockerfile#L1" target="_blank" rel="noopener noreferrer">Dockerfile:1</a> | image has no HEALTHCHECK | | vuln | ⚪ UNKNOWN | <a href="https://go.dev/cl/847185" target="_blank" rel="noopener noreferrer">CVE-2026-78659</a> | golang.org/x/[email protected] | HTTP/2 server memory exhaustion due to Trailer headers in net/http | | vuln | ⚪ UNKNOWN | <a href="https://go.dev/cl/835145" target="_blank" rel="noopener noreferrer">CVE-2026-78660</a> | golang.org/x/[email protected] | HTTP/2 transport accepts malformed framing-related headers in net/http | | vuln | ⚪ UNKNOWN | <a href="https://go.dev/cl/847186" target="_blank" rel="noopener noreferrer">CVE-2026-78669</a> | golang.org/x/[email protected] | Excessive CPU consumption from repeated initial window changes in net/http | | vuln | ⚪ UNKNOWN | <a href="https://go.dev/cl/847187" target="_blank" rel="noopener noreferrer">CVE-2026-78663</a> | golang.org/x/[email protected] | Double flow control refund on HTTP/2 server streams in net/http | | vuln | ⚪ UNKNOWN | <a href="https://go.dev/cl/847188" target="_blank" rel="noopener noreferrer">CVE-2026-97032</a> | golang.org/x/[email protected] | HTTP/2 server crash due to HPACK encoder race in net/http | </details>
james merged commit d79ea017b0 into main 2026-10-08 23:21:52 +00:00
james deleted branch decision-log 2026-10-08 23:21:53 +00:00
Sign in to join this conversation.
No reviewers
No labels
No milestone
No project
No assignees
2 participants
Notifications
Due date
The due date is invalid or out of range. Please use the format "yyyy-mm-dd".

No due date set.

Dependencies

No dependencies set

Reference
ColibriSec/centinela!9
No description provided.