security(docs): hide runtime provider failures from users

This commit is contained in:
2026-07-21 18:57:34 +02:00
parent 9dab1c842a
commit e9f3106657
2 changed files with 40 additions and 1 deletions

View File

@@ -113,7 +113,7 @@ def docs_context(
},
"evidence": {
"optional_modules": optional_modules,
"sources": _evidence_sources(),
"sources": _evidence_sources() if documentation_type == "admin" else [],
"documentation": documentation_layers["evidence"],
},
},
@@ -356,6 +356,8 @@ def _collect_documentation_topics(
try:
provided_topics = tuple(provider(context))
except Exception as exc:
if documentation_type != "admin":
continue
provided_topics = (
DocumentationTopic(
id=f"{manifest.id}.runtime-documentation-unavailable",

View File

@@ -151,6 +151,42 @@ class DocsContextTests(unittest.TestCase):
"values": ["*.example.invalid"],
})
def test_runtime_provider_failure_is_diagnostic_only(self) -> None:
def failing_provider(_context):
raise RuntimeError("sensitive provider detail")
registry = PlatformRegistry()
registry.register(ModuleManifest(
id="private-module",
name="Private module",
version="1.0.0",
documentation_providers=(failing_provider,),
))
principal = FakePrincipal({"docs:documentation:read"})
user_layers = _classify_documentation(
registry,
principal,
settings=None,
session=None,
documentation_type="user",
locale="en",
)
admin_layers = _classify_documentation(
registry,
principal,
settings=None,
session=None,
documentation_type="admin",
locale="en",
)
self.assertFalse(any(user_layers.values()))
self.assertEqual(
[topic["id"] for topic in admin_layers["evidence"]],
["private-module.runtime-documentation-unavailable"],
)
def test_docs_default_to_user_and_admin_projection_requires_admin_authority(self) -> None:
query_default = signature(docs_context).parameters["documentation_type"].default
self.assertEqual(query_default.default, "user")
@@ -184,6 +220,7 @@ class DocsContextTests(unittest.TestCase):
self.assertNotIn("scope_count", payload["actor"])
self.assertEqual(payload["layers"]["configured"]["permissions"], [])
self.assertEqual(payload["layers"]["available"]["routes"], [])
self.assertEqual(payload["layers"]["evidence"]["sources"], [])
def test_topic_anchor_is_stable(self) -> None:
self.assertEqual(