T09 · Insecure Skill Coding Practices
Error
- Location
- scripts/queue_manager.py:327
- Finding
- Approval-State Bypass Allows Unapproved or Rejected Replies to Be Published<![CDATA[ ## Vulnerability Details **File Location**: `scripts/queue_manager.py:327-346` **Vulnerability Type**: Authorization and workflow-state validation failure **Risk Level**: High ### Vulnerable Code ```python def action_post(item_id: str) -> bool: """Manually retry posting an approved reply.""" queue = load_queue() idx, item = find_queue_item(queue, item_id) if not item: print(f"❌ Item #{item_id} not found in queue") return False reply_text = item.get("approved_reply") or item.get("draft_reply") if not reply_text: print(f"❌ No approved reply for item #{item_id}") return False success = post_reply_to_app_store(item["review_id"], reply_text) if success: now = datetime.now(timezone.utc).isoformat() queue[idx]["status"] = "posted" queue[idx]["posted_at"] = now save_queue(queue) update_review_status(item["review_id"], "posted", replied_at=now) print(f"✅ Reply posted!") return success ``` ### Technical Analysis The declared security model requires human approval before an AI-generated reply is posted. However, `action_post()` does not verify that the selected item has an `approved` status. It also falls back to `draft_reply` when `approved_reply` is absent. Consequently, the method can publish items in `pending`, `rejected`, `skipped`, or other states. This is a direct state-machine authorization bypass: possession of local command-execution access to the script is treated as sufficient approval, even though the application explicitly maintains approval state. Partial review-ID matching in `find_queue_item()` can further increase the chance of selecting an unintended item when abbreviated identifiers are used. ### Attack Path 1. A new negative review is fetched and an AI-generated draft is stored with status `pending`. 2. The draft has not been approved, or it is subsequently marked `rejected`. 3. A local caller or an AI agent invokes: ...[truncated 756 chars]
- Remediation
- <![CDATA[ ## Remediation Suggestions - Require the item to have exactly `status == "approved"` before posting. - Require a non-empty `approved_reply`; never fall back to `draft_reply` in the retry operation. - Reject posting for `pending`, `rejected`, `skipped`, and `posted` entries. - Use immutable, full queue-item identifiers rather than ambiguous prefixes or changing sequential positions. - Record approval metadata, including approval time and approving identity or channel. - Add tests proving that pending, rejected, skipped, and already-posted items cannot be posted. Example hardening: ```python if item.get("status") != "approved": print(f"❌ Item is not approved (status: {item.get('status')})") return False reply_text = item.get("approved_reply") if not isinstance(reply_text, str) or not reply_text.strip(): print("❌ Approved reply is empty") return False ``` ]]>
