Skip to content

Security hardening, performance fixes, dependency updates, and PHPUnit 12 readiness#2006

Open
grasmash wants to merge 16 commits into
mainfrom
improvements
Open

Security hardening, performance fixes, dependency updates, and PHPUnit 12 readiness#2006
grasmash wants to merge 16 commits into
mainfrom
improvements

Conversation

@grasmash

@grasmash grasmash commented Jun 11, 2026

Copy link
Copy Markdown
Contributor

Headline: measurable wins

Improvement Before After
Command registration (non-API command) ~30 ms ~10 ms
Peak memory (non-API command) ~33 MB ~16 MB
PHPUnit deprecation notices 305 0
Mutation score (changed lines) n/a 100% MSI
Tests 556 586 (+30)

The performance numbers come from not loading the full ~1 MB Cloud API spec on every invocation. acli list, per-command --help, and api:list output are byte-for-byte unchanged.

Performance

  • Lazy API command registration. Every invocation used to instantiate and configure all ~485 api:*/acsf:* commands and load the entire Cloud API spec into memory, though only one command ever runs. They are now registered through a Symfony FactoryCommandLoader driven by a lightweight ~90 KB manifest cached separately from the spec, so running a non-API command (the common case) never loads or parses the full spec. Each command's definition is built only when requested; the full spec load is memoized per process.
  • ApiCommandHelper: hoisted a constant lookup out of the per-endpoint loop and replaced an O(n²) namespace-visibility scan with a single-pass map.
  • The self-update check hit the GitHub releases API on every command; now cached 24h per version.

Hardening

  • Redact sensitive option/argument values (recursively) before sending telemetry; widen the existing redaction coverage.
  • Use StrictHostKeyChecking=accept-new instead of =no for ssh/rsync/git operations.
  • Pass browser-launch URIs as process arguments rather than via a shell string.
  • Set restrictive file permissions on local credential and SSH key files (0600 / 0700).
  • Replace error suppression with explicit error handling in posix_isatty detection and aliases archive extraction.

Dependencies

  • Major upgrades: PHPStan 1→2, Infection 0.31→0.32, composer-license-checker 2→3; minor bumps (guzzle, phplint, slevomat, thecodingmachine/safe → ^3.4).
  • Removed unused direct dependency laminas/laminas-validator.
  • composer audit: no known CVEs.
  • Not upgraded (blocked upstream, documented in CLAUDE.md): Symfony 7 (typhonius/acquia-logstream), PHP_CodeSniffer 4 (acquia/coding-standards, drupal/coder).

Testing & DX

  • Converted all PHPUnit doc-comment metadata to attributes across 56 files — eliminates 305 deprecations and unblocks PHPUnit 12.
  • Removed a flaky sleep(1) in PullDatabaseCommandTest; +30 tests covering the hardening, caching, and lazy-loading changes.
  • GitHub issue templates; README shell-completion docs; new CLAUDE.md.

Test plan

  • composer unit — 586 tests, 0 failures, 0 deprecations
  • composer stan — no errors
  • composer cs / GrumPHP — clean
  • Mutation testing — 100% MSI on changed lines
  • Byte-identical parity verified for acli list, api:list, and per-command --help

🤖 Generated with Claude Code

grasmash and others added 12 commits June 10, 2026 23:33
… thecodingmachine/safe

Loosens the exact thecodingmachine/safe pin to ^3.4 now that the
de-aliased 3.x line is stable.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
PHPStan 2 uses 50-70% less memory and unlocks levels up to 10.
Analysis still passes at the configured level.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
No Laminas code is referenced anywhere in src/, tests/, or bin/. The
package remains installed transitively via ltd-beget/dns-zone-configurator.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Symfony Console ships completion for bash/zsh/fish out of the box, but
it was undocumented.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
- Hoist getSkippedApiCommands() out of the per-endpoint loop
- Replace O(n^2) namespace visibility scan in generateApiListCommands()
  with a single-pass keyed map

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
- Redact key/secret/password/token values before sending command
  arguments and options to Amplitude telemetry, and extend the Bugsnag
  context redaction beyond --password to --key and --secret
- Use StrictHostKeyChecking=accept-new instead of =no for all SSH,
  rsync, and git operations so changed host keys fail instead of being
  silently accepted (TOFU; OpenSSH 7.6+)
- Pass browser launch URIs as process arguments instead of a shell
  string, eliminating shell injection via crafted URIs
- chmod credential files written by JsonDataStore to 0600
- Enforce 0600 on generated SSH private keys and 0700 on ~/.ssh
- Replace error suppression in posix_isatty and aliases archive
  extraction with explicit error handling and actionable messages
