Skip to content

Feature: --cors-proxy — WAC-gated CORS proxy for arbitrary upstreams #378

Description

@melvincarvalho

Browser apps frequently need to fetch URLs from origins that don't return CORS headers — git remotes (GitHub/GitLab/Gitea), other Solid pods exposing linked data, ActivityPub actors, Nostr relays' HTTP endpoints, RSS/Atom feeds, images, etc. Public CORS proxies (cors.isomorphic-git.org, etc.) work but introduce third-party trust, privacy, and uptime concerns.

JSS already has the building blocks (`src/utils/ssrf.js`, WAC, optional `--pay`) for a self-hosted, authenticated CORS proxy that any browser-side Solid app can use. This issue tracks the general-purpose feature; the git-specific request in #377 is a special case (proxy a git URL through this).

Phase 1 — WAC-gated, free

Flag `--cors-proxy`
Endpoint `/proxy/` (or `/proxy?url=...`)
Auth WAC on the `/proxy/` resource. Pod owner writes a `.acl` granting whatever scope they want — themselves, a list of WebIDs, `foaf:Agent`, or fully public.
Forwards request method, body, headers — minus origin-bound ones (`Origin`, `Host`, `Cookie`, `Referer`); preserves `Authorization` and `Git-Protocol` so smart-HTTP and bearer-auth flows work.
Adds to response `Access-Control-Allow-Origin`, `Access-Control-Allow-Methods`, `Access-Control-Allow-Headers`, `Access-Control-Expose-Headers` (centralized const, same shape as `GIT_CORS_HEADERS` from #374).
SSRF guard `src/utils/ssrf.js` on initial URL and on every redirect hop (DNS rebinding mitigation). Blocks RFC1918, link-local, loopback, IPv6 equivalents. Blocks unusual ports unless allowlisted.
Limits Max upstream response size (configurable, default e.g. 50 MB), max upstream timeout, max redirects (default 5).
Streaming Response is streamed, not buffered, so large pack files / images don't blow memory.

Estimate

~250–400 LOC handler + a handful of tests + flag plumbing in `bin/jss.js`, `src/config.js`, `src/server.js`. 1–2 days for a competent dev.

Phase 2 — optional `--pay` integration (deferred)

Layered on top of Phase 1:

  • Per-request debit at `--pay-cost`
  • Optional `--pay-per-byte` for bandwidth pricing
  • Free-tier rate limit per WebID before paid tier kicks in
  • 402 ↔ 401 mapping so git clients (which don't speak 402) can still surface the gate as a credentials prompt

Not part of v1; tracked here for context.

Use cases unlocked

  • Browser-side git via JSS Git cloning from any host without third-party proxy (closes Feature: JSS as a CORS proxy for git remotes (with optional paid access via HTTP 402) #377)
  • Browser-side linked-data follow-your-nose fetches across pods
  • Browser-side ActivityPub clients fetching foreign actors
  • RSS/Atom readers in pod-hosted apps
  • Image / video embedding from non-CORS hosts
  • Webledger lookups from a browser
  • Anywhere a Solid browser app currently has to either give up or use a third-party proxy

Risks / caveats

  • SSRF is the dominant risk. This endpoint by definition fetches arbitrary URLs. Existing `src/utils/ssrf.js` covers initial-URL validation; the new work is re-validating every redirect target (otherwise an attacker hosts a 302 to `169.254.169.254`).
  • Open-relay reputation: an unauthenticated/public proxy will get added to spam blocklists and abuse reports. The WAC gate is the mitigation — pod operators control their own audience.
  • Resource exhaustion: bandwidth and memory bounds are required. `--cors-proxy-max-bytes`, request timeout, and redirect cap are non-optional.
  • Header smuggling: be deliberate about the request-header allowlist. `Cookie`, `Authorization`, and `Origin` need explicit policy. Phase 1 default: forward `Authorization` + `Git-Protocol` + `Content-Type` + `Accept*`; strip the rest.

Refs

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Type

    No type

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions