zwesty
Write-ups / Day 3 / Metadata
Day 3 · Sat, Oct 3 · Metadata

BOM Voyage

Prey slaughtered

Byte order marks · reading text as bytes, not as an editor shows it · not trusting file names · reusing earlier days' zip handling

STTMClaude Homebase Both

Flags are shortened to their first 8 and last 4 characters because they're unique per team.

Challenge ID
metadata-forensics-day-03-bom-voyage (+ /time_trial, /mastery)
Solved
2026-10-07, all three modes, in 5 min 35 s from the first start (14:47:58 → 14:53:33 UTC)
Player
Zwesty (solo), with Claude Code (Opus 5.5)

The challenge

A text export pipeline produced files that look identical in an editor but behave differently in scripts. Find the encoding landmines before they break the investigation.

One question per file, the same field every time:

Determine text.utf8_bom_present for <file>.

Answer format: an enum, true or false, trimmed.

Mode answer_id File asked about
Single Solve evidence-note-text-utf8-bom-present metadata-day-03-evidence.zip -> evidence-note.txt
Time Trial roster-text-utf8-bom-present … -> roster.csv
Mastery set1.evidence-note-copy-text-utf8-bom-present metadata-day-03-evidence-01.zip -> evidence-note-copy.txt
set2.d03-text-001-text-utf8-bom-present metadata-day-03-evidence-02.zip -> export-001-with-bom.txt

Reading the prompt: what it said we'd need

Clue What it pointed to What I did about it
"BOM Voyage" (the title) A pun on bon voyage. BOM = byte order mark Look at the first bytes of each file
"Look identical in an editor" Something invisible: editors hide a BOM, and line endings too Read the files as raw bytes, never as displayed text
"Behave differently in scripts" Scripts do see a BOM, for example as junk before a CSV's first column name Answer from the bytes alone
"Encoding landmines" Several kinds of hidden encoding trait (BOMs, line endings, UTF-16) The solver reports all of them, but answers only the field asked
text.utf8_bom_present (the field name) A family of text.* fields; this is one member Answer this one exactly, and stop on any other text.* field rather than guess
Mastery file export-001-with-bom.txt A name that makes a claim, the Day 1–2 lesson that labels can lie Never read the answer from the name
Same zips and names as Days 1–2 The Metadata lane compounds: Hash to Ride → Looks Can Be Deceiving → this Build on Day 2's solver

What a BOM is

A byte order mark is an optional marker at the very start of a text file that says which Unicode encoding it uses:

Encoding BOM bytes
UTF-8 EF BB BF
UTF-16 little-endian FF FE
UTF-16 big-endian FE FF
UTF-32 little-endian FF FE 00 00

UTF-8 doesn't need one, but some Windows tools add it (Notepad in older versions, PowerShell 5.1's Out-File -Encoding utf8). Editors hide it, so two files can look identical while one starts with three extra bytes. Scripts trip over it: a CSV's first header becomes name instead of name, so row["name"] fails, and a line-1 comparison like == "Case ..." comes out false.

What was in the files

Every file was tiny, plain ASCII text. Only the first bytes (and, once, the line endings) differed:

Mode File Bytes Starts with BOM Line endings Answer
Single Solve evidence-note.txt 33 43 61 73 65 "Case" none LF false
Time Trial roster.csv 29 68 6f 73 74 "host" none LF false
Mastery set 1 evidence-note-copy.txt 33 43 61 73 65 "Case" none LF false
Mastery set 2 export-001-with-bom.txt 32 EF BB BF 45 78 "…Ex" UTF-8 CRLF true

The contents, as bytes (the case IDs are random per variant):

evidence-note.txt        Case 3DF9200ADB1A evidence note.\n
roster.csv               host,case\nWS-01,0DFBA6451415\n
evidence-note-copy.txt   Case E189CF04FA4B evidence note.\n
export-001-with-bom.txt  \xEF\xBB\xBF Export 1\r\nCase 68240FF2A75D\r\n

In an editor, the last file would look like any other two-line text file. As bytes, it has two landmines: the BOM, and Windows CRLF line endings, the only CRLF file seen in the event so far. A script splitting on \n would leave a stray \r at the end of Case 68240FF2A75D, and an exact match on the case ID would fail.

The zips: the Single Solve and Time Trial were one zip each. Mastery was one outer zip with the two set zips nested inside (729 B in total). All local and central name records agreed. The Hash to Ride trap wasn't used this time, but the solver still checked it.

