Skip to content
Back to skills

Phy Concurrency Audit

ASecurity

Static concurrency and race-condition auditor for Go, Java, Python, Node.js/TypeScript. Detects shared-state mutations without locks (Go map races, Java non-atomic increments, Python thread/asyncio shared lists), TOCTOU (time-of-check-time-of-use) patterns across all languages, sync.Mutex copied by value, WaitGroup.Add inside goroutines, SimpleDateFormat as instance field, double-checked locking without volatile, asyncio shared state without Lock. Maps findings to CWE-362 (race condition) and...

  • 2,128 stars
  • 0 votes
  • 0 copies
  • 3 views
  • Added June 6, 2026
ai-agentstypescriptpythongojavabashnodenodejstestinggitapi

Works with

  • cli
  • api

Security analysis

A100/100

Pro scans all 2 files and shows the line behind each finding

Scanned June 6, 2026

npx -y skills add LeoYeAI/openclaw-master-skills --skill phy-concurrency-audit --agent claude-code

Installs into .claude/skills of the current project.

Are you the author of Phy Concurrency Audit?

Add the live security badge to your README. It updates with every re-scan.

Security grade badge for Phy Concurrency Audit
[![Security: A — Skills Directory](https://www.skillsdirectory.com/api/skills/leoyeai-phy-concurrency-audit/badge)](https://www.skillsdirectory.com/skills/leoyeai-phy-concurrency-audit)

More formats (shields.io, HTML) on the badges page. Keep it an A: scan every change in CI with Pro.

Download with Pro
SKILL.md
---
name: phy-concurrency-audit
description: Static concurrency and race-condition auditor for Go, Java, Python, Node.js/TypeScript. Detects shared-state mutations without locks (Go map races, Java non-atomic increments, Python thread/asyncio shared lists), TOCTOU (time-of-check-time-of-use) patterns across all languages, sync.Mutex copied by value, WaitGroup.Add inside goroutines, SimpleDateFormat as instance field, double-checked locking without volatile, asyncio shared state without Lock. Maps findings to CWE-362 (race condition) and CWE-367 (TOCTOU). Zero competitors on ClawHub — not a single concurrency-audit SKILL.md in 13,700+ files.
license: Apache-2.0
tags:
  - concurrency
  - race-condition
  - toctou
  - go
  - java
  - python
  - nodejs
  - thread-safety
  - correctness
metadata:
  author: PHY041
  version: "1.0.0"
---

# 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, "CWE-362",
         "SimpleDateFormat as instance/static field — not thread-safe",
         "SimpleDateFormat maintains mutable state internally — concurrent use causes wrong dates or exceptions.",
         "Use ThreadLocal<SimpleDateFormat> or replace with thread-safe DateTimeFormatter (Java 8+)."),

        ("JAVA_DOUBLE_CHECKED_LOCKING",
         re.compile(r'if\s*\(\s*\w+\s*==\s*null\s*\)\s*\{\s*synchronized\s*\('),
         HIGH, "CWE-362",
         "Double-checked locking without volatile — broken without memory barrier",
         "Without volatile, the JIT can reorder writes; the second thread sees a partially constructed object.",
         "Add 'volatile' to the field declaration: private volatile MyClass instance;"),

        ("JAVA_SYNC_ON_NONFINAL",
         re.compile(r'synchronized\s*\(\s*(?!this|class)\w+\s*\)'),
         MEDIUM, "CWE-362",
         "synchronized() on potentially non-final field — lock can change",
         "If the locked object's reference changes between synchronized blocks, protection is lost.",
         "Only synchronize on final fields, 'this', or class literals."),
    ],
    ".py": [
        ("PY_THREAD_GLOBAL_MUTATION",
         re.compile(r'def\s+\w+\s*\([^)]*\)\s*:\s*(?:[^\n]*\n\s*)*?global\s+\w+', re.MULTILINE),
         HIGH, "CWE-362",
         "Thread function using 'global' — likely shared state without lock",
         "Global mutations in threads (counter += 1, list.append) are not atomic in CPython despite the GIL.",
         "Protect with threading.Lock(): with lock: global_var += 1"),

        ("PY_THREAD_LIST_MUTATION",
         re.compile(r'threading\.Thread\s*\([^)]*target\s*=\s*\w+'),
         MEDIUM, "CWE-362",
         "threading.Thread — verify shared list/dict access is protected by threading.Lock()",
         "Compound operations on shared containers (check-then-modify) are not atomic under GIL.",
         "Use threading.Lock() or queue.Queue for thread-safe producer/consumer patterns."),

        ("PY_ASYNCIO_SHARED_MUTATION",
         re.compile(r'async\s+def\s+\w+.*?(?:\n.*?){0,10}(?:shared|cache|results|state)\s*[\[.]=', re.DOTALL),
         HIGH, "CWE-362",
         "asyncio coroutine mutating shared variable — TOCTOU risk at await boundaries",
         "Between two await points, another coroutine can run and modify the same shared state.",
         "Use asyncio.Lock() to protect read-modify-write sequences across await boundaries."),

        ("PY_TOCTOU_EXISTS_OPEN",
         re.compile(r'os\.path\.exists\s*\([^)]+\)(?:[^\n]*\n){0,5}[^\n]*\bopen\s*\(', re.MULTILINE),
         HIGH, "CWE-367",
         "TOCTOU: os.path.exists() followed by open() — race window for file swap",
         "An attacker can replace the file with a symlink between exists() check and open() call.",
         "Remove exists() check; use try/except FileNotFoundError on open() directly (atomic)."),

        ("PY_TOCTOU_ISDIR_MAKEDIRS",
         re.compile(r'os\.path\.isdir\s*\([^)]+\)(?:[^\n]*\n){0,5}[^\n]*os\.makedirs\s*\('),
         MEDIUM, "CWE-367",
         "TOCTOU: isdir() + makedirs() — another process can create dir between checks",
         "Race causes FileExistsError even though the directory now exists.",
         "Use os.makedirs(path, exist_ok=True) — single atomic operation."),

        ("PY_TOCTOU_EXISTS_RENAME",
         re.compile(r'os\.path\.exists\s*\([^)]+\)(?:[^\n]*\n){0,5}[^\n]*(?:os\.rename|os\.remove|os\.chmod|shutil\.move)\s*\('),
         HIGH, "CWE-367",
         "TOCTOU: exists() check then file operation — file can be replaced between calls",
         "Symlink attack: attacker replaces target file with symlink to sensitive file before chmod/rename.",
         "Use try/except on the operation directly; or use os.open() with O_NOFOLLOW flag for security-sensitive ops."),
    ],
    ".js": _build_js_concurrency_patterns(),
    ".ts": _build_js_concurrency_patterns(),
}

