Skip to content

feat: tier 5 — harden twenty-four low-severity gaps#120

Merged
markshust merged 2 commits into
developfrom
feature/tier5-low-hardening
Jun 12, 2026
Merged

feat: tier 5 — harden twenty-four low-severity gaps#120
markshust merged 2 commits into
developfrom
feature/tier5-low-hardening

Conversation

@markshust

Copy link
Copy Markdown
Collaborator

Tier 5 — Low-Severity Hardening

Fifth of six audit-remediation PRs. Hardens twenty-four low-severity gaps across ~20 packages without changing public contracts. 24 TDD tasks, full suite green (6677 passing, 0 failures). Built on Tier 1-4.

Fixes

webhook inbound replay protection (timestamp freshness window) · session-file 0600/0700 perms + checked writes · SSE CRLF rejection + subscription heartbeat · OpenSSL AEAD-cipher enforcement · log line-injection escaping · container circular-dependency detection · request CGI Content-Type header · manifest php*-vendor filter · FakeSession null parity · deterministic layout ambiguity · cache-file tmp cleanup/mkdir race · codeindexer unserialize allowlist + corrupt-cache rebuild · docs-fts malformed-MATCH → DocsException · mcp stacked-statement guard · lsp malformed-frame resilience · translator single-pass placeholders · mysql/pgsql SQL type-map parity · MySQL explicit PDO param binding · empty whereIn([])1 = 0 · GD format/alpha preservation · admin-api wildcard section visibility · queue RetryCommand attempt reset · debugbar lazy all() + default-mask completeness.

New config keys

  • webhook.timestamp_tolerance (default 300s).
  • log.escape_newlines (default true).
  • Expanded debugbar default masked list (password/secret/key/token/dsn, top-level + nested).

Public-surface additions (non-breaking)

  • JobInterface::resetAttempts() / Job::resetAttempts().
  • New exceptions: SessionWriteException, CircularDependencyException, SseException::invalidField(), EncryptionException::nonAeadCipher()/invalidCipher().

Notes for reviewers

  • EncryptionException was re-based onto MarkoException by the standards pass (public constructor/getters/factories preserved byte-for-byte; verified against its test).
  • The doc-updater wrote these pages under docs/src/content/docs/packages/ (prior tiers used packages/docs-markdown/docs/packages/) — you may want to reconcile the two doc trees.
  • Devil's-advocate-reviewed (rebased the plan's stale "tier3 empty" / pre-merge notes onto the merged Tier 1-4 reality: task 020 scoped to empty whereIn since no whereNotIn() exists; task 023 preserves the JobEnvelope seam; task 008 dropped a double-decode). phpcs + php-cs-fixer clean.

🤖 Generated with Claude Code

markshust and others added 2 commits June 12, 2026 14:29
Rebased onto merged Tier 1-4: task 020 scoped to empty whereIn -> '1 = 0'
(no whereNotIn() method exists); task 023 must preserve Tier 1's JobEnvelope
verifyAndUnwrap (only insert resetAttempts before push); task 008 dropped the
path() rawurldecode (Tier 3 RouteMatcher already decodes per-param — would
double-decode), scoped to the Content-Type/Length header CGI-key fallback;
task 004 OpenSslEncryptor stays a plain class (extensibility trap). Corrected
stale 'tier3 empty' note and drifted query-builder line numbers.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Audit remediation Tier 5 — all 24 tasks, TDD, full-suite green (6677 passing).
Built on merged Tier 1-4. No public-contract breakage.

- Webhook inbound replay protection: X-Webhook-Timestamp freshness window
  (webhook.timestamp_tolerance, default 300s) folded into the HMAC.
- Session-file: 0600 files / 0700 dir; loud SessionWriteException on short writes.
- SSE: reject CR/LF in event/id; subscription heartbeat + idle timeout.
- OpenSSL: enforce AEAD cipher at construction; is_string payload checks.
- Log: opt-out CR/LF escaping in the line formatter (log.escape_newlines, default on).
- Core: container CircularDependencyException; manifest keeps php*-vendor packages.
- Routing: Request::header() reads the CGI CONTENT_TYPE/CONTENT_LENGTH keys.
- testing: FakeSession::has() null parity with production Session.
- Layout: deterministic ambiguous-sort-order detection (out of the comparator).
- cache-file: tmp cleanup on rename failure, clear() globs tmp, mkdir race tolerant.
- codeindexer: allowed_classes + corrupt-cache rebuild (never silently empty).
- docs-fts: malformed FTS5 MATCH → DocsException (no raw PDOException leak).
- mcp: reject stacked statements in the read-only DB tool.
- lsp: distinguish EOF from a malformed frame (-32700, keep serving).
- Translator: single-pass strtr placeholder replacement.
- SQL generators: mysql/pgsql type-map parity (uuid/enum/bool/tinyint/blob/decimal).
- MySQL connection: explicit PDO param type binding (bool/null/int parity w/ pgsql).
- Query builders: empty whereIn([]) → valid `1 = 0` on both.
- media-gd: preserve source format + alpha; loud error on encode failure.
- admin-api: wildcard-aware section visibility; show() filter parity with index().
- queue: RetryCommand resets attempts (Job/JobInterface::resetAttempts) so retries
  actually run — preserving Tier 1's JobEnvelope verify/wrap seam.
- debugbar: lazy all() (summaries, no full decode); default mask covers
  password/secret/key/token/dsn at top level and nested.

Devil's-advocate-reviewed (rebased the stale cross-tier notes onto merged
Tier 1-4), standards-enforced (no src readonly-class promotions), docs updated,
phpcs + php-cs-fixer clean. Includes a test-fixture global-function rename and
EncryptionException rebased onto MarkoException (public contract preserved).

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
@github-actions github-actions Bot added the enhancement New feature or request label Jun 12, 2026
@markshust markshust merged commit 98ac7a5 into develop Jun 12, 2026
1 check passed
@markshust markshust deleted the feature/tier5-low-hardening branch June 12, 2026 19:01
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

enhancement New feature or request

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant