T09 · Insecure Skill Coding Practices
Error
- Location
- SKILL.md:14
- Finding
- Long-Lived Session Tokens Exposed Through URLs and Browser Storage<![CDATA[ ## Vulnerability Details **File Location**: `SKILL.md`, lines 14–91 **Vulnerability Type**: Insecure bearer-token transport and storage **Risk Level**: High ### Vulnerable Instructions ```text 14: - **Token storage (designated location = environment variables).** All secrets are kept out of the prompt, URL and logs, and stored in a single designated place: environment variables. Do not hard-code tokens in code/URLs, and never send them as query params. Designated variables: 15: - `AUTH_TOKEN` — the full `Authorization` header value for the login/session token (e.g. `Bearer <token>`). Set once after Step 1 and reused verbatim for **every** backend API call and the next session. Re-authorize only when the API returns `401`. 18: - **Token lifetime (checked)**: login/session JWT is long-lived (`jwt.expiration` ≈ 7 years). GMB `access_token` is short-lived (`expires_in`, typically ~3600s / 1h), `refresh_token` is long-lived — the backend refreshes automatically. 28: Connect Google Business = {API_BASE_URL}/oauth2/connectGoogleBusiness?token=<url-encoded-token>&redirectState=<url-encoded-return-url> 52: > - Prefer the **SSO handoff (Mode B)** below so the URL only contains `handoffId` — the token never appears in the address bar, so no sensitive-URL warning. 59: 3. The callback carries `?token=<token>`; store `Bearer <token>` as the session token (in web: `localStorage`; in app: uni storage under key `token`). 68: 2. Give the merchant this URL: `{API_BASE_URL}/oauth2/authorization/google-autoreply?handoffId=<handoffId>` and ask them to complete the sign-in. After login the browser is **redirected back to `returnUrl` with `token` & `tokenHead` appended** (e.g. `returnUrl?token=...&tokenHead=...`). 71: - `EXPIRED` → the slot expired / was never opened; create a new handoff and retry, or fall back to asking the merchant to paste the `?token=` value. 91: {API_BASE_URL}/oauth2/connectGoogleBusiness?token=<url-encoded-token>&redirectState=<u ...[truncated 4037 chars]
- Remediation
- <![CDATA[ ## Remediation Suggestions 1. **Remove bearer tokens from every URL** - Eliminate `?token=`, `tokenHead`, and equivalent credential parameters from callbacks, redirects, and Google Business connection URLs. - Do not include bearer tokens in URL fragments as a substitute; browser-side scripts and extensions may still expose them. 2. **Use one-time authorization codes** - Return a cryptographically random, single-use code after authentication. - Bind the code to the initiating client, redirect URI, session, and PKCE challenge. - Exchange it through a server-to-server or otherwise protected back-channel request. - Give the code a short expiration period and invalidate it immediately after use. 3. **Harden the SSO handoff mechanism** - Return the token only through the authenticated polling channel. - Remove the token-bearing redirect and copy-and-paste fallbacks. - Ensure handoff identifiers are random, short-lived, single-use, and resistant to enumeration. - Bind each handoff to the initiating session and intended merchant. - Rate-limit polling and invalidate the slot after successful retrieval. 4. **Restrict redirect destinations** - Maintain an exact allowlist of approved HTTPS origins and paths. - Reject user-controlled or dynamically supplied destinations that are not pre-registered. - Prevent open redirects, scheme changes, wildcard subdomains, and credential-bearing redirects. 5. **Replace `localStorage` session persistence** - For web clients, prefer cookies configured with `Secure`, `HttpOnly`, and an appropriate `SameSite` policy. - Apply CSRF protection to state-changing requests when cookie-based authentication is used. - If client-side token access is unavoidable, keep access tokens in memory and minimize their lifetime. 6. **Reduce credential lifetime and privilege** - Replace the approximately seven-year JWT with short-lived access tokens. - Use rotating refresh tokens with replay d ...[truncated 1295 chars]
