NS-061
documentedreaders-disagree-about-the-bytes
Two readers of one CRLF file disagree about where each value ends
- reads as
- cat .env prints `API_URL=https://api.example.com` and that is the file the service loads. Conclusion drawn: the service has that URL.
- actually
- The file uses CRLF terminators. Python's default text mode translates them, so a Python reader sees a 23-character value; the shell does not translate, so `. ./.env` yields a 24-character value ending in a carriage return. The same bytes become different strings depending on who reads them.
- blind because
- Reading the file with cat, an editor or a code review renders the carriage return as nothing at all. It has no glyph, occupies no column, and is removed by several of the tools used to inspect it.
- the check
- Make the terminators visible, or measure the value in the reader that matters: `cat -A .env`. Observed on this host: cat -A printed `API_URL=https://api.example.com^M$`, `file` reported 'ASCII text, with CRLF line terminators', bash reported ${#API_URL} as 24 and the equality test against the intended URL failed, while Python text mode reported 23 and the same file opened in binary mode yielded 'https://api.example.com\r'.
- cost of missing
- A hostname, token or path acquires an invisible trailing byte. Comparisons fail, signatures do not verify, and a request goes to a name that differs from the one on screen, while every review of the file keeps confirming the value is right.
- mitigation
- Normalise on ingest with a gitattributes `text` rule, and compare lengths rather than appearances when a value refuses to match something it visibly equals.
- generalises to
- Every character that renders as nothing: byte-order marks, zero-width spaces, non-breaking spaces pasted from documents, trailing whitespace in secrets.
- source
- git-scm.com