Merge f-0213-secure (fix 0855796e6) into f-0213-int

Conflicts in strata_cli/bench_facts.py (the redactor), three hunks: kept every side. The patterns stay QATUI's (the key and the names are matched as whole tokens, a hyphen next to the key does not hide it, the names are not touched inside a label the function wrote), each entry carries SECURE's third field (is a login or host name) for redact_document, and the host names come from socket.gethostname() and sysinfo.node() (LIFE), both marked as names.
This commit is contained in:
Niko1221 committed 2026-11-04 17:40:48 +01:00
commit 792b626980
3 files changed
+68 -11

No files matched your search

+44 -9
View File
@@ -53,9 +53,9 @@ def redactor(key: str = ""):
The key is replaced only where it stands as a whole token and in its own case (#4483): a short key is also a common word or a part of a hash. A hyphen
next to it does not make it part of a longer word (`pack-<key>`, `<key>-1`: the key was a leak there); letters, digits and the underscore do.
The names (user, host) are not touched inside a label this function wrote: redacting a text twice gives the same text (a user called "user" gave `<<user>>`)."""
subs: list[tuple[re.Pattern, str]] = []
subs: list[tuple[re.Pattern, str, bool]] = [] # (pattern, label, is a login or host name)
def add(value, label, word=False, exact=False):
def add(value, label, word=False, exact=False, name=False):
if value and len(str(value)) >= 4:
text = str(value)
for form in sorted({text, text.replace("\\", "/"), text.replace("/", "\\")}, key=len, reverse=True):
@@ -64,17 +64,17 @@ def redactor(key: str = ""):
pattern = r"(?<!\w)" + pattern + r"(?!\w)"
elif word:
pattern = r"(?<![\w<-])" + pattern + r"(?![\w>-])"
subs.append((re.compile(pattern, 0 if exact else re.I), label))
subs.append((re.compile(pattern, 0 if exact else re.I), label, name))
add(ROOT, "<strata>")
add(Path.home(), "<home>")
add(tempfile.gettempdir(), "<tmp>")
try:
add(getpass.getuser(), "<user>", True)
add(getpass.getuser(), "<user>", True, name=True)
except Exception: # noqa: BLE001 - no user name is not an error
pass
add(socket.gethostname(), "<host>", True)
add(sysinfo.node(), "<host>", True)
add(socket.gethostname(), "<host>", True, name=True)
add(sysinfo.node(), "<host>", True, name=True)
add(usable_key(key), "<key>", word=True, exact=True)
subs.sort(key=lambda item: -len(item[0].pattern))
path_user = re.compile(r"(?i)([A-Za-z]:)?([\\/])(Users|home)([\\/])(?!<)[^\\/\s\"':;,]+")
@@ -82,17 +82,52 @@ def redactor(key: str = ""):
r"|169\.254)\.\d{1,3}\.\d{1,3}\b")
bearer = re.compile(r"(?i)(bearer\s+)[A-Za-z0-9._~+/=-]{8,}")
def clean(text):
def clean(text, names=True):
"""names=False leaves a login or host name that is also an ordinary word ("user", "test", "admin") alone: for fixed
schema text, where the word is not the machine's name; paths, addresses and the key are still replaced."""
if not isinstance(text, str):
return text
for pattern, label in subs:
text = pattern.sub(label, text)
for pattern, label, is_name in subs:
if names or not is_name:
text = pattern.sub(label, text)
text = path_user.sub(lambda m: f"{m.group(1) or ''}{m.group(2)}{m.group(3)}{m.group(4)}<user>", text)
text = bearer.sub(r"\1<key>", text)
return private_ip.sub("<ip>", text)
clean.structural = lambda text: clean(text, names=False)
return clean
def redact_values(value, clean):
"""redact_object for free text: the strings are replaced, the keys stay."""
if isinstance(value, dict):
return {k: redact_values(v, clean) for k, v in value.items()}
if isinstance(value, list):
return [redact_values(v, clean) for v in value]
return clean(value) if isinstance(value, str) else value
def redact_document(doc: dict, clean) -> dict:
"""The benchmark report as results.json carries it (#4484): the text an operator or a server wrote (hardware, model and configuration
descriptions, error messages, engine options, request checks) gets the full redaction; everything else is fixed schema text (field
names, enum values, status reasons) and only loses paths, addresses and the key, so a login called "user" or "test" does not turn
the report's own words into <user>."""
structural = getattr(clean, "structural", clean)
out = redact_object(doc, structural)
for block in ("hardware", "model", "configuration"):
if block in doc:
out[block] = redact_values(doc[block], clean)
for i, record in enumerate(doc.get("requests") or []):
if isinstance(record, dict) and isinstance(record.get("error"), dict):
out["requests"][i]["error"] = {**out["requests"][i]["error"],
"message": redact_values(record["error"].get("message"), clean)}
extensions = doc.get("extensions")
if isinstance(extensions, dict):
for field in ("other_engine_options", "request_checks"):
if field in extensions:
out["extensions"][field] = redact_values(extensions[field], clean)
return out
def redact_object(value, clean):
if isinstance(value, dict):
return {clean(k) if isinstance(k, str) else k: redact_object(v, clean) for k, v in value.items()}
+1 -1
View File
@@ -1007,7 +1007,7 @@ def run(target: Target, out: Path | None, *, out_root: Path | None = None, optio
"request_checks": {rid: v for rid, v in extras.items()},
"optional_stage_skipped": skipped_optional,
"api_key_sent": bool(target.key)}}
doc = facts.redact_object(doc, redact) # results.json gets what the README and the other files get (#4484)
doc = facts.redact_document(doc, redact) # results.json gets what the README and the other files get (#4484)
if doc["run"]["optional_duration_s"] is None:
why.add("/run/optional_duration_s", "the optional concurrency stage was not selected" if not optional_concurrency
else "the optional concurrency stage did not run")
+23 -1
View File
@@ -580,7 +580,9 @@ class RunTests(StandardBase):
self.server.script["performance-prompt-4k-r2"] = {"cached": spec["input_tokens_frozen"] - 10}
# the shared template prefix alone is allowed
self.server.script["performance-prompt-4k-r1"] = {"cached": 40}
doc, _ = self.run_standard()
# whatever the login of the machine running the test is: "user" is an ordinary word of the report, not a name here
with mock.patch.object(bench_facts.getpass, "getuser", return_value="no-such-login"):
doc, _ = self.run_standard()
self.assertEqual(doc["run"]["status"], "nonconforming")
row = self.summary(doc, "prompt-4k", "prompt_tps")
self.assertEqual(row["n"], 2)
@@ -859,6 +861,26 @@ class CommandLineTests(StandardBase):
self.assertIn("<ip>", doc["configuration"]["track_notes"])
self.assertEqual(doc["request_counts"]["performance"]["planned"] > 0, True) # counters are untouched
def test_a_login_that_is_an_ordinary_word_does_not_rewrite_the_reports_own_words(self):
"""INT3 on l620, where the login is exactly "user": the report's fixed text (status reasons, enum values, field names) came out as
"<user>", while the operator's own text still loses the name."""
spec = corpus_specs()["performance-prompt-4k-r2"]
out = self.root / "wordlogin"
with mock.patch.object(bench_facts.getpass, "getuser", return_value="user"), \
mock.patch.object(bench_facts.socket, "gethostname", return_value="summaries"), \
mock.patch.object(bench_facts.platform, "node", return_value="summaries"):
self.server.script["performance-prompt-4k-r2"] = {"cached": spec["input_tokens_frozen"] - 10}
code, _, errors = self.invoke("--url", self.server.url, "--out", str(out), "--track", "custom",
"--track-notes", "ask the user at summaries time")
self.assertEqual(code, 6, errors)
doc = bs.load(out)
self.assertEqual(bs.validate(doc), [])
self.assertTrue(any("unexpected reuse of the user payload in performance-prompt-4k-r2" in r
for r in doc["extensions"]["status_reasons"]), doc["extensions"]["status_reasons"])
self.assertEqual(doc["configuration"]["track_notes"], "ask the <user> at <host> time")
text = (out / "results.json").read_text(encoding="utf-8")
self.assertIn('"summaries"', text) # a field name that equals the host name stays a field name
def test_a_credential_of_none_is_no_key_and_the_word_is_left_alone(self):
"""#4483: STRATA_KEY=none was sent as "Bearer none" and the word was replaced wherever it occurred."""
out = self.root / "nonekey"