Use promo code BETATEST1 for full access
seekrit
Docs/Hugging Face Spaces

Sync to Hugging Face Spaces

A Space runs your app on Hugging Face's infrastructure, so there is no process to inject into: the Space's secrets are its environment, and the Hub is the only way to set them. A binding owns one Space's secrets.

At a glance
What seekrit writesA Space's secrets, delivered to the container as environment variables
Addressed byThe Space ID, owner/name
Connection carriesNothing — a token plus the Space ID is the whole address
Token permissionA Hugging Face access token with write on the Space's repository
Takes effectOn the restart the write triggers
Value visibilityWrite-only — the Hub never shows a secret's value again, not even to you

New to sync? Read Third-party sync first — the decryption grant, name mapping, deletions, and failure handling are the same on every destination.

caution

A push restarts the Space. The Hub applies a configuration change by restarting your app, and it has no bulk endpoint for a Space that already exists — so a run that rotates five secrets makes five requests and the Space restarts as it applies them, settling on the final set. In-flight requests to the Space are interrupted.

Nothing periodic causes this. seekrit re-runs a binding when a secret it reads actually changed, so a Space restarts when you rotate something rather than on a timer — with one exception, in the troubleshooting table below: a binding holding a name that can never land keeps retrying, and on this destination that is visible.

1. Create an access token

On huggingface.co, Settings → Access Tokens → Create new token. The token needs write access to the Space's repository. A fine-grained token scoped to that one repository — or to the organization that owns it — is the least-privilege choice; a classic write token reaches everything you own.

printf '%s' "$HF_TOKEN" \
  | seekrit sync connect --name acme-hf --provider huggingface-spaces

That is the whole connection. There is nothing to scope: a Hugging Face token belongs to one user and carries their access everywhere, and a Space ID is globally unique — so the token plus the destination is the whole address.

note

There is no self-hosted option here, and that is deliberate. HF_ENDPOINT in the Python client points at a Hub mirror, which serves repository content — not the settings API this writes to, and not somewhere a secret should be sent. seekrit always talks to huggingface.co.

2. Bind an environment

A Space has one set of secrets, shared by every replica, so the Space is the whole destination. The Hub's convention is that staging and production are separate Spaces (acme/demo, acme/demo-staging), so pointing at an environment means naming that Space.

seekrit sync verify acme-hf --provider huggingface-spaces \
  --hf-space acme/support-demo

seekrit sync enable --connection acme-hf --provider huggingface-spaces \
  --hf-space acme/support-demo \
  --app support-demo --env production --acknowledge-decryption

--hf-space is the owner/name shown under the Space's title; a pasted huggingface.co/spaces/… URL is accepted and reduced to the ID. --app is the seekrit application the environment belongs to.

Verify reads the Space's variables rather than its secrets. It proves the same two things you can get wrong from the connection dialog — the token works, and the Space is reachable — and variables are by definition the Hub's public, non-sensitive lane, so the check pulls nothing sensitive into seekrit.

Secrets, never variables

A Space has two slots, and only one of them is a place for a credential:

SecretsVariables
Visible after savingNo — write-onlyYes, to anyone who can see the settings
Copied when the Space is duplicatedNoYes
What seekrit writesEverythingNothing

seekrit never writes a variable. There is no option to, because a seekrit secret pushed into that slot would be published.

How a push behaves

  • Each secret is its own request. The Hub's endpoint is an upsert keyed by name, so a push touches exactly the names seekrit manages and nothing else. A secret you added by hand survives untouched — the same property Heroku's merge semantics give, for the same reason.
  • A rejected name costs only itself. Unlike destinations that write the whole environment in one request, one bad name here fails one name and the rest land normally.
  • Removals are last. A name this binding wrote and no longer manages is deleted after the writes, so a Space that has been deleted is discovered by a failing write rather than mistaken for a key that was already gone.
  • seekrit cannot tell a redundant write from a real one, because the Hub never returns a value. It does not try to work around that by recording anything derived from your values in the Hub's readable description field — a hash or a fingerprint there would be an offline-guessable oracle for every low-entropy secret, sitting in someone else's UI. Every secret seekrit writes is described as Managed by seekrit, and nothing more.
  • A value the Hub echoes back in an error is scrubbed before the reason is stored on the run row.

Name rules

note

The Hub sets some environment variables in every Space itself — SPACE_ID, SPACE_HOST, SPACE_AUTHOR_NAME, SPACE_REPO_NAME, SPACE_TITLE, SPACE_CREATOR_USER_ID, ACCELERATOR, CPU_CORES, and MEMORY. seekrit refuses those names rather than sending them, and reports the refusal against that name alone. Watch for it if you use a --prefix.

The four OAuth variables the Hub injects when a Space enables Hugging Face OAuth — OAUTH_CLIENT_ID, OAUTH_CLIENT_SECRET, OAUTH_SCOPES, OPENID_PROVIDER_URL — are not refused, because a Space that talks to some other OAuth provider has a real reason to set them. If your Space uses Hugging Face OAuth, do not sync those four names to it.

Unlike some destinations, a secret with an empty value is fine here.

Troubleshooting

SymptomCauseFix
The connection fails to verify, "rejected the access token"The token was revoked, or expiredCreate a new one under Settings → Access Tokens and re-create the connection
403 on every nameA read token, or a fine-grained token that does not cover this repositoryGive the token write on the Space's repo (or its organization)
404 on the SpaceThe browser URL or a bare name in the ID slot, a model/dataset repo rather than a Space, or a private Space this token cannot seeUse owner/name, and check it is a Space
One name failed, rest landedThe mapped name is one the Hub sets itselfRename it, or fix the prefix
The Space restarts several times during one syncExpected — the Hub applies each secret on its own, and restarts each timeNothing to fix; it settles on the final set
The Space restarts on a scheduleA value really is changing every run — an interpolated reference, or a rotation scheduleCheck the run ledger for which name
The Space restarts every minute, foreverOne name in this environment can never land — a built-in above, a name the Hub rejects, or an oversized value. A run that pushes some names and fails others never counts as a clean success, so seekrit keeps retrying itexclude that name from the binding, or rename it. The run ledger names it

Alternatives worth knowing

A Space is a runtime you do not control, which is why sync is the answer here. Two neighbouring Hugging Face surfaces are not:

  • Jobs take their secrets at submit time (hf jobs run), so there is nothing persistent to sync into — run the submitting command under seekrit run and no copy exists at the platform at all.
  • Notebooks on the Hub, Colab, or anywhere else should resolve at the top of the notebook instead. See Jupyter notebooks.

See also