Appearance
SecOps Least Privilege Audit — LPRIV-001, LPRIV-002, LPRIV-003
Audit of SecOps platform app registration permissions, managed identity RBAC, and PostgreSQL access. Completed April 2026 as part of the LPRIV least privilege workstream.
LPRIV-001 — SecOps App Registration Graph Permissions
App: cirius-findings-tracker (App ID: cc42af53-8eef-4705-a1d6-227c3313175d)
Date audited: April 15, 2026
The cirius-findings-tracker app registration is used by the SecOps Container App (ca-secops-prod) for Azure AD SSO (MSAL PKCE) and for group membership lookups that drive role authorization.
Graph Permissions
| Permission | Type | Feature / Code Path | Status |
|---|---|---|---|
User.Read | Delegated (Scope) | MSAL PKCE sign-in flow — reads the signed-in user's basic profile | ✅ Required |
GroupMember.Read.All | Application (Role) | RBAC — checks whether the signed-in user is a member of the SecOps Entra group (ffdf1dcb) to determine access level | ✅ Required |
User.Read.All | Application (Role) | Known-good rule attribution — resolves Entra object IDs to UPNs when displaying rule approvers in the UI | ✅ Required |
Finding: 3 permissions assigned (not 19+ as estimated at story creation). All 3 have clear, documented owners. No orphaned permissions detected.
Verdict: PASS — permissions are minimal and appropriate.
LPRIV-001b — SecOps Bot App Registration Graph Permissions
App: Cirius SecOps Bot (App ID: 7a3ea16d-a903-4252-9203-1159136a7eac)
This is a separate app registration used by the Teams bot only — not the SecOps web app.
| Permission | Type | Feature / Code Path | Status |
|---|---|---|---|
User.Read.All | Application (Role) | /policies and /accept bot commands — resolves Teams aadObjectId → Entra UPN via Graph GET /users/{id} | ✅ Required |
Verdict: PASS — single permission, clear owner.
LPRIV-002 — SecOps Managed Identity RBAC
Identity: id-secops-prod (Principal ID: a0d94b27-21ab-48d0-8d98-9b0fe915cb3d) Client ID: 3e2ed6a2-d2d7-4aec-a289-a6ea493ad93aResource group: rg-logging-logs
Date audited: April 15, 2026
RBAC Role Assignments
| Role | Scope Type | Resource | Feature | Status |
|---|---|---|---|---|
AcrPull | Resource | ciriusagentsprod (Container Registry) | Container App image pull at startup | ✅ Required |
Key Vault Secrets User | Resource | cirius-openai-kv-prod (Key Vault) | Runtime secret access (API keys, DB URL, bot tokens) | ✅ Required |
Findings:
- No
ContributororOwnerassignments detected. - Both roles are scoped to the specific resource, not resource group or subscription.
AcrPullis the minimum role for image pull — no push access.Key Vault Secrets Useris read-only (GET secret versions) — no set/delete/manage.
Verdict: PASS — RBAC posture is minimal and correctly scoped.
LPRIV-003 — SecOps PostgreSQL Access Role
Database: psql-secops-prod.postgres.database.azure.com (Azure PostgreSQL Flexible Server) Database name: secops
Date audited: April 15, 2026
Finding: Application Connects as Database Administrator
The SecOps Container App connects to psql-secops-prod using the administrator login (secopsadmin) — the same account set as administrator_login in Terraform. This is the highest-privilege database account.
Connection string (from Key Vault secops-db-url):
postgresql+asyncpg://secopsadmin:***@psql-secops-prod.postgres.database.azure.com/secops?ssl=requireRisk: If the Container App is compromised, an attacker has full database administrator access — including the ability to read all tables, drop tables, create new superuser accounts, or exfiltrate all data. The principle of least privilege requires the application to connect as a role with only the permissions it needs.
Minimum Required Permissions
Based on the SecOps application's SQLAlchemy models and API endpoints:
| Schema Object | Required Operations |
|---|---|
| All application tables (incidents, findings, known_good, changes, etc.) | SELECT, INSERT, UPDATE, DELETE |
public schema | USAGE, CREATE (for Alembic migrations) |
| Sequences (for auto-increment IDs) | USAGE, SELECT |
| No access needed | pg_catalog system tables beyond what implicit grants cover |
Remediation Required
Create a scoped secops_app PostgreSQL role and update the connection string. Rory's sign-off required before implementation — this change requires:
- Connecting to
psql-secops-prodassecopsadmin - Running
CREATE ROLE,GRANT,ALTER DEFAULT PRIVILEGES - Updating
secops-db-urlsecret in Key Vault with the new credentials - Restarting
ca-secops-prodto pick up the new connection string
Verdict: FAIL — application connects as database administrator. Remediation tracked in LPRIV-003.
Proposed Remediation SQL
sql
-- Create scoped application role
CREATE ROLE secops_app WITH LOGIN PASSWORD '<strong-random-password>';
-- Grant schema usage
GRANT USAGE, CREATE ON SCHEMA public TO secops_app;
-- Grant DML on all existing tables
GRANT SELECT, INSERT, UPDATE, DELETE ON ALL TABLES IN SCHEMA public TO secops_app;
-- Grant sequence usage (for auto-increment PKs)
GRANT USAGE, SELECT ON ALL SEQUENCES IN SCHEMA public TO secops_app;
-- Ensure future tables created by Alembic are accessible
ALTER DEFAULT PRIVILEGES IN SCHEMA public GRANT SELECT, INSERT, UPDATE, DELETE ON TABLES TO secops_app;
ALTER DEFAULT PRIVILEGES IN SCHEMA public GRANT USAGE, SELECT ON SEQUENCES TO secops_app;After running, update Key Vault secret secops-db-url with the new credentials and restart the Container App revision.
LPRIV-004 — Key Vault Access Policy Scope
Identity: id-secops-prod via Key Vault Secrets User on cirius-openai-kv-prod
Date audited: April 15, 2026
Built-in Role: Key Vault Secrets User
Azure RBAC role definition (Key Vault Secrets User) grants exactly two data actions:
| Data Action | Scope |
|---|---|
Microsoft.KeyVault/vaults/secrets/getSecret/action | Get secret value |
Microsoft.KeyVault/vaults/secrets/readMetadata/action | Read secret metadata |
No access to:
set/delete/purge/backup/restoreon secrets- Keys (encrypt, decrypt, sign, verify, wrap, unwrap)
- Certificates (get, list, import, create, delete)
Finding: The managed identity has read-only access to secrets only. No key, certificate, or write operations are permitted.
Verdict: PASS — Key Vault access is minimal and correctly scoped.
LPRIV-005 — Table Storage Access
Date audited: April 15, 2026
The SecOps security agents use Azure Table Storage (ciriussecuritymemprod, table SecurityMemory) for IOC deduplication and memory. The web app (ca-secops-prod) does not access Table Storage.
Two managed identities exist in the SecOps platform:
| Identity | Used By | Purpose |
|---|---|---|
id-secops-prod | ca-secops-prod Container App (web app) | Web app identity — KV secrets, ACR pull |
id-agents-prod | Container App Jobs (orchestrator, AWS, DDE) | Agent identity — LAW, Table Storage, Blob, OpenAI |
id-agents-prod RBAC Assignments (Agent Identity)
| Role | Scope | Feature |
|---|---|---|
Key Vault Secrets User | cirius-openai-kv-prod (resource) | Agent runtime secrets (API keys, tokens) |
Log Analytics Reader | cirius-logging-law-central (resource) | KQL queries across all security agents |
AcrPull | ciriusagentsprod (resource) | Container image pull for agent jobs |
Storage Table Data Contributor | ciriussecuritymemprod (storage account) | IOC memory table — SecurityMemory |
Storage Blob Data Contributor | ciriusarchiveprod (storage account) | Evidence archive uploads |
Cognitive Services OpenAI User | cirius-openai-prod (resource) | SecurityAgent / LLM triage |
Reader | Logging subscription | Azure resource discovery for agent inventory |
Security Reader | Logging subscription | Defender for Cloud findings |
Table Storage Finding
Storage Table Data Contributor on ciriussecuritymemprod is the most specific scope achievable in Azure RBAC — Azure does not support per-table role assignments. Since ciriussecuritymemprod is a dedicated storage account whose sole purpose is the SecurityMemory table, this is effectively table-scoped.
Storage Table Data Contributor is a data plane role. It grants entity-level operations only — it does NOT grant management plane operations like deleting the storage account or modifying its firewall rules.
Finding: id-secops-prod (web app) has no Table Storage access — not applicable. id-agents-prod has Storage Table Data Contributor on the dedicated ciriussecuritymemprod account — correctly scoped, not broad Storage Account Contributor.
Verdict: PASS for both identities — Table Storage access is minimal and correctly scoped.
Summary
| Story | Component | Result | Action |
|---|---|---|---|
| LPRIV-001 | cirius-findings-tracker Graph permissions | ✅ PASS — 3 permissions, all required | No action needed |
| LPRIV-001 | Cirius SecOps Bot Graph permissions | ✅ PASS — 1 permission, required | No action needed |
| LPRIV-002 | id-secops-prod managed identity RBAC | ✅ PASS — 2 roles, minimal scope | No action needed |
| LPRIV-003 | PostgreSQL access role | ❌ FAIL — connecting as DB admin | Create scoped secops_app role |
| LPRIV-004 | Key Vault access scope (id-secops-prod) | ✅ PASS — Key Vault Secrets User (read-only secrets) | No action needed |
| LPRIV-005 | Table Storage access | ✅ PASS — Storage Table Data Contributor on dedicated account | No action needed |
Document History
| Date | Change | Author |
|---|---|---|
| April 2026 | Added LPRIV-004 (Key Vault scope) and LPRIV-005 (Table Storage) — both PASS; discovered and documented id-agents-prod identity with 8 RBAC roles | Kobe |
| April 2026 | Initial audit — LPRIV-001, LPRIV-002, LPRIV-003 | Kobe |