java-topology/tools/scan-cwe407.sh
russell@unturf.com db29a08762 undefect. CWE-407 — 92 sites, 42 ecosystems
B&W print-friendly diagrams + tinkerpop-0001 + wave-3 proof sections.
Squash of 94 local commits onto remote master.
2026-03-26 19:48:18 -04:00

91 lines
3.8 KiB
Bash
Executable file

#!/usr/bin/env bash
# CWE-407 scanner: O(n) linear membership checks in graph traversal code.
#
# Pattern: Any call to a linear-scan membership check (.contains, "in list",
# std::find, etc.) inside a function whose name suggests graph traversal
# (tarjan, scc, dfs, topo, cycle, closure, reachab*).
#
# Usage:
# scan-cwe407.sh <repo-root> [file-glob]
#
# Output: TSV — file:line | function-context | snippet | verdict
#
# Supports: .java .py .kt .scala .cpp .h .c .go .rs .rb .js .ts .cs .swift
set -euo pipefail
ROOT="${1:-.}"
GLOB="${2:-}"
# ── Membership check patterns per language ─────────────────────────────────────
JAVA_MEM='\.contains\s*\(|List\.contains|ArrayList\.contains|LinkedList\.contains|Deque\.contains|ArrayDeque\.contains'
PY_MEM='[^a-z]in (stack|path|visited|seen|queue|nodes)|path\.append|\.contains\('
KT_MEM='\.contains\s*\(|in\s+(?:stack|path|visited|list|nodes)\b'
SCALA_MEM='\.contains\s*\(|\.exists\s*\{|List\.contains|Seq\.contains|ArrayBuffer\.contains'
CPP_MEM='std::find[(]|[.]count[(]|linear_search|find.*begin.*end'
GO_MEM='slices\.Contains|contains\s*\(.*\[\]|for.*range.*==\s*\w'
RS_MEM='\.contains\s*\(&|iter\(\)\.any\s*\(|\.position\s*\('
# ── Graph/traversal function name patterns ─────────────────────────────────────
GRAPH_FN='tarjan|scc|strongly.connected|dfs|bfs|topolog|topo.sort|cycle|closure|reachab|postorder|preorder|dominator|liveness|worklist|scfg|cfg'
# ── Scan a single file ─────────────────────────────────────────────────────────
scan_file() {
local f="$1"
local ext="${f##*.}"
local mem_pat=""
case "$ext" in
java) mem_pat="$JAVA_MEM" ;;
py) mem_pat="$PY_MEM" ;;
kt|kts) mem_pat="$KT_MEM" ;;
scala) mem_pat="$SCALA_MEM" ;;
cpp|cc|cxx|h|hpp|c) mem_pat="$CPP_MEM" ;;
go) mem_pat="$GO_MEM" ;;
rs) mem_pat="$RS_MEM" ;;
*) return ;;
esac
# Two-pass: find graph-related functions first, then check for membership
# within ±20 lines of any match.
local graph_lines
graph_lines=$(grep -inE "$GRAPH_FN" "$f" 2>/dev/null | cut -d: -f1 || true)
[ -z "$graph_lines" ] && return
while IFS= read -r lineno; do
local start=$(( lineno - 20 ))
local end=$(( lineno + 20 ))
[ $start -lt 1 ] && start=1
# Check if any membership pattern appears in the window
local hit
hit=$(sed -n "${start},${end}p" "$f" 2>/dev/null \
| grep -iE "$mem_pat" 2>/dev/null || true)
if [ -n "$hit" ]; then
echo -e "CANDIDATE\t${f}:${lineno}\t$(echo "$hit" | head -1 | sed 's/^\s*//')"
fi
done <<< "$graph_lines"
}
export -f scan_file
export JAVA_MEM PY_MEM KT_MEM SCALA_MEM CPP_MEM GO_MEM RS_MEM GRAPH_FN
# ── Find files ─────────────────────────────────────────────────────────────────
echo "# CWE-407 scan: $ROOT"
echo "# $(date -u)"
echo "# file:line | snippet"
echo "#"
if [ -n "$GLOB" ]; then
find "$ROOT" -name "$GLOB" -type f
else
find "$ROOT" -type f \( \
-name "*.java" -o -name "*.py" -o -name "*.kt" -o -name "*.scala" \
-o -name "*.cpp" -o -name "*.cc" -o -name "*.c" -o -name "*.h" \
-o -name "*.go" -o -name "*.rs" \
\) -not -path "*/.git/*" -not -path "*/build/*" -not -path "*/target/*"
fi | sort | while IFS= read -r f; do
scan_file "$f"
done
echo "#"
echo "# Scan complete."