charset declared after the first 1024 bytes
The encoding declaration must be fully inside the first 1024 bytes or the parser never sees it.
- Avoid
- Harmful
- Severity
- HarmfulActively breaks something for users.
- Basis
- SpecifiedThe evidence is in a current standard.
- Detectable
- YesMatched exactly. Autofixable when the rule carries a fix.
- Matches
logic moduleDocument rule, inside <head>.- Fix
- None — reports only
- Impacts
- Interoperability and Security
- Related
meta/http-equiv-x-ua-compatible
Before a browser can parse a document it has to decide how to decode the bytes. With no
HTTP charset parameter and no byte order mark, it runs a prescan: it reads the start
of the byte stream looking for an encoding declaration, and the HTML Standard caps that
prescan at 1024 bytes. An encoding declaration that is not completely serialized inside
that window does not exist as far as the parser is concerned.
Why avoid
Past the window, the browser falls back to a locale-dependent guess and may then discover
the real declaration mid-parse — at which point it has to throw away the tree and reparse
the document from the beginning. The visible failure is mojibake: café rendered as
café for some visitors and correctly for others, depending on their locale, which is
exactly the kind of bug that does not reproduce on the developer’s machine.
It is a security boundary as well as a correctness one. When the encoding is guessed rather than declared, an attacker who controls part of the page can influence how the rest of it is decoded, which is the mechanism behind the classic UTF-7 XSS: markup that is inert under UTF-8 becomes active script under a guessed encoding.
The rule is easy to trip without noticing. A long <title>, a block of Open Graph tags,
an inline critical-CSS <style>, or a comment banner ahead of the declaration will each
push it past 1024 bytes, and nothing warns you.
Use instead
Put the declaration first, before anything else in <head>:
<!doctype html>
<html lang="en">
<head>
<meta charset="utf-8">
<title>…</title>
</head>
</html>
Sending Content-Type: text/html; charset=utf-8 on the response is stronger still — it
is authoritative and needs no prescan — but keep the <meta> too, so the file stays
correct when it is opened from disk or served by something you do not control.
Detectability
Detectable wherever the original source text is available, which is why this is a
kind: "document" rule: no selector can express “ends after byte 1024”. The logic
compares the declaration’s end offset against the limit, so it reports in the CLI and the
ESLint plugin but stays silent in the browser adapter, where a live DOM node has no
source offsets to compare.
Two deliberate limits. It measures offsets in characters rather than bytes, and any
non-ASCII byte ahead of the declaration only makes the true count larger — so the check
can under-report but never invents a finding. And it looks at <meta charset> only: an
http-equiv="Content-Type" declaration is bound by the same 1024 bytes, but deciding
whether one is a valid declaration means parsing its content, and that is a separate
rule.
Resources
- HTML Standard — character encoding declaration — “must be serialized completely within the first 1024 bytes”.
- HTML Standard — prescan a byte stream to determine its encoding — the 1024-byte cap, and the reparse that follows a late discovery.
- W3C Internationalization — declaring character encodings — the practical guidance, including the HTTP header interaction.
This page is generated from content/rules/head/charset-position.md, the same file the linter is built from. Think the rule is wrong, or that browsers moved on? Say so — that is the most useful issue you can file.