84 lines
2.9 KiB
Python
84 lines
2.9 KiB
Python
"""Security regression (audit finding): the human-readable ``enroll explain``
|
|
text output embeds harvested, attacker-influenceable values (host name, captured
|
|
file paths shown as examples, include/exclude patterns, snapshot notes). A value
|
|
containing a raw newline could forge an additional output line, and a control
|
|
byte could smuggle a terminal escape sequence when the explanation is printed to
|
|
a terminal or written with ``--out``. These must be neutralised. The JSON output
|
|
is unaffected (json.dumps escapes control characters and cannot be restructured
|
|
by a string value).
|
|
"""
|
|
|
|
from __future__ import annotations
|
|
|
|
import json
|
|
|
|
from enroll.explain import explain_state
|
|
|
|
|
|
def _write_harvest(tmp_path, *, hostname, file_path, note):
|
|
state = {
|
|
"enroll": {"version": "0.1", "harvest_time": 1},
|
|
"host": {
|
|
"hostname": hostname,
|
|
"os": "debian",
|
|
"pkg_backend": "dpkg",
|
|
"os_release": {},
|
|
},
|
|
"inventory": {"packages": {}},
|
|
"roles": {
|
|
"etc_custom": {
|
|
"role_name": "etc_custom",
|
|
"managed_files": [
|
|
{
|
|
"path": file_path,
|
|
"src_rel": "etc/x",
|
|
"owner": "root",
|
|
"group": "root",
|
|
"mode": "0644",
|
|
"reason": "custom_unowned",
|
|
}
|
|
],
|
|
"managed_dirs": [],
|
|
"excluded": [],
|
|
"notes": [note],
|
|
}
|
|
},
|
|
}
|
|
d = tmp_path / "harvest"
|
|
(d / "artifacts" / "etc_custom" / "etc").mkdir(parents=True)
|
|
(d / "state.json").write_text(json.dumps(state))
|
|
(d / "artifacts" / "etc_custom" / "etc" / "x").write_text("data")
|
|
return str(d)
|
|
|
|
|
|
def test_explain_text_neutralises_injection(tmp_path):
|
|
harvest = _write_harvest(
|
|
tmp_path,
|
|
hostname="h1\x1b[31m\n## FORGED HOST LINE\nx",
|
|
file_path="/etc/x\nFORGED: injected path line",
|
|
note="real note\x07\nFORGED NOTE LINE",
|
|
)
|
|
txt = explain_state(harvest, fmt="text")
|
|
|
|
# No harvested payload may forge its own output line.
|
|
for line in txt.splitlines():
|
|
stripped = line.lstrip()
|
|
assert not stripped.startswith("## FORGED HOST LINE")
|
|
assert not stripped.startswith("FORGED:")
|
|
assert not stripped.startswith("FORGED NOTE LINE")
|
|
# No terminal escape / control bytes survive into the printed text.
|
|
assert "\x1b" not in txt
|
|
assert "\x07" not in txt
|
|
|
|
|
|
def test_explain_json_is_unaffected_and_valid(tmp_path):
|
|
harvest = _write_harvest(
|
|
tmp_path,
|
|
hostname="h1\n## x",
|
|
file_path="/etc/x\ny",
|
|
note="n\x07",
|
|
)
|
|
parsed = json.loads(explain_state(harvest, fmt="json"))
|
|
# JSON faithfully preserves the raw values (escaped) and stays structurally
|
|
# valid; the control bytes are encoded, not executable.
|
|
assert parsed["host"]["hostname"].startswith("h1")
|