T09 · Insecure Skill Coding Practices
Error
- Location
- scripts/dashpass-cli.mjs:519
- Finding
- Mutual confirmation can be automatically fabricated and bypassed by one process<![CDATA[ ## Vulnerability Details **File Location**: `scripts/dashpass-cli.mjs:519-535`; `scripts/mutual-confirm.mjs:233-242,273-299` **Vulnerability Type**: Broken multi-party authorization **Risk Level**: High ### Vulnerable Code ```javascript // scripts/dashpass-cli.mjs:519-535 // Protocol: request → approve → execute const req = requestDecrypt(service, 'cli get --mutual', 'cc'); approveDecrypt(req, 'evo'); const shareA = readShareA(); const shareB = readShareB(); let decrypted; try { decrypted = executeDecrypt( shareA, shareB, decodeByteArray(best.data.encryptedBlob), decodeByteArray(best.data.salt), decodeByteArray(best.data.nonce), ); } catch (e) { console.error('[get] Mutual decryption failed:', e.message); process.exit(1); } ``` ```javascript // scripts/mutual-confirm.mjs:233-242 export function approveDecrypt(request, approverRole) { auditLog({ action: 'approve', service: request.credentialName, requester: request.requesterRole, approver: approverRole, result: 'approved', }); return { ...request, approver: approverRole, approvedAt: new Date().toISOString(), status: 'approved' }; } ``` ```javascript // scripts/mutual-confirm.mjs:273-299 export function executeDecrypt(shareAHex, shareBHex, encryptedBlobBuf, saltBuf, nonceBuf) { const privKeyBytes = combineShares(shareAHex, shareBHex); let aesKey = null; try { const ecdh = createECDH('secp256k1'); ecdh.setPrivateKey(privKeyBytes); const sharedSecret = ecdh.computeSecret(ecdh.getPublicKey()); aesKey = Buffer.from(hkdfSync('sha256', sharedSecret, saltBuf, 'dashpass-v1', 32)); sharedSecret.fill(0); const tag = encryptedBlobBuf.slice(encryptedBlobBuf.length - 16); const ct = encryptedBlobBuf.slice(0, encryptedBlobBuf.length - 16); const decipher = createDecipheriv('aes-256-gcm', aesKey, nonceBuf); decipher.setAuthTag(tag); const plain = Buffer.concat([decipher.update(ct), decipher.final()]); audit ...[truncated 2254 chars]
- Remediation
- <![CDATA[ ## Remediation Suggestions 1. Store the two shares under independently authenticated principals or on separate hosts. No single CLI process should be able to read both raw shares. 2. Require the approver to sign a canonical request containing: - Request ID - Credential/document ID - Requested operation - Credential version - Requester identity - Expiration time - Random nonce 3. Pass the signed approval into `executeDecrypt()` and verify its signature, scope, expiry, and replay status before reconstructing the key. 4. Enforce requester and approver separation instead of accepting arbitrary role strings. 5. Persist used request IDs or nonces to prevent replay. 6. Make decryption impossible when the approval object is missing, denied, expired, malformed, or for a different credential. 7. Add negative tests proving that self-approval, role spoofing, approval reuse, and approval substitution all fail. ]]>
