Conversation
WebMCP (Chrome 150+, behind a flag) lets a page register tools on itself with document.modelContext.registerTool(); an agent in the same tab finds them with getTools() and runs them with executeTool(). The exchange never leaves the browser -- no child process, no transport, no config file naming a server. Every surface `prismor discover` knew about is a file on disk: an agent by its hook config, an MCP server by its declaration, a key by an env var. The browser offers none of those, so a page exposing a transfer-funds tool to an in-tab agent appeared in none of the three sections and coverage still read 100%. The tool calls themselves are decided at runtime and are not observable from disk; this does not pretend otherwise. What is on disk is the precondition, and that is what gets reported: profiles with the experiment enabled (Local State, matched on substrings because the flag is renamed between milestones), and extensions that name the API in their own code. The known-id map only supplies a friendly name -- the source scan is the signal, since an allowlist cannot see an extension nobody has catalogued yet. Extensions that cannot reach page context are skipped, which is both correct (WebMCP is exposed to the page) and what keeps the scan off almost every bundle in a profile. These findings stay out of the coverage ratio. Prismor has no interception point inside a tab, so an enabled profile is not governable surface that was skipped -- it is surface nothing can cover yet, and scoring it would drive the number down with findings no --fix can clear. That is the distinction AgentRecord.coverable already draws for agents Prismor has no hook for, and report_payload reports these as coverable: false for the same reason. There is no --fix path for the section. Locations and names only. No history, no extension storage, no page contents. The control plane drops findings whose kind is outside agent|mcp|credential (normalizeFinding enum-checks it), so these rows report cleanly today and land in the console once that set is extended -- a one-line follow-up on the web side, not a blocker here.
os.walk returns filesystem order, arbitrary on ext4. With the scan ceilings the walk can stop early, so which files got read varied by platform -- the bounded-scan test passed on macOS and failed on Linux, and the same bundle could report a finding on one machine and not another. Sorting makes the early stop reproducible.
Adds the section to the host-discovery deep dive and the CLI reference, including why it is reported but never scored and why it takes no --fix.
Verified against Chrome 152: it ships two matching flags, enable-webmcp-testing and devtools-webmcp-support, and only the first takes document.modelContext from undefined to a live object. The finding text asserted page tool registration for both. Now states that the experiment is enabled and leaves it there, and the real flag names are pinned in a test.
|
Validated against a real browser (Chrome 152.0.7977.65), which the original implementation had not been. The flag names are real. The API name is confirmed. In a throwaway profile, It caught a wording bug (last commit). Only Still not covered, and honestly can't be from a disk sweep: a page actually registering tools against a live in-tab agent. That is the runtime half this PR explicitly does not claim to see. |
Closes #339.
What
Adds a fourth inventory to
prismor discover: the browser.WebMCP (Chrome 150+, behind a flag) lets a page register tools on itself with
document.modelContext.registerTool(); an agent in the same tab finds them withgetTools()and runs them withexecuteTool(). The exchange never leaves the browser — no child process, no transport, no config file naming a server — so a page offering atransfer-fundstool to an in-tab agent appeared in none of the three existing sections, and coverage still read 100%.What it does and does not claim
Which tools a page exposes is decided at runtime and is not observable from disk. This does not pretend otherwise. What is on disk is the precondition, and that is what gets reported, per Chromium-family browser (Chrome and its channels, Edge, Brave, Chromium, Arc):
Local State→browser.enabled_labs_experiments. Matched on substrings, because the flag gets renamed between milestones while the capability it gates stays the same; a pinned exact string would silently stop matching.modelContext. The known-id map (currently just the Model Context Tool Inspector) only supplies a friendly name; the source scan is the actual signal, since an allowlist cannot see an extension nobody has catalogued yet.Extensions that cannot reach page context are skipped. That is correct — WebMCP is exposed to the page, so an extension that never gets there is not calling it — and it is also what keeps the scan off almost every bundle in a profile. On my machine 30 extensions across 9 profiles narrow to 15 worth scanning, and the whole sweep runs in 0.2s.
Locations and names only. No history, no extension storage, no page contents — the same construction as
CredentialRecord, whose fields cannot hold a secret value.Why these findings stay out of the coverage score
Prismor has no interception point inside a browser tab. An enabled WebMCP profile is therefore not governable surface that was skipped; it is surface nothing can cover yet. Folding it into the ratio would drive the number down with findings no
--fixcan clear.That is the distinction
AgentRecord.coverablealready draws for agents Prismor has no hook for: reported, badged distinctly, excluded from the score. This section gets the same treatment — badgedno coveragerather thanSHADOW, reported ascoverable: falsein the wire payload, and given no--fixpath, because offering one would promise a remediation that does not exist.Notes for review
os.walkordering was a real bug, not just a test bug (second commit). The scan has file/byte ceilings, so the walk can stop early — andos.walkreturns filesystem order, which is arbitrary on ext4. Unsorted, which files got read varied by platform: the bounded-scan test passed on macOS and failed on Linux, and the same bundle could report a finding on one machine and not another. Found by running the suite on a real Linux box; fixed by sorting.normalizeFindinginlib/shadow-inventory.tsenum-checks againstagent|mcp|credentialand returns null otherwise, so these rows report cleanly (no 4xx) and land in the console once that set is extended — a one-line follow-up on the web side, not a blocker here.discover_webmcp(workspace)ignoresworkspace; the browser surface is a property of the machine, not of a checkout. The parameter is accepted so it matches every otherdiscover_*entry point.Testing
tests/test_discover_webmcp.py, against a fake$HOMElaid out for whichever platform the suite is running on, so the path enumeration is under test rather than stubbed. Covers both signals, the page-context filter, localised (__MSG_) names, the newest-version and multi-profile dedupes, malformed/missingLocal State, the scan ceilings, and that none of it movescoverageor the shadow counts.scripts/check_oss_safe.pyandtests/test_oss_guard.pypass.