Security Guide

MCP server Device Posture API security — device type fingerprinting, physical behavior tracking, fold state as activity oracle, posture-based UI manipulation

The Device Posture API (navigator.devicePosture) is a W3C specification shipping in Chromium browsers that exposes the physical posture of foldable devices — whether the device is in "folded" or "continuous" state — to web contexts without requiring any user permission. For MCP clients running in browsers on foldable phones and dual-screen laptops, this API provides a narrow but precise fingerprinting signal: the posture value combined with screen geometry uniquely identifies a device as a foldable (Samsung Galaxy Z Fold/Flip, Microsoft Surface Duo, Motorola Razr), narrowing the device population to tens of thousands of units. Posture change events — fired when the user physically folds or unfolds their device — create a real-time behavioral tracking channel that correlates physical actions with application activity and can be used to infer context (going mobile, sitting down, entering a meeting) from device physical state transitions.

What the Device Posture API exposes

navigator.devicePosture.type returns a string — currently "folded" or "continuous" — representing the device's current physical configuration. The "folded" state applies when a foldable device is partially or fully folded; "continuous" applies when it is fully open or the device is not foldable. A change event fires on navigator.devicePosture whenever the posture transitions.

This API requires no permission. It is accessible synchronously via navigator.devicePosture.type from any script on the page — including MCP client-side code, third-party scripts, and MCP tools executing in the browser context. There is no way for the user to revoke or deny access to posture information, no indicator that posture is being read, and no rate limiting on the change event.

On non-foldable devices, navigator.devicePosture.type returns "continuous" constantly and the change event never fires. The security impact is therefore limited to users of foldable devices — a growing but still minority population. However, for that population, the fingerprinting precision is unusually high because the set of foldable device models is small and their screen geometry is distinctive.

Device type fingerprinting from posture + screen geometry

A non-foldable device always reports navigator.devicePosture.type === "continuous". A device that ever reports "folded" is unambiguously a foldable. Combined with screen.width, screen.height, window.visualViewport, and the env(fold-width) CSS environment variable, the exact foldable model is identifiable with high precision:

// Device Posture fingerprinting — no permission required

function fingerPrintFoldable() {
  if (!navigator.devicePosture) return { isFoldable: false };

  const posture = navigator.devicePosture.type;
  const screenW = screen.width;
  const screenH = screen.height;
  const pixelRatio = window.devicePixelRatio;

  // Samsung Galaxy Z Fold 6 (unfolded): 1812×2176 logical, DPR 2.625
  // Samsung Galaxy Z Fold 6 (folded):    904×2176 logical
  // Samsung Galaxy Z Flip 6 (open):      1080×2640 logical
  // Samsung Galaxy Z Flip 6 (folded):    748×2640 logical
  // Microsoft Surface Duo 2 (unfolded):  1344×1892 logical, DPR 2.5
  // Motorola Razr+ (open):              1080×2640 logical

  const models = [
    { model: 'Galaxy Z Fold 6 (unfolded)', w: 1812, h: 2176, dpr: 2.625, posture: 'continuous' },
    { model: 'Galaxy Z Fold 6 (folded)',   w: 904,  h: 2176, dpr: 2.625, posture: 'folded' },
    { model: 'Galaxy Z Flip 6 (open)',     w: 1080, h: 2640, dpr: 3.0,   posture: 'continuous' },
    { model: 'Galaxy Z Flip 6 (folded)',   w: 748,  h: 2640, dpr: 3.0,   posture: 'folded' },
    { model: 'Surface Duo 2 (unfolded)',   w: 1344, h: 1892, dpr: 2.5,   posture: 'continuous' },
  ];

  for (const candidate of models) {
    if (Math.abs(screenW - candidate.w) < 10 &&
        Math.abs(screenH - candidate.h) < 10 &&
        Math.abs(pixelRatio - candidate.dpr) < 0.1 &&
        posture === candidate.posture) {
      return { isFoldable: true, model: candidate.model, posture };
    }
  }

  return {
    isFoldable: posture === 'folded' || (posture === 'continuous' && 'devicePosture' in navigator),
    posture,
    screenW, screenH, pixelRatio
  };
}

// This fingerprint persists across sessions and survives cookie/localStorage clearing
// because it reflects hardware characteristics, not stored state

Fold state transitions as a physical behavior oracle

The change event on navigator.devicePosture fires in real time when the user physically folds or unfolds their device. The timestamps and sequence of these transitions encode physical behavior: a user who unfolds their phone and opens an application is beginning a focused work session; a user who folds mid-session is transitioning (putting the device in a pocket, switching to a different mode, attending to something in the physical environment). The cadence of fold/unfold cycles over a longer session encodes usage patterns at the level of physical context switches.

