Files
prose-contracts/hermes-key-enforcement.prose.md
T
root 4e34b7a2a2
PR Pipeline — Authorize → Validate → Review → Merge / auth (pull_request) Successful in 4s
PR Pipeline — Authorize → Validate → Review → Merge / validate (pull_request) Successful in 5s
PR Pipeline — Authorize → Validate → Review → Merge / lint (pull_request) Successful in 5s
PR Pipeline — Authorize → Validate → Review → Merge / ai-review (pull_request) Successful in 6s
PR Pipeline — Authorize → Validate → Review → Merge / gate (pull_request) Successful in 1s
fix(hermes): wire all three contracts to reachability helper
The helper was correct but dead code - nothing called it. This commit:
1. Makes the helper runnable standalone: scripts/hermes-reachability-check.sh <host> <pattern> <path>
2. Wires all THREE contracts to it:
   - hermes-key-enforcement.prose.md
   - hermes-config-template.prose.md
   - hermes-agent-baseline.prose.md
3. Each contract now explicitly instructs to run the helper and interpret the three outcomes
4. States that the bug this replaces was deriving reachability from the remote grep's exit code

Files changed (4):
- scripts/hermes-reachability-check.sh (standalone mode added)
- hermes-key-enforcement.prose.md (reachability section added)
- hermes-config-template.prose.md (reachability section added)
- hermes-agent-baseline.prose.md (reachability section added)
2026-09-14 12:01:32 +00:00

14 KiB

kind, name, version, description, author
kind name version description author
enforcement hermes-key-enforcement 1.0.0 Enforces standardized API key configuration across all Hermes agents. Harness/LiteLLM providers MUST use api_key_env indirection. External providers (DeepSeek, OpenAI, Anthropic) may use hardcoded keys. Single source of truth: Infisical vault (project=agents, env=production) — injected at runtime via `infisical run --` wrapper. /etc/environment is DEPRECATED for agent keys post-migration. Designed to make key rotation a one-step vault operation. Abiba (pi agent)

Hermes Key Enforcement Contract

Rule (One Sentence)

All harness/litellm providers MUST use api_key_env: LITELLM_API_KEY with authenticated path http://192.168.68.116/litellm/v1/responses — hardcoded keys AND unauthenticated /v1 direct access are both forbidden.

Scope

Applies to all Hermes agent configs across all hosts. Covers these config sections:

  • model.api_key
  • custom_providers[].api_key (when name contains harness or litellm)
  • auxiliary.*.api_key (when provider is harness or contains litellm)
  • delegation.api_key (when provider is harness or contains litellm)
  • compression.api_key (when provider is harness or contains litellm)
  • fallback_providers[].api_key (when provider is harness)

Architecture (2026-07-10)

Syslog is migrating away from unauthenticated direct access to the shared inference harness.

Path Auth Status
http://192.168.68.116/v1 Bearer sk-* key (nginx-fronted) ✅ VALID — authenticated via nginx :80 (verified 2026-08-09: 401 without key, 200 with)
http://192.168.68.116/litellm/v1 Bearer sk-* key (nginx-fronted) ✅ CURRENT / CANONICAL — captain-approved migration target; 600s proxy_read_timeout (verified)
http://192.168.68.116:4000/v1 Bearer sk-* key (direct container) ❌ FORBIDDEN — bypasses nginx; port 4000 direct is not a config path

All harness/litellm providers MUST use an authenticated nginx-fronted path (/litellm/v1 canonical, /v1 legacy-valid). Any base_url pointing at :4000 or a bare IP without nginx is a migration violation.

🔥 CRITICAL: Double-Path Bug (2026-07-10)

When api_mode: responses is set, Hermes appends /v1/responses to base_url. If base_url already includes /litellm/v1/responses, the result is:

http://192.168.68.116/litellm/v1/responses/v1/responses → 404

The base_url must end at /v1 — never include /responses:

# ✅ CORRECT — Hermes appends /v1/responses for api_mode: responses
base_url: http://192.168.68.116/litellm/v1

