Skip to content

Repository files navigation

VeilFill

Agent maps. Human approves. Browser fills.

VeilFill is a privacy-minimizing WebMCP prototype for a fictional scholarship application. An agent receives opaque, short-lived claim handles and the minimum semantic metadata needed to map them to form fields. It never needs the fictional name, address, email address, or income value. A person reviews the staged disclosure map, and only the browser can resolve approved handles into a local-only submission payload.

The project was built for the OpenAI WebMCP Challenge. Open the public VeilFill demo.

VeilFill mapping cockpit showing the Request, Map, and Review workflow

The claim, precisely scoped

“0 raw values crossed the WebMCP tool boundary” means that the current run's canary instrumentation found none of the vault's exact fictional raw values in:

  • arguments passed into VeilFill's WebMCP tool handlers; or
  • structured results returned from those handlers.

The number is measured from the run, not hard-coded. The receipt also reports canary detections in the rendered VeilFill DOM, patched console/debug paths, and monitored fetch, XMLHttpRequest, and sendBeacon calls.

This is not a claim of zero agent PII exposure in every context. Claim existence, semantic type, freshness, verification level, field purpose, and required period are metadata, and the agent can see them. An agent may also know information from chat or personalization. VeilFill demonstrates a data-minimization pattern for a cooperative site; it does not make WebMCP itself a vault or protect against malicious same-origin code, XSS, browser extensions, DevTools, or memory inspection. See the threat model for the complete boundary and limitations.

Why capability mapping?

A direct-value form tool can place a name, address, or income directly in agent-visible tool arguments and history. VeilFill separates the decision which evidence fits this field? from the authority disclose this value.

The scholarship scenario makes that decision non-trivial. For the household-income field, the agent must distinguish monthly self-reported income, individual annual income, and issuer-verified 2025 household income using meaning, period, freshness, and verification metadata—without seeing any amount. Address candidates similarly distinguish a current residence from a mailing address.

Architecture and trust boundary

flowchart LR
  Agent["Agent"] -->|"handles, field IDs, metadata"| Tools["WebMCP tools"]
  Tools --> Actions["Shared validated action layer"]
  Actions --> Stage["Staged disclosure map"]
  Stage --> Review["Human review + consent"]

  subgraph Browser["Browser-local application"]
    Vault["In-memory closure vault\nfictional raw values"]
    Review -->|"human-only action"| Resolve["One-time local resolution"]
    Vault --> Resolve
    Resolve --> Recipient["Same-page mock recipient"]
  end

  Vault -. "no raw-value WebMCP edge" .-> Tools
Loading

There are two data planes:

Plane Contents Authority
Agent-visible Opaque session handles, semantic descriptors, scope, expiry, verification, purpose, field IDs, state versions, and safe audit IDs Read requirements and capabilities; stage and validate a proposal
Browser-local Fictional raw values held in an in-memory closure, human consent, one-time resolution state, and ephemeral local payload Human-only approval and local completion

Agent calls and simulator actions use the same validated domain controller. Mutation tools require expectedStateVersion, reject stale writes, and never modify the separate human-approval state. Handles are random per session, scoped to the current origin/form/allowed fields, short-lived, forge-resistant by lookup, and consumed once during local resolution.

There is no login, backend, database, local storage, IndexedDB, analytics pipeline, or external application recipient. Normal document and static-asset delivery still uses the network; the application itself performs no external scholarship submission.

WebMCP tool surface

VeilFill registers real tools through document.modelContext.registerTool when the experimental API is available.

Tool Lifecycle Role
get_application_requirements Mapping phase Return field purpose, accepted claim types, verification preference, and period
list_claim_capabilities Mapping phase Return only form-relevant opaque handles and semantic metadata
stage_claim_mappings Mapping phase Strictly validate and stage handle-to-field mappings against a state version
remove_optional_disclosures Mapping phase Withhold staged optional disclosures; safe no-op when none are staged
validate_disclosure_plan Mapping phase Check completeness, handle validity, scope, TTL, semantics, verification, period, uniqueness, and optional withholding
get_privacy_receipt After local completion only Return the measured, value-free privacy receipt

Only current WebMCP annotations are used: readOnlyHint and untrustedContentHint. After local completion, mapping tools are unregistered and only the receipt tool is exposed.

These capabilities are deliberately not registered:

  • approve_disclosures
  • submit_application
  • resolve_claim_value
  • read_claim_value
  • reveal_vault

The absence of these tools is the consent boundary. An agent can stage a proposal; it cannot approve, resolve, or submit it.

Run locally

Use Node.js 24 for parity with CI (the package supports Node.js >=22.13.0). No account, secret, database, or service credential is required.

npm ci
npm run dev

Open the local URL printed by the development server, normally http://localhost:3000.

Simulator fallback

In a browser without document.modelContext, the app explicitly reports “Local simulator — WebMCP unavailable.” Choose Run local simulator to exercise the same requirement, mapping, optional-withholding, validation, and receipt action layer. Simulator trace rows are labeled local_simulator; they are never presented as external agent calls.

After five required mappings are ready:

  1. Review each staged mapping and confirm that phone and demographic claims are withheld.
  2. Check the human consent box.
  3. Choose Approve 5 & submit locally.
  4. Inspect the generated Privacy Receipt. The recipient is the same-page Local scholarship demo; no external application is sent.

Test with real WebMCP

WebMCP remains experimental and browser behavior can change. Follow the current Chrome WebMCP documentation for the authoritative setup. For local Chrome testing at the time of this submission:

  1. Open chrome://flags/#enable-webmcp-testing.

  2. Set WebMCP for testing to Enabled and relaunch Chrome.

  3. Load the app from a real HTTP origin such as http://localhost:3000 in a normal browser tab.

  4. Confirm that the UI separately reports API support and the number of registered tools.

  5. Open the page in a ChatGPT or Codex environment that can discover page WebMCP tools, then send:

    Map the minimum valid disclosure plan. Exclude every optional claim. Do not submit.

  6. Watch the live Tool Trace, state version, Veil Rail, and Disclosure Review update from the external calls.

  7. Complete consent and local submission yourself; the agent has no tool for either action.

The presence of document.modelContext or registered tools does not prove that an agent is connected. VeilFill reports an external call only after it receives one. Chrome documents WebMCP as a human-in-the-loop local-browser API; headless automation is not the authoritative real-agent smoke test.

Verification

Run the release checks independently:

npm run typecheck
npm run lint
npm run test:unit
npm run test:privacy
npm run build
npm run test:render

The privacy suite covers the vault/action invariants and exact-canary inspection surfaces. The registration tests cover the mapping tool set, external-call routing, dynamic post-submit lifecycle, unsupported environments, and registration failures. The rendered-output test inspects the production build.

Browser UI and accessibility checks are available separately:

npx playwright install chromium
npm run test:e2e

Set PLAYWRIGHT_BASE_URL to run the same browser matrix against an already deployed revision without starting the local development server.

Playwright is intentionally not part of the conservative GitHub Actions job because native experimental WebMCP is designed for interactive browser workflows and has not been treated as reliable in generic headless CI. The real WebMCP path should be smoke-tested manually in an enabled, agent-connected browser before submission.

Verified release results on Node.js 24: 24 unit tests, 2 production-render tests, and 6 Playwright scenarios passed; 6 additional Playwright cases were intentionally skipped by their opposite device project. The same browser matrix passed against the public deployment. Axe reported zero violations before and after local submission, the production build completed, and npm audit reported zero vulnerabilities.

Privacy instrumentation

The detector keeps the exact fictional canary set inside its own closure and publishes content-free events only: channel, inspected count, leak count, status, timestamp, and an opaque audit ID. It inspects nested WebMCP inputs/results, rendered text and attributes, live form-control values, debug/console calls, common percent-encoded URL forms, and bounded request bodies on selected outbound browser APIs. The Privacy Receipt is a numeric projection of those events plus application-state counts such as semantic descriptors shared, human-approved disclosures, withheld optional fields, inspected tool calls, and observed state versions.

Canary instrumentation is evidence for this controlled demo, not a general information-flow proof. Adversarially encoded, transformed, partial, inferred, or exfiltrated values on unpatched channels may not match an exact canary. The audit log is in memory and is not tamper-evident. These limits are documented in docs/THREAT_MODEL.md.

Project map

  • app/ — responsive workbench, comparison view, human consent, and receipt UI
  • lib/veilfill/ — closure vault, capability policy, validation, simulator, and shared state controller
  • lib/webmcp/ — strict input schemas and registration lifecycle
  • lib/privacy/ — content-free canary auditing and browser-surface instrumentation
  • tests/ — domain, privacy, registration, render, accessibility, and browser-flow checks
  • docs/SUBMISSION.md — English challenge submission copy and testing instructions
  • docs/DEMO_SCRIPT.md — 90–120 second narration and under-three-minute shot list
  • docs/DESIGN_DIRECTION.md — reference lock and interface decisions
  • docs/THREAT_MODEL.md — assets, trust boundaries, mitigations, and residual risk

License

Released under the MIT License.

About

Agent maps. Human approves. Browser fills — a privacy-minimizing WebMCP scholarship demo.

Topics

Resources

Stars

0 stars

Watchers

0 watching

Forks

Releases

Packages

Contributors

Languages