Skip to content

Security Model

Z-M-Huang edited this page Mar 19, 2026 · 3 revisions

Security Model

git-doc-mcp executes untrusted code from remote URLs. Its security model uses defense-in-depth with three isolation layers, strict URL validation, and scoped secret access.

Three-Layer Isolation

┌───────────────────────────────────────┐
│  Main Process (CLI)                   │
│  - Loads manifest                     │
│  - Manages secrets                    │
│  - MCP protocol                       │
│                                       │
│  ┌─────────────────────────────────┐  │
│  │  Worker Process (Layer 1)       │  │
│  │  - Separate Node.js process     │  │
│  │  - Sanitized environment        │  │
│  │  - Crash isolation              │  │
│  │                                 │  │
│  │  ┌───────────────────────────┐  │  │
│  │  │  isolated-vm (Layer 2)    │  │  │
│  │  │  - V8 isolate             │  │  │
│  │  │  - Memory: 128MB          │  │  │
│  │  │  - CPU timeout: 30s       │  │  │
│  │  │  - No filesystem access   │  │  │
│  │  │  - No network access      │  │  │
│  │  │                           │  │  │
│  │  │  ┌─────────────────────┐  │  │  │
│  │  │  │  ctx API (Layer 3)  │  │  │  │
│  │  │  │  - URL validation   │  │  │  │
│  │  │  │  - Secret scoping   │  │  │  │
│  │  │  │  - Audit logging    │  │  │  │
│  │  │  └─────────────────────┘  │  │  │
│  │  └───────────────────────────┘  │  │
│  └─────────────────────────────────┘  │
└───────────────────────────────────────┘

Layer 1: Worker Process

  • Separate Node.js process with sanitized environment variables
  • Crash isolation: worker crash doesn't affect the main process
  • Communication via newline-delimited JSON over stdin/stdout

Layer 2: isolated-vm

  • V8 isolate: completely separate JavaScript heap
  • Memory limit: 128MB (configurable via --memory-limit, range 8MB-1GB)
  • CPU timeout: 30 seconds (hardcoded)
  • No direct access to filesystem, network, or Node.js APIs
  • Action code can only interact with the outside world through ctx callbacks

Layer 3: Controlled API (ctx)

  • ctx.fetch: All URLs validated for SSRF before any request
  • ctx.getSecret: Secrets only returned when URL matches the scope pattern
  • ctx.log: Safe logging (strings only, no code injection)
  • Audit logging: Every fetch and secret access is logged

SSRF Protection

All HTTP(S) URLs are validated before any network request, whether from the manifest (action URLs, resource URIs) or from action scripts (ctx.fetch).

Local file paths for action and resource.uri fields bypass SSRF validation -- they are read directly from disk, not over the network. Local file paths are only allowed when the manifest itself is loaded from a local file; remote manifests with local paths are rejected.

What's Blocked

Category Examples
Private IPv4 10.0.0.0/8, 172.16.0.0/12, 192.168.0.0/16
Loopback 127.0.0.0/8, localhost
Link-local 169.254.0.0/16
Private IPv6 ::1, fc00::/7, fe80::/10
HTTP (default) All http:// URLs (use --allow-http to override)

DNS Validation

URLs are resolved to IP addresses and validated:

  1. DNS lookup is performed
  2. Resolved IPs are checked against blocked ranges
  3. If any resolved IP is private, the request is blocked
  4. This prevents DNS rebinding attacks

Redirect Validation

Every redirect is re-validated:

  1. Redirect URL is resolved and checked for SSRF
  2. Cross-origin redirects strip Authorization and Cookie headers
  3. Maximum 5 redirects (not configurable)
  4. Relative redirects are resolved against the current URL

Manifest Integrity

Trust-on-First-Use (TOFU)

First run:
  manifest.yml → SHA256 → stored in ~/.git-doc-mcp/

Subsequent runs:
  manifest.yml → SHA256 → compare with stored hash
    ├── Match: OK, proceed
    ├── Mismatch + --trust-changed: accept new hash
    └── Mismatch (default): FAIL, refuse to start

Hash Pinning

For CI/CD or security-critical deployments, pin the manifest hash explicitly:

git-doc-mcp --manifest https://example.com/manifest.yml \
  --manifest-hash "sha256:a1b2c3d4e5f6..."

Action Hash Verification

Every action script has a SHA256 hash in the manifest:

tools:
  - name: my-tool
    action: https://example.com/actions/my-tool.v1.js
    actionHash: "sha256:a1b2c3d4e5f6..."

Before execution, the fetched action code is hashed and compared. If the hash doesn't match, execution is refused.

Secret Scoping

Secrets are never freely accessible to action scripts. Every ctx.getSecret(name, url) call validates the URL against the secret's declared scope.

secrets:
  - name: GITHUB_TOKEN
    scope:
      - "https://api.github.com/*"
      - "https://raw.githubusercontent.com/*"
// In action script:
ctx.getSecret('GITHUB_TOKEN', 'https://api.github.com/repos/owner/repo');
// → Returns the token (URL matches scope)

ctx.getSecret('GITHUB_TOKEN', 'https://evil.com/steal');
// → Returns undefined (URL doesn't match scope)

Scope Enforcement Rules

  • Exact origin: api.github.com won't match evil.api.github.com
  • Path-boundary wildcards: /repos/* matches /repos/foo but not /repos-private
  • No scheme downgrade: HTTPS scope won't match HTTP URLs
  • Audit logging: All access attempts (granted and denied) are logged

Rate Limiting

Sliding-window rate limiter to prevent abuse:

# Default: 60 calls per minute
git-doc-mcp --manifest ./manifest.yml --rate-limit 60

# Unlimited (for trusted environments)
git-doc-mcp --manifest ./manifest.yml --rate-limit 0

Rate limiting applies to tool calls, not individual fetch requests within actions.

Audit Logging

All security-relevant events are logged to ~/.git-doc-mcp/logs/audit.jsonl:

{"timestamp":"2024-01-15T10:30:00Z","event":"fetch","url":"https://api.github.com/repos/...","status":"200","duration_ms":150,"manifestName":"my-mcp"}
{"timestamp":"2024-01-15T10:30:01Z","event":"secret-access","secretName":"GITHUB_TOKEN","url":"https://api.github.com/...","status":"allowed","manifestName":"my-mcp"}
{"timestamp":"2024-01-15T10:30:02Z","event":"redirect","url":"https://new.example.com/...","details":{"from":"https://old.example.com/..."},"manifestName":"my-mcp"}

Logs are automatically rotated to prevent disk exhaustion.

Security Checklist for Manifest Authors

  • Use HTTPS for all remote action URLs and resource URIs (local file paths are acceptable for development)
  • Compute accurate SHA256 hashes for all action scripts
  • Scope secrets to the minimum required URL patterns
  • Mark secrets as required: false unless the tool truly can't function without them
  • Validate API responses in action scripts before using data
  • Use specific error messages that don't leak internal details
  • Version action scripts (e.g., my-tool.v2.js) rather than modifying in place
  • Test with --manifest-hash pinning before deploying to production

Clone this wiki locally