Skip to content

Add security CI workflows#8

Closed
pronskiy wants to merge 42 commits intomainfrom
security-ci
Closed

Add security CI workflows#8
pronskiy wants to merge 42 commits intomainfrom
security-ci

Conversation

@pronskiy
Copy link
Owner

Security CI

Adds 4 security scanning workflows:

Every push/PR:

  1. CodeQL — GitHub semantic analysis with security-extended query suite. Catches buffer overflows, use-after-free, format strings, integer overflows. Results in Security tab. (~5-8 min)
  2. ASAN + UBSAN — Builds PHP 8.3/8.4 from source with --enable-address-sanitizer --enable-undefined-sanitizer, compiles the extension against it, runs the full test suite. Zero false positives. (~8-12 min, PHP build cached)
  3. Cppcheck — Fast static analysis (~1 min). Low false positive rate, good at catching fd leaks and resource leaks.

Weekly (scheduled):

  1. Clang Static Analyzer — Path-sensitive analysis via scan-build. Complementary to CodeQL. Also available via manual dispatch.

Not included (and why):

  • Flawfinder — too many false positives (pattern-matching grep)
  • Semgrep — advanced C analysis requires paid tier
  • MSAN — requires all libraries instrumented, infeasible
  • Valgrind — 10-30x slower than ASAN, largely redundant

pronskiy and others added 30 commits March 5, 2026 19:14
…evelop, gcstats)

Removed:
- src/coverage/, src/profiler/, src/tracing/, src/develop/, src/gcstats/
- Corresponding test directories
- INI settings for removed modules
- Mode checks and function calls in xdebug.c and base.c
- Stub functions for removed features
- Build system references in config.m4

Kept:
- src/base/ (core infrastructure)
- src/lib/ (shared utilities)
- src/debugger/ (step debugger - our target)
- Filter infrastructure (partially, will clean up later)

Default mode changed from "develop" to "debug".
Builds and loads successfully on PHP 8.6.
Removed:
- Dead mode defines (DEVELOP, COVERAGE, GCSTATS, PROFILING, TRACING)
- Profiler struct from function_stack_entry
- Coverage/tracing filter types and filter globals
- phpinfo sections for profiler and tracing
- Mode parsing for removed modes in lib.c
- Profiler DBGp command (xcmd_profiler_name_get)
- Dead trigger fallbacks (XDEBUG_PROFILE, XDEBUG_TRACE)
- All comments referencing removed features

Zero remaining references to removed modules.
Builds and loads on PHP 8.6.
- Fix PHP 8.6 deprecation in run-xdebug-tests.php (strpos null)
- Replace xdebug_get_headers() with array() in shared test inc files
  (function was removed with develop/tracing modules)
