Skip to content

PAT-computed results are cached in a public, auth-agnostic key namespace #164

Description

@lukaso-bot

What

A result computed with a caller-supplied PAT is written to a cache slot that has no auth component in its key, and every public route then reads that same slot anonymously. So a private-repo answer — project path, issue/PR title, release tag — can be served to someone who has no access to that repo.

Mechanism

cacheKey() is called with five parts and nothing else:

('res', '<host>/<projectPath>', 'issue#N' | 'pr#N' | 'sha:X', 'cull'|'strict', 'nopre'|'pre')

Every one of these callsites builds it identically:

  • packages/web/src/routes/lookup.ts:71/api/lookup, computes with makeProvider(env, req, host)
  • packages/web/src/routes/issue.tsx:101 — public permalink page
  • packages/web/src/routes/pr.tsx:86 — public permalink page
  • packages/web/src/routes/result.tsx:79 — public permalink page
  • packages/web/src/routes/badge.ts:110 — public badge

makeProviderresolveProviderToken (auth.ts:13,29) honours the X-User-Github-Token / X-User-Gitlab-Token request headers in preference to the server secret. checkSameOrigin (auth.ts:56) returns true when there is no Origin header, so plain curl reaches /api/lookup.

The token therefore changes what is computed but not where it is stored.

Reproduction shape

  1. A user (legitimately, this is the documented private-repo path) posts their own PAT:
    curl -H 'X-User-Github-Token: <pat>' -d '{"input":"acme/private#42"}' https://released.blabberate.com/api/lookup
    The result is written to the shared slot for 30 minutes (or 30 days once resolve.ts policy applies).
  2. Anyone who guesses or knows the repo path + number opens
    https://released.blabberate.com/i/acme/private/42 — a cache hit, rendered as a full public card with the issue title.

No PAT is leaked, but the content it unlocked is.

Scope — this is pre-existing on main, not introduced by #144

Worth being precise, because it was raised as a #144 finding. The review's claim was that #144 "widens it to the full card" because it aligns /internal's key with the public one, adding the OG PNG as a reader.

That part is not right: issue.tsx on main already uses the byte-identical 5-part key (git show origin/main:packages/web/src/routes/issue.tsx) and already renders the full card, including the title, on a public unauthenticated URL. pr.tsx and result.tsx likewise. So the full-card exposure exists today with no OG involvement at all; #144 adds one more rendering of a slot that is already publicly readable, and widens nothing.

That makes this a defect in code #144 does not touch, which is why it is filed here rather than folded into that PR.

Options

  1. Namespace user-PAT lookups. Add a key part when the token came from a request header — e.g. a 6th part that is anon for the server-token path and a digest of the PAT otherwise. Cheap, keeps the anonymous slot shared and hot, and each PAT holder gets their own slot.
  2. Do not cache PAT-computed results at all. Simplest and safest; costs a recompute per private lookup, which is rare traffic.
  3. Accept it, and say so. Document on /how-it-works that a lookup made with your own token populates a public cache. This is the only option that needs a product call rather than a code change.

Option 1 or 2 look right; the choice between them is a taste call about whether private lookups deserve caching at all.

Not urgent, but not nothing

Exploiting it needs an attacker to know the exact private repo path and issue/PR number, and needs a PAT holder to have looked it up first. There is no evidence in the error history of it happening. But it is a confidentiality bug with a small, well-understood fix.

Found by the round-18 automated review on #144 (thread PRRT_kwDOSe0Tzs6dTung); premise re-verified against origin/main before filing.

Activity

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Projects

    No projects

      Milestone

      No milestone

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions