01 · Install
Quick start
Today, PageCTRL ships as one dependency-free browser file. Self-host that reviewed file and load it before any script that registers WebMCP tools.
<script src="/pagecontrol.js"></script>
<script type="module">
const guard = window.PageControl;
await guard.init({
appName: "My Store",
budget: { limit: 300, currency: "USD" },
defaultMode: "allow",
defaultMaxPerMinute: 30,
tools: {
add_to_cart: { maxAmount: 200, maxQty: 5 },
checkout: { mode: "approve", chargesBudget: true },
delete_account: { mode: "deny" },
},
});
// Register tools through PageControl, then lock the reviewed surface.
await guard.registerTool(myTool);
guard.seal();
</script>The SDK must see tool registration to wrap it. In Next.js, load the script with beforeInteractive.
The script tag installs the in-page guard. Server credentials are needed only when an integration adds signed checkout.
02 · Configure
Keep credentials on the server
The browser SDK needs no secret. Signed checkout adds two server environments: the merchant app and the separate PageCTRL signing service. Create two different random secrets with openssl rand -base64 32.
Merchant app
Add these values to the repository-root .env.local file for local work, or to the merchant app's Vercel environment for deployment.
PAGECONTROL_SERVICE_TOKENUsed by: the merchant server when requesting a signed grant. Create it: run openssl rand -base64 32, then paste the same output into Vercel and Render.
PAGECONTROL_DASHBOARD_SESSION_SECRETUsed by: the merchant dashboard to sign and encrypt its session cookies. Create it: run the command again and keep this different value only in Vercel.
PAGECONTROL_API_URLUsed by: the merchant server to request grants and download the verification key. Value: https://api.pagecontrol.app.
# Merchant app — .env.local or Vercel
PAGECONTROL_API_URL=https://api.pagecontrol.app
PAGECONTROL_SERVICE_TOKEN=PASTE_THE_SHARED_RANDOM_SECRET
PAGECONTROL_DASHBOARD_SESSION_SECRET=PASTE_A_DIFFERENT_RANDOM_SECRETSigning service
Add these values to the Render service environment. The PAGECONTROL_SERVICE_TOKEN value must match the merchant app exactly. The dashboard session secret does not belong in Render. Generate the Ed25519 key with openssl genpkey -algorithm ED25519 -outform DER | base64 and paste its output directly into Render.
PAGECONTROL_SERVICE_TOKENUsed by: Render to authenticate the merchant server. Value: exactly the same random token stored in Vercel.
PAGECONTROL_PRIVATE_KEYUsed by: Render to sign each 60-second checkout grant. Storage: Render only—never Vercel, browser code, or Git.
PAGECONTROL_ALLOWED_ORIGINSUsed by: Render to restrict which storefronts may receive grants. Format: comma-separated exact origins with no paths or trailing slashes.
# Signing service — Render
PAGECONTROL_SERVICE_TOKEN=PASTE_THE_SHARED_RANDOM_SECRET
PAGECONTROL_PRIVATE_KEY=PASTE_THE_ED25519_PRIVATE_KEY
PAGECONTROL_ALLOWED_ORIGINS=https://your-store.example
PAGECONTROL_ISSUER=https://api.pagecontrol.appPublic verification key
You do not copy a public key into the script or .env. Render derives it from PAGECONTROL_PRIVATE_KEY and publishes it as a public JWK. The merchant server downloads it automatically from PAGECONTROL_API_URLwhile verifying a grant.
- Open the PageCTRL public JWKSafe to inspect and share. It verifies signatures but cannot create them.
Do not add NEXT_PUBLIC_ to either name. After changing a deployed variable, redeploy the affected service.
- Vercel environment variablesWhere to store the merchant app values and apply them to a deployment.
- Render environment variablesWhere to store the signing token, private key, and allowed origins.
- Merchant dashboardSee the two environments side by side without exposing production secrets.
03 · Understand
One guarded execution path
When native WebMCP exists, PageCTRL registers wrapped tools with document.modelContext. It mirrors the active context to navigator.modelContext for older clients.
- 1Validate
Reject malformed inputs before merchant code runs.
- 2Enforce
Resolve modes, rate limits, quantities, amounts, and reserved budget.
- 3Ask
Keep sensitive calls pending until a person allows or blocks them.
- 4Record
Redact sensitive strings and append a hash-linked journey entry.
If native WebMCP arrives after page load, PageCTRL watches for 10 seconds and migrates already-guarded tools. Without native support, the same pipeline remains available through its demo shim.
04 · Align
One guard, two beneficiaries
PageCTRL faces both directions. It gives people the confidence to delegate a task, while giving merchants a controlled WebMCP surface instead of unrestricted automation.
Keep the final say
- Spending boundary
The agent cannot exceed the session budget or per-action caps.
- Approval before impact
Address changes and checkout can stop for a real decision.
- Immediate pause
The kill switch stops new calls and denies pending approvals.
- Readable evidence
A redacted journey shows what ran, what was blocked, and why.
Open the store without opening chaos
- Validated inputs
Malformed arguments stop before merchant code executes.
- Operational limits
Rate, quantity, amount, and budget rules contain runaway agents.
- Sealed tool surface
A third-party script cannot silently replace a reviewed tool.
- Shared accountability
The flight recorder gives both sides the same call history.
The merchant sets the minimum protection. The person may make it stricter, but cannot weaken that baseline.
05 · Register
Register a guarded tool
A tool uses the normal WebMCP definition. The optional label and guard fields stay inside PageCTRL and are removed before native registration.
await PageControl.registerTool({
name: "add_to_cart",
label: "Add to cart",
description: "Add one catalog item to the cart.",
inputSchema: {
type: "object",
properties: {
id: { type: "string", minLength: 1 },
qty: { type: "integer", minimum: 1 },
},
required: ["id", "qty"],
},
annotations: { readOnlyHint: false },
guard: {
getQty: ({ qty }) => qty,
getCost: ({ id, qty }) => catalog[id].price * qty,
},
execute: async ({ id, qty }, { signal }) => {
return JSON.stringify(await cart.add(id, qty, { signal }));
},
});Returns the quantity checked against maxQty.
Returns the amount checked against caps and session budget.
Pass through to native WebMCP unchanged.
Registration and execution cancellation propagate safely.
It calls document.modelContext.getTools() after setup and whenever the native toolchange event fires. A browser-reported tool that PageCTRL did not wrap is shown as unguarded.
06 · Control
Layer merchant and user policy
The merchant sets the minimum protection. A user can make it stricter immediately. Moving back toward the merchant setting requires explicit confirmation.
allowRun after validation and limits pass.approveWait for a human decision.denyReturn a plain-language refusal.// Tightening a rule applies immediately.
PageControl.setUserPolicy("checkout", { mode: "deny" });
// Reducing a user-added restriction requires an explicit human confirmation.
PageControl.setUserPolicy(
"checkout",
{ mode: "approve" },
{ humanConfirmed: true },
);07 · Decide
Use the built-in human approval UI
PageCTRL renders its own keyboard-accessible approval dialog with Run once and Block actions. Unanswered requests deny themselves after 60 seconds. Pausing the guard denies every pending request.
The dialog needs no merchant UI code. Public events contain an opaque display handle, never the internal approval id. After seal(), only a browser-trusted click in PageCTRL's controls can resolve the request.
const stop = PageControl.on("approval", ({ pending }) => {
// Handles are display-only. They cannot settle an approval.
renderPendingApprovals(pending);
});
// Unsubscribe when your UI unmounts.
stop();08 · Reference
Client API
init(config)Set the merchant policy, session budget, default mode, and rate limit.
registerTool(tool, options?)Register a WebMCP tool through the guarded execution pipeline.
seal()Lock the reviewed tool surface and flag later replacements or additions.
on(event, callback)Subscribe to entries, tools, budget, approvals, alerts, state, or environment.
approve(id) / deny(id)Setup-only helpers. Both refuse after the reviewed surface is sealed.
pause() / resume()Stop every agent call immediately, then restore guarded execution.
setUserPolicy(name, rule, options?)Change a user rule without going below the merchant policy.
setBudget(limit, options?)Setup-only helper. The trusted panel owns budget changes after seal.
getPolicies()Read merchant, user, and effective policy maps.
getJourney() / exportJourney()Read or download the redacted, hash-chained flight record.
getEnvironment()Return native WebMCP or shim mode and the active API surface.
canInterceptNativeRegistration()Check whether direct modelContext.registerTool calls enter PageCTRL on this host.
getSurface()Compare browser-reported WebMCP tools with the tools PageCTRL wrapped.
explainLast()Return the plain-language reason for the most recent blocked call.
09 · Observe
Events
PageControl.on(event, callback) returns an unsubscribe function.
entryA new guarded call or system event was added to the journey.
approvalThe pending human-approval list changed.
alertTampering, late registration, migration trouble, or suspicious output was detected.
toolsThe registered tool surface or a tool's tamper status changed.
budgetReserved or spent session budget changed.
stateThe kill switch paused or resumed execution.
environmentPageCTRL entered native WebMCP or fallback shim mode.
surfaceThe guarded and unguarded browser-reported tool lists changed.
10 · Scope
Protect the action layer, not the whole internet
PageCTRL sees structured WebMCP calls inside the page. It does not sit on the network path, so it cannot see ordinary crawlers, DDoS traffic, or direct server attacks.
Edge security decides which automated traffic reaches a site. PageCTRL decides what an admitted agent may do through WebMCP.
11 · Verify
Trust and privacy boundary
The browser SDK calls no PageCTRL backend and sends no telemetry.
The redacted record survives a reload and disappears with the tab unless the user exports it.
Email addresses and long card-number-like strings are masked first.
An in-page guard cannot protect a user from the page owner itself.
Production distribution
This repository does not claim a live PageCTRL CDN. For production distribution, publish an immutable versioned asset and provide its real Subresource Integrity hash. The browser then rejects any changed file, including one served by a compromised CDN.
<script
src="https://your-cdn.example/pagecontrol/v1.0.0/pagecontrol.js"
integrity="sha384-YOUR_RELEASE_HASH"
crossorigin="anonymous">
</script>HTTPS authenticates the named origin. Subresource Integrity verifies the exact release bytes. Neither stops a malicious merchant from omitting the SDK or drawing a lookalike interface.
The approval component today
The SDK creates the approval dialog, styles it, traps keyboard focus, runs the countdown, and wires the decision. The merchant writes no approval component. Today that dialog still lives in the merchant page, so same-page scripts can inspect or imitate it.
A malicious site can omit the SDK or imitate its interface. Independent verification requires the browser or a companion extension.
12 · Extend
Roadmap: move trust outside the host page
These are deliberate next layers, not claims about the current release. Each one reduces merchant integration work or moves a trust decision into a stronger browser boundary.
- 1Versioned distribution
Publish immutable releases from a PageCTRL origin with a real SRI hash and CORS headers.
- 2Placement Web Component
Add an
<page-control-panel>custom element for layout only. The boot script must still load first so no tool registration escapes the guard. - 3Cross-origin approval frame
Serve the sensitive decision UI from a PageCTRL origin. The browser same-origin policy then prevents the host page from reading its internal DOM.
- 4Independent verification
A companion extension can warn when a site claims protection without loading the genuine release.
- 5Browser-owned prompt
Long term, the browser should own the unforgeable approval surface while PageCTRL supplies policy decisions.
<!-- Roadmap sketch — not available in v1.0.0. -->
<script
src="https://your-cdn.example/pagecontrol/v1.0.0/pagecontrol.js"
data-budget="300"
data-currency="USD">
</script>
<page-control-panel></page-control-panel>Architecture references
- Stripe Web ElementsSensitive fields are tokenized inside hosted Elements instead of touching the merchant server.
- MDN: Same-origin policyExplains the browser boundary that restricts cross-origin frame access.
- MDN: Custom elementsDefines the standards-based path to an
<page-control-panel>placement API. - Cloudflare bot solutionsShows why scraping and request-level bot control belong at the edge, outside PageCTRL.