[PR #281] [MERGED] fal: um fal key stores the API key in the OS keyring (Linux) or an owner-only file #280

Closed
opened 2026-10-15 20:55:07 +00:00 by bsakaguchi · 0 comments
Owner

📋 Pull Request Information

Original PR: https://github.com/rehan-remade/universal-modder/pull/281
Author: @Selene0623
Created: 10/13/2026
Status: ✅ Merged
Merged: 10/14/2026
Merged by: @rehan-remade

Base: main ← Head: fal-keyring


📝 Commits (8)

  • 1f62a8b docs(agents): note render3d needs Blender on PATH
  • 15e9f78 skills: add game-research-websearch
  • 645fdf7 fal: keep the API key in the OS keyring
  • 8d41344 fal: review fixes for the keyring key store
  • b6850c8 Merge remote-tracking branch 'origin/main' into merge/pr-281
  • 37c235c fal: read secret-tool output as UTF-8 (text_utf8 from #276)
  • ca542ff SECURITY.md: um fal also reads the key um fal key set stored
  • 37f9e96 Merge remote-tracking branch 'origin/main' into land/pr-281

📊 Changes

14 files changed (+441 additions, -52 deletions)

View changed files

📝 .agents/skills/fal-assets/SKILL.md (+19 -8)
📝 .claude/skills/fal-assets/SKILL.md (+19 -8)
📝 .codex/config.toml (+2 -0)
📝 AGENTS.md (+4 -1)
📝 CLAUDE.md (+4 -0)
📝 README.md (+9 -2)
📝 SECURITY.md (+2 -1)
📝 examples/aoe2-de-civ/README.md (+4 -4)
📝 examples/aoe2-de-civ/assets/gen.sh (+2 -2)
📝 examples/terraria-tmodloader/README.md (+1 -1)
📝 examples/terraria-tmodloader/assets/make_art.sh (+1 -1)
📝 skills/fal-assets/SKILL.md (+19 -8)
➕ tests/test_fal_key.py (+146 -0)
📝 um/fal.py (+209 -16)

📄 Description

fal: keep the API key in the OS keyring, authenticate MCP with OAuth

um fal looked for FAL_KEY in the environment, then a plaintext
~/.secrets/fal-key, then a .env — and the fal MCP server that ships with
the repo was configured the same way, with Authorization: Bearer $FAL_KEY in every agent config. That pushes a long-lived secret into a
shell profile or a dotfile that agents read, which is the leak this
change removes.

Now:

  • um fal key set|status|migrate|clear stores the key with libsecret
    (KWallet / gnome-keyring) and only writes a plaintext file when asked
    (--file-only, mode 600). Resolution order is FAL_KEY (CI) → OS
    keyring → plaintext fallback → .env, and a fallback prints a warning.
  • The MCP configs drop the Authorization header and use the server's
    OAuth flow instead ("oauth": true), so no key is stored anywhere.
    Each client authenticates once: opencode mcp auth fal, /mcp →
    Authenticate (Claude Code), codex mcp login fal.
  • Docs, skills and examples no longer tell users to export FAL_KEY=....

Why this shape, and not the environment variable the docs suggest:

  • fal's hosted MCP server already advertises OAuth — an anonymous
    initialize returns www-authenticate: Bearer realm="OAuth", resource_metadata="https://mcp.fal.ai/.well-known/ oauth-protected-resource/mcp", with authorization_servers: ["https://auth.fal.ai"]. Sentry, Linear and Notion answer the same
    way, so OAuth is what remote MCP servers use, and the client ends up
    holding a rotating refresh token instead of a pasted bearer.
  • OpenCode's V2 MCP docs take the same position for remote servers —
    OAuth by default, credentials kept outside the project config
    (opencode mcp auth <name>), with {env:NAME} as the substitution
    mechanism rather than a shell $NAME
    (https://opencode.ai/v2/docs/mcp-servers).
  • For the CLI there is no OAuth client, so the key has to be stored
    somewhere. The desktop keyring is the freedesktop way to do it:
    secret-tool is the standard libsecret front end, the same store
    git-credential-libsecret and other CLI tools use. It keeps the
    secret out of the repo, out of .env, out of shell history and out
    of any file an agent can read, and it survives reboots without a
    daemon of our own.

Nothing here changes the fal MCP server or the models; um fal behaves
exactly as before once a key is present.

Tested: uv run --with pytest pytest -q tests (63 passed, 1 skipped),
./bin/um publish check . (0 failures), and a keyring store/read-back
round-trip plus --file-only permission check on Linux/KWallet.


🔄 This issue represents a GitHub Pull Request. It cannot be merged through Gitea due to API limitations.

## 📋 Pull Request Information **Original PR:** https://github.com/rehan-remade/universal-modder/pull/281 **Author:** [@Selene0623](https://github.com/Selene0623) **Created:** 10/13/2026 **Status:** ✅ Merged **Merged:** 10/14/2026 **Merged by:** [@rehan-remade](https://github.com/rehan-remade) **Base:** `main` ← **Head:** `fal-keyring` --- ### 📝 Commits (8) - [`1f62a8b`](https://github.com/rehan-remade/universal-modder/commit/1f62a8bc4ad16f9c57bbbafd65f8259548f0c02c) docs(agents): note render3d needs Blender on PATH - [`15e9f78`](https://github.com/rehan-remade/universal-modder/commit/15e9f78cbb098242d0e4d6f24390eabae351687b) skills: add game-research-websearch - [`645fdf7`](https://github.com/rehan-remade/universal-modder/commit/645fdf714099eca71e1818b48761029897d976d7) fal: keep the API key in the OS keyring - [`8d41344`](https://github.com/rehan-remade/universal-modder/commit/8d413447dcfe0814813d8021b8e272b367cf52e3) fal: review fixes for the keyring key store - [`b6850c8`](https://github.com/rehan-remade/universal-modder/commit/b6850c8174d3d6adffe1f087e75790fc11245181) Merge remote-tracking branch 'origin/main' into merge/pr-281 - [`37c235c`](https://github.com/rehan-remade/universal-modder/commit/37c235cec81586499b2918902364fee96a9c2918) fal: read secret-tool output as UTF-8 (text_utf8 from #276) - [`ca542ff`](https://github.com/rehan-remade/universal-modder/commit/ca542ff2ae40e46c6bae4045e6703a831216bf39) SECURITY.md: um fal also reads the key `um fal key set` stored - [`37f9e96`](https://github.com/rehan-remade/universal-modder/commit/37f9e96c503f1c70366072729f61d30be5bd8a58) Merge remote-tracking branch 'origin/main' into land/pr-281 ### 📊 Changes **14 files changed** (+441 additions, -52 deletions) <details> <summary>View changed files</summary> 📝 `.agents/skills/fal-assets/SKILL.md` (+19 -8) 📝 `.claude/skills/fal-assets/SKILL.md` (+19 -8) 📝 `.codex/config.toml` (+2 -0) 📝 `AGENTS.md` (+4 -1) 📝 `CLAUDE.md` (+4 -0) 📝 `README.md` (+9 -2) 📝 `SECURITY.md` (+2 -1) 📝 `examples/aoe2-de-civ/README.md` (+4 -4) 📝 `examples/aoe2-de-civ/assets/gen.sh` (+2 -2) 📝 `examples/terraria-tmodloader/README.md` (+1 -1) 📝 `examples/terraria-tmodloader/assets/make_art.sh` (+1 -1) 📝 `skills/fal-assets/SKILL.md` (+19 -8) ➕ `tests/test_fal_key.py` (+146 -0) 📝 `um/fal.py` (+209 -16) </details> ### 📄 Description fal: keep the API key in the OS keyring, authenticate MCP with OAuth `um fal` looked for FAL_KEY in the environment, then a plaintext ~/.secrets/fal-key, then a .env — and the fal MCP server that ships with the repo was configured the same way, with `Authorization: Bearer $FAL_KEY` in every agent config. That pushes a long-lived secret into a shell profile or a dotfile that agents read, which is the leak this change removes. Now: - `um fal key set|status|migrate|clear` stores the key with libsecret (KWallet / gnome-keyring) and only writes a plaintext file when asked (`--file-only`, mode 600). Resolution order is FAL_KEY (CI) → OS keyring → plaintext fallback → .env, and a fallback prints a warning. - The MCP configs drop the Authorization header and use the server's OAuth flow instead (`"oauth": true`), so no key is stored anywhere. Each client authenticates once: `opencode mcp auth fal`, `/mcp` → Authenticate (Claude Code), `codex mcp login fal`. - Docs, skills and examples no longer tell users to `export FAL_KEY=...`. Why this shape, and not the environment variable the docs suggest: - fal's hosted MCP server already advertises OAuth — an anonymous `initialize` returns `www-authenticate: Bearer realm="OAuth", resource_metadata="https://mcp.fal.ai/.well-known/ oauth-protected-resource/mcp"`, with `authorization_servers: ["https://auth.fal.ai"]`. Sentry, Linear and Notion answer the same way, so OAuth is what remote MCP servers use, and the client ends up holding a rotating refresh token instead of a pasted bearer. - OpenCode's V2 MCP docs take the same position for remote servers — OAuth by default, credentials kept outside the project config (`opencode mcp auth <name>`), with `{env:NAME}` as the substitution mechanism rather than a shell `$NAME` (https://opencode.ai/v2/docs/mcp-servers). - For the CLI there is no OAuth client, so the key has to be stored somewhere. The desktop keyring is the freedesktop way to do it: `secret-tool` is the standard libsecret front end, the same store `git-credential-libsecret` and other CLI tools use. It keeps the secret out of the repo, out of `.env`, out of shell history and out of any file an agent can read, and it survives reboots without a daemon of our own. Nothing here changes the fal MCP server or the models; `um fal` behaves exactly as before once a key is present. Tested: `uv run --with pytest pytest -q tests` (63 passed, 1 skipped), `./bin/um publish check .` (0 failures), and a keyring store/read-back round-trip plus `--file-only` permission check on Linux/KWallet. --- <sub>🔄 This issue represents a GitHub Pull Request. It cannot be merged through Gitea due to API limitations.</sub>
bsakaguchi 2026-10-15 20:55:07 +00:00
Sign in to join this conversation.
No labels
pull-request
No milestone
No project
No assignees
1 participant
Notifications
Due date
The due date is invalid or out of range. Please use the format "yyyy-mm-dd".

No due date set.

Dependencies

No dependencies set.

Reference
rehan-remade/universal-modder#280
No description provided.