T09 · Insecure Skill Coding Practices
- Location
scripts/powpow-register.mjs:40- Finding
Predictable Shared Temporary File Exposes Session Credentials
- Content
View full analysis
Vulnerability Details
File Location:
scripts/powpow-register.mjs, lines 40–56, 203–211, and 420–424
Vulnerability Type: Predictable temporary file and symlink attack
Risk Level: MediumVulnerable Code
js const STATE_FILE = join(tmpdir(), 'powpow-register-state.json'); function loadState() { try { return JSON.parse(readFileSync(STATE_FILE, 'utf8')); } catch { return {}; } } function saveState(patch) { const s = { ...loadState(), ...patch }; writeFileSync(STATE_FILE, JSON.stringify(s, null, 2), { mode: 0o600 }); return s; }js saveState({ username: r.data?.user?.username || username, nickname: r.data?.user?.nickname || nickname, userId: r.data?.userId, token, step: 'registered-pending', registeredAt: new Date().toISOString(), });js function cmdCleanup() { if (existsSync(STATE_FILE)) unlinkSync(STATE_FILE); ok('State file deleted'); }Technical Analysis
The Skill stores workflow state at the fixed path
powpow-register-state.jsonin the system-wide temporary directory. That state includes the user's email, account metadata, payment identifiers, and an authentication JWT.Although
writeFileSync()specifies mode0600, that mode is reliably applied when creating a new file; it does not correct the permissions or ownership of an existing file. The write also follows symbolic links and does not use exclusive creation or no-follow protections.On a multi-user host, an unrelated local user can create the predictable directory entry before the victim invokes the Skill. The entry can be an attacker-owned readable file or a symbolic link to another victim-writable target. The Skill does not verify that the path is a regular file owned by the current user before reading, writing, or deleting it.
A single global path also causes unrelated concurrent Skill invocations under the same account to share an ...[truncated 1670 chars]
- Remediation
View remediation
Remediation Suggestions
- Create a private, unpredictable per-run directory with
mkdtempSync()and place the state file inside it. - Associate each state directory with a specific conversation or workflow instead of using one process-global filename.
- Create the file atomically with exclusive creation semantics and reject pre-existing entries.
- Use no-follow behavior where supported, or validate with
lstat()that the path is a regular file rather than a symbolic link. - Verify file ownership, link count, and permission mode before every read and write.
- Open the file through a descriptor with restrictive permissions and perform subsequent operations through that validated descriptor.
- Remove the complete private temporary directory when the workflow ends or is aborted.
- Avoid placing reusable bearer tokens in URL query parameters where possible; prefer a short-lived, single-use login exchange token.
- Add automated tests covering symlink pre-creation, attacker-owned files, concurrent workflows, stale state, and cleanup failures.
- Create a private, unpredictable per-run directory with
