Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
22 changes: 17 additions & 5 deletions .github/workflows/ci.yml
Original file line number Diff line number Diff line change
Expand Up @@ -12,8 +12,8 @@
#
# TRIGGERS:
# - Pull requests to main/development branches
# - Push to development branch (for CI monitoring/iteration)
# - Manual runs via workflow_dispatch
# - Does NOT run on push to reduce CI noise
#
# DO NOT create additional workflow files for:
# - Slack notifications (already integrated here)
Expand All @@ -27,18 +27,25 @@
# - Removed: performance-audit-slack.yml
# - Removed: performance-audit-slack-on-failure.yml
# - Kept: ci.yml (this file) with all functionality merged
# - Triggers: Only on PRs (not on push) to reduce CI noise
# - Triggers: PRs + push to development (for CI iteration)
#
# ============================================================================
name: CI

on:
push:
branches:
- development
pull_request:
branches:
- main
- development
workflow_dispatch: # Allow manual triggers

# Opt into Node.js 24 for actions (Node.js 20 deprecated, forced June 2 2026)
env:
FORCE_JAVASCRIPT_ACTIONS_TO_NODE24: true

# Prevent duplicate runs: use branch name as concurrency key
concurrency:
group: ${{ github.workflow }}-${{ github.head_ref || github.ref }}
Expand All @@ -47,14 +54,15 @@ concurrency:
jobs:
performance-checks:
name: Performance Checks
runs-on: ubuntu-latest
runs-on: macos-latest
timeout-minutes: 10

steps:
- name: Checkout code
uses: actions/checkout@v4

- name: Install jq (for JSON processing and Slack)
run: sudo apt-get update && sudo apt-get install -y jq
run: command -v jq >/dev/null 2>&1 || brew install jq

- name: Make scripts executable
run: |
Expand All @@ -68,10 +76,14 @@ jobs:
echo "Running performance audit on toolkit repository..."

# Run in JSON mode for Slack integration
# --skip-magic-strings: prevents CI hang (Magic String Detector times out on large repos)
# --paths "dist/bin dist/lib": skip test fixtures which contain intentional antipatterns
./dist/bin/check-performance.sh \
--paths "." \
--paths "dist/bin dist/lib" \
--format json \
--strict \
--skip-magic-strings \
--no-log \
> audit-results.json && EXIT_CODE=0 || EXIT_CODE=$?

echo "exit_code=$EXIT_CODE" >> $GITHUB_OUTPUT
Expand Down
2 changes: 1 addition & 1 deletion .github/workflows/wp-performance.yml
Original file line number Diff line number Diff line change
Expand Up @@ -36,7 +36,7 @@ on:
jobs:
grep-checks:
name: Performance Pattern Detection
runs-on: ubuntu-latest
runs-on: macos-latest

steps:
- name: Checkout code
Expand Down
4 changes: 1 addition & 3 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -6,9 +6,7 @@
# ============================================

