Skip to content

Security: v3.0.1 hardening#10

Merged
adrorocker merged 7 commits into
3.xfrom
security/v3.0.1-hardening
May 1, 2026
Merged

Security: v3.0.1 hardening#10
adrorocker merged 7 commits into
3.xfrom
security/v3.0.1-hardening

Conversation

@adrorocker
Copy link
Copy Markdown
Member

@adrorocker adrorocker commented May 1, 2026

Summary

Two defects in the host/TLD parsing path are fixed and v3.0.0 is marked deprecated. No public API changes; all existing tests still pass.

Fixes

  • Host::tld() — replace dynamic-regex suffix removal with str_ends_with + substr. The old code escaped only . characters before passing the resulting pattern through preg_replace, so any other PCRE metacharacter (* + ? | ^ $ ( ) [ ] { } \) in the supplied TLD landed in a hand-built pattern unescaped — and tld() is a public setter, so a caller could feed it arbitrary input. Result: PCRE compile errors, unintended match behavior, small ReDoS surface. The regex was unnecessary; the suffix is just .<tld> at the end of the host.
  • HostParser — replace strpos(...) !== false scheme detection with str_starts_with. The old check matched http:// / https:// anywhere in the input, so URLs like evil.example.com/?u=http://x were treated as already-schemed and skipped the prefix needed by parse_url.

Other changes

  • New tests/Unit/SecurityTest.php — regression coverage for both findings plus sanity checks.
  • New docs/USAGE.md — full usage guide, secure PSL fetch, caching, API reference, error handling.
  • README slimmed to a landing page that links to USAGE / SECURITY / CHANGELOG.
  • SECURITY.md — supported-versions table updated; new "Known insecure versions" subsection naming v3.0.0.
  • CHANGELOG.md[3.0.1] entry with Security / Deprecated / Docs subsections.
  • composer.json — bump to 3.0.1.

Deprecation

v3.0.0 is deprecated. Consumers should upgrade to v3.0.1.

Test plan

  • ./vendor/bin/phpcs — clean
  • ./vendor/bin/phpstan analyse[OK] No errors
  • ./vendor/bin/pest — 36 passing, 4 pre-existing IDN failures unchanged from origin/3.x baseline (these are a parse_url-corrupts-UTF-8 issue separate from this PR)
  • Manual sanity check: validate('evil.example.com/?u=http://x')->toString() returns evil.example.com; existing compass.com substring case still resolves correctly
  • Reviewer: confirm no behavior change on the existing test suite

Notes for the maintainer

  • After merge, plan is to tag v3.0.1 (annotated) and publish a GitHub Release with notes mirroring the CHANGELOG.
  • A separate follow-up should address the pre-existing IDN test failures (parse_url corrupting UTF-8 multi-byte hosts) — that's a correctness bug that shipped in v3.0.0 but is not what this PR is about.

adrorocker added 7 commits May 1, 2026 12:44
`Host::tld()` built a PCRE pattern from `'.' . $tld` after escaping only
dots. Every other PCRE metacharacter (`* + ? | ^ $ ( ) [ ] { } \`) passed
through unescaped, and `$tld` is reachable from a public setter, so
malicious or surprising input could change the regex shape, trigger
PCRE compile errors, or open a small ReDoS surface.

The regex was unnecessary: `$tld` is the rightmost suffix of `$host`, so
`str_ends_with` + `substr` removes it cleanly without building any
pattern. Behavior for every existing test case is unchanged.
`strpos($host, 'http://') !== false` matches the literal anywhere in the
string, not at position 0, so an input like
`evil.example.com/?u=http://x` was treated as already-schemed and the
`http://` prefix was not prepended before `parse_url`. The intent was
"does the input start with a scheme?", so use `str_starts_with`.
- `Host::tld()` accepts regex metacharacters without crashing.
- `HostParser` only treats a scheme as present when the input *starts*
  with `http://`/`https://`, not when those literals appear elsewhere
  in a URL's path/query.
- `HostParser` still rejects input without an extractable host.
- Sanity-check the substring-TLD case (`a.b.c.compass.com`) survives
  the regex→string-op swap in `Host::tld()`.
Standalone guide covering installation, secure PSL fetch, caching,
worked examples, full API surface, error-handling model, and security
notes. README will be slimmed in a follow-up commit and link here.
Keep README as a landing page: tagline, requirements, installation,
10-line quick start, and links to USAGE/SECURITY/CHANGELOG. The full
usage guide now lives in docs/USAGE.md.
- CHANGELOG: add 3.0.1 entry with Security/Deprecated/Docs subsections.
- SECURITY: mark v3.0.0 as deprecated, add a "Known insecure versions"
  subsection pointing v3.0.0 users at v3.0.1.

No exploit detail is published. See the CHANGELOG for the high-level
description of the two fixes.
Bump composer.json to 3.0.1. Final commit before tagging.
@adrorocker adrorocker merged commit 5e32ea9 into 3.x May 1, 2026
3 checks passed
@adrorocker adrorocker deleted the security/v3.0.1-hardening branch May 1, 2026 19:48
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