T09 · Insecure Skill Coding Practices
Warning
- Location
- scripts/feishu_oauth_server.js:42
- Finding
- OAuth credentials and user tokens are stored without restrictive file permissions## Vulnerability Details **File Location**: `scripts/feishu_oauth_server.js:42-48` and `scripts/feishu_oauth_server.js:117-124` **Vulnerability Type**: Plaintext sensitive-data storage with environment-dependent permissions **Risk Level**: Medium ### Vulnerable Code ```js function saveCredentials(appId, appSecret) { const payload = { saved_at: new Date().toISOString(), app_id: appId, app_secret: appSecret, }; fs.writeFileSync(CONFIG_OUTPUT, `${JSON.stringify(payload, null, 2)}\n`, 'utf8'); } ``` ```js function writeTokenFile(appId, tokenData) { const payload = { saved_at: new Date().toISOString(), redirect_uri: REDIRECT_URI, app_id: appId, ...tokenData, }; fs.writeFileSync(TOKEN_OUTPUT, `${JSON.stringify(payload, null, 2)}\n`, 'utf8'); } ``` ### Technical Analysis The OAuth server writes two security-sensitive JSON files: - The optional OAuth configuration file contains the Feishu App Secret. - The token file contains the user access token and may contain other token response fields, including reusable refresh-token data. The calls to `fs.writeFileSync` do not specify a restrictive file mode such as `0o600`. Consequently, newly created file permissions depend on the process umask. In an environment with a permissive umask or shared workspace, other local users or processes may be able to read these files. The code also does not inspect or repair permissions when overwriting pre-existing files. The token file is created automatically after successful OAuth authorization. App Secret persistence is user-selectable through the “remember credentials” option, but selecting that option still stores the secret in plaintext without enforcing owner-only access. ### Attack Path 1. A user runs `scripts/feishu_oauth_server.js`. 2. The user submits a Feishu App ID and App Secret and completes OAuth authorization. 3. The server writes `.feishu-user-token.json`; if credential remembrance is enabled, it also writes `.feishu-oaut ...[truncated 1188 chars]
- Remediation
- ## Remediation Suggestions 1. Create secret-bearing files with owner-only permissions: ```js fs.writeFileSync(CONFIG_OUTPUT, `${JSON.stringify(payload, null, 2)}\n`, { encoding: 'utf8', mode: 0o600, }); fs.writeFileSync(TOKEN_OUTPUT, `${JSON.stringify(payload, null, 2)}\n`, { encoding: 'utf8', mode: 0o600, }); ``` 2. Explicitly correct permissions after writing, including for files that already existed: ```js fs.chmodSync(CONFIG_OUTPUT, 0o600); fs.chmodSync(TOKEN_OUTPUT, 0o600); ``` 3. Use atomic writes: create a temporary file in the same directory with mode `0o600`, flush it, and rename it over the destination. This reduces the risk of partial writes and permission inconsistencies. 4. Avoid storing the App Secret when possible. Prefer environment variables or an operating-system credential store. If persistence is necessary, clearly warn that the secret will be stored locally and provide an explicit deletion mechanism. 5. Store OAuth tokens in a platform credential manager or encrypted secret store rather than plaintext JSON where supported. 6. Validate that the destination directory is not group- or world-writable and reject symbolic-link destinations before writing sensitive files. 7. Document token cleanup and revocation procedures, including deletion of both local files and revocation through Feishu when compromise is suspected.
