Phy Concurrency Audit
适用于 Go、Java、Python、Node.js/TypeScript 的静态并发和竞争条件审核器。检测无锁的共享状态突变(Go 映射竞争、Java 非原子增量、Python 线程/异步共享列表)、所有语言的 TOCTOU(检查时间-使用时间)模式、按值复制的sync.Mutex、WaitGroup.Add 内部 goroutine、SimpleDateFormat 作为实例字段、无 volatile 的双重检查锁定、无 Lock 的异步共享状态。将结果映射到 CWE-362(竞争条件)和 CWE-367 (TOCTOU)。 ClawHub 上零竞争对手——13,700 多个文件中没有一个并发审计 SKILL.md。
安装 / 下载方式
TotalClaw CLI推荐
totalclaw install totalclaw:phy041~phy-concurrency-auditcURL直接下载,无需登录
curl -fsSL https://skills.taituai.com/api/skills/totalclaw%3Aphy041~phy-concurrency-audit/file -o phy-concurrency-audit.mdGit 仓库获取源码
git clone https://github.com/openclaw/skills/commit/c4f1952a9ab2d4eedf1442d1a3e0069161ddd7ba## 概述(中文)
适用于 Go、Java、Python、Node.js/TypeScript 的静态并发和竞争条件审核器。检测无锁的共享状态突变(Go 映射竞争、Java 非原子增量、Python 线程/异步共享列表)、所有语言的 TOCTOU(检查时间-使用时间)模式、按值复制的sync.Mutex、WaitGroup.Add 内部 goroutine、SimpleDateFormat 作为实例字段、无 volatile 的双重检查锁定、无 Lock 的异步共享状态。将结果映射到 CWE-362(竞争条件)和 CWE-367 (TOCTOU)。 ClawHub 上零竞争对手——13,700 多个文件中没有一个并发审计 SKILL.md。
## 原文
# phy-concurrency-audit
Static scanner for **race conditions** (CWE-362) and **TOCTOU vulnerabilities** (CWE-367) in Go, Java, Python, and Node.js/TypeScript codebases. Finds shared-state mutations without synchronization, unsafe concurrency patterns, and check-then-act anti-patterns. Zero external API calls, zero dependencies beyond Python 3 stdlib.
## Why Concurrency Bugs Are Special
- **Non-deterministic**: reproduce only under load, on multi-core machines, or on specific OS schedulers
- **Silent corruption**: data races don't crash immediately — they corrupt state silently for hours
- **Security impact**: TOCTOU races in file ops enable symlink attacks and privilege escalation (CWE-367)
- **Hard to test**: even `go test -race` only finds races that actually execute during the test run
- Static analysis catches the ones that never trigger in testing
## What It Detects
### Go Race Conditions
| Pattern | Severity | CWE |
|---------|----------|-----|
| Global `map` variable read/written in concurrent functions | CRITICAL | CWE-362 |
| `sync.Mutex` or `sync.RWMutex` copied by value after first use | HIGH | CWE-362 |
| `sync.WaitGroup.Add()` called inside a goroutine | HIGH | CWE-362 |
| Package-level slice/map mutated without mutex | HIGH | CWE-362 |
| `atomic.Value` used without checking type consistency | MEDIUM | CWE-362 |
| Missing `sync.Once` for lazy singleton initialization | MEDIUM | CWE-362 |
| Struct fields accessed in goroutines without receiver mutex | HIGH | CWE-362 |
### Java Thread-Safety Violations
| Pattern | Severity | CWE |
|---------|----------|-----|
| `count++` / `i++` on shared instance/static field without `synchronized` or `AtomicInteger` | HIGH | CWE-362 |
| `HashMap` or `ArrayList` as instance field in class with concurrent methods | HIGH | CWE-362 |
| `SimpleDateFormat` as `static` or instance field (not thread-safe) | HIGH | CWE-362 |
| Double-checked locking without `volatile` keyword | HIGH | CWE-362 |
| `synchronized` block on non-final field (lock can change) | MEDIUM | CWE-362 |
| `Vector`/`Hashtable` used (thread-safe but deprecated, miss modern happens-before) | LOW | CWE-362 |
| `Calendar.getInstance()` result stored as field | MEDIUM | CWE-362 |
### Python Threading & Asyncio
| Pattern | Severity | CWE |
|---------|----------|-----|
| Thread function mutating module-level list/dict without `threading.Lock` | HIGH | CWE-362 |
| `counter += 1` / `list.append()` on global var in thread without lock | HIGH | CWE-362 |
| `asyncio` coroutine mutating shared object without `asyncio.Lock` | HIGH | CWE-362 |
| `multiprocessing.Process` sharing mutable list/dict without `Manager` | HIGH | CWE-362 |
| `threading.Thread` target accessing `global` without lock | HIGH | CWE-362 |
### Node.js / TypeScript Async Races
| Pattern | Severity | CWE |
|---------|----------|-----|
| Module-level object/array mutated inside `async` request handler | HIGH | CWE-362 |
| TOCTOU: `await cache.has(key)` followed by `await cache.get(key)` | HIGH | CWE-367 |
| Counter increment `count++` in async handler (non-atomic) | MEDIUM | CWE-362 |
| Promise chain modifying shared closure variable | MEDIUM | CWE-362 |
### TOCTOU Patterns (All Languages)
| Pattern | Severity | CWE |
|---------|----------|-----|
| `os.path.exists(path)` followed by `open(path)` without lock | HIGH | CWE-367 |
| `os.path.isdir(d)` then `os.makedirs(d)` without exist_ok=True | MEDIUM | CWE-367 |
| `os.path.exists` then `os.rename/remove/chmod` | HIGH | CWE-367 |
| `if not User.exists()` then `User.create()` — DB check-then-insert | HIGH | CWE-367 |
| Java `File.exists()` then `new FileOutputStream(file)` | HIGH | CWE-367 |
| Node.js `fs.existsSync()` then `fs.writeFileSync()` | HIGH | CWE-367 |
| Go `os.Stat(path)` check then `os.Open(path)` | HIGH | CWE-367 |
## Implementation
```python
#!/usr/bin/env python3
"""
phy-concurrency-audit — Race condition & TOCTOU static scanner
Usage: python3 audit_concurrency.py [path] [--json] [--ci]
"""
import argparse
import json
import os
import re
import sys
from dataclasses import dataclass
from pathlib import Path
from typing import Optional
CRITICAL, HIGH, MEDIUM, LOW = "CRITICAL", "HIGH", "MEDIUM", "LOW"
SEV_ORDER = {CRITICAL: 0, HIGH: 1, MEDIUM: 2, LOW: 3}
ICONS = {CRITICAL: "🔴", HIGH: "🟠", MEDIUM: "🟡", LOW: "⚪"}
@dataclass
class Finding:
file: str
line: int
pattern_name: str
matched_text: str
severity: str
cwe: str
title: str
detail: str
fix: str
# ─── Pattern registry ────────────────────────────────────────────────────────
PATTERNS: dict[str, list] = {
".go": [
("GO_GLOBAL_MAP_RACE",
re.compile(r'^var\s+\w+\s*=\s*(?:map\[|make\s*\(\s*map)', re.MULTILINE),
HIGH, "CWE-362",
"Package-level map — likely shared across goroutines without mutex",
"Go maps are NOT goroutine-safe. Any concurrent read+write is a data race that can panic.",
"Protect with sync.RWMutex or replace with sync.Map for concurrent access."),
("GO_MUTEX_COPY",
re.compile(r'\b(?:sync\.Mutex|sync\.RWMutex)\b'),
HIGH, "CWE-362",
"sync.Mutex/RWMutex — verify it is not copied after first use",
"Copying a Mutex after first Lock/Unlock produces a copy of a locked mutex — deadlock.",
"Always pass Mutex by pointer (*sync.Mutex) or embed in struct accessed via pointer."),
("GO_WAITGROUP_ADD_IN_GOROUTINE",
re.compile(r'go\s+func\s*\([^)]*\)\s*\{[^}]*wg\.Add\s*\(', re.DOTALL),
HIGH, "CWE-362",
"WaitGroup.Add() inside goroutine — race between Add and Wait",
"If wg.Wait() is called before wg.Add() completes, Wait returns prematurely.",
"Always call wg.Add(n) BEFORE launching goroutines, not inside them."),
("GO_ATOMIC_VALUE_MISUSE",
re.compile(r'\batomic\.Value\b'),
MEDIUM, "CWE-362",
"atomic.Value — verify consistent type across Store/Load calls",
"Storing different types in the same atomic.Value panics at runtime.",
"Ensure all Store() calls use the same concrete type; document the stored type in a comment."),
],
".java": [
("JAVA_NON_ATOMIC_INCREMENT",
re.compile(r'(?:private|protected|public|static)\s+(?:int|long|volatile\s+int|volatile\s+long)\s+\w+\s*(?:=\s*\d+)?\s*;'),
HIGH, "CWE-362",
"Shared numeric field — verify increments use AtomicInteger/AtomicLong",
"count++ on a shared field is a read-modify-write that is NOT atomic — silently loses updates.",
"Replace int count with AtomicInteger count = new AtomicInteger(0); use count.incrementAndGet()."),
("JAVA_SHARED_HASHMAP",
re.compile(r'(?:private|protected|public)\s+(?:final\s+)?HashMap\s*<'),
HIGH, "CWE-362",
"HashMap as instance field — not thread-safe for concurrent access",
"Concurrent reads+writes to HashMap can cause infinite loop (Java 7) or data loss (Java 8+).",
"Replace with ConcurrentHashMap or wrap access with synchronized blocks."),
("JAVA_SHARED_ARRAYLIST",
re.compile(r'(?:private|protected|public)\s+(?:final\s+)?ArrayList\s*<'),
HIGH, "CWE-362",
"ArrayList as instance field — not thread-safe for concurrent modification",
"Concurrent modification throws ConcurrentModificationException or silently drops elements.",
"Replace with CopyOnWriteArrayList or Collections.synchronizedList(new ArrayList<>())."),
("JAVA_SIMPLE_DATE_FORMAT",
re.compile(r'(?:private|protected|public|static)\s+(?:final\s+)?SimpleDateFormat\b'),
HIGH