T09 · Insecure Skill Coding Practices
- Location
SKILL.md:353- Finding
Bearer Token Stored in a Plaintext Configuration File Without Explicit Access Controls
- Content
View full analysis
Vulnerability Details
File Location:
SKILL.md:353-362
Vulnerability Type: Plaintext credential storage with insufficient file-permission hardening
Risk Level: MediumThe skill instructs the agent to write the Memos instance access token directly to
config.json:js const config = { instance_url: "https://memo.example.com", access_token: "abc123", default_page_size: 10, default_visibility: "PRIVATE", }; const skillDir = __dirname; const configPath = path.join(skillDir, "config.json"); fs.writeFileSync(configPath, `${JSON.stringify(config, null, 2)}\n`, "utf8");Related instructions at
SKILL.md:64-74subsequently read this plaintext token and use it as a bearer credential:js // Read configuration const config = JSON.parse(fs.readFileSync(configPath, "utf8")); const instanceUrl = config.instance_url; const accessToken = config.access_token; const headers = { Authorization: `Bearer ${accessToken}`, "Content-Type": "application/json", };Technical Analysis
fs.writeFileSyncis used without an explicit restrictivemode. Consequently, the resulting permissions depend on the runtime environment and process umask rather than being guaranteed to allow access only to the owning user. The bearer token is stored unencrypted in a predictable file inside the skill directory.Any local account, process, backup system, artifact collector, or accidental repository commit that can read this file can recover the credential. A Memos bearer token is reusable authentication material; possession may be sufficient to impersonate the user until the token expires or is revoked.
The documentation also demonstrates passing the token in a
curlauthorization-header argument atSKILL.md:106-110:bash curl -X POST \ -H "Authorization: Bearer $MEMOS_TOKEN" \ -H "Content-Type: application/json" \ -d '{"content":"Test content","tags":["test"]}' \ ...[truncated 1772 chars]- Remediation
View remediation
Remediation Suggestions
-
Store the access token in an operating-system credential manager, dedicated secret manager, or protected runtime environment rather than in the skill directory.
-
If file-based storage is unavoidable, create the file with explicit owner-only permissions:
js fs.writeFileSync( configPath, `${JSON.stringify(config, null, 2)}\n`, { encoding: "utf8", mode: 0o600, flag: "w" }, ); -
Before reading an existing configuration file, verify that it is owned by the expected user, is not a symbolic link, and is not accessible by group or other users.
-
Separate non-sensitive settings from credentials. Keep
instance_url, page size, and visibility inconfig.json, while resolving the token from a secret store at runtime. -
Add
config.jsonto.gitignoreand provide a redactedconfig.example.jsoncontaining placeholders only. -
Avoid exposing the bearer token in command-line arguments. Use a protected temporary curl configuration, a secured header file, or another mechanism that does not make the expanded token visible in the process argument list. Ensure any temporary file is created with mode
0600and deleted promptly. -
Never print the token in logs, errors, command traces, or confirmation messages. Redact authorization headers from diagnostics.
-
Apply least privilege and limited lifetimes to access tokens. Document immediate revocation and rotation procedures for suspected disclosure.
-
