Skip to content

auth: 400 for a segment of dots and whitespace alone; keep an unknown name's ladder to its table - #70

Merged
shreeve merged 1 commit into
mainfrom
auth/strict-dot-runs
Oct 11, 2026
Merged

shreeve merged 1 commit into
mainfrom
auth/strict-dot-runs

Conversation

@shreeve

@shreeve shreeve commented Oct 11, 2026 •

Copy link
Copy Markdown
Owner

Follows #66 (merged) and adds the follow-up #65 needs. Both came out of an adversarial review of the two PRs.

A segment of dots and whitespace alone is 400

#66 read a run of dots and spaces that folds away (..%20, ..., %20..) two ways: dropped, or as ... A path that mixes readings matches neither. Behind a backend that trims spaces but keeps ... as a name, /app/x/.../..%20/..%20/admin/s resolves to /app/admin/s. With gates /app/admin/ {alice} and / {alice, bob}, bob passed. With only the /app/admin/ gate, the request passed with no session at all. #66's own test missed this because its gate sits at the root.

No fixed set of forms covers every mix, so the wall refuses instead (rule 1). On a gated site, any request whose decoded path, or its one further decoding, has a segment (\ read as /, ;parameter cut) made of dots and Unicode whitespace alone, other than . and .., gets a 400 before any gate is matched. That also closes ..%09, ..%0b, and ..%c2%a0, which a strip/TrimSpace backend reads as ... Names that merely contain dots or spaces still pass (report..pdf, v1.2., a b, .hidden).

With the 400 in place, #66's dot-run reading (authFoldPath's dotRuns) never sees a request, so this PR removes it.

The fold trims any trailing whitespace

Segments shed trailing Unicode whitespace as well as dots and spaces. A gate prefix holding any Unicode whitespace is refused; before, only space, tab, CR, and LF were.

An unknown name's account ladder stays with its table (#65 follow-up)

#65 keyed the account ladder on the credential. Every name a table doesn't hold has the empty credential, so those names shared one row across every table in the process. Burning dave on site B made site A answer 429 for dave, where a held name answered 401. That let an attacker tell which names a table holds. A name the table doesn't hold now keys on that table's realm (authSite.accountScope).

Tests

  • TestAuthWallRefusesAmbiguousSegments: the reproductions above, with and without a session, under a nested gate and a lone gate, plus names that must still pass. All 30 refusal checks fail with the 400 disabled.
  • TestAuthGateFoldsTrailingDotsAndSpaces: adds %09, %c2%a0, .%0b, and a prefix holding NBSP.
  • TestAuthAccountLadderFollowsTheCredential: burns dave on site A, then signs in as dave on site B through the real door and expects 401. It fails on the old key.
  • Acceptance: a segment of dots and whitespace alone is 400 (nothing reaches the app).
  • make test: unit suites pass, and acceptance passes 175/175 in the foreground.

Not in scope (flagged by the review, left for a decision)

Full-width dots and slashes, overlong %c0%ae, ..%00, triple encoding, and .%20. are outside the declared forms. Rejecting control characters and invalid UTF-8 on gated sites would close most of them.

@shreeve
shreeve force-pushed the auth/strict-dot-runs branch from b72a3db to 3901ec9 Compare October 11, 2026 12:32
…g whitespace, and keep an unknown name's ladder to its table

A segment such as ..%20, ..., or %20.. reads three ways on a backend:
kept, dropped, or climbed as .., and a path mixing readings
(/app/x/.../..%20/..%20/admin/s) escaped every fixed set of gate forms,
including the dot-run reading of #66. The wall now answers 400 for such
a segment in the decoded path or its one further decoding, before any
gate, so that reading never sees a request and is removed. The fold
trims trailing Unicode whitespace, not only spaces, and a gate prefix
holding any Unicode whitespace is refused.

A name no users table holds shared one account-ladder row across every
table, so burning it on one site answered 429 on another where a held
name answered 401. It now keys on the table's realm.
@shreeve
shreeve force-pushed the auth/strict-dot-runs branch from 3901ec9 to fb407a4 Compare October 11, 2026 13:04
@shreeve
shreeve merged commit 9d96e66 into main Oct 11, 2026
4 checks passed
@shreeve
shreeve deleted the auth/strict-dot-runs branch October 11, 2026 13:42
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.

1 participant