T09 · Insecure Skill Coding Practices
- Location
scripts/powpow-register.mjs:40- Finding
Process-Global State File Enables Cross-User Session Confusion
- Content
View full analysis
Vulnerability Details
File Location:
scripts/powpow-register.mjs, lines 40–56, 205–228, and 391
Vulnerability Type: Shared mutable state and cross-user credential confusion
Risk Level: HighTechnical Analysis
The script stores every registration workflow in one fixed file under the operating system's temporary directory:
javascript const BASE_URL = 'https://global.powpow.online'; const STATE_FILE = join(tmpdir(), 'powpow-register-state.json'); const PASSWORD_ENV = 'POWPOW_REGISTER_PASSWORD'; // ---------- 状态文件 ---------- 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; }Successful registration writes the account's bearer session token into this shared state:
javascript const token = r.cookies['powpow_token'] || ''; if (!token) die('注册成功但未捕获到会话 cookie(平台响应异常)'); 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(), });The login-link command subsequently reads whichever token is currently present and places it into a one-click authentication URL:
javascript function cmdLoginLink() { const state = requireState('token'); const target = arg('target') || 'pending'; const redirect = target === 'home' ? '/' : '/subscription'; const url = `${BASE_URL}/api/auth/me?token=${encodeURIComponent(state.token)}&redirect=${encodeURIComponent(redirect)}`; ok( target === 'home' ? '登录链接(直达首页;免费测试期内点开即用,测试期结束则会落到订阅页)' : 'pending 登录链接(直达订阅页,用于支付流程)', { url } ); }Refreshed credentials are also written to the same global state:
` ...[truncated 2593 chars]
- Remediation
View remediation
Remediation Suggestions
- Replace the fixed state filename with a cryptographically random, per-workflow state directory and file.
- Require every stateful command to receive an opaque workflow identifier that cannot be guessed by another user.
- Bind each workflow to the initiating Agent conversation and user identity, then reject commands where that ownership does not match.
- Do not merge unverified patches into shared state. Maintain a complete, schema-validated state object for each isolated workflow.
- Create state files atomically using exclusive creation flags and retain restrictive permissions such as
0600. - Separate sensitive credentials from non-sensitive workflow metadata. Prefer an Agent-provided secret store for JWTs where available.
- Before generating login links, creating orders, querying payments, or refreshing credentials, verify that the stored account and workflow belong to the current user.
- Delete only the current workflow's state during cleanup rather than deleting a process-global file.
- Add concurrency tests that interleave two independent registration and payment workflows and verify that no email, token, username, or order identifier crosses between them.
- Consider returning a short-lived, single-use login artifact instead of exposing a reusable bearer JWT in a URL.
