Repository navigation
feat(llc): company-scoped secrets — versioned, encrypted, RBAC (GH#8217) - #8467
Merged
Merged
Conversation
…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>
Contributor
|
| 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_urlTypeScript:
import config from '@/config/ssot-config'
// Use: config.vm.main, config.port.backend, config.backendUrl📖 See SSOT_CONFIG_GUIDE.md for documentation.
…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>
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Summary
llc/models/secret.py): UUID PK,(company_id, name)unique constraint,valueas encryptedLargeBinary,versionauto-increment,revoked_atsoft-delete20260523_028(chains from20260523_027approvals)llc/services/secret.py):set/get/revoke/listwith Fernet encryption; HKDF-SHA256 derives a per-company key fromLLC_SECRET_MASTER_KEYenv var — cross-company decryption is cryptographically impossible;SecretNotFoundfor missing/revokedllc/api/secrets.py):GET /llc/secrets/{company_id}(names+versions only, never values),POSTset,GET /{name}(decrypted value),DELETE /{name}revoke;X-Agent-Company-Idheader enforces per-company access controlTest plan
py_compilepasses on all 7 changed files ✅ (verified in PR)InvalidTokenwhen decrypted with company-B key ✅ (standalone verified)pytest llc/tests/test_secrets.pyin full dev env after mergePOST /llc/secrets/{company_id}→ 201,GET /llc/secrets/{company_id}/{name}→ plaintext,DELETE→ 204, secondGET→ 404X-Agent-Company-Idheader → 403Closes GH#8217
Thinking Path
Implemented versioned company-scoped secrets as a new LLC module. Core design choices:
company_idasinfoparameter derives per-company Fernet keys from a single master key — cross-company isolation is cryptographic, not just access-control.set()(version resets to 1); this matches the "rotated credentials" use case.What Changed
llc/models/secret.py— newLLCSecretSQLAlchemy model with UniqueConstraint + indexllc/models/__init__.py— exportsLLCSecretllc/services/secret.py—SecretServicewith HKDF key derivation, set/get/revoke/list methodsllc/api/secrets.py— 4 FastAPI routes under/llc/secrets/{company_id}llc/api/__init__.py— wiressecrets_routerinto LLC API routerllc/tests/test_secrets.py— 11 unit testsmigrations/versions/20260523_028_llc_secrets.py— Alembic migration (revision=028, down=027)Verification
LLCServiceBase.activity_logoptional DI slot confirmed — guardif self.activity_log:correct per GH#8216 designInvalidTokenraised when ciphertext from company-A is decrypted with company-B keysecrets_routerimported and included inllc/api/__init__.py— new module has a production caller (wiring check passes)Model Used
claude-sonnet-4-6
🤖 Generated with Claude Code