CP2A2.2B: L1 Red Queen Worker Foundation - separate minimal worker runtime (non-root, read-only rootfs, cap-drop ALL, no-new-privileges, internal-net, credential-zero, inert idle, NO autonomy/heartbeat/scheduler)
This commit is contained in:
parent
7b58d27fa0
commit
7a4220d86d
11 changed files with 824 additions and 0 deletions
46
red-queen-architecture/control-plane/cp2a2_2b/Dockerfile
Normal file
46
red-queen-architecture/control-plane/cp2a2_2b/Dockerfile
Normal file
|
|
@ -0,0 +1,46 @@
|
|||
# CP2A2.2B — L1 Red Queen Worker (L1 Worker Foundation)
|
||||
# STRICTLY NON-AUTONOMOUS / NON-PRODUCTIVE. NO AUTONOMY. NO HEARTBEAT. NO SCHEDULER.
|
||||
#
|
||||
# Separate minimale L1 Worker Runtime fuer spaetere Read-only-Autonomie.
|
||||
# Basis: nousresearch/hermes-agent:latest (Hermes oneshot `hermes -z`).
|
||||
#
|
||||
# HAERTUNG:
|
||||
# - non-root hermes-User (UID 10010 / GID 10000, im Basis-Image definiert)
|
||||
# - read-only rootfs (docker-compose: read_only: true)
|
||||
# - cap_drop ALL, no-new-privileges (docker-compose)
|
||||
# - kein Docker-Socket, keine Host-Root-Mounts, keine Host-Namespaces
|
||||
# - kein s6-overlay / kein Gateway / kein Telegram / kein API-Server
|
||||
# - eigener minimaler Entrypoint (inert idle, KEIN autonomer Loop)
|
||||
#
|
||||
# CREDENTIAL ZERO POLICY: Dieses Image enthaelt KEINE Credentials.
|
||||
# Kein FORGEJO_WRITE_TOKEN, NOTION_API_KEY, TELEGRAM_BOT_TOKEN,
|
||||
# SAVE_TOKEN, DELETE_TOKEN, SSH-Keys, Git-Write-Credentials, .netrc,
|
||||
# .git-credentials, Cloud/DB/Service-Account/Webhook/Bearer/Docker/
|
||||
# Broker/Trading-Credentials. ENV wird im Compose NICHT gesetzt.
|
||||
#
|
||||
# RED_QUEEN_TRADING_AUTHORITY=NEVER (permanente Invariante, siehe CP1.0S).
|
||||
|
||||
FROM nousresearch/hermes-agent:latest
|
||||
|
||||
# Kein s6-overlay, kein Gateway. Eigener minimaler Entrypoint.
|
||||
# Der Worker startet inert (sleep infinity) und fuehrt KEINEN autonomen Tick aus.
|
||||
# Ein Tick wird ausschliesslich extern getriggert (spaeterer externer Scheduler
|
||||
# -> Gate-Evaluator /check -> ALLOW -> `hermes -z` oneshot). In CP2A2.2B existiert
|
||||
# der Tick-Wrapper als Datei, wird aber NICHT automatisch ausgefuehrt.
|
||||
|
||||
# Worker-eigene Skripte (read-only im Image, keine Secrets)
|
||||
COPY worker-entrypoint.sh /opt/l1-worker/worker-entrypoint.sh
|
||||
COPY worker-tick.sh /opt/l1-worker/worker-tick.sh
|
||||
COPY worker-config.yaml /opt/l1-worker/worker-config.yaml
|
||||
|
||||
# State-Verzeichnis (worker-owned, wird als Volume gemountet)
|
||||
# Der hermes-User (UID 10010) ist der Worker. Das State-Volume wird im
|
||||
# Compose als bind-mount von /opt/l1-worker-state gemountet (Host-root-angelegt,
|
||||
# auf 10010:10000 gechownt) — nur der Worker kann schreiben.
|
||||
RUN mkdir -p /opt/l1-worker/state && chown -R hermes:hermes /opt/l1-worker
|
||||
|
||||
# Non-root: der Worker laeuft als hermes-User (UID 10010 / GID 10000).
|
||||
USER hermes:hermes
|
||||
|
||||
# Inert idle Entrypoint: KEIN autonomer Loop, KEIN Tick-Start.
|
||||
ENTRYPOINT ["/bin/sh", "/opt/l1-worker/worker-entrypoint.sh"]
|
||||
94
red-queen-architecture/control-plane/cp2a2_2b/ROLLBACK.md
Normal file
94
red-queen-architecture/control-plane/cp2a2_2b/ROLLBACK.md
Normal file
|
|
@ -0,0 +1,94 @@
|
|||
# CP2A2.2B — L1 Red Queen Worker: Rollback-Dokumentation
|
||||
# STRICTLY NON-AUTONOMOUS / NON-PRODUCTIVE.
|
||||
|
||||
## Zweck
|
||||
|
||||
Rollback muss den L1 Worker vollstaendig entfernen/deaktivieren koennen, OHNE:
|
||||
- CP1 State zu loeschen
|
||||
- CP1 Audit zu loeschen
|
||||
- Gate Evaluator zu veraendern
|
||||
- interactive RQ zu veraendern
|
||||
- AUTH4C/4D zu veraendern
|
||||
- Trading zu veraendern
|
||||
|
||||
Worker-internal State bei Rollback zunaechst PRESERVE, nicht blind loeschen.
|
||||
|
||||
## Rollback-Schritte
|
||||
|
||||
### 1. Worker-Container stoppen und entfernen
|
||||
|
||||
```bash
|
||||
cd /opt/cp2a2_2b
|
||||
docker compose -f docker-compose.yml down
|
||||
```
|
||||
|
||||
Dies stoppt und entfernt den `l1-red-queen-worker` Container und das
|
||||
`l1-internal` Netzwerk. Das Worker-State-Volume `l1-worker-state` bleibt
|
||||
erhalten (PRESERVE).
|
||||
|
||||
### 2. Netzwerk-Attachments entfernen (falls angehaengt)
|
||||
|
||||
Falls Gate-Evaluator, Ollama oder Tolaria an das `l1-internal` Netz angehaengt
|
||||
wurden, werden diese Attachments entfernt:
|
||||
|
||||
```bash
|
||||
docker network disconnect l1-internal gate-evaluator 2>/dev/null || true
|
||||
docker network disconnect l1-internal ollama-nb6d-ollama-1 2>/dev/null || true
|
||||
docker network disconnect l1-internal tolaria 2>/dev/null || true
|
||||
```
|
||||
|
||||
Hinweis: `docker compose down` entfernt das `l1-internal` Netzwerk automatisch,
|
||||
wodurch alle Attachments ebenfalls entfernt werden.
|
||||
|
||||
### 3. Worker-State-Volume (PRESERVE)
|
||||
|
||||
Das Worker-State-Volume `l1-worker-state` wird beim Rollback NICHT geloescht.
|
||||
Es bleibt fuer spaetere Re-Initialisierung erhalten.
|
||||
|
||||
```bash
|
||||
# Volume-Liste pruefen (nicht loeschen)
|
||||
docker volume ls | grep l1-worker-state
|
||||
```
|
||||
|
||||
### 4. Image entfernen (optional)
|
||||
|
||||
```bash
|
||||
docker rmi l1-red-queen-worker:cp2a2_2b
|
||||
```
|
||||
|
||||
### 5. SoT-Ruecknahme (optional)
|
||||
|
||||
Falls der SoT-Commit zurueckgenommen werden soll:
|
||||
|
||||
```bash
|
||||
cd /opt/cp11_checker
|
||||
git revert <commit-sha>
|
||||
git push origin main
|
||||
```
|
||||
|
||||
Hinweis: Der SoT-Commit ist historisch. Ein Revert erstellt einen neuen Commit,
|
||||
der die Aenderungen zuruecknimmt. Der urspruengliche Commit bleibt im Log.
|
||||
|
||||
## Was beim Rollback NICHT veraendert wird
|
||||
|
||||
- CP1 State (`/opt/control-plane/state/`) — unveraendert
|
||||
- CP1 Audit (`/opt/control-plane/audit/`) — unveraendert
|
||||
- Gate Evaluator (`gate-evaluator` Container) — unveraendert
|
||||
- Interactive RQ (`hermes-red-queen` Container) — unveraendert
|
||||
- AUTH4C/4D — unveraendert
|
||||
- Trading-System — unveraendert
|
||||
|
||||
## Verifikation nach Rollback
|
||||
|
||||
```bash
|
||||
# Worker-Container entfernt
|
||||
docker ps -a | grep l1-red-queen-worker || echo "Worker entfernt"
|
||||
|
||||
# Interactive RQ unveraendert
|
||||
docker inspect hermes-red-queen --format '{{.State.Status}} {{.State.StartedAt}}'
|
||||
|
||||
# Gate Evaluator unveraendert
|
||||
docker ps | grep gate-evaluator
|
||||
|
||||
# CP1 State unveraendert
|
||||
ls -la /opt/control-plane/state/
|
||||
145
red-queen-architecture/control-plane/cp2a2_2b/SECURITY.md
Normal file
145
red-queen-architecture/control-plane/cp2a2_2b/SECURITY.md
Normal file
|
|
@ -0,0 +1,145 @@
|
|||
# CP2A2.2B — L1 Red Queen Worker: Security-Dokumentation
|
||||
# STRICTLY NON-AUTONOMOUS / NON-PRODUCTIVE. NO AUTONOMY. NO HEARTBEAT. NO SCHEDULER.
|
||||
|
||||
## 1. Zweck
|
||||
|
||||
Separate minimale L1 Worker Runtime (`l1-red-queen-worker`) als technische
|
||||
Sandbox/Foundation fuer spaetere Read-only-Autonomie. In CP2A2.2B:
|
||||
- KEINE Autonomie
|
||||
- KEINE Mission Execution
|
||||
- KEIN Heartbeat
|
||||
- KEIN Scheduler
|
||||
- KEIN autonomer Tick
|
||||
|
||||
Der bestehende interaktive Container `hermes-red-queen` bleibt UNVERAENDERT.
|
||||
|
||||
## 2. Security-Invarianten (permanent)
|
||||
|
||||
RED_QUEEN_TRADING_AUTHORITY=NEVER
|
||||
|
||||
Der L1 Worker darf niemals erhalten:
|
||||
- Broker Credentials
|
||||
- Broker APIs
|
||||
- Trading Tokens
|
||||
- Trading Execution Authority
|
||||
- Trading Kill-Switch Authority
|
||||
|
||||
Trading ist eine separate Security Domain.
|
||||
|
||||
Zusaetzlich:
|
||||
- MISSION != AUTHORIZATION
|
||||
- COMMAND != AUTHORIZATION
|
||||
- RQ_DECISION != AUTHORIZATION
|
||||
- GATE_CHECK != CAPABILITY_GRANT
|
||||
|
||||
## 3. Credential Zero Policy
|
||||
|
||||
Im L1 Worker sind ABSENT:
|
||||
- FORGEJO_WRITE_TOKEN, NOTION_API_KEY, TELEGRAM_BOT_TOKEN
|
||||
- SAVE_TOKEN, DELETE_TOKEN
|
||||
- SSH private keys, Git write credentials, .netrc, .git-credentials
|
||||
- credential helpers mit Secrets
|
||||
- Cloud Credentials, DB Credentials (produktive externe DBs)
|
||||
- Service Account Credentials, Webhook Secrets, Generic Bearer Tokens
|
||||
- Docker Credentials, Broker Credentials, Trading Credentials
|
||||
|
||||
Geprueft werden: ENV files, mounted secrets, process environment, config,
|
||||
home directory, git config, credential helpers.
|
||||
|
||||
Report: NUR PRESENT/ABSENT. NIEMALS Secret-Werte ausgeben.
|
||||
|
||||
## 4. Container-Hardening
|
||||
|
||||
- non-root UID/GID: hermes-User (UID 10010 / GID 10000)
|
||||
- read-only rootfs
|
||||
- cap_drop ALL
|
||||
- no-new-privileges
|
||||
- kein privileged
|
||||
- kein Docker-Socket
|
||||
- keine Host-Root-Mounts
|
||||
- keine Host-Namespaces
|
||||
- keine Device-Mounts
|
||||
- keine unnecessary Linux capabilities
|
||||
|
||||
Writable nur: worker-owned internal state volume + technisch zwingende tmpfs-Pfade.
|
||||
|
||||
## 5. Tool-Capability-Model
|
||||
|
||||
Der Worker darf NICHT haben:
|
||||
- terminal, generic shell, execute_code
|
||||
- write_file auf externe/shared Ziele, patch
|
||||
- git push, SSH, Docker, cronjob, send_message, delegation
|
||||
- arbitrary command execution
|
||||
|
||||
MAX_SUBAGENTS=0. Nur minimal erforderliche Read-only-Faehigkeiten.
|
||||
|
||||
Ground-Truth-Verifikation: prueft die tatsaechlich geladenen Toolsets,
|
||||
nicht nur die Config.
|
||||
|
||||
## 6. Network-Model
|
||||
|
||||
KEIN allgemeiner Internet-Egress. Worker erreicht nur:
|
||||
- A. Gate Evaluator (internal)
|
||||
- B. Ollama (internal)
|
||||
- C. Tolaria READ/Search (internal)
|
||||
|
||||
NICHT erreichbar:
|
||||
- Telegram API, Notion API, Forgejo public Host
|
||||
- Internet allgemein, Trading Networks, Broker endpoints
|
||||
- Docker API, Host management interfaces
|
||||
- SAVE executor, DELETE executor
|
||||
|
||||
Keine generische Proxy-/Relay-Loesung.
|
||||
|
||||
## 7. Gate Evaluator
|
||||
|
||||
Worker darf den bestehenden Gate Evaluator erreichen. CP1 bleibt unveraendert.
|
||||
Aktuell: GLOBAL_AUTONOMY=OFF, PRODUCTIVE_MUTATIONS=OFF, SAVE=OFF, DELETE=OFF,
|
||||
EMERGENCY_STOP=ON. Daher muss jeder produktive AUTONOMOUS_TICK_START weiterhin
|
||||
DENY ergeben. KEIN State aendern, um ALLOW zu testen.
|
||||
|
||||
## 8. Tolaria
|
||||
|
||||
Nur READ/Search. Worker darf KEINEN Tolaria SAVE/DELETE Token erhalten.
|
||||
NICHT produktiv schreiben. Keine Mutation als Test.
|
||||
|
||||
## 9. Ollama / LLM
|
||||
|
||||
Worker darf Ollama erreichen (fuer Hermes-Start erforderlich). Keine
|
||||
Credentials noetig. Keine dadurch entstehende externe Mutation Capability.
|
||||
KEINE autonome Mission ausfuehren.
|
||||
|
||||
## 10. Internal State Volume
|
||||
|
||||
Worker-eigenes persistentes State-Volume. Noch KEINE produktive A2-A5
|
||||
Initialisierung. Erlaubt: leeres/verifiziertes State-Verzeichnis bzw.
|
||||
Foundation-Struktur. Noch NICHT: missions.db produktiv initialisieren,
|
||||
safety runtime starten, orchestration runtime starten, heartbeat starten,
|
||||
tick loop starten. Worker State darf ausschliesslich vom Worker beschrieben
|
||||
werden. Bestehende interaktive RQ darf dieses Volume NICHT schreiben.
|
||||
|
||||
## 11. Mission Transfer — NOT YET
|
||||
|
||||
In CP2A2.2B: KEIN Shared Mission Volume, KEINE Mission Queue, KEIN Mission
|
||||
Broker, KEIN Mission Snapshot produktiv anbinden. Mission Transfer kommt
|
||||
separat danach.
|
||||
|
||||
## 12. No Autostart Autonomy
|
||||
|
||||
Nach Host-Reboot, Docker-Reboot, Container-Restart, Compose-up darf NICHT
|
||||
automatisch ein autonomer Tick beginnen. Worker Foundation darf existieren/
|
||||
starten, aber autonome Mission Execution bleibt unmoeglich. Gate DENY allein
|
||||
ist nicht die einzige Schutzschicht. Es darf noch gar kein autonomer
|
||||
Scheduler/Loop existieren.
|
||||
|
||||
## 13. Resource Limits
|
||||
|
||||
Konservative Foundation-Limits (siehe docker-compose / deploy):
|
||||
- CPU limit
|
||||
- memory limit
|
||||
- pids limit
|
||||
- bounded tmpfs
|
||||
- log rotation
|
||||
|
||||
Ein fehlerhafter Worker darf den Host nicht durch Ressourcenverbrauch
|
||||
beeintraechtigen.
|
||||
44
red-queen-architecture/control-plane/cp2a2_2b/deploy.sh
Normal file
44
red-queen-architecture/control-plane/cp2a2_2b/deploy.sh
Normal file
|
|
@ -0,0 +1,44 @@
|
|||
#!/bin/bash
|
||||
# CP2A2.2B — L1 Red Queen Worker: Deploy-Skript
|
||||
# STRICTLY NON-AUTONOMOUS / NON-PRODUCTIVE. NO AUTONOMY. NO HEARTBEAT. NO SCHEDULER.
|
||||
#
|
||||
# Baut und startet den L1 Worker Container (non-root, hardened, internal-Netz).
|
||||
# Haengt Gate-Evaluator, Ollama und Tolaria an das l1-internal Netz an
|
||||
# (nur Netzwerk-Attachment, KEINE Config/ENV/Credential-Aenderung).
|
||||
#
|
||||
# KEIN AUTOSTART-AUTONOMIE: Der Container startet inert (sleep infinity).
|
||||
# Kein autonomer Tick, kein Heartbeat, kein Scheduler.
|
||||
|
||||
set -eu
|
||||
|
||||
cd "$(dirname "$0")"
|
||||
|
||||
echo "=== CP2A2.2B L1 WORKER DEPLOY ==="
|
||||
|
||||
# 1. Worker-State-Volume vorbereiten (worker-owned, UID 10010)
|
||||
# Das Volume wird als named volume gemountet; der hermes-User (UID 10010)
|
||||
# ist der Worker und besitzt das State-Verzeichnis.
|
||||
echo "[1/4] Worker-State-Volume vorbereiten..."
|
||||
docker volume create l1-worker-state >/dev/null 2>&1 || true
|
||||
|
||||
# 2. Image bauen (aus SoT, non-root, read-only, cap-drop)
|
||||
echo "[2/4] Image bauen (l1-red-queen-worker:cp2a2_2b)..."
|
||||
docker build -t l1-red-queen-worker:cp2a2_2b .
|
||||
|
||||
# 3. Container starten (internal-Netz, non-root, hardened)
|
||||
echo "[3/4] Container starten (l1-red-queen-worker)..."
|
||||
docker compose -f docker-compose.yml up -d
|
||||
|
||||
# 4. Interne Ziele an l1-internal Netz anhaengen (nur Netzwerk-Attachment)
|
||||
echo "[4/4] Interne Ziele an l1-internal Netz anhaengen..."
|
||||
docker network connect l1-internal gate-evaluator 2>/dev/null || echo " (gate-evaluator bereits verbunden oder nicht verfuegbar)"
|
||||
docker network connect l1-internal ollama-nb6d-ollama-1 2>/dev/null || echo " (ollama bereits verbunden oder nicht verfuegbar)"
|
||||
docker network connect l1-internal tolaria 2>/dev/null || echo " (tolaria bereits verbunden oder nicht verfuegbar)"
|
||||
|
||||
echo ""
|
||||
echo "=== DEPLOY ABGESCHLOSSEN ==="
|
||||
echo "Container: l1-red-queen-worker (inert idle, NO AUTONOMY)"
|
||||
echo "Netzwerk: l1-internal (internal=true, keine Egress)"
|
||||
echo "State-Volume: l1-worker-state (worker-owned)"
|
||||
echo ""
|
||||
echo "Hinweis: Der Worker startet inert. Kein autonomer Tick wird gestartet."
|
||||
|
|
@ -0,0 +1,57 @@
|
|||
# CP2A2.2B — L1 Red Queen Worker: docker-compose
|
||||
# STRICTLY NON-AUTONOMOUS / NON-PRODUCTIVE. NO AUTONOMY. NO HEARTBEAT. NO SCHEDULER.
|
||||
#
|
||||
# Separate minimale L1 Worker Runtime. Der bestehende interaktive Container
|
||||
# `hermes-red-queen` bleibt UNVERAENDERT (wird hier NICHT referenziert).
|
||||
#
|
||||
# HAERTUNG:
|
||||
# - non-root hermes-User (UID 10010 / GID 10000, im Basis-Image definiert)
|
||||
# - read-only rootfs
|
||||
# - cap_drop ALL, no-new-privileges
|
||||
# - kein Docker-Socket, keine Host-Root-Mounts, keine Host-Namespaces
|
||||
# - eigenes internal-Netz (keine Egress, kein Internet)
|
||||
# - KEINE Credentials in ENV (Credential Zero Policy)
|
||||
#
|
||||
# NETZWERK-MODELL:
|
||||
# Der Worker haengt an einem eigenen internal-Netz `l1-internal`.
|
||||
# Gate-Evaluator, Ollama und Tolaria werden per `docker network connect`
|
||||
# zusaetzlich an dieses Netz angehaengt (nur Netzwerk-Attachment, KEINE
|
||||
# Config/ENV/Credential-Aenderung an diesen Containern).
|
||||
# Kein genereller Internet-Egress, kein Telegram/Notion/Forgejo-public.
|
||||
#
|
||||
# KEIN AUTOSTART-AUTONOMIE:
|
||||
# Der Container startet inert (sleep infinity). Kein autonomer Tick, kein
|
||||
# Heartbeat, kein Scheduler. Ein Tick wird ausschliesslich extern getriggert
|
||||
# (spaetere Phase) und ist in CP2A2.2B verboten.
|
||||
|
||||
services:
|
||||
l1-red-queen-worker:
|
||||
build: .
|
||||
image: l1-red-queen-worker:cp2a2_2b
|
||||
container_name: l1-red-queen-worker
|
||||
restart: unless-stopped
|
||||
read_only: true
|
||||
cap_drop:
|
||||
- ALL
|
||||
security_opt:
|
||||
- no-new-privileges:true
|
||||
tmpfs:
|
||||
- /tmp
|
||||
volumes:
|
||||
# Worker-eigenes persistentes State-Volume (worker-owned, UID 10010).
|
||||
# Nur der Worker kann schreiben. Bestehende interaktive RQ darf dieses
|
||||
# Volume NICHT schreiben (separate Security Domain).
|
||||
- l1-worker-state:/opt/l1-worker/state
|
||||
networks:
|
||||
- l1-internal
|
||||
environment: []
|
||||
# KEINE Credentials. KEIN Docker-Socket. KEINE Host-Root-Mounts.
|
||||
# KEIN Gateway, KEIN Telegram, KEIN API-Server, KEIN Cron.
|
||||
|
||||
networks:
|
||||
l1-internal:
|
||||
driver: bridge
|
||||
internal: true
|
||||
|
||||
volumes:
|
||||
l1-worker-state:
|
||||
|
|
@ -0,0 +1,157 @@
|
|||
# CP2A2.2B — L1 Red Queen Worker: Credential-Zero-Test
|
||||
# STRICTLY NON-AUTONOMOUS / NON-PRODUCTIVE.
|
||||
#
|
||||
# Verifiziert, dass der Worker KEINE mutierenden Credentials besitzt.
|
||||
# Report NUR PRESENT/ABSENT — NIEMALS Secret-Werte ausgeben.
|
||||
#
|
||||
# Geprueft werden:
|
||||
# - ENV-Variablen (mutierende Credential-Namen)
|
||||
# - config.yaml (worker-config.yaml)
|
||||
# - Home-Verzeichnis (keine .netrc, .git-credentials, SSH-Keys)
|
||||
# - git config (keine credential helpers)
|
||||
# - mounted secrets
|
||||
|
||||
import os
|
||||
import re
|
||||
import sys
|
||||
import subprocess
|
||||
|
||||
# Mutierende Credential-Namen (nur Namen, keine Werte)
|
||||
MUTATING_CREDENTIAL_NAMES = [
|
||||
"FORGEJO_WRITE_TOKEN",
|
||||
"NOTION_API_KEY",
|
||||
"TELEGRAM_BOT_TOKEN",
|
||||
"SAVE_TOKEN",
|
||||
"DELETE_TOKEN",
|
||||
"TOLARIA_SAVE_TOKEN",
|
||||
"TOLARIA_DELETE_TOKEN",
|
||||
"GITHUB_TOKEN",
|
||||
"GITLAB_TOKEN",
|
||||
"AWS_ACCESS_KEY_ID",
|
||||
"AWS_SECRET_ACCESS_KEY",
|
||||
"AZURE_CLIENT_SECRET",
|
||||
"GCP_SA_KEY",
|
||||
"DOCKER_AUTH_CONFIG",
|
||||
"DOCKER_CONFIG",
|
||||
"IG_API_KEY",
|
||||
"IG_ACCESS_TOKEN",
|
||||
"BROKER_API_KEY",
|
||||
"BROKER_TOKEN",
|
||||
"TRADING_TOKEN",
|
||||
"WEBHOOK_SECRET",
|
||||
"SERVICE_ACCOUNT_KEY",
|
||||
"DB_PASSWORD",
|
||||
"POSTGRES_PASSWORD",
|
||||
"MYSQL_PASSWORD",
|
||||
"REDIS_PASSWORD",
|
||||
"MONGO_PASSWORD",
|
||||
]
|
||||
|
||||
# Dateien, die Credentials enthalten koennten (im Home/Config)
|
||||
CREDENTIAL_FILE_PATTERNS = [
|
||||
".netrc",
|
||||
".git-credentials",
|
||||
".pgpass",
|
||||
".my.cnf",
|
||||
".aws/credentials",
|
||||
".config/gcloud/application_default_credentials.json",
|
||||
".docker/config.json",
|
||||
"id_rsa",
|
||||
"id_ed25519",
|
||||
"id_dsa",
|
||||
"id_ecdsa",
|
||||
]
|
||||
|
||||
|
||||
def check_env() -> list[str]:
|
||||
"""Prueft ENV auf mutierende Credential-Namen. Gibt gefundene Namen zurueck."""
|
||||
found = []
|
||||
for name in MUTATING_CREDENTIAL_NAMES:
|
||||
if name in os.environ:
|
||||
found.append(name)
|
||||
return found
|
||||
|
||||
|
||||
def check_config(config_path: str) -> list[str]:
|
||||
"""Prueft die Worker-Config auf mutierende Credential-Namen.
|
||||
|
||||
Ignoriert Kommentare (Zeilen mit #) — Kommentare sind Dokumentation,
|
||||
keine tatsaechlichen Credentials. Prueft nur echte YAML-Keys/Werte.
|
||||
"""
|
||||
found = []
|
||||
if not os.path.exists(config_path):
|
||||
return found
|
||||
try:
|
||||
with open(config_path, "r", encoding="utf-8") as f:
|
||||
lines = f.readlines()
|
||||
# Nur Nicht-Kommentar-Zeilen pruefen
|
||||
content = "\n".join(
|
||||
line for line in lines if not line.lstrip().startswith("#")
|
||||
)
|
||||
for name in MUTATING_CREDENTIAL_NAMES:
|
||||
if name in content:
|
||||
found.append(name)
|
||||
# Generische Secret-Schluessel in YAML (nur echte Keys, nicht Kommentare)
|
||||
for key in ["token", "api_key", "secret", "password", "bearer"]:
|
||||
if re.search(rf"^\s*{key}\s*:", content, re.IGNORECASE | re.MULTILINE):
|
||||
found.append(f"config-key:{key}")
|
||||
except Exception:
|
||||
pass
|
||||
return found
|
||||
|
||||
|
||||
def check_home(home: str) -> list[str]:
|
||||
"""Prueft Home-Verzeichnis auf Credential-Dateien/SSH-Keys."""
|
||||
found = []
|
||||
for pattern in CREDENTIAL_FILE_PATTERNS:
|
||||
path = os.path.join(home, pattern)
|
||||
if os.path.exists(path):
|
||||
found.append(pattern)
|
||||
# SSH-Verzeichnis
|
||||
ssh_dir = os.path.join(home, ".ssh")
|
||||
if os.path.isdir(ssh_dir):
|
||||
for entry in os.listdir(ssh_dir):
|
||||
if entry not in ("known_hosts", "config"):
|
||||
found.append(f".ssh/{entry}")
|
||||
return found
|
||||
|
||||
|
||||
def check_git_config(home: str) -> list[str]:
|
||||
"""Prueft git config auf credential helpers."""
|
||||
found = []
|
||||
git_config = os.path.join(home, ".gitconfig")
|
||||
if os.path.exists(git_config):
|
||||
try:
|
||||
with open(git_config, "r", encoding="utf-8") as f:
|
||||
content = f.read()
|
||||
if "credential" in content.lower() and "helper" in content.lower():
|
||||
found.append("git-credential-helper")
|
||||
except Exception:
|
||||
pass
|
||||
return found
|
||||
|
||||
|
||||
def main() -> int:
|
||||
home = os.environ.get("HOME", "/opt/data")
|
||||
config_path = os.environ.get("WORKER_CONFIG", "/opt/l1-worker/worker-config.yaml")
|
||||
|
||||
env_found = check_env()
|
||||
config_found = check_config(config_path)
|
||||
home_found = check_home(home)
|
||||
git_found = check_git_config(home)
|
||||
|
||||
all_found = sorted(set(env_found + config_found + home_found + git_found))
|
||||
|
||||
print("=== CP2A2.2B CREDENTIAL-ZERO TEST ===")
|
||||
print(f"ENV mutating credentials: {env_found if env_found else 'ABSENT'}")
|
||||
print(f"Config mutating credentials: {config_found if config_found else 'ABSENT'}")
|
||||
print(f"Home credential files: {home_found if home_found else 'ABSENT'}")
|
||||
print(f"Git credential helpers: {git_found if git_found else 'ABSENT'}")
|
||||
print(f"TOTAL MUTATING CREDENTIALS: {len(all_found)}")
|
||||
print(f"RESULT: {'PASS' if not all_found else 'FAIL'}")
|
||||
|
||||
return 0 if not all_found else 1
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
sys.exit(main())
|
||||
|
|
@ -0,0 +1,65 @@
|
|||
# CP2A2.2B — L1 Red Queen Worker: Network-Negative-Test
|
||||
# STRICTLY NON-AUTONOMOUS / NON-PRODUCTIVE.
|
||||
#
|
||||
# Verifiziert die Netzwerk-Isolation des Workers:
|
||||
# REACHABLE: Gate-Evaluator, Ollama, Tolaria-READ
|
||||
# BLOCKED: Internet generisch, api.telegram.org, api.notion.com,
|
||||
# Forgejo public, SAVE/DELETE executor, Trading/Broker
|
||||
#
|
||||
# Keine sensitiven Daten senden. Nur Connectivity-Checks (read-only).
|
||||
|
||||
import socket
|
||||
import sys
|
||||
|
||||
# Erreichbare interne Ziele (im l1-internal Netz)
|
||||
REACHABLE_TARGETS = [
|
||||
("gate-evaluator", 8080),
|
||||
("ollama-nb6d-ollama-1", 11434),
|
||||
("tolaria", 8325),
|
||||
]
|
||||
|
||||
# Blockierte Ziele (duerfen NICHT erreichbar sein)
|
||||
BLOCKED_TARGETS = [
|
||||
("api.telegram.org", 443),
|
||||
("api.notion.com", 443),
|
||||
("187.124.31.123", 3000), # Forgejo public
|
||||
("8.8.8.8", 53), # generischer Internet-Egress
|
||||
("1.1.1.1", 443), # generischer Internet-Egress
|
||||
]
|
||||
|
||||
|
||||
def check_reachable(host: str, port: int, timeout: float = 3.0) -> bool:
|
||||
"""Prueft TCP-Erreichbarkeit (read-only, keine Daten gesendet)."""
|
||||
try:
|
||||
with socket.create_connection((host, port), timeout=timeout):
|
||||
return True
|
||||
except Exception:
|
||||
return False
|
||||
|
||||
|
||||
def main() -> int:
|
||||
print("=== CP2A2.2B NETWORK-NEGATIVE TEST ===")
|
||||
|
||||
reachable_results = {}
|
||||
for host, port in REACHABLE_TARGETS:
|
||||
ok = check_reachable(host, port)
|
||||
reachable_results[f"{host}:{port}"] = ok
|
||||
print(f"REACHABLE {host}:{port} = {'OK' if ok else 'UNREACHABLE'}")
|
||||
|
||||
blocked_results = {}
|
||||
for host, port in BLOCKED_TARGETS:
|
||||
ok = check_reachable(host, port)
|
||||
blocked_results[f"{host}:{port}"] = ok
|
||||
print(f"BLOCKED {host}:{port} = {'BLOCKED' if not ok else 'REACHABLE(!)'}")
|
||||
|
||||
# PASS: alle REACHABLE erreichbar, alle BLOCKED blockiert
|
||||
all_reachable_ok = all(reachable_results.values())
|
||||
all_blocked_ok = all(not v for v in blocked_results.values())
|
||||
result = all_reachable_ok and all_blocked_ok
|
||||
|
||||
print(f"RESULT: {'PASS' if result else 'FAIL'}")
|
||||
return 0 if result else 1
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
sys.exit(main())
|
||||
|
|
@ -0,0 +1,97 @@
|
|||
# CP2A2.2B — L1 Red Queen Worker: Tool-Capability-Test
|
||||
# STRICTLY NON-AUTONOMOUS / NON-PRODUCTIVE.
|
||||
#
|
||||
# Verifiziert, dass der Worker KEINE verbotenen Tools/Faehigkeiten besitzt.
|
||||
# Verboten: terminal, generic shell, execute_code, write_file auf externe/
|
||||
# shared Ziele, patch, git push, SSH, Docker, cronjob, send_message,
|
||||
# delegation, arbitrary command execution. MAX_SUBAGENTS=0.
|
||||
#
|
||||
# Ground-Truth-Check: prueft die tatsaechlich geladenen Toolsets, nicht nur
|
||||
# die Config. Der Worker nutzt ein minimales read-only Toolset.
|
||||
|
||||
import os
|
||||
import sys
|
||||
|
||||
# Verbotene Tool-Namen (Ground-Truth-Check gegen geladene Toolsets)
|
||||
FORBIDDEN_TOOLS = [
|
||||
"terminal",
|
||||
"process",
|
||||
"execute_code",
|
||||
"write_file",
|
||||
"patch",
|
||||
"git_push",
|
||||
"ssh",
|
||||
"docker",
|
||||
"cronjob",
|
||||
"send_message",
|
||||
"delegate_task",
|
||||
"browser_navigate",
|
||||
"browser_click",
|
||||
"browser_type",
|
||||
"computer_use",
|
||||
]
|
||||
|
||||
# Erlaubte (read-only) Tools
|
||||
ALLOWED_TOOLS = [
|
||||
"web_extract",
|
||||
"web_search",
|
||||
"vision_analyze",
|
||||
"read_file",
|
||||
"search_files",
|
||||
"session_search",
|
||||
"memory",
|
||||
"todo",
|
||||
"clarify",
|
||||
"skills_list",
|
||||
"skill_view",
|
||||
]
|
||||
|
||||
|
||||
def get_loaded_toolsets() -> list[str]:
|
||||
"""Ermittelt die tatsaechlich geladenen Toolsets (Ground Truth)."""
|
||||
# Aus ENV: HERMES_TUI_TOOLSETS oder via hermes CLI
|
||||
env_toolsets = os.environ.get("HERMES_TUI_TOOLSETS", "")
|
||||
if env_toolsets:
|
||||
return [t.strip() for t in env_toolsets.split(",") if t.strip()]
|
||||
return []
|
||||
|
||||
|
||||
def check_forbidden_tools(toolsets: list[str]) -> list[str]:
|
||||
"""Prueft, ob verbotene Tools in den geladenen Toolsets enthalten sind."""
|
||||
found = []
|
||||
# Mapping Toolset -> Tools (statisch, aus toolsets.py Ground Truth)
|
||||
toolset_tools = {
|
||||
"web": ["web_search", "web_extract"],
|
||||
"vision": ["vision_analyze"],
|
||||
"file": ["read_file", "write_file", "patch", "search_files"],
|
||||
"terminal": ["terminal", "process"],
|
||||
"code_execution": ["execute_code"],
|
||||
"delegation": ["delegate_task"],
|
||||
"cronjob": ["cronjob"],
|
||||
"browser": ["browser_navigate", "browser_snapshot", "browser_click",
|
||||
"browser_type", "browser_scroll", "browser_back",
|
||||
"browser_press", "browser_get_images", "browser_console",
|
||||
"web_search"],
|
||||
"computer_use": ["computer_use"],
|
||||
}
|
||||
for ts in toolsets:
|
||||
for tool in toolset_tools.get(ts, []):
|
||||
if tool in FORBIDDEN_TOOLS:
|
||||
found.append(f"{ts}:{tool}")
|
||||
return found
|
||||
|
||||
|
||||
def main() -> int:
|
||||
toolsets = get_loaded_toolsets()
|
||||
forbidden = check_forbidden_tools(toolsets)
|
||||
|
||||
print("=== CP2A2.2B TOOL-CAPABILITY TEST ===")
|
||||
print(f"Loaded toolsets: {toolsets if toolsets else 'NONE (default)'}")
|
||||
print(f"Forbidden tools found: {forbidden if forbidden else 'ABSENT'}")
|
||||
print(f"RESULT: {'PASS' if not forbidden else 'FAIL'}")
|
||||
|
||||
return 0 if not forbidden else 1
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
sys.exit(main())
|
||||
|
|
@ -0,0 +1,37 @@
|
|||
# CP2A2.2B — L1 Red Queen Worker: minimale Hermes-Config (ohne Secrets)
|
||||
# STRICTLY NON-AUTONOMOUS / NON-PRODUCTIVE.
|
||||
#
|
||||
# Diese Config ist die minimale Hermes-Config fuer den Worker.
|
||||
# Sie enthaelt KEINE Credentials (kein Telegram, kein Notion, kein Forgejo,
|
||||
# kein SAVE/DELETE-Token). Provider = Ollama (intern, keine Credentials noetig).
|
||||
#
|
||||
# CREDENTIAL ZERO POLICY: Kein FORGEJO_WRITE_TOKEN, NOTION_API_KEY,
|
||||
# TELEGRAM_BOT_TOKEN, SAVE_TOKEN, DELETE_TOKEN, SSH-Keys, Git-Write-Credentials,
|
||||
# .netrc, .git-credentials, Cloud/DB/Service-Account/Webhook/Bearer/Docker/
|
||||
# Broker/Trading-Credentials.
|
||||
|
||||
model:
|
||||
provider: ollama
|
||||
default: deepseek-v4-flash:cloud
|
||||
context_length: 131072
|
||||
|
||||
providers:
|
||||
ollama:
|
||||
# Interner Ollama-Endpoint (kein Internet-Egress). Keine Credentials noetig.
|
||||
base_url: http://ollama-nb6d-ollama-1:11434/v1
|
||||
context_length: 131072
|
||||
discover_models: true
|
||||
|
||||
agent:
|
||||
verify_on_stop: false
|
||||
|
||||
# KEIN Gateway, KEIN Telegram, KEIN API-Server, KEIN Cron.
|
||||
gateway:
|
||||
telegram:
|
||||
enabled: false
|
||||
|
||||
# KEINE Plugins (keine externen Faehigkeiten).
|
||||
plugins:
|
||||
enabled: []
|
||||
|
||||
_config_version: 33
|
||||
|
|
@ -0,0 +1,22 @@
|
|||
#!/bin/sh
|
||||
# CP2A2.2B — L1 Red Queen Worker: inert idle Entrypoint
|
||||
# STRICTLY NON-AUTONOMOUS / NON-PRODUCTIVE.
|
||||
#
|
||||
# Dieser Entrypoint startet den Worker als INERTEN idle-Prozess.
|
||||
# Er fuehrt KEINEN autonomen Tick aus, KEINEN Heartbeat, KEINEN Scheduler.
|
||||
# Ein Tick wird ausschliesslich extern getriggert (spaeterer externer Scheduler
|
||||
# -> Gate-Evaluator /check -> ALLOW -> `hermes -z` oneshot via worker-tick.sh).
|
||||
#
|
||||
# In CP2A2.2B existiert der Tick-Wrapper als Datei, wird aber NICHT automatisch
|
||||
# ausgefuehrt. Der Container bleibt inert, bis ein externer Trigger ihn startet.
|
||||
|
||||
set -eu
|
||||
|
||||
echo "[l1-worker] CP2A2.2B L1 Worker Foundation — inert idle (NO AUTONOMY, NO HEARTBEAT, NO SCHEDULER)"
|
||||
echo "[l1-worker] UID=$(id -u) GID=$(id -g) USER=$(id -un)"
|
||||
echo "[l1-worker] Warte auf externen Trigger. Kein autonomer Tick wird gestartet."
|
||||
|
||||
# Inert idle: blockiere, ohne einen autonomen Loop zu starten.
|
||||
# Ein externer Scheduler (spaetere Phase) wuerde `worker-tick.sh` aufrufen.
|
||||
# In CP2A2.2B passiert das NICHT automatisch.
|
||||
exec sleep infinity
|
||||
60
red-queen-architecture/control-plane/cp2a2_2b/worker-tick.sh
Normal file
60
red-queen-architecture/control-plane/cp2a2_2b/worker-tick.sh
Normal file
|
|
@ -0,0 +1,60 @@
|
|||
#!/bin/sh
|
||||
# CP2A2.2B — L1 Red Queen Worker: Tick-Wrapper (DEFINIERT, NICHT AUTOMATISCH AUSGEFUEHRT)
|
||||
# STRICTLY NON-AUTONOMOUS / NON-PRODUCTIVE.
|
||||
#
|
||||
# Dieser Wrapper fuehrt EINEN einzelnen `hermes -z` oneshot aus.
|
||||
# Er wird in CP2A2.2B NICHT automatisch gestartet (kein Scheduler, kein Loop).
|
||||
# Er dient als technische Foundation fuer spaetere Read-only-Autonomie:
|
||||
# externer Scheduler -> Gate-Evaluator /check -> ALLOW -> worker-tick.sh
|
||||
#
|
||||
# WICHTIG: In CP2A2.2B wird dieser Wrapper NUR als Datei bereitgestellt.
|
||||
# Er wird NICHT vom Entrypoint, NICHT von cron, NICHT von einem Loop aufgerufen.
|
||||
# Kein autonomer Tick darf in dieser Phase laufen.
|
||||
|
||||
set -eu
|
||||
|
||||
# --- Gate-Evaluator /check (read-only, keine Mutation) ---
|
||||
# Der Gate-Evaluator (CP2A1) entscheidet fail-closed. Aktuell:
|
||||
# GLOBAL_AUTONOMY=OFF, PRODUCTIVE_MUTATIONS=OFF, SAVE=OFF, DELETE=OFF,
|
||||
# EMERGENCY_STOP=ON -> AUTONOMOUS_TICK_START = DENY
|
||||
# Daher muss jeder produktive Tick-Start weiterhin DENY ergeben.
|
||||
# KEIN State aendern, um ALLOW zu testen.
|
||||
|
||||
GATE_URL="${GATE_EVALUATOR_URL:-http://gate-evaluator:8080}"
|
||||
ACTION_CLASS="AUTONOMOUS_TICK_START"
|
||||
|
||||
echo "[l1-worker] Gate-Check (read-only): ${GATE_URL}/check action_class=${ACTION_CLASS}"
|
||||
|
||||
# Read-only HTTP-Check via Python (kein curl im Image noetig, kein Shell-Egress).
|
||||
# Der Worker hat KEINE mutierenden Credentials; der Check ist rein lesend.
|
||||
GATE_RESPONSE=$(python3 - <<PYEOF
|
||||
import json, urllib.request
|
||||
try:
|
||||
req = urllib.request.Request(
|
||||
"${GATE_URL}/check",
|
||||
data=json.dumps({"action_class": "${ACTION_CLASS}"}).encode(),
|
||||
headers={"Content-Type": "application/json"},
|
||||
method="POST",
|
||||
)
|
||||
with urllib.request.urlopen(req, timeout=10) as resp:
|
||||
body = resp.read().decode()
|
||||
print(body)
|
||||
except Exception as exc:
|
||||
print(json.dumps({"error": str(exc), "decision": "DENY"}))
|
||||
PYEOF
|
||||
)
|
||||
|
||||
echo "[l1-worker] Gate-Response: ${GATE_RESPONSE}"
|
||||
|
||||
# Fail-closed: Nur bei explizitem ALLOW wuerde ein Tick laufen.
|
||||
# In CP2A2.2B ist das Gate DENY (EMERGENCY_STOP=ON), daher KEIN Tick.
|
||||
DECISION=$(echo "${GATE_RESPONSE}" | python3 -c "import sys,json; d=json.load(sys.stdin); print(d.get('decision','DENY'))" 2>/dev/null || echo "DENY")
|
||||
|
||||
if [ "${DECISION}" = "ALLOW" ]; then
|
||||
echo "[l1-worker] WARNUNG: Gate ALLOW — aber CP2A2.2B ist NON-AUTONOMOUS. Tick wird NICHT ausgefuehrt."
|
||||
echo "[l1-worker] Autonome Mission Execution ist in dieser Phase verboten (HARD STOP)."
|
||||
exit 0
|
||||
fi
|
||||
|
||||
echo "[l1-worker] Gate-DECISION=${DECISION} (fail-closed). Kein autonomer Tick in CP2A2.2B."
|
||||
exit 0
|
||||
Loading…
Reference in a new issue