T09 · Insecure Skill Coding Practices
Error
- Location
- tool/scripts/post-voucher.js:14
- Finding
- Maker-checker approval can be self-asserted or bypassed<![CDATA[ ## Vulnerability Details **File Location**: `tool/scripts/post-voucher.js:14-18`; related bypasses at `tool/scripts/rectify-entry.js:19-21` and `tool/scripts/reverse-voucher.js:14-21` **Vulnerability Type**: Client-controlled authorization state **Risk Level**: High ### Vulnerable Code ```javascript if (payload.voucher.status !== 'CONFIRMED' && payload.voucher.status !== 'POSTED') { console.error("Maker-Checker Rule Enforced: Voucher must be CONFIRMED before posting."); process.exit(1); } ``` The rectification command additionally forces the trusted state: ```javascript const payload = JSON.parse(fs.readFileSync(newPayloadPath, 'utf8')); payload.voucher.status = 'POSTED'; // Force post for rectification try { const id = engine.postVoucher(payload.voucher, payload.lines); ``` The reversal command also constructs a posted voucher without independent approval: ```javascript const reversedVoucher = { ...voucher, id: undefined, // let it auto-increment voucher_no: null, // generate new status: 'POSTED', narration: `Reversal of ${voucher.voucher_no}: ${voucher.narration || ''}` }; ``` ### Technical Analysis The posting command treats the `status` property of caller-supplied JSON as proof that a checker approved the voucher. A caller can set the property to either `CONFIRMED` or `POSTED`; there is no authenticated approval record, approver identity, role separation, signature, or binding between an approval and the exact contents of the voucher. The rectification and reversal paths bypass even this superficial check by directly assigning `POSTED`. This contradicts the documented maker-checker workflow, under which the agent may prepare a voucher but must not submit it until the user explicitly approves it. ### Attack Path 1. A local caller or accounting agent creates a voucher JSON file. 2. The caller sets `voucher.status` to `POSTED` or `CONFIRMED`. 3. The caller runs `node scripts/post-voucher.js payload.json`. 4. ...[truncated 751 chars]
- Remediation
- <![CDATA[ ## Remediation Suggestions - Never accept `CONFIRMED` or `POSTED` as trusted state from an input payload. - Store approvals in a separate database table containing: - Voucher or draft identifier - Cryptographic hash of all approved voucher fields and lines - Authenticated approver identity - Approval timestamp - Maker and checker roles - Approval status and revocation state - Require the posting transaction to retrieve and consume a valid approval associated with the exact voucher hash. - Enforce that maker and checker identities are different. - Make `POSTED` an internal state transition that only the posting engine can assign. - Apply the same approval enforcement to posting, reversal, and rectification. - Record authorization failures and successful approvals in an append-only audit log. ]]>
