Skip to main content

Hardware token approval for AI agent tool calls - WebAuthn security layer for MCP tools

Project description

CoSig

CI codecov PyPI version Python 3.12+ License Code style: ruff

⚠️ ALPHA RELEASE (v0.2.0a1) CoSig is in early development. APIs may change. Security audit recommended before production use. See KNOWN_ISSUES.md for current limitations and known issues.

WebAuthn co-signing library for MCP servers — require human approval via hardware token (YubiKey) for AI tool calls.

What is CoSig?

CoSig adds a security layer to MCP (Model Context Protocol) servers. When an AI agent wants to execute a sensitive operation, the human must co-sign the request by tapping their hardware security key (YubiKey, Touch ID, Windows Hello).

Use case: Add YubiKey approval to FastMCP tools using a simple decorator.

Security guarantees:

  • Unauthorized execution blocked — AI can't run tools without human tap
  • Replay attacks prevented — Each signature has a unique counter
  • Non-repudiation — Cryptographic proof the user approved
  • Audit trail — Every action logged
  • Enterprise-grade encryption — AES-256-GCM with HMAC tamper detection
  • Immutable audit logs — Blockchain-style hash chaining prevents tampering

Demo

See CoSig in action:

CoSig Demo

Watch the video demonstration to see how YubiKey approval works with MCP servers.

Server Requirements

⚠️ Important: CoSig requires a CoSig Cloud server to handle WebAuthn approvals.

You have two options:

  1. Self-hosted: Deploy cosig-cloud on your own infrastructure
  2. Enterprise hosting: Contact us about CoSig Enterprise for managed hosting

The CoSig Cloud server provides:

  • WebAuthn approval flow UI
  • Credential verification
  • Audit log storage
  • API endpoints for approval status

See the cosig-cloud repository for deployment instructions.

Quick Start

Installation

pip install cosig fastmcp

FastMCP Integration

# my_mcp_server.py
from fastmcp import FastMCP
from cosig import require_approval
import asyncpg

mcp = FastMCP("Enterprise Server")

# Dangerous operation - requires YubiKey approval
@mcp.tool()
@require_approval()
async def delete_customer_data(customer_id: str) -> str:
    """Delete customer PII - requires YubiKey tap for GDPR compliance"""
    conn = await asyncpg.connect('postgresql://...')
    await conn.execute('DELETE FROM customers WHERE id = $1', customer_id)
    await conn.close()
    return f"Deleted customer {customer_id}"

# Another sensitive operation
@mcp.tool()
@require_approval()
async def deploy_to_production(service: str, version: str) -> str:
    """Deploy to production - requires SRE approval via YubiKey"""
    result = await deploy_service(service, version, environment="production")
    return f"Deployed {service} v{version} to production"

# Safe operation - no approval needed
@mcp.tool()
async def get_customer_info(customer_id: str) -> str:
    """Read-only operation - no approval required"""
    conn = await asyncpg.connect('postgresql://...')
    data = await conn.fetchrow('SELECT * FROM customers WHERE id = $1', customer_id)
    await conn.close()
    return str(data)

How It Works

  1. First call → Tool raises ApprovalRequiredError with URL
  2. User opens URL → Approval page shows tool call details
  3. User taps YubiKey → WebAuthn verification
  4. Client retries → Tool executes successfully
# In your MCP client:
try:
    result = await mcp.call_tool("delete_customer_data", {"customer_id": "cust_123"})
except ApprovalRequiredError as e:
    print(f"Approval required: {e.approval_url}")
    # User taps YubiKey at the URL
    # Then retry:
    result = await mcp.call_tool("delete_customer_data", {"customer_id": "cust_123"})
    print(result)  # "Deleted customer cust_123"

Real-World Use Cases

Financial Services

from fastmcp import FastMCP
from cosig import require_approval

mcp = FastMCP("Financial Services")

@mcp.tool()
@require_approval()
async def release_hold(account_id: str, hold_id: str) -> str:
    """Release payment hold - requires compliance officer YubiKey approval"""
    # Release hold...
    return f"Released hold {hold_id}"

@mcp.tool()
@require_approval()
async def modify_credit_limit(customer_id: str, new_limit: float) -> str:
    """Modify customer credit limit - requires manager YubiKey approval"""
    # Update limit...
    return f"Updated limit to ${new_limit}"

Healthcare

@mcp.tool()
@require_approval()
async def access_patient_records(patient_id: str) -> str:
    """Access PHI - requires HIPAA-compliant YubiKey approval"""
    # Log access, retrieve records...
    return "Records retrieved"

@mcp.tool()
@require_approval()
async def edit_medical_billing_code(patient_id: str, old_code: str, new_code: str) -> str:
    """Edit medical billing code - requires billing manager YubiKey approval"""
    # Update billing code...
    return f"Updated code from {old_code} to {new_code}"

Infrastructure & DevOps

@mcp.tool()
@require_approval()
async def scale_production_cluster(cluster_name: str, node_count: int) -> str:
    """Scale production Kubernetes cluster - requires SRE YubiKey approval"""
    # Scale cluster...
    return f"Scaled to {node_count} nodes"

@mcp.tool()
@require_approval()
async def modify_firewall_rules(rule_id: str, action: str) -> str:
    """Modify production firewall - requires security team YubiKey approval"""
    # Update firewall...
    return "Firewall updated"

@mcp.tool()
@require_approval()
async def rotate_api_keys(service_name: str) -> str:
    """Rotate production API keys - requires YubiKey approval"""
    # Rotate keys...
    return "Keys rotated"

Data & Analytics

@mcp.tool()
@require_approval()
async def export_customer_data(dataset_name: str, destination: str) -> str:
    """Export customer PII - requires data governance YubiKey approval"""
    # Export data...
    return "Data exported"

@mcp.tool()
@require_approval()
async def run_expensive_query(query: str) -> str:
    """Run expensive analytics query - requires YubiKey approval to prevent cost overruns"""
    # Execute query...
    return "Query completed"

Higher Education Administration

@mcp.tool()
@require_approval()
async def release_course_signup_hold(student_id: str, hold_id: str) -> str:
    """Release course registration hold - requires registrar YubiKey approval"""
    # Release hold in student information system...
    return f"Released hold {hold_id} for student {student_id}"

@mcp.tool()
@require_approval()
async def access_student_records(student_id: str) -> str:
    """Access student records - requires FERPA-compliant YubiKey approval"""
    # Log access, retrieve records...
    return "Student records retrieved"

@mcp.tool()
@require_approval()
async def release_student_record(student_id: str, recipient: str) -> str:
    """Release student transcript - requires registrar YubiKey approval"""
    # Release transcript to authorized recipient...
    return f"Released transcript for {student_id} to {recipient}"

Configuration

Configure CoSig programmatically in your application:

from cosig import configure

configure(
    cosig_url="https://cosig.example.com",     # CoSig approval server URL
    api_key="your-api-key",                     # API key for authentication
    timeout=300,                                # Approval timeout in seconds
)

For production deployments:

  • Use HTTPS and set the CoSig URL to your domain
  • Store API keys in a secrets manager (AWS Secrets Manager, Azure Key Vault, HashiCorp Vault)
  • See SECURITY.md for enterprise security architecture details

How It Works

┌─────────────┐     1. Create Plan      ┌─────────────┐
│   AI Agent  │ ───────────────────────▶│   CoSig     │
│             │                         │             │
│ "Delete     │                         │ Plan:       │
│  customer   │                         │ - Action    │
│  cust_123"  │                         │ - Status:   │
└─────────────┘                         │   PENDING   │
                                        └──────┬──────┘
                                               │
                                     2. Challenge
                                               ▼
┌─────────────┐     3. Tap YubiKey     ┌─────────────┐
│    Human    │ ◀──────────────────────│   Browser   │
│ (Compliance │                        │             │
│  Officer)   │                        │ "Approve    │
└──────┬──────┘                        │  deletion?" │
       │                               └─────────────┘
       │ 4. Signed Credential
       ▼