def _build_js_concurrency_patterns():
    return [
        ("JS_MODULE_STATE_ASYNC_MUTATION",
         re.compile(r'(?:const|let|var)\s+\w+\s*=\s*(?:\{|\[)(?:[^;]*\n){0,5}.*?(?:app|router|server)\.[a-z]+\s*\(.*?\+\+|\s+\w+\.push\(|\s+\w+\[', re.DOTALL),
         MEDIUM, "CWE-362",
         "Module-level state mutated in request handler — concurrent requests race",
         "Node.js is single-threaded but async: two requests can interleave at await points, corrupting shared state.",
         "Move state into request scope, use a database/cache, or protect with async-mutex library."),

        ("JS_TOCTOU_CACHE_HAS_GET",
         re.compile(r'await\s+\w+\.has\s*\([^)]+\)(?:[^\n]*\n){0,5}[^\n]*await\s+\w+\.get\s*\('),
         HIGH, "CWE-367",
         "TOCTOU: await cache.has() followed by await cache.get() — key can be deleted between awaits",
         "Another async operation can delete the cache key after has() returns true but before get().",
         "Use atomic getOrCreate pattern: const val = cache.get(key) ?? (await compute()); cache.set(key, val);"),

        ("JS_COUNTER_ASYNC",
         re.compile(r'(?:let|var)\s+\w*(?:count|counter|total|hits)\w*\s*=\s*0(?:[^;]*\n){0,20}.*?\+\+', re.DOTALL),
         MEDIUM, "CWE-362",
         "Counter incremented across async boundaries — not atomic",
         "count++ is not atomic when two requests execute counter++ concurrently at an await boundary.",
         "Use a single-tick increment (no await between read and write), or use an atomic counter library."),

        ("JS_TOCTOU_FS_EXISTS_WRITE",
         re.compile(r'(?:fs\.existsSync|await\s+fs\.(?:promises\.)?access)\s*\([^)]+\)(?:[^\n]*\n){0,5}[^\n]*(?:fs\.writeFileSync|await\s+fs\.(?:promises\.)?writeFile)\s*\('),
         HIGH, "CWE-367",
         "TOCTOU: fs.existsSync + fs.writeFile — race between check and write",
         "Another process can create or modify the file between existsSync() and writeFile().",
         "Use flags: fs.writeFileSync(path, data, {flag: 'wx'}) — fails atomically if file already exists."),
    ]

# Shared-state context helpers
GO_GOROUTINE_MARKER = re.compile(r'\bgo\s+(?:func|\w+)\s*\(')
JAVA_CONCURRENT_MARKER = re.compile(r'(?:Thread|Executor|CompletableFuture|@Async|synchronized|Runnable|Callable)\b')
PY_CONCURRENT_MARKER = re.compile(r'(?:threading|asyncio|concurrent\.futures|multiprocessing)\b')
JS_ASYNC_MARKER = re.compile(r'\basync\s+function|\bPromise\.all|\bawait\b')

CONCURRENCY_MARKERS = {
    ".go": GO_GOROUTINE_MARKER,
    ".java": JAVA_CONCURRENT_MARKER,
    ".py": PY_CONCURRENT_MARKER,
    ".js": JS_ASYNC_MARKER,
    ".ts": JS_ASYNC_MARKER,
}

SKIP_DIRS = {".git", "node_modules", "vendor", "__pycache__", ".venv", "venv",
             "dist", "build", "target", "test", "tests", "__tests__", "spec"}

def scan_file(filepath: Path) -> list[Finding]:
    suffix = filepath.suffix.lower()
    if suffix not in PATTERNS:
        return []
    try:
        lines = filepath.read_text(encoding="utf-8", errors="replace").splitlines()
    except (OSError, PermissionError):
        return []

    full_text = "\n".join(lines)

    # Skip files with no concurrency markers at all (reduces false positives significantly)
    marker = CONCURRENCY_MARKERS.get(suffix)
    if marker and not marker.search(full_text):
        # Only skip for non-TOCTOU checks if truly no concurrent code
        pass  # Still scan for TOCTOU even in non-concurrent files

    findings: list[Finding] = []
    for (name, pat, base_sev, cwe, title, detail, fix) in PATTERNS[suffix]:
        for m in pat.finditer(full_text):
            lineno = full_text[:m.start()].count("\n") + 1
            line_text = lines[lineno - 1]

            findings.append(Finding(
                file=str(filepath),
                line=lineno,
                pattern_name=name,
                matched_text=line_text.strip()[:120],
                severity=base_sev,
                cwe=cwe,
                title=title,
                detail=detail,
                fix=fix,
            ))
    return findings

def walk_files(root: Path) -> list[Path]:
    exts = {".go", ".java", ".py", ".js", ".ts"}
    results = []
    for dirpath, dirnames, filenames in os.walk(root):
        dirnames[:] = [d for d in dirnames if d not in SKIP_DIRS]
        for fname in filenames:
            if Path(fname).suffix.lower() in exts:
                results.append(Path(dirpath) / fname)
    return results

def format_report(findings: list[Finding], scanned: int) -> str:
    by_sev = {CRITICAL: [], HIGH: [], MEDIUM: [], LOW: []}
    for f in findings:
        by_sev[f.severity].append(f)

    lines = [
        "━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━",
        "  CONCURRENCY AUDIT — Race Conditions & TOCTOU (CWE-362/367)",
        "━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━",
        f"  Scanned:  {scanned} files",
        f"  Findings: {len(by_sev[CRITICAL])} CRITICAL  {len(by_sev[HIGH])} HIGH  "
        f"{len(by_sev[MEDIUM])} MEDIUM  {len(by_sev[LOW])} LOW",
        "",
    ]

    for sev in [CRITICAL, HIGH, MEDIUM, LOW]:
        group = by_sev[sev]
        if not group:
            continue
        lines.append(f"{ICONS[sev]} {sev} ({len(group)} findings)")
        lines.append("")
        for f in sorted(group, key=lambda x: x.file):
            rel = os.path.relpath(f.file)
            lines += [
                f"  {rel}:{f.line} — {f.pattern_name}",
                f"  Code:   {f.matched_text}",
                f"  Issue:  {f.title}",
                f"  Why:    {f.detail}",
                f"  {f.cwe}",
                f"  Fix:    {f.fix}",
                "",
            ]

    critical_high = len(by_sev[CRITICAL]) + len(by_sev[HIGH])
    lines += [
        "━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━",
        f"  CI gate: {'exit 1 — race conditions detected' if critical_high else 'exit 0 — clean'}",
        "  Runtime verification: go test -race ./... | ThreadSanitizer",
        "━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━",
    ]
    return "\n".join(lines)

def main():
    parser = argparse.ArgumentParser(description="Race condition & TOCTOU scanner")
    parser.add_argument("path", nargs="?", default=".", help="Root directory to scan")
    parser.add_argument("--json", action="store_true")
    parser.add_argument("--ci", action="store_true", help="Exit 1 if HIGH or CRITICAL found")
    args = parser.parse_args()

    files = walk_files(Path(args.path).resolve())
    all_findings: list[Finding] = []
    for f in files:
        all_findings.extend(scan_file(f))
    all_findings.sort(key=lambda x: (SEV_ORDER[x.severity], x.file, x.line))

    if args.json:
        import dataclasses
        print(json.dumps([dataclasses.asdict(f) for f in all_findings], indent=2))
    else:
        print(format_report(all_findings, len(files)))

    if args.ci:
        has_high = any(f.severity in (CRITICAL, HIGH) for f in all_findings)
        sys.exit(1 if has_high else 0)

if __name__ == "__main__":
    main()
```

## Usage

```bash
# Scan current project
python3 audit_concurrency.py

# CI fail-gate (exits 1 on HIGH/CRITICAL)
python3 audit_concurrency.py --ci

# JSON output for pipeline integration
python3 audit_concurrency.py --json | jq '[.[] | select(.cwe == "CWE-367")]'

# GitHub Actions
- name: Concurrency Audit
  run: python3 .claude/skills/phy-concurrency-audit/audit_concurrency.py --ci
```

## Sample Output

```
━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
  CONCURRENCY AUDIT — Race Conditions & TOCTOU (CWE-362/367)
━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
  Scanned:  78 files
  Findings: 0 CRITICAL  4 HIGH  2 MEDIUM  0 LOW

🟠 HIGH (4 findings)

  internal/cache/store.go:12 — GO_GLOBAL_MAP_RACE
  Code:   var requestCache = map[string]*Response{}
  Issue:  Package-level map — likely shared across goroutines without mutex
  Why:    Go maps are NOT goroutine-safe. Concurrent read+write causes panic.
  CWE-362
  Fix:    Protect with sync.RWMutex or replace with sync.Map for concurrent access.

  src/DateParser.java:8 — JAVA_SIMPLE_DATE_FORMAT
  Code:   private static final SimpleDateFormat sdf = new SimpleDateFormat("yyyy-MM-dd");
  Issue:  SimpleDateFormat as static field — not thread-safe
  Why:    SimpleDateFormat maintains mutable state; concurrent use corrupts date parsing.
  CWE-362
  Fix:    Use ThreadLocal<SimpleDateFormat> or replace with DateTimeFormatter (Java 8+).

  api/file_handler.py:34 — PY_TOCTOU_EXISTS_OPEN
  Code:   if os.path.exists(upload_path):
  Issue:  TOCTOU: os.path.exists() followed by open() — race window for file swap
  Why:    Attacker can replace file with symlink between exists() and open().
  CWE-367
  Fix:    Remove exists() check; use try/except FileNotFoundError on open() directly.

  routes/api.js:67 — JS_TOCTOU_CACHE_HAS_GET
  Code:   if (await redisClient.has(userId)) {
  Issue:  TOCTOU: await cache.has() followed by await cache.get()
  Why:    Key can be evicted between has() and get() under concurrent load.
  CWE-367
  Fix:    Use atomic getOrCreate: const val = await cache.get(key) ?? compute();

━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
  CI gate: exit 1 — race conditions detected
  Runtime verification: go test -race ./... | ThreadSanitizer
━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
```

## Relationship to Runtime Detectors

This skill does **static** analysis — fast, zero infrastructure, no load required. Use alongside:

| Tool | What It Adds |
|------|-------------|
| `go test -race ./...` | Finds Go races that actually execute during tests |
| Java ThreadSanitizer | Runtime instrumentation for JVM races |
| Python `threading-sanitizer` | Runtime detection for CPython |
| `node --inspect` + `clinic.js` | Profiles async bottlenecks + race-prone patterns |

## Companion Skills

| Skill | Use Together For |
|-------|-----------------|
| `phy-memory-leak-detector` | Goroutine/listener leaks + race conditions — concurrent correctness sweep |
| `phy-deserialization-audit` | Security sweep: races often affect auth/session state |
| `phy-k8s-security-audit` | Race conditions in init containers + probe handlers |
| `phy-ssrf-audit` | Async SSRF: race between URL validation and fetch |

Files in this skill

  • SKILL.md23.3 KB
  • _meta.json294 B

Attribution

Is this your skill, or is something wrong with this listing? Request removal or report an issue. Author removals are honored within 72 hours.

Comments

Loading comments…

Newsletter

Get the weekly list of safe new skills, and the ones we pulled.