[PR #4909] [CLOSED] fix(pcap): report capture instants in UTC instead of host-local time #6670

Closed
opened 2026-11-01 21:42:12 +00:00 by bsakaguchi · 0 comments
Owner

📋 Pull Request Information

Original PR: https://github.com/morluto/rea/pull/4909
Author: @mertkayacs
Created: 10/17/2026
Status: ❌ Closed

Base: main ← Head: fix/pcap-utc-capture-instants


📝 Commits (4)

  • 82bc2f5 test(pcap): expect capture instants as UTC from epoch capinfos timestamps
  • df3bd2d fix(pcap): report capture instants in UTC instead of host-local time
  • 9c57278 fix(pcap): keep pre-epoch capture instants exact
  • 4dac658 test(pcap): read the real capinfos oracle from epoch seconds

📊 Changes

9 files changed (+111 additions, -24 deletions)

View changed files

📝 scripts/verify-packet-capture.mjs (+15 -5)
📝 src/pcap/PcapConfiguration.test.ts (+39 -6)
📝 src/pcap/PcapOutput.ts (+39 -6)
📝 src/pcap/PcapProvider.ts (+1 -1)
📝 tests/boundary/mcp/pcapMcp.test.ts (+2 -2)
📝 tests/boundary/pcap/pcapProvider.test.ts (+5 -1)
📝 tests/boundary/pcap/pcapProviderCrossPlatform.test.ts (+2 -0)
📝 tests/fixtures/pcap/capinfos-stub.cmd (+5 -2)
📝 tests/fixtures/pcap/capinfos-stub.sh (+3 -1)

📄 Description

Summary

inspect_pcap_capture reports capture_start and capture_end as the text capinfos prints by default, which is the packet time in the host's local time zone with no zone marker. The same capture therefore reads 2026-10-11 14:13:40.608505 on one host and 2026-10-11 16:13:40.608505 on another, and neither string names an instant. This change runs capinfos with -S, which prints exact epoch seconds, and projects that value to an RFC 3339 UTC instant that keeps every fractional digit (2026-10-11T14:13:40.608505Z). Fixes #4907.

Problem and expected behavior

Trigger: rea inspect-pcap-capture <file> --json on any capture with at least one packet, on a host whose TZ is not UTC. REA runs capinfos -M -- <file> with LC_ALL=C but inherits TZ. capinfos formats Earliest packet time with format_nstime_as_iso8601(..., local = true, ...), which uses localtime and appends no Z or offset (wsutil/to_str.c, format_nstime_as_iso8601; capinfos.c, absolute_time_string). With -S the same function path calls display_epoch_time, which prints the nstime seconds and the capture's fractional precision verbatim (capinfos.c time_as_secs, wsutil/to_str.c display_signed_time).

Violated invariant: a reported timestamp must identify one instant independently of the analyzing host. REA's own timestamp contracts require an explicit UTC offset (offsetDateTimeSchema), and AGENTS.md asks that identity values stay separate from display formatting. A zone-less local string is a display spelling of the host, not an identity of the capture.

Expected: capture_start and capture_end are the same bytes for the same capture on every host, carry an explicit zone, and keep the producer's exact precision.

Change and scope

  • PcapProvider runs capinfos -M -S -- <snapshot>.
  • parseCapinfosOutput projects [-]<seconds>[.<fraction>] to YYYY-MM-DDTHH:MM:SS[.<fraction>]Z from the integer seconds and the fraction digits, so nothing is rounded or extended; a pre-epoch value (capinfos prints a leading minus) borrows from the next whole second so the instant stays exact (-1.250000 is 1969-12-31T23:59:58.750000Z). n/a (empty capture), a wall-clock spelling from a capinfos run without -S, and a value outside years 0000 to 9999 stay null, which the schema already allows.
  • scripts/verify-packet-capture.mjs (the real-TShark job) reads its capinfos oracle with -S too and projects the epoch text independently of the product before comparing.
  • Not included: the capture_duration_seconds path (already a plain number), tshark field selection, and any change to the result schema types.

Contribution scope: ordinary fix inside the existing capinfos read boundary; not applicable.

Contract and boundary impact

  • Semantic owner and earliest changed stage: src/pcap/PcapOutput.ts (parseCapinfosOutput), the capinfos metadata owner; transport or result projection.
  • Contribution scope fit and linked maintainer decision: existing read-only capability; not applicable.
  • CLI and MCP/tool-catalog contract: capture_start and capture_end keep their string | null type. Their value changes from a host-local, zone-less spelling to an RFC 3339 UTC instant. Consumers that parsed the old spelling as local time of the analyzing host need to read the new one as UTC.
  • Provider, bridge, target-format, or platform compatibility: -S exists in every Wireshark 3.x and 4.x capinfos (documented in the capinfos man page). The Windows and POSIX stubs under tests/fixtures/pcap/ were updated to accept it.
  • Evidence, artifact, provenance, or reconstruction contract: the producer's exact seconds and fractional digits are preserved; unknown spellings become null instead of being reported as observed text.
  • Target selection, authority, upstream behavior, and cleanup details: unchanged.
  • Generated metadata, package, or installation impact: none.

Expected effects:

  • Read existing files or captured data.
  • Launch or attach to a process.
  • Inject instrumentation or run scripts/code.
  • Change process memory, registers, files, or configuration.
  • Contact network services.
  • Recover protected code or work around protections.
  • Documentation or usability only; no target interaction.
  • Not sure yet; investigation or maintainer guidance needed.

Evidence and regression coverage

  • Tests added or updated: src/pcap/PcapConfiguration.test.ts (real-output fixture now uses -S spellings; new case covers a fraction-less value, a fraction with trailing zeros, n/a and a wall-clock spelling), tests/boundary/pcap/pcapProvider.test.ts and pcapProviderCrossPlatform.test.ts (stub capinfos rejects a call without -S, and the projected capture_start/capture_end are asserted end to end through the provider), tests/boundary/mcp/pcapMcp.test.ts (fixture values), tests/fixtures/pcap/capinfos-stub.{sh,cmd}.

  • Base reproduction or other evidence: executed with capinfos 4.2.2 on a two-packet capture (output in #4907): the default spelling is 2026-10-11 17:13:40.608505 under TZ=Europe/Istanbul and 2026-10-11 14:13:40.608505 under TZ=UTC, with no marker; -S prints 1791728020.608505 under both. This matches Wireshark capinfos.c and wsutil/to_str.c as cited above; src/pcap/PcapCommand.ts sets LC_ALL/LANG but not TZ. On the base revision 2fb5e204a35723f6f843d56ca47b0db94d8fcc44 the test commit alone fails: PcapConfiguration.test.ts 3 failed / 6 passed in its parseCapinfosOutput block, and the focused boundary run 12 failed / 18 passed / 1 skipped, because the stub refuses the -M -- <path> argument list and the old spelling is reported.

  • User-visible CLI/MCP output: "capture_start": "2026-10-11T14:13:40.608505Z" instead of "capture_start": "2026-10-11 14:13:40.608505".

  • Remaining proof gaps: the Windows .cmd stub path was not executed here (Linux only).

  • Observed, derived, and inferred claims remain distinguishable.

  • Artifact identity, source provenance, and failed attempts remain preserved.

  • Unsupported, incomplete, unavailable, or uncertain outcomes remain visible.

Validation performed

  • npm run test:local -- src/pcap/PcapConfiguration.test.ts src/pcap/PcapOutput.test.ts — 26 passed (on the test-only commit: 3 failed in parseCapinfosOutput, 17 of 20 passed in that file).
  • npm run test:focused -- tests/boundary/pcap/pcapProvider.test.ts tests/boundary/pcap/pcapProviderCrossPlatform.test.ts tests/boundary/mcp/pcapMcp.test.ts — 30 passed, 1 skipped (on the test-only commit: 12 failed, 18 passed, 1 skipped).
  • npm run check:fast — typecheck and lint successful (2 tasks).
  • npx prettier --check on the six touched TypeScript files — clean.
  • Rebased on d0253c99d; npm run test:local -- src/pcap/PcapConfiguration.test.ts src/pcap/PcapOutput.test.ts there — 26 passed.
  • After the pre-epoch and oracle follow-ups: npm run test:local on the same two files — 26 passed; npm run test:focused on the three boundary files — 30 passed, 1 skipped; npm run check:fast — passed; npm run build:cached then REA_TSHARK_COMMAND=/usr/bin/tshark node scripts/verify-packet-capture.mjs with TShark and capinfos 4.2.2 on Linux x64 — passed (exit 0), so the product and the oracle agree on real capinfos output.

Compatibility, safety, and release

  • Breaking changes or migration steps: none in types. The two string values change spelling as described under contract impact.
  • Real Hopper/Ghidra, browser, or OS coverage: real TShark and capinfos 4.2.2 on Linux through scripts/verify-packet-capture.mjs; no other provider involved.
  • Package or release metadata impact: none.
  • Security, privacy, process, or containment review: one additional read-only flag to an already invoked tool; no new process authority.

Author readiness

Human owner: @mertkayacs

  • I reviewed the final diff and relevant existing code, understand the change and its edge cases, and take responsibility for the complete PR, including AI-assisted work.
  • I checked the evidence above, confirmed it supports the current change, and recorded meaningful validation gaps.
  • I declare this PR ready for initial maintainer review.

Before merge

  • This PR addresses a concrete problem or an agreed enhancement.
  • Material scope and authority questions are resolved, and any required maintainer scope decision covers the final diff.
  • The PR has one focused outcome and the title follows type(scope): outcome.
  • Related issue is linked, or the reason for not linking one is stated above.
  • Tests cover changed observable behavior and meaningful failure paths.
  • Owning docs, contracts, and generated metadata are updated where needed.
  • User-visible CLI/MCP changes include representative output.
  • I checked the final diff for secrets, unrelated cleanup, and unsupported claims.

🔄 This issue represents a GitHub Pull Request. It cannot be merged through Gitea due to API limitations.

## 📋 Pull Request Information **Original PR:** https://github.com/morluto/rea/pull/4909 **Author:** [@mertkayacs](https://github.com/mertkayacs) **Created:** 10/17/2026 **Status:** ❌ Closed **Base:** `main` ← **Head:** `fix/pcap-utc-capture-instants` --- ### 📝 Commits (4) - [`82bc2f5`](https://github.com/morluto/rea/commit/82bc2f580dd15495e090c66d60439bd92ad9b0da) test(pcap): expect capture instants as UTC from epoch capinfos timestamps - [`df3bd2d`](https://github.com/morluto/rea/commit/df3bd2d39f1dd1c9e18693a04a7e70a0d87288ec) fix(pcap): report capture instants in UTC instead of host-local time - [`9c57278`](https://github.com/morluto/rea/commit/9c57278c5ed5fa327581a1ff45df06fda822ca2c) fix(pcap): keep pre-epoch capture instants exact - [`4dac658`](https://github.com/morluto/rea/commit/4dac658c41663d6fc0159dca832e6a43f0fe4c54) test(pcap): read the real capinfos oracle from epoch seconds ### 📊 Changes **9 files changed** (+111 additions, -24 deletions) <details> <summary>View changed files</summary> 📝 `scripts/verify-packet-capture.mjs` (+15 -5) 📝 `src/pcap/PcapConfiguration.test.ts` (+39 -6) 📝 `src/pcap/PcapOutput.ts` (+39 -6) 📝 `src/pcap/PcapProvider.ts` (+1 -1) 📝 `tests/boundary/mcp/pcapMcp.test.ts` (+2 -2) 📝 `tests/boundary/pcap/pcapProvider.test.ts` (+5 -1) 📝 `tests/boundary/pcap/pcapProviderCrossPlatform.test.ts` (+2 -0) 📝 `tests/fixtures/pcap/capinfos-stub.cmd` (+5 -2) 📝 `tests/fixtures/pcap/capinfos-stub.sh` (+3 -1) </details> ### 📄 Description ## Summary `inspect_pcap_capture` reports `capture_start` and `capture_end` as the text capinfos prints by default, which is the packet time in the host's local time zone with no zone marker. The same capture therefore reads `2026-10-11 14:13:40.608505` on one host and `2026-10-11 16:13:40.608505` on another, and neither string names an instant. This change runs capinfos with `-S`, which prints exact epoch seconds, and projects that value to an RFC 3339 UTC instant that keeps every fractional digit (`2026-10-11T14:13:40.608505Z`). Fixes #4907. ## Problem and expected behavior Trigger: `rea inspect-pcap-capture <file> --json` on any capture with at least one packet, on a host whose `TZ` is not UTC. REA runs `capinfos -M -- <file>` with `LC_ALL=C` but inherits `TZ`. capinfos formats `Earliest packet time` with `format_nstime_as_iso8601(..., local = true, ...)`, which uses `localtime` and appends no `Z` or offset (wsutil/to_str.c, `format_nstime_as_iso8601`; capinfos.c, `absolute_time_string`). With `-S` the same function path calls `display_epoch_time`, which prints the `nstime` seconds and the capture's fractional precision verbatim (capinfos.c `time_as_secs`, wsutil/to_str.c `display_signed_time`). Violated invariant: a reported timestamp must identify one instant independently of the analyzing host. REA's own timestamp contracts require an explicit UTC offset (`offsetDateTimeSchema`), and AGENTS.md asks that identity values stay separate from display formatting. A zone-less local string is a display spelling of the host, not an identity of the capture. Expected: `capture_start` and `capture_end` are the same bytes for the same capture on every host, carry an explicit zone, and keep the producer's exact precision. ## Change and scope - `PcapProvider` runs `capinfos -M -S -- <snapshot>`. - `parseCapinfosOutput` projects `[-]<seconds>[.<fraction>]` to `YYYY-MM-DDTHH:MM:SS[.<fraction>]Z` from the integer seconds and the fraction digits, so nothing is rounded or extended; a pre-epoch value (capinfos prints a leading minus) borrows from the next whole second so the instant stays exact (`-1.250000` is `1969-12-31T23:59:58.750000Z`). `n/a` (empty capture), a wall-clock spelling from a capinfos run without `-S`, and a value outside years 0000 to 9999 stay `null`, which the schema already allows. - `scripts/verify-packet-capture.mjs` (the real-TShark job) reads its capinfos oracle with `-S` too and projects the epoch text independently of the product before comparing. - Not included: the `capture_duration_seconds` path (already a plain number), tshark field selection, and any change to the result schema types. Contribution scope: ordinary fix inside the existing capinfos read boundary; not applicable. ## Contract and boundary impact - Semantic owner and earliest changed stage: `src/pcap/PcapOutput.ts` (`parseCapinfosOutput`), the capinfos metadata owner; transport or result projection. - Contribution scope fit and linked maintainer decision: existing read-only capability; not applicable. - CLI and MCP/tool-catalog contract: `capture_start` and `capture_end` keep their `string | null` type. Their value changes from a host-local, zone-less spelling to an RFC 3339 UTC instant. Consumers that parsed the old spelling as local time of the analyzing host need to read the new one as UTC. - Provider, bridge, target-format, or platform compatibility: `-S` exists in every Wireshark 3.x and 4.x capinfos (documented in the capinfos man page). The Windows and POSIX stubs under `tests/fixtures/pcap/` were updated to accept it. - Evidence, artifact, provenance, or reconstruction contract: the producer's exact seconds and fractional digits are preserved; unknown spellings become `null` instead of being reported as observed text. - Target selection, authority, upstream behavior, and cleanup details: unchanged. - Generated metadata, package, or installation impact: none. Expected effects: - [x] Read existing files or captured data. - [ ] Launch or attach to a process. - [ ] Inject instrumentation or run scripts/code. - [ ] Change process memory, registers, files, or configuration. - [ ] Contact network services. - [ ] Recover protected code or work around protections. - [ ] Documentation or usability only; no target interaction. - [ ] Not sure yet; investigation or maintainer guidance needed. ## Evidence and regression coverage - Tests added or updated: `src/pcap/PcapConfiguration.test.ts` (real-output fixture now uses `-S` spellings; new case covers a fraction-less value, a fraction with trailing zeros, `n/a` and a wall-clock spelling), `tests/boundary/pcap/pcapProvider.test.ts` and `pcapProviderCrossPlatform.test.ts` (stub capinfos rejects a call without `-S`, and the projected `capture_start`/`capture_end` are asserted end to end through the provider), `tests/boundary/mcp/pcapMcp.test.ts` (fixture values), `tests/fixtures/pcap/capinfos-stub.{sh,cmd}`. - Base reproduction or other evidence: executed with capinfos 4.2.2 on a two-packet capture (output in #4907): the default spelling is `2026-10-11 17:13:40.608505` under `TZ=Europe/Istanbul` and `2026-10-11 14:13:40.608505` under `TZ=UTC`, with no marker; `-S` prints `1791728020.608505` under both. This matches Wireshark `capinfos.c` and `wsutil/to_str.c` as cited above; `src/pcap/PcapCommand.ts` sets `LC_ALL`/`LANG` but not `TZ`. On the base revision `2fb5e204a35723f6f843d56ca47b0db94d8fcc44` the test commit alone fails: `PcapConfiguration.test.ts` 3 failed / 6 passed in its `parseCapinfosOutput` block, and the focused boundary run 12 failed / 18 passed / 1 skipped, because the stub refuses the `-M -- <path>` argument list and the old spelling is reported. - User-visible CLI/MCP output: `"capture_start": "2026-10-11T14:13:40.608505Z"` instead of `"capture_start": "2026-10-11 14:13:40.608505"`. - Remaining proof gaps: the Windows `.cmd` stub path was not executed here (Linux only). - [x] Observed, derived, and inferred claims remain distinguishable. - [x] Artifact identity, source provenance, and failed attempts remain preserved. - [x] Unsupported, incomplete, unavailable, or uncertain outcomes remain visible. ## Validation performed - `npm run test:local -- src/pcap/PcapConfiguration.test.ts src/pcap/PcapOutput.test.ts` — 26 passed (on the test-only commit: 3 failed in `parseCapinfosOutput`, 17 of 20 passed in that file). - `npm run test:focused -- tests/boundary/pcap/pcapProvider.test.ts tests/boundary/pcap/pcapProviderCrossPlatform.test.ts tests/boundary/mcp/pcapMcp.test.ts` — 30 passed, 1 skipped (on the test-only commit: 12 failed, 18 passed, 1 skipped). - `npm run check:fast` — typecheck and lint successful (2 tasks). - `npx prettier --check` on the six touched TypeScript files — clean. - Rebased on `d0253c99d`; `npm run test:local -- src/pcap/PcapConfiguration.test.ts src/pcap/PcapOutput.test.ts` there — 26 passed. - After the pre-epoch and oracle follow-ups: `npm run test:local` on the same two files — 26 passed; `npm run test:focused` on the three boundary files — 30 passed, 1 skipped; `npm run check:fast` — passed; `npm run build:cached` then `REA_TSHARK_COMMAND=/usr/bin/tshark node scripts/verify-packet-capture.mjs` with TShark and capinfos 4.2.2 on Linux x64 — passed (exit 0), so the product and the oracle agree on real capinfos output. ## Compatibility, safety, and release - Breaking changes or migration steps: none in types. The two string values change spelling as described under contract impact. - Real Hopper/Ghidra, browser, or OS coverage: real TShark and capinfos 4.2.2 on Linux through `scripts/verify-packet-capture.mjs`; no other provider involved. - Package or release metadata impact: none. - Security, privacy, process, or containment review: one additional read-only flag to an already invoked tool; no new process authority. ## Author readiness Human owner: @mertkayacs - [x] I reviewed the final diff and relevant existing code, understand the change and its edge cases, and take responsibility for the complete PR, including AI-assisted work. - [x] I checked the evidence above, confirmed it supports the current change, and recorded meaningful validation gaps. - [x] I declare this PR ready for initial maintainer review. ## Before merge - [x] This PR addresses a concrete problem or an agreed enhancement. - [x] Material scope and authority questions are resolved, and any required maintainer scope decision covers the final diff. - [x] The PR has one focused outcome and the title follows `type(scope): outcome`. - [x] Related issue is linked, or the reason for not linking one is stated above. - [x] Tests cover changed observable behavior and meaningful failure paths. - [x] Owning docs, contracts, and generated metadata are updated where needed. - [x] User-visible CLI/MCP changes include representative output. - [x] I checked the final diff for secrets, unrelated cleanup, and unsupported claims. --- <sub>🔄 This issue represents a GitHub Pull Request. It cannot be merged through Gitea due to API limitations.</sub>
bsakaguchi 2026-11-01 21:42:12 +00:00
Sign in to join this conversation.
No milestone
No project
No assignees
1 participant
Notifications
Due date
The due date is invalid or out of range. Please use the format "yyyy-mm-dd".

No due date set.

Dependencies

No dependencies set.

Reference
morluto/rea#6670
No description provided.