Skip to content

fix: preserve escaped characters in fork-curl gzip payloads - #250

Merged
marandaneto merged 3 commits into
mainfrom
fix/fork-curl-gzip-escaping
Sep 28, 2026
Merged

marandaneto merged 3 commits into
mainfrom
fix/fork-curl-gzip-escaping

Conversation

@marandaneto

@marandaneto marandaneto commented Sep 27, 2026 •

Copy link
Copy Markdown
Member

💡 Motivation and Context

The fork-curl consumer pipes JSON through shell echo before gzip compression. On shells that interpret backslash escapes, event properties containing newlines or literal backslashes can become invalid JSON or change value.

Use printf '%s' to preserve the JSON bytes. Add a loopback regression test that sends real curl requests in plain and gzip modes, then checks the decoded payload. It covers control characters, literal backslashes, quotes, percent signs and Unicode.

Declare ext-zlib in Composer's development dependencies and explicitly enable it in PHPUnit and coverage CI so the gzip regression always runs. SDK users do not acquire a new runtime dependency.

This is the production fix found in #249, isolated on a fresh branch from main so it can be reviewed and released separately. It includes a patch change intent. The audit PR is unchanged and will need its duplicate change intent removed when it is synced after this fix merges.

💚 How did you test it?

  • On the original code, the plain JSON case passed and the gzip case failed with JsonException: Control character error.
  • After the fix, the regression and queue-consumer suites passed: 16 cases and 49 assertions.
  • The broader suite passed 696 cases with external networking blocked and loopback allowed. Two previously failing tests were excluded: ConsumerFileTest::testSend and ConsumerSocketTest::testProductionProblems. Existing warning and deprecation notices remain.
  • All 16 Python adapter and report-checker tests passed.
  • PHP_CodeSniffer passed for all changed PHP files. Composer validation, the public API snapshot and git diff --check passed.
  • After making zlib a required development dependency, reran the 16 focused cases (49 assertions), Composer install/validation/platform checks, the API snapshot, PHP_CodeSniffer, actionlint and whitespace validation. All passed.
  • Autoreview passed at a4aa9e6852fa7e485677490c2c23115387173cf4 with no actionable findings.

Local validation used PHP 8.5.10 on macOS. Linux and the other supported PHP versions were not run locally.

📝 Checklist

  • I reviewed the submitted code.
  • I added tests to verify the changes.
  • I updated the docs if needed.
  • No breaking change or entry added to the changelog.

If releasing new changes

  • Ran pnpm change to generate a change intent file

🤖 Agent context

Autonomy: Human-driven (agent-assisted)

Pi used file and shell tools to isolate the previously tested fix in a new worktree from main. The test runs real curl and gzip processes against a local HTTP fixture reused from the audit, rather than mocking compression. The autoreview helper reviewed the committed branch. No session was published. Human review is required.

@marandaneto marandaneto self-assigned this Sep 27, 2026
@marandaneto
marandaneto marked this pull request as ready for review September 27, 2026 09:23
@marandaneto
marandaneto requested a review from a team as a code owner September 27, 2026 09:23
@github-actions

github-actions Bot commented Sep 27, 2026 •

Copy link
Copy Markdown
Contributor

posthog-php-lib_curl Compliance Report

Date: 2026-09-27T13:36:23.778037+00:00
Duration: 118897ms

✅ All Tests Passed!

47/47 tests passed


Capture Tests

✅ 30/30 tests passed

View Details
Test Status Duration
Format Validation.Event Has Required Fields ✅ 30ms
Format Validation.Event Has Uuid ✅ 522ms
Format Validation.Event Has Lib Properties ✅ 525ms
Format Validation.Distinct Id Is String ✅ 524ms
Format Validation.Token Is Present ✅ 525ms
Format Validation.Custom Properties Preserved ✅ 525ms
Format Validation.Event Has Timestamp ✅ 526ms
Format Validation.Non Utc Event Timestamp Is Converted To Utc ✅ 524ms
Retry Behavior.Retries On 503 ✅ 5834ms
Retry Behavior.Does Not Retry On 400 ✅ 2528ms
Retry Behavior.Does Not Retry On 401 ✅ 2529ms
Retry Behavior.Respects Retry After Header ✅ 8533ms
Retry Behavior.Implements Backoff ✅ 16249ms
Retry Behavior.Retries On 500 ✅ 5636ms
Retry Behavior.Retries On 502 ✅ 5633ms
Retry Behavior.Retries On 504 ✅ 5630ms
Retry Behavior.Max Retries Respected ✅ 17052ms
Deduplication.Generates Unique Uuids ✅ 235ms
Deduplication.Preserves Uuid On Retry ✅ 5633ms
Deduplication.Preserves Uuid And Timestamp On Retry ✅ 10840ms
Deduplication.Preserves Uuid And Timestamp On Batch Retry ✅ 5638ms
Deduplication.No Duplicate Events In Batch ✅ 531ms
Deduplication.Different Events Have Different Uuids ✅ 526ms
Compression.Sends Gzip When Enabled ✅ 525ms
Batch Format.Uses Proper Batch Structure ✅ 524ms
Batch Format.Flush With No Events Sends Nothing ✅ 520ms
Batch Format.Multiple Events Batched Together ✅ 514ms
Error Handling.Does Not Retry On 403 ✅ 2527ms
Error Handling.Does Not Retry On 413 ✅ 2527ms
Error Handling.Retries On 408 ✅ 5633ms

Feature_Flags Tests

✅ 17/17 tests passed

View Details
Test Status Duration
Request Payload.Request With Person Properties Device Id ✅ 524ms
Request Payload.Flags Request Uses V2 Query Param ✅ 522ms
Request Payload.Flags Request Hits Flags Path Not Decide ✅ 522ms
Request Payload.Flags Request Omits Authorization Header ✅ 522ms
Request Payload.Token In Flags Body Matches Init ✅ 523ms
Request Payload.Groups Round Trip ✅ 522ms
Request Payload.Groups Default To Empty Object ✅ 524ms
Request Payload.Disable Geoip False Propagates As Geoip Disable False ✅ 522ms
Request Payload.Disable Geoip Omitted Defaults To False ✅ 522ms
Request Payload.Flag Keys To Evaluate Contains Only Requested Key ✅ 523ms
Request Lifecycle.No Flags Request On Init Alone ✅ 518ms
Request Lifecycle.No Flags Request On Normal Capture ✅ 509ms
Request Lifecycle.Two Flag Calls Produce Two Remote Requests ✅ 526ms
Request Lifecycle.Mock Response Value Is Returned To Caller ✅ 524ms
Retry Behavior.Retries Flags On 502 ✅ 626ms
Retry Behavior.Retries Flags On 504 ✅ 626ms
Side Effect Events.Get Feature Flag Captures Feature Flag Called Event ✅ 526ms

@github-actions

github-actions Bot commented Sep 27, 2026 •

Copy link
Copy Markdown
Contributor

posthog-php-fork_curl Compliance Report

Date: 2026-09-27T13:36:19.483873+00:00
Duration: 112944ms

⚠️ Some Tests Failed

36/47 tests passed, 11 failed


Capture Tests

⚠️ 19/30 tests passed, 11 failed

View Details
Test Status Duration
Format Validation.Event Has Required Fields ✅ 37ms
Format Validation.Event Has Uuid ✅ 530ms
Format Validation.Event Has Lib Properties ✅ 531ms
Format Validation.Distinct Id Is String ✅ 531ms
Format Validation.Token Is Present ✅ 532ms
Format Validation.Custom Properties Preserved ✅ 533ms
Format Validation.Event Has Timestamp ✅ 532ms
Format Validation.Non Utc Event Timestamp Is Converted To Utc ✅ 532ms
Retry Behavior.Retries On 503 ❌ 5535ms
Retry Behavior.Does Not Retry On 400 ✅ 2536ms
Retry Behavior.Does Not Retry On 401 ✅ 2535ms
Retry Behavior.Respects Retry After Header ❌ 5537ms
Retry Behavior.Implements Backoff ❌ 15549ms
Retry Behavior.Retries On 500 ❌ 5539ms
Retry Behavior.Retries On 502 ❌ 5539ms
Retry Behavior.Retries On 504 ❌ 5536ms
Retry Behavior.Max Retries Respected ❌ 15551ms
Deduplication.Generates Unique Uuids ✅ 541ms
Deduplication.Preserves Uuid On Retry ❌ 5537ms
Deduplication.Preserves Uuid And Timestamp On Retry ❌ 10544ms
Deduplication.Preserves Uuid And Timestamp On Batch Retry ❌ 5542ms
Deduplication.No Duplicate Events In Batch ✅ 539ms
Deduplication.Different Events Have Different Uuids ✅ 533ms
Compression.Sends Gzip When Enabled ✅ 535ms
Batch Format.Uses Proper Batch Structure ✅ 532ms
Batch Format.Flush With No Events Sends Nothing ✅ 519ms
Batch Format.Multiple Events Batched Together ✅ 522ms
Error Handling.Does Not Retry On 403 ✅ 2532ms
Error Handling.Does Not Retry On 413 ✅ 2536ms
Error Handling.Retries On 408 ❌ 5537ms

