Skip to content

test(ui): cover analytics-proxy's allowlist, cookie/IP, and set-cookie stripping (#8387)#8450

Merged
loopover-orb[bot] merged 2 commits into
JSONbored:mainfrom
galuis116:test/analytics-proxy-coverage
Jul 24, 2026
Merged

test(ui): cover analytics-proxy's allowlist, cookie/IP, and set-cookie stripping (#8387)#8450
loopover-orb[bot] merged 2 commits into
JSONbored:mainfrom
galuis116:test/analytics-proxy-coverage

Conversation

@galuis116

Copy link
Copy Markdown
Contributor

Summary

Closes #8387.

apps/loopover-ui/src/lib/analytics-proxy.ts's handleAnalyticsProxy relays the app's cookieless analytics beacon (POST /stats/api/send) to the Umami-compatible upstream without ever exposing that host to the browser. The module's own comments spell out four security-sensitive behaviors, and it had zero test coverage -- no test file referenced handleAnalyticsProxy or analytics-proxy at all, so a regression in any of them would ship with no signal.

Adds apps/loopover-ui/src/lib/analytics-proxy.test.ts (vi.stubGlobal("fetch", ...) with afterEach(() => vi.unstubAllGlobals()), the established pattern in this directory), covering all four plus the fail-safe:

  1. Forwarding -- an allowed POST /stats/api/send reaches https://tasty.aethereal.dev/api/send with the query string preserved, and the upstream's status / statusText / body are relayed back.
  2. Method gate -- a disallowed method returns 405 with an allow header and never calls fetch.
  3. Allowlist -- a path outside ROUTES (/stats/admin, /stats/api/collect) returns undefined (SSR fallthrough) and never calls fetch. This is the load-bearing "must NOT become an open proxy onto the Umami host, whose admin/auth API lives on the same origin" guarantee.
  4. Cookie stripping -- the visitor's first-party cookie is not forwarded (the fix for closed issue [Bug]: Umami analytics reverse-proxy forwards first-party request cookies to the upstream, breaking its "cookieless / strips cookies" guarantee #597's cookieless-guarantee bug), while a non-stripped header still passes through, so the assertion isn't just "drops everything".
  5. IP trust -- x-forwarded-for is re-derived from the trusted cf-connecting-ip and a client-supplied x-forwarded-for is dropped (no geolocation spoofing); with no cf-connecting-ip, no x-forwarded-for is set.
  6. set-cookie stripping -- the upstream's set-cookie is not relayed to the browser, while unrelated response headers still are.
  7. Fail-safe -- an upstream fetch throw yields 502 rather than taking the page down.

Test-only, no production change.

