Quickstart#
A full docker-compose setup is included that starts Neo4j, PostgreSQL, Telegraf, Seizu and its workers, and can run Cartography to load the graph database.
First clone the seizu repo:
git clone https://github.com/mappedsky/seizu
cd seizu
Start the stack:
make up
make up streams logs to your terminal. Once fully started, the UI will be accessible at: http://localhost:3000
The backend API (and MCP server) is accessible at: http://localhost:8080
For a real deployment rather than an evaluation, see Backend Installation & Configuration or, on Kubernetes, the Helm charts at Kubernetes (Helm).
Running on a VM or remote host#
If the docker-compose stack is running on a virtual machine or remote host rather than your local machine, you must forward the relevant ports over SSH before the UI and MCP clients can reach the stack. Only ports 3000 and 8080 are exposed to the host by the default compose configuration:
Port |
Service |
|---|---|
3000 |
Frontend dev server (UI) |
8080 |
Backend API and MCP server |
9000 |
Authentik OIDC provider (only when the |
8888 |
Claude MCP OAuth callback (only when using Claude with auth enabled) |
Forward ports with SSH local port forwarding:
# Basic stack (no auth)
ssh -L 3000:localhost:3000 -L 8080:localhost:8080 user@vm-host
# With Authentik auth enabled
ssh -L 3000:localhost:3000 -L 8080:localhost:8080 -L 9000:localhost:9000 user@vm-host
# With Authentik auth enabled and Claude running on the VM
ssh -L 3000:localhost:3000 -L 8080:localhost:8080 -L 9000:localhost:9000 -L 8888:localhost:8888 user@vm-host
Add -N to open the tunnels without starting a shell, or -f -N to background them. Once the tunnels are up, http://localhost:3000 and http://localhost:8080 resolve to the remote stack as if it were running locally.
Seeding reports#
After starting the stack for the first time, seed the example reports into PostgreSQL from the YAML config:
make seed_dashboard
This reads .config/dev/seizu/reporting-dashboard.yaml, creates each report in
the application database, and sets the dashboard pointer. After resetting the
PostgreSQL volume, re-run make seed_dashboard to repopulate it.
To reset the database and reseed:
make drop_db
make up
make seed_dashboard
Loading CVE data#
The quickstart configuration is based around the NIST CVE data. Load the full CVE database:
make sync_cve
Loading GitHub data#
To sync GitHub organization and repository data into the graph, create a GitHub Personal Access Token. Use a classic PAT so that all data is syncable — fine-grained tokens cannot read GitHub Packages. For a complete sync, grant these scopes:
repo(orpublic_repofor public repositories only) — repository files, commit history, dependency manifests, collaborators, and branch protection rulesread:org— organization membership and team dataread:user— user profile informationuser:email— user email addressessecurity_events(optional) — Dependabot alerts for private repositoriesread:packages(optional) — GitHub Container Registry packages, image manifests, layers, tags, and SLSA attestations
For the full set of supported permissions — including fine-grained token and GitHub App alternatives — see Cartography’s GitHub module configuration docs.
Cartography reads its GitHub configuration as a base64-encoded JSON object, not a bare token. The bare GitHub token belongs in that JSON object’s token field; the resulting encoded object goes in CARTOGRAPHY_GITHUB_CONFIG. Build it from your token and organization name (replace both placeholders):
printf '%s' '{"organization":[{"token":"<your_github_pat>","url":"https://api.github.com/graphql","name":"<your_org_name>"}]}' | base64 | tr -d '\n'
Put the resulting string in .env:
CARTOGRAPHY_GITHUB_CONFIG=<base64_value_from_above>
CARTOGRAPHY_GITHUB_TOKEN remains a deprecated compatibility alias for the
old Seizu setting. Despite that historical name, its value must also be the
complete base64-encoded configuration, not a bare PAT.
Then run:
make sync_github
Enriching CVE metadata#
Other modules — GitHub, for example — create references to CVEs in the graph without the full CVE details. sync_cve_metadata enriches those referenced CVEs with data from the NIST NVD database, so run it after the module that introduced the references:
make sync_github # creates CVE references
make sync_cve_metadata # enriches them
Setting a free NVD API key in .env is optional but strongly recommended — it makes the sync considerably faster. With a key, the module fetches the individual referenced CVEs; without one, it falls back to pulling an entire year of CVE data at a time.
CARTOGRAPHY_NIST_NVD_TOKEN=<your_nvd_api_key>
Enabling the chat assistant#
Chat is off by default. It needs a model and its API key; everything else has a working default.
CHAT_ENABLED=true
CHAT_LLM_MODEL=anthropic/claude-sonnet-4-6
ANTHROPIC_API_KEY=sk-ant-...
make down && make up
CHAT_LLM_MODEL is any model id LiteLLM understands, so an OpenAI, Gemini or
DeepSeek model works the same way with its own key. CHAT_LLM_PROVIDER=mock is
the default and only echoes input — it cannot call tools, so a real provider is
required for anything useful.
Open http://localhost:3000/app/chat and ask something about your graph, such as
“Which repositories have the most critical CVEs?”. In the default
unauthenticated dev mode every request gets all permissions, so there is nothing
to grant; with auth enabled, chat:use gates the UI and chat:tools:call lets
the agent run tools.
Chat history needs the PostgreSQL checkpoint database, which the compose stack provisions for you. For per-user model choice, an economy fallback or spend caps, create a profile under Model Profiles in the sidebar. See Chat Assistant.
Adding the sandbox#
The sandbox lets the assistant run code, and is what makes skills that ship scripts runnable. It is off by default and needs a sandbox provider — E2B’s free tier is the quickest way to try it:
SANDBOX_ENABLED=true
SANDBOX_API_KEY=e2b_...
make down && make up
Ask the assistant “Can you run a Python script that prints the first 10
Fibonacci numbers?” to confirm it works. Sandboxes have no outbound internet
unless you set SANDBOX_ALLOW_INTERNET=true. See Sandbox,
including the self-hosted option if you would rather not use a hosted provider.
Running workflows#
Workflows need no enabling: make up already starts Temporal and the
seizu-temporal-worker that executes them, and make seed_dashboard installs
several examples. Open Workflows in the sidebar, pick one, and use Run
now; the run and each of its stages appear in the workflow’s history, and in
the Temporal UI at http://localhost:8233.
A workflow runs ordered stages, with the activities inside one stage running in parallel. Each activity publishes a named output that later stages can consume, so a query stage can feed a notification stage.
To restrict which code-defined workflows are available, set
TEMPORAL_ENABLED_WORKFLOWS to a comma-separated list on both the web
service and the worker; unset means all of them. Note that the agent_chat
workflow runs an AI session, so it needs chat configured as above. See
Workflows and Built-in workflows.
Enabling external MCP tools#
Seizu’s chat assistant can use tools from other MCP servers, reached through an identity-aware proxy. The compose stack bundles two upstream servers to try this against: the GitHub MCP server and a deps.dev package-metadata server.
Add a fine-grained GitHub PAT
and the proxy configuration to .env:
DEV_GITHUB_MCP_TOKEN=<fine-grained-development-pat>
DEV_GITHUB_MCP_READ_ONLY=1
MCP_EXTERNAL_ENABLED=true
MCP_EXTERNAL_PROXIES=[{"name":"github","url":"http://external-mcp-github-auth:8080/mcp","upstream_urls":["https://mcp.github.test/mcp"],"transport":"streamable_http","auth_mode":"header_delegation"},{"name":"deps","url":"http://external-mcp-deps:8080/mcp","upstream_urls":["https://mcp.deps.test/mcp"],"transport":"streamable_http","auth_mode":"header_delegation","require_confirmation":false}]
make external_mcp_enable
make down && make up
To disable the agent capability and its local services, run
make external_mcp_disable, then make down && make up. Proxy URLs and
credentials are retained in .env. Authentication is toggled independently.
Discovered tools are namespaced ext__<proxy>__<tool> and appear on the
MCP Toolsets page as read-only entries, so you can see what each proxy
offers. They are available to the chat assistant; Seizu does not re-export them
from its own MCP endpoint.
In this local setup every GitHub call uses the one development PAT, so treat it as a shared service account and scope it to what you need for testing. See External MCP for per-user identity and the OAuth path.
Local OpenTelemetry collector#
Enable the local trace collector with:
make otel_enable
make down && make up
This sets OTEL_COLLECTOR_ENABLED=true, enables telemetry, and selects
http://otel-collector:4318/v1/traces for the web service and workers.
make up includes the tracing Compose profile while the toggle is enabled.
Trace batches are written to .compose/otel/spans.jsonl.
To disable the collector and local trace export:
make otel_disable
make down && make up
Disabling clears the local endpoint; a configured remote endpoint is preserved.
make down removes the collector even after the toggle is disabled. Existing
trace files are retained.
Testing authentication#
The stack includes an embedded Authentik OIDC provider. To enable it, use auth_enable_bootstrap, which generates SESSION_TOKEN_ENCRYPTION_KEY and REPORT_QUERY_SIGNING_SECRET into your .env file (skipping either if already set) and then enables auth:
make auth_enable_bootstrap && make down && make up
If you have already run auth_enable_bootstrap before and just want to re-enable auth without regenerating secrets:
make auth_enable && make down && make up
On first run, Authentik takes about two minutes to initialize. Once ready, visit http://localhost:3000 and log in with one of the seeded Seizu users:
Admin:
seizu-admin/seizuEditor:
seizu-editor/seizuViewer:
seizu-viewer/seizu
Everything reaches Authentik as localhost:9000 — the browser and any MCP or
CLI client directly, and the backend through scripts/dev_oidc_loopback.py,
which the seizu container starts alongside gunicorn and which forwards its
own loopback :9000 on to authentik-server:9000. That is deliberate: Authentik
derives the iss claim from the request’s Host header, so a backend reaching
it under a second hostname would mint a different issuer than your MCP client
gets, and the same login would become two Seizu users with separate reports,
chat threads and confirmations. See
the authentication decisions.
To disable auth and return to the default unauthenticated mode:
make auth_disable && make down && make up