Failures

retry_behavior.retries_on_503

Expected at least 3 requests, got 1

retry_behavior.respects_retry_after_header

Expected at least 2 requests, got 1

retry_behavior.implements_backoff

Expected at least 3 requests, got 1

retry_behavior.retries_on_500

Expected at least 2 requests, got 1

retry_behavior.retries_on_502

Expected at least 2 requests, got 1

retry_behavior.retries_on_504

Expected at least 2 requests, got 1

retry_behavior.max_retries_respected

Expected 4 requests, got 1

deduplication.preserves_uuid_on_retry

Need at least 2 requests to check retry

deduplication.preserves_uuid_and_timestamp_on_retry

Expected at least 3 requests, got 1

deduplication.preserves_uuid_and_timestamp_on_batch_retry

Expected at least 2 requests, got 1

error_handling.retries_on_408

Expected at least 2 requests, got 1

Feature_Flags Tests

✅ 17/17 tests passed

View Details
Test Status Duration
Request Payload.Request With Person Properties Device Id ✅ 524ms
Request Payload.Flags Request Uses V2 Query Param ✅ 524ms
Request Payload.Flags Request Hits Flags Path Not Decide ✅ 523ms
Request Payload.Flags Request Omits Authorization Header ✅ 523ms
Request Payload.Token In Flags Body Matches Init ✅ 523ms
Request Payload.Groups Round Trip ✅ 523ms
Request Payload.Groups Default To Empty Object ✅ 525ms
Request Payload.Disable Geoip False Propagates As Geoip Disable False ✅ 526ms
Request Payload.Disable Geoip Omitted Defaults To False ✅ 522ms
Request Payload.Flag Keys To Evaluate Contains Only Requested Key ✅ 522ms
Request Lifecycle.No Flags Request On Init Alone ✅ 518ms
Request Lifecycle.No Flags Request On Normal Capture ✅ 517ms
Request Lifecycle.Two Flag Calls Produce Two Remote Requests ✅ 525ms
Request Lifecycle.Mock Response Value Is Returned To Caller ✅ 525ms
Retry Behavior.Retries Flags On 502 ✅ 626ms
Retry Behavior.Retries Flags On 504 ✅ 625ms
Side Effect Events.Get Feature Flag Captures Feature Flag Called Event ✅ 533ms

@greptile-apps

greptile-apps Bot commented Sep 27, 2026 •

Copy link
Copy Markdown

Retrigger

[Medium risk] Fixes payload encoding in the event delivery system.

The PR appears safe to merge; the test-portability issue is non-blocking.

Reviews (1) · Last reviewed commit: "fix: preserve escaped characters in fork..."

Comment thread test/fixtures/http-server.php
@github-actions

github-actions Bot commented Sep 27, 2026 •

Copy link
Copy Markdown
Contributor

posthog-php-socket Compliance Report

Date: 2026-09-27T13:37:04.975582+00:00
Duration: 149268ms

⚠️ Some Tests Failed

36/47 tests passed, 11 failed


Capture Tests

⚠️ 19/30 tests passed, 11 failed

