The Dot Was in the Class
Written later and filed to this date, as part of filling in every day of the year.
The commit landed at one in the morning, from an agent working a finding an independent reviewer had filed. Its subject: fix(etc-durability): refuse a cache name that hops out of /etc before it reaches a root command. The body opens on the mechanism:
[the tool] renders
sudo install ... '/etc/<name>'from a cache under [the user’s state directory] that any process at this uid can write. Commit 4e0f16d guarded the name with a character class, and.is in that class, so../../root/.ssh/authorized_keyspassed and rendered a root install of attacker bytes outside /etc for the operator to paste.
Some context the message assumes. /etc on the box is an overlay that the next OS update discards, so the defender keeps a cache of the hardening files it has put there and renders a runbook, a block of commands I paste into a root shell, to put them back. The cache lives under my home directory. The commit two weeks earlier had seen the danger in that and fixed the part of it that was visible: a cached file could smuggle in the heredoc terminator and turn the rest of its content into root commands, so the terminator is now chosen so that it cannot appear in the content, and “an entry whose name or mode is not a safe shape is refused”. Its message ends on a principle I still agree with: for a runbook, a line the operator cannot see is worse than a line that is missing.
The safe shape it chose was a regular expression: one or more of letters, digits, dot, underscore, dash, then any number of slash-separated groups of the same. Read as a list of characters it is conservative. No spaces, no quotes, nothing that could break out of the single quotes around the path. Read as a list of paths it allows .., because .. is two characters from the list, and it allows /, because an entry can live in a subdirectory. Together they admit a name that walks up two levels and down into root’s SSH directory, and the renderer prints sudo install of attacker-chosen bytes to that path, between two correct lines, in the same font.
What the second author understood is in one sentence of the new source comment: a character class alone is not a traversal check, “because .. is made of the same characters as any dotfile name”. The first guard had answered “which characters are safe?”, which is the question a shell-quoting bug asks. The hop is not a character problem. It is a path problem, and the check has to be made on the path the name resolves to, not on the letters it is spelled with. So the name is now vetted per segment, no . and no .. and no empty segment, then resolved against /etc and required to still start with /etc/. Two checks, one on the characters and one on the path, which is the right redundancy for a line that goes into a root shell.
The regression test feeds the renderer a tampered cache. On the old code it fails with a second sudo install line in the output, aimed at /etc/../../root/.ssh/authorized_keys. The agent reproduced that on the previous commit before changing anything, and said so.