From 8140fbad22814b99ad56e1161a43ec203da669d8 Mon Sep 17 00:00:00 2001 From: Kenneth Lien Date: Fri, 20 Mar 2026 10:37:13 -0700 Subject: [PATCH] Lock telegram/discord .env files to owner (chmod 600) The bot token is a credential. Tighten perms on load so hand-written or pre-existing .env files get locked down, and update the configure skill to chmod after writing. No-op on Windows. --- external_plugins/discord/server.ts | 4 +++- external_plugins/discord/skills/configure/SKILL.md | 3 ++- external_plugins/telegram/server.ts | 4 +++- external_plugins/telegram/skills/configure/SKILL.md | 3 ++- 4 files changed, 10 insertions(+), 4 deletions(-) diff --git a/external_plugins/discord/server.ts b/external_plugins/discord/server.ts index f34006a..078c29a 100644 --- a/external_plugins/discord/server.ts +++ b/external_plugins/discord/server.ts @@ -25,7 +25,7 @@ import { type Attachment, } from 'discord.js' import { randomBytes } from 'crypto' -import { readFileSync, writeFileSync, mkdirSync, readdirSync, rmSync, statSync, renameSync, realpathSync } from 'fs' +import { readFileSync, writeFileSync, mkdirSync, readdirSync, rmSync, statSync, renameSync, realpathSync, chmodSync } from 'fs' import { homedir } from 'os' import { join, sep } from 'path' @@ -37,6 +37,8 @@ const ENV_FILE = join(STATE_DIR, '.env') // Load ~/.claude/channels/discord/.env into process.env. Real env wins. // Plugin-spawned servers don't get an env block — this is where the token lives. try { + // Token is a credential — lock to owner. No-op on Windows (would need ACLs). + chmodSync(ENV_FILE, 0o600) for (const line of readFileSync(ENV_FILE, 'utf8').split('\n')) { const m = line.match(/^(\w+)=(.*)$/) if (m && process.env[m[1]] === undefined) process.env[m[1]] = m[2] diff --git a/external_plugins/discord/skills/configure/SKILL.md b/external_plugins/discord/skills/configure/SKILL.md index b6d8df0..a1e15f8 100644 --- a/external_plugins/discord/skills/configure/SKILL.md +++ b/external_plugins/discord/skills/configure/SKILL.md @@ -80,7 +80,8 @@ as the correct long-term choice. Don't skip the lockdown offer. 2. `mkdir -p ~/.claude/channels/discord` 3. Read existing `.env` if present; update/add the `DISCORD_BOT_TOKEN=` line, preserve other keys. Write back, no quotes around the value. -4. Confirm, then show the no-args status so the user sees where they stand. +4. `chmod 600 ~/.claude/channels/discord/.env` — the token is a credential. +5. Confirm, then show the no-args status so the user sees where they stand. ### `clear` — remove the token diff --git a/external_plugins/telegram/server.ts b/external_plugins/telegram/server.ts index e2265b8..8acd52a 100644 --- a/external_plugins/telegram/server.ts +++ b/external_plugins/telegram/server.ts @@ -18,7 +18,7 @@ import { import { Bot, InputFile, type Context } from 'grammy' import type { ReactionTypeEmoji } from 'grammy/types' import { randomBytes } from 'crypto' -import { readFileSync, writeFileSync, mkdirSync, readdirSync, rmSync, statSync, renameSync, realpathSync } from 'fs' +import { readFileSync, writeFileSync, mkdirSync, readdirSync, rmSync, statSync, renameSync, realpathSync, chmodSync } from 'fs' import { homedir } from 'os' import { join, extname, sep } from 'path' @@ -30,6 +30,8 @@ const ENV_FILE = join(STATE_DIR, '.env') // Load ~/.claude/channels/telegram/.env into process.env. Real env wins. // Plugin-spawned servers don't get an env block — this is where the token lives. try { + // Token is a credential — lock to owner. No-op on Windows (would need ACLs). + chmodSync(ENV_FILE, 0o600) for (const line of readFileSync(ENV_FILE, 'utf8').split('\n')) { const m = line.match(/^(\w+)=(.*)$/) if (m && process.env[m[1]] === undefined) process.env[m[1]] = m[2] diff --git a/external_plugins/telegram/skills/configure/SKILL.md b/external_plugins/telegram/skills/configure/SKILL.md index 3d846cf..31ad2f3 100644 --- a/external_plugins/telegram/skills/configure/SKILL.md +++ b/external_plugins/telegram/skills/configure/SKILL.md @@ -77,7 +77,8 @@ offer. 2. `mkdir -p ~/.claude/channels/telegram` 3. Read existing `.env` if present; update/add the `TELEGRAM_BOT_TOKEN=` line, preserve other keys. Write back, no quotes around the value. -4. Confirm, then show the no-args status so the user sees where they stand. +4. `chmod 600 ~/.claude/channels/telegram/.env` — the token is a credential. +5. Confirm, then show the no-args status so the user sees where they stand. ### `clear` — remove the token