Add red blue security loop

This commit is contained in:
juvdiaz 2026-06-30 13:21:15 -06:00
parent 0af1257d86
commit 7f537c4025
9 changed files with 147 additions and 1 deletions

View File

@ -244,6 +244,18 @@ is stale:
This keeps local AI answers grounded in repo files and runbooks. This keeps local AI answers grounded in repo files and runbooks.
## Red/Blue Loop
Safe red-team/blue-team checks are grouped under one command:
```bash
./jeannie red-blue plan
./jeannie red-blue run --local
```
Local mode runs deterministic prompt-injection and eval checks. Live scans are
listed but gated so they can run from the Debian host when intended.
## Deploying ## Deploying
From the Debian server: From the Debian server:

View File

@ -244,6 +244,18 @@ is stale:
This keeps local AI answers grounded in repo files and runbooks. This keeps local AI answers grounded in repo files and runbooks.
## Red/Blue Loop
Safe red-team/blue-team checks are grouped under one command:
```bash
./{{ main_script }} red-blue plan
./{{ main_script }} red-blue run --local
```
Local mode runs deterministic prompt-injection and eval checks. Live scans are
listed but gated so they can run from the Debian host when intended.
## Deploying ## Deploying
From the Debian server: From the Debian server:

View File

@ -387,6 +387,10 @@ the public website deployment.
`security/prompt-injection-lab`. Cases cover hostile runbooks, tool output, `security/prompt-injection-lab`. Cases cover hostile runbooks, tool output,
fake dashboard mitigations, and unsafe command arguments. fake dashboard mitigations, and unsafe command arguments.
`red-blue {plan|run|ledger}`
: Plan or run the homelab red-team/blue-team loop. `run --local` executes only
deterministic local checks; live security scans remain explicit.
Kyverno hardening policies Kyverno hardening policies
: `apps/supply-chain-policy` includes audit-mode checks for privileged pods, : `apps/supply-chain-policy` includes audit-mode checks for privileged pods,
privilege escalation, hostPath use, resource requests/limits, and mutable image privilege escalation, hostPath use, resource requests/limits, and mutable image

View File

@ -0,0 +1,16 @@
# Red-Team / Blue-Team Loop
This directory defines the safe recurring security loop for the homelab. The
first implementation runs local deterministic checks and prints the gated live
checks that should run from the Debian host.
Run:
```bash
./jeannie red-blue plan
./jeannie red-blue run --local
```
The loop is intentionally top-risk oriented: fix, accept, or defer findings in
`infra/red-blue-loop/ledger.tsv`.

View File

@ -0,0 +1,8 @@
id mode command risk
prompt_injection local ./jeannie prompt-injection-lab run prompt injection regressions
ai_evals local ./jeannie ai-evals run Jeannie/RAG behavior regressions
secret_scan live ./jeannie security-secrets secret leakage
web_scan live ./jeannie security-web public web exposure
k8s_policy live ./jeannie security-k8s Kubernetes hardening drift
nuclei live ./jeannie security-nuclei public known-CVE exposure
1 id mode command risk
2 prompt_injection local ./jeannie prompt-injection-lab run prompt injection regressions
3 ai_evals local ./jeannie ai-evals run Jeannie/RAG behavior regressions
4 secret_scan live ./jeannie security-secrets secret leakage
5 web_scan live ./jeannie security-web public web exposure
6 k8s_policy live ./jeannie security-k8s Kubernetes hardening drift
7 nuclei live ./jeannie security-nuclei public known-CVE exposure

View File

@ -0,0 +1,3 @@
date finding status owner notes
2026-06-30 initial-loop accepted jv Loop created; add real findings as fixed, accepted, or deferred.
1 date finding status owner notes
2 2026-06-30 initial-loop accepted jv Loop created; add real findings as fixed, accepted, or deferred.

View File

@ -6346,6 +6346,10 @@ ai_memory() {
"${REPO_ROOT}/scripts/ai-memory" "${@:2}" "${REPO_ROOT}/scripts/ai-memory" "${@:2}"
} }
red_blue_loop() {
"${REPO_ROOT}/scripts/red-blue-loop" "${@:2}"
}
validate_homelab() { validate_homelab() {
"${REPO_ROOT}/scripts/validate-homelab" "${REPO_ROOT}/scripts/validate-homelab"
} }
@ -6606,6 +6610,7 @@ Security Learning
security-runtime Check runtime security sensors. security-runtime Check runtime security sensors.
security-attack-path Print prioritized attack-path findings. security-attack-path Print prioritized attack-path findings.
prompt-injection-lab {list|run|show} Run local prompt-injection defense drills. prompt-injection-lab {list|run|show} Run local prompt-injection defense drills.
red-blue {plan|run|ledger} Run or plan safe red-team/blue-team loop.
AI And Indexing AI And Indexing
ai-index Build the local homelab RAG index. ai-index Build the local homelab RAG index.
@ -6890,6 +6895,9 @@ case "${1:-}" in
prompt-injection-lab) prompt-injection-lab)
prompt_injection_lab "$@" prompt_injection_lab "$@"
;; ;;
red-blue)
red_blue_loop "$@"
;;
openwrt) openwrt)
openwrt openwrt
;; ;;

