diff --git a/admin/scripts/check_html_safety.py b/admin/scripts/check_html_safety.py
index 57023b3..8209d67 100644
--- a/admin/scripts/check_html_safety.py
+++ b/admin/scripts/check_html_safety.py
@@ -68,7 +68,9 @@
Coverage holes are printed, never hidden. A module whose `__artifacts_v2__` is not a
static literal cannot have its `html_columns` read, so `unguarded-html-columns` cannot
-run on it; those are listed as NOT CHECKED on every run.
+run on it; those are listed as NOT CHECKED on every run. The other two rules do still
+run on them -- an unreadable declaration is treated as a sink rather than as a
+non-sink, so the uncertainty costs coverage of one rule instead of all three.
`scripts/artifact_report.py` is deliberately out of scope. It implements the
escape/no-escape branch itself, so it is the sink these rules protect, not a producer.
@@ -444,7 +446,13 @@ def scan_file(path, rel_path):
# Elsewhere the writer escapes the whole cell, so a hand-built tag renders as
# visible text -- a display bug, not an injection, and not this check's business.
in_scope_functions = FRAMEWORK_FUNCTIONS.get(rel_path)
- is_sink = bool(declares) or uses_media_helper(tree) or in_scope_functions
+ # `declares` is None when __artifacts_v2__ could not be read statically. Treat
+ # that as a sink: an unreadable declaration is the one case where we cannot tell,
+ # and guessing "not a sink" turns all three rules off silently. A module can then
+ # gain an html_columns and stay unguarded with nothing in the output changing.
+ # Assume the worst when the answer is unknown.
+ is_sink = (declares is None or bool(declares) or uses_media_helper(tree)
+ or in_scope_functions)
findings = []
if is_sink:
@@ -515,7 +523,8 @@ def main():
if skipped:
print(f'NOT CHECKED -- {len(skipped)} module(s) have no statically readable '
- f'__artifacts_v2__, so html_columns could not be read:')
+ f'__artifacts_v2__, so html_columns could not be read. They are still '
+ f'scanned for unescaped markup and remote destinations:')
for rel_path, reason in skipped:
print(f' {rel_path}: {reason}')
print()