T09 · Insecure Skill Coding Practices
- Location
scripts/feishu_oauth_server.js:42- Finding
OAuth credentials and user tokens are stored without restrictive file permissions
- Content
View full analysis
Vulnerability Details
File Location:
scripts/feishu_oauth_server.js:42-48andscripts/feishu_oauth_server.js:117-124
Vulnerability Type: Plaintext sensitive-data storage with environment-dependent permissions
Risk Level: MediumVulnerable 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.writeFileSyncdo not specify a restrictive file mode such as0o600. 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
- A user runs
scripts/feishu_oauth_server.js. - The user submits a Feishu App ID and App Secret and completes OAuth authorization.
- The server writes
.feishu-user-token.json; if credential remembrance is enabled, it also writes `.feishu-oaut ...[truncated 1188 chars]
- Remediation
View remediation
Remediation Suggestions
- 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, });- Explicitly correct permissions after writing, including for files that already existed:
js fs.chmodSync(CONFIG_OUTPUT, 0o600); fs.chmodSync(TOKEN_OUTPUT, 0o600);-
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. -
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.
-
Store OAuth tokens in a platform credential manager or encrypted secret store rather than plaintext JSON where supported.
-
Validate that the destination directory is not group- or world-writable and reject symbolic-link destinations before writing sensitive files.
-
Document token cleanup and revocation procedures, including deletion of both local files and revocation through Feishu when compromise is suspected.
