DEV Community

Bruce Hernandez
Bruce Hernandez

Posted on Fully Autonomous

A Failed Request Is Not Proof of Blocking: Test the Decision, Not Just the Error

Your negative test returns an error. The dashboard turns green. Everyone moves on.

But did the security control deny the request—or did the service simply break?

Here is a three-case exercise for separating a policy decision from an application failure. You can follow the short demonstration, then adapt the evidence pattern to a test environment you control.

The setup: three places to observe one request

The demonstration uses three local HTTP services:

Test harness → application policy gateway → synthetic destination
Enter fullscreen mode Exit fullscreen mode

The dashboard is on loopback port 8765, the gateway on 8766, and the destination on 8767. Each test has a new correlation ID, carried across the instrumented services.

This is an application-level policy lab. The destination remains directly reachable locally by design. It demonstrates how to collect evidence; it does not establish host isolation, packet filtering, or production protection.

Watch the 2-minute walkthrough.

1. Establish a working allowed case

Send the allowed request through the gateway to /public.

Look for all three observations:

  • The client receives HTTP 200.
  • The gateway records ALLOW and rule LAB-ALLOW-READ.
  • The destination records that same correlation ID and path.

The matching destination event matters. A successful response from a different route, a cache, or a different request would not establish the path you intended to test.

Record the route and relevant identity/context alongside the result. This small lab has no authentication model; an identity-based system needs that additional context.

2. Test the intended denial

Send the request to /admin through the same gateway.

In this lab, expect HTTP 403, gateway decision DENY, and rule LAB-DEFAULT-DENY. There should be no destination event for that correlation ID.

Do not stop at “403 means secure.” HTTP 403 communicates a refusal to fulfill the request; it does not identify which component made the decision or prove your intended policy caused it. See HTTP Semantics, RFC 9110 §15.5.4.

The gateway's decision and named rule connect the observation to the control being tested. The destination observation provides corroboration within this instrumented lab.

3. Add the failure that should NOT count as a denial

Send /maintenance through the gateway.

The client receives HTTP 503. That looks like another failed request—but the gateway records ALLOW, and the destination records receiving it. The synthetic destination deliberately returns the error.

If your test only checks “response is not 200,” both the denial and the service failure can look like a pass. You have tested availability, without establishing the authorization decision.

Compare the complete evidence:

Case Client status Gateway decision Matching destination event
Allowed /public 200 ALLOW Present
Denied /admin 403 DENY Absent in this lab
Service error /maintenance 503 ALLOW Present

These exact expectations belong to this lab. Your application might intentionally use different responses; write its expected behavior before running the test.

Turn the exercise into a useful test record

For each case, preserve:

  1. Expected outcome, route, and relevant identity or request context.
  2. Timestamp and correlation/request ID.
  3. Actual client response.
  4. Enforcement-point decision and the rule or policy version responsible.
  5. Matching destination observation, including the scope and time window searched.
  6. Verdict, evidence location, and any unresolved limitation.

Use synthetic identifiers. Redact credentials, tokens, and personal data before sharing evidence.

Missing logs alone are weak evidence in a real environment: collection gaps, delay, retention, or a mismatched identifier can also explain absence. Check that instrumentation works using the allowed case, and investigate contradictions rather than marking the control passed.

Try this with your own negative tests

Pick one existing “access denied” test. Add an allowed case on the intended route and a controlled application-error case. Then ask: would the test fail if the policy allowed the request but the backend returned an error?

If the answer is no, improve the assertion and evidence before trusting the green result.

Free checklist and further reading

Use my free Security Control Verification Checklist to organize expected behavior, tests, and evidence. No signup is required.

For more structured reading, I am the author of both books below. These links open my author site, where you can choose the book’s Amazon button:

The exercise above stands on its own; the books are optional further reading.

Prepared with AI assistance using the existing local lab source and walkthrough. The lab uses synthetic data.

Top comments (0)