The name …-with-bom.txt told the truth here. That's still no reason to trust it: the next variant could carry the same name without a BOM, and the solver would get it right either way.

Single Solve: CORRECT in 28 seconds

Started at 14:47:58 UTC. The zip (205 B) was downloaded with its SHA-256 matching. Its one member started 43 61 73 65, with no EF BB BF. false was CORRECT at 14:48:26, flag be80373f…a4d0.

The solver

Our Day 3 solver loads Day 2's Looks Can Be Deceiving solver by path, which in turn loads Day 1's Hash to Ride. That gives it all the zip handling for free: - names from the zip's own headers; - the local/central name check; - nested and stored zips; - per-set scoping for Mastery.

It adds one field: - …-text-utf8-bom-present: true only if the file's first three bytes are exactly EF BB BF. - false for a UTF-16 or UTF-32 BOM, and for a BOM that isn't at the very start. - The file name is never consulted. - Any other text.* field stops the run before submitting, rather than guessing.

It also prints every file's hidden traits (BOM type, CRLF/LF/CR counts, trailing newline, NUL, and non-ASCII bytes), ready for whichever text.* field a later day asks about.

6 tests, all passing before any timed run: - the real Single Solve zip; - true and false cases; - a file named …-with-bom.txt without one, a UTF-16 BOM, and a BOM after a space, all false; - the Mastery shape: two zips, an answer ID that doesn't match the file name, one zip stored; - an unknown text.* field, which stops; - the trait counts.

Time Trial: 1.94 s of a 6-second clock

started_at 2026-10-07T14:52:16.656Z  deadline 2026-10-07T14:52:22.656Z
  artifact metadata-day-03-evidence.zip 187 bytes sha256 941acab50c421c8f… (checked)
  roster-text-utf8-bom-present: false
  start         726 ms
  artifact      390 ms
  solve           1 ms
  submit        819 ms
  TOTAL        1936 ms
result CORRECT  flag 40db8a6f…f2a6

Mastery: 3.05 s of a 5-second clock, first attempt

started_at 2026-10-07T14:53:30.784Z  deadline 2026-10-07T14:53:35.784Z
  artifact metadata-forensics-day-03-bom-voyage-mastery.zip 729 bytes sha256 ed058314fa5b627b… (checked)
  set1.evidence-note-copy-text-utf8-bom-present: false
  set2.d03-text-001-text-utf8-bom-present: true
  start        1883 ms
  artifact      336 ms
  solve           1 ms
  submit        834 ms
  TOTAL        3053 ms
result CORRECT  flag 62d5bae1…2d95

This was the tightest margin of the day, under 2 seconds spare. Mastery's clock (5 s) was shorter than the Time Trial's (6 s), and the start call alone took 1.9 s, like every Mastery start on 2026-10-07 (1.75 s, 1.80 s, 1.88 s). The solve itself took 1 ms; nearly all the time is network and server.

Time to solve

All three modes were solved on 2026-10-07 by 14:53:33 UTC, 5 min 35 s after the first start. The Single Solve took 28 s, and the Time Trial (1.94 s of 6 s) and the Mastery (3.05 s of 5 s) each went through on the first submit.

Takeaways

  • Read text as bytes when it matters. An editor's view hides BOMs, CRLF, and UTF-16. xxd, od -c, or Python's repr(bytes) show the truth.
  • A name that makes a claim is a claim, not evidence. …-with-bom.txt happened to be right, but the solver never relied on it.
  • Answer only what was asked, but record the rest. CRLF turned up unasked in Mastery. It's noted, and the solver already counts it, ready for a future text.line_endings-style field. Meanwhile, an unknown field stops the run instead of guessing.
  • Compounding lanes reward reuse. Three days of Metadata solvers stack, so this one only needed the new field. The Single Solve took 28 s.
  • Mastery start calls are the bottleneck. All three Mastery starts on 2026-10-07 took 1.75–1.9 s each, against 0.5–0.7 s for other starts. With a 5-second clock that leaves about 2 seconds, so if Mastery clocks shrink further, speeding up our own network calls stops being optional.
  • Landmines are everywhere. While writing up this challenge, a real BOM character slipped into the middle of a line in our notes. It was found by searching for the bytes EF BB BF and replaced with the visible text .