Skip to content

CD: forward only safe headers, send credentials only to trusted services, and put all per-type settings under mapping #217

Closed

Description

@MoonWorm

With propagation: HEADERS (the default), the CD resolver copied almost every client header onto every include call. That broke includes and leaked credentials:

  • Accept-Encoding was forwarded. A downstream service with compression on returned a gzipped body, the JDK client doesn't decompress it, and the fetch failed.
  • Conditional and range headers (If-None-Match, If-Modified-Since, If-Match, If-Unmodified-Since, If-Range, Range) were forwarded. A downstream service could answer 304 or 206, which the resolver treats as a failure.
  • Credentials (Authorization, Cookie, the X-Authenticated-* principal headers) went to every mapped service, partner and third-party APIs included.
  • Repeated headers were reduced to their first value. Under HTTP/2, cookies are often split across several lines, so the session cookie could be lost.
  • Hop-by-hop headers (TE, Trailer, Transfer-Encoding, Keep-Alive, Proxy-Connection) and the client's Accept were forwarded.
  • The X-Disable-Compound-Docs lookup was case-sensitive, so X-Disable-Compound-Docs: true from a client could be ignored.

Per-type settings were also spread over three maps: mapping, batchSizeMapping and the new trust list.

Header propagation

  • Never forwarded: headers that change the response (Accept, Accept-Encoding, Content-Type, the conditional and range headers, X-Disable-Compound-Docs), hop-by-hop headers, and the client-address headers as before. Include calls ask for application/vnd.api+json themselves.
  • Every value is sent: CompoundDocsRequest.headers is now a case-insensitive Map<String, List<String>>. Cookies are joined into one Cookie header, as HTTP/1.1 requires.
  • Credentials only where trusted: jsonapi4j.cd.credentialHeaders (default: Authorization, Cookie, Proxy-Authorization, X-Authenticated-User-Id, X-Authenticated-User-Granted-Scopes, X-Authenticated-Client-Entitlements) go to:
    • resource types served by this app itself (loopback or mapping.default), always;
    • a type mapped to another service only with propagateCredentials: true.
  • DomainSettings gets propagateCredentials. The two-argument constructor and of() mean "not trusted", and a custom DomainSettingsResolver decides per route.

One mapping entry per resource type

jsonapi4j:
  cd:
    mapping:
      orders:
        url: https://orders.internal/jsonapi   # served by another service
        maxBatchSize: 50
        propagateCredentials: true
      rates:
        url: https://partner.example.com/jsonapi   # no credentials by default
      countries:
        maxBatchSize: 20                       # same-app type: no url, batch size only
      default:
        url: https://api.internal:8443/jsonapi # this app, when loopback isn't reachable
  • Validation points at the exact key:
    • an entry needs url or maxBatchSize;
    • url must be an absolute http(s) URL;
    • maxBatchSize must be positive;
    • propagateCredentials without a url is rejected;
    • the reserved default entry takes only url.
  • The standalone DefaultDomainSettingsResolver now wraps a Map<String, DomainSettings>.
  • The sample apps' illustrative localhost mappings become batch-size-only entries, with a commented-out example of a remote service.

Breaking changes

  • Keys renamed:
    • mapping.<type> becomes mapping.<type>.url;
    • batchSizeMapping.<type> becomes mapping.<type>.maxBatchSize;
    • mapping.default becomes mapping.default.url (env var JSONAPI4J_CD_MAPPING_DEFAULT_URL).
  • Credentials: remote mapped services no longer receive the client's credentials unless propagateCredentials: true.
  • No longer forwarded downstream: the client's Accept, Accept-Encoding, conditional and Range headers.
  • Standalone API:
    • DefaultDomainSettingsResolver.from(...) is removed;
    • CompoundDocsRequest headers become Map<String, List<String>>;
    • CompoundDocsResolverConfig gains a credentialHeaders argument.

Tests

  • The HTTP client tested against a local server: headers that change the response are replaced, credentials are left out for an untrusted destination and sent (with cookies joined) to a trusted one, and repeated headers arrive with every value.
  • Case-insensitive header lookup.
  • Trust for the app's own routes in fallback routing.
  • mapping validation and the domainSettings() / maxBatchSize() derivation.

Docs: the CD plugin page (property rows, a full mapping example, a new "Header propagation" section, updated base-URL and limitations sections), the standalone resolver docs, configuration.md, and the Claude Code plugin references.

Not verified: Quarkus environment-variable binding for a nested mapping entry.

Activity

  1. added a commit that references this issue on Oct 1, 2026
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