T09 · Insecure Skill Coding Practices
Error
- Location
- runner.cjs:92
- Finding
- OAuth Authorization Code Routed Through an Unverifiable Third-Party Callback Service<![CDATA[ ## Vulnerability Details **File Location**: `runner.cjs:6-7, 92-124` **Vulnerability Type**: Untrusted intermediary in OAuth authorization flow **Risk Level**: High ### Vulnerable Code ```js const CALLBACK_SERVER = 'https://linkedin-oauth-server-production.up.railway.app'; const REDIRECT_URI = `${CALLBACK_SERVER}/callback`; ``` ```js async function startOAuthFlow() { const state = Date.now().toString(); const authUrl = `https://www.linkedin.com/oauth/v2/authorization?response_type=code&client_id=${process.env.LINKEDIN_CLIENT_ID}&redirect_uri=${encodeURIComponent(REDIRECT_URI)}&scope=${encodeURIComponent(SCOPE)}&state=${state}`; console.log("\nPlease authorize the application by visiting this URL:\n"); console.log(authUrl); console.log("\nOpening browser..."); const startCmd = process.platform == 'darwin' ? 'open' : process.platform == 'win32' ? 'start' : 'xdg-open'; exec(`${startCmd} "${authUrl}"`); console.log("\nWaiting for authorization (this may take a few seconds)..."); let code = null; for (let i = 0; i < 60; i++) { await new Promise(resolve => setTimeout(resolve, 1000)); try { const response = await fetch(`${CALLBACK_SERVER}/api/token/${state}`); if (response.ok) { const data = await response.json(); code = data.code; break; } } catch (e) { // Continue polling } } if (!code) { throw new Error("Authorization timeout. Please try again."); } ``` ### Technical Analysis LinkedIn sends the OAuth authorization code to a hosted Railway service rather than directly to the local client. The client then retrieves the code by polling an endpoint keyed only by the OAuth state value. The callback server's implementation is not included in the audited project. Consequently, the audit cannot verify its authentication, authorization, code retenti ...[truncated 2261 chars]
- Remediation
- <![CDATA[ ## Remediation Suggestions 1. Replace the hosted callback relay with a loopback redirect such as `http://127.0.0.1:<random-port>/callback`. 2. Use PKCE with a cryptographically random `code_verifier` and corresponding `code_challenge` where supported. 3. If LinkedIn offers a device authorization flow appropriate to this client, prefer it over a shared callback relay. 4. If the relay must remain: - Publish and independently review its source code. - Authenticate callback retrieval rather than treating knowledge of state as authorization. - Bind each record to a separate high-entropy client secret or public-key proof. - Encrypt temporary authorization records. - Enforce short expiration and atomic one-time consumption. - Never log authorization codes or tokens. - Document ownership, hosting controls, retention policy, and incident response procedures. 5. Revoke existing authorizations and reauthorize users after replacing or hardening the callback infrastructure. ]]>
