Paths: File paths (
references/,../ln-*) are relative to this skill directory.
Concurrency Correctness Auditor (L3 Worker)
Type: L3 Worker
Specialized worker auditing concurrency correctness, async hazards, and cross-process resource access.
Purpose & Scope
- Audit concurrency (Category 11: High Priority)
- 7 checks: async races, thread safety, TOCTOU, deadlocks, blocking I/O, resource contention, cross-process races
- Two-layer detection: grep finds candidates, agent reasons about context
- Emit
FIX_RACE,FIX_DEADLOCK, orCONTROL_ASYNC_SIDE_EFFECT - Calculate compliance score (X/10)
Inputs
MANDATORY READ: Load references/audit_worker_core_contract.md.
Tool policy: follow host AGENTS.md MCP preferences; load references/mcp_tool_preferences.md and references/mcp_integration_patterns.md only when host policy is absent or MCP behavior is unclear.
Receives contextStore with: tech_stack, best_practices, codebase_root, output_dir.
Use hex-graph first when dataflow or call-path analysis materially improves concurrency findings. Use hex-line first for local code reads when available. If MCP is unavailable, unsupported, or not indexed, continue with built-in Read/Grep/Glob/Bash and state the fallback in the report.
Workflow
Detection policy: use two-layer detection (candidate scan, then context verification); load references/two_layer_detection.md only when the verification method is ambiguous.
- Parse context -- extract tech_stack, language, output_dir from contextStore
- Per check (1-7):
- Layer 1: Grep/Glob scan to find candidates
- Layer 2: Read 20-50 lines around each candidate. Apply check-specific critical questions. Classify: confirmed / false positive / needs-context
- Collect confirmed findings with severity, location, effort, recommendation
- Calculate score per
references/audit_scoring.md - Write Report -- build in memory, write to
{output_dir}/ln-628--global.md(atomic single Write) - Return Summary
Audit Rules
Unified severity escalation: For ALL checks -- if finding affects payment/auth/financial code -> escalate to CRITICAL regardless of other factors.
1. Async/Event-Loop Races (CWE-362)
What: Shared state corrupted across await/yield boundaries in single-threaded async code.
Layer 1 -- Grep patterns:
| Language | Pattern | Grep |
|---|---|---|
| JS/TS | Read-modify-write across await | \w+\s*[+\-*/]?=\s*.*await (e.g., result += await something) |
| JS/TS | Check-then-initialize race | if\s*\(!?\w+\) followed by \w+\s*=\s*await in same block |
| Python | Read-modify-write across await | \w+\s*[+\-*/]?=\s*await inside async def |
| Python | Shared module-level state in async | Module-level \w+\s*= + modified inside async def |
| All | Shared cache without lock | `.set( |
Layer 2 -- Critical questions:
- Is the variable shared (module/global scope) or local?
- Can two async tasks interleave at this await point?
- Is there a lock/mutex/semaphore guarding the access?
Severity: CRITICAL (payment/auth) | HIGH (user-facing) | MEDIUM (background)
Safe pattern exclusions: Local variables, const declarations, single-use await (no interleaving possible).
Effort: M
2. Thread/Goroutine Safety (CWE-366)
What: Shared mutable state accessed from multiple threads/goroutines without synchronization.
Layer 1 -- Grep patterns:
| Language | Pattern | Grep |
|---|---|---|
| Go | Map access without mutex | map\[.*\].*= in struct without sync.Mutex or sync.RWMutex |
| Go | Variable captured by goroutine | go func + variable from outer scope modified |
| Python | Global modified in threads | global\s+\w+ in function + threading.Thread in same file |
| Java | HashMap shared between threads | HashMap + Thread|Executor|Runnable in same class without synchronized|ConcurrentHashMap |
| Rust | Rc in multi-thread context | Rc<RefCell + thread::spawn|tokio::spawn in same file |
| Node.js | Worker Threads shared state | workerData|SharedArrayBuffer|parentPort + mutable access without Atomics |
Layer 2 -- Critical questions:
- Is this struct/object actually shared between threads? (single-threaded code -> FP)
- Is mutex/lock in embedded struct or imported module? (grep may miss it)
- Is
go funccapturing by value (safe) or by reference (unsafe)?
Severity: CRITICAL (payment/auth) | HIGH (data corruption possible) | MEDIUM (internal)
Safe pattern exclusions: Go map in init() or main() before goroutines start. Rust Arc<Mutex<T>> (already safe). Java Collections.synchronizedMap().
Effort: M
3. TOCTOU -- Time-of-Check Time-of-Use (CWE-367)
What: Resource state checked, then used, but state can change between check and use.
Layer 1 -- Grep patterns:
| Language | Check | Use | Grep |
|---|---|---|---|
| Python | os.path.exists() | open() | os\.path\.exists\( near open\( on same variable |
| Python | os.access() | os.open() | os\.access\( near os\.open\(|open\( |
| Node.js | fs.existsSync() | fs.readFileSync() | existsSync\( near readFileSync\(|readFile\( |
| Node.js | fs.accessSync() | fs.openSync() | accessSync\( near openSync\( |
| Go | os.Stat() | os.Open() | os\.Stat\( near os\.Open\(|os\.Create\( |
| Java | .exists() | new FileInputStream | \.exists\(\) near new File|FileInputStream|FileOutputStream |
Layer 2 -- Critical questions:
- Is the check used for control flow (vulnerable) or just logging (safe)?
- Is there a lock/retry around the check-then-use sequence?
- Is the file in a temp directory controlled by the application (lower risk)?
- Could an attacker substitute the file (symlink attack)?
Severity: CRITICAL (security-sensitive: permissions, auth tokens, configs) | HIGH (user-facing file ops) | MEDIUM (internal/background)
Safe pattern exclusions: Check inside try/catch with retry. Check for logging/metrics only. Check + use wrapped in file lock.
Effort: S-M (replace check-then-use with direct use + error handling)
4. Deadlock Potential (CWE-833)
What: Lock acquisition in inconsistent order, or lock held during blocking operation.
Layer 1 -- Grep patterns:
| Language | Pattern | Grep |
|---|---|---|
| Python | Nested locks | with\s+\w+_lock: (multiline: two different locks nested) |
| Python | Lock in loop | for.*: with \.acquire\(\) inside loop body |
| Python | Lock + external call | \.acquire\(\) followed by await|requests\.|urllib before release |
| Go | Missing defer unlock | \.Lock\(\) without defer.*\.Unlock\(\) on next line |
| Go | Nested locks | Two \.Lock\(\) calls in same function without intervening \.Unlock\(\) |
| Java | Nested synchronized | synchronized\s*\( (multiline: nested blocks with different monitors) |
| JS | Async mutex nesting | await\s+\w+\.acquire\(\) (two different mutexes in same function) |
Layer 2 -- Critical questions:
- Are these the same lock (reentrant = OK) or different locks (deadlock risk)?
- Is the lock ordering consistent across all call sites?
- Does the external call inside lock have a timeout?
Severity: CRITICAL (payment/auth) | HIGH (app freeze risk)
Safe pattern exclusions: Reentrant locks (same lock acquired twice). Locks with explicit timeout (asyncio.wait_for, tryLock).
Effort: L (lock ordering redesign)
5. Blocking I/O in Async Context (CWE-400)
What: Synchronous blocking calls inside async functions or event loop handlers.
Layer 1 -- Grep patterns:
| Language | Blocking Call | Grep | Replacement |
|---|---|---|---|
| Python | time.sleep in async def | time\.sleep inside async def | await asyncio.sleep |
| Python | `requests. |