T09 · Insecure Skill Coding Practices
- Location
scripts/duffel.py:58- Finding
Predictable Shared Temporary Files Allow Booking and Cancellation State Tampering
- Content
View full analysis
Vulnerability Details
File Location:
scripts/duffel.py:18,scripts/duffel.py:58-74, andscripts/duffel.py:375-399
Vulnerability Type: Predictable and insecure temporary-file handling
Risk Level: HighThe 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
View remediation
Remediation Suggestions
- Replace global
/tmppaths with a private per-user state directory, such as an appropriate platform-specific cache directory, created with mode0700. - Create state files with mode
0600and secure flags such asO_CREAT,O_EXCL, andO_NOFOLLOWwhere supported. - Validate with
lstator descriptor-based checks that each state object is a regular file owned by the current user and is not a symbolic link. - Write through a securely created temporary file in the private directory, flush it, and atomically rename it into place.
- Namespace state by Duffel account, process, workflow, or a cryptographically random session identifier rather than using one global file.
- 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. - Prefer requiring the user to pass the cancellation ID explicitly or create and confirm a cancellation within one controlled invocation.
- 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.
- Add expiration timestamps and reject stale offer and cancellation state.
- Remove sensitive temporary files after successful use and handle interruption cleanup safely.
- Replace global