// Fold transition behavioral tracking — fired in real time, no permission

const postureLog = [];

if (navigator.devicePosture) {
  // Capture initial state
  postureLog.push({
    state: navigator.devicePosture.type,
    timestamp: Date.now(),
    event: 'initial'
  });

  navigator.devicePosture.addEventListener('change', () => {
    postureLog.push({
      state: navigator.devicePosture.type,
      timestamp: Date.now(),
      event: 'transition'
    });

    // Observable patterns:
    // - User folds 30s after opening app: likely interrupted, context switch
    // - User unfolds after 45 minutes: returning from pocket, resuming session
    // - Rapid fold/unfold: checking notifications, testing the device
    // - Never folds during session: desktop/laptop equivalent usage context
  });
}

// Exfiltrate postureLog at session end or periodically
// The sequence reveals physical context switches correlated with application activity

Posture-conditioned UI manipulation

A malicious MCP tool can use posture state to conditionally render different UI elements depending on whether the device is folded or unfolded — displaying legitimate-looking UI when the device is flat and a surveillance dashboard is likely visible, but switching to a different configuration when the device is folded (and typically held in one hand, with a smaller display). This enables a class of screen-orientation-conditioned deception attacks where the UI the user sees depends on their physical device configuration.

More practically, MCP clients that adjust their UI layout for folded vs unfolded modes (a common responsive design pattern for foldable devices) should audit whether posture state influences security-relevant UI elements: consent dialogs, permission prompts, or tool result displays. A consent dialog rendered incorrectly in folded mode (buttons off-screen, text truncated, action buttons overlapping) can be exploited by a malicious tool that triggers consent requests only when the device is folded.

// VULNERABLE: security-relevant UI rendered differently based on posture
// without ensuring the folded layout is equally clear

navigator.devicePosture?.addEventListener('change', () => {
  if (navigator.devicePosture.type === 'folded') {
    // Folded mode — compressed layout
    // PROBLEM: if a consent dialog is currently shown, the folded layout
    // may push the "Deny" button off-screen, leaving only "Allow" visible
    applyFoldedLayout();
  } else {
    applyFullLayout();
  }
});

// CORRECT: security-relevant dialogs (permission grants, consent) are
// posture-independent — always render with both options fully visible
// Test consent UI in folded mode explicitly before shipping foldable support
Risk Attack vector Defense
Device model fingerprinting posture + screen.width/height/DPR combo identifies foldable model precisely No defense available from application — browser must add noise or gating
Physical behavior tracking Fold/unfold event timestamps encode context switches and usage cadence Avoid logging posture transitions; do not include them in analytics or tool responses
Posture-conditioned deception Malicious tool triggers security prompts only in folded mode (degraded layout) Security dialogs must be posture-independent; test all permission UI in folded mode
Fold state exfiltration via MCP tool response Tool includes navigator.devicePosture.type in response — exposes device class Never include hardware state in MCP tool responses unless functionally required

SkillAudit findings for Device Posture API misuse

High Device posture state included in MCP tool response or model context. The MCP tool reads navigator.devicePosture.type and includes the value in its response to the model. The LLM receives device class information (foldable vs non-foldable, current fold state) that was not explicitly provided by the user. This is an unsolicited hardware fingerprint delivered to the model. Grade impact: −16.
High Posture change events logged or transmitted without user disclosure. The MCP client registers a change listener on navigator.devicePosture and records or transmits the transition timestamps. Posture transition logs encode physical behavior patterns. No disclosure is made to the user that physical device actions are being tracked. Grade impact: −18.
High Security-relevant UI (permission dialogs, consent flows) not tested in folded posture. The MCP client's permission grant or tool consent UI is rendered using a posture-responsive layout but has not been verified to display all action buttons and full decision text in folded mode. Folded mode layout failures can suppress the "Deny" option. Grade impact: −16.
Medium Posture combined with screen geometry in fingerprinting profile. The MCP tool or client code reads navigator.devicePosture.type in combination with screen.width, screen.height, and devicePixelRatio. The combination uniquely identifies foldable device models and is included in session analytics, telemetry, or tool output. Grade impact: −12.
Low Posture state read but not used for any functional purpose. navigator.devicePosture.type is accessed in MCP client or tool code but the value is not used to adapt any layout, feature flag, or behavior. Dead code read of a hardware fingerprinting signal — no current impact but should be removed to prevent future misuse. Grade impact: −4.

Audit your MCP server for these issues

SkillAudit checks for Device Posture API access patterns automatically — paste a GitHub URL and get a graded report in 60 seconds.

Run a free audit →