Skip to content

fix: treat drop action same as deny for status code resolution#261

Open
erfianugrah wants to merge 1 commit intocorazawaf:mainfrom
erfianugrah:fix/drop-action-status-code
Open

fix: treat drop action same as deny for status code resolution#261
erfianugrah wants to merge 1 commit intocorazawaf:mainfrom
erfianugrah:fix/drop-action-status-code

Conversation

@erfianugrah
Copy link
Copy Markdown

Problem

obtainStatusCodeFromInterruptionOrDefault() only checks for action == "deny". When a SecRule uses the drop action, it falls through to defaultStatusCode, which is http.StatusOK (200) in the ServeHTTP call path:

return caddyhttp.HandlerError{
    StatusCode: obtainStatusCodeFromInterruptionOrDefault(it, http.StatusOK),
    ...
}

The request is blocked (the upstream handler is never called), but Caddy sees a 200 status on the error, so handle_errors serves error pages with the wrong code. From the client's perspective, a blocked request comes back as HTTP 200.

coraza-caddy runs as HTTP middleware — it doesn't have access to the underlying TCP socket, so the intended "send FIN and close" behavior of drop isn't possible here. The practical thing to do is treat it the same as deny.

Fix

One-line change: it.Action == "deny"it.Action == "deny" || it.Action == "drop".

When drop fires, it now uses the rule's status: field (or 403 if unset), matching deny behavior. This makes handle_errors and file_server serve error pages with the correct HTTP status.

Tests

Added interceptor_test.go with table-driven tests covering deny, drop, redirect, pass, and unknown actions.

obtainStatusCodeFromInterruptionOrDefault only checks for action ==
"deny", so "drop" falls through to defaultStatusCode (200 in
ServeHTTP). Since coraza-caddy runs as HTTP middleware and can't do
TCP-level FIN/RST, this means drop rules silently return 200 instead
of blocking.

This adds "drop" to the condition so it uses the rule's status field
(or 403 if unset), same as deny. Caddy's handle_errors can then serve
the right error page.

Includes table-driven tests covering deny, drop, redirect, pass, and
unknown actions.
@fzipi fzipi requested a review from Copilot March 12, 2026 15:15
@fzipi
Copy link
Copy Markdown
Member

fzipi commented Mar 12, 2026

@erfianugrah Can you solve conflicts?

Copy link
Copy Markdown

Copilot AI left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

This PR fixes incorrect HTTP status codes returned when Coraza triggers a drop interruption in the Caddy middleware path, aligning drop with deny for status code resolution so downstream Caddy error handling serves the correct status.

Changes:

  • Treat drop the same as deny in obtainStatusCodeFromInterruptionOrDefault() so the interruption’s configured status (or 403) is used.
  • Expand the function’s doc comment to explain why drop is mapped to HTTP semantics in this middleware.
  • Add table-driven unit tests covering deny, drop, and fall-through actions.

Reviewed changes

Copilot reviewed 2 out of 2 changed files in this pull request and generated 1 comment.

File Description
interceptor.go Updates status code selection logic (and documentation) so drop resolves to the rule status/403 like deny.
interceptor_test.go Adds unit tests validating status code behavior across interruption actions.

💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.

You can also share your feedback on Copilot code review. Take the survey.

"github.com/stretchr/testify/require"
)

func TestObtainStatusCodeFromInterruption(t *testing.T) {
Copy link

Copilot AI Mar 12, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Test name is slightly ambiguous: it exercises obtainStatusCodeFromInterruptionOrDefault(), but the test function is named TestObtainStatusCodeFromInterruption. Consider renaming the test to match the function under test for easier grep/discovery (e.g., include "OrDefault" in the test name).

Suggested change
func TestObtainStatusCodeFromInterruption(t *testing.T) {
func TestObtainStatusCodeFromInterruptionOrDefault(t *testing.T) {

Copilot uses AI. Check for mistakes.
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants