T09 · Insecure Skill Coding Practices
- Location
SKILL.md:227- Finding
Non-Atomic Idempotency Check Permits Duplicate Side Effects
- Content
View full analysis
Vulnerability Details
File Location:
SKILL.md, lines 227–234
Vulnerability Type: Check-then-act race condition and incomplete crash-safe idempotency
Risk Level: Highpython def process_with_idempotency(item, redis_client): key = f"idem:{item['id']}" # Check whether the item has already been processed if redis_client.exists(key): return {"status": "skipped", "reason": "already_processed"} # Process the item result = do_process(item) # Mark it as processed with a TTL redis_client.setex(key, 86400, "1") # 24-hour TTL return resultTechnical Analysis
The example implements idempotency as three independent operations:
- Check whether the Redis key exists.
- Execute
do_process(item). - Store the completion key.
This is a check-then-act race condition. Two concurrent workers processing the same item can both observe that the key does not exist and then both execute
do_process(item). The latersetexcalls do not undo duplicate side effects.The design is also unsafe under process failure. If a worker successfully performs the side effect but crashes before
setex, no completion marker is recorded. A resumed job or retry will consequently execute the operation again.The fixed 24-hour TTL introduces another duplication window: the same item can be processed again after the key expires, even when the underlying business operation must remain permanently idempotent.
Attack Path
- An attacker or ordinary input source submits records with the same
item['id'], or causes the same item to be assigned to multiple concurrent workers. - Worker A calls
redis_client.exists(key)and receives false. - Before Worker A writes the marker, Worker B checks the same key and also receives false.
- Both workers invoke
do_process(item). - Both workers perform the associated side effect, such as charging an order, sending a message, or ...[truncated 1043 chars]
- Remediation
View remediation
Remediation Suggestions
- Atomically reserve each idempotency key before processing by using Redis
SET key token NX EX ttl. A worker must not proceed unless it successfully acquires the reservation. - Store an unguessable per-worker token as the value and use a Lua script or transaction when changing or releasing the reservation, ensuring that one worker cannot modify another worker's reservation.
- Model explicit states such as
processing,completed, andfailed. Carefully define recovery behavior for expiredprocessingreservations. - Pass the same durable idempotency key to downstream services that support native idempotency controls.
- For database-backed side effects, write the idempotency record and business mutation in one transaction with a unique constraint on the idempotency key.
- When the side effect and state store cannot participate in one transaction, use a transactional outbox or inbox pattern with a deduplicating consumer.
- Retain completion records for at least as long as duplicate execution would remain harmful. Do not use a 24-hour TTL for operations requiring permanent deduplication.
- Add concurrency tests in which multiple workers process the same identifier simultaneously, as well as fault-injection tests that terminate a worker between the side effect and completion-state update.
- Atomically reserve each idempotency key before processing by using Redis
