From f064b162e0c4b2f598030ed3220fdf44cfedcb65 Mon Sep 17 00:00:00 2001 From: John Bohannon Date: Sat, 11 Apr 2026 15:31:27 -0400 Subject: [PATCH 1/2] chore: ignore .worktrees directory --- .gitignore | 1 + 1 file changed, 1 insertion(+) diff --git a/.gitignore b/.gitignore index 3107b7c..5b9fda2 100644 --- a/.gitignore +++ b/.gitignore @@ -1,6 +1,7 @@ node_modules/ .DS_Store *.log +.worktrees/ spec/v4.new.json spec/.issue-title.txt spec/.issue-body.md From 4addf57939faf2fb4b2c17b6fb9eac3ca6e46de8 Mon Sep 17 00:00:00 2001 From: John Bohannon Date: Sat, 11 Apr 2026 15:33:07 -0400 Subject: [PATCH 2/2] fix: harden OAuth callback against HTML injection and shell interpolation - HTML-escape the `error` query param before rendering it in the OAuth error page, preventing potential XSS if a malicious redirect delivers unexpected content - Replace `exec()` shell string interpolation with `execFile()` so the URL is passed as a discrete argument rather than through the shell, eliminating any shell-metacharacter exposure --- src/auth.js | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) diff --git a/src/auth.js b/src/auth.js index c07cfb2..9d5f807 100644 --- a/src/auth.js +++ b/src/auth.js @@ -1,6 +1,6 @@ import { createHash, randomBytes } from 'node:crypto'; import { createServer } from 'node:http'; -import { exec } from 'node:child_process'; +import { execFile } from 'node:child_process'; import { setTokens, getOAuthClientId, getRefreshToken, getOAuthRedirectUri } from './config.js'; const REDIRECT_PORT = 9876; @@ -26,7 +26,7 @@ function openBrowser(url) { process.platform === 'win32' ? 'start' : process.platform === 'darwin' ? 'open' : 'xdg-open'; - exec(`${cmd} "${url}"`); + execFile(cmd, [url]); } function waitForCallback() { @@ -42,8 +42,9 @@ function waitForCallback() { server.close(); resolve(code); } else { + const safeError = (error || 'Unknown error').replace(/&/g, '&').replace(//g, '>').replace(/"/g, '"'); res.writeHead(400, { 'Content-Type': 'text/html' }); - res.end(`

Authorization failed

${error || 'Unknown error'}

`); + res.end(`

Authorization failed

${safeError}

`); server.close(); reject(new Error(`Authorization failed: ${error || 'unknown error'}`)); }