Scope

  • The PR title follows type(scope): short summary Conventional Commit format.
  • This PR is focused and does not mix unrelated backend, UI, MCP, docs, dependency, and deploy changes.
  • This follows CONTRIBUTING.md and does not reintroduce GitHub Pages, VitePress, site/, or CNAME.
  • I linked a currently open issue this PR resolves (Closes #8387).

Validation

  • git diff --check
  • npm run actionlint - no workflow files touched.
  • npm run ui:typecheck
  • npm --workspace @loopover/ui run test -- --run src/lib/analytics-proxy.test.ts - 8/8 pass.
  • npm run test:coverage / test:workers / build:mcp - not run: this adds one test file under apps/loopover-ui/ and touches no backend, worker, or MCP surface.
  • npm audit --audit-level=moderate - no dependency changes.
  • New behavior has unit tests for new branches and fallback paths - this PR is that coverage.

If any required check was skipped, explain why:

  • Test-only change under apps/**, which codecov.yml ignores, so codecov/patch has no changed lines to score.

Safety

  • No secrets, wallet details, hotkeys, coldkeys, user PATs, private keys, raw trust scores, private rankings, or private maintainer evidence are exposed.
  • Public GitHub text stays sanitized, low-noise, and does not imply compensation guarantees or optimization tactics.
  • Auth, cookie, CORS, GitHub App, Cloudflare, or session changes include negative-path tests - yes: the cookie-strip, allowlist-rejection, method-rejection, IP-spoof-rejection, and set-cookie-strip cases are all negative-path assertions.
  • API/OpenAPI/MCP behavior is updated and tested where needed - n/a.
  • UI changes use live API data or real empty/error/loading states - n/a, no component or rendering change.
  • Visible UI changes include a UI Evidence section - n/a: test-only, no visible UI difference.
  • Public docs/changelogs are updated where needed - n/a.

Notes

  • The upstream fetch is stubbed via a small typed recorder rather than reading mock.calls, so the forwarded URL/method/headers are inspected without casting through the Workers RequestInit<CfProperties> type.

…e stripping (JSONbored#8387)

apps/loopover-ui/src/lib/analytics-proxy.ts relays the cookieless analytics
beacon to the Umami-compatible upstream without exposing that host to the
browser. Its four security behaviors -- strict path allowlist, first-party
cookie stripping (the JSONbored#597 fix), cf-connecting-ip-only x-forwarded-for
(anti-geo-spoof), and upstream set-cookie stripping -- had zero coverage; no
test referenced handleAnalyticsProxy at all.

Adds apps/loopover-ui/src/lib/analytics-proxy.test.ts (vi.stubGlobal fetch,
the established pattern in this dir), covering:

- an allowed POST forwards to https://tasty.aethereal.dev/api/send with the
  query preserved and the upstream status/statusText/body relayed
- a disallowed method returns 405 + allow header and never fetches
- a non-allowlisted path (/stats/admin, /stats/api/collect) returns undefined
  (SSR fallthrough) and never fetches
- the visitor cookie is stripped while unrelated headers pass through
- x-forwarded-for is re-derived from cf-connecting-ip and a client-supplied
  value is dropped; no cf-connecting-ip means no x-forwarded-for
- upstream set-cookie is stripped while other response headers are relayed
- an upstream fetch throw fails quietly with 502

Test-only, no production change.
@galuis116
galuis116 requested a review from JSONbored as a code owner July 24, 2026 13:26
@superagent-security

Copy link
Copy Markdown
Contributor

Superagent didn't find any vulnerabilities or security issues in this PR.

@loopover-orb loopover-orb Bot added the gittensor:bug Gittensor-scored bug fix — scores a 0.05x multiplier. label Jul 24, 2026
@loopover-orb

loopover-orb Bot commented Jul 24, 2026

Copy link
Copy Markdown
Contributor

Tip

✅ LoopOver review result - approve/merge recommended

Review updated: 2026-07-24 13:57:56 UTC

1 file · 1 AI reviewer · no blockers · readiness 93/100 · CI green · clean

✅ Suggested Action - Approve/Merge

  • safe to merge

Review summary
Test-only addition covering handleAnalyticsProxy's forwarding, method gate, allowlist, cookie stripping, IP trust, set-cookie stripping, and fetch-throw fail-safe. The assertions exercise the real handler against a stubbed global fetch and check both the forwarded request and the relayed response, not just a fabricated payload, so the coverage is genuine rather than pinning an unreachable branch. Notably there's an existing, near-duplicate test/unit/analytics-proxy.test.ts covering largely the same behaviors (allowlist, cookie strip, IP trust, set-cookie strip) against the same handler — this PR doesn't reference or reconcile that file, so the repo now has two overlapping suites for one module.

Nits — 5 non-blocking
  • apps/loopover-ui/src/lib/analytics-proxy.test.ts duplicates most of the scenarios already covered in test/unit/analytics-proxy.test.ts (cookie strip, IP trust, set-cookie strip, allowlist/405) — worth noting in the PR description or consolidating so there isn't split, overlapping coverage of the same module in two locations.
  • The JSDoc-style comment on stubUpstream (line 12) is the only doc comment in the file; consider matching the plainer inline-comment style used elsewhere in this file and in test/unit/analytics-proxy.test.ts for consistency.
  • The 'no cf-connecting-ip' test (send({ headers: { x-forwarded-for: ... } })) only asserts x-forwarded-for is null but doesn't assert fetch was still called / response succeeded, so it's narrower than it could be, though not incorrect.
  • Cross-reference or fold this new test file's scenarios against test/unit/analytics-proxy.test.ts so future changes only need to update one location.
  • Consider adding a case where the query string is empty to confirm no trailing '?' is appended, since the current forwarding test only exercises a non-empty query.

Decision drivers

  • ✅ Code review — No blockers (1 reviewer)
  • ✅ Gate result — Passing (No configured blocker found.)
Context & advisory signals — never blocks the verdict
Signal Result Evidence
Linked issue ✅ Linked #8387
Related work ✅ No active overlap found No same-issue or scoped active PR overlap found.
Change scope ✅ 20/20 Low review scope from cached public metadata (1 linked issue).
Validation posture ✅ 25/25 PR body includes validation/test evidence.
Contributor workload ✅ 10/10 Author activity: 1897 registered-repo PR(s), 1235 merged, 56 issue(s).
Contributor context ✅ Confirmed Gittensor contributor galuis116; Gittensor profile; 1897 PR(s), 56 issue(s).
Improvement ℹ️ Insufficient signal risk: clean · value: insufficient-signal · LLM: moderate
Linked issue satisfaction

Partially addressed
The new test file covers forwarding, the method gate, the allowlist, cookie stripping, cf-connecting-ip-based x-forwarded-for, and set-cookie stripping, but it never asserts that content-encoding/content-length are stripped from the relayed response headers as the issue explicitly requires, and the 502 fail-safe test doesn't verify the response body is null.

Review context
  • Author: galuis116
  • Role context: outside_contributor
  • Public audience mode: oss maintainer
  • Lane context: Repository is configured for direct PR review.
  • Public profile languages: not available
  • Official Gittensor activity: 1897 PR(s), 56 issue(s).
  • PR-specific overlap: none found.
Contributor next steps
  • Start here: Triage stale or unlinked PRs.
Signal definitions
  • Related work = same linked issue, overlapping active PRs, or title/path similarity.
  • Change scope = cached public metadata such as size labels, draft state, and review-burden hints.
  • Validation posture = whether the PR provides enough public validation/test evidence for maintainer review.
  • Contributor workload = public contributor activity and cleanup pressure, not a repo-wide quality failure.
  • Contributor context = public GitHub/Gittensor identity context; non-Gittensor status is not a blocker.
🧪 Chat with LoopOver

Ask LoopOver a question about this PR directly in a comment — grounded only in the same cached, public-safe facts shown above, never a new claim.

  • @loopover ask &lt;question&gt; answers contribution-quality Q&A with source citations and freshness.
  • @loopover chat &lt;question&gt; answers in natural prose from cached decision-pack facts via local inference (maintainer/collaborator; read-only).
  • A plain-language @loopover mention with a real question is routed to the closest matching read-only command automatically — no exact syntax required.

Full command reference: https://loopover.ai/docs/loopover-commands

🧪 Experimental — new and may change.

🟩 Safe / merged · 🟦 Advisory · 🟨 Held for review · 🟥 Blocked / closed


💰 Earn for open-source contributions like this. Gittensor lets GitHub contributors earn for the work they already do — register to start earning →.

Checked by LoopOver, a quiet PR intelligence layer for OSS maintainers.

  • Re-run LoopOver review

@loopover-orb loopover-orb Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

LoopOver approves — the gate is satisfied and CI is green.

@loopover-orb
loopover-orb Bot merged commit 12448b9 into JSONbored:main Jul 24, 2026
9 checks passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

gittensor:bug Gittensor-scored bug fix — scores a 0.05x multiplier.

Projects

None yet

Development

Successfully merging this pull request may close these issues.

analytics-proxy.ts's cookie-stripping/allowlist/IP-trust logic has zero test coverage

1 participant