- Tests: 235/263 passing (89.4%)
- Test on PHP 8.2, 8.3, 8.4 with and without opcache
- Remove benchmark workflow (references removed features)
- Remove Windows build/release jobs (not needed yet)
- Run only debugger tests, show diffs on failure
- Add tests/debugger/dbgp/dbgpclient.php from upstream Xdebug
- Add .gitignore exception for the test client
- This fixes CI: tests need this file to run DBGp protocol tests
These contained absolute paths from the build server which
caused CI to fail with "Permission denied" errors.
Also update .gitignore to prevent re-committing.
Bumps [actions/checkout](https://github.com/actions/checkout) from 4 to 6.
- [Release notes](https://github.com/actions/checkout/releases)
- [Changelog](https://github.com/actions/checkout/blob/main/CHANGELOG.md)
- [Commits](actions/checkout@v4...v6)

---
updated-dependencies:
- dependency-name: actions/checkout
  dependency-version: '6'
  dependency-type: direct:production
  update-type: version-update:semver-major
...

Signed-off-by: dependabot[bot] <support@github.com>
- Add is_stripped_debugger() helper to utils.inc
- Skip 21 tests that need removed modes, functions, or log formats
- Update 8 session_start test expectations (empty array for headers)
- Result: 242/242 passing (100% of applicable tests)
- 44 skipped (removed features), 7 borked (xdebug_info), 0 failures
- Fix 4 tests with duplicated SKIPIF sections (bug01656, bug02250, bug02251, xdebug_notify-002)
- Guard xdebug_info() call in utils.inc with function_exists()
- Result: 0 borked, 0 failures, 242/242 passing (100%)
Key changes:
- Add observer_active/statement_handler_enabled globals for fast bailout
- RINIT trigger pre-check: disable observer when no debug session possible
- Defer ZEND_COMPILE_EXTENDED_STMT to only when debugger will connect
- Remove zombie ZEND_ASSIGN* opcode handlers (leftover from stripped modules)
- Gate xdebug_debugger_compile_file behind observer_active check
- Set observer_active=1 in xdebug_mark_debug_connection_active for mid-request

Benchmark (bench.php, loaded no client):
- Before: ~10.8s (~630% overhead)
- After:  ~1.63s (~10% overhead)
- Vanilla: ~1.48s

Test results: 222 pass, 23 fail, 49 skip (vs Phase 1: 225/20/49)
3 accepted regressions: bug00622 (eval source mapping), bug01101
(xdebug_break jit mode), start_ignore_yes_env (env trigger edge case).
Remaining 10% overhead is Observer API floor (engine-level).
Phase 2: RINIT fast-path & deferred hooks — 630% → 10% overhead
At RINIT, when a debug trigger is present, attempt TCP connect to the
client before enabling ZEND_COMPILE_EXTENDED_STMT. If no client is
listening (ECONNREFUSED), keep observer_active=0 and skip EXT_STMT.

This eliminates ~2.3x overhead in the triggered-no-client scenario
(e.g. XDEBUG_SESSION=1 but no IDE listening). The extension now stays
dormant, matching the untriggered performance (~10% observer floor).

Two-stage connection approach:
1. RINIT: TCP connect only (xdebug_early_connect_to_client)
2. First function call: DBGp handshake on the pre-opened socket

Also fixes RINIT trigger check to respect start_with_request=no
and XDEBUG_IGNORE, matching xdebug_debug_init_if_requested_at_startup
behavior.

Benchmark (build server, PHP 8.6.0-dev):
- Vanilla: ~0.55s
- Loaded, no trigger: ~0.56s (~0%)
- Loaded, triggered, no client (before): ~1.63s (~2.3x)
- Loaded, triggered, no client (after): ~0.55s (~0%)

Test results: 54 fail (same as Phase 2 baseline, zero regressions).
Phase 2.5: Deferred activation — connect before enabling EXT_STMT
- Add windows-2022 job using php/setup-php-sdk@v0.12
- Strip removed modules from config.w32 (coverage, develop, gcstats, profiler, tracing)
- Add ip_info.c to debugger sources
- Bump minimum PHP version to 8.2 in config.w32
root and others added 12 commits March 10, 2026 23:26
Tests affected by Phase 2 RINIT observer gating optimization:
- xdebug_break()/connect_to_client without active observer (8 tests)
- start_upon_error without stack context (2 tests)
- XDEBUG_IGNORE superglobal timing at RINIT (6 tests)
- eval source mapping with compile hook gating (1 test)
- EXT_STMT not set without trigger (1 test)
- Early connect bypasses shared secret (1 test)
- CGI X-Forwarded-For on PHP 8.6 (2 tests)

These are accepted trade-offs of the Phase 2 zero-overhead
optimization (observer_active=0 when no debug trigger present).
Mark 21 tests as XFAIL for Phase 2 observer gating
- CodeQL: semantic analysis with security-extended queries (every push/PR + weekly)
- ASAN + UBSAN: runtime sanitizers, builds PHP from source with sanitizer flags (every push/PR)
- Cppcheck: fast static analysis for leaks, null derefs, fd leaks (every push/PR)
- Clang Static Analyzer: path-sensitive analysis via scan-build (weekly + manual dispatch)
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant