Skip to main content

Any break requires three things: knowing the layout, understanding the routine and help from outside or inside

Project description

Breslin

An authorised red-team CTF agent for Kubernetes. It runs opencode in headless mode inside a deliberately-constrained pod and attempts a catalog of read-only enumeration / credential-access / lateral-movement techniques against the cluster, validating that the platform's security controls (RBAC, NetworkPolicy, Pod Security Admission) actually hold.

The agent runs as a one-shot Kubernetes Job: it captures planted KARECTL{...} flags where controls are weak, and records BLOCKED where they hold. A blocked attempt is as valuable as a capture.

⚠️ This is a sanctioned security-validation tool. It is read-only by design (no delete / patch / create against live objects) and is scoped to a sandbox namespace via namespace-only RBAC. Only run it against clusters you own or are explicitly authorised to test.


Repository layout

.
├── Dockerfile              # container image (system tools + kubectl + opencode + uv venv)
├── pyproject.toml          # Python project (uv-managed); console script: redteam-agent
├── uv.lock
├── src/redteam_agent/      # the Python wrapper (was entrypoint.sh)
│   ├── cli.py              #   orchestration + opencode launch
│   ├── config.py           #   backend config + opencode config writer
│   └── preflight.py        #   identity canary + backend reachability
├── mission/                # agent instruction payload (baked into the image)
│   ├── MISSION.md          #   rules of engagement + objective
│   └── skills/             #   skill catalog the agent reads and executes
├── deploy/                 # Kubernetes manifests (Kustomize)
│   ├── base/               #   namespace, SA, RBAC, ConfigMap, ExternalSecret,
│   │                       #   PVC, Job, killswitch CronJob, CiliumNetworkPolicy
│   ├── overlays/           #   per-environment patches
│   │   ├── local/          #     local k3s (locally-built image)
│   │   └── dev/            #     dev cluster (registry image)
│   └── flags/              #   planted CTF flag Secrets (tier-1 / tier-2)
└── scripts/load-image.sh   # build + import image into k3s on a Multipass VM

This repo is a standalone application. It was previously a component in an ArgoCD app-of-apps monorepo; the ApplicationSet/GitOps wiring lived in the parent repo and has been removed. You deploy it directly with kustomize.


How the agent works

src/redteam_agent/cli.py is the container ENTRYPOINT. On each run it:

  1. Prints identity / RBAC context (kubectl auth can-i --list).
  2. Runs the identity canary — aborts (exit code 2 = INVALID environment) if the pod can create ClusterRoleBindings or read kube-system secrets. A CTF pod that powerful means the sandbox is misconfigured.
  3. Resolves the LLM backend from env vars and checks it is reachable (fails fast, exit 1, if not).
  4. Stages MISSION.md + the skill catalog into /workspace.
  5. Writes the opencode config for the chosen backend and launches opencode headless under a wall-clock timeout, teeing output to a per-run log.
  6. Guarantees a findings-*.md document exists in /workspace/output.

Supported backends (via AGENT_BACKEND): openai, azure-openai, anthropic, ollama. See Configuration.


Local development (Python + uv)

This project uses uv. Dependencies are pinned in uv.lock.

# Install deps into a local .venv (incl. dev tools)
uv sync --extra dev

# Lint
uv run ruff check src/

# Run the wrapper locally (outside Kubernetes).
# With no SA token mounted, kubectl calls fail gracefully and the run is
# recorded as a non-cluster smoke test. A reachable backend is still required.
export AGENT_BACKEND=openai
export OPENAI_API_KEY=sk-...
export AGENT_MAX_ITERATIONS=5
uv run redteam-agent

Running locally still shells out to opencode and kubectl. For a faithful end-to-end test, build the image and run it in the cluster (below).


Build the image

docker build -t redteam-agent:local-dev .

The build installs system recon tooling, kubectl, the opencode binary, and creates the uv-managed virtualenv at /opt/venv. The redteam-agent console script is the ENTRYPOINT.

