feat(security): enforce 0600 config permissions and support DISCORD_TOKEN fallback - #38
Conversation
|
Thanks Emi, this is a good direction. Three things before I merge:
Also: a few tests only Rest of my notes are follow-up material, not blockers. |
- Tighten config file permissions with os.fchmod before write - Warn on stderr if chmod operations fail instead of silently passing - Rank DISCORD_TOKEN below ~/.discli/config.json as a generic fallback - Clear both DISCORD_BOT_TOKEN and DISCORD_TOKEN in conftest's no_network fixture - Update token resolution diagram, code snippets, and docs
|
Thanks for the thorough review, Rohit! All points make total sense and are addressed in the latest commit:
All 381 tests pass cleanly, including when run with external |
…en unprotected Two problems in the new save_config() write path. The recovery block called os.close(fd) after the `with` had already closed it. The swallowed EBADF is the harmless case; the harmful one is a concurrent thread claiming that number in between and losing its own file to the stray close. Narrow the guard to fdopen() itself, which is the only point where nothing else owns the descriptor. The os.open() failure path fell back to path.write_text(), writing the token with whatever the umask gave it and warning about none of it -- the exact exposure this function exists to prevent, and silent, unlike every other failure path here. os.open() and write_text() open with the same flags, so the fallback could not succeed where the primary had failed anyway. Let the error propagate. Regression tests cover both, plus descriptor release when fdopen() fails. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
|
Thanks for this — the security intent is right and I pushed one commit (e2057f8) to fix two things in the 1. Double close of the descriptor. The recovery block called 2. The Three regression tests cover both, plus descriptor release when Two smaller things I did not touch, since they're outside the fix and worth your call — I'll open a follow-up:
One note on the PR description: it says the option became |
Closes #37
Summary
Automatically secures
~/.discli/config.jsonby enforcing0600file permissions and0700directory permissions on POSIX systems (preventing world-readability on multi-user systems, VPS deployments, and AI agent sandboxes), and addsDISCORD_TOKENas an environment variable fallback alongsideDISCORD_BOT_TOKEN.Changes
src/discli/config.py: Enforce0700on~/.discliand0600onconfig.jsonduringsave_config(). Automatically tightens existing loose modes.src/discli/cli.py: Configure@click.option("--token", envvar=["DISCORD_BOT_TOKEN", "DISCORD_TOKEN"])for seamless token resolution.src/discli/client.py,src/discli/commands/doctor.py,src/discli/commands/setup.py: RecognizeDISCORD_TOKENin diagnostic checks, setup wizard, and error reporting.tests/test_config.py,tests/test_client.py,tests/test_setup.py: Comprehensive test coverage for permission enforcement and token resolution precedence.README.md,configuration.mdx,token-resolution.mdx, andcli-commands.mdx.Test plan
uv run pytest tests/(374 passed, 4 skipped)0o600and directory mode is0o700viastaton LinuxDISCORD_TOKENwhenDISCORD_BOT_TOKENis unsetDISCORD_BOT_TOKENoverDISCORD_TOKEN