Unexpected Link header injected into Space responses

I also did a little testing on my side, in case it helps narrow down the issue: (I’ll leave the rest to HF. @hysts )


Assuming the added field is of this form:

Link: <https://huggingface.co/spaces/OWNER/SPACE>; rel="canonical"

my current reading would be:

  1. It does appear to be added after the application response somewhere in the public Spaces serving path. In a small controlled public Docker Space, the application emitted no Link field over an internal loopback request, while the corresponding public .hf.space response contained the Hub Space page as rel="canonical".

  2. The header itself does not appear to be new. I found public response captures containing the same form as far back as October 2024.

  3. Its likely role is URL canonicalization between the direct .hf.space deployment and the Hub Space page, but that purpose is an inference from the value and the standard meaning of rel="canonical". Only Hugging Face can confirm the actual intent and whether this is a supported contract or an implementation detail.

  4. I could not find a documented Space setting that removes or overrides it. The current custom_headers documentation only allows COEP, COOP, and CORP headers. That is not proof that no support-side or internal option exists, only that there does not seem to be a documented public one.

  5. I would not yet generalize this to every Space configuration. It is now directly reproducible on one public Docker Space, and similar fields have appeared in several older Gradio-related response captures, but protected/private Spaces, Static Spaces, custom domains, and some other paths remain untested.

What the controlled probe showed

I used a small public Docker Space with application-owned sentinel fields and an endpoint that performs a loopback request to 127.0.0.1:7860, so the origin response can be compared with what an external client receives.

The most important comparison was:

Internal loopback response:
  origin_link_values: []

Public .hf.space response:
  Link: <https://huggingface.co/spaces/John6666/probe_link_header>;rel="canonical"

The application sentinel fields were still present publicly, so this did not look like a completely unrelated response. The visible sequence was approximately:

application response
    ↓
public Spaces serving path adds the canonical Link
    ↓
external client

This does not identify the exact internal component. From outside, I cannot distinguish CDN, edge, routing proxy, or another serving component.

The probe also tested what happens when the application already supplies Link fields.

Application response:

Link: </docs>; rel="help"

Public response:

Link: </docs>; rel="help"
Link: <https://huggingface.co/spaces/John6666/probe_link_header>;rel="canonical"

When the application supplied two different links, both were preserved and the HF canonical was appended:

Link: </docs>; rel="help"
Link: </probe/no-link>; rel="alternate"; type="application/json"
Link: <https://huggingface.co/spaces/John6666/probe_link_header>;rel="canonical"

More notably, when the application supplied its own canonical target, the public response contained two different canonical relations:

Link: <https://origin.example.invalid/probe>; rel="canonical"
Link: <https://huggingface.co/spaces/John6666/probe_link_header>;rel="canonical"

So, at least in this test, the behavior was closer to preserve and append than replace, filter, or deduplicate.

The same added canonical was observed on:

Dimension Tested values where it appeared
Method GET, HEAD, POST
Status 200, 204, 307, 404
Content HTML, JSON, JavaScript, empty body
Protocol HTTP/1.1 and HTTP/2
Origin Link none, one field, two fields, application canonical

This characterizes one public Docker Space on July 11, 2026. It should not be read as a guarantee for every Space or future serving version.

Practical interpretation

The most useful next branch depends on what is treating the extra field as an error.

  • If this is a strict response-schema, snapshot, or allowlist check, the robust default is usually to validate application-owned fields rather than require the entire public field set to exactly equal the container output.
  • If the application already uses Link, the consumer needs to handle multiple field values correctly. The probe shows that an application value may coexist with the platform canonical.
  • If this is response signing or HMAC verification, signing the complete field set before a managed intermediary modifies it will be fragile; selected application-owned components may be a better contract.
  • If this only fails in a crawler, validator, PWA scanner, or SEO tool, compare that tool with curl or an ordinary HTTP client before concluding that the canonical is the cause.
  • If the hard requirement is that the final public response contain only and exactly the fields emitted by the application, this may be a broader managed-hosting boundary rather than a Link-specific problem.

The smallest additional information that would identify the relevant branch is:

  • the exact raw Link field value;
  • the client, gateway, validator, or scanner that rejects it;
  • the concrete error or failed assertion;
  • whether the application also emits any Link fields;
  • whether ignoring the added canonical makes the failure disappear.

A baseline comparison using an actual GET request is generally more informative than relying only on HEAD or OPTIONS:

curl --http2 -sS -D public.headers -o public.body \
  "https://<space-subdomain>.hf.space/<path>"

curl -sS -D origin.headers -o origin.body \
  "http://127.0.0.1:7860/<path>"

OPTIONS should be checked separately because it may use a different serving path.

Probe results and older public observations

Controlled Docker Space probe

The probe separated four questions:

  1. Does the origin actually emit Link?
  2. Does the public response add it?
  3. What happens to application-supplied Link fields?
  4. Does the behavior vary by method, status, content type, or HTTP version?

Each ordinary application response included fields such as:

X-Origin-Sentinel: hf-link-probe-origin-v1
X-Origin-Request-Id: ...

The loopback snapshot endpoint requested the corresponding route directly from the application inside the container and returned the origin field list in its JSON body.

For the no-Link case, the origin snapshot contained:

{
  "origin_status": 200,
  "origin_link_values": [],
  "origin_headers": [
    ["server", "uvicorn"],
    ["content-type", "application/json"],
    ["x-origin-sentinel", "hf-link-probe-origin-v1"],
    ["cache-control", "no-store"]
  ]
}

The public response to the ordinary endpoint contained the same origin sentinel plus:

x-proxied-host: http://10.x.x.x
x-proxied-path: /probe/no-link?...
Link: <https://huggingface.co/spaces/John6666/probe_link_header>;rel="canonical"

That is reasonably strong evidence that, for this Space, the canonical was added after the application produced its response.

Raw behavior summary

Origin behavior Public behavior
No Link HF canonical added
One rel="help" Original field preserved; HF canonical appended
Two application links Both preserved; HF canonical appended
Application canonical Application canonical preserved; second canonical appended
HTML / JSON / JavaScript HF canonical present
200 / 204 / 307 / 404 HF canonical present
HTTP/1.1 / HTTP/2 Same semantic ordering
OPTIONS Different-looking path; no canonical or origin sentinel

Adjacent OPTIONS observation

A browser-style preflight was also sent with:

Origin: https://example-client.invalid
Access-Control-Request-Method: POST
Access-Control-Request-Headers: content-type,x-probe-id

It returned:

HTTP/2 200
Content-Length: 0
Vary: origin, access-control-request-method, access-control-request-headers
Access-Control-Allow-Methods: POST
Access-Control-Allow-Headers: content-type,x-probe-id
Access-Control-Max-Age: 600
Access-Control-Allow-Origin: https://example-client.invalid

However, it did not contain:

X-Origin-Sentinel
X-Origin-Method
X-Origin-Request-Id
server: uvicorn
x-proxied-host
x-proxied-path
Link

The application had an explicit OPTIONS route and middleware that should have added the sentinel to an ordinary application response.

A cautious interpretation is therefore:

The browser-style preflight response did not look like the ordinary application response path. It may have been answered before the container, or the container response may have been replaced by the serving layer.

This is only an adjacent observation. It does not prove that the canonical behavior and the preflight behavior share one implementation or root cause.

There is a nearby HF Forum report about Access-Control-Allow-Credentials disappearing from Docker Space preflight responses. The symptoms are compatible with a special OPTIONS path, but the reports should not be treated as proof of the same bug.

Older public observations

The controlled probe is stronger for characterizing the behavior, but older public captures help with the timeline.

Date Response type What the source shows Important limitation
October 2024 308 video/file response Same Hub-page canonical plus x-proxied-path in Gradio issue #9851 The issue concerned a Gradio file/video path; the canonical was not established as its cause
February 2025 200 application/json API response Same canonical in a Space API discussion The application-level problem concerned the subsequent API event, not the header
March 2025 404 application/json API response Same canonical in an HF Forum API error capture The immediate failure was an unavailable API route
May 2026 200 application/javascript service worker Same canonical in PWABuilder issue #5837 The reporter later revised the broader failure hypothesis toward headless/WAF blocking

These examples do not establish that the canonical caused any of those failures. They do indicate that the field itself predates the recent Spaces incident reports.

The header can therefore be old even if some other detail changed more recently, such as:

  • which response types receive it;
  • append or merge behavior;
  • Docker applicability;
  • handling of existing application fields;
  • behavior in a particular region or serving revision.

I did not find a corresponding entry in the public Spaces documentation or changelog, but absence from a public changelog is not evidence that no serving-layer change occurred.

What the header may affect, depending on the consumer

What rel="canonical" means

RFC 6596 defines rel="canonical" as a way to identify a preferred IRI for resources with duplicative content. It can be expressed in an HTTP Link field:

Link: <https://example.com/preferred>; rel="canonical"

It is metadata used by consumers such as search engines. It is not equivalent to:

HTTP/1.1 301 Moved Permanently
Location: https://example.com/preferred

A normal HTTP client should not treat the canonical field itself as a network redirect unless that client has explicitly implemented such behavior.

