T09 · Insecure Skill Coding Practices
Error
- Location
- scripts/duffel.py:58
- Finding
- Predictable Shared Temporary Files Allow Booking and Cancellation State Tampering## Vulnerability Details **File Location**: `scripts/duffel.py:18`, `scripts/duffel.py:58-74`, and `scripts/duffel.py:375-399` **Vulnerability Type**: Predictable and insecure temporary-file handling **Risk Level**: High The CLI stores security-sensitive offer and cancellation state in fixed, globally predictable files under `/tmp`. These files are created and read without secure creation, ownership validation, symlink protection, restrictive permissions, session isolation, or verification that the stored cancellation belongs to the order supplied by the user. **Vulnerable code:** ```python LAST_SEARCH_FILE = "/tmp/duffel-last-search.json" ``` ```python def save_search(offers_data): """Save search results for index-based reference.""" with open(LAST_SEARCH_FILE, "w") as f: json.dump(offers_data, f) def load_offer(id_or_index): """Load an offer by ID or 1-based index from last search.""" try: idx = int(id_or_index) with open(LAST_SEARCH_FILE) as f: data = json.load(f) offers = data if isinstance(data, list) else data.get("offers", []) if idx < 1 or idx > len(offers): print(f"Error: Index {idx} out of range (1-{len(offers)})") sys.exit(1) return offers[idx - 1] except (ValueError, FileNotFoundError): return {"id": id_or_index} ``` ```python if not args.confirm: # Get cancellation quote payload = {"data": {"order_id": args.order_id}} data = api_post("/air/order_cancellations", payload) cancel = data.get("data", {}) if args.json: print(json.dumps(cancel, indent=2)) return refund = cancel.get("refund_amount", "0") currency = cancel.get("refund_currency", "?") print(f"\n⚠️ Cancellation quote for order {args.order_id}") print(f" Refund: {currency} {refund}") print(f" Cancellation ID: {cancel.get('id', '?' ...[truncated 4118 chars]
- Remediation
- ## Remediation Suggestions 1. Replace global `/tmp` paths with a private per-user state directory, such as an appropriate platform-specific cache directory, created with mode `0700`. 2. Create state files with mode `0600` and secure flags such as `O_CREAT`, `O_EXCL`, and `O_NOFOLLOW` where supported. 3. Validate with `lstat` or descriptor-based checks that each state object is a regular file owned by the current user and is not a symbolic link. 4. Write through a securely created temporary file in the private directory, flush it, and atomically rename it into place. 5. Namespace state by Duffel account, process, workflow, or a cryptographically random session identifier rather than using one global file. 6. Bind cached cancellation state to its order ID. Before confirmation, verify that the stored order ID exactly equals `args.order_id`; reject missing, stale, or mismatched state. 7. Prefer requiring the user to pass the cancellation ID explicitly or create and confirm a cancellation within one controlled invocation. 8. Avoid trusting cached price, currency, passenger, or offer metadata for a financial action. Re-fetch the selected offer by ID and verify its current price and passenger mapping before booking. 9. Add expiration timestamps and reject stale offer and cancellation state. 10. Remove sensitive temporary files after successful use and handle interruption cleanup safely.
