Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
22 commits
Select commit Hold shift + click to select a range
04557ad
test(mcp): require typed stateless tool routing boundary
seonghobae Aug 15, 2026
3431162
style(mcp): apply canonical rustfmt to red contract
seonghobae Aug 15, 2026
afdc7c4
feat(mcp): enforce typed stateless routing boundary
seonghobae Aug 15, 2026
af30802
style(mcp): apply canonical production rustfmt
seonghobae Aug 15, 2026
3ac6855
test(mcp): pin routing policy boundaries
seonghobae Aug 15, 2026
bfed566
refactor(mcp): centralize explicit tool mapping
seonghobae Aug 15, 2026
e5ac16d
style(mcp): apply canonical rustfmt diagnostics
seonghobae Aug 15, 2026
b1585c0
test(mcp): bind routed tool identity to policy action
seonghobae Aug 16, 2026
0808e53
feat(mcp): fail closed on route-action mismatch
seonghobae Aug 16, 2026
50c80ab
style(mcp): apply canonical rustfmt diagnostics
seonghobae Aug 16, 2026
169814e
test(mcp): require deterministic tool catalog
seonghobae Aug 16, 2026
b0a0b30
test(mcp): apply canonical catalog formatting
seonghobae Aug 16, 2026
23a17b5
feat(mcp): expose deterministic reviewed tool catalog
seonghobae Aug 16, 2026
9520ffe
docs(changelog): record deterministic MCP tool catalog
seonghobae Aug 16, 2026
6bd31a5
Merge branch 'main' into feat/mcp-authority-route
seonghobae Aug 16, 2026
1d86d28
test(mcp): reject malformed cross-field tool names before mismatch
seonghobae Aug 17, 2026
9d0f524
fix(mcp): validate each untrusted tool name before comparison
seonghobae Aug 17, 2026
2fb12da
docs(mcp): distinguish active routing from shipped adapter
seonghobae Aug 17, 2026
9bc35d1
docs(mcp): record active routing authority boundary
seonghobae Aug 17, 2026
f9a1fa9
docs(mcp): clarify active routing foundation
seonghobae Aug 17, 2026
abfb261
docs(mcp): add 2026-07-28 primary specification
seonghobae Aug 17, 2026
7d1b610
docs(mcp): trace active routing boundary
seonghobae Aug 17, 2026
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
3 changes: 2 additions & 1 deletion CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -11,12 +11,13 @@ All notable changes to OriginWeave are documented in this file. The format follo
- Rust workspace for independently reusable core, policy, destination, network, TLS, resource, and evidence modules.
- Canonical HTTPS and loopback-origin boundary with case-normalized schemes and hosts, default-port normalization, IPv4/IPv6 handling, browser-special numeric-host rejection, and explicit malformed-input errors.
- Typed browser actions, capabilities, risk classes, execution modes, robots decisions, secret-delivery contracts, immutable canonical action-intent digests, and intent-bound approval scopes.
- Active PR #168 adds deterministic MCP `2026-07-28` stateless tool-routing foundations with bounded names, a single reviewed tool-to-action registry shared by routing and discovery metadata, and fail-closed policy binding that grants no ambient authority. This is active-PR evidence only; the complete MCP adapter, transport serialization, discovery response handling, OAuth, browser I/O, and persistence remain planned until separately integrated on protected `main`.
- Deterministic fail-closed policy evaluation for untrusted instructions, origin grants, crawler restrictions, execution-mode and purpose consistency, approvals, and brokered secrets.
- Fail-closed resolved-destination policy with IPv4/IPv6 special-purpose and reviewed cloud-platform endpoint classification, IPv4-mapped canonicalization, explicit class grants, non-empty origin-bound DNS snapshots capped at 256 resolver addresses, concrete connection pinning, DNS-set expansion detection, and per-hop redirect reauthorization.
- Direct-only `originweave-network` TCP boundary with explicit canonical `SocketAddr` authority, zero IPv6 flow and scope metadata unless separately modeled, a non-cloneable single-use plan, a 30-second per-attempt timeout ceiling, at most four attempts, exact `peer_addr` verification before stream exposure, and no hostname re-resolution or ambient proxy inheritance.
- Authenticated `originweave-tls` service-identity boundary that consumes an existing verified TCP stream, requires exact TLS-origin and transport-origin equality, derives RFC 9525 DNS or literal-IP reference identity only from the canonical HTTPS origin, validates WebPKI with explicit roots and fixed time, permits only TLS 1.2 and TLS 1.3, and never reconnects or resolves.
- Bounded TLS policy for total handshake time, ALPN identifiers, trust-root count and bytes, and server-presented certificate count and bytes, with explicit optional-versus-required ALPN behavior and `NotConfigured` revocation evidence.
- Credential-free TLS evidence containing canonical origin, requested and observed peer, DNS/IP reference identity, TLS version, cipher-suite identifier, selected ALPN or explicit absence, leaf certificate and SPKI hashes, server-presented certificate hashes and bounds, trust-bundle identity and hash, validity interval, fixed verification time, revocation configuration, and measured handshake duration.
- Credential-free TLS evidence containing canonical origin, TCP peers, reference identity, TLS version, cipher-suite identifier, selected ALPN or explicit absence, leaf certificate and SPKI hashes, server-presented certificate hashes and bounds, trust-bundle identity and hash, validity interval, fixed verification time, revocation configuration, and measured handshake duration.
- Credential-free connection and redirect evidence containing canonical addresses, destination classes, target digests, hop numbers, and approved-address counts.
- Credential-free verified TCP evidence containing the logical origin, requested socket, observed peer, destination class, successful attempt number, and per-attempt timeout.
- Standard `Display` and `std::error::Error` contracts for destination, redirect, digest, direct-network, and TLS failures, including preserved destination-policy, rustls, and operating-system sources where applicable.
Expand Down
6 changes: 4 additions & 2 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@

OriginWeave is a Chromium-compatible, Rust-first control plane for governed AI agents on the web. It is designed to let an agent observe, extract, and act without turning untrusted page content into authority, exposing secrets to a model, connecting to an unapproved network destination, accepting an unauthenticated web service, or losing the evidence required to explain what happened.

> Project status: pre-alpha. The current repository contains independently reusable safety, resolved-destination, direct TCP peer-binding, and authenticated TLS service-identity kernels. Chromium, WebDriver BiDi, CDP, MCP, HTTP, proxy, WARC, and persistent provenance adapters are planned but not yet shipped.
> Project status: pre-alpha. The current protected repository contains independently reusable safety, resolved-destination, direct TCP peer-binding, and authenticated TLS service-identity kernels. Chromium, WebDriver BiDi, CDP, complete MCP, HTTP, proxy, WARC, and persistent provenance adapters are planned but not yet shipped. Active PR #168 implements only a bounded MCP `2026-07-28` stateless tool-routing and typed-action/policy foundation; it remains non-shipped active-PR evidence and does not make the complete MCP adapter available.

## Why OriginWeave

Expand Down Expand Up @@ -40,6 +40,8 @@ The repository is organized as independently consumable Rust crates:
- `originweave-resource`: task-level RAM, VRAM, thread, and frame-time budgets with cumulative mitigation plans.
- `originweave-evidence`: universally value-redacted network evidence and source-bound provenance records.

Active PR #168 additionally carries a non-shipped `originweave-core` MCP routing registry and `originweave-policy` binding for the MCP `2026-07-28` `tools/call` boundary. That foundation validates and maps an explicit tool name to an existing typed action; it does not implement transport parsing, `tools/list`, OAuth, browser control, secret materialization, persistence, or ambient authority.

See [ARCHITECTURE.md](ARCHITECTURE.md) and the [architecture decision records](docs/adr/) for binding design decisions.