# Log files from scans (may contain file paths and code snippets)
dist/logs/*.log
dist/logs/*.json
!dist/logs/.gitkeep
dist/logs/

# HTML reports from scans (may contain proprietary code)
dist/reports/*.html
Expand Down
42 changes: 42 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,48 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0

## [Unreleased]

### Added

- Added Phase 1 `nonstandard-wordpress-translation-alias` reliability rule
- Detects non-standard `_()` translation alias usage in PHP files
- Recommends WordPress-native helpers like `__()`, `esc_html__()`, and `esc_attr__()` based on output context
- Adds fixture coverage for both the ACF-style Launchpad-shaped case and a standalone baseline `_()` call
- Adds safe fixture coverage proving standard WordPress i18n helpers do not trigger the rule

### Documentation

- Added `PROJECT/1-INBOX/PATTERN-PROPOSAL-LAUNCHPAD-CRASH.md`
- Captures the plan for converting the Launchpad crash lessons into generalized WPCC anti-pattern proposals
- Recommends against a single environment-specific "crash detector"
- Prioritizes `_()` alias detection first, bootstrap/file-scope side-effect detection second, and attribute-context helper misuse as experimental follow-up

### Fixed

#### Phase 0: Timeout Guards for Unprotected Recursive Grep Calls

- **Wrapped 8 raw `grep -r` file-discovery calls with `run_with_timeout "$MAX_SCAN_TIME"`**
- These one-off check sections lacked any timeout protection and could stall indefinitely
- Aggregated pattern detection (Magic String Detector) was already protected; these checks were not
- **Affected checks:**
- `AJAX_FILES` β€” wp_ajax handlers without nonce validation (line ~4216)
- `TERMS_FILES` β€” get_terms without number limit (line ~4617)
- `CRON_FILES` β€” Unvalidated cron intervals (line ~5024)
- `N1_FILES` β€” N+1 meta-in-loop patterns (line ~5271, pipeline: timeout wraps first recursive grep)
- `THANKYOU_CONTEXT_FILES` β€” WooCommerce coupon logic in thank-you context (line ~5463)
- `SMART_COUPONS_FILES` β€” WooCommerce Smart Coupons detection (line ~5554)
- `PERF_RISK_FILES` β€” WooCommerce Smart Coupons performance risk (line ~5566)
- `JSON_RESPONSE_FILES` β€” HTML-escaping in JSON response URL fields (line ~5633)
- **Behavior on timeout:** Check returns empty result, reports "passed," scan continues
- **Impact:** Eliminates "apparent hang" reports on small/medium repositories where a single check stalled
- **No new functions or abstractions** β€” reuses existing `run_with_timeout` infrastructure

### Technical Details

- **File Modified:** `dist/bin/check-performance.sh`
- **Pattern:** Each change is `run_with_timeout "$MAX_SCAN_TIME"` prefixed before an existing `grep -rl` / `grep -rln` / `grep -rlE` command
- **Remaining raw `grep -r`:** Only inside `fast_grep()` and `cached_grep()` fallback paths (by design β€” these are the wrapper-internal fallbacks for JS-only projects with no PHP file cache)
- **Related:** See `PROJECT/1-INBOX/FEATURE-SEMGREP-MIGRATION-PLAN.md` for full Phase 0-3 roadmap

---

## [2.2.5] - 2026-02-07
Expand Down
192 changes: 192 additions & 0 deletions PROJECT/1-INBOX/FEATURE-SEMGREP-MIGRATION-PLAN.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,192 @@
# Semgrep Migration and Search Backend Stabilization

**Created:** 2026-02-10
**Status:** Phase 0a Complete
**Priority:** High
**Last Updated:** 2026-02-09

## Problem/Request
Intermittent scan stalls occur on very large repositories (expected risk) and sometimes on smaller projects (unexpected). The current scanner mixes cached and uncached recursive search paths, with several raw `grep -r*` and `xargs grep` call sites that can still cause unstable runtime behavior.

## Context
- Scanner entrypoint: `dist/bin/check-performance.sh`
- Current architecture already has useful safeguards:
- `MAX_SCAN_TIME`
- `run_with_timeout()`
- `cached_grep()`
- `.wpcignore` support
- `--skip-magic-strings`
- Remaining high-risk hotspots still use raw recursive grep or raw xargs patterns:
- `dist/bin/check-performance.sh:2617`
- `dist/bin/check-performance.sh:3222`
- `dist/bin/check-performance.sh:4216`
- `dist/bin/check-performance.sh:4617`
- `dist/bin/check-performance.sh:5024`
- `dist/bin/check-performance.sh:5271`
- `dist/bin/check-performance.sh:5463`
- `dist/bin/check-performance.sh:5554`
- `dist/bin/check-performance.sh:5633`

## Direct Answer: Improvements Possible on Raw Recursive/Xargs Paths
Yes. The most impactful improvements are:
- Replace raw `grep -r*` call sites with one wrapper that enforces timeout, exclusion handling, and consistent output parsing.
- Remove `cat file | xargs grep` patterns in favor of null-delimited input (`tr '\n' '\0' | xargs -0`) or direct file iteration.
- Add per-check elapsed-time logging and heartbeat progress output for long-running loops.
- Precompute extension-specific file lists once (PHP/JS/TS) and reuse them across checks.
- Add a global fail-safe per phase so a single problematic check degrades gracefully instead of appearing hung.

## 4-Phase Plan

### Phase 0: Quick Wins (Current System, Low-Medium Effort)
**Goal:** Reduce hangs quickly without changing core detection architecture.
**Status:** βœ… Phase 0a Complete | Phase 0b Deferred

**Scope**
- Keep Bash + current pattern system.
- Patch unstable search call sites and observability gaps.

**Tasks**
1. ~~Replace known raw recursive/xargs hotspots with safer cached/wrapped calls.~~ β†’ Refined: xargs calls at lines 2617/3222 are intentionally using pre-cached file lists inside already-protected paths β€” not actual hotspots. The real issue was 8 unprotected `grep -rl` file-discovery calls.
2. Standardize null-delimited file handling for all multi-file grep execution. β†’ Deferred (existing `cached_grep` already uses `tr '\n' '\0' | xargs -0`; the 8 patched calls are file-discovery, not line-matching)
3. βœ… **Ensure every expensive check uses timeout guards.** β€” Complete (2026-02-09). Wrapped 8 raw `grep -r` calls with `run_with_timeout "$MAX_SCAN_TIME"`:
- `AJAX_FILES` (line ~4216)
- `TERMS_FILES` (line ~4617)
- `CRON_FILES` (line ~5024)
- `N1_FILES` (line ~5271, pipeline β€” timeout wraps recursive grep stage)
- `THANKYOU_CONTEXT_FILES` (line ~5463)
- `SMART_COUPONS_FILES` (line ~5554)
- `PERF_RISK_FILES` (line ~5566)
- `JSON_RESPONSE_FILES` (line ~5633)
4. Add heartbeat logs every 10 seconds for long loops. β†’ Deferred to Phase 0b
5. Add top-N slow checks summary at end of scan. β†’ Deferred to Phase 0b
6. Improve docs for `.wpcignore`, `--skip-magic-strings`, and `MAX_SCAN_TIME`. β†’ Deferred to Phase 0b

**Deliverables**
- βœ… Stability patch in `dist/bin/check-performance.sh`
- Short troubleshooting section in docs β†’ Deferred to Phase 0b
- Baseline performance snapshot (before/after) β†’ Deferred to Phase 0b

**Exit Criteria**
- [x] No unguarded raw `grep -r*` in active scan path (remaining raw grep -r only inside `fast_grep()`/`cached_grep()` fallback paths β€” by design)
- [ ] Small-project scans complete reliably in repeated runs β†’ Needs verification testing
- [ ] Users can identify long-running checks from logs β†’ Deferred to Phase 0b

**Implementation Notes (2026-02-09):**
- The xargs calls at lines 2617 and 3222 were originally listed as hotspots but are inside already-protected paths (pre-cached file list + `run_with_timeout`). Removed from scope.
- Timeout behavior: on timeout, check returns empty result, reports "passed," scan continues. Silent degradation chosen over hang. Per-check timeout warnings deferred (see BACKLOG.md).
- No new functions or abstractions introduced β€” reuses existing `run_with_timeout` infrastructure.

### Phase 1: Unified Search Backend Wrapper
**Goal:** Normalize all search operations behind one backend wrapper with safe defaults.

**Scope**
- Introduce a single search API layer in shell helper(s).
- Keep default backend regex-based and behavior-compatible.

**Tasks**
1. Create wrapper functions for:
- file discovery
- line match search
- context extraction
2. Enforce shared behavior:
- timeout
- exclusion rules
- null-safe file handling
- consistent `file:line:code` formatting
3. Route all checks through wrapper API.
4. Add regression fixtures for output parity.

**Deliverables**
- Unified wrapper module
- Refactored scanner call sites
- Regression report proving parity vs current output

**Exit Criteria**
- [ ] All checks call wrapper functions, not raw search commands
- [ ] Timeout/exclusion behavior is consistent across checks
- [ ] Fixture suite passes with no critical regressions

### Phase 2: Optional Semgrep Backend Pilot (5-10 Noisy Rules)
**Goal:** Validate Semgrep on targeted noisy rules without destabilizing the scanner.

**Scope**
- Semgrep is optional via feature flag.
- Pilot only direct/noisy rule subset.

**Candidate Rules (initial)**
1. `unsanitized-superglobal-read`
2. `spo-002-superglobal-manipulation`
3. `wpdb-query-no-prepare`
4. `php-eval-injection`
5. `php-dynamic-include`
6. `php-shell-exec-functions`
7. `php-hardcoded-credentials`
8. `unsanitized-superglobal-isset-bypass`
9. `file-get-contents-url`
10. `wp-json-html-escape` (evaluate feasibility)

**Tasks**
1. Implement `--search-backend semgrep` toggle (default remains current backend).
2. Author Semgrep rules for pilot set.
3. Build comparison harness using fixtures and IRL samples:
- precision proxy (false-positive rate)
- recall proxy (findings retained)
- runtime comparison
4. Generate a per-rule scorecard.

**Deliverables**
- Optional Semgrep integration path
- Pilot Semgrep rule pack
- Benchmark report with recommendation per rule

**Exit Criteria**
- [ ] Pilot runs end-to-end in CI/local test flow
- [ ] Scorecard exists for each pilot rule
- [ ] Clear go/no-go decision per rule

### Phase 3: Production Promotion Strategy
**Goal:** Promote only Semgrep rules that beat current implementation, keep Bash where it is stronger.

**Scope**
- Keep Bash for aggregated, clone-detection, and workflow/context-heavy checks.
- Promote Semgrep selectively for proven direct pattern checks.

**Tasks**
1. Define promotion gates (accuracy and runtime thresholds).
2. Migrate winning pilot rules to Semgrep default path.
3. Keep fallback to Bash backend per rule.
4. Update docs and changelog with backend matrix and troubleshooting.

**Deliverables**
- Hybrid production scanner (Semgrep + Bash)
- Rule ownership matrix (Semgrep-managed vs Bash-managed)
- Rollback plan and fallback toggles

**Exit Criteria**
- [ ] Promoted rules meet agreed quality/performance gates
- [ ] No regression in fixture and smoke suites
- [ ] Clear operational fallback documented

## Success Metrics
- [ ] 95th percentile scan time reduced on medium repositories
- [ ] Fewer reports of "apparent hangs" on small repositories
- [ ] Equal or lower false-positive rate on migrated rules
- [ ] Zero loss of coverage for aggregated/scripted checks

## Risks
- Semgrep dependency and install friction for some users
- Rule drift between Bash and Semgrep implementations
- Initial parity gaps on WordPress-specific edge cases

## Mitigations
- Keep Semgrep optional until scorecards validate migration
- Maintain per-rule fallback to Bash
- Use fixture + IRL validation for every migration decision

## Acceptance Criteria
- [x] Four-phase plan approved for execution order
- [x] Phase 0 task list accepted as immediate next sprint
- [x] Phase 0a (timeout guards) implemented and merged
- [ ] Phase 0b (observability) completed
- [ ] Success metrics and promotion gates agreed before Phase 2 rollout

Loading
Loading