T09 · Insecure Skill Coding Practices
Error
- Location
- scripts/vault.js:6
- Finding
- Keystore Path Traversal Through Attacker-Controlled Identity Names## Vulnerability Details **File Location**: `scripts/vault.js:6-33` **Vulnerability Type**: Path traversal and unauthorized filesystem access **Risk Level**: High ### Vulnerable Code ```js const VAULT_PATH = path.join(process.cwd(), 'data/keystore'); module.exports = { getOrGenerateIdentity: async (localAgentId) => { await sodium.ready; if (!fs.existsSync(VAULT_PATH)) fs.mkdirSync(VAULT_PATH, { recursive: true }); const keyPath = path.join(VAULT_PATH, `${localAgentId}.keys.json`); if (fs.existsSync(keyPath)) { const keys = JSON.parse(fs.readFileSync(keyPath)); return { publicKey: Buffer.from(keys.publicKey, 'hex'), privateKey: Buffer.from(keys.privateKey, 'hex') }; } const keypair = sodium.crypto_sign_keypair(); fs.writeFileSync(keyPath, JSON.stringify({ publicKey: sodium.to_hex(keypair.publicKey), privateKey: sodium.to_hex(keypair.privateKey) }), { mode: 0o600 }); return { publicKey: keypair.publicKey, privateKey: keypair.privateKey }; }, signPayload: async (localAgentId, payloadString) => { await sodium.ready; const keyPath = path.join(VAULT_PATH, `${localAgentId}.keys.json`); const keys = JSON.parse(fs.readFileSync(keyPath)); const signature = sodium.crypto_sign_detached( payloadString, Buffer.from(keys.privateKey, 'hex') ); return sodium.to_hex(signature); }, ``` The attacker-controlled values originate from `index.js:26-27` and `index.js:65-67`: ```js async function registerIdentity(params) { const localAgentId = params.alias || `agent-${uuidv4()}`; const keys = await vault.getOrGenerateIdentity(localAgentId); } async function signMessage(params) { const { localId, payload } = params; const contentStr = typeof payload === 'string' ? payload : JSON.stringify(payload); const signatureHex = await vault.signPayload(local ...[truncated 1972 chars]
- Remediation
- ## Remediation Suggestions - Restrict identity names to a conservative allowlist, such as `/^[A-Za-z0-9_-]{1,64}$/`. - Reject path separators, `.` and `..` components, control characters, and platform-specific separator variants. - Resolve the final path and verify containment before every read or write: ```js const base = path.resolve(VAULT_PATH); const candidate = path.resolve(base, `${localAgentId}.keys.json`); if (!candidate.startsWith(base + path.sep)) { throw new Error('Invalid identity identifier'); } ``` - Use opaque, randomly generated identifiers as file names rather than user-provided aliases. - Store the human-readable alias as validated metadata instead of using it as a path component. - Protect against symlink-based redirection by checking path components and using safe file-open flags. - Consider exclusive creation (`wx`) when creating new identities to prevent unintended replacement. - Apply the same centralized path-validation routine to registration and signing.