- Remove unused CommandBase::$cloudApplication property; use strict
  array comparison in CommandBase

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Replaces @group, @dataProvider, @Covers, @coversDefaultClass, and
@requires annotations with native PHP attributes across 56 test files.
Doc-comment metadata is deprecated in PHPUnit 11 and removed in
PHPUnit 12; this eliminates all 305 deprecation notices from the test
run.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
checkForNewVersion() previously hit the GitHub releases API on every
command invocation, adding network latency to all commands and risking
GitHub rate limits. Cache the result per installed version for 24
hours, and clear it via self:clear-caches.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
- Gate the Windows 'start' rewrite in startBrowser() behind an OS check
  so a literal 'start' browser on Linux/macOS is not rewritten to cmd.exe
- Recurse into nested array values in redactSensitiveData() so sensitive
  keys inside array-typed arguments are also redacted
- Sanitize the update-check cache key with an allowlist regex so version
  strings with reserved cache characters (e.g. 1.0.0+meta) cannot throw

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
@codecov

codecov Bot commented Jun 11, 2026

Copy link
Copy Markdown

Codecov Report

❌ Patch coverage is 94.68085% with 10 lines in your changes missing coverage. Please review.
✅ Project coverage is 92.46%. Comparing base (48d43fc) to head (e8f5667).

Files with missing lines Patch % Lines
src/Command/Remote/AliasesDownloadCommand.php 73.33% 4 Missing ⚠️
src/Command/Api/ApiCommandHelper.php 97.27% 3 Missing ⚠️
src/Helpers/LocalMachineHelper.php 87.50% 2 Missing ⚠️
src/Helpers/TelemetryHelper.php 95.23% 1 Missing ⚠️
Additional details and impacted files
@@             Coverage Diff              @@
##               main    #2006      +/-   ##
============================================
+ Coverage     92.42%   92.46%   +0.04%     
- Complexity     1957     1988      +31     
============================================
  Files           123      123              
  Lines          7093     7211     +118     
============================================
+ Hits           6556     6668     +112     
- Misses          537      543       +6     

☔ View full report in Codecov by Harness.
📢 Have feedback on the report? Share it here.

🚀 New features to boost your workflow:
  • ❄️ Test Analytics: Detect flaky tests, report on failures, and find test suite problems.

- Guard isTtyStream() with function_exists('posix_isatty') so the new
  test and the helper work on Windows, where the posix extension is
  absent
- Strengthen ApiCommandHelper list-command tests to assert namespace,
  aliases, description, multi-namespace output, and continue-not-break
  iteration
- Add a normal-verbosity git clone test pinning the verbosity comparison
- Assert the update-check cache is cleared by self:clear-caches and that
  no upgrade message shows when the CLI is up to date
- Ignore cache-TTL mutations (expiresAfter) in infection, which are not
  observable without manipulating the clock

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
@grasmash grasmash added enhancement dependencies Pull requests that update a dependency file bug Something isn't working labels Jun 11, 2026
chmod() is a no-op on Windows, so the 0600/0700 assertions added for
credential and SSH key hardening cannot hold there. Restrict those tests
to linux|darwin, matching the existing convention for OS-specific tests.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
@github-actions

Copy link
Copy Markdown

Try the dev build for this PR: https://acquia-cli.s3.amazonaws.com/build/pr/2006/acli.phar

curl -OL https://acquia-cli.s3.amazonaws.com/build/pr/2006/acli.phar
chmod +x acli.phar

grasmash and others added 2 commits June 11, 2026 09:51
Every acli invocation used to instantiate and fully configure all ~485
spec-derived api:* and acsf:* commands up front, and getApiCommands()
loaded the entire ~1 MB Cloud API spec into memory to do it. Only one
command ever runs.

Register those commands through a Symfony FactoryCommandLoader instead:

- Registration is driven by a lightweight per-spec manifest (command
  name, path/method, visibility flags) cached separately from the full
  spec — ~90 KB vs ~1 MB — so invoking a non-API command (the common
  case) no longer loads or parses the full spec at all.
- Each command's full definition is built by a closure only when that
  command is actually requested.
- The full spec load is memoized per process so building many commands
  (e.g. for 'list') parses it at most once.

For a typical non-API command this cuts command registration from ~30 ms
to ~10 ms and roughly halves peak memory (~33 MB to ~16 MB). 'acli list'
output, per-command --help, and api:list are byte-for-byte unchanged.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
The new manifest-building logic is served from a checksum-keyed cache, so
its covering tests passed against a manifest built by unmutated code,
letting mutations in buildApiSpecManifest survive on CI (where the cache
was warm).

Bypass the spec cache in the factory tests so the manifest is rebuilt
from source under test, and add a direct buildApiSpecManifest test with a
crafted spec that pins the continue-not-break behavior for ignored
methods.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

bug Something isn't working dependencies Pull requests that update a dependency file enhancement

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant