AID-H-028.001: MCP Server Authenticity Validation & Connection Pinning

Version 1.20261015 · Actionable sub-technique

Before server/discover or any MCP request is sent, verify remote server identity beyond endpoint discovery. Enforce an allowlisted HTTPS origin or approved local stdio command, transport identity verification, and risk-based SPKI, certificate, or SPIFFE pinning with fail-closed mismatch behavior.

Pillars: infra, app

Lifecycle: building, operation

Scope boundary

Owns the client's pre-request trust decision for the selected remote server or local stdio command, including origin allowlisting, transport authentication, and risk-based certificate, SPKI, or workload-identity pinning. It creates no MCP protocol session.

AID-H-024: AID-H-028.001 authenticates the transport endpoint selected for connection; AID-H-024 resolves the intended logical server and tool contract before transport trust is evaluated. A certificate-valid endpoint can still be the wrong resolved service.

AID-H-034.001: AID-H-028.001 enforces the client's server-authentication decision; AID-H-034.001 configures the server's own transport and exposure baseline. Client pinning and server hardening are complementary endpoints of the connection.

Implementation guidance

AID-H-028.001-G001

Implement per-server allowlisting and optional SPKI or certificate-fingerprint pinning for configured MCP endpoints, with fail-closed behavior on identity mismatch.
Concept:

Store a small trust record for each allowed MCP server. At minimum include the server URL, the expected hostname, and either the expected certificate fingerprint or an SPKI hash. When the client connects, compare what the server presents to what you already trust. If it does not match, stop the connection and log the event.

Example allowlist file
{
  "servers": [
    {
      "name": "finance-mcp",
      "url": "https://mcp.finance.internal:8443",
      "hostname": "mcp.finance.internal",
      "spki_sha256": "ET4Zw38AtZHETHLH25DNB6EDZsy0oi+LOhGWDlXXbkQ=",
      "min_tls_version": "TLSv1.2"
    }
  ]
}
Example Node.js verification hook
// File: src/mcp/pinned-agent.mjs
import https from "node:https";
import tls from "node:tls";
import crypto from "node:crypto";
import fs from "node:fs";

const cfg = JSON.parse(fs.readFileSync("./mcp_allowlist.json", "utf8"));
const allowed = new Map(cfg.servers.map(s => [s.url, s]));

function spkiHash(cert) {
  const x509 = new crypto.X509Certificate(cert.raw);
  const spkiDer = x509.publicKey.export({ type: "spki", format: "der" });
  return crypto.createHash("sha256").update(spkiDer).digest("base64");
}

function connectPinned(serverUrl) {
  const rule = allowed.get(serverUrl);
  if (!rule) throw new Error(`Server not allowlisted: ${serverUrl}`);

  const agent = new https.Agent({
    minVersion: rule.min_tls_version,
    checkServerIdentity: (host, cert) => {
      const tlsErr = tls.checkServerIdentity(host, cert);
      if (tlsErr) return tlsErr;
      const got = spkiHash(cert);
      if (got !== rule.spki_sha256) {
        return new Error(`SPKI pin mismatch for ${host}`);
      }
      return undefined;
    }
  });

  return agent;
}

Operational notes: Keep a rotation runbook. During planned certificate rotation, update the pin in a reviewed config change before the new certificate goes live. Keep a short emergency allowlist override path, but require approval and log every use.

Production implementation

At the client connector before DNS/TLS use, normalize the HTTPS origin, match it to the signed server registry, verify the live certificate chain and policy-selected SPKI or certificate pin, and deny unknown, stale or mismatched identity without fallback.

Independent verification

A separately credentialed verifier independently reads back or replays registry selection, DNS result, TLS chain, pin, effective connection and deny receipt for every connection attempt, including wrong-origin, pin-rotation and interception fixtures.

AID-H-028.001-G002

Validate server URI, expected identity, and minimum transport properties before discovery or request dispatch, and reject unknown or downgraded connections.
Concept:

Do not let the client connect to arbitrary MCP URLs. Validate the requested endpoint against a local allowlist and enforce basic transport rules such as minimum TLS version and expected subject or SAN hostname.

Example Python pre-connection gate
from urllib.parse import urlparse

ALLOWED = {
    "mcp.finance.internal": {
        "schemes": {"https"},
        "ports": {443, 8443},
        "min_tls": "TLSv1.2"
    }
}

def validate_endpoint(url: str):
    parsed = urlparse(url)
    if parsed.hostname not in ALLOWED:
        raise ValueError(f"Unknown MCP server: {parsed.hostname}")
    rule = ALLOWED[parsed.hostname]
    if parsed.scheme not in rule["schemes"]:
        raise ValueError("Disallowed scheme")
    if parsed.port and parsed.port not in rule["ports"]:
        raise ValueError("Disallowed port")
    return True