View Details
Test Status Duration
Format Validation.Event Has Required Fields ✅ 27ms
Format Validation.Event Has Uuid ✅ 524ms
Format Validation.Event Has Lib Properties ✅ 525ms
Format Validation.Distinct Id Is String ✅ 524ms
Format Validation.Token Is Present ✅ 525ms
Format Validation.Custom Properties Preserved ✅ 524ms
Format Validation.Event Has Timestamp ✅ 525ms
Format Validation.Non Utc Event Timestamp Is Converted To Utc ✅ 524ms
Retry Behavior.Retries On 503 ❌ 9233ms
Retry Behavior.Does Not Retry On 400 ✅ 2527ms
Retry Behavior.Does Not Retry On 401 ✅ 2525ms
Retry Behavior.Respects Retry After Header ❌ 9234ms
Retry Behavior.Implements Backoff ❌ 19246ms
Retry Behavior.Retries On 500 ❌ 9237ms
Retry Behavior.Retries On 502 ❌ 9236ms
Retry Behavior.Retries On 504 ❌ 9230ms
Retry Behavior.Max Retries Respected ❌ 18751ms
Deduplication.Generates Unique Uuids ✅ 534ms
Deduplication.Preserves Uuid On Retry ❌ 9234ms
Deduplication.Preserves Uuid And Timestamp On Retry ❌ 14240ms
Deduplication.Preserves Uuid And Timestamp On Batch Retry ❌ 9241ms
Deduplication.No Duplicate Events In Batch ✅ 531ms
Deduplication.Different Events Have Different Uuids ✅ 525ms
Compression.Sends Gzip When Enabled ✅ 524ms
Batch Format.Uses Proper Batch Structure ✅ 523ms
Batch Format.Flush With No Events Sends Nothing ✅ 519ms
Batch Format.Multiple Events Batched Together ✅ 513ms
Error Handling.Does Not Retry On 403 ✅ 2525ms
Error Handling.Does Not Retry On 413 ✅ 2527ms
Error Handling.Retries On 408 ❌ 5529ms

Failures

retry_behavior.retries_on_503

Expected at least 3 requests, got 1

retry_behavior.respects_retry_after_header

Expected at least 2 requests, got 1

retry_behavior.implements_backoff

Expected at least 3 requests, got 1

retry_behavior.retries_on_500

Expected at least 2 requests, got 1

retry_behavior.retries_on_502

Expected at least 2 requests, got 1

retry_behavior.retries_on_504

Expected at least 2 requests, got 1

retry_behavior.max_retries_respected

Expected 4 requests, got 1

deduplication.preserves_uuid_on_retry

Need at least 2 requests to check retry

deduplication.preserves_uuid_and_timestamp_on_retry

Expected at least 3 requests, got 1

deduplication.preserves_uuid_and_timestamp_on_batch_retry

Expected at least 2 requests, got 1

error_handling.retries_on_408

Expected at least 2 requests, got 1

Feature_Flags Tests

✅ 17/17 tests passed

View Details
Test Status Duration
Request Payload.Request With Person Properties Device Id ✅ 523ms
Request Payload.Flags Request Uses V2 Query Param ✅ 522ms
Request Payload.Flags Request Hits Flags Path Not Decide ✅ 522ms
Request Payload.Flags Request Omits Authorization Header ✅ 522ms
Request Payload.Token In Flags Body Matches Init ✅ 521ms
Request Payload.Groups Round Trip ✅ 522ms
Request Payload.Groups Default To Empty Object ✅ 522ms
Request Payload.Disable Geoip False Propagates As Geoip Disable False ✅ 521ms
Request Payload.Disable Geoip Omitted Defaults To False ✅ 522ms
Request Payload.Flag Keys To Evaluate Contains Only Requested Key ✅ 522ms
Request Lifecycle.No Flags Request On Init Alone ✅ 516ms
Request Lifecycle.No Flags Request On Normal Capture ✅ 509ms
Request Lifecycle.Two Flag Calls Produce Two Remote Requests ✅ 527ms
Request Lifecycle.Mock Response Value Is Returned To Caller ✅ 523ms
Retry Behavior.Retries Flags On 502 ✅ 624ms
Retry Behavior.Retries Flags On 504 ✅ 625ms
Side Effect Events.Get Feature Flag Captures Feature Flag Called Event ✅ 525ms

@marandaneto
marandaneto merged commit 05ddb4a into main Sep 28, 2026
28 checks passed
@marandaneto
marandaneto deleted the fix/fork-curl-gzip-escaping branch September 28, 2026 12:54
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.

2 participants