## Safety model
Expand Down Expand Up @@ -97,7 +99,7 @@ isolated Chromium session
→ redacted provenance bundle
```

Subsequent work connects the live Chromium network service, adds explicit proxy and download policy, WARC/PROV persistence, MCP and Browser Agent Protocol adapters, extension compatibility testing, GPU/RAM telemetry, prompt-injection benchmarks, and an accessible approval interface. See [docs/product-roadmap.md](docs/product-roadmap.md).
Subsequent work connects the live Chromium network service, adds explicit proxy and download policy, WARC/PROV persistence, completes the MCP and Browser Agent Protocol adapters beyond the active routing foundation, expands extension compatibility testing, adds GPU/RAM telemetry and prompt-injection benchmarks, and builds an accessible approval interface. See [docs/product-roadmap.md](docs/product-roadmap.md).

## Hourly product-development loop

Expand Down
3 changes: 3 additions & 0 deletions crates/originweave-core/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,9 @@ repository.workspace = true
homepage.workspace = true
publish = false

[lib]
path = "src/root.rs"

[dependencies]

[lints]
Expand Down
228 changes: 228 additions & 0 deletions crates/originweave-core/src/mcp.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,228 @@
//! Fail-closed MCP routing integrity for the external adapter boundary.
//!
//! This module validates only the stateless MCP protocol/method/tool routing
//! envelope and derives an existing [`ActionKind`]. It is deliberately not an
//! authorization decision: callers must independently enforce OriginWeave
//! capability, risk, approval, origin, secret-broker, and evidence policies.
//! No MCP arguments, outputs, credentials, or arbitrary model-visible values
//! are retained by this boundary.

use std::fmt;

use crate::{ActionKind, Capability, RiskClass};

/// MCP protocol generation accepted by this stateless adapter boundary.
pub const MCP_PROTOCOL_VERSION: &str = "2026-07-28";

/// The only MCP method that can enter the typed action-routing boundary.
pub const MCP_TOOLS_CALL_METHOD: &str = "tools/call";

/// Maximum accepted MCP tool-name length in bytes.
pub const MAX_MCP_TOOL_NAME_BYTES: usize = 128;

/// One deterministic MCP tool descriptor derived from OriginWeave's reviewed action registry.
///
/// The descriptor is discovery metadata only. It does not grant capabilities, origin access,
/// approval, secret access, or any other authority.
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub struct McpToolCatalogEntry {
tool_name: &'static str,
action_kind: ActionKind,
}

impl McpToolCatalogEntry {
/// Return the canonical MCP tool name exposed by this registry entry.
#[must_use]
pub const fn tool_name(&self) -> &'static str {
self.tool_name
}

/// Return the typed OriginWeave action represented by this registry entry.
#[must_use]
pub const fn action_kind(&self) -> ActionKind {
self.action_kind
}

/// Return the capability required by the represented action.
#[must_use]
pub const fn required_capability(&self) -> Capability {
self.action_kind.required_capability()
}

/// Return the risk class assigned to the represented action.
#[must_use]
pub const fn risk_class(&self) -> RiskClass {
self.action_kind.risk_class()
}
}

/// The complete explicit MCP tool-to-action registry accepted by this boundary.
///
/// Order is deterministic so adapters can derive stable discovery output from this single
/// reviewed registry rather than maintaining a second mapping that could drift from routing.
const MCP_TOOL_CATALOG: &[McpToolCatalogEntry] = &[
McpToolCatalogEntry {
tool_name: "originweave.observe",
action_kind: ActionKind::Observe,
},
McpToolCatalogEntry {
tool_name: "originweave.extract",
action_kind: ActionKind::Extract,
},
McpToolCatalogEntry {
tool_name: "originweave.navigate",
action_kind: ActionKind::Navigate,
},
McpToolCatalogEntry {
tool_name: "originweave.download",
action_kind: ActionKind::Download,
},
McpToolCatalogEntry {
tool_name: "originweave.draft",
action_kind: ActionKind::Draft,
},
McpToolCatalogEntry {
tool_name: "originweave.submit",
action_kind: ActionKind::Submit,
},
McpToolCatalogEntry {
tool_name: "originweave.upload",
action_kind: ActionKind::Upload,
},
McpToolCatalogEntry {
tool_name: "originweave.fill_secret",
action_kind: ActionKind::FillSecret,
},
McpToolCatalogEntry {
tool_name: "originweave.purchase",
action_kind: ActionKind::Purchase,
},
McpToolCatalogEntry {
tool_name: "originweave.delete",
action_kind: ActionKind::Delete,
},
McpToolCatalogEntry {
tool_name: "originweave.manage_permission",
action_kind: ActionKind::ManagePermission,
},
];

/// Return the deterministic reviewed MCP tool catalog.
///
/// Adapters may use this slice to derive discovery responses. Serialization, pagination, cache
/// policy, transport I/O, and authorization remain outside this stateless registry boundary.
#[must_use]
pub const fn supported_mcp_tools() -> &'static [McpToolCatalogEntry] {
MCP_TOOL_CATALOG
}

/// A deterministic failure while validating untrusted MCP routing metadata.
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub enum McpToolBoundaryError {
/// The request names an MCP protocol generation this boundary does not support.
UnsupportedProtocolVersion,
/// MCP routing metadata disagrees with the method or tool name in the body.
HeaderBodyMismatch,
/// The request method is not the supported `tools/call` operation.
UnsupportedMethod,
/// The tool name violates the bounded ASCII MCP routing syntax.
InvalidToolName,
/// The tool name has no explicit mapping to an OriginWeave typed action.
UnknownTool,
}

impl fmt::Display for McpToolBoundaryError {
fn fmt(&self, formatter: &mut fmt::Formatter<'_>) -> fmt::Result {
match self {
Self::UnsupportedProtocolVersion => {
formatter.write_str("unsupported MCP protocol version")
}
Self::HeaderBodyMismatch => {
formatter.write_str("MCP routing headers do not match the request body")
}
Self::UnsupportedMethod => formatter
.write_str("only MCP tools/call requests can enter the typed action boundary"),
Self::InvalidToolName => {
formatter.write_str("MCP tool name violates the bounded ASCII routing syntax")
}
Self::UnknownTool => {
formatter.write_str("MCP tool is not mapped to an OriginWeave typed action")
}
}
}
}

impl std::error::Error for McpToolBoundaryError {}

/// An MCP tool call whose routing envelope has been validated and mapped.
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub struct ValidatedMcpToolCall {
tool_name: &'static str,
action_kind: ActionKind,
}

impl ValidatedMcpToolCall {
/// Validate one stateless MCP tool-call routing envelope.
///
/// Routing integrity is intentionally narrower than authorization. A
/// successful value proves only that the untrusted protocol version,
/// routing metadata, body method, and body tool name agree with one
/// explicitly supported mapping. Each untrusted tool name is shape-validated
/// before cross-field comparison so malformed or oversized names cannot
/// bypass the bounded routing syntax through mismatch handling.
pub fn new(
protocol_version: &str,
routing_method: &str,
routing_tool_name: &str,
body_method: &str,
body_tool_name: &str,
) -> Result<Self, McpToolBoundaryError> {
if protocol_version != MCP_PROTOCOL_VERSION {
return Err(McpToolBoundaryError::UnsupportedProtocolVersion);
}
if !valid_tool_name(routing_tool_name) || !valid_tool_name(body_tool_name) {
return Err(McpToolBoundaryError::InvalidToolName);
}
if routing_method != body_method || routing_tool_name != body_tool_name {
return Err(McpToolBoundaryError::HeaderBodyMismatch);
}
if routing_method != MCP_TOOLS_CALL_METHOD {
return Err(McpToolBoundaryError::UnsupportedMethod);
}

let (tool_name, action_kind) = map_tool(routing_tool_name)?;
Ok(Self {
tool_name,
action_kind,
})
}

/// Return the canonical static tool name selected by the explicit mapping.
#[must_use]
pub const fn tool_name(&self) -> &'static str {
self.tool_name
}

/// Return the existing OriginWeave typed action selected by this tool.
#[must_use]
pub const fn action_kind(&self) -> ActionKind {
self.action_kind
}
}

fn valid_tool_name(tool_name: &str) -> bool {
if tool_name.is_empty() || tool_name.len() > MAX_MCP_TOOL_NAME_BYTES {
return false;
}
tool_name
.bytes()
.all(|byte| byte.is_ascii_alphanumeric() || matches!(byte, b'_' | b'-' | b'.'))
}

fn map_tool(tool_name: &str) -> Result<(&'static str, ActionKind), McpToolBoundaryError> {
MCP_TOOL_CATALOG
.iter()
.find(|entry| entry.tool_name == tool_name)
.map(|entry| (entry.tool_name, entry.action_kind))
.ok_or(McpToolBoundaryError::UnknownTool)
}
15 changes: 15 additions & 0 deletions crates/originweave-core/src/root.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
//! Shared security and governance contracts for OriginWeave.
//!
//! The historical core contracts remain source-compatible while adapter-specific
//! boundaries can live in focused modules without changing their authority model.

#![forbid(unsafe_code)]
#![deny(missing_docs)]

#[path = "lib.rs"]
mod contracts;

pub use contracts::*;

/// Stateless MCP routing validation that maps only explicit tools to typed actions.
pub mod mcp;
Loading
Loading