HTTP method override defeats method-only review
HTTP method override can turn an approved POST into DELETE or PATCH. Learn how to test tunneled verbs and make reviews judge the effective action.

Method-based review is unsafe when the reviewer sees only the request line. A request can arrive as POST, carry X-HTTP-Method-Override: DELETE, and reach a delete handler after an application middleware rewrites the method. If an approval screen, gateway rule, authorization check, or audit record classifies that request as an ordinary POST, it has reviewed the envelope and missed the action.
This behavior is not an HTTP protocol trick that every server understands. It is an application convention, and that makes it easy to overlook. Support varies by framework, middleware order, route, and deployment. The right response is not to assume every POST is secretly destructive. It is to discover where overrides are accepted, resolve them before any security decision, and reject ambiguity.
The wire method and effective method are different facts
An override-capable stack has two methods to account for: the method on the HTTP request line and the method the application ultimately uses for routing. I call them the wire method and the effective method. They often match. The dangerous case is when an intermediary approves or filters the wire method while a later component routes on the effective method.
RFC 9110 says the method token is the primary source of request semantics. It defines POST as resource-specific processing, while DELETE asks the origin server to remove the association between a target resource and its current functionality. The RFC does not standardize X-HTTP-Method-Override. That header belongs to a family of compatibility conventions created for clients or intermediaries that could handle only GET and POST.
The convention is still implemented in ordinary software. Express's method-override middleware defaults to reading X-HTTP-Method-Override on POST. When it accepts a value, it changes req.method and retains the earlier value in req.originalMethod. ASP.NET Core offers HTTP method override middleware whose default header is also X-HTTP-Method-Override. Spring's HiddenHttpMethodFilter takes a different path: it reads a _method form parameter on POST and permits PUT, DELETE, and PATCH.
Those details sharpen a distinction teams routinely blur. "The gateway allowed POST" describes transport handling. "The application performed DELETE" describes behavior. An approval or access decision needs the latter. If the component making that decision cannot determine the effective method, it must reject override signals rather than silently classifying the request by its outer verb.
Framework support alone does not prove exposure. Express applications must install and order the middleware. ASP.NET Core applications must add the middleware. Spring applications must enable and place the filter. Your deployed configuration, including reverse proxies and route-specific middleware, decides the result. Source inspection gives you candidates; a state-aware test gives you evidence.
An approved POST can reach a DELETE handler
A tunneled write slips through when components disagree about which representation of the action is authoritative. Consider an agent proposing this request against a project API:
POST /v1/projects/42 HTTP/1.1
Host: api.example.test
Authorization: Bearer [injected outside the agent]
X-HTTP-Method-Override: DELETE
Content-Length: 0
The approval layer labels it "POST /v1/projects/42" and applies a rule that permits POST for this session. A reverse proxy forwards the unfamiliar header. RFC 9110 generally requires a proxy to forward unrecognized fields unless configuration blocks or transforms them. Inside the application, override middleware changes the method before routing. The router selects the DELETE handler, and the project disappears.
This failure does not require a broken DELETE authorization check. The application may correctly authorize the principal for deletion. The defect sits earlier: a human or automated reviewer approved a materially different action because its classifier ignored a supported input. The same split can affect a web application firewall, a rate limiter, a CSRF control, request metrics, and an access log.
Middleware order decides which controls see which fact. The Express documentation is unusually direct about this: method override must run before middleware that needs to know the request method. That advice is correct for routing and CSRF logic inside one process. It cannot repair a gateway outside the process that already made a method-based decision from the request line.
There is a second, quieter failure. The edge log records POST, the application log records DELETE, and the incident reviewer treats them as unrelated requests. A shared request identifier can join the records, but only if both sides preserve it and the team knows to compare method fields. A single normalized action record is less error prone.
Do not infer impact from the word DELETE alone. RFC 9110 notes that DELETE removes the association between a resource and its current functionality; storage reclamation and destruction depend on the application. A DELETE endpoint may archive, deactivate, enqueue work, or remove data. Review the route's actual consequence, not a generic verb description.
Test the endpoint with a controlled method matrix
The reliable test compares resulting state across native methods, override headers, and parameter conventions on a disposable resource. Run it in a staging environment or against a fixture designed for destructive tests. Use an identity with only the permissions the real caller has, and recreate the fixture before every case so one successful deletion cannot contaminate the next result.
Start with a baseline POST that contains no override. Record its status, response body, and the fixture's state. Then send a native DELETE. This tells you whether the route exists and whether the test identity can exercise it. Finally, repeat the POST with each convention. A minimal header probe looks like this:
BASE='https://staging.example.test'
ID='override-probe-17'
curl -sS \n -D response.headers \n -o response.body \n -w 'case=x-http-method-override outer=POST status=%{http_code} bytes=%{size_download}
' \n -X POST "$BASE/v1/projects/$ID" \n -H 'Authorization: Bearer test-token' \n -H 'X-HTTP-Method-Override: DELETE'
curl -sS \n -o state.body \n -w 'verify=read-after-request status=%{http_code} bytes=%{size_download}
' \n -H 'Authorization: Bearer test-token' \n "$BASE/v1/projects/$ID"
The output deliberately has a stable shape that CI can parse:
case=x-http-method-override outer=POST status=204 bytes=0
verify=read-after-request status=404 bytes=71
That sample illustrates an override that deleted the fixture; it is not a universal expected status. Some APIs return 200 with a result document, 202 for queued deletion, or a soft-deleted representation that remains readable. Define success from your application's contract.
Use a matrix rather than an improvised sequence. The control is POST with no override, which must produce normal POST behavior. The native case is DELETE with no override, which establishes the documented delete behavior. Then send POST once with each of X-HTTP-Method-Override: DELETE, X-HTTP-Method: DELETE, and X-Method-Override: DELETE. Finish with POST carrying ?_method=DELETE in the query and _method=DELETE in a form body. Every override row must either match your deliberate tunneled-method design or fail without changing state.
OWASP's Web Security Testing Guide names the three header variants above and recommends replaying requests with override headers when a restricted method is rejected. I would go further than comparing status codes, because a different status can come from validation, routing, or an intermediary. Check the resource, its version, and any queued job after every probe.
Capture observations at each hop if you own the stack: edge request log, approval event, application request log, selected route, and data outcome. You are looking for disagreement. A secure result can be either consistent rejection or deliberate normalization followed by the same authorization and approval used for native DELETE.
Status codes are clues while state change is proof
An HTTP status cannot by itself tell you whether an override executed. A 204 followed by a missing fixture is strong evidence. A 200 might be a normal POST response, a delete receipt, or a custom error encoded badly. A 405 might come from the edge before the override middleware, while another path or content type still reaches it.
Build the assertion around a canary with a unique identifier and a known initial version. Before the probe, fetch the resource and retain its version marker or representation digest. After the probe, fetch it again and inspect any operation record the API returns. For update tests, choose a field whose change is harmless and unmistakable. For deletion tests, know whether the product uses hard deletion, soft deletion, or delayed cleanup.
Response comparison still helps. Save the status, selected response headers, body size, and a digest of the body. Compare the override request with both controls: plain POST and native DELETE. If the override response resembles DELETE and the state matches DELETE, you have a convincing finding. If the response resembles POST but state changes as DELETE would, the logging or response layer may be using the outer method.
Redirects deserve their own check. A client can change request behavior while following a 301, 302, 303, 307, or 308, and tools differ in how they retain methods and bodies. First run with redirects disabled and record the Location field. Then follow the redirect deliberately and capture every hop. Do not mix redirect behavior with override behavior in one opaque result.
Async APIs need a longer observation window, but they do not justify guessing. If the response supplies an operation identifier, poll that operation under the documented test contract. If it supplies no durable handle, consult the queue or application log in staging. Mark an inconclusive case as inconclusive instead of calling it safe.
Also verify negative state. A rejected override should not create, update, archive, enqueue, email, or emit a privileged webhook. This is where teams get misled by a tidy 4xx response: an early component rejects the response path after a later component has already committed a side effect. Instrument the test fixture closely enough to detect that sequence.
Header variants and ambiguity belong in the same test
Testing only the canonical spelling misses both compatibility code and parser disagreement. The common header names are X-HTTP-Method-Override, X-HTTP-Method, and X-Method-Override, but applications can define custom getters. Query strings and form bodies can carry _method, and older integrations sometimes use names tied to a vendor or framework.
Header name casing is not a separate convention. RFC 9110 makes field names case insensitive, so x-http-method-override and X-HTTP-Method-Override name the same field. A security filter that matches the capitalized spelling only is defective even if the application library normalizes names.
Duplicate and conflicting values expose a harder class of bugs. RFC 9110 allows a recipient to combine repeated field lines into a comma-separated value when the field's definition permits that form, and it warns implementers to consider duplicates even for fields expected to be singletons. Override headers are not standardized singleton fields with one shared parsing rule. Express's middleware documentation says it uses the first occurrence of a repeated header, while multiple installed override handlers can create precedence between different header names.
Add case variation by sending x-http-method-override: DELETE; it must behave like the canonical casing. Send two identical DELETE values and require one documented result or rejection. Send PUT followed by DELETE, then put different values in two override header names; reject both conflicts as ambiguous. Reject a comma value such as PUT, DELETE, a native PATCH paired with override DELETE unless your contract explicitly defines it, and a DELETE header paired with _method=PATCH.
Do not stop at DELETE. Probe PUT and PATCH because a review system may classify create, replace, partial update, and delete differently. Test an unsupported token as a negative control. Avoid TRACE and CONNECT unless the test environment and scope explicitly permit them; those methods exercise different infrastructure behavior and add risk without improving an override finding.
Whitespace and value casing can reveal inconsistent normalization. Send values such as delete, DELETE where your client permits it, and an empty value. The secure rule is simple: normalize only what your documented convention allows, validate against an explicit method set, and reject everything else. Quietly choosing the first parseable value creates a bypass that will reappear when a proxy or library changes.
Agent-generated requests make the blind spot easier to trigger
AI agents do not create the method-override weakness, but they make incomplete review a worse control. An agent can compose arbitrary headers, reuse examples from documentation, and retry a failed native DELETE as a POST tunnel without understanding that the second form crosses an approval boundary. A person scanning a compact approval card is likely to focus on the visible verb and path.
Treat every request component produced by the agent as untrusted action input. That includes headers that look like compatibility metadata. Credential injection outside the model limits secret exposure, but it does not make the requested operation benign. The request still carries the agent's chosen path, method, headers, query, and body into a system that may interpret them jointly.
Tool schemas can reduce ambiguity before the request exists. A generic HTTP tool with free-form headers gives the caller enough vocabulary to request overrides. A route-specific delete tool can name the destructive action directly and omit override fields. The second design is easier to review, but only when its implementation cannot smuggle additional headers through a hidden escape hatch.
If a generic HTTP tool is necessary, parse the proposed request before showing it to a person. The parser should use the same override inventory and precedence rules as the executor. Freeze the normalized action after approval so the executor cannot resolve a different value later. If any intermediary changes headers after approval, either include that deterministic transform in the reviewed action or resolve again and require a new decision.
Retries need attention too. POST is not inherently harmless or idempotent. RFC 9110 defines POST broadly as resource-specific processing, and many POST endpoints create or mutate data without any override. An agent tool must not infer that a request is safe to retry merely because the outer method says POST, nor infer that every POST needs destructive approval. It needs route semantics plus the effective method.
Keep test identities and production identities separate when evaluating agent behavior. A broad production credential can make every probe succeed and hide authorization differences. Use the same narrow role the agent will hold, verify that native and tunneled forms receive identical decisions, and record what the human approval displayed. The finding is a review mismatch only when you can show the proposed action, displayed action, executed action, and resulting state as one chain.
The earliest control must classify the effective action
Every method-based decision must run after one canonical resolver, or it must reject all override inputs. That applies to human approval, authorization, CSRF defense, routing restrictions, rate limits, and audit classification. Repeating ad hoc parsing in each control guarantees disagreement.
Represent the resolution explicitly:
{
"transport_method": "POST",
"effective_method": "DELETE",
"override_source": "header:x-http-method-override",
"override_value": "DELETE",
"target": "/v1/projects/42",
"ambiguous": false
}
The resolver should inventory every supported header and parameter, collect all supplied values, and reject the request if more than one distinct candidate appears. It should accept overrides only from allowed outer methods, usually POST. It should validate the value against the methods the application intentionally supports, then produce an immutable action record for downstream controls.
Place authorization after resolution and bind it to the route plus effective method. "Can this principal POST to this path?" is too weak when POST is a tunnel. The useful question is "Can this principal perform DELETE on this resource under the current conditions?" Apply the same handler authorization whether the client sent native DELETE or a supported tunneled form.
An approval screen should lead with the consequence and show both representations when they differ. A compact label such as DELETE /v1/projects/42 via POST override gives the reviewer the effective action without hiding the transport detail. Showing only the override header in an expandable raw-request panel makes the reviewer find the dangerous fact under time pressure.
If an upstream gateway cannot reproduce the application's resolution rules, do not teach it a partial subset. Reject requests containing any recognized override signal before approval, or move the approval point behind trusted normalization. A gateway that understands one header while the application understands three gives a false sense of coverage.
Keep resolution separate from business semantics. The resolver can conclude that the effective method is DELETE; the route owns whether that means archive, revoke, detach, or erase. The approval system needs route-aware action descriptions for high-consequence operations. Method normalization is necessary, but a verb alone cannot describe every effect.
Stripping one header is an incomplete fix
Removing X-HTTP-Method-Override at the proxy is safe only when the service does not intentionally support any override path. It is popular because it is simple and often blocks the first proof of concept. It fails when another accepted header, _method parameter, direct upstream route, or later middleware still supplies the effective method.
Microsoft's YARP header guidance names X-HTTP-Method-Override, X-HTTP-Method, and X-Method-Override, says they pass through the proxy by default, and points to a request-header removal transform when the operator wants to prevent bypass. That is useful edge hardening. It is not evidence that the destination ignores query or body overrides, nor does it cover traffic that reaches the destination without YARP.
Choose one of two defensible designs. If compatibility tunneling has no current consumer, disable the middleware in the application and reject all known signals at the edge. If clients rely on tunneling, document one accepted input, remove or reject the alternatives, normalize it before controls, and test native and tunneled requests as the same action.
Inventory code and configuration before choosing. Search for framework middleware, custom request wrappers, _method, all three common header names, and code that assigns a request method. Check route groups because middleware may apply only under an API prefix. Inspect generated API gateways and older compatibility modules that a framework upgrade carried forward.
Then inspect the network path. A content delivery layer, load balancer, service mesh, reverse proxy, application server, and framework may each normalize or log headers differently. Confirm that no public route bypasses the component doing rejection. For internal agent traffic, do not assume a private network makes caller-controlled headers trustworthy.
Stripping can break legitimate clients, which is why teams postpone it. Measure usage before removal by logging presence without logging secrets or full bodies. Announce a deprecation, fail clearly in a test environment, and remove server support with the edge rule. Leaving undocumented compatibility behavior active forever costs more than migrating the clients that still need it.
Logs need both the request line and chosen action
An audit record should preserve the transport method, effective method, override source, normalized value, selected route, authenticated principal, target resource, decision, response status, and confirmed outcome. Without both methods, a reviewer cannot reconstruct why POST reached a DELETE handler. Without the outcome, the record shows intent but not effect.
Record ambiguity rejections too. A request with conflicting override values is security-relevant even when it never reaches a handler. Store header names and normalized method tokens, not authorization values or unrelated request bodies. If a value is invalid, retain a bounded and escaped representation so control characters cannot forge log lines.
Join records across layers with a request identifier generated at the first trusted hop. Do not accept an external identifier without replacing or namespacing it. The edge, resolver, approval service, application, and async worker should carry the trusted identifier so an investigator can follow one action without relying on timestamps.
Method fields also need precise names. A generic method property invites each component to write a different meaning. Use transport_method for the request line and effective_method for the routed action. If middleware exposes originalMethod, map it deliberately rather than assuming the name means the same thing in every framework.
Sallyport executes an agent's HTTP call without exposing the credential to the agent and records the call in its Activity journal. That separation does not make an override safe; review still has to classify the effective method before execution.
Tamper evidence matters after an autonomous run. A write-blind, hash-chained record makes silent editing detectable, but integrity cannot repair missing semantics. Log the normalized action at decision time, not in a later job that attempts to infer it from access logs.
Review dashboards should surface mismatches. A count of transport_method != effective_method, grouped by service and override source, quickly reveals unexpected compatibility traffic. Alert on newly observed sources, ambiguous requests, and destructive effective methods whose approval event recorded only the outer method.
A regression test should fail closed
The durable fix is a contract test that runs against the deployed path and fails whenever an unapproved override changes state. A unit test for the resolver is useful, but it cannot detect a proxy rule disappearing, middleware moving before authorization, or a route becoming reachable through a different ingress.
Write expected behavior for every row in the matrix. If tunneling is disabled, each override signal should produce a documented 4xx response and leave the fixture unchanged. If one convention is supported, its DELETE result should match native DELETE for authorization, approval classification, routing, and audit fields. Conflicts and unknown tokens should always fail before side effects.
Include these assertions in the deployed-path test:
- the approval event names the effective method;
- native and tunneled forms receive the same authorization decision;
- rejected inputs leave the canary version and side-effect counters unchanged;
- logs contain both method fields under one trusted request identifier;
- direct upstream access cannot skip normalization.
Run the suite when proxy configuration, framework middleware, authentication, approval code, routes, or agent tooling changes. A nightly run catches infrastructure drift, while a release gate catches deliberate changes. Keep the fixture isolated so repeated execution never touches human data.
Treat a newly accepted convention as a security change, even if a framework calls it compatibility support. Require an owner, a client need, a precedence rule, an allowed outer method, an allowed effective-method set, and removal criteria. Otherwise a one-line middleware addition silently expands the vocabulary of actions your controls must understand.
The cleanest system has one action description from ingress to outcome. If the request says POST on the wire and DELETE in the application, that difference must become explicit before any person or component says yes. Anything later is incident reconstruction.
FAQ
What is HTTP method override?
HTTP method override is an application convention that carries an effective verb such as DELETE inside a request sent with another verb, usually POST. A framework or middleware reads a header or parameter and changes the method used for routing.
Is X-HTTP-Method-Override a standard HTTP header?
No. RFC 9110 defines HTTP method semantics but does not standardize X-HTTP-Method-Override. Support comes from particular frameworks, middleware, APIs, and custom code, so test the deployed service instead of assuming.
Which method override headers should I test?
Test X-HTTP-Method-Override, X-HTTP-Method, and X-Method-Override. Also inspect query and form conventions such as _method, plus any custom getter configured in the application.
Can a POST request really delete a resource?
Yes, if the destination accepts an override such as X-HTTP-Method-Override: DELETE and routes the request as DELETE. Prove the behavior with a disposable fixture and a state check; the response status alone is not enough.
Should a proxy block all method override headers?
Block and reject them when no legitimate client needs tunneling. If the service intentionally supports an override, normalize the one documented convention before approval and authorization, and reject every conflicting alternative.
Does header capitalization affect method override?
It should not, because HTTP field names are case insensitive. A filter that blocks only one capitalization can disagree with a framework that normalizes header names.
How should duplicate override headers be handled?
Reject repeated or conflicting override values as ambiguous. Different proxies and libraries can combine or select duplicate values differently, so choosing the first value silently is unsafe.
Is a 405 response enough to prove overrides are disabled?
No. The 405 may come from one layer, while another route, header, parameter, or content type still accepts an override. Verify state and logs for every convention through the same deployed path callers use.
What should an approval screen show for a tunneled request?
Show the effective action first, such as DELETE /v1/projects/42 via POST override. Preserve the outer method and override source as supporting detail so the reviewer sees both consequence and transport.
What should method override audit logs contain?
Record the transport method, effective method, override source, selected route, principal, target, decision, response, and outcome under one trusted request identifier. Log ambiguity rejections without copying credentials or unbounded request data.