┌─────────────┐     5. Execute         ┌─────────────┐
│   CoSig     │ ───────────────────────▶│  Database   │
│             │                         │             │
│ Verifies:   │                         │ DELETE FROM │
│ - Signature │                         │ customers   │
│ - Counter   │                         │ WHERE...    │
│ - Binding   │                         └─────────────┘
└─────────────┘
       │
       │ 6. Audit Log
       ▼
┌─────────────────────────────────────────────────────┐
│ Audit Trail                                         │
│ [2025-12-22 10:00:00] alice approved deletion      │
│ [2025-12-22 10:00:01] Deleted customer cust_123    │
│ YubiKey: credential_abc123, sign_count: 42         │
└─────────────────────────────────────────────────────┘

Why CoSig?

For Compliance Teams

  • GDPR/CCPA: Prove human approved data deletion
  • SOX: Audit trail for financial operations
  • HIPAA: Log PHI access with non-repudiable proof
  • PCI-DSS: Require approval for sensitive operations

For Security Teams

  • Zero Trust: AI can't act without human approval
  • Phishing-resistant: Hardware token can't be stolen remotely
  • Replay prevention: Each signature is unique
  • Audit trail: Every action logged with cryptographic proof

For DevOps Teams

  • Production safety: Prevent accidental deployments
  • Cost control: Approve expensive operations
  • Change management: Require approval for infrastructure changes

Development

# Clone the repo
git clone https://github.com/skyforest/cosig
cd cosig

# Install dev dependencies
make dev

# Run tests
make test

# Lint and type check
make lint

# Format code
make format

Requirements

  • Python 3.12+
  • FIDO2 hardware key (YubiKey 5, Titan, SoloKey, etc.)
  • Modern browser with WebAuthn support

License

BSD-3-Clause - See LICENSE for details.

Project details


Download files

Download the file for your platform. If you're not sure which to choose, learn more about installing packages.

Source Distribution

cosig-0.2.0a4.tar.gz (21.8 kB view details)

Uploaded Source

Built Distribution

If you're not sure about the file name format, learn more about wheel file names.

cosig-0.2.0a4-py3-none-any.whl (19.3 kB view details)

Uploaded Python 3

File details

Details for the file cosig-0.2.0a4.tar.gz.

File metadata

  • Download URL: cosig-0.2.0a4.tar.gz
  • Upload date:
  • Size: 21.8 kB
  • Tags: Source
  • Uploaded using Trusted Publishing? No
  • Uploaded via: twine/6.2.0 CPython/3.12.8

File hashes

Hashes for cosig-0.2.0a4.tar.gz
Algorithm Hash digest
SHA256 2b35c8b5792fb4854691d7d5d68198cb6267535c3ba13f2fa65ff73706dc55cd
MD5 70a46e946151ba16e476151c9d52d30e
BLAKE2b-256 929bc4f6cba6418fa6d9bef0f47d3ae3305182212958d74e4bfea6298f1dc4fb

See more details on using hashes here.

File details

Details for the file cosig-0.2.0a4-py3-none-any.whl.

File metadata

  • Download URL: cosig-0.2.0a4-py3-none-any.whl
  • Upload date:
  • Size: 19.3 kB
  • Tags: Python 3
  • Uploaded using Trusted Publishing? No
  • Uploaded via: twine/6.2.0 CPython/3.12.8

File hashes

Hashes for cosig-0.2.0a4-py3-none-any.whl
Algorithm Hash digest
SHA256 93614209d3e1c951a9c28838283bd7a8ba1641588b0c3e71a6a2c1487d3ae465
MD5 570c6faaf0b9728ccfeb284191479ecf
BLAKE2b-256 ffd297822c3cddd143070c8277ee1ddf2303574064df76068c0d2d2add875a67

See more details on using hashes here.

Supported by

AWS Cloud computing and Security Sponsor Datadog Monitoring Depot Continuous Integration Fastly CDN Google Download Analytics Pingdom Monitoring Sentry Error logging StatusPage Status page