Cloudflare injects its JS-detection bootstrap inline into origin HTML.
`script-src 'self'` stops it executing, so the challenge never records
that it was solved and Cloudflare serves another one on the next
request -- a loop no visitor can escape, which reads as a Cloudflare
bug rather than a header of ours.
Allowing it needs `'unsafe-inline'`, since an inline script cannot be
allowlisted by URL and Cloudflare cannot know a nonce we generate. That
is a real weakening, so it is opt-in: the default policy is unchanged
byte for byte, and only an instance Cloudflare actually fronts turns it
on. Nothing else in the policy relaxes.
Co-Authored-By: Claude Opus 5 <[email protected]>
There was no .dockerignore, so `COPY . .` carried target/ -- three
gigabytes of artifacts built by a different toolchain than the builder
stage's -- plus cache/ and .git into every build. Mirror .gitignore's
allowlist and keep the context to what the build actually reads.
The final image is unchanged: the runtime stage only copies the binary,
public/ and config/.
Co-Authored-By: Claude Opus 5 <[email protected]>
The outside-click handler guarded `event.target` against non-elements
and then called `.closest` on it unguarded a line later, so the guard
bought nothing. Take the guarded reference through both uses.
While here, close every popover except the clicked one: the handler left
open popovers alone whenever the click landed on any trigger, so opening
a second account signal left the first one hanging open over the
timeline.
Co-Authored-By: Claude Opus 5 <[email protected]>
`flag` binary searches both tables, so an entry inserted out of order
stops resolving with no error anywhere -- and the sort order lived only
in a comment. Check it, along with the two invariants the rest of the
lookup rests on: names are lowercased, because the needle is, and codes
are `A..=Z`, because `Flag`'s `Display` offsets off `b'A'` and panics
otherwise.
Co-Authored-By: Claude Opus 5 <[email protected]>
The `/photo/N` unfurl encodes the photo in the status id Discord echoes
back, zero-padding the tweet to 19 digits so the prefix stays outside
snowflake range. Nothing exercised that round trip, and the padding is
easy to break without noticing, so pin the encoding, the plain-id case,
the tenth-photo fallback and the rejections. Same for `keep_only_photo`,
which the unfurl leans on to narrow the media.
Co-Authored-By: Claude Opus 5 <[email protected]>
The panel generated `f-<name>` and `e-<name>` checkboxes for ten filters,
but `SearchQuery` named a subset of nine fields and serde dropped the
rest, so "retweets only" and every exclude but replies did nothing. The
one exclude field that existed, `e-retweets`, was a name no checkbox ever
submitted.
Collect the toggles by prefix instead, validating each against
`VALID_FILTERS`, and drive the checkboxes from a single table so the two
sides cannot drift apart again. An exclude now wins over the matching
include, which would otherwise build a query that can never match.
Profile search and the search feed grew the same handling: both parsed
the query text alone and ignored the toggles entirely. The feed keys its
cache on the built query, since filters are part of a feed's identity.
Co-Authored-By: Claude Opus 5 <[email protected]>