Repository navigation
feat(server): add --api-key authentication - #639
Merged
Merged
Conversation
The daemon reaches the serve's /v1/stats and prepare-stop itself, so the key travels in the start request, reaches the serve through its environment and is kept in the state file for re-adoption. The daemon's own FREETOKEN_API_KEY is no longer passed down to the serve.
dsh's web search reads DEEPSEEK_API_KEY and sends it to api.deepseek.com, so dsh now reads the key from FREETOKEN_API_KEY through the apiKeyEnv of its llm-deepseek section, and DEEPSEEK_API_KEY stays the dummy.
KarrAcaRn
pushed a commit
to KarrAcaRn/FreeToken-ByAI
that referenced
this pull request
Oct 10, 2026
Upstream merged its own --api-key (FlashML-org#639), which supersedes the FlashML-org#305 adoption on next. Resolution: upstream's implementation wins everywhere (app-scoped _ApiKeyMiddleware via install_api_key(app, key), Bearer or x-api-key, resolve_api_key from freetoken.launch, daemon apiKey, ctl, launch), and next's module-level _API_KEY gate is dropped. next's other server work stays: TLS (--ssl-certfile/--ssl-keyfile), --dist-port, --launch-nonce, loopback-pinned proxy trust (_uvicorn_config/_run_uvicorn) and the Field-validated daemon StartBody. tests/server/test_api_key.py is upstream's; the runtime-identity key test now builds its own app with install_api_key instead of flipping _API_KEY. Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
KarrAcaRn
pushed a commit
to KarrAcaRn/FreeToken-ByAI
that referenced
this pull request
Oct 10, 2026
…L-org#639 Assisted-by: Claude Opus 5.5
KarrAcaRn
pushed a commit
to KarrAcaRn/FreeToken-ByAI
that referenced
this pull request
Oct 10, 2026
…L-org#639 871c600 carried only the renderer's upstream-state refresh (FlashML-org#502 and FlashML-org#134 merged, FlashML-org#133 closed); this adds the FlashML-org#305 note it was meant to. Assisted-by: Claude Opus 5.5
trcwebdesign
pushed a commit
to trcwebdesign/FreeToken
that referenced
this pull request
Oct 14, 2026
* feat(server): require an API key on every route but /health * feat(shell): send the server's API key * feat(ctl): send the server's API key * feat(launch): hand the server's API key to the agent * feat(daemon): pass the serve's API key as apiKey The daemon reaches the serve's /v1/stats and prepare-stop itself, so the key travels in the start request, reaches the serve through its environment and is kept in the state file for re-adoption. The daemon's own FREETOKEN_API_KEY is no longer passed down to the serve. * fix(launch): keep the API key out of the variable dsh's web search reads dsh's web search reads DEEPSEEK_API_KEY and sends it to api.deepseek.com, so dsh now reads the key from FREETOKEN_API_KEY through the apiKeyEnv of its llm-deepseek section, and DEEPSEEK_API_KEY stays the dummy. * fix(server): check the API key on OPTIONS requests * fix(launch): write agent configs that hold the API key owner-only * chore: drop comments that restate the code
pretor
pushed a commit
to pretor/FreeToken
that referenced
this pull request
Oct 14, 2026
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
feat(server): add --api-key authentication
Closes #152, closes #557. #305 and #558 add the server side (and, in #305, the shell); this also covers
ft ctl,ft launchand the daemon.ft servehas no way to require credentials today, so anything that can reach its port can drive the whole API. This adds an opt-in API key and teaches FreeToken's own clients to send it.Server
--api-key KEY, orFREETOKEN_API_KEYwhen the flag is absent; the flag wins. Unset means no authentication, as today. An explicit--api-key ""is rejected at startup./healthneedsAuthorization: Bearer <key>orx-api-key: <key>, including/generateand the control routes; anything else gets401 {"error": "Unauthorized"}.ServerArgs.api_keyis excluded from theParsed argumentsstartup log line.Clients
Each takes
--api-key, elseFREETOKEN_API_KEY.ft shellsends the key on chat and control requests;ft shell --model ... --api-key KEYhands the server's own key to the attached shell.ft ctlsends it on every request.ft launchsends it on model discovery and hands it to the agent: through the environment for Claude Code, Codex, OpenCode and dsh, through the config file for Hermes and OpenClaw.--dry-runnever prints the key; where the plan shows it, it shows<api-key>.0600) before the write, and backups are created with the mode of the file they copy.FREETOKEN_API_KEY(set asapiKeyEnvof itsllm-deepseeksection) andDEEPSEEK_API_KEYstays the dummy, because dsh's web search also readsDEEPSEEK_API_KEYand sends it toapi.deepseek.com.Daemon
POST /engine/startand/engine/switchtake an optionalapiKey. The daemon gives it to the serve asFREETOKEN_API_KEYin its environment (never on its command line), sends it on its own calls to the serve, and keeps it inserve.json, now written0600, so a re-adopted serve stays reachable. The key is part of the serve config: a start with a different key is a 409, like a different model.FREETOKEN_API_KEYis no longer passed down: a serve requires a key only when its start request carries one.--api-key(or an argparse prefix of it) insideargsis rejected with a 400, because the daemon would not know that key.ft daemon start|switch --api-key KEY(default$FREETOKEN_API_KEY).Compatibility
With no key configured no middleware is installed, and
ft ctlandft launchsend the same requests as before; the shell's control requests now carry its placeholder bearer, which a server without a key ignores.FREETOKEN_API_KEYin the environment offt servenow turns authentication on.Testing
NVIDIA RTX PRO 6000 Blackwell Server Edition (sm_120, 96 GB), driver 595.91.07; Intel Xeon Platinum 8559C (16 cores), 249 GiB RAM;
Qwen/Qwen3-4B(bf16, local copy at/mnt/nvme/models/Qwen3-4B).pytest tests/server tests/daemon -m "not slow": 645 passed, including 23 new tests intests/server/test_api_key.py(the--dry-runtest covers all six agents) and 6 intests/daemon/test_daemon_serve_manager.py. Each commit passes on its own.FREETOKEN_API_KEY=$K ft serve --model /mnt/nvme/models/Qwen3-4B --port 19190:/v1/models,/v1/chat/completions,/generate,/v1/stats,/v1/requests,/v1/cache/status,/v1/cache/rebuildand/v1/admin/prepare-stop;Authorization: Bearergets 200 on/v1/models,/v1/chat/completionsand/v1/responses;x-api-keygets 200 on/v1/modelsand/v1/messages;/healthand a CORS preflight from an allowed origin stay open, a plainOPTIONSwithout a key gets 401, and a 401 carries the CORS headers;OPTIONSincluded, do not enter/v1/requests, and the key does not appear in the server log.ft shell --server ... --api-key $K(chat and/cache); without a key it exits withHTTP 401.ft shell --model /mnt/nvme/models/Qwen3-4B --api-key $K: the server answers 401 without the key while the attached shell chats.ft daemon start /mnt/nvme/models/Qwen3-4B --api-key $Kunder a daemon whose own environment held a differentFREETOKEN_API_KEY:/engine/statsis proxied;/engine/statusand the logs, andserve.jsonis mode 600;-- --api-key xin the serve args is answered with a 400;ft daemon stopseals its accounting.Started without
apiKey, the serve stays open.FREETOKEN_API_KEY=$K ft serve --model /mnt/nvme/models/Qwen3-4B --port 19190 --memory-ratio 0.3:ft ctlwith the key (--api-keyorFREETOKEN_API_KEY):stats,generate,requests,cacheandcache --kv 32k; without a key,statsreportsHTTP 401andhealthanswers.ft launch claude|codex|opencode, Claude Code and OpenCode with the key inFREETOKEN_API_KEY, Codex with--api-key: each agent answered.ft launch dsh --api-key $K -- --profile headless(dsh 0.1.5-rc.1) answered; withDEEPSEEK_SEARCH_BASE_URLpointed at a local capture server, its web search sentfreetokenand not the key. Without a key, discovery stops with a message naming--api-key.--dry-runfor all six agents never prints the key; Claude Code, Codex, OpenCode and dsh show<api-key>.ft serve --model /mnt/nvme/models/Qwen3-4B --port 19190 --memory-ratio 0.3with no key:/v1/modelsanswers without a header, the log has no API key line,ft ctl statsandcache --kv 32kandft shell(chat and/cache) work,ft launch --dry-runprints the same output asmain(377a3bc) for all six agents, andft launch claudewith an emptyFREETOKEN_API_KEYanswers.