Skip to content

feat(llc): company-scoped secrets — versioned, encrypted, RBAC (GH#8217) - #8467

Merged
mrveiss merged 3 commits into
Dev_new_guifrom
issue-8217
May 23, 2026
Merged

mrveiss merged 3 commits into
Dev_new_guifrom
issue-8217

Conversation

@mrveiss

@mrveiss mrveiss commented May 23, 2026 •

Copy link
Copy Markdown
Owner

Summary

  • LLCSecret model (llc/models/secret.py): UUID PK, (company_id, name) unique constraint, value as encrypted LargeBinary, version auto-increment, revoked_at soft-delete
  • Alembic migration 20260523_028 (chains from 20260523_027 approvals)
  • SecretService (llc/services/secret.py): set/get/revoke/list with Fernet encryption; HKDF-SHA256 derives a per-company key from LLC_SECRET_MASTER_KEY env var — cross-company decryption is cryptographically impossible; SecretNotFound for missing/revoked
  • API routes (llc/api/secrets.py): GET /llc/secrets/{company_id} (names+versions only, never values), POST set, GET /{name} (decrypted value), DELETE /{name} revoke; X-Agent-Company-Id header enforces per-company access control
  • 11 unit tests covering round-trip encrypt/decrypt, version increment, revoked-secret reactivation, cross-company isolation, missing master key error, list-excludes-values invariant

Test plan

  • py_compile passes on all 7 changed files ✅ (verified in PR)
  • Encryption isolation: company-A ciphertext raises InvalidToken when decrypted with company-B key ✅ (standalone verified)
  • Run pytest llc/tests/test_secrets.py in full dev env after merge
  • Smoke: POST /llc/secrets/{company_id} → 201, GET /llc/secrets/{company_id}/{name} → plaintext, DELETE → 204, second GET → 404
  • Verify wrong X-Agent-Company-Id header → 403

Closes GH#8217

Thinking Path

Implemented versioned company-scoped secrets as a new LLC module. Core design choices:

  1. HKDF-SHA256 with company_id as info parameter derives per-company Fernet keys from a single master key — cross-company isolation is cryptographic, not just access-control.
  2. Soft-delete (revoked_at) over hard-delete preserves audit trail in activity log.
  3. Revoked secrets can be reactivated by set() (version resets to 1); this matches the "rotated credentials" use case.
  4. List endpoint never returns values — plaintext is only returned on explicit named GET to minimize exposure surface.

What Changed

  • llc/models/secret.py — new LLCSecret SQLAlchemy model with UniqueConstraint + index
  • llc/models/__init__.py — exports LLCSecret
  • llc/services/secret.py — SecretService with HKDF key derivation, set/get/revoke/list methods
  • llc/api/secrets.py — 4 FastAPI routes under /llc/secrets/{company_id}
  • llc/api/__init__.py — wires secrets_router into LLC API router
  • llc/tests/test_secrets.py — 11 unit tests
  • migrations/versions/20260523_028_llc_secrets.py — Alembic migration (revision=028, down=027)

Verification

  • Migration chain verified: 022→023→024→025→026→027→028 (complete, no gaps)
  • LLCServiceBase.activity_log optional DI slot confirmed — guard if self.activity_log: correct per GH#8216 design
  • Cross-company encryption isolation confirmed: InvalidToken raised when ciphertext from company-A is decrypted with company-B key
  • secrets_router imported and included in llc/api/__init__.py — new module has a production caller (wiring check passes)

Model Used

claude-sonnet-4-6

🤖 Generated with Claude Code

…rol (GH#8217)

- LLCSecret SQLAlchemy model: id, company_id, name (unique per co.), encrypted
  value (LargeBinary), version (auto-increments on update), created_by_agent_id,
  created_at, updated_at, revoked_at (soft-delete).
- HKDF-SHA256 key derivation: per-company Fernet key from LLC_SECRET_MASTER_KEY
  env var; cross-company decryption is cryptographically impossible.
- Alembic migration 20260523_027 (down_revision=20260523_026).
- SecretService extending LLCServiceBase: set/get/revoke/list; SecretNotFound
  raised for missing or revoked secrets; activity_log wired when injected.
- API routes /llc/secrets/{company_id}: list (no values), POST set, GET /{name}
  (decrypted value on explicit request only), DELETE /{name} revoke.
  X-Agent-Company-Id header enforces per-company access control.
- 11 unit tests covering encrypt/decrypt round-trips, version increment,
  revoked-secret reactivation, cross-company isolation, missing-key error.
- Wired into llc.api and llc.models __init__.py.

Co-Authored-By: Paperclip <noreply@paperclip.ing>
@github-actions

github-actions Bot commented May 23, 2026 •

Copy link
Copy Markdown
Contributor

⚠️ SSOT Configuration Compliance: Violations Found

Metric Count
Total Violations 7
SSOT Violations (high priority) 7
Other Violations 0

⚠️ 7 values have SSOT config equivalents!

These should be replaced with SSOT config imports:

Python:

from src.config.ssot_config import config
# Use: config.vm.main, config.port.backend, config.backend_url

TypeScript:

import config from '@/config/ssot-config'
// Use: config.vm.main, config.port.backend, config.backendUrl

📖 See SSOT_CONFIG_GUIDE.md for documentation.

mrveiss and others added 2 commits May 23, 2026 14:25
…new_gui

028 (approvals, PR #8458) merged into Dev_new_gui after this branch was cut.
Secrets was using revision=027/down=026 which collides with approvals.
Linear chain: 025→026(activity_log)→027(approvals)→028(secrets).

Co-Authored-By: Paperclip <noreply@paperclip.ing>
mrveiss added a commit that referenced this pull request May 23, 2026
…_new_gui

GH#8218 branch was cut before approvals (027) and secrets (028) merged.
Migration renamed 026→029, chains off secrets (028).
Conflict in llc/api/__init__.py resolved: includes both agent_api/api_keys
(GH#8218) and approvals (Dev_new_gui) routers.
PR #8468 must merge after PR #8467 (secrets, 028).

Co-Authored-By: Paperclip <noreply@paperclip.ing>
@mrveiss
mrveiss merged commit 2af26f1 into Dev_new_gui May 23, 2026
7 of 14 checks passed
@mrveiss
mrveiss deleted the issue-8217 branch May 23, 2026 11:29
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant