From fe3a3d7d9432249329c2ca12f5e727d91cd3b7fb Mon Sep 17 00:00:00 2001 From: openhands Date: Mon, 16 Mar 2026 12:39:39 +0000 Subject: [PATCH] fix: feat: persist red-team cross-patterns in repo for continuity across runs (#853) - Move CROSS_PATTERNS_FILE from /tmp/red-team-cross-patterns.jsonl to tools/red-team/cross-patterns.jsonl (repo-tracked path) - Remove the reset (> file) at sweep start so patterns accumulate across runs - Generate a SWEEP_ID (sweep-YYYYMMDD-HHMMSS) at sweep start and stamp each new entry with sweep_id for traceability - Deduplicate on (pattern, candidate, result): entries already present in the file are skipped; intra-batch duplicates are also suppressed - Create tools/red-team/ directory with .gitkeep - Add mkdir -p guards in both scripts so the directory is created on first run Co-Authored-By: Claude Sonnet 4.6 --- scripts/harb-evaluator/red-team-sweep.sh | 38 ++++++++++++++++++++---- scripts/harb-evaluator/red-team.sh | 3 +- tools/red-team/.gitkeep | 0 3 files changed, 35 insertions(+), 6 deletions(-) create mode 100644 tools/red-team/.gitkeep diff --git a/scripts/harb-evaluator/red-team-sweep.sh b/scripts/harb-evaluator/red-team-sweep.sh index 42744df..78217e0 100755 --- a/scripts/harb-evaluator/red-team-sweep.sh +++ b/scripts/harb-evaluator/red-team-sweep.sh @@ -11,7 +11,7 @@ INJECT="$REPO_ROOT/tools/push3-transpiler/inject.sh" ATTACKS_OUT="$REPO_ROOT/onchain/script/backtesting/attacks" PROGRESS_FILE="/tmp/red-team-sweep-progress.json" MEMORY_FILE="$REPO_ROOT/tmp/red-team-memory.jsonl" -CROSS_PATTERNS_FILE="/tmp/red-team-cross-patterns.jsonl" +CROSS_PATTERNS_FILE="$REPO_ROOT/tools/red-team/cross-patterns.jsonl" SWEEP_TSV="/tmp/sweep-results.tsv" OPT_SOL="$REPO_ROOT/onchain/src/OptimizerV3.sol" TIMEOUT_PER="${1:-3600}" @@ -22,9 +22,11 @@ die() { log "FATAL: $*" >&2; exit 1; } [[ -f "$INJECT" ]] || die "inject.sh not found at $INJECT" mkdir -p "$ATTACKS_OUT" +mkdir -p "$(dirname "$CROSS_PATTERNS_FILE")" -# Reset cross-patterns file for this sweep invocation (prevents stale data from prior runs) -> "$CROSS_PATTERNS_FILE" +# Generate a unique sweep ID for traceability across runs +SWEEP_ID="sweep-$(date -u +%Y%m%d-%H%M%S)" +log "Sweep ID: $SWEEP_ID" # Load progress completed=() @@ -217,11 +219,12 @@ PYEOF # 4c. Extract abstract patterns into cross-candidate file, then clear raw memory if [[ -f "$MEMORY_FILE" && -s "$MEMORY_FILE" ]]; then set +e - _extract_out=$(python3 - "$MEMORY_FILE" "$CROSS_PATTERNS_FILE" <<'PYEOF' + _extract_out=$(python3 - "$MEMORY_FILE" "$CROSS_PATTERNS_FILE" "$SWEEP_ID" <<'PYEOF' import json, sys mem_file = sys.argv[1] cross_file = sys.argv[2] +sweep_id = sys.argv[3] if len(sys.argv) > 3 else "unknown" new_entries = [] with open(mem_file) as f: @@ -237,11 +240,36 @@ if not new_entries: print("No memory entries to extract") sys.exit(0) +# Load existing (pattern, candidate, result) keys for deduplication +existing_keys = set() +try: + with open(cross_file) as f: + for line in f: + line = line.strip() + if line: + try: + e = json.loads(line) + existing_keys.add((e.get("pattern", ""), e.get("candidate", ""), e.get("result", ""))) + except Exception: + pass +except FileNotFoundError: + pass + +appended = 0 +skipped = 0 with open(cross_file, 'a') as f: for e in new_entries: + key = (e.get("pattern", ""), e.get("candidate", ""), e.get("result", "")) + if key in existing_keys: + skipped += 1 + continue + e["sweep_id"] = sweep_id + existing_keys.add(key) # prevent intra-batch duplicates f.write(json.dumps(e) + '\n') + appended += 1 -print(f"Extracted {len(new_entries)} entr{'y' if len(new_entries)==1 else 'ies'} to cross-patterns file") +total = appended + skipped +print(f"Extracted {appended} new entr{'y' if appended==1 else 'ies'} ({skipped} duplicate{'s' if skipped!=1 else ''} skipped) to cross-patterns file") PYEOF ) _py_exit=$? diff --git a/scripts/harb-evaluator/red-team.sh b/scripts/harb-evaluator/red-team.sh index 9f27438..607b134 100755 --- a/scripts/harb-evaluator/red-team.sh +++ b/scripts/harb-evaluator/red-team.sh @@ -26,7 +26,7 @@ REPORT_DIR="$REPO_ROOT/tmp" REPORT="$REPORT_DIR/red-team-report.txt" STREAM_LOG="$REPORT_DIR/red-team-stream.jsonl" MEMORY_FILE="$REPO_ROOT/tmp/red-team-memory.jsonl" -CROSS_PATTERNS_FILE="/tmp/red-team-cross-patterns.jsonl" +CROSS_PATTERNS_FILE="$REPO_ROOT/tools/red-team/cross-patterns.jsonl" ATTACK_EXPORT="$REPORT_DIR/red-team-attacks.jsonl" ATTACK_SNAPSHOTS="$REPORT_DIR/red-team-snapshots.jsonl" DEPLOYMENTS="$REPO_ROOT/onchain/deployments-local.json" @@ -581,6 +581,7 @@ PROMPT=${PROMPT//\{\{MEMORY_SECTION\}\}/$MEMORY_SECTION} # ── 7. Create output directory and run the agent ─────────────────────────────── mkdir -p "$REPORT_DIR" mkdir -p "$(dirname "$MEMORY_FILE")" +mkdir -p "$(dirname "$CROSS_PATTERNS_FILE")" log "Spawning Claude red-team agent (timeout: ${CLAUDE_TIMEOUT}s)..." log " Report will be written to: $REPORT" diff --git a/tools/red-team/.gitkeep b/tools/red-team/.gitkeep new file mode 100644 index 0000000..e69de29