73 lines
3.2 KiB
Markdown
73 lines
3.2 KiB
Markdown
# Defect: Windows scan timestamps lose their UTC offset
|
|
|
|
## Status
|
|
|
|
Open and reproducible in the accepted Windows worker package validated on
|
|
2026-09-25. The live validation did not modify product code so that the tested
|
|
package remained identical to the accepted artifact.
|
|
|
|
## Symptom
|
|
|
|
Windows `scan.result_error` diagnostics can be stored with `occurred_at` about
|
|
the machine's local UTC offset in the future. In the validation environment the
|
|
offset was approximately three hours. The same naive timestamps also populate
|
|
Windows `target_scans.started_at` and `target_scans.ended_at`.
|
|
|
|
This can misorder diagnostics, distort time-window filters, and make the admin
|
|
panel show a scan event later than the server receipt that contains it.
|
|
|
|
## Live evidence
|
|
|
|
The accepted Windows package ran with a fresh state directory and one slot. In
|
|
the expanded live cohort, all 45 Windows `scan.result_error` diagnostics had an
|
|
`occurred_at` to server `received_at` delta between approximately 10,816 and
|
|
10,819 seconds. All 135 normal Windows scan-result rows used naive local start
|
|
and end timestamps with the same approximately three-hour displacement when
|
|
treated as UTC. The four Windows timeout-path rows and all 102 Linux rows had
|
|
normal small timing deltas.
|
|
|
|
The retained raw scanner material contained an explicit `+03:00` timestamp.
|
|
The persisted diagnostic retained the same wall-clock digits but labeled them
|
|
as UTC with `Z`. Monotonic scan durations and worker progress transport
|
|
timestamps remained correct.
|
|
|
|
Sensitive raw targets, scanner output, and credentials are retained only in the
|
|
restricted live evidence file and are intentionally not reproduced here.
|
|
|
|
## Root cause
|
|
|
|
`app/scanner.py` creates scan timestamps with naive local datetimes:
|
|
|
|
- `scan_target_result()` uses `datetime.now().isoformat()` for
|
|
`scan_started_at` and `timestamp` near lines 16339 and 16416.
|
|
- fallback result construction in `scan_single_target()` does the same near
|
|
lines 16844, 16863, and 16877.
|
|
|
|
Diagnostic construction parses those values and, when no timezone is present,
|
|
uses `occurred.replace(tzinfo=timezone.utc)` near line 16581. That operation
|
|
relabels local wall-clock time as UTC instead of converting it. The error is
|
|
visible on non-UTC hosts and is hidden on UTC Linux hosts.
|
|
|
|
## Expected behavior
|
|
|
|
All persisted protocol timestamps must identify a real UTC instant. Scanner
|
|
result timestamps should be emitted as timezone-aware UTC values, and legacy
|
|
naive values must not be silently reinterpreted as known UTC instants.
|
|
|
|
## Suggested correction and regression coverage
|
|
|
|
Emit `datetime.now(timezone.utc).isoformat()` at every result-construction site
|
|
and preserve the offset through serialization. Add a non-UTC-host regression
|
|
test that verifies:
|
|
|
|
- scanner start/end timestamps identify the actual UTC instant;
|
|
- diagnostic `occurred_at` precedes or closely tracks server `received_at`;
|
|
- Windows and Linux admin time-window filters return the same logical events;
|
|
- monotonic duration fields remain unchanged.
|
|
|
|
## Validation artifact
|
|
|
|
The expanded unrestricted evidence is retained at
|
|
`build/live-trace-20260925/raw-evidence-expanded.json`. It contains sensitive
|
|
raw internals and must not be published as a general operator report.
|