Load into local k3s (Multipass VM)

./scripts/load-image.sh            # builds, saves, transfers, imports into k3s
# or pass a VM name: ./scripts/load-image.sh my-vm

Push to a registry (dev cluster)

docker tag redteam-agent:local-dev ghcr.io/<org>/redteam-agent:latest
docker push ghcr.io/<org>/redteam-agent:latest

Run it in the cluster

The agent runs as a Kubernetes Job, applied with Kustomize. Inspect the rendered manifests before applying:

kubectl kustomize deploy/overlays/local      # render local overlay
kubectl kustomize deploy/overlays/dev         # render dev overlay

1. Credentials

The Job reads LLM credentials from a Secret named openai-credentials (mounted via envFrom ... optional: true). Two ways to provide it:

  • External Secrets Operator (ESO)deploy/base/external-secret.yaml pulls the key from a ClusterSecretStore named secret-store. Use this if your cluster runs ESO.

  • Plain Secret — if you don't run ESO, create the Secret directly. The key name must match the backend (OPENAI_API_KEY for openai/azure-openai, ANTHROPIC_API_KEY for anthropic):

    kubectl create namespace redteam-sandbox
    kubectl -n redteam-sandbox create secret generic openai-credentials \
      --from-literal=OPENAI_API_KEY="sk-..."
    

    If your cluster has no ESO CRDs, remove external-secret.yaml from deploy/base/kustomization.yaml first (otherwise the apply fails on the unknown ExternalSecret kind).

2. Plant the CTF flags (optional)

kubectl apply -k deploy/flags

This creates the tier-1 (same-namespace) and tier-2 (cross-namespace) flag Secrets the agent hunts for.

3. Deploy and run

# Local k3s overlay (uses the locally-loaded image)
kubectl apply -k deploy/overlays/local

# Watch the Job
kubectl -n redteam-sandbox get jobs,pods -w

# Stream the agent's output
kubectl -n redteam-sandbox logs -f job/redteam-agent

Findings are written to the redteam-output PVC at /workspace/output. To pull them out:

POD=$(kubectl -n redteam-sandbox get pod -l app=redteam-agent -o name | head -1)
kubectl -n redteam-sandbox cp "${POD#pod/}:/workspace/output" ./output

4. Re-run

A Job is immutable. To run again, delete and re-apply:

kubectl -n redteam-sandbox delete job redteam-agent
kubectl apply -k deploy/overlays/local

A killswitch CronJob (deploy/base/cronjob.yaml) hard-deletes stale redteam=true jobs/pods hourly as a safety net.

Clean up

kubectl delete -k deploy/overlays/local
kubectl delete -k deploy/flags

Configuration reference

Set via the agent-config ConfigMap (deploy/base/configmap.yaml) or per-environment overlay patches.

Environment Variable Default Description
AGENT_BACKEND openai LLM backend: openai / azure-openai / anthropic / ollama
AGENT_MAX_ITERATIONS 50 Advisory iteration cap (informs the timeout)
AGENT_TIMEOUT_SECONDS max_iterations × 60 Wall-clock timeout for the opencode run
OPENAI_API_KEY OpenAI key (from the openai-credentials Secret)
OPENAI_MODEL gpt-4o-mini OpenAI model name
OPENAI_API_BASE https://api.openai.com/v1 OpenAI-compatible base URL
OLLAMA_HOST http://localhost:11434 Ollama server URL
OLLAMA_MODEL qwen2.5-coder:7b Ollama model name
ANTHROPIC_API_KEY Anthropic key (anthropic backend)
ANTHROPIC_MODEL claude-sonnet-4-20250514 Anthropic model name
AZURE_OPENAI_ENDPOINT https://<resource>.openai.azure.com
AZURE_OPENAI_DEPLOYMENT gpt-4.1 Azure deployment name (used as the model)
AZURE_OPENAI_API_VERSION 2024-02-01 Azure API version
AZURE_OPENAI_API_KEY Azure OpenAI key

Cost control (hosted backends)

  • Use a dedicated project/key with a spend cap.
  • Use a cheap model (gpt-4o-mini) for iteration; switch to a larger model only for documented "real" runs.
  • Keep AGENT_MAX_ITERATIONS conservative (5–25 for dev).
  • Review token usage in the run log after each run.

Security model

  • Namespace-only RBAC. researcher-sa gets a Role (not ClusterRole) scoped to redteam-sandbox: read pods/services/configmaps/PVCs and get/list secrets. No cluster-scoped grants, ever.
  • Hardened pod. Non-root (UID 1000), read-only root filesystem, all capabilities dropped, seccompProfile: RuntimeDefault, namespace enforces PSA restricted.
  • Default-deny egress. A CiliumNetworkPolicy allows only DNS, the in-cluster API server, and the configured LLM API FQDNs; cloud IMDS (169.254.169.254) is explicitly denied.
  • Identity canary. The wrapper aborts before doing anything if its identity is unexpectedly powerful.

Project details


Download files

Download the file for your platform. If you're not sure which to choose, learn more about installing packages.

Source Distribution

breslin-0.1.0.tar.gz (13.5 kB view details)

Uploaded Source

Built Distribution

If you're not sure about the file name format, learn more about wheel file names.

breslin-0.1.0-py3-none-any.whl (17.1 kB view details)

Uploaded Python 3

File details

Details for the file breslin-0.1.0.tar.gz.

File metadata

  • Download URL: breslin-0.1.0.tar.gz
  • Upload date:
  • Size: 13.5 kB
  • Tags: Source
  • Uploaded using Trusted Publishing? Yes
  • Uploaded via: uv/0.11.19 {"installer":{"name":"uv","version":"0.11.19","subcommand":["publish"]},"python":null,"implementation":{"name":null,"version":null},"distro":{"name":"Ubuntu","version":"24.04","id":"noble","libc":null},"system":{"name":null,"release":null},"cpu":null,"openssl_version":null,"setuptools_version":null,"rustc_version":null,"ci":true}

File hashes

Hashes for breslin-0.1.0.tar.gz
Algorithm Hash digest
SHA256 13981f6bf4708a3f90e58b04fc380b09a401bf3dc23d31c32f5928db14a236c4
MD5 7c6ad63dc8cc9334b26f1f5f5a42f180
BLAKE2b-256 2cf547f74b385636876066d9d8828f3e78c5df308e6830c5447fa75aced524ae

See more details on using hashes here.

File details

Details for the file breslin-0.1.0-py3-none-any.whl.

File metadata

  • Download URL: breslin-0.1.0-py3-none-any.whl
  • Upload date:
  • Size: 17.1 kB
  • Tags: Python 3
  • Uploaded using Trusted Publishing? Yes
  • Uploaded via: uv/0.11.19 {"installer":{"name":"uv","version":"0.11.19","subcommand":["publish"]},"python":null,"implementation":{"name":null,"version":null},"distro":{"name":"Ubuntu","version":"24.04","id":"noble","libc":null},"system":{"name":null,"release":null},"cpu":null,"openssl_version":null,"setuptools_version":null,"rustc_version":null,"ci":true}

File hashes

Hashes for breslin-0.1.0-py3-none-any.whl
Algorithm Hash digest
SHA256 890cd45d21d284f858fce1922424ed57cd8c5b468ed3ab1dd8b20c081277f91f
MD5 96d645c40da805a646f0e23a564ac0f2
BLAKE2b-256 6dde66eecf74dd431fda678afd4999d18c91fe927c42d007a3c86195c11aa831

See more details on using hashes here.

Supported by

AWS Cloud computing and Security Sponsor Datadog Monitoring Depot Continuous Integration Fastly CDN Google Download Analytics Pingdom Monitoring Sentry Error logging StatusPage Status page