From 76e1ceb58dd4d6bb6f4015534cae7a16c8a45d51 Mon Sep 17 00:00:00 2001 From: Rachelle Date: Tue, 14 Apr 2026 15:36:37 +1200 Subject: [PATCH 1/4] 11.19 --- __tests__/shield-page-compare-cta.test.tsx | 7 +- __tests__/shield-threat-model-page.test.tsx | 43 +++ app/shield/page.tsx | 9 +- app/shield/threat-model/page.tsx | 340 ++++++++++++++++++++ app/sitemap.ts | 6 + content/changelog.json | 34 +- 6 files changed, 426 insertions(+), 13 deletions(-) create mode 100644 __tests__/shield-threat-model-page.test.tsx create mode 100644 app/shield/threat-model/page.tsx diff --git a/__tests__/shield-page-compare-cta.test.tsx b/__tests__/shield-page-compare-cta.test.tsx index 4d1a591..1d0d9bb 100644 --- a/__tests__/shield-page-compare-cta.test.tsx +++ b/__tests__/shield-page-compare-cta.test.tsx @@ -31,7 +31,10 @@ describe('/shield page compare CTA', () => { screen.getByRole('heading', { name: /Not sure if Shield is right for you/i }), ).toBeInTheDocument() - const link = screen.getByRole('link', { name: /Compare Shield to alternatives/i }) - expect(link).toHaveAttribute('href', '/shield/compare') + const compareLink = screen.getByRole('link', { name: /Compare Shield to alternatives/i }) + expect(compareLink).toHaveAttribute('href', '/shield/compare') + + const threatModelLink = screen.getByRole('link', { name: /Read the threat model/i }) + expect(threatModelLink).toHaveAttribute('href', '/shield/threat-model') }) }) diff --git a/__tests__/shield-threat-model-page.test.tsx b/__tests__/shield-threat-model-page.test.tsx new file mode 100644 index 0000000..8981b9e --- /dev/null +++ b/__tests__/shield-threat-model-page.test.tsx @@ -0,0 +1,43 @@ +import { cleanup, render, screen } from '@testing-library/react' +import { afterEach, describe, expect, it } from 'vitest' +import ShieldThreatModelPage, { metadata } from '@/app/shield/threat-model/page' + +afterEach(() => { + cleanup() +}) + +describe('/shield/threat-model page', () => { + it('renders the hero heading and no TODO placeholder strings', () => { + const { container } = render() + + expect( + screen.getByRole('heading', { level: 1, name: /Shield threat model/ }), + ).toBeInTheDocument() + + expect(container.textContent ?? '').not.toContain('TODO') + }) + + it('renders the coverage table with both integration columns', () => { + render() + + expect(screen.getByRole('columnheader', { name: /Native plugin/ })).toBeInTheDocument() + expect(screen.getByRole('columnheader', { name: /Hosted proxy \(MCP\)/ })).toBeInTheDocument() + }) + + it('links the recommendation to getting started docs', () => { + render() + + const docLink = screen.getByRole('link', { name: /^Getting started$/ }) + expect(docLink).toHaveAttribute('href', '/docs/getting-started') + }) + + it('mentions roadmap wording for native integrations', () => { + render() + + expect(screen.getByText(/Windsurf on the roadmap/i)).toBeInTheDocument() + }) + + it('declares the correct canonical URL', () => { + expect(metadata.alternates?.canonical).toBe('https://multicorn.ai/shield/threat-model') + }) +}) diff --git a/app/shield/page.tsx b/app/shield/page.tsx index cf047e0..b2a100d 100644 --- a/app/shield/page.tsx +++ b/app/shield/page.tsx @@ -677,7 +677,7 @@ export default function ShieldPage() { control tools - Agent Safehouse, agentsh, and AgentGate - and find the right fit for your team.

-
+
Compare Shield to alternatives + + Read the threat model +
diff --git a/app/shield/threat-model/page.tsx b/app/shield/threat-model/page.tsx new file mode 100644 index 0000000..1cde7ef --- /dev/null +++ b/app/shield/threat-model/page.tsx @@ -0,0 +1,340 @@ +import type { Metadata } from 'next' +import Link from 'next/link' +import { Footer } from '@/components/Footer' + +const CANONICAL_URL = 'https://multicorn.ai/shield/threat-model' +const OG_IMAGE_URL = 'https://multicorn.ai/images/og-image.png' + +const META_DESCRIPTION = + 'How Multicorn Shield governs AI agents in native plugin mode versus hosted MCP proxy mode. What threats Shield catches, what sits outside the proxy, and how to choose an integration.' + +export const metadata: Metadata = { + title: 'Shield threat model | Multicorn Shield', + description: META_DESCRIPTION, + alternates: { + canonical: CANONICAL_URL, + }, + openGraph: { + title: 'Shield threat model | Multicorn Shield', + description: META_DESCRIPTION, + url: CANONICAL_URL, + siteName: 'Multicorn', + type: 'website', + images: [ + { + url: OG_IMAGE_URL, + width: 1200, + height: 630, + alt: 'Shield threat model', + }, + ], + }, + twitter: { + card: 'summary_large_image', + title: 'Shield threat model | Multicorn Shield', + description: META_DESCRIPTION, + images: [OG_IMAGE_URL], + }, +} + +type Coverage = 'covered' | 'not-covered' + +const THREAT_ROWS: readonly { + readonly scenario: string + readonly nativePlugin: Coverage + readonly hostedProxy: Coverage +}[] = [ + { + scenario: 'Rogue or malicious MCP server', + nativePlugin: 'covered', + hostedProxy: 'covered', + }, + { + scenario: 'Credential replay against recorded activity', + nativePlugin: 'covered', + hostedProxy: 'covered', + }, + { + scenario: 'Agent misuses approved MCP access', + nativePlugin: 'covered', + hostedProxy: 'covered', + }, + { + scenario: 'Runaway spending on governed actions', + nativePlugin: 'covered', + hostedProxy: 'covered', + }, + { + scenario: "Misuse of the host app's built-in tools", + nativePlugin: 'covered', + hostedProxy: 'not-covered', + }, + { + scenario: 'Direct external API calls that bypass MCP', + nativePlugin: 'covered', + hostedProxy: 'not-covered', + }, + { + scenario: 'Native shell, file, or system access outside MCP', + nativePlugin: 'covered', + hostedProxy: 'not-covered', + }, +] + +function CoverageCell({ value }: { readonly value: Coverage }) { + if (value === 'covered') { + return ( + + + + ) + } + return ( + + + + ) +} + +export default function ShieldThreatModelPage() { + return ( + <> +
+
+
+ +
+
+

+ How Shield integrates +

+
+
+

Native plugin

+

+ The plugin runs inside the host. It sees the same tool and action surface the host + exposes to the model, not only traffic that happens to go out through MCP. + Consent, permissions, spending checks, and activity records can apply to the full + picture of what the agent tried to do. +

+

+ Native plugin integrations today include OpenClaw and Claude Code, with Windsurf + on the roadmap. When we say "full coverage" on this page, we mean this + path. +

+
+
+

Hosted MCP proxy

+

+ The host sends MCP tool calls through Shield's hosted proxy. Shield governs + that MCP-shaped traffic: approvals, policy, logging, and spend rules attach to + calls that flow through the proxy. Anything the host does without routing through + MCP never touches Shield, by design. +

+

+ This is the right mental model for Cursor, Windsurf, Claude Desktop, and other + clients you connect through the proxy. You get strong governance for MCP tools. + You do not get visibility into built-in host capabilities that never pass through + MCP. +

+
+
+
+
+ +
+
+

+ Coverage by scenario +

+

+ The table is read left to right: each row is a situation teams ask about. A check + means Shield is positioned to detect, block, or record that class of behaviour in that + integration mode. An X means the behaviour can happen without passing through Shield + in hosted-proxy mode, so Shield does not see it. +

+

+ Credential replay is a special case. For every action Shield logs, the activity trail + is tamper-evident: entries are chained so a forged "past" record does not + match the chain. That property holds for hosted-proxy mode as well as native plugin + mode, as long as the action was one Shield recorded. Replay of credentials or history + outside that recorded surface is a different problem. +

+ +
+ + + + + + + + + + {THREAT_ROWS.map((row, index) => ( + + + + + + ))} + +
+ Scenario + + Native plugin + + Hosted proxy (MCP) +
+ {row.scenario} + + + + +
+
+
+
+ +
+
+

+ What Shield is built to catch +

+

+ Rogue or malicious MCP server. Shield + sits on the path between your agent and MCP tools you configure. Traffic can be + inspected, policy can require approval, and unexpected servers or tool shapes surface + in activity rather than failing silently inside a long-running session. +

+

+ Credential replay and audit integrity.{' '} + When Shield writes an activity record, it becomes part of a hash-linked chain. An + attacker who wants to pretend an action already happened, or to substitute a different + past, breaks that chain. That is true in native plugin mode for the full action set, + and in hosted-proxy mode for every MCP call Shield actually logged. +

+

+ + An agent stretching past the access you granted. + {' '} + If the model keeps calling tools you did not approve, or probes for broader access, + Shield can block, prompt, or flag those attempts depending on your policy. The + important part is the attempt is visible and attributable, not lost in console noise. +

+

+ Runaway spending on governed actions.{' '} + Limits and alerts attach to the operations Shield sees. On the native plugin path that + is the broadest set of spend. On the hosted-proxy path that is spend that flows + through MCP under Shield. Both are real controls; the difference is how much of the + host's behaviour Shield can govern. +

+
+
+ +
+
+

+ What hosted proxy does not see +

+

+ Hosted MCP proxy mode is intentional about its boundary. The host application may ship + its own tools: file pickers, terminals, browser automation, first-party APIs, and + other shortcuts that never emit an MCP request through your Shield URL. Shield does + not intercept those calls. They are not hidden on purpose; they are simply a different + channel. +

+

+ That shows up in three ways teams care about.{' '} + Built-in tools can read, write, or call + services without MCP.{' '} + Direct external API usage from the host + skips the proxy if the integration is not MCP-shaped.{' '} + Native shell or file access on the + machine runs under the host's own permissions. None of that traffic is + automatically mirrored into Shield unless you route it there. +

+

+ If your risk model assumes Shield "sees everything the agent can do," hosted + proxy mode does not meet that bar. It meets the bar for "everything the agent + does through MCP while pointed at Shield." That is a meaningful slice for many + teams, but it is not the same as full host coverage. +

+
+
+ +
+
+

+ When you need full coverage +

+

+ For the strongest governance story, use a native plugin integration where your host + supports one. You get consent, permissions, activity, and spending aligned to the full + tool surface the model can reach, not only MCP-routed work. Start from the docs if you + are wiring a new agent:{' '} + + Getting started + + . +

+

+ If you stay on hosted proxy mode, treat MCP traffic as the governed zone and plan + controls elsewhere for anything the host can still do on its own. +

+
+
+
+