RFC 6596 also says that the canonical target should represent content that is duplicative or a superset of the referring resource, and recommends specifying only one canonical relation for a resource.

That leaves a reasonable question about the scope observed here. The same Hub Space page was attached as the canonical target for:

  • a JSON API result;
  • JavaScript;
  • a 204 No Content response;
  • a 404 response;
  • a redirect response.

Using HTTP Link on a non-HTML resource is not automatically invalid. HTTP canonical fields can be used for non-HTML content. The less obvious part is the intended semantic relationship between the Hub Space page and each individual API result, asset, empty response, or error response.

I would phrase that as a question about intended meaning rather than claim an RFC violation:

What canonical relationship is intended for API responses, static assets, empty responses, and errors?

Multiple Link fields

RFC 8288 defines Web Linking and permits multiple links in one HTTP response.

Therefore, this is not inherently an error:

Link: </docs>; rel="help"
Link: </next>; rel="next"
Link: <https://example.com/page>; rel="canonical"

Different HTTP libraries may expose those as:

  • multiple raw field lines;
  • one comma-separated field value;
  • a list;
  • or, in less robust libraries, only the first or last value.

This means a consumer should not assume:

there will be exactly one Link field

and should avoid parsing it with a naïve unconditional comma split.

The more specific ambiguity from the probe is this:

Link: <https://origin.example.invalid/probe>; rel="canonical"
Link: <https://huggingface.co/spaces/John6666/probe_link_header>;rel="canonical"

Different relation types can coexist normally. Two different targets both claiming to be canonical are more questionable. RFC 6596 explicitly recommends only one canonical relation because more than one authoritative target is confusing.

The practical effect still depends on the consumer:

  • a normal API client may ignore both;
  • a crawler may choose one using its own heuristics;
  • a custom parser may reject duplicates;
  • an application may read only the first or last field;
  • a search engine may treat the signals as conflicting.

The probe confirms the coexistence. It does not establish what any particular client will do with it.

If the issue is a strict schema or response snapshot

If the current setup rejects every response field not produced by the application, a managed hosting layer is likely to remain fragile even if this one canonical could be removed.

Other platform-owned fields may change independently, including:

  • trace or request identifiers;
  • cache fields;
  • CORS fields;
  • security fields;
  • proxy metadata;
  • rate-limit fields;
  • CDN-related fields.

A more stable contract is usually:

status + body + required application-owned fields

rather than:

the complete public field set must exactly equal the origin field set

This does not mean the extra canonical is necessarily desirable. It separates an application contract from fields owned by the serving platform.

If the issue is response signing

If a response is signed before it passes through an intermediary, signing the complete final field set assumes no intermediary will add, remove, normalize, or combine anything.

RFC 9421 defines HTTP Message Signatures around explicitly selected message components rather than requiring an opaque signature over every possible field.

If signing is the issue, possible routes include:

  • signing the response body digest;
  • signing selected application-owned fields;
  • excluding platform-managed fields;
  • verifying that field combination and normalization are handled consistently.

This is only one possible branch; the original post does not indicate whether signatures are involved.

If the application already uses Link

The probe suggests that application fields may be preserved rather than replaced.

That is helpful for relations such as:

next
prev
alternate
help
preload

but it also means the downstream parser must tolerate the added canonical.

Potential traps include:

  • assuming one field only;
  • taking only the first value;
  • taking only the last value;
  • assuming one value per relation type;
  • treating every Link as a redirect;
  • naïvely splitting quoted or parameterized values.

If the application itself sends rel="canonical", the duplicate-canonical case is probably the most concrete issue to show HF.

If the issue appears only in a crawler or validator

A useful control is:

Client Result
curl or ordinary HTTP library Does the API operation complete?
Normal browser navigation Does the resource load?
Failing crawler/scanner What does it do after reading Link?
Same crawler against a non-HF host Does it fail behind a WAF or bot challenge too?

The PWABuilder issue is a useful cautionary example. The canonical initially looked like the cause, but the reporter later reproduced the broader scanner failure on other firewalled domains and revised the hypothesis toward headless/WAF blocking.

So:

the response contains an unexpected canonical

and:

the canonical caused the failure

should be tested separately.

If exact wire-level response ownership is required

Hugging Face documents .hf.space as the Space’s direct serving URL in the embedding documentation, and documents API use for Gradio Spaces in Spaces as API endpoints.

However, I did not find a statement promising that a Docker container’s origin fields will be byte-for-byte or field-for-field identical to the final public response.

If the requirement is:

The public response must contain only and exactly the fields emitted by my process.

