feat(launcher): trusted client-instance identity for project-scoped launches
Issue #985. The production launcher could mint one trusted
GITEA_MCP_CLIENT_INSTANCE per launch, but two gaps kept real launches on
untrusted legacy-pid-* identities:
1. build_application_mcp_servers required a profile for all five sanctioned
namespaces, so a project-scoped configuration exposing only author,
reviewer, and merger could not use it without inventing controller and
reconciler profiles that must not exist.
2. The module produced configuration data but had no runnable entry point, so
every real launch bypassed it entirely.
Changes:
- resolve_launch_namespaces() validates an explicit namespace subset as an
allow-list; unknown, duplicate, and empty selections are refused rather than
silently narrowing a launch. Omitting it preserves five-namespace behaviour.
- build_application_mcp_servers() accepts that subset, requires profiles only
for the launched namespaces, starts only those workers, and reports
excluded_namespaces / project_scoped.
- collect_instance_ids_from_mcp_servers() inspects the launch's own namespaces
instead of an assumed five, and reports missing_servers, so a three-namespace
launch can prove shared attribution without reading as two absent workers.
- Runnable entry point: python3 -m mcp_application_launcher mints one trusted
identity, writes a per-launch 0600 mcpServers config, and execs the client.
CLIENT_LAUNCH_SPECS is a data-driven registry so other supported clients use
the same mint-once/propagate-to-all mechanism. --dry-run prints the plan.
- Provenance sealing now fails closed. The inst- format is public and
reproducible, so format alone could previously let anyone who set one
environment variable manufacture a trusted identity. Trust now additionally
requires GITEA_MCP_INSTANCE_PROVENANCE=trusted_launcher, which only the
launcher writes; a well-formed but unsealed value is classified
unsealed_launcher and refused, while still being reported for diagnosis.
Deliberate behaviour change: tests/test_issue_978_instance_fleet_snapshot.py
test_client_hints_trusted_when_set previously asserted that a well-formed ID
alone was trusted. It now supplies the launcher seal, and a new companion test
asserts the unsealed case fails closed. This tightens the contract; no
assertion was weakened.
No static or persistent per-project instance IDs are introduced, duplicate
worker and cohort detection are untouched, and no fleet or mutation gate is
relaxed.
Tests: tests/test_issue_985_project_scoped_launcher.py, 47 passed, 3 subtests.
Full suite from a branches/ worktree: 28 failed, 6252 passed, 6 skipped against
a master baseline at 32ab8392 of 28 failed, 6204 passed, 6 skipped; the failing
sets are byte-identical, so zero regressions and zero masked failures.
Closes #985
Co-Authored-By: Claude Opus 4.8 (1M context) <[email protected]>
This commit is contained in:
@@ -1032,9 +1032,43 @@ class ClientHintsTests(unittest.TestCase):
|
||||
)
|
||||
|
||||
def test_client_hints_trusted_when_set(self):
|
||||
"""Trusted requires the launcher seal as well as the format (#985).
|
||||
|
||||
Before #985 a well-formed ``inst-…`` value alone was accepted as
|
||||
trusted. The format is public and reproducible, so anyone able to set
|
||||
one environment variable could hand-write a valid-looking ID; #985
|
||||
therefore additionally requires the provenance marker that only the
|
||||
production launcher writes. This case now supplies the full sealed env
|
||||
the launcher actually emits.
|
||||
"""
|
||||
import gitea_mcp_server as server
|
||||
|
||||
inst = fleet.generate_client_instance_id("codex", launch_nonce="t", now=NOW)
|
||||
with mock.patch.dict(
|
||||
os.environ,
|
||||
{
|
||||
"GITEA_MCP_CLIENT": "codex",
|
||||
"GITEA_MCP_CLIENT_INSTANCE": inst,
|
||||
"GITEA_MCP_INSTANCE_PROVENANCE": (
|
||||
fleet.INSTANCE_ID_PROVENANCE_TRUSTED
|
||||
),
|
||||
"GITEA_MCP_FLEET_RUN_ID": "run-1",
|
||||
},
|
||||
clear=False,
|
||||
):
|
||||
hints = server._client_identity_hints()
|
||||
self.assertTrue(hints["instance_identity_trusted"])
|
||||
self.assertTrue(hints["instance_launcher_sealed"])
|
||||
self.assertEqual(hints["client_instance_id"], inst)
|
||||
self.assertEqual(hints["fleet_run_id"], "run-1")
|
||||
|
||||
def test_client_hints_wellformed_but_unsealed_not_trusted(self):
|
||||
"""#985: a valid-looking ID without the launcher seal fails closed."""
|
||||
import gitea_mcp_server as server
|
||||
|
||||
inst = fleet.generate_client_instance_id(
|
||||
"codex", launch_nonce="unsealed", now=NOW
|
||||
)
|
||||
with mock.patch.dict(
|
||||
os.environ,
|
||||
{
|
||||
@@ -1044,10 +1078,16 @@ class ClientHintsTests(unittest.TestCase):
|
||||
},
|
||||
clear=False,
|
||||
):
|
||||
os.environ.pop("GITEA_MCP_INSTANCE_PROVENANCE", None)
|
||||
hints = server._client_identity_hints()
|
||||
self.assertTrue(hints["instance_identity_trusted"])
|
||||
self.assertFalse(hints["instance_identity_trusted"])
|
||||
self.assertFalse(hints["instance_launcher_sealed"])
|
||||
# Still reported for diagnosis rather than silently discarded.
|
||||
self.assertEqual(hints["client_instance_id"], inst)
|
||||
self.assertEqual(hints["fleet_run_id"], "run-1")
|
||||
self.assertEqual(
|
||||
hints["instance_id_provenance"],
|
||||
fleet.INSTANCE_ID_PROVENANCE_UNSEALED,
|
||||
)
|
||||
|
||||
def test_malformed_user_supplied_instance_not_trusted(self):
|
||||
import gitea_mcp_server as server
|
||||
|
||||
Reference in New Issue
Block a user