@@ -0,0 +1,60 @@
|
||||
#!/usr/bin/env python3
|
||||
"""Fail if production Compose broadens Agent secrets or misses runtime env knobs."""
|
||||
from __future__ import annotations
|
||||
import re
|
||||
import sys
|
||||
from pathlib import Path
|
||||
|
||||
try:
|
||||
import yaml
|
||||
except Exception as exc: # PyYAML is intentionally only a release-dev dependency.
|
||||
print(f"compose-env-check: PyYAML unavailable: {exc}", file=sys.stderr)
|
||||
raise SystemExit(2)
|
||||
|
||||
root = Path(__file__).resolve().parents[1]
|
||||
compose = yaml.safe_load((root / "docker-compose.yml").read_text())
|
||||
agent = compose["services"]["agent"]
|
||||
if "env_file" in agent:
|
||||
raise SystemExit("compose-env-check: production Agent must not use env_file")
|
||||
actual = set((agent.get("environment") or {}).keys())
|
||||
|
||||
# Canonical host-config variables are those documented in .env.example and
|
||||
# literally referenced by non-test Agent runtime source. Internal service values
|
||||
# are added separately below.
|
||||
documented: set[str] = set()
|
||||
for line in (root / ".env.example").read_text().splitlines():
|
||||
match = re.match(r"([A-Z][A-Z0-9_]+)=", line)
|
||||
if match:
|
||||
documented.add(match.group(1))
|
||||
source = "\n".join(
|
||||
p.read_text(errors="ignore")
|
||||
for p in (root / "services/agent").rglob("*.go")
|
||||
if not p.name.endswith("_test.go")
|
||||
)
|
||||
expected = {name for name in documented if re.search(rf'"{re.escape(name)}"', source)}
|
||||
expected.update({"NEUROFORGE_URL", "NEUROFORGE_API_KEY", "BRAIN_ACTIVITY_URL", "BRAIN_ACTIVITY_API_KEY"})
|
||||
missing = sorted(expected - actual)
|
||||
if missing:
|
||||
print("compose-env-check: Agent runtime variables missing from explicit environment:", file=sys.stderr)
|
||||
print("\n".join(f" {name}" for name in missing), file=sys.stderr)
|
||||
raise SystemExit(1)
|
||||
|
||||
forbidden = {
|
||||
"NEUROFORGE_ADMIN_TOKEN",
|
||||
"NEUROFORGE_WORKER_TOKEN",
|
||||
"NEUROFORGE_METRICS_TOKEN",
|
||||
"NEUROFORGE_CLUSTER_TOKEN",
|
||||
"NEUROFORGE_CONTROL_READ_TOKEN",
|
||||
"KB_INTEGRATION_TOKEN",
|
||||
"BASIC_AUTH_PASSWORD",
|
||||
"CONTROL_BASIC_AUTH_PASSWORD",
|
||||
}
|
||||
leaked = sorted(forbidden & actual)
|
||||
if leaked:
|
||||
print("compose-env-check: unrelated privileged secrets exposed to Agent: " + ", ".join(leaked), file=sys.stderr)
|
||||
raise SystemExit(1)
|
||||
|
||||
knowledge = compose["services"]["knowledge"]
|
||||
if "env_file" in knowledge:
|
||||
raise SystemExit("compose-env-check: production Knowledge must not use env_file")
|
||||
print(f"compose-env-check: passed ({len(actual)} explicit Agent environment entries)")
|
||||
Reference in New Issue
Block a user