T09 · Insecure Skill Coding Practices
- Location
scripts/auth.mjs:47- Finding
OAuth callback accepts uncorrelated authorization codes on a network-accessible listener
- Content
View full analysis
Vulnerability Details
File Location:
scripts/auth.mjs:47-74;scripts/refresh_token.js:70-134
Vulnerability Type: OAuth login CSRF and insecure local callback listener
Risk Level: HighVulnerable Code
scripts/auth.mjs:47-74:javascript async function getNewToken(oauth2Client) { return new Promise((resolve, reject) => { const authUrl = oauth2Client.generateAuthUrl({ access_type: 'offline', scope: SCOPES, }); const server = http.createServer(async (req, res) => { try { const qs = new url.URL(req.url, 'http://localhost:3000').searchParams; const code = qs.get('code'); if (code) { res.end('✅ Authentication successful! You can close this window.'); server.close(); const { tokens } = await oauth2Client.getToken(code); oauth2Client.setCredentials(tokens); resolve(tokens); } } catch (e) { reject(e); } }).listen(3000, () => { console.log('🌐 Opening browser for authentication...'); console.log(`If browser doesn't open, visit: ${authUrl}`); open(authUrl); }); }); }scripts/refresh_token.js:70-134:javascript const SCOPES = ['https://www.googleapis.com/auth/tasks']; const authUrl = `https://accounts.google.com/o/oauth2/v2/auth?${new url.URLSearchParams({ client_id, redirect_uri: redirect_uris[0], response_type: 'code', scope: SCOPES.join(' '), access_type: 'offline', prompt: 'consent' }).toString()}`; console.log('\n📋 Authorize this app by visiting:\n'); console.log(authUrl); console.log('\n'); // Start local server to receive callback const server = http.createServer(async (req, res) => { if (req.url.indexOf('/?code=') > -1) { const qs = new url.URL(req.url, 'http://localhost:3000').searchParams; const code = qs.get('code'); res.end('✅ Authentication successful! You can close this window.'); // Exchange code ...[truncated 4256 chars]- Remediation
View remediation
Remediation Suggestions
- Replace the custom OAuth implementation with a maintained installed-application flow from Google's authentication library where possible.
- Generate at least 32 bytes of cryptographically secure random data for each authorization attempt:
javascript import crypto from 'crypto'; const state = crypto.randomBytes(32).toString('base64url');- Include
statein the authorization URL and require an exact, timing-safe match at the callback. - Bind explicitly to a loopback address and preferably use an ephemeral port:
javascript server.listen(0, '127.0.0.1', () => { const { port } = server.address(); });- Construct the redirect URI from the actual loopback port and ensure it is permitted by the installed-application OAuth client configuration.
- Validate that the request uses
GETand that the pathname exactly matches the configured callback path. - Reject callbacks containing
error, missingcode, duplicate parameters, or invalidstate. - Add a short timeout that closes the server and invalidates the pending state.
- Add PKCE with an S256 code challenge where supported.
- Remove or deprecate the duplicate
refresh_token.jsflow so that only one hardened authentication implementation is exposed to users.
