mirror of
https://github.com/garrytan/gstack.git
synced 2026-05-02 11:45:20 +02:00
fix: harden file/directory permissions to owner-only (C5+H9+M9+M10)
Add mode 0o700 to all mkdirSync calls for state/session directories. Add mode 0o600 to all writeFileSync calls for session.json, chat.jsonl, and log files. Add umask 077 to setup script. Prevents auth tokens, chat history, and browser logs from being world-readable on multi-user systems. Closes C5, H9, M9, M10 from security audit #783. Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
This commit is contained in:
@@ -79,7 +79,7 @@ export function resolveConfig(
|
||||
*/
|
||||
export function ensureStateDir(config: BrowseConfig): void {
|
||||
try {
|
||||
fs.mkdirSync(config.stateDir, { recursive: true });
|
||||
fs.mkdirSync(config.stateDir, { recursive: true, mode: 0o700 });
|
||||
} catch (err: any) {
|
||||
if (err.code === 'EACCES') {
|
||||
throw new Error(`Cannot create state directory ${config.stateDir}: permission denied`);
|
||||
|
||||
+10
-10
@@ -398,10 +398,10 @@ function createSession(): SidebarSession {
|
||||
lastActiveAt: new Date().toISOString(),
|
||||
};
|
||||
const sessionDir = path.join(SESSIONS_DIR, id);
|
||||
fs.mkdirSync(sessionDir, { recursive: true });
|
||||
fs.writeFileSync(path.join(sessionDir, 'session.json'), JSON.stringify(session, null, 2));
|
||||
fs.writeFileSync(path.join(sessionDir, 'chat.jsonl'), '');
|
||||
fs.writeFileSync(path.join(SESSIONS_DIR, 'active.json'), JSON.stringify({ id }));
|
||||
fs.mkdirSync(sessionDir, { recursive: true, mode: 0o700 });
|
||||
fs.writeFileSync(path.join(sessionDir, 'session.json'), JSON.stringify(session, null, 2), { mode: 0o600 });
|
||||
fs.writeFileSync(path.join(sessionDir, 'chat.jsonl'), '', { mode: 0o600 });
|
||||
fs.writeFileSync(path.join(SESSIONS_DIR, 'active.json'), JSON.stringify({ id }), { mode: 0o600 });
|
||||
chatBuffer = [];
|
||||
chatNextId = 0;
|
||||
return session;
|
||||
@@ -411,7 +411,7 @@ function saveSession(): void {
|
||||
if (!sidebarSession) return;
|
||||
sidebarSession.lastActiveAt = new Date().toISOString();
|
||||
const sessionFile = path.join(SESSIONS_DIR, sidebarSession.id, 'session.json');
|
||||
try { fs.writeFileSync(sessionFile, JSON.stringify(sidebarSession, null, 2)); } catch (err: any) {
|
||||
try { fs.writeFileSync(sessionFile, JSON.stringify(sidebarSession, null, 2), { mode: 0o600 }); } catch (err: any) {
|
||||
console.error('[browse] Failed to save session:', err.message);
|
||||
}
|
||||
}
|
||||
@@ -558,7 +558,7 @@ function spawnClaude(userMessage: string, extensionUrl?: string | null, forTabId
|
||||
tabId: agentTabId,
|
||||
});
|
||||
try {
|
||||
fs.mkdirSync(gstackDir, { recursive: true });
|
||||
fs.mkdirSync(gstackDir, { recursive: true, mode: 0o700 });
|
||||
fs.appendFileSync(agentQueue, entry + '\n');
|
||||
} catch (err: any) {
|
||||
addChatEntry({ ts: new Date().toISOString(), role: 'agent', type: 'agent_error', error: `Failed to queue: ${err.message}` });
|
||||
@@ -614,7 +614,7 @@ function startAgentHealthCheck(): void {
|
||||
|
||||
// Initialize session on startup
|
||||
function initSidebarSession(): void {
|
||||
fs.mkdirSync(SESSIONS_DIR, { recursive: true });
|
||||
fs.mkdirSync(SESSIONS_DIR, { recursive: true, mode: 0o700 });
|
||||
sidebarSession = loadSession();
|
||||
if (!sidebarSession) {
|
||||
sidebarSession = createSession();
|
||||
@@ -1331,7 +1331,7 @@ async function start() {
|
||||
chatBuffer = [];
|
||||
chatNextId = 0;
|
||||
if (sidebarSession) {
|
||||
try { fs.writeFileSync(path.join(SESSIONS_DIR, sidebarSession.id, 'chat.jsonl'), ''); } catch (err: any) {
|
||||
try { fs.writeFileSync(path.join(SESSIONS_DIR, sidebarSession.id, 'chat.jsonl'), '', { mode: 0o600 }); } catch (err: any) {
|
||||
console.error('[browse] Failed to clear chat file:', err.message);
|
||||
}
|
||||
}
|
||||
@@ -1693,8 +1693,8 @@ start().catch((err) => {
|
||||
// stderr because the server is launched with detached: true, stdio: 'ignore'.
|
||||
try {
|
||||
const errorLogPath = path.join(config.stateDir, 'browse-startup-error.log');
|
||||
fs.mkdirSync(config.stateDir, { recursive: true });
|
||||
fs.writeFileSync(errorLogPath, `${new Date().toISOString()} ${err.message}\n${err.stack || ''}\n`);
|
||||
fs.mkdirSync(config.stateDir, { recursive: true, mode: 0o700 });
|
||||
fs.writeFileSync(errorLogPath, `${new Date().toISOString()} ${err.message}\n${err.stack || ''}\n`, { mode: 0o600 });
|
||||
} catch {
|
||||
// stateDir may not exist — nothing more we can do
|
||||
}
|
||||
|
||||
@@ -49,7 +49,7 @@ function writeToInbox(message: string, pageUrl?: string, sessionId?: string): vo
|
||||
}
|
||||
|
||||
const inboxDir = path.join(gitRoot, '.context', 'sidebar-inbox');
|
||||
fs.mkdirSync(inboxDir, { recursive: true });
|
||||
fs.mkdirSync(inboxDir, { recursive: true, mode: 0o700 });
|
||||
|
||||
const now = new Date();
|
||||
const timestamp = now.toISOString().replace(/:/g, '-');
|
||||
@@ -65,7 +65,7 @@ function writeToInbox(message: string, pageUrl?: string, sessionId?: string): vo
|
||||
sidebarSessionId: sessionId || 'unknown',
|
||||
};
|
||||
|
||||
fs.writeFileSync(tmpFile, JSON.stringify(inboxMessage, null, 2));
|
||||
fs.writeFileSync(tmpFile, JSON.stringify(inboxMessage, null, 2), { mode: 0o600 });
|
||||
fs.renameSync(tmpFile, finalFile);
|
||||
console.log(`[sidebar-agent] Wrote inbox message: ${filename}`);
|
||||
}
|
||||
@@ -413,8 +413,8 @@ function pollKillFile(): void {
|
||||
|
||||
async function main() {
|
||||
const dir = path.dirname(QUEUE);
|
||||
fs.mkdirSync(dir, { recursive: true });
|
||||
if (!fs.existsSync(QUEUE)) fs.writeFileSync(QUEUE, '');
|
||||
fs.mkdirSync(dir, { recursive: true, mode: 0o700 });
|
||||
if (!fs.existsSync(QUEUE)) fs.writeFileSync(QUEUE, '', { mode: 0o600 });
|
||||
|
||||
lastLine = countLines();
|
||||
await refreshToken();
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
#!/usr/bin/env bash
|
||||
# gstack setup — build browser binary + register skills with Claude Code / Codex
|
||||
set -e
|
||||
umask 077 # Restrict new files to owner-only (0o600 files, 0o700 dirs)
|
||||
|
||||
if ! command -v bun >/dev/null 2>&1; then
|
||||
echo "Error: bun is required but not installed." >&2
|
||||
|
||||
Reference in New Issue
Block a user