-
Notifications
You must be signed in to change notification settings - Fork 2
Parallelize independent scanner execution for 2-3x speedup #3
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Changes from all commits
2eb0f6b
7ada8d5
0475aa1
84559d8
6232003
c22a795
78f071a
268411c
26eec65
bbacfaf
25a100b
600f012
ad08cbb
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change | ||||||||||||||||||
|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|
|
|
@@ -23,6 +23,7 @@ SHOW_FIX=0 | |||||||||||||||||||
| QUIET=0 | ||||||||||||||||||||
| NO_INTERACTIVE=0 | ||||||||||||||||||||
| REMEDIATE=0 | ||||||||||||||||||||
| PARALLEL_SCANNERS=1 | ||||||||||||||||||||
| CONFIG_DIR="" | ||||||||||||||||||||
|
|
||||||||||||||||||||
| # ─── Usage ─────────────────────────────────────────────────────────────────── | ||||||||||||||||||||
|
|
@@ -36,6 +37,7 @@ Options: | |||||||||||||||||||
| --json Output findings as JSON array only | ||||||||||||||||||||
| --fix Show auto-fix commands in report | ||||||||||||||||||||
| --quiet Print summary line only | ||||||||||||||||||||
| --sequential Run scanners sequentially (default is parallel) | ||||||||||||||||||||
| --no-interactive Disable interactive post-scan menu | ||||||||||||||||||||
| --remediate Run scan then pipe findings to Claude for AI remediation | ||||||||||||||||||||
| --config-dir PATH Explicit path to openclaw config directory | ||||||||||||||||||||
|
|
@@ -56,6 +58,7 @@ while [[ $# -gt 0 ]]; do | |||||||||||||||||||
| --json) JSON_OUTPUT=1; shift ;; | ||||||||||||||||||||
| --fix) SHOW_FIX=1; shift ;; | ||||||||||||||||||||
| --quiet) QUIET=1; shift ;; | ||||||||||||||||||||
| --sequential) PARALLEL_SCANNERS=0; shift ;; | ||||||||||||||||||||
| --no-interactive) NO_INTERACTIVE=1; shift ;; | ||||||||||||||||||||
| --remediate) REMEDIATE=1; NO_INTERACTIVE=1; shift ;; | ||||||||||||||||||||
| --config-dir) | ||||||||||||||||||||
|
|
@@ -110,6 +113,63 @@ if [[ "$JSON_OUTPUT" -eq 0 ]] && [[ "$QUIET" -eq 0 ]]; then | |||||||||||||||||||
| printf '\n' | ||||||||||||||||||||
| fi | ||||||||||||||||||||
|
|
||||||||||||||||||||
| # ─── Parallel scanner execution function ──────────────────────────────────── | ||||||||||||||||||||
|
|
||||||||||||||||||||
| run_scanners_parallel() { | ||||||||||||||||||||
| local temp_dir="" | ||||||||||||||||||||
| temp_dir="$(mktemp -d "${TMPDIR:-/tmp}/clawpinch.XXXXXX")" | ||||||||||||||||||||
|
|
||||||||||||||||||||
| # Use RETURN trap for cleanup — fires when function returns, doesn't | ||||||||||||||||||||
| # interfere with main script's own traps or Ctrl+C handling | ||||||||||||||||||||
| trap 'rm -rf "$temp_dir"' RETURN | ||||||||||||||||||||
|
|
||||||||||||||||||||
| # Track background job PIDs | ||||||||||||||||||||
| declare -a pids=() | ||||||||||||||||||||
|
|
||||||||||||||||||||
| # Launch all scanners in parallel | ||||||||||||||||||||
| for scanner in "${scanners[@]}"; do | ||||||||||||||||||||
| local scanner_name="$(basename "$scanner")" | ||||||||||||||||||||
| local temp_file="$temp_dir/${scanner_name}.json" | ||||||||||||||||||||
|
|
||||||||||||||||||||
| # Run scanner in background, redirecting output to temp file | ||||||||||||||||||||
| ( | ||||||||||||||||||||
| # Initialize with empty array in case scanner fails to run | ||||||||||||||||||||
| echo '[]' > "$temp_file" | ||||||||||||||||||||
|
|
||||||||||||||||||||
| # Run scanner - exit code doesn't matter, we just need valid JSON output | ||||||||||||||||||||
| # (Scanners exit with code 1 when they find critical findings, but still output valid JSON) | ||||||||||||||||||||
| # Use command -v instead of has_cmd — bash functions aren't inherited by subshells | ||||||||||||||||||||
| if [[ "$scanner" == *.sh ]]; then | ||||||||||||||||||||
| bash "$scanner" > "$temp_file" 2>/dev/null || true | ||||||||||||||||||||
| elif [[ "$scanner" == *.py ]]; then | ||||||||||||||||||||
| # Python 3 only — scanners use f-strings and type hints that fail under Python 2 | ||||||||||||||||||||
| if command -v python3 &>/dev/null; then | ||||||||||||||||||||
| python3 "$scanner" > "$temp_file" 2>/dev/null || true | ||||||||||||||||||||
| else | ||||||||||||||||||||
| echo "WARN: skipping $scanner_name (python3 not found)" >&2 | ||||||||||||||||||||
| fi | ||||||||||||||||||||
| fi | ||||||||||||||||||||
| ) & | ||||||||||||||||||||
|
|
||||||||||||||||||||
| pids+=("$!") | ||||||||||||||||||||
| done | ||||||||||||||||||||
|
|
||||||||||||||||||||
| # Wait for all background jobs to complete | ||||||||||||||||||||
| for pid in "${pids[@]}"; do | ||||||||||||||||||||
| wait "$pid" 2>/dev/null || true | ||||||||||||||||||||
| done | ||||||||||||||||||||
|
|
||||||||||||||||||||
| # Merge all JSON outputs in a single jq command (avoids N jq calls in a loop) | ||||||||||||||||||||
| local json_files=("$temp_dir"/*.json) | ||||||||||||||||||||
| if [[ -e "${json_files[0]}" ]]; then | ||||||||||||||||||||
| ALL_FINDINGS="$(jq -s 'add' "${json_files[@]}" 2>/dev/null)" || ALL_FINDINGS="[]" | ||||||||||||||||||||
|
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. In the newly added In contrast, the sequential execution mode (lines 269-276) validates each scanner's output individually and provides warnings if a scanner fails to produce valid JSON. The parallel mode should adopt a similar robust approach to ensure that a single failure does not suppress all other security alerts.
Comment on lines
+163
to
+165
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Glob expansion not quoted - will fail if no If all scanners fail or produce no output,
Suggested change
Use command substitution fallback for Prompt To Fix With AIThis is a comment left during a code review.
Path: clawpinch.sh
Line: 160:162
Comment:
Glob expansion not quoted - will fail if no `.json` files exist.
If all scanners fail or produce no output, `"$temp_dir"/*.json` expands to literal string `*.json`, causing `jq` to fail silently.
```suggestion
local json_files=("$temp_dir"/*.json)
if [[ -e "${json_files[0]}" ]]; then
ALL_FINDINGS="$(jq -s 'add' "${json_files[@]}" 2>/dev/null || echo '[]')"
else
ALL_FINDINGS="[]"
fi
```
Use command substitution fallback for `jq` failure.
How can I resolve this? If you propose a fix, please make it concise. |
||||||||||||||||||||
| else | ||||||||||||||||||||
| ALL_FINDINGS="[]" | ||||||||||||||||||||
| fi | ||||||||||||||||||||
|
|
||||||||||||||||||||
| # Temp directory cleaned up by RETURN trap | ||||||||||||||||||||
| } | ||||||||||||||||||||
|
|
||||||||||||||||||||
| # ─── Discover scanner scripts ─────────────────────────────────────────────── | ||||||||||||||||||||
|
|
||||||||||||||||||||
| scanners=() | ||||||||||||||||||||
|
|
@@ -143,7 +203,32 @@ _SPINNER_PID="" | |||||||||||||||||||
| # Record scan start time | ||||||||||||||||||||
| _scan_start="${EPOCHSECONDS:-$(date +%s)}" | ||||||||||||||||||||
|
|
||||||||||||||||||||
| for scanner in "${scanners[@]}"; do | ||||||||||||||||||||
| # ─── Execute scanners (parallel or sequential) ────────────────────────────── | ||||||||||||||||||||
|
|
||||||||||||||||||||
| if [[ "$PARALLEL_SCANNERS" -eq 1 ]]; then | ||||||||||||||||||||
| # Parallel execution | ||||||||||||||||||||
| if [[ "$JSON_OUTPUT" -eq 0 ]] && [[ "$QUIET" -eq 0 ]]; then | ||||||||||||||||||||
| start_spinner "Running ${scanner_count} scanners in parallel..." | ||||||||||||||||||||
| fi | ||||||||||||||||||||
|
|
||||||||||||||||||||
| # Record parallel execution start time | ||||||||||||||||||||
| _parallel_start="${EPOCHSECONDS:-$(date +%s)}" | ||||||||||||||||||||
|
|
||||||||||||||||||||
| run_scanners_parallel | ||||||||||||||||||||
|
|
||||||||||||||||||||
| # Calculate parallel execution elapsed time | ||||||||||||||||||||
| _parallel_end="${EPOCHSECONDS:-$(date +%s)}" | ||||||||||||||||||||
| _parallel_elapsed=$(( _parallel_end - _parallel_start )) | ||||||||||||||||||||
|
|
||||||||||||||||||||
| # Count findings from merged results | ||||||||||||||||||||
| _parallel_count="$(echo "$ALL_FINDINGS" | jq 'length')" | ||||||||||||||||||||
|
|
||||||||||||||||||||
| if [[ "$JSON_OUTPUT" -eq 0 ]] && [[ "$QUIET" -eq 0 ]]; then | ||||||||||||||||||||
| stop_spinner "Parallel scan" "$_parallel_count" "$_parallel_elapsed" | ||||||||||||||||||||
| fi | ||||||||||||||||||||
| else | ||||||||||||||||||||
| # Sequential execution | ||||||||||||||||||||
| for scanner in "${scanners[@]}"; do | ||||||||||||||||||||
| scanner_idx=$((scanner_idx + 1)) | ||||||||||||||||||||
| scanner_name="$(basename "$scanner")" | ||||||||||||||||||||
| scanner_base="${scanner_name%.*}" | ||||||||||||||||||||
|
|
@@ -200,7 +285,8 @@ for scanner in "${scanners[@]}"; do | |||||||||||||||||||
| if [[ "$JSON_OUTPUT" -eq 0 ]] && [[ "$QUIET" -eq 0 ]]; then | ||||||||||||||||||||
| stop_spinner "$local_name" "$local_count" "$_scanner_elapsed" | ||||||||||||||||||||
| fi | ||||||||||||||||||||
| done | ||||||||||||||||||||
| done | ||||||||||||||||||||
| fi | ||||||||||||||||||||
|
|
||||||||||||||||||||
| # Calculate total scan time | ||||||||||||||||||||
| _scan_end="${EPOCHSECONDS:-$(date +%s)}" | ||||||||||||||||||||
|
|
||||||||||||||||||||
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Claimed "1.5-3x faster" speedup doesn't match "2-3x faster" from title and line 79.
Use consistent speedup claims throughout documentation.
Prompt To Fix With AI