then the relevant options are broader:

  1. allow and ignore platform-managed fields;
  2. define the downstream contract only over application-owned components;
  3. ask HF whether a supported header override or opt-out exists;
  4. place a controllable gateway in a supported architecture, if possible;
  5. use hosting where the final HTTP boundary is fully under application control.

The last option is not automatically necessary, but it becomes relevant when exact public-response ownership is a hard requirement rather than a preference.

Untested boundaries and questions for Hugging Face

The controlled probe covered one public Docker Space at one point in time. The following cases were not tested and should not be inferred from that result. They are listed mainly so the boundaries remain visible.

Other Space configurations

HF currently documents three Space SDK choices—Gradio, Docker, and Static—and public, protected, and private visibility modes in the Spaces overview.

Not tested:

  • Gradio Space serving behavior;
  • Static Space serving behavior;
  • protected Space embed responses;
  • authenticated private Space responses;
  • unauthenticated private Space error responses;
  • whether all configurations use the same append behavior.

Static Spaces in particular have a different build-and-serving route, so the Docker result should not automatically be generalized to them.

Custom domains

HF documents custom domains as CNAMEs pointing to hf.space in the custom-domain guide.

Not tested:

  • whether the canonical target remains the Hub Space page;
  • whether it changes to the custom domain;
  • whether the .hf.space and custom-domain responses differ;
  • whether an application canonical is still retained and followed by the HF canonical;
  • whether the custom domain can control or override the field.

This could matter for a site that intends its custom domain, rather than the Hub page, to be the representative URL.

HTML canonical declarations

The probe tested application-supplied HTTP fields such as:

Link: <https://origin.example.invalid/probe>; rel="canonical"

It did not test an HTML document containing:

<link rel="canonical" href="https://example.com/preferred">

The interaction between an HTML canonical and the platform-added HTTP canonical remains untested.

Credentialed browser requests

The browser-style preflight was non-credentialed.

Not tested:

  • cookies;
  • credentials: "include";
  • Access-Control-Allow-Credentials;
  • authenticated browser sessions;
  • OAuth;
  • behavior inside the Hub iframe.

HF documents that the Hub page and the application run on different domains and that the app is embedded in an iframe, which can introduce cookie restrictions. See Cookie limitations in Spaces.

Other response paths

Not tested:

  • conditional requests and 304 Not Modified;
  • byte ranges and 206 Partial Content;
  • file download paths;
  • streaming responses;
  • Server-Sent Events;
  • WebSocket upgrades;
  • cached versus cache-busted responses;
  • different regions or CDN points of presence;
  • cold versus warm replicas;
  • immediate behavior after rebuild or restart.

These probably do not need testing for the present report unless the affected setup uses one of them.

Lifecycle cases

The direct Space subdomain can change when a Space is moved or renamed, according to the direct URL documentation.

Not tested:

  • how quickly the canonical target updates after a rename or transfer;
  • whether cached responses retain an old Hub target;
  • behavior after duplication;
  • whether the behavior has changed over time.

Questions requiring HF confirmation

The external probe can characterize visible behavior, but it cannot establish intent, support guarantees, or internal ownership.

The remaining HF-side questions seem to be:

  1. Is this canonical Link intentionally added by the Spaces serving infrastructure?
  2. Is its purpose to canonicalize .hf.space URLs to the Hub Space page?
  3. Is this a supported public behavior or an internal implementation detail?
  4. Which SDKs and visibility modes receive it?
  5. Is it intentionally added to API responses, assets, redirects, empty responses, and errors?
  6. Is preserving application Link fields and appending without relation-level deduplication intentional?
  7. What is expected when the application already supplies a different rel="canonical"?
  8. What canonical target is intended on a custom domain?
  9. Is there a supported opt-out, removal, or override mechanism?
  10. Has the scope or merge behavior changed recently?
  11. Is browser preflight intentionally handled outside the ordinary application response path?
  12. If OPTIONS is handled separately, which application-owned CORS fields are preserved?

The current Spaces configuration reference says that custom_headers adds fields to all served responses, but currently permits only COEP, COOP, and CORP.

Therefore, the safe statement is:

I could not find a documented way to remove or override this canonical field.

rather than:

There is no way to remove it.

So my current best-supported interpretation is:

  • the canonical is platform-added rather than application-generated, at least in the controlled Docker Space;
  • the field itself predates the recent Spaces reports;
  • application-supplied Link fields are preserved and the Hub canonical is appended in this test;
  • two different canonical targets can consequently coexist;
  • whether that is the actual cause of the reported issue depends on the downstream consumer or contract;
  • intent, full scope, and any supported override still require clarification from Hugging Face.