Operational notes: Log every deny with server_url, client_version, user_id, and reason. Review repeated denials because they may indicate typosquatting, phishing, or config drift.

Production implementation

At the transport factory, parse one canonical URI, require policy-approved HTTPS/TLS and hostname, prohibit credential-bearing URLs and unapproved redirects, bind the negotiated server identity and protocol profile, and stop dispatch on downgrade or ambiguity.

Independent verification

A separately credentialed verifier independently reads back or replays the effective socket peer, redirect chain, negotiated TLS/profile and dispatcher decision with malformed URI, HTTP, alternate-port, redirect and downgrade fixtures.

AID-H-028.001-G003

Monitor certificate transparency for public-facing MCP domains and alert when unexpected certificates are issued.
Concept:

This is an advanced control for public or internet-facing MCP domains. It is usually not needed for private internal names that do not use public certificate issuance.

Example daily CT check using Cert Spotter API
import json
import math
import os
import requests

API_TOKEN = os.environ["CERTSPOTTER_TOKEN"]
DOMAIN = os.environ["VERIFIED_MCP_CT_DOMAIN"]
EXPECTED_ISSUERS = set(json.loads(os.environ["VERIFIED_MCP_CT_ISSUERS_JSON"]))
CT_HTTP_TIMEOUT_SECONDS = float(os.environ["VERIFIED_CT_HTTP_TIMEOUT_SECONDS"])
CT_RESPONSE_MAX_BYTES = int(os.environ["VERIFIED_CT_RESPONSE_MAX_BYTES"])
if (
    not DOMAIN or not EXPECTED_ISSUERS
    or not math.isfinite(CT_HTTP_TIMEOUT_SECONDS)
    or CT_HTTP_TIMEOUT_SECONDS <= 0
    or CT_RESPONSE_MAX_BYTES <= 0
):
    raise RuntimeError("verified CT policy inputs are incomplete or invalid")

with requests.get(
    "https://api.certspotter.com/v1/issuances",
    params={"domain": DOMAIN, "include_subdomains": "true", "expand": "dns_names"},
    headers={"Authorization": f"Bearer {API_TOKEN}", "Accept": "application/json"},
    timeout=CT_HTTP_TIMEOUT_SECONDS,
    allow_redirects=False,
    stream=True,
) as response:
    response.raise_for_status()
    if response.url != "https://api.certspotter.com/v1/issuances?domain=" + requests.utils.quote(DOMAIN, safe="") + "&include_subdomains=true&expand=dns_names":
        raise RuntimeError("Cert Spotter request authority or query differs")
    raw = bytearray()
    for chunk in response.iter_content(chunk_size=min(65536, CT_RESPONSE_MAX_BYTES + 1)):
        if chunk:
            raw.extend(chunk)
        if len(raw) > CT_RESPONSE_MAX_BYTES:
            raise RuntimeError("Cert Spotter response exceeds signed decoded-byte cap")
certificates = json.loads(bytes(raw))
if not isinstance(certificates, list):
    raise RuntimeError("Cert Spotter response root must be an array")
for cert in certificates:
    issuer = cert.get("issuer", "")
    if issuer and not any(ok in issuer for ok in EXPECTED_ISSUERS):
        print(f"ALERT unexpected issuer for {DOMAIN}: {issuer}")

Operational notes: Use this as a monitoring signal, not as the only connection control. The primary enforcement should still be allowlisting and identity pinning inside the client.

Production implementation

At the CT monitoring job, query the policy-approved log source with a policy-derived timeout, reconcile all observed issuances against the authorized DNS names, issuers and key identities, and create a signed quarantine/rotation alert for every unexpected certificate.

Independent verification

A separately credentialed verifier independently reads back or replays the complete CT result population, issuer/key comparison, alert delivery and client-registry quarantine readback with expected, unexpected, partial-log and API-failure fixtures.

Open-source tools

Source-available / open-weight tools

Commercial / hosted tools

Threat mappings

MITRE ATLAS

MAESTRO

OWASP LLM Top 10 2026

OWASP ML Top 10 2023

OWASP Top 10 for Agentic Applications 2026

NIST Adversarial Machine Learning 2025

Cisco Integrated AI Security and Safety Framework

Google Secure AI Framework 2.0 - Risks

Databricks AI Security Framework 3.0

AID-H-028.001: MCP Server Authenticity Validation & Connection Pinning | AIDEFEND

Loading interactive view…

A Practical Knowledge Base for AI Security Defenses

Version