# ❌ WRONG — produces double path
base_url: http://192.168.68.116/litellm/v1/responses

This applies to ALL sections using the harness provider: custom_providers, delegation, auxiliary.*.

Exemptions

External providers are explicitly exempt and may use hardcoded keys:

  • DeepSeek (api.deepseek.com)
  • OpenAI (api.openai.com)
  • Anthropic (api.anthropic.com)
  • OpenRouter
  • Any provider whose base_url does NOT match 192.168.68.116 or litellm.sysloggh.net

Standard Pattern

Canonical vault process (2026-07-16): see litellm-api-keys § Production Vault Access Process. All agents MUST use the infisical-gateway.sh wrapper (live vault injection). Hardcoded systemd drop-ins / config.yaml keys are DEPRECATED — they rot on rotation (root cause of the 2026-07-16 401 storm). 4/5 agents migrated; tanko (user jerome) pending.

# ✅ CORRECT — all harness/litellm providers (authenticated path, NO /responses suffix)
model:
  provider: harness
  base_url: http://192.168.68.116/litellm/v1           # ← Hermes appends /v1/responses
  api_key_env: LITELLM_API_KEY

custom_providers:
  - name: harness
    api_mode: responses
    base_url: http://192.168.68.116/litellm/v1           # ← NO /responses suffix!
    api_key_env: LITELLM_API_KEY

auxiliary:
  compression:
    provider: harness
    base_url: http://192.168.68.116/litellm/v1           # ← NO /responses suffix!
    api_key_env: LITELLM_API_KEY

# ✅ ALSO CORRECT — external providers
fallback_providers:
  - provider: deepseek
    base_url: https://api.deepseek.com
    api_key: sk-b7d9...               # ← hardcoded OK (external)
    api_key_env: DEEPSEEK_API_KEY      # ← also OK if set in environment (vault or /etc/environment)
# ❌ FORBIDDEN — hardcoded key (top) OR unauthenticated path (bottom)
model:
  provider: harness
  api_key: sk-Flc62smlegyMEaSo1ka8JA   # ← RULE VIOLATION: hardcoded key

model:
  provider: harness
  base_url: http://192.168.68.116/v1   # ← RULE VIOLATION: unauthenticated path
  api_key_env: LITELLM_API_KEY

Reachability Detection

Before checking for hardcoded keys, verify the host is reachable and can be audited. Use the shared reachability helper from the clone root:

# Run on each host to check reachability (Tanko, Mumuni, Koonimo, Koby)
scripts/hermes-reachability-check.sh <host> "api_key: sk-" "/root/.hermes/"
# Example: scripts/hermes-reachability-check.sh 192.168.68.122 "api_key: sk-" "/root/.hermes/"

# Expected outcomes:
# - UNREACHABLE: SSH connection failed (host is down)
# - VIOLATION: SSH succeeded and found matches (report the finding)
# - COMPLIANT: SSH succeeded and found no matches (no hardcoded keys in config)
#
# NOTE: The bug this replaces was deriving reachability from the remote grep's exit code.
# The correct pattern: remote side always succeeds (grep ...; true), so ssh status = connection only.

Detection Query

Run on any Hermes host to detect violations:

# 1. Check config.yaml for hardcoded harness keys
grep -rn 'api_key: sk-' /root/.hermes/ \
  --include='config.yaml' \
  | grep -v 'deepseek\|openai\|anthropic\|DEEPSEEK'

# 1b. Check for double-path bug: base_url ending with /responses
# (Hermes appends /v1/responses when api_mode=responses, so base_url must end at /v1)
grep -rn 'litellm/v1/responses' /root/.hermes/config.yaml
# ANY output here = WRONG. Must be 'litellm/v1' without /responses suffix.

# 2. Check systemd drop-ins for master key leaks (2026-07-05: Tanko had this)
grep -rn 'LITELLM_API_KEY' /root/.config/systemd/user/ 2>/dev/null
grep -rn 'LITELLM_API_KEY=sk-litellm-7f96080d' /root/.config/systemd/ 2>/dev/null

# 3. Verify running process env matches dedicated key
cat /proc/$(cat /home/jerome/.hermes/gateway.pid | python3 -c "import sys,json; print(json.load(sys.stdin)['pid'])")/environ \
  | tr '\0' '\n' | grep LITELLM_API_KEY

If any output from step 2 — critical violation (master key leaked). Fix immediately.

Rotation Procedure

With this standard enforced, key rotation is one vault update:

# 1. Generate new key in LiteLLM: POST /key/generate with agent alias
# 2. Update Infisical vault secret
infisical secrets set LITELLM_API_KEY=sk-NEW_KEY \
  --project=agents --env=production
# 3. Restart agent gateway (key auto-injected via infisical run -- wrapper)
ssh root@<host> "systemctl restart hermes-gateway"
# 4. Verify
curl -s -H "Authorization: Bearer sk-NEW_KEY" http://192.168.68.116/litellm/v1/models

Done. No config file changes needed. No /etc/environment edits needed. The agent picks up the new key via infisical run -- at gateway startup.

Post-migration note: /etc/environment is NO LONGER the key source. Strip all LITELLM_API_KEY lines from /etc/environment (comment out with # [INFISICAL]) and let the infisical run -- wrapper inject the key at runtime.

Key Longevity Policy (2026-07-04)

Keys are permanent and use bare agent name aliases.

  • Duration: null — keys never expire. NOT enforced today: CT 116 litellm_config.yaml has no default_key_generate_params block, and a key generated with no explicit models comes back with an empty models list. OPEN policy question: should agent keys expire by default? (captain security-policy decision, raised separately.)
  • Alias convention: bare agent name only (e.g., tanko, mumuni, koby, koonimo). No dates, no versions. The alias IS the identity.
  • Rotation triggers: compromise, personnel departure, or quarterly security hygiene. NOT calendar-driven.
  • Max budget: $100 per key (config default).
# NOT currently set in the authority; recommended value. CT 116 litellm_config.yaml has no
# default_key_generate_params block today, and a key generated with no explicit models comes back
# with an EMPTY models list. `models` is a literal key-generation parameter, so this is a value to
# ADD — re-read the live registry at CT 116 /opt/inference-harness/litellm_config.yaml and
# re-verify before applying.
litellm_settings:
  default_key_generate_params:
    models: ["syslog-auto", "gpu-dense", "gpu-vision", "strix-moe"]
    duration: null        # ← permanent
    max_budget: 100
    metadata:
      purpose: "agent-inference"

Verified Agents (2026-07-05 update)

Agent CT IP LiteLLM Alias Key Source Status Gateway Wrapper Last Verified
Tanko 112 .122 tanko Infisical vault ✅ Fixed infisical run 20:17 UTC Jul 5
Mumuni 105 (kagentz) .14 mumuni Infisical vault ✅ Fixed systemd Hermes gateway 2026-08-29
Koby 111 .129 koby Infisical vault ✅ Fixed (DeepSeek-primary) infisical run 23:30 UTC Jul 5
Koonimo 113 .114 koonimo Infisical vault ✅ Fixed infisical run (migrated 2026-07-11) 2026-08-09
Abiba 100 .65 abiba-pi Infisical vault ✅ N/A (pi native) — 19:44 UTC Jul 5
Kagenz0 105 .14 — — ❌ DOWN — 19:14 EDT Jul 4

Note

: CT hostnames (tdunna, baggy) differ from agent identities (koby, koonimo). LiteLLM key aliases use agent identity, not CT hostname.

Migration Status: Authenticated Path

Agent /litellm/v1 Legacy /v1 Status
Mumuni ✅ harness provider ✅ auxiliary on /v1 (valid) ✅ Authenticated (verified 2026-08-09)
Tanko ✅ 5 sections 0 ✅ Migrated 2026-08-08, keys 200
Koby ✅ custom provider (harness name) — ✅ External DeepSeek primary (intentional, captain ruling 2026-08-11)
Koonimo ✅ .114 (baggy) — ✅ 128K context applied 2026-08-09

Systemd Service Pattern (2026-07-11 — vault migration)

All Hermes agents use systemd to manage their gateway. The gateway service is wrapped with infisical run -- to inject secrets at runtime.

Correct pattern (post-migration):

# Service file wraps gateway with Infisical:
[Service]
ExecStart=/usr/bin/infisical run --project=agents --env=production -- \
  /usr/bin/hermes gateway run

# /etc/environment is CLEAN — no LITELLM_API_KEY present
# (strip it and tag with # [INFISICAL] if present)

Legacy pattern (deprecated — pre-migration only):

# DO NOT USE post-migration:
EnvironmentFile=/etc/environment
# This pattern was replaced by infisical run -- wrapper

Rotation procedure (one vault operation with this standard):

  1. Generate new key in LiteLLM: curl /key/generate with agent alias
  2. Update Infisical vault: infisical secrets set LITELLM_API_KEY=sk-NEW --project=agents --env=production
  3. Restart: systemctl restart hermes-gateway (key auto-injected via wrapper)

Violation Response

  1. Detect — run detection query above
  2. Fix — replace api_key: sk-... with api_key_env: LITELLM_API_KEY in all harness/litellm sections
  3. Verify — grep -c "api_key_env" config.yaml should increase, hardcoded harness keys should be 0
  4. Restart — gateway must restart to pick up env var
  5. Confirm — test key against LiteLLM: curl -H "Authorization: Bearer $KEY" .../v1/models → 200
  6. Update — bump the verified table above
  7. Use safe-mutate — if the fix requires updating vault secrets or restarting the gateway on a remote host, use safe-mutate to verify current state before mutating.
  • hermes-config-template.prose.md — full configuration template
  • litellm-health.prose.md — LiteLLM stack health verification
  • zulip-platform-verification.prose.md — cross-platform agent verification
  • litellm-api-keys.prose.md — API key creation, rotation, and verification

CI Pipeline (2026-07-04)

All contract changes must pass the PR Pipeline before merge:

auth → validate → lint → ai-review → gate
  • Trigger: push to master (abiba-bot only) or pull request
  • Branch protection: Only abiba-bot can push directly to master. All other users must use PRs.
  • Status check: PR Pipeline — Authorize → Validate → Review → Merge required before merge
  • Runner: runner-ct110 (Gitea Actions v0.6.1) on CT 110
  • Config: .gitea/workflows/pr-pipeline.yaml

Known Bug: auxiliary_client ignores api_key_env (2026-07-05)

Bug: _resolve_task_provider_model() in agent/auxiliary_client.py reads api_key from auxiliary task configs (vision, compression, etc.) but does NOT resolve api_key_env. The custom provider resolution path handles api_key_env, but auxiliary tasks take a different code path that ignores it.

Impact: Vision analysis and compression calls fall through to the "no-key-required" placeholder, causing 401 errors on LiteLLM/harness (which require sk-* keys).

Workaround: Set api_key directly alongside api_key_env in each auxiliary task config:

auxiliary:
  vision:
    api_key: sk-<agent-key-from-vault>           # ← workaround (get via: infisical secrets get LITELLM_API_KEY --project=agents --env=production --plain)
    api_key_env: LITELLM_API_KEY
    base_url: http://192.168.68.116/litellm/v1
    model: gpu-vision
    provider: harness
  compression:
    api_key: sk-<agent-key-from-vault>           # ← workaround (same as above)
    api_key_env: LITELLM_API_KEY
    base_url: http://192.168.68.116/litellm/v1
    model: syslog-auto
    provider: harness

Affected agents: All Hermes agents with harness/LiteLLM provider and api_key_env in auxiliary configs (all 4 Hermes agents patched 2026-07-05).

Source location: agent/auxiliary_client.py line 5478