View File

@ -34,7 +34,7 @@ EOF
is_read_only_command() { is_read_only_command() {
case "$1" in case "$1" in
status|capacity|capacity-advisor|capacity-limits|recover-plan|incident|safety-case|agent-sandbox|scorecard|gitops-status|cert-check|backup-status|synthetic-checks|resource-budget|route-inventory|golden-ledger|workers|change-journal|map|validate|access-audit|doctor-versions|doctor-edge|doctor-gitea|doctor-rpi|doctor-cluster|preflight|doctor-preapply|inventory-check|secrets-check|tailnet-policy-check|ai-check|ask|ai-evals|ai-scheduler|ai-memory|impact|review-last-change|security-scan|security-host|security-trivy|security-secrets|security-nuclei|security-web|security-logs|security-attack-path|prompt-injection-lab|control-plane) status|capacity|capacity-advisor|capacity-limits|recover-plan|incident|safety-case|agent-sandbox|scorecard|gitops-status|cert-check|backup-status|synthetic-checks|resource-budget|route-inventory|golden-ledger|workers|change-journal|map|validate|access-audit|doctor-versions|doctor-edge|doctor-gitea|doctor-rpi|doctor-cluster|preflight|doctor-preapply|inventory-check|secrets-check|tailnet-policy-check|ai-check|ask|ai-evals|ai-scheduler|ai-memory|impact|review-last-change|security-scan|security-host|security-trivy|security-secrets|security-nuclei|security-web|security-logs|security-attack-path|prompt-injection-lab|red-blue|control-plane)
return 0 return 0
;; ;;
*) *)

83
scripts/red-blue-loop Executable file
View File

@ -0,0 +1,83 @@
#!/usr/bin/env python3
"""Run or plan the homelab red-team/blue-team loop."""
from __future__ import annotations
import argparse
import csv
import subprocess
from pathlib import Path
REPO_ROOT = Path(__file__).resolve().parents[1]
CHECKS_FILE = REPO_ROOT / "infra" / "red-blue-loop" / "checks.tsv"
LEDGER_FILE = REPO_ROOT / "infra" / "red-blue-loop" / "ledger.tsv"
def checks() -> list[dict[str, str]]:
with CHECKS_FILE.open(encoding="utf-8", newline="") as handle:
return list(csv.DictReader(handle, delimiter="\t"))
def plan() -> int:
print("Red/Blue Loop")
print("=============")
for row in checks():
print(f"{row['mode']:5} {row['id']:18} {row['command']}")
print(f" risk: {row['risk']}")
print()
print(f"Ledger: {LEDGER_FILE.relative_to(REPO_ROOT)}")
return 0
def run(local_only: bool) -> int:
failures = 0
selected = [row for row in checks() if row["mode"] == "local" or not local_only]
print("Red/Blue Loop Run")
print("=================")
for row in selected:
print(f"\n== {row['id']} ==")
if row["mode"] != "local" and local_only:
print("skip: live check gated")
continue
process = subprocess.run(
row["command"].split(),
cwd=REPO_ROOT,
text=True,
stdout=subprocess.PIPE,
stderr=subprocess.STDOUT,
check=False,
)
print(process.stdout.rstrip())
if process.returncode != 0:
failures += 1
print()
print(f"failures={failures}")
print(f"ledger={LEDGER_FILE.relative_to(REPO_ROOT)}")
return 1 if failures else 0
def ledger() -> int:
print(LEDGER_FILE.read_text(encoding="utf-8").rstrip())
return 0
def main() -> int:
parser = argparse.ArgumentParser(description=__doc__)
subparsers = parser.add_subparsers(dest="command", required=True)
subparsers.add_parser("plan")
run_parser = subparsers.add_parser("run")
run_parser.add_argument("--local", action="store_true", help="Run only deterministic local checks.")
subparsers.add_parser("ledger")
args = parser.parse_args()
if args.command == "plan":
return plan()
if args.command == "run":
return run(local_only=args.local)
if args.command == "ledger":
return ledger()
return 2
if __name__ == "__main__":
raise SystemExit(main())