# gatekeeper

**Category:** 🔒 Security  
**Repository:** https://github.com/Runestone-Labs/gatekeeper  
**npm Downloads (last month):** 86  
**Views:** 0  
**Installs:** 0  
**Upvotes:** 0  
**Directory Page:** https://allmcps.com/mcp/gatekeeper

## Description
Run MCP tool calls through Runestone Gatekeeper — policy enforcement, human approval, audit.

## Claude Desktop Quick Installation
Heuristic fallback — verify the package name and runner against the repository README before running it. Uses `npx` (confidence: low):

```json
"mcpServers": {
  "gatekeeper": {
    "command": "npx",
    "args": ["-y","gatekeeper"]
  }
}
```

## Documentation & README

# Runestone Agent Gatekeeper

[![CI](https://github.com/Runestone-Labs/gatekeeper/actions/workflows/ci.yml/badge.svg)](https://github.com/Runestone-Labs/gatekeeper/actions/workflows/ci.yml)
[![npm](https://img.shields.io/npm/v/@runestone-labs/gatekeeper-client)](https://www.npmjs.com/package/@runestone-labs/gatekeeper-client)
[![License](https://img.shields.io/badge/License-Apache_2.0-blue.svg)](LICENSE)

A policy-based gatekeeper service that sits between AI agents and real-world tools (shell, HTTP, filesystem), enforcing approvals, denials, USD budgets, and audit logging.

## Who Is This For

If you're running AI agents (Claude, GPT, local LLMs, MCP servers) that execute tool calls against real systems — and you're responsible for what happens when those calls go wrong — Gatekeeper is for you. Specifically:

- **You build agentic apps** and your agent can run shell commands, write files, or hit external APIs.
- **You've already had one "why did it do that?" moment** — or you're staring down the possibility.
- **You want a self-hostable, auditable, single-binary boundary** between your agent and the world, without adopting an enterprise SSO stack or wiring everything through a cloud proxy.
- **You need USD budgets and signed approvals** as first-class primitives, not features bolted onto an observability tool.

You were probably using: nothing (and trusting the prompt), ad-hoc Python wrappers around `subprocess`, a cloud LLM gateway, or shell-level `sudoers` rules that don't understand tool semantics.

## What Problem This Solves

AI agents need to execute actions in the real world: running shell commands, writing files, making HTTP requests. Without guardrails, an agent can accidentally (or adversarially) execute dangerous operations.

The Gatekeeper intercepts all tool requests and:
- **Allows** low-risk operations immediately
- **Denies** operations that match dangerous patterns
- **Requires human approval** for sensitive operations
- **Catches sensitive-boundary crossings** — Keychain, SSH keys, cloud credentials, browser profiles, package-registry tokens, env files (see [Sensitive Boundary Protection](#sensitive-boundary-protection))
- **Rejects when a USD / token / call budget is exceeded** (optional) — per **actor** (a rolling guardrail) or per **run** (`scope: run`, keyed on `runId`: caps a single agentic run at the action boundary, where recursive burn compounds)
- **Optionally proxies Anthropic model calls** — route inference through the gatekeeper so every `/v1/messages` call is audited, the API key stays centralized, and real per-token **cost** is metered onto the audit row and into budgets (off by default; see [docs/API.md](docs/API.md#all-anthropic))

All decisions are logged to an append-only audit trail (jsonl or Postgres). An aggregation endpoint (`/usage`) exposes call counts — and real cost/token sums — by actor × tool × day. A budget endpoint (`/budget`) surfaces current spend vs cap per configured rule.

## Sensitive Boundary Protection

Coding agents fail in subtle ways. They start with a safe task, then escalate
into sensitive local operations while trying to be helpful. Gatekeeper ships
a built-in rule pack that catches these crossings.

**Real failure pattern:** A coding agent debugging a Puppeteer / Chromium
"Safe Storage" prompt on macOS first applied a normal flag-based fix
(`--use-mock-keychain`, `--password-store=basic`, throwaway `--user-data-dir`).
When the prompt persisted, the agent pivoted to inspecting the user's
Keychain — and proposed deleting entries "so there's nothing to access."

```bash
# Step 2 of the agent's reasoning — Gatekeeper requires approval (high risk)
security find-generic-password -s "Chromium Safe Storage"

# Step 3 of the agent's reasoning — Gatekeeper denies outright (critical)
security delete-generic-password -s "Chromium Safe Storage"
```

Gatekeeper classifies these against a built-in rule pack:

| Resource class            | Examples                                                    |
| ------------------------- | ----------------------------------------------------------- |
| `credential_store`        | macOS Keychain (find / dump / delete)                       |
| `private_key`             | `~/.ssh/id_*`, `*.pem`, `~/.ssh` directory enumeration      |
| `cloud_credentials`       | `~/.aws/credentials`, `~/.config/gcloud/`, `~/.azure/`      |
| `env_secret`              | `cat .env`, `.env.*` reads via shell                        |
| `package_registry_token`  | `~/.npmrc`, `~/.pypirc`                                     |
| `developer_auth`          | `~/.git-credentials`, `gh auth token`                       |
| `browser_profile`         | Chrome / Chromium / Brave / Arc / Firefox profile dirs      |
| `unknown_sensitive`       | Broad recursive home-dir secret greps                       |

Each rule has an `effect` (`allow | require_approval | deny`), a stable
`category`, a `risk` level, and an optional `safer_alternative` redirect.
Rules are mirrored into `riskFlags` (`boundary:keychain-read`,
`category:credential_store_access`, `risk:high`) so existing audit consumers
work unchanged.

**Defaults always load** — no opt-in required. Override or extend them under
`sensitive_boundaries:` in your `policy.yaml`; the full reference dump lives
at [`policies/sensitive-boundaries.yaml`](policies/sensitive-boundaries.yaml).
A demo fixture for the Puppeteer → Keychain escalation is at
[`examples/sensitive-boundaries/keychain-scope-creep.json`](examples/sensitive-boundaries/keychain-scope-creep.json).

### Use it from Claude Code

[`@runestone-labs/gatekeeper-claude-code`](integrations/claude-code/) is a
Claude Code PreToolUse hook that routes `Bash` / `Write` / `Edit` / `WebFetch`
through Gatekeeper before Claude Code executes them. Install once globally,
drop the [`settings.example.json`](integrations/claude-code/settings.example.json)
snippet into `~/.claude/settings.json`, and every gated tool call gets
evaluated against the boundary pack:

```bash
npm install -g @runestone-labs/gatekeeper-claude-code
```

```json
{
  "hooks": {
    "PreToolUse": [
      {
        "matcher": "Bash|Write|Edit|WebFetch",
        "hooks": [{ "type": "command", "command": "gatekeeper-claude-code-hook" }]
      }
    ]
  }
}
```

Fail-open by default if the Gatekeeper server is down; set
`GATEKEEPER_FAIL_CLOSED=1` to flip to fail-closed. See
[`integrations/claude-code/README.md`](integrations/claude-code/README.md)
for the full configuration reference.

### Use it from any MCP client

[`@runestone-labs/gatekeeper-mcp`](integrations/mcp-server/) is a stdio MCP
server that lets any MCP client (Claude Desktop, Claude Code, Cursor, …) run
real tool calls — `shell_exec`, `files_write`, `http_request`, a generic
`gatekeeper_call`, plus `gatekeeper_health` — but only by routing every call
through Gatekeeper. Identity, role, and origin are pinned server-side (the
model can't escalate via tool arguments), and it fails closed: a deny, a
pending approval, or any malformed Gatekeeper response surfaces as an error,
never as a successful result.

Add it to your MCP client config:

```json
{
  "mcpServers": {
    "gatekeeper": {
      "command": "npx",
      "args": ["-y", "@runestone-labs/gatekeeper-mcp"],
      "env": {
        "GATEKEEPER_URL": "http://127.0.0.1:3847",
        "GATEKEEPER_ROLE": "agent"
      }
    }
  }
}
```

It's also listed on the MCP Registry (preview) as
`io.github.runestone-labs/gatekeeper`. See
[`integrations/mcp-server/README.md`](integrations/mcp-server/README.md) for
the full tool table and security model.

## Threat Model

This gatekeeper protects against:

1. **Accidental damage**: Agent runs `rm -rf /` or overwrites critical files
2. **Prompt injection execution**: Malicious content tricks agent into dangerous actions
3. **Exfiltration**: Agent sends secrets to external services
4. **SSRF attacks**: Agent accesses internal services via HTTP

This gatekeeper does NOT protect against:

- Malicious operator with access to the policy file
- Attacks on the gatekeeper service itself
- Social engineering of the human approver
- Denial of service (no rate limiting)

## Quick Start with Docker

The fastest way to try Gatekeeper:

```bash
git clone https://github.com/Runestone-Labs/gatekeeper.git
cd gatekeeper
npm run bootstrap    # generates .env with a fresh GATEKEEPER_SECRET
docker compose up
```

`npm run bootstrap` creates `.env` from `.env.example` and writes a random 48-character `GATEKEEPER_SECRET` into it. Skip if you already have a `.env` you want to keep — docker-compose will error clearly if the secret isn't set.

Gatekeeper is now running at http://127.0.0.1:3847 with demo mode enabled.

Test it with the quickstart script (walks through DENY, ALLOW, and APPROVE):
```bash
bash examples/quickstart.sh
```

Or test individual decisions:
```bash
# requestId must be a UUID
# This will be DENIED (dangerous pattern)
curl -s -X POST http://127.0.0.1:3847/tool/shell.exec \
  -H "Content-Type: application/json" \
  -d '{"requestId":"'"$(uuidgen)"'","actor":{"type":"agent","name":"test","role":"openclaw"},"args":{"command":"rm -rf /"}}'

# This will be ALLOWED
curl -s -X POST http://127.0.0.1:3847/tool/http.request \
  -H "Content-Type: application/json" \
  -d '{"requestId":"'"$(uuidgen)"'","actor":{"type":"agent","name":"test","role":"openclaw"},"args":{"url":"https://httpbin.org/get","method":"GET"}}'
```

To customize policy:
```bash
cp policy.example.yaml policy.yaml
# Edit policy.yaml, then update docker-compose.yaml volume to use ./policy.yaml
docker-compose restart
```

For manual installation without Docker, see below.

## Quick Start (Manual)

### 1. Install Dependencies

```bash
npm install
```

### 2. Configure Environment

```bash
# Required: Secret for HMAC signing (at least 32 characters)
export GATEKEEPER_SECRET="your-secret-key-at-least-32-chars-long"

# Provider selection (optional)
export APPROVAL_PROVIDER=local   # local | slack | runestone (default: local)
export AUDIT_SINK=jsonl          # jsonl | runestone (default: jsonl)
export POLICY_SOURCE=yaml        # yaml | runestone (default: yaml)

# Optional: Slack webhook for approval notifications (when using slack provider)
export SLACK_WEBHOOK_URL="https://hooks.slack.com/services/..."

# Optional: Custom port (default: 3847)
export GATEKEEPER_PORT=3847

# Optional: Bind host (default: 127.0.0.1)
# Use 0.0.0.0 when running in Docker.
export GATEKEEPER_HOST=127.0.0.1

# Optional: Base URL for approval links
export BASE_URL="http://127.0.0.1:3847"

# Optional: Route Anthropic model calls through the gatekeeper (default: off)
# When enabled, point your SDK's ANTHROPIC_BASE_URL at <gatekeeper>/anthropic.
# See docs/API.md for the full /anthropic proxy reference.
export ENABLE_ANTHROPIC_PROXY=false

# Optional: Anthropic API key. When set, the /anthropic proxy injects it on
# proxied calls so the key can live only in gatekeeper (not in the agent).
export ANTHROPIC_API_KEY="sk-ant-..."
```

### 3. Create Policy File

```bash
cp policy.example.yaml policy.yaml
# Edit policy.yaml to match your requirements
```

### 4. Start the Server

```bash
npm start
# Or for development with auto-reload:
npm run dev
```

## Demo (2 minutes)

See all three decision types in action:

```bash
# Install dependencies
npm install

# Set a demo secret (or use your own)
export GATEKEEPER_SECRET="demo-secret-at-least-32-characters-long"

# Run the demo
npm run demo
```

The demo runs through:
1. **DENY** - Dangerous command (`rm -rf /`) is blocked
2. **APPROVE** - Safe command (`ls -la`) requires approval, then auto-approved
3. **ALLOW** - HTTP request executes immediately

### Recording

```bash
# Record with asciinema (creates demo.cast)
npm run demo:record

# Playback
asciinema play demo.cast

# Create GIF/MP4 with VHS (requires: brew install vhs)
npm run demo:gif
```

### Outputs

- `demo.cast` - Terminal recording (asciinema format)
- `demo.gif` - Animated GIF for sharing
- `demo.mp4` - Video file
- `data/audit/YYYY-MM-DD.jsonl` - Audit log with all demo actions

## Provider Architecture

The gatekeeper uses a pluggable provider system for flexibility:

### Approval Providers
- **local** (default): Logs approval URLs to console
- **slack**: Sends interactive approval requests via Slack webhook
- **runestone**: Enterprise control plane (coming soon)

### Audit Sinks
- **jsonl** (default): Writes to daily JSONL files in `data/audit/`
- **runestone**: Stream to cloud for search and compliance (coming soon)

### Policy Sources
- **yaml** (default): Load from local YAML file
- **runestone**: Managed policies with version control (coming soon)

## Example Requests

All tool requests must include `actor.role` to enforce principal policies. For safe retries, include an `idempotencyKey`.

### Execute a Tool (Allow Decision)

```bash
curl -X POST http://127.0.0.1:3847/tool/http.request \
  -H "Content-Type: application/json" \
  -d '{
    "requestId": "550e8400-e29b-41d4-a716-446655440000",
    "actor": {
      "type": "agent",
      "name": "my-agent",
      "role": "openclaw",
      "runId": "run-123"
    },
    "args": {
      "url": "https://api.example.com/data",
      "method": "GET"
    }
  }'
```

Response (200):
```json
{
  "decision": "allow",
  "requestId": "550e8400-e29b-41d4-a716-446655440000",
  "reasonCode": "POLICY_ALLOW",
  "humanExplanation": "Policy allows \"http.request\".",
  "policyVersion": "sha256:abc123...",
  "success": true,
  "result": {
    "status": 200,
    "headers": {"content-type": "application/json"},
    "body": "{...}"
  },
  "executionReceipt": {
    "startedAt": "2024-01-15T10:30:00.000Z",
    "completedAt": "2024-01-15T10:30:00.120Z",
    "durationMs": 120
  }
}
```

### Execute a Tool (Approve Decision)

```bash
curl -X POST http://127.0.0.1:3847/tool/shell.exec \
  -H "Content-Type: application/json" \
  -d '{
    "requestId": "550e8400-e29b-41d4-a716-446655440001",
    "actor": {
      "type": "agent",
      "name": "my-agent",
      "role": "openclaw"
    },
    "args": {
      "command": "ls -la /tmp"
    }
  }'
```

Response (202):
```json
{
  "decision": "approve",
  "requestId": "550e8400-e29b-41d4-a716-446655440001",
  "approvalId": "abc123...",
  "expiresAt": "2026-01-31T13:00:00.000Z",
  "reasonCode": "POLICY_APPROVAL_REQUIRED",
  "humanExplanation": "Policy requires human approval before running \"shell.exec\".",
  "message": "Approval required. Check local for approval links.",
  "approvalRequest": {
    "approvalId": "abc123...",
    "expiresAt": "2026-01-31T13:00:00.000Z",
    "reasonCode": "POLICY_APPROVAL_REQUIRED",
    "humanExplanation": "Policy requires human approval before running \"shell.exec\"."
  },
  "policyVersion": "sha256:abc123..."
}
```

### Execute a Tool (Deny Decision)

```bash
curl -X POST http://127.0.0.1:3847/tool/shell.exec \
  -H "Content-Type: application/json" \
  -d '{
    "requestId": "550e8400-e29b-41d4-a716-446655440002",
    "actor": {
      "type": "agent",
      "name": "my-agent",
      "role": "openclaw"
    },
    "args": {
      "command": "rm -rf /"
    }
  }'
```

Response (403):
```json
{
  "decision": "deny",
  "requestId": "550e8400-e29b-41d4-a716-446655440002",
  "reasonCode": "TOOL_DENY_PATTERN",
  "humanExplanation": "Request matches a deny pattern configured for this tool.",
  "policyVersion": "sha256:abc123..."
}
```

### Capability Tokens (Pre-Approved)

If a tool is configured with `decision: approve`, you can pre-authorize a specific call
with a capability token scoped to tool + args hash:

```bash
npm run capability:create -- --tool shell.exec --args /tmp/args.json --ttl 3600
```

Include the `capabilityToken` in the tool request. Gatekeeper will allow the call
without manual approval if the token is valid.

### Health Check

```bash
curl http://127.0.0.1:3847/health
```

Response:
```json
{
  "version": "0.3.2",
  "policyHash": "sha256:abc123...",
  "uptime": 3600,
  "pendingApprovals": 2,
  "demoMode": false,
  "providers": {
    "approval": "local",
    "policy": "yaml"
  },
  "database": {
    "available": true,
    "healthy": true,
    "latencyMs": 2
  },
  "memory": {
    "enabled": true
  }
}
```

## Policy Configuration

See `policy.example.yaml` for a complete example.

```yaml
tools:
  shell.exec:
    decision: approve           # allow | approve | deny
    deny_patterns:
      - "rm -rf"               # Regex patterns to block
    allowed_cwd_prefixes:
      - "/tmp/"                # Allowed working directories
    allowed_commands:
      - "ls"
      - "git"
    sandbox_command_prefix:
      - "firejail"
      - "--noprofile"
      - "--"
    max_output_bytes: 1048576
    max_timeout_ms: 30000

  files.write:
    decision: approve
    allowed_paths:
      - "/tmp/"
    deny_extensions:
      - ".env"
    max_size_bytes: 10485760

  http.request:
    decision: allow
    allowed_methods: ["GET", "POST"]
    allowed_domains:
      - "api.example.com"
    deny_domains:
      - "pastebin.com"
    deny_ip_ranges:            # SSRF protection
      - "127.0.0.0/8"
      - "169.254.0.0/16"
    max_body_bytes: 1048576
    max_redirects: 3
```

For a complete policy writing tutorial, see [docs/POLICY_GUIDE.md](docs/POLICY_GUIDE.md).

## Approval Flow

1. Agent submits tool request
2. Gatekeeper evaluates against policy
3. If `approve`: Creates pending approval, sends notification via configured provider
4. Human clicks Approve or Deny link
5. If Approved: Tool executes, result returned
6. All actions logged to audit trail

Approval links are:
- HMAC-signed (tamper-proof)
- Single-use (prevents replay)
- Time-limited (1 hour expiry)

For a detailed approval workflow guide, see [docs/APPROVALS.md](docs/APPROVALS.md).

## Audit Logs

All requests are logged via the configured audit sink. Default (jsonl) writes to `data/audit/YYYY-MM-DD.jsonl`:

```json
{
  "timestamp": "2026-01-31T12:00:00.000Z",
  "requestId": "550e8400-...",
  "tool": "shell.exec",
  "decision": "approve",
  "actor": {"type": "agent", "name": "my-agent", "role": "openclaw"},
  "argsSummary": "{\"command\":\"ls -la\"}",
  "riskFlags": [],
  "policyHash": "sha256:abc123...",
  "gatekeeperVersion": "0.3.2"
}
```

Logs are:
- Append-only (never modified)
- One file per day (easy rotation)
- Include policy hash (for forensics)
- Secrets are redacted

For a complete audit log reference with querying examples, see [docs/AUDIT_LOGS.md](docs/AUDIT_LOGS.md).

## Client Library

Install the TypeScript client for integrating your agent with Gatekeeper:

```bash
npm install @runestone-labs/gatekeeper-client
```

```typescript
import { GatekeeperClient } from '@runestone-labs/gatekeeper-client';

const client = new GatekeeperClient({
  baseUrl: 'http://127.0.0.1:3847',
  role: 'openclaw',
});

// Execute a shell command through the gatekeeper
const result = await client.shellExec({ command: 'ls -la' });
console.log(result.decision); // 'allow' | 'approve' | 'deny'
```

See the full [client README](integrations/typescript-client/README.md) for all available methods.

## Using with Real Agents

Gatekeeper is designed to be agent-agnostic. Any agent that can route tool calls over HTTP can integrate with Gatekeeper. See [INTEGRATING_AGENTS.md](INTEGRATING_AGENTS.md) for the integration pattern.

## Enterprise Control Plane

**Runestone Control Plane** provides:

- **Managed Policies**: Version-controlled policy configuration with templates
- **Searchable Audit**: Full-text search across all audit logs with compliance exports
- **Web-based Approvals**: Modern approval UI with mobile notifications
- **Team Workflows**: Approval routing, escalation, and delegation

Contact: enterprise@runestone.dev

## Security Decisions

| Feature | Implementation | Rationale |
|---------|----------------|-----------|
| Approval signing | HMAC-SHA256 of full payload | Prevents parameter tampering |
| Single-use approvals | Status field + atomic update | Prevents replay attacks |
| Expiry | 1 hour default | Limits approval window |
| Input validation | Zod with `.strict()` | Rejects unknown fields |
| Shell constraints | cwd allowlist, timeout caps | Limits blast radius |
| SSRF protection | DNS resolution + IP checks | Blocks internal access |
| Audit logging | Append-only via pluggable sink | Tamper-evident trail |

## Development

```bash
# Type check
npm run typecheck

# Run tests
npm run test:run

# Run with auto-reload
npm run dev

# Run production
npm start
```

## Memory Module (Optional)

The memory module provides graph-based knowledge storage (entities, episodes, evidence) via PostgreSQL + Apache AGE. It is an **optional module** — Gatekeeper works as a standalone policy engine without it.

**Without a database**: Only core tools (`shell.exec`, `files.write`, `http.request`) are registered. Policy enforcement, approvals, and JSONL audit logging work normally.

**With a database**: Memory tools are additionally registered, providing a knowledge graph for AI assistants.

### Configuration

```bash
# Set DATABASE_URL to enable the memory module
export DATABASE_URL="postgresql://user:pass@localhost:5432/memory"

# Or explicitly control (overrides DATABASE_URL detection)
export ENABLE_MEMORY=true   # or false to disable even with a DATABASE_URL
```

### Database Setup

```bash
# Generate migration SQL from schema changes
npm run db:generate

# Apply migrations to a running database
npm run db:migrate

# Or push schema directly (dev only)
npm run db:push
```

### Memory Tools

| Tool | Description |
|------|-------------|
| `memory.upsert` | Create/update entities (people, projects, concepts) |
| `memory.link` | Create relationships between entities |
| `memory.unlink` | Remove relationships between entities |
| `memory.query` | Query entities (with full-text search) and traverse relationships |
| `memory.episode` | Log decisions, events, and observations |
| `memory.evidence` | Attach evidence/provenance to entities or episodes |

```bash
# Create an entity
curl -X POST http://127.0.0.1:3847/tool/memory.upsert \
  -H "Content-Type: application/json" \
  -d '{"requestId":"...","actor":{"type":"agent","name":"test","role":"openclaw"},"args":{"type":"person","name":"Alice"}}'

# Link two entities
curl -X POST http://127.0.0.1:3847/tool/memory.link \
  -H "Content-Type: application/json" \
  -d '{"requestId":"...","actor":{"type":"agent","name":"test","role":"openclaw"},"args":{"sourceId":"<id1>","targetId":"<id2>","relation":"knows"}}'
```

### Schema Architecture

The database schema is split into two modules:
- **`src/db/schema/audit.ts`** — Audit logs table (core gatekeeper, always available)
- **`src/db/schema/memory.ts`** — Knowledge graph tables: entities, episodes, evidence (optional module)

The KG schema is a generic entity/episode/evidence model. Application-specific ontology (entity types, facet types, edge relations) is defined by the consuming application, not by gatekeeper.

See [docs/MEMORY.md](docs/MEMORY.md) for setup and full API reference.

## Documentation

### Guides
- [docs/POLICY_GUIDE.md](docs/POLICY_GUIDE.md) - How to write and customize policies (including budgets)
- [docs/APPROVALS.md](docs/APPROVALS.md) - Approval workflow details and troubleshooting
- [docs/AUDIT_LOGS.md](docs/AUDIT_LOGS.md) - Audit log format and querying
- [docs/MEMORY.md](docs/MEMORY.md) - Graph-based memory system setup and API reference
- [docs/KG_PATTERNS.md](docs/KG_PATTERNS.md) - Practical patterns for using the memory module (entities, episodes, evidence, provenance conventions)
- [docs/DEPLOY.md](docs/DEPLOY.md) - Production deployment + non-obvious operational behaviors

### Reference
- [THREAT_MODEL.md](THREAT_MODEL.md) - Security assumptions and non-goals
- [INTEGRATING_AGENTS.md](INTEGRATING_AGENTS.md) - Using Gatekeeper with real agents
- [RUNESTONE_CLOUD.md](RUNESTONE_CLOUD.md) - OSS vs Cloud architecture

### Contributing
- [CONTRIBUTING.md](CONTRIBUTING.md) - How to contribute
- [SECURITY.md](SECURITY.md) - Security policy and vulnerability reporting
- [GOVERNANCE.md](GOVERNANCE.md) - Project governance
- [CHANGELOG.md](CHANGELOG.md) - Release history

## License

Apache-2.0 - See [LICENSE](LICENSE) for details.

