Spaces:
Running
Setting up GitHub Action secrets — least-privilege edition
The CHAINSTATE deploy workflow needs three secrets in your GitHub repository. This document gives you the minimum-scope version of each — a leaked secret here cannot touch anything outside this one project.
All three are added the same way:
GitHub repo → Settings → Secrets and variables → Actions → New repository secret
Use the exact secret name from the table; the workflow looks them up by these names verbatim.
Before you create anything · two one-time dashboard steps
These are not secrets — they're account-level setup that has to exist once, ever.
A · Pick your workers.dev subdomain (one time, ever)
- Go to https://dash.cloudflare.com → Workers & Pages
- If this is your first Worker, Cloudflare prompts you to pick a subdomain (e.g.
redciprianpater). - After this, your CHAINSTATE worker URL will be:
https://chainstate-worker.<your-subdomain>.workers.dev
You will never repeat this step. It's a one-time account choice.
B · Create the three KV namespaces locally (one time, ever)
The CF API token below cannot create namespaces for you — you must create them locally first, then commit their IDs into wrangler.toml. On your laptop:
# install wrangler if you haven't:
npm install --global wrangler
# log in (opens a browser, one-time):
wrangler login
# create the three namespaces:
wrangler kv:namespace create CHAINSTATE_NODES
wrangler kv:namespace create CHAINSTATE_CACHE
wrangler kv:namespace create CHAINSTATE_CONSENSUS
Each command prints something like:
🌀 Creating namespace with title "chainstate-worker-CHAINSTATE_NODES"
✨ Success!
Add the following to your configuration file in your kv_namespaces array:
{ binding = "CHAINSTATE_NODES", id = "a1b2c3d4e5f6789012345678901234ab" }
Take the id from each and paste into the matching block in wrangler.toml, replacing the REPLACE_ME_AFTER_kv_namespace_create_* placeholders. Commit and push.
Summary table
| Secret name | Source | Scope |
|---|---|---|
CF_API_TOKEN |
https://dash.cloudflare.com/profile/api-tokens · Custom Token, not the template | Only Workers Scripts + KV (no zones, no D1, no R2) |
CF_ACCOUNT_ID |
https://dash.cloudflare.com — right sidebar on any zone overview | (Not secret in the cryptographic sense; just an identifier) |
HF_TOKEN |
https://huggingface.co/settings/tokens · Fine-grained, scoped to one Space | Write to CPater/chainstate only |
1 · CF_API_TOKEN — least-privilege Custom Token
The "Edit Cloudflare Workers" template grants more than you need (it includes zone routes, tail logs, R2 read, etc). For CHAINSTATE you only need two account-level permissions. Build a Custom Token instead.
Sign in at https://dash.cloudflare.com → My Profile → API Tokens
Click Create Token, then scroll to the bottom and click "Get started" under "Create Custom Token" (do NOT use the template).
Token name:
chainstate-deployPermissions section — add exactly these two rows and nothing else:
Scope Resource Action Account Workers Scripts Edit Account Workers KV Storage Edit Account Resources:
- Choose "Include" → "Specific account" → select the single account that owns your
workers.devsubdomain. Do not select "All accounts."
- Choose "Include" → "Specific account" → select the single account that owns your
Zone Resources:
- Leave empty (or set to "All zones from an account" if Cloudflare insists). You do not bind a custom domain in
wrangler.toml, so zone permissions are unnecessary.
- Leave empty (or set to "All zones from an account" if Cloudflare insists). You do not bind a custom domain in
Client IP Address Filtering (optional but recommended):
- Restrict to GitHub Actions IP ranges if you want belt-and-braces. The list is published at https://api.github.com/meta (the
actionsarray). Many people skip this because the list changes; a 1-year TTL plus quick revocation is usually enough.
- Restrict to GitHub Actions IP ranges if you want belt-and-braces. The list is published at https://api.github.com/meta (the
TTL: set to 1 year so the token is force-rotated annually.
Click Continue to summary → Create Token → copy immediately (CF only shows it once).
Add to GitHub: name =
CF_API_TOKEN, value = the token.
What this token can do:
- Upload, modify, or delete Worker scripts in your account
- Read, write, or delete KV namespace entries
What this token cannot do:
- Touch DNS, Pages, R2, D1, queues, durable objects, zones, certificates, accounts, billing, or any other Cloudflare product
- Affect any of your other (non-Workers) services
Even-tighter variant: dedicated CF account
If you want zero blast radius, create a separate Cloudflare account just for CHAINSTATE. Cloudflare lets you create multiple accounts on the same email (use sub-addressing like you+chainstate@example.com). Add the Custom Token within that account, and the token cannot reach any of your other Cloudflare work even by account compromise.
2 · CF_ACCOUNT_ID — just an identifier
This is the 32-hex Cloudflare account ID. It is not a secret in the cryptographic sense — it doesn't grant any access on its own. But it has to match the account the CF_API_TOKEN was created under, so it goes alongside it.
- https://dash.cloudflare.com → click into any zone (any domain you own; or Workers & Pages if you have no domains)
- Right sidebar: Account ID — 32 lowercase hex chars
- Copy and add to GitHub: name =
CF_ACCOUNT_ID, value = the hex string
3 · HF_TOKEN — fine-grained, scoped to one Space
A "Write" role token grants write access to every repo and Space on your HuggingFace account. Use a fine-grained token scoped to just CPater/chainstate:
- Sign in at https://huggingface.co
- Settings → Access Tokens (or https://huggingface.co/settings/tokens)
- Click + Create new token
- Token type: select "Fine-grained" (do NOT select Read or Write).
- Token name:
chainstate-deploy - Token expiration: set to 1 year to force rotation.
- Scroll to "Repositories permissions":
- Click "Add a repository"
- Repository:
CPater/chainstate - Type: Space (not Model, not Dataset)
- Permission: Write contents (this is the only one you need; do not check "Manage repository settings")
- Leave every other section unchecked:
- User permissions: none
- Organization permissions: none
- Inference endpoints: none
- Billing: none
- Click Create token → copy the
hf_...value immediately. - Add to GitHub: name =
HF_TOKEN, value = thehf_...token.
What this token can do:
- Write files into
CPater/chainstateSpace repo
What this token cannot do:
- Touch any of your other Spaces, models, or datasets
- Read or write your account profile
- Bill anything on your account
- Read private repos you own
- Use inference endpoints
Verifying the secrets are wired correctly
After all three are added, push a no-op commit to main:
git commit --allow-empty -m "trigger first deploy"
git push origin main
In the GitHub Actions tab you should see two jobs:
cf-worker— succeeds when wrangler deploys the Worker. The job log prints the Worker URL near the end (Worker is uploaded… https://chainstate-worker.<your-subdomain>.workers.dev). If you see "permission denied on KV namespace", the IDs inwrangler.tomlare wrong — re-create withwrangler kv:namespace listand update.hf-space— succeeds whenhuggingface_hub.upload_folderfinishes. Last line isHF Space updated ✓. If you see "Token is unauthorized", the token is missing the repo binding — fix in step 3.7 above.
Then test the Worker directly:
curl https://chainstate-worker.<your-subdomain>.workers.dev/status
curl -X POST https://chainstate-worker.<your-subdomain>.workers.dev/query \
-H 'Content-Type: application/json' \
-d '{"query": "∫∂x → ?", "swarmSize": 20, "consensusDepth": 3}'
Verify the HF Space at https://cpater-chainstate.static.hf.space/. The Query/Terminal/SCAN pages will use the Worker once you wire its URL into the Space — easiest way is to add this script tag to index.html (somewhere before the closing </body>):
<script>window.__CHAINSTATE_WORKER = "https://chainstate-worker.<your-subdomain>.workers.dev";</script>
Or set it temporarily via the browser console:
window.__CHAINSTATE_WORKER = "https://chainstate-worker.<your-subdomain>.workers.dev"
Triggering deploys
The workflow ships on three triggers:
- Push to
main— every commit on the main branch deploys. Useful for fast iteration. - GitHub Release — publish a release (e.g.
v0.1.0) and the workflow runs. Useful for tagging stable deploys. - Manual dispatch — go to GitHub repo → Actions → deploy → Run workflow. Useful when you want to retry without a commit.
To publish a release:
git tag v0.1.0
git push origin v0.1.0
Then in GitHub: Releases → Draft new release → choose v0.1.0 → Publish release. The workflow fires automatically.
Rotating secrets
CF tokens have no built-in expiry by default but we set a 1-year TTL above. HF fine-grained tokens also have an explicit expiry. To rotate:
- Create a new token following the steps above
- GitHub repo → Settings → Secrets → Actions → click the existing secret → Update
- Paste the new value, save
- Delete the old token at the source (CF dashboard / HF tokens page)
There is no need to re-run any workflow; the next push or release will use the new value.
Security notes
- These secrets are repository-scoped in GitHub — they're available to workflow jobs in this one repo only. They are not exposed to forks or PRs from forks.
- The values are encrypted at rest in GitHub and decrypted only inside the runner VM. Any line containing the literal value is masked as
***in logs. - Anyone with push access to
maincan effectively use these secrets via a malicious workflow change. Protectmainwith required reviews / branch protection if you have collaborators. - Both Cloudflare and HuggingFace let you revoke a token immediately if it leaks; you should not rely on git history scrubbing if a token was committed accidentally — revoke first, then clean.
- If you ever suspect a token is compromised: revoke at source (CF or HF dashboard), then generate a new one and update the GitHub secret. The workflow uses the new value on the very next run.