Skip to content

Reject keys that are invalid for an explicit bare or literal key type - #630

Open
choudhryfrompak wants to merge 1 commit into
python-poetry:masterfrom
choudhryfrompak:validate-explicit-key-types
Open

choudhryfrompak wants to merge 1 commit into
python-poetry:masterfrom
choudhryfrompak:validate-explicit-key-types

Conversation

@choudhryfrompak

@choudhryfrompak choudhryfrompak commented Oct 3, 2026 •

Copy link
Copy Markdown

Summary

SingleKey only escapes basic keys. If you pass t=KeyType.Bare or t=KeyType.Literal yourself, the key text goes between the delimiters as is. A newline, =, a quote or a dot in the key then changes the structure of the dumped document:

import tomlkit
from tomlkit.items import KeyType, SingleKey

doc = tomlkit.document()
doc.add(SingleKey('user = "x"\nadmin', KeyType.Bare), True)
doc.add(SingleKey("x' = 1\n[evil]\n'y", KeyType.Literal), 2)
print(tomlkit.dumps(doc))
# user = "x"
# admin = true
# 'x' = 1
# [evil]
# 'y' = 2

That parses back as {'user': 'x', 'admin': True, 'x': 1, 'evil': {'y': 2}}, not as two keys. Same on 0.15.1 from PyPI.

With this change SingleKey raises ValueError when an explicit type can't represent the key. A bare key has to be non-empty and use only A-Za-z0-9_-. A literal key can't contain ' or a control character other than tab, which is the rule StringType.SLL.invalid_sequences already has for literal strings. Basic keys were already escaped and haven't changed. Keys from the parser pass original and skip the check, and so do keys whose type is picked automatically (tomlkit.key(), doc["..."], table.add("...")). I moved the bare character check into a small _is_bare_key() helper so the automatic choice and the new check use the same rule.

This can break code that used KeyType.Bare to write a dotted header on purpose, e.g. SingleKey("tool.poetry", t=KeyType.Bare). I found that in one small project on GitHub. It already gave an inconsistent document (doc.unwrap() has a "tool.poetry" key while the output parses as nested tables), and table(is_super_table=True) gives the same [tool.poetry] header properly. If you'd rather keep dots allowed for bare keys, I can relax it.

Tests are in tests/test_items.py: invalid bare and literal keys raise, and valid keys of each explicit type round-trip through parse. The 8 rejection cases fail on master. The full suite passes with the toml-test submodule checked out (1080 passed, 1068 before), and ruff and ruff-format are clean on the changed files.

SingleKey only escapes basic keys. When a caller passes t=KeyType.Bare or
t=KeyType.Literal, the key text was written between the delimiters as-is,
so a key containing a newline, "=", a quote or a dot changed the structure
of the dumped document instead of being one key:

    doc.add(SingleKey('user = "x"\nadmin', KeyType.Bare), True)
    dumps(doc)  # 'user = "x"\nadmin = true\n'

Raise ValueError when the key cannot be written in the requested form:
bare keys must be non-empty and use only A-Za-z0-9_-, and literal keys
must not contain a single quote or a control character other than tab
(the same rule as single-line literal strings). Keys built by the parser
pass their original text and are not affected, nor are keys whose type
is chosen automatically.
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant