{
  "$schema": "https://json-schema.org/draft/2020-12/schema",
  "title": "OpenLore MCP server — security capability declaration",
  "description": "Machine-readable declaration of the server's security-relevant capabilities (filesystem, subprocess, network, credentials, local daemon) plus an accepted-risk register of benign source->sink patterns a generic scanner may flag. Kept in sync with the code by security-capabilities.test.ts. See openspec/specs/mcp-security/spec.md.",
  "tool": "openlore",
  "specDomain": "mcp-security",
  "capabilities": {
    "filesystem": {
      "reads": [
        { "tree": "<project root>", "what": "source files for static analysis (tree-sitter parse, call graph, signatures)" },
        { "tree": ".openlore/", "what": "analysis artifacts (llm-context.json, mapping.json), SQLite edge store, config.json, decisions, memory, serve.json — treated as untrusted, size-bounded, shape-validated, fail-closed" },
        { "tree": "openspec/", "what": "spec.md domains, change proposals, decision ADRs" },
        { "tree": ".git/", "what": "read indirectly via the git subprocess (history, diffs, change coupling)" }
      ],
      "writes": [
        { "tree": ".openlore/", "what": "analysis cache, edge store, decisions store, memory notes, telemetry JSONL, serve.json discovery file" },
        { "tree": "openspec/", "what": "synced spec decisions (append-only to pre-existing specs), change proposals (sanitized slug), decision ADRs (kebab-cased filenames)" },
        { "tree": ".git/hooks/", "what": "post-commit / pre-commit hook install — opt-in via explicit --install-hook only" }
      ],
      "confinement": "Every tool path argument is confined to the validated project root via validateDirectory + safeJoin, which enforce canonical (symlink-resolved) containment. Mutators write only within .openlore/ and openspec/."
    },
    "subprocess": {
      "spawns": [
        { "bin": "git", "how": "execFile/execFileSync/spawnSync with an argv array (no shell); refs validated against argument injection (leading-dash + shell-meta)", "why": "history, diffs, change coupling, drift detection" },
        { "bin": "openlore (self)", "how": "spawn(process.execPath | resolved bin, ['decisions','--consolidate'|'analyze']) with fixed argv", "why": "background decision consolidation / re-analyze" },
        { "bin": "claude | gemini | vibe (LLM CLIs)", "how": "execFileSync(bin, argv) with fixed argv; bin overridable by env (CLAUDE/GEMINI_CLI/MISTRAL_VIBE_CLI)", "why": "optional CLI-based LLM provider transport (alternative to HTTP)" }
      ],
      "method": "argv arrays only; no shell:true and no shell-binary (-c) invocation anywhere on the core+cli server surface.",
      "exception": "src/pi (the VS Code extension launcher) uses a Windows-only shell:true with FIXED arguments — see acceptedRisks."
    },
    "network": {
      "egress": [
        { "to": "configured LLM provider endpoint", "source": "baseUrl from config / provider env vars", "data": "prompts derived from analysis + (for LLM-backed tools) repo-derived content" },
        { "to": "configured embedding provider endpoint", "source": "EMBED_BASE_URL / config.embedding", "data": "text to embed for semantic search" },
        { "to": "loopback (127.0.0.1)", "source": "local serve daemon transport", "data": "in-process tool dispatch only" }
      ],
      "allowedHosts": [
        "api.anthropic.com",
        "api.openai.com",
        "generativelanguage.googleapis.com",
        "127.0.0.1",
        "localhost",
        "::1"
      ],
      "prohibited": "No telemetry, analytics, update-check, or any other ad-hoc network sink. Repo content is sent only to the configured provider on LLM-backed tool calls."
    },
    "credentials": {
      "source": "environment variables / config (provider API keys, embedding key, optional serve token)",
      "use": "authenticate the configured provider request (and gate the local daemon) only",
      "confinement": "redacted from every output channel — tool results, errors, telemetry, logs — via secret-redaction (shared with sanitizeMcpError)."
    },
    "localDaemon": {
      "bind": "127.0.0.1 by default",
      "auth": "optional x-openlore-token compared in constant time; REQUIRED to bind any non-loopback host (refuses to start otherwise)",
      "defense": "DNS-rebinding guard (Host header must be a loopback name or the bound host) + cross-site Origin rejection, enforced before any dispatch"
    }
  },
  "acceptedRisks": [
    {
      "id": "read-source-invoke-git",
      "pattern": "fs.readFile(repo source) -> execFile(git, argv)",
      "why": "Reading the analyzed repository and invoking git over it is the tool's core purpose. git is spawned with an argv array (never a shell) and refs are validated, so no source or repo content can be reinterpreted as a command."
    },
    {
      "id": "read-config-call-llm",
      "pattern": "read config/env (baseUrl, apiKey) -> fetch(configured LLM/embedding endpoint)",
      "why": "LLM-backed tools must reach the operator's configured provider with the operator's own credentials. Egress is restricted to the configured endpoint (enumerated + asserted); no other destination receives repo-derived data."
    },
    {
      "id": "read-artifact-deserialize",
      "pattern": "fs.readFile(.openlore/*.json) -> JSON.parse",
      "why": "On-disk artifacts are treated as untrusted: reads are size-bounded (ARTIFACT_MAX_BYTES), the top-level shape is validated, and parsing fails closed to a 're-run analyze' result. A poisoned artifact cannot crash the server or be emitted as authoritative."
    },
    {
      "id": "windows-extension-launcher-shell",
      "pattern": "fixed constant argv -> spawn(..., { shell: true }) in src/pi/extension.ts",
      "why": "The VS Code extension launches the openlore CLI on Windows with shell:true and a FIXED argv ('openlore', ['serve', ...]) — no untrusted value is interpolated into the command. It is the editor-integration launcher, outside the analyzed MCP server surface (src/core, src/cli) that the no-shell gate scans."
    },
    {
      "id": "serve-output-redirect-fds",
      "pattern": "git stdout/stderr -> openSync temp file fds (was: /bin/sh -c redirect)",
      "why": "Inside the MCP server, libuv pipe() fails EBADF because FD 0/1 are the JSON-RPC sockets. git output is redirected to temp-file descriptors instead of pipes — without a shell — so the workaround introduces no command-string interpolation."
    }
  ]
}
