DEV Community

OnePassApps
OnePassApps

Posted on

The Method Contract of app.onepassapps.com: 56 URLs, One 405

I work on OnePassApps, so this is our own site, and everything below was measured on app.onepassapps.com today from outside the browser - plain HTTPS requests, no session, no cookies, no JavaScript.

We have written about what this site sends back on an ordinary document request: the header set, the cache tiers, the route contract, the DNS and TLS layers. All of those describe the same request shape - a GET for a page. This one asks a narrower question: what does the site answer when the only thing that changes is the HTTP method?

One method reaches every URL

I took the 56 URLs in our own sitemap.xml and sent each one a GET, a HEAD and a POST.

  • GET answers 200 on all 56.
  • HEAD answers 200 on all 56, with the same headers and no body.
  • POST answers 405 on all 56.

There is no per-page variation anywhere: no URL accepts a write, and no URL answers anything else.

The refusal is empty, and it is identical everywhere

Every one of the 56 405 answers carries a content-length of 0 and returns zero bytes. Nothing is stored and nothing is queued. The answer is the same on the home page, on a guide page and on a URL that does not exist.

The 405 keeps the security headers and drops the document ones

Comparing the two responses over HTTP/2 with the same client:

  • The 200 carries cache-control and content-type; the 405 carries neither.
  • The 405 carries content-length; the 200 does not.
  • Everything else is identical, including the whole security set: content-security-policy, strict-transport-security, permissions-policy, referrer-policy, x-content-type-options, x-frame-options, cross-origin-opener-policy, cross-origin-resource-policy and x-permitted-cross-domain-policies.

So a refused write still tells the browser the same policy story as a served page - same CSP, same referrer policy, same framing rules - it simply has nothing to describe. Over HTTP/2 the 200 carried 19 header names and the 405 carried 18; absolute counts move with the client and the HTTP version, so the difference above is the part that holds.

No Allow header

RFC 9110 lets a server answering 405 name the methods the resource does support in an Allow header. Ours sends none, on any of the 56 URLs. A client that sends a write and wants to know what to try instead has to know from outside: on this site, GET and HEAD.

The method is checked before the route

A GET to a URL that does not exist answers 404 with the miss page. A POST to that same URL answers 405 with an empty body - the method is refused before the server looks the route up. The 405 is therefore the same for a page that exists and for a page that never existed.

Two hosts, two answers to OPTIONS

The document host and the API host do not answer OPTIONS the same way.

  • On app.onepassapps.com, OPTIONS answers 405. It does send access-control-allow-origin: *, the same open origin the documents send, but it sends no access-control-allow-methods and no access-control-max-age.
  • On api.onepassapps.com, OPTIONS answers 204 with access-control-allow-methods: GET,POST,PUT,PATCH,DELETE,OPTIONS and access-control-max-age: 600, and it echoes the origin back instead of a wildcard.

That difference is why a browser never preflights a write against our pages: there is nothing on the document host to preflight. We have covered the API host's origin rules in an earlier post; this is the document host's side of the same boundary.

TRACE is refused by the edge, not by the app

TRACE is refused as well, but its answer is not ours - the body is a short Cloudflare refusal rather than the empty body the app returns for the other methods.

What this means in practice

  • GET and HEAD are the only methods that reach a document on this site; every other method answers 405 with an empty body.
  • The refusal is uniform: 56 of 56 URLs, the same headers, the same zero bytes, the same for a route that exists and a route that does not.
  • A HEAD request is the cheap way to check a route - it answers 200 with the same headers and no body.
  • The security headers do not change with the method, so the browser policy on a refused request matches the policy on a served page.

Try it yourself: the app or read the guides.

I work on OnePassApps, and this is our own site, measured today rather than described from memory.

Top comments (0)