T09 · Insecure Skill Coding Practices
- Location
scripts/htyd-mcp.mjs:25- Finding
Authorization Credential Stored in a Plaintext File Without Restrictive Permissions
- Content
View full analysis
Vulnerability Details
File Location:
scripts/htyd-mcp.mjs, lines 25–32 and 74–81
Vulnerability Type: Plaintext storage of sensitive authentication credentials
Risk Level: MediumVulnerable Code
js function safeWriteJson(filePath, obj) { try { fs.writeFileSync(filePath, JSON.stringify(obj, null, 2), "utf8"); } catch { // ignore } }The authorization value is passed directly to this function:
js const configPath = getConfigFilePath(); const cfg = safeReadJson(configPath) ?? {}; const fromFile = !forcePrompt && typeof cfg.authorization === "string" ? cfg.authorization.trim() : ""; if (fromFile) return { Authorization: fromFile }; const authorization = await promptForAuthorization(); if (!authorization) return {}; safeWriteJson(configPath, { ...cfg, authorization }); return { Authorization: authorization };Technical Analysis
The client persists the complete bearer token or authorization header in
~/.htyd-mcp-client-streamable.jsonas plaintext. The call tofs.writeFileSyncdoes not specify a restrictive file mode, such as0o600.On POSIX systems, a newly created file consequently uses the runtime's default creation mode subject to the process umask. Under a common umask of
022, this can produce a file readable by other local users. If the file already exists with unsafe permissions, the implementation neither detects nor repairs those permissions. On other operating systems, protection depends entirely on inherited directory and account access-control settings.The empty
catchblock also suppresses storage and permission-related errors, preventing the user from knowing whether the credential was stored safely.Attack Path
- The user starts the client without setting
MCP_APP_KEYorMCP_AUTHORIZATION. - The client prompts the user for an AppKey or complete authorization value.
- The credential is normalized and ...[truncated 1118 chars]
- The user starts the client without setting
- Remediation
View remediation
Remediation Suggestions
-
Store long-lived credentials in the operating system's credential manager, such as Windows Credential Manager, macOS Keychain, or a Linux secret service.
-
If file storage is unavoidable, create the file with owner-only permissions:
js fs.writeFileSync( filePath, JSON.stringify(obj, null, 2), { encoding: "utf8", mode: 0o600 } ); -
Create the parent directory with mode
0o700where POSIX permissions are supported. -
Inspect existing file permissions before reading credentials and reject or repair files accessible by group or other users.
-
Avoid persisting the complete
Authorizationheader when a shorter-lived or revocable token can be used. -
Provide an option that disables credential persistence entirely.
-
Report write and permission failures to the user rather than silently suppressing them.
-
Document credential rotation and revocation procedures in case the configuration file is exposed.
-
