Skip to content

[Security follow-up C-4] Add MCP token expiration + rotationΒ #45

@hoainho

Description

@hoainho

🌟 Before claiming this issue

Two quick steps before you open a PR:

  1. ⭐ Star the repository β€” low-friction signal that you'll follow through
  2. πŸ’¬ Comment I'll take this (or similar) below β€” prevents two contributors racing on the same issue

Full policy: CONTRIBUTING.md β†’ How to claim. If a claim is older than 7 days with no PR, you can reclaim it politely.


Tracks follow-up #4 from PR #17 security review. Blocked on mcp-server-v1 M3 (~Jul 28, 2026).

What

MCP pairing tokens currently never expire. Add token expiration (e.g., 24-hour TTL) + a rotation mechanism so users can revoke + re-pair.

Why

A pairing token grants full debugger control. If a user pairs from a shared machine, leaves the browser open, then a roommate uses the laptop, the token persists indefinitely. Even on personal machines: tokens leak via screen-recording, screen-share, OS-level keylogger, etc. A 24h TTL bounds the exposure window.

Rotation lets a user "rotate" without going through the full pairing handshake β€” useful after the user notices suspicious activity in the panel.

Acceptance criteria

  • Tokens written to storage include a { token: string; expiresAt: number } shape (not just the raw string)
  • readToken() returns null if expiresAt < Date.now() AND deletes the expired entry
  • Settings UI has a "Rotate pairing token" button (calls server's rotation endpoint, replaces stored token)
  • CHANGELOG: document the 24h TTL default + how to rotate
  • Unit test: write token with expiresAt: Date.now() - 1000, then readToken() returns null and chrome.storage.session.get() shows no entry remains

Implementation hint

const TOKEN_TTL_MS = 24 * 60 * 60 * 1000; // 24 hours

type StoredToken = { token: string; expiresAt: number };

async function saveToken(token: string) {
  const entry: StoredToken = {
    token,
    expiresAt: Date.now() + TOKEN_TTL_MS,
  };
  await chrome.storage.session.set({ [TOKEN_KEY]: entry });
}

async function readToken(): Promise<string | null> {
  const { [TOKEN_KEY]: entry } = await chrome.storage.session.get(TOKEN_KEY);
  if (!entry) return null;
  if ((entry as StoredToken).expiresAt < Date.now()) {
    await chrome.storage.session.remove(TOKEN_KEY);
    return null;
  }
  return (entry as StoredToken).token;
}

async function rotateToken() {
  const newToken = await mcpServer.rotateToken(); // hits the server's rotate endpoint
  await saveToken(newToken);
}

Context

Metadata

Metadata

Assignees

No one assigned

    Labels

    help wantedExtra attention is neededsecuritySecurity-related (see SECURITY.md for vulnerabilities)

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions