T09 · Insecure Skill Coding Practices
- Location
scripts/powpow-register.mjs:40- Finding
Global temporary state file causes cross-conversation session confusion
- Content
View full analysis
Vulnerability Details
File Location:
scripts/powpow-register.mjs:40-55, 155-166, 203-225
Vulnerability Type: Shared authentication state without per-user or per-conversation isolation
Risk Level: HighComplete Code Snippet
js 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; }The registration-code operation updates the same shared file without clearing state left by another workflow:
js async function cmdSendCode() { const email = arg('email', { required: true }); const r = await api('/api/auth/register/send-code', { method: 'POST', body: { email } }); if (r.status === 429) die('发送太频繁,请 1 分钟后再试。'); if (!r.ok) die(r.data?.error || `发送失败(HTTP ${r.status})`); saveState({ email, step: 'code-sent', codeSentAt: new Date().toISOString() }); ok('验证码已发送,请查收邮箱(注意垃圾邮件文件夹),10 分钟内有效', { email }); }A successful registration saves its session token into that global state, and a later invocation reads whichever token is currently present:
js 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(), }); function cmdLoginLink() { const state = requireState('token'); const target = arg('target') || 'pending'; const redirect = target === 'home' ? '/' : '/subscription'; const url = `${BASE_URL}/api/auth/me?token=${encodeUR ...[truncated 2881 chars]- Remediation
View remediation
Remediation Suggestions
- Generate a cryptographically random workflow identifier when registration begins and require it for every subsequent command.
- Bind each workflow identifier to the authenticated agent user or conversation so another conversation cannot select or reuse it.
- Store state in a private per-workflow directory with restrictive permissions, for example:
text <private-runtime-dir>/powpow-register/<random-workflow-id>/state.json - Create fresh state for every new registration rather than merging new registration fields into an existing workflow. Explicitly discard stale tokens, order identifiers, and account fields.
- Reject commands when the state owner does not match the current authenticated conversation or user.
- Use atomic file creation and locking to prevent concurrent updates from corrupting or mixing state.
- Expire abandoned workflows automatically and remove their tokens after a short, defined lifetime. Keep explicit
cleanupas an additional control rather than the only cleanup mechanism. - Avoid placing bearer tokens in URLs where possible. Prefer a short-lived, single-use login exchange code scoped to the intended account and redirect.
- Add tests covering concurrent registrations, interrupted workflows, stale state, cross-conversation command execution, and cleanup of one workflow while another remains active.
