T09 · Insecure Skill Coding Practices
- Location
scripts/powpow-register.mjs:40- Finding
Predictable temporary state file permits session-token disclosure and workflow-state manipulation
- Content
View full analysis
Vulnerability Details
File Location:
scripts/powpow-register.mjs, lines 40–55 and 203–210
Vulnerability Type: Insecure predictable temporary file
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; }The sensitive session token is subsequently stored in this file:
js const token = r.cookies['powpow_token'] || ''; if (!token) die('Registration succeeded, but no session cookie was captured'); 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(), });Technical Analysis
The state path is a fixed, globally predictable name under the system temporary directory. On a shared operating system, another local user can create this path before the victim invokes the Skill.
The
{ mode: 0o600 }option only determines permissions when Node.js creates a new file. It does not securely replace an existing file, correct an existing file's permissions, reject symbolic links, or verify ownership. Consequently,writeFileSync()can write sensitive state into a pre-created attacker-readable file or follow a symbolic link.The file contains a bearer JWT captured from the registration response. The token is later accepted by the platform's one-click-login bridge and authenticated API requests. The same file also controls workflow fields such as the username, provider, order number, and current session token.
loadState()trusts this pre-existing content without validating file type, ow ...[truncated 1824 chars]- Remediation
View remediation
Remediation Suggestions
- Create a private per-user or per-session directory under the temporary directory with mode
0700. - Use an unpredictable per-session filename instead of a global fixed name.
- Atomically create the state file with exclusive-create and no-follow semantics, such as
O_CREAT | O_EXCL | O_NOFOLLOW, with mode0600. - Before every read or update, use
lstat()and verify that the path is a regular file, is owned by the current effective user, has no unexpected hard links, and has restrictive permissions. - Do not perform read-modify-write operations through a path that may have been replaced between checks. Use an already-opened, validated file descriptor where possible.
- Write updates to a securely created file in the private directory and atomically rename it within that same protected directory.
- Keep the bearer token only in process-local protected state where workflow architecture permits, and minimize its lifetime.
- Generate separate state namespaces for concurrent registrations so one invocation cannot overwrite or consume another invocation's account state.
- Create a private per-user or per-session directory under the temporary directory with mode
