T09 · Insecure Skill Coding Practices
Warning
- Location
- scripts/_common.js:157
- Finding
- Refreshed Access Tokens Are Exposed in Process Output<![CDATA[ ## Vulnerability Details **File Location**: - `scripts/_common.js:157-172` - `scripts/_common.js:194-222` - `scripts/refresh-token.js:6-16` - `scripts/refresh-facebook-token.js:3-13` **Vulnerability Type**: Sensitive credential exposure through stdout **Risk Level**: Medium ### Vulnerable Code ```js // scripts/_common.js:157-172 const newToken = data.access_token; const expiresInDays = Math.floor(data.expires_in / 86400); // Update runtime process.env.INSTAGRAM_ACCESS_TOKEN = newToken; // Persist to .env file let envContent = fs.readFileSync(envPath, "utf-8"); envContent = envContent.replace( /INSTAGRAM_ACCESS_TOKEN=.*/, `INSTAGRAM_ACCESS_TOKEN=${newToken}` ); fs.writeFileSync(envPath, envContent); log(`IG token refreshed (expires in ${expiresInDays} days)`); return { access_token: newToken, expires_in: data.expires_in, expires_in_days: expiresInDays }; ``` ```js // scripts/_common.js:194-222 const newToken = data.access_token; const expiresInDays = data.expires_in ? Math.floor(Number(data.expires_in) / 86400) : null; process.env.FACEBOOK_USER_ACCESS_TOKEN = newToken; let envContent = fs.readFileSync(envPath, "utf-8"); if (/^FACEBOOK_USER_ACCESS_TOKEN=.*/m.test(envContent)) { envContent = envContent.replace( /^FACEBOOK_USER_ACCESS_TOKEN=.*/m, `FACEBOOK_USER_ACCESS_TOKEN=${newToken}` ); } else { envContent = envContent.trimEnd() + `\nFACEBOOK_USER_ACCESS_TOKEN=${newToken}\n`; } fs.writeFileSync(envPath, envContent); if (expiresInDays != null) { log(`FB token refreshed (expires in ${expiresInDays} days)`); } else { log("FB token refreshed"); } return { access_token: newToken, expires_in: data.expires_in, expires_in_days: expiresInDays, }; ``` ```js // scripts/refresh-token.js:6-16 (async () => { try { const { named } = parseArgs(); loadEnv(named.env); const result = await refreshIgToken(); process.stdout.write(JSON.stringify(result, null, 2) + "\n"); process.exit(0); } catch (err) { ...[truncated 2576 chars]
- Remediation
- <![CDATA[ ## Remediation Suggestions 1. Never include `access_token` in a returned result or JSON command output. 2. Return only non-sensitive status metadata: ```js return { refreshed: true, expires_in: data.expires_in, expires_in_days: expiresInDays, }; ``` 3. Apply the same redaction to skipped-refresh behavior; do not return the existing token when refresh is skipped. 4. Review `SPEC.md` examples and remove sample response structures that encourage returning an `access_token` field. 5. Add an output-sanitization layer that recursively redacts keys such as `access_token`, `token`, `client_secret`, and `app_secret`. 6. Ensure the `.env` file has restrictive permissions, preferably mode `0600`, before writing credentials. 7. Configure agent, CI, and observability systems not to retain sensitive command output. 8. Rotate both tokens if existing command output may have been retained in logs or transcripts. 9. Add automated tests asserting that refresh-command stdout never contains the old or refreshed token. ]]>
