Skip to content

fix(privacy): fetch the star count server-side, drop all third-party calls - #8

Merged
ClaraVnk merged 1 commit into
mainfrom
fix/no-third-party-calls
Jul 20, 2026
Merged

fix(privacy): fetch the star count server-side, drop all third-party calls#8
ClaraVnk merged 1 commit into
mainfrom
fix/no-third-party-calls

Conversation

@ClaraVnk

Copy link
Copy Markdown
Contributor

Contexte

static/footer.js fetched api.github.com from the visitor's browser on every page load β€” including paste pages.

On a zero-knowledge paste service that is the wrong shape. Opening a secret link disclosed the reader's IP, User-Agent and the timing of the read to a third party. The service promises the server cannot read your content, and then had the client announce itself somewhere else. It also cost a third-party connection on the critical path of every page load.

Changements

  • app/stars.py β€” the count is fetched server-side, at most once an hour, and rendered into the page. Background task started in lifespan and cancelled on shutdown; the fetch runs on a worker thread so a hanging GitHub cannot block the event loop. Failures keep the previous value and are logged, never raised β€” a vanity counter must not affect serving pastes.
  • GITHUB_REPO setting (default stackopshq/ghostbit, documented in .env.example). Set it to "" and the outbound call disappears entirely, for air-gapped deployments.
  • Footer degrades honestly: with no count available it links to GitHub without a number instead of rendering a misleading 0 β€” which is what the old markup shipped in the HTML before the fetch resolved.
  • static/footer.js deleted, and connect-src drops back to 'self' now that nothing calls out.

urllib rather than httpx: httpx is a test-only dependency here, and webhook.py already establishes stdlib HTTP as the convention for outbound calls. No new runtime dependency.

Tests

Browser-verified against the running app, on both page types:

index: third-party requests = NONE
index: footer = "⭐ 6 stars on GitHub"
index: CSP violations / errors = none
paste: third-party requests = NONE
paste: footer = "⭐ 6 stars on GitHub"
paste: CSP violations / errors = none

The paste page was reached by actually creating a paste through the UI, so the end-to-end encrypt-and-submit flow is exercised too. Separately, booting with GITHUB_REPO="" renders ⭐ Star us on GitHub with no errors.

118 tests pass β€” three added: no api.github.com in either page's HTML, connect-src stays 'self'-only, and the footer degrades correctly with and without a count. ruff check / ruff format --check clean.

Risques

The server now makes an outbound call it did not make before, which is a change in deployment posture: air-gapped or egress-filtered installs will log a warning hourly. That is why GITHUB_REPO="" exists and why every failure path keeps serving pages. The alternative β€” deleting the counter outright β€” was rejected as a product decision that is not mine to make; this preserves the feature while removing the leak.

One behaviour change worth noting: the count is now identical for all visitors and up to an hour stale, rather than per-browser cached in localStorage. For a star count that is fine, and it removes two localStorage keys from visitors' browsers.

…calls

The footer fetched api.github.com from the visitor's browser on every page
load, paste pages included. On a zero-knowledge paste service that is the wrong
shape: opening a secret link disclosed the reader's IP, User-Agent and the
timing of the read to a third party. The service promises the server cannot see
your content, then had the client announce itself elsewhere.

The count is now fetched by the server at most once an hour and rendered into
the page. GITHUB_REPO="" disables the outbound call entirely for air-gapped
deployments; the footer then links to GitHub without a number, and never shows
a misleading 0 when the count is simply unknown.

Refreshing runs as a background task started in lifespan and cancelled on
shutdown, with the fetch on a worker thread so a hanging GitHub cannot block
the event loop. Failures keep the previous value and are logged, never raised:
a vanity counter must not affect serving pastes.

Uses urllib rather than httpx β€” httpx is a test-only dependency here, and
webhook.py already establishes stdlib HTTP as the convention for outbound calls.

With nothing calling out any more, connect-src drops back to 'self'.

Verified in a real browser on both page types: zero third-party requests, no
CSP violations, footer renders the count, and a paste round-trips end to end.
@ClaraVnk
ClaraVnk merged commit a41daf4 into main Jul 20, 2026
3 checks passed
@ClaraVnk
ClaraVnk deleted the fix/no-third-party-calls branch July 20, 2026 11:01
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