From 16edccf6d259a213fe733f2359684d6f29f427fc Mon Sep 17 00:00:00 2001 From: "pulse-triage[bot]" <249995291+pulse-triage[bot]@users.noreply.github.com> Date: Sun, 11 Oct 2026 05:03:08 +0100 Subject: [PATCH] Clarify Docker-only tokens in host authentication triage Separate a registered Docker reporting token from the scopes needed by the host module. Keep scope rejection distinct from HTTP 401 and identify the token-generation screen before changing credentials or reinstalling. Contract-Neutral: Documentation and source-alignment controls only; no authentication, token authority, runtime or install contract changes. Change-source: pulse-maintainer --- docs/UNIFIED_AGENT.md | 16 ++++++++++ frontend-modern/public/docs/UNIFIED_AGENT.md | 16 ++++++++++ scripts/tests/test_agent_auth_docs.py | 32 ++++++++++++++++++++ 3 files changed, 64 insertions(+) diff --git a/docs/UNIFIED_AGENT.md b/docs/UNIFIED_AGENT.md index 100056f600..b7eb55ca73 100644 --- a/docs/UNIFIED_AGENT.md +++ b/docs/UNIFIED_AGENT.md @@ -1112,6 +1112,22 @@ service is presenting the same token. Last-used metadata is not a token-use audit and does not identify which consumer authenticated. These observations do not prove that the running service is using that record. +**If the listed record has only Docker reporting:** `docker:report` permits +Docker/Podman telemetry, not the host module's lookup or OS, CPU and disk +reports. A normal host needs **Agent reporting** (`agent:report`) and +**Agent config fetch** (`agent:config:read`). Command execution and administrator +scope are not required. Once authentication succeeds, a missing reporting scope +returns 403 with `missing_scope`; Docker-only scope does not explain an earlier +401 or prove which credential the service presented. + +The **Infrastructure → Install on a host → Generate token** flow includes both +host scopes automatically. **API Access → Create token** allows a manually +selected scope set. If the intended host record is Docker-only, retain which +screen generated it before choosing a repair; do not generate another token, +broaden its permissions, reinstall or probe the agent just to distinguish the +two flows. Inventory presence and `Never` alone establish neither successful +credential hand-off nor accepted reporting. + **If the intended record is absent:** separate server registration from the agent's credential hand-off before another installation. On that same instance, open **Settings → Infrastructure → Install on a host**, give one new install diff --git a/frontend-modern/public/docs/UNIFIED_AGENT.md b/frontend-modern/public/docs/UNIFIED_AGENT.md index 100056f600..b7eb55ca73 100644 --- a/frontend-modern/public/docs/UNIFIED_AGENT.md +++ b/frontend-modern/public/docs/UNIFIED_AGENT.md @@ -1112,6 +1112,22 @@ service is presenting the same token. Last-used metadata is not a token-use audit and does not identify which consumer authenticated. These observations do not prove that the running service is using that record. +**If the listed record has only Docker reporting:** `docker:report` permits +Docker/Podman telemetry, not the host module's lookup or OS, CPU and disk +reports. A normal host needs **Agent reporting** (`agent:report`) and +**Agent config fetch** (`agent:config:read`). Command execution and administrator +scope are not required. Once authentication succeeds, a missing reporting scope +returns 403 with `missing_scope`; Docker-only scope does not explain an earlier +401 or prove which credential the service presented. + +The **Infrastructure → Install on a host → Generate token** flow includes both +host scopes automatically. **API Access → Create token** allows a manually +selected scope set. If the intended host record is Docker-only, retain which +screen generated it before choosing a repair; do not generate another token, +broaden its permissions, reinstall or probe the agent just to distinguish the +two flows. Inventory presence and `Never` alone establish neither successful +credential hand-off nor accepted reporting. + **If the intended record is absent:** separate server registration from the agent's credential hand-off before another installation. On that same instance, open **Settings → Infrastructure → Install on a host**, give one new install diff --git a/scripts/tests/test_agent_auth_docs.py b/scripts/tests/test_agent_auth_docs.py index c4af9fa4a7..9ace3488fd 100644 --- a/scripts/tests/test_agent_auth_docs.py +++ b/scripts/tests/test_agent_auth_docs.py @@ -131,6 +131,38 @@ class AgentAuthDocsTest(unittest.TestCase): "not, by itself, a reason to mint a replacement"): self.assertIn(fact, text) + def test_docker_only_record_is_not_host_authority_or_a_401_diagnosis(self): + text = " ".join(section().split()) + routes = (ROOT / "internal/api/router_routes_registration.go").read_text() + for endpoint, required in (("docker/report", "ScopeDockerReport"), + ("agent/report", "ScopeAgentReport"), + ("agent/lookup", "ScopeAgentReport")): + self.assertIn( + f'"/api/agents/{endpoint}", RequireAuth(r.config, ' + f'RequireScope(config.{required},', routes, + ) + labels = (ROOT / "frontend-modern/src/constants/apiScopes.ts").read_text() + for constant, value, label in ( + ("AGENT_REPORT_SCOPE", "agent:report", "Agent reporting"), + ("AGENT_CONFIG_READ_SCOPE", "agent:config:read", "Agent config fetch"), + ): + self.assertIn(f"export const {constant} = '{value}';", labels) + self.assertIn(f"value: {constant},\n label: '{label}'", labels) + self.assertIn(f"**{label}** (`{value}`)", text) + scopes = (ROOT / "internal/api/agenttokens/install.go").read_text() + host = scopes.split("func HostScopes(", 1)[1].split("\n}", 1)[0] + for required in ("ScopeAgentReport", "ScopeAgentConfigRead"): + self.assertIn(f"config.{required}", host) + self.assertNotIn("ScopeWildcard", host) + self.assertNotIn("ScopeSettingsWrite", host) + for fact in ("If the listed record has only Docker reporting", "`docker:report`", + "not the host module's lookup", "scope does not explain an earlier 401", + "Command execution and administrator scope are not required", + "includes both host scopes automatically", "manually selected scope set", + "retain which screen generated it", "do not generate another token", + "broaden its permissions, reinstall or probe the agent"): + self.assertIn(fact, text) + if __name__ == "__main__": unittest.main()