T09 · Insecure Skill Coding Practices
Warning
- Location
- scripts/portal-bridge.js:20
- Finding
- One-Time Authentication Token Exposed in URL Query String<![CDATA[ ## Vulnerability Details **File Location**: `SKILL.md:39, 53-55`; `scripts/portal-bridge.js:20, 31-46` **Vulnerability Type**: Authentication token exposure through query-string URLs **Risk Level**: Medium ### Vulnerable Code The Skill explicitly recommends placing the Supabase one-time authentication credential in a URL query parameter: ```text https://site/portal/?token_hash=<hashed_token>&type=magiclink ``` ```js const u = new URL('https://site/portal/'); u.searchParams.set('token_hash', gen.hashed_token); u.searchParams.set('type', gen.verification_type || 'magiclink'); const magicLink = u.toString(); ``` The portal reads the token from the query string and does not remove it until the asynchronous verification request has settled: ```js var q = new URLSearchParams(window.location.search); // (3) error first — short-circuit and don't try to verify anything. var err = q.get('error_description') || q.get('error') || q.get('error_code'); if (err) { setTimeout(function () { alert('הקישור פג או כבר נוצל. בקש מהסוכן לשלוח קישור חדש.\n\n(' + decodeURIComponent(err) + ')'); }, 300); history.replaceState(null, '', window.location.pathname); return; } // (1) token_hash flow — explicit verifyOtp. var tokenHash = q.get('token_hash'); var type = q.get('type'); if (tokenHash && type) { sb.auth.verifyOtp({ token_hash: tokenHash, type: type }) .then(function (res) { if (res.error) { console.warn('verifyOtp failed:', res.error.message); setTimeout(function () { alert('הקישור פג או כבר נוצל. בקש מהסוכן לשלוח קישור חדש.'); }, 300); } }) .finally(function () { // Strip the one-time token from the URL so a refresh doesn't replay. history.replaceState(null, '', window.location.pathname); }); } ``` ### Technical Analysis Although named `token_hash`, this value is a bearer-like one-time credential accepted by Supabase's `verifyOtp` operation. Possession ...[truncated 2951 chars]
- Remediation
- <![CDATA[ ## Remediation Suggestions 1. Copy `token_hash` and `type` into local variables and immediately remove authentication parameters from the visible URL before beginning asynchronous verification: ```js var q = new URLSearchParams(window.location.search); var tokenHash = q.get('token_hash'); var type = q.get('type'); if (tokenHash && type) { history.replaceState(null, '', window.location.pathname); sb.auth.verifyOtp({ token_hash: tokenHash, type: type }).then(function (res) { if (res.error) { console.warn('verifyOtp failed:', res.error.message); } }); } ``` 2. Execute the bridge inline in the document head before analytics, tag managers, monitoring agents, images, or other third-party resources can inspect or receive the original URL. 3. Configure a restrictive response header on the portal and redirect entry points: ```http Referrer-Policy: no-referrer ``` 4. Configure reverse proxies, hosting platforms, analytics products, and application logs to redact `token_hash`, `code`, `access_token`, and similar authentication parameters. 5. Avoid loading unnecessary resources on the token-consumption entry page. A minimal interstitial route can clean the URL and verify the token before loading the full portal. 6. Keep tokens short-lived and single-use, monitor failed or duplicate consumption attempts, and invalidate outstanding links when suspicious use is detected. 7. Keep `SUPABASE_SERVICE_ROLE_KEY` exclusively in server-side secret storage. Validate or allow-list `SUPABASE_URL` so the service-role credential cannot be sent to an unintended host through configuration tampering. ]]>
