Skip to content
IndexyzPublic

About

No description, website, or topics provided.

Resources

Stars

0 stars

Watchers

0 watching

Forks

Repository files navigation

Cairn Banner

Cairn

More Than Just AI Penetration Testing — Towards General State-Space Search

Tencent TCH ChainReactors

Cairn is a general-purpose problem-solving engine.
It defines no roles, no workflows. Given an origin and a goal, it searches for a path through an unknown state space.
AI Penetration Testing is one such problem — and a proven one.

Discord X

Cairn runtime screenshot

What is Cairn?

Penetration testing is fundamentally a directed search through a near-infinite state space:

  • Origin: known (target IP, target system)
  • Goal: defined (get a shell, capture the flag)
  • Path: unknown

This structure is not unique to penetration testing. Vulnerability research, mathematical proof, CTF challenges — any problem with a clear starting point, a clear success condition, and an unknown path in between shares the same shape.

Cairn is built for this class of problems. Penetration testing is the first domain it has been validated on.

The engine is built on a Blackboard Architecture with an explicit fact-intent graph. Three primitives are all it needs:

Concept Meaning
Fact A confirmed, objective finding written to the board
Intent A declared direction of exploration, not yet executed
Hint Human judgment injected at any time; absorbed by agents on the next read

The graph grows from origin toward goal. Every new Fact is a stepping stone; every Intent is a step into the unknown.

Agent Workers run an OODA loop — Observe the full graph, Orient to the current state, Decide on next intents, Act to explore — and write their findings back as new Facts. Workers have no fixed roles. Tasks are generated at runtime from the graph's current state, not from predefined job descriptions.

Agents coordinate exclusively through the shared board (Stigmergy). No direct communication. No information silos.

Cairn in Action

cairn.mp4

How It Works

Three task types, all executed by the same Worker:

Task What it does Output
Bootstrap At project start, attempts to solve the problem directly Fact + possible Complete
Reason Reads the full graph: is the goal met? What should be explored next? Complete / new Intents / no-op
Explore Claims one Intent, executes the exploration, reports findings One Fact

System architecture:

          ┌──────────────────────────────────┐
          │           Cairn Server           │
          │    Facts + Intents + Hints       │
          └─────────────────┬────────────────┘
                            │
                     Read / Write API
                            │
          ┌─────────────────┴────────────────┐
          │             Dispatcher           │
          │   Schedules tasks, manages       │
          │   containers, writes protocol    │
          └──────────┬───────────────┬───────┘
                     │               │
     ┌───────────────┴──┐     ┌──────┴──────────────┐
     │  Worker Container│     │  Worker Container   │
     │   (Project A)    │     │   (Project B)       │
     │  ┌────┐  ┌────┐  │     │  ┌────┐  ┌────┐     │
     │  │ W. │  │ W. │  │     │  │ W. │  │ W. │     │
     │  └────┘  └────┘  │     │  └────┘  └────┘     │
     └──────────────────┘     └─────────────────────┘

Cairn Server maintains graph consistency only.

Cairn Dispatcher reads the graph, schedules tasks, spins up and tears down worker containers, and is the sole writer to the protocol. Each project gets its own Worker Container; multiple Agent Workers run concurrently inside it. Agent Workers only receive a prompt and return structured output.

Workers can also run directly on the dispatcher host instead of in per-project containers — local mode, no Docker required. See Local mode below.

Supported worker backends: Claude Code, Codex, and Pi.

Results

Tencent Cloud Hackathon · AI Penetration Testing Challenge · 2nd Edition

610 teams · 1,345 participants · top universities and security firms across China

Metric Value
Problems solved 54 / 54 — only team to AK
Final ranking 3rd

The system had never been tested before the competition. The full pipeline came online for the first time at 4 AM on race day. No training, no tuning, no domain-specific tooling. Zero MCP tools, zero RAG, zero predefined agent roles.

Further Reading

Getting Started

Prerequisites

  • macOS or Linux
  • Python ≥ 3.12
  • Node.js ≥ 20 (builds the web UI)
  • Docker (container execution only — not needed for local mode)

Pull required images

Both setup methods require the worker container image:

docker pull ghcr.io/oritera/cairn-worker-container:latest

The image is multi-arch (linux/amd64, linux/arm64) and Docker picks the host architecture automatically. On an arm64 host that cannot emulate amd64 — or when pinning either side — set container.platform in dispatch.yaml.

The app image (API server plus the built Web UI, which also serves the dispatcher) is published the same way as ghcr.io/<fork owner>/cairn:latest, also multi-arch. docker compose up --build builds it locally instead of pulling it.

Create your local dispatcher configuration and fill in your LLM endpoints and API keys:

cp dispatch.example.yaml dispatch.yaml

Docker Compose (recommended)

Pull the base image used to build Cairn:

docker pull ghcr.io/astral-sh/uv:python3.13-trixie
docker compose up --build

This starts cairn-server on port 8000 and cairn-dispatcher once the server passes its health check. The dispatcher mounts dispatch.yaml from the project root and connects to Docker via the host socket. Data is persisted to ./datas/cairn/.

Manual

# Build the web UI (served by the server from cairn/src/cairn/server/static)
npm --prefix web install
npm --prefix web run build

# Start the server
uv run --project cairn cairn serve

# Run the dispatcher
uv run --project cairn cairn dispatch --config dispatch.yaml

# Run startup health checks only
uv run --project cairn cairn dispatch --config dispatch.yaml --startup-healthcheck-only

For UI work, run npm --prefix web run dev instead of building; Vite proxies the API to 127.0.0.1:8000.

Model list

The worker/model list lives in the server database and is edited from the Models page in the UI. The dispatcher hot-reloads it before every scheduling iteration, so saving takes effect on the next tick without a restart.

workers in dispatch.yaml is a seed only: it is written into an empty database once, on dispatcher startup. After that the database wins, and entries added or removed in the UI are what the dispatcher uses. common_env in dispatch.yaml is still merged into every model's env. Concurrency caps (runtime.max_workers, max_running_projects, max_project_workers) and runtime.interval still come from the config file and need a restart.

An empty model list is a normal state: the dispatcher logs a warning and idles without dispatching, then resumes as soon as a model is added. You can therefore start the dispatcher first and configure models from the Models page.

Each row on the Models page has a test button that runs the dispatcher's own worker healthcheck against the saved model and reports status and latency. The probe is sent from the server host, so it does not prove anything about the dispatcher host or a local-mode CLI.

The UI ships English, Simplified Chinese and Japanese. Switch language in the sidebar footer or on the Settings page; the choice and the date formats follow it and are stored per browser.

The concurrency column shows slots in use against the cap, derived from the live leases; it turns amber when a model is saturated.

API probe keys are stored as plain text in the database.

Several dispatchers

More than one dispatcher can share one server. Each instance gets an identity — CAIRN_INSTANCE_ID, or a random suffix when unset — and writes leases as model@instance, so two instances never treat the same model as the same owner: the loser of a claim gets 409 and retries on a later tick.

  • concurrency caps are enforced against the global lease count (GET /load), not per process
  • worker containers are named cairn-dispatch-<instance>-<project>, so instances sharing a Docker host do not reuse or stop each other's containers
  • in local mode the workspace directory stays per project (<workspace_root>/<project_id>) and is shared between instances

Set a stable CAIRN_INSTANCE_ID per host so a restarted dispatcher keeps its identity. The NixOS module defaults it to the hostname.

Local mode (no Docker)

Instead of one container per project, workers can run directly on the dispatcher host, reusing the machine's already-configured claude / codex / pi CLIs — no Docker, and no API keys in the config.

cp dispatch.local.example.yaml dispatch.yaml

# Start the server
uv run --project cairn cairn serve

# Run the dispatcher on the same host, where the CLIs are installed and logged in
uv run --project cairn cairn dispatch --config dispatch.yaml

Local mode is selected by runtime.execution: local (see dispatch.local.example.yaml). On startup the dispatcher checks each configured worker CLI is installed and runnable, and reminds you they must already be logged in. Each project gets an isolated working directory under local.workspace_root (default: the dispatcher's current directory). Run the dispatcher directly on the host — not inside Docker — since the agents run with your user's permissions and no sandbox.

NixOS

The flake exports packages.<system>.cairn, overlays.default and nixosModules.default. The module runs the server and, optionally, the dispatcher as hardened systemd services with a dedicated cairn user and /var/lib/cairn state directory.

{
  inputs.cairn.url = "github:oritera/Cairn";

  outputs = { nixpkgs, cairn, ... }: {
    nixosConfigurations.host = nixpkgs.lib.nixosSystem {
      system = "x86_64-linux";
      modules = [
        cairn.nixosModules.default
        {
          virtualisation.docker.enable = true;

          services.cairn = {
            enable = true;
            openFirewall = true;
            environmentFile = "/var/lib/cairn/secrets.env";

            dispatcher = {
              enable = true;
              workers = [
                {
                  name = "claudecode_deepseek";
                  type = "claudecode";
                  taskTypes = [ "bootstrap" "reason" "explore" ];
                  env.ANTHROPIC_BASE_URL = "https://api.deepseek.com/anthropic";
                  env.ANTHROPIC_MODEL = "deepseek-v4-pro";
                }
              ];
            };
          };
        }
      ];
    };
  };
}

The environmentFile (a KEY=value file, e.g. managed by sops-nix or agenix) is loaded into both units, so keys stay out of the Nix store. dispatcher.workers is only the seed list — after first start the model list lives in the database and is edited on the Models page.

Key options:

Option Default Meaning
services.cairn.server.host 127.0.0.1 API bind address; use 0.0.0.0 for remote access
services.cairn.server.port 8000 API port
services.cairn.stateDir /var/lib/cairn Database and workspace directory
services.cairn.dispatcher.execution container container uses the Docker socket, local uses host CLIs
services.cairn.dispatcher.serverUrl local server Point at a remote server instead
services.cairn.dispatcher.extraPackages [] CLIs to put on the dispatcher PATH (local mode)
services.cairn.dispatcher.container.cap_add [] Capabilities for worker containers

In local mode add the agent CLIs to extraPackages and point dispatcher.local.workspaceRoot at a writable directory; the dispatcher then runs with PrivateDevices off and no Docker access. Operators are expected to add port forwarding or a reverse proxy themselves when exposing the API.

Tests

Run the fast regression suite without Docker or live model endpoints:

uv run --project cairn --group dev pytest

Disclaimer

Cairn is a general-purpose problem-solving engine. Although it supports penetration testing, CTF solving, security assessment, and vulnerability research workflows, it is intended to be used only in environments where you have explicit authorization to operate.

You are solely responsible for how you use this project. Do not use Cairn against systems, networks, applications, or data without clear prior permission from the owner or operator. Unauthorized security testing, exploitation, or data access may be illegal and may cause harm.

The developers and contributors of this project do not endorse or accept responsibility for any misuse, abuse, damage, loss, or legal consequences arising from its use. By using this project, you agree to ensure that your activities comply with all applicable laws, regulations, contractual obligations, and professional or organizational policies in your jurisdiction.

Star History

Star History Chart

⚖️ License

This project is licensed under GNU AGPLv3 for personal and educational use.

Commercial Use: If you wish to use this project in a commercial or proprietary environment without the AGPL-3.0 open-source obligations, please contact me to obtain a commercial license.

Contributions: By submitting a Pull Request, you agree that your contributions may be used under both the AGPL-3.0 and the project's commercial license.

About

No description, website, or topics provided.

Resources

Stars

0 stars

Watchers

0 watching

Forks

Releases

Packages

Contributors

Languages