Security
Last updated: 2026-05-05
This page is for two readers. An F100 procurement reader following a sales-link to understand what Mycelium ships for logical access control. And a security researcher looking to report a vulnerability. Both are below.
Part A. Access control inside your tenant (Phase 4 RBAC)
Mycelium ships folder-glob role-based access control inside every tenant. Each JWT carries a roles claim. Every vault-touching route resolves the role-set against the target path before reading or writing. Denials produce structured audit-log entries with the role-set, the target path, and the actor identity.
This is the first deliverable on the SOC 2 CC6.1 control path. Audit window is targeted 2027. We are not SOC 2 certified yet. The mapping below is what an auditor would read at quarter-end against the AICPA Trust Services Criteria.
A1. Default role library (auto-seeded on tenant creation)
Every tenant created on the Mycelium runtime hydrates with this role library. The operator can upsert, edit, or delete roles via the admin API after creation. Glob semantics follow the gitignore convention: ** matches zero or more path segments, * matches any number of characters within a single segment.
| Role | Read globs | Write globs | Purpose |
|---|---|---|---|
tenant_admin | ** | ** | Full access. Default fallback for legacy JWTs and webhook sources without a natural-owner role. |
executive | ** | ** | Full access. Distinguishable from tenant_admin in audit. |
hr | External Inputs/Workday/**, External Inputs/Microsoft 365/users/**, External Inputs/Slack/hr-*/** | External Inputs/Workday/** | Workforce data. |
legal | External Inputs/Confluence/legal/**, External Inputs/DocuSign/**, External Inputs/Slack/legal-*/** | External Inputs/Confluence/legal/** | Legal and contracts. |
gtm | External Inputs/Salesforce/**, External Inputs/HubSpot/**, External Inputs/Slack/sales-*/**, External Inputs/Gmail/** | External Inputs/Salesforce/**, External Inputs/HubSpot/** | Revenue and customers. |
engineering | External Inputs/GitHub/**, External Inputs/Linear/**, External Inputs/GitLab/**, External Inputs/Slack/eng-*/** | External Inputs/GitHub/**, External Inputs/Linear/**, External Inputs/GitLab/** | Code and tickets. |
it | External Inputs/ServiceNow/**, External Inputs/Jira/**, External Inputs/Box/it/** | External Inputs/ServiceNow/**, External Inputs/Jira/** | IT service management. |
finance | External Inputs/SAP/**, External Inputs/Workday/expense_report/**, External Inputs/Workday/journal_entry/**, External Inputs/Snowflake/finance/** | External Inputs/SAP/** | Finance and accounting. |
data | External Inputs/Snowflake/**, External Inputs/Databricks/** | External Inputs/Snowflake/**, External Inputs/Databricks/** | Data team. |
viewer | ** | (none) | Read-only audit role. |
A2. Admin API surface
All admin endpoints require the admin tenant token. Role library upsert rejects with HTTP 400 if the proposed library would exceed the per-tenant byte cap (10 KB serialized JSON, covers ~50 realistic roles), or if the proposed inheritance list introduces a cycle. Cycle detection runs at save time, so the operator sees a clean 400 instead of a 500 on first read.
GET /admin/roles/{tenant_id}: list role libraryPOST /admin/roles/{tenant_id}: create or replace one roleDELETE /admin/roles/{tenant_id}/{role_name}: remove one role (HTTP 409 on protected role)GET /admin/audit/denials/{tenant_id}: tail of recent denial audit linesPOST /admin/jwt/{tenant_id}: mint a role-scoped JWT via HTTP
Delete rejects on protected roles. tenant_admin is the fallback for legacy JWTs without a roles claim AND the default-source-role for webhook sources without a natural-owner role. Deleting it would leave the tenant with no role that matches the legacy fallback or the webhook fallback, bricking the install. Operators who want a different fallback identity upsert a replacement role with the same **-globs under a different name first.
A3. Webhook denial behavior
Each webhook source maps to a default role for ingestion. If the role’s write-globs no longer cover the source’s folder (because the operator deleted or scoped down the role), the receiver still returns HTTP 200 (webhook contract) but emits one webhook.<source>.denied audit line with status: "denied", the roles set, and the target path. The event payload is written to vaults/<tenant_id>/.dead-letter/<source>/<event_id>.json with a stable event id so redeliveries deduplicate.
Operators replay after fixing the role library via POST /admin/webhooks/replay/{tenant_id}/{event_id}. If the replay still trips RBAC, the dead-letter file is left in place. Only an all-OK replay unlinks it.
A4. Backward compatibility
JWTs minted before Phase 4 do not carry a roles claim. The auth layer detects an absent roles key and falls back to ["tenant_admin"], so existing tokens keep working as full-access until the operator re-mints with explicit roles. Tenants persisted before Phase 4 hydrate with roles=[]; the cold-start auth fallback covers them too.
A token that DOES carry a roles claim with an empty list is a deliberate deny-all token, not a legacy fallback. The auth layer returns [] and every RBAC gate denies. This is the explicit revocation affordance for first-party callers. Malformed claim shapes (non-list values) are treated as legacy and fall back to tenant_admin, since a malformed claim is more often a deserialization issue from a buggy proxy than a deliberate revocation, and failing closed there would brick legitimate callers.
A5. Operational caveats (the part most pages hide)
What is NOT done is the trust signal. Three caveats operators evaluating Mycelium should know about Phase 4:
- Single-worker assumption. The tenant registry cache lives in-process. Running uvicorn with
--workers > 1and mutating roles via the admin API leaves other workers serving stale role data until they reload. For single-worker deployments (the Phase 4 default), this is invisible. Multi-worker deployments need a Redis-backed tenant cache OR a fan-out invalidation hook. Phase 5 scope. - Per-tenant per-source default-role override is not yet exposed. The mapping from webhook source to default ingestion role is hardcoded in
src/rbac.py::_SOURCE_DEFAULT_ROLE. Operators who want to demote or upgrade a source’s default role per-tenant edit the role’s globs to scope coverage, not the source-mapping itself. Per-tenant override of the source-mapping is a Phase 5 admin endpoint. - Audit-log denials view truncates.
GET /admin/audit/denials/{tenant_id}reads the last ~10K lines ofaudit-log.jsonland filters to denials for that tenant. Tenants with very high denial volume see only the most recent slice. Phase 5 adds cursor-based pagination.
A6. SOC 2 CC6.1 mapping
CC6.1 of the AICPA Trust Services Criteria requires logical access security software, infrastructure, and architectures over protected information assets. Phase 4 deliverables map as follows. We are not SOC 2 certified yet. Audit window is targeted 2027.
| CC6.1 requirement | Phase 4 deliverable |
|---|---|
| Verify identities | JWT sub + tenant_id claims (existing, untouched). |
| Enforce role-based permissions | New roles JWT claim + assert_can_read / assert_can_write gates on every vault-touching route. |
| Rule sets to limit logical access | Role.read_globs / Role.write_globs folder-glob ACLs on each Role. |
| Separation of incompatible functions | Default role library auto-seeds 10 roles aligned to F100 functional teams. |
| Periodic review of access roles | Admin endpoints for list, upsert, delete, and denials tail (see A2 above). |
| Auditable evidence chain | Every denial emits one structured JSONL audit line carrying roles, target_path, user_id, tenant_id, status: "denied". SSE broadcaster fanout is unchanged; subscribers can filter on status. |
The audit log at audit-log.jsonl plus the role library at tenants[*].roles is the evidence chain a CC6.1 auditor reads at quarter-end.
A7. Deferred to Phase 5+
- Per-entry frontmatter
visibility:enforcement (folder-glob ACLs first). - Per-tenant per-source default-role overrides (operators tighten beyond the seeded library).
- Frontend admin UI for role management (JSON endpoints + curl + the CLI cover Phase 4).
- Multi-worker tenant cache (Redis-backed or fan-out invalidation).
- Cursor-based pagination on the denials view.
Part B. Vulnerability disclosure
Mycelium runs a coordinated disclosure program. Researchers who report vulnerabilities in good faith and in line with the rules below are acknowledged in our public CHANGELOG and credited in any related security advisory.
B1. Reporting
Send vulnerability reports to security@mycelium-ai.co. Encrypted submissions are welcome; PGP key fingerprint available on request.
Please include:
- A clear, reproducible description of the issue
- The affected URL, endpoint, version, or component
- Proof-of-concept output or steps that demonstrate the impact
- Your preferred name for credit (or a request to remain anonymous)
B2. Response timeline
- Acknowledgement within one business day
- Initial triage and severity assessment within five business days
- Remediation plan and target date within fifteen business days
- Public disclosure window: ninety days from initial report, or sooner if a fix has shipped and the customer base has been notified, whichever comes first
B3. Scope
In scope:
- mycelium-ai.co and all subdomains operated by Mycelium
- The open-source memory layer at github.com/adelaidasofia/ai-brain-starter
- The productized runtime endpoints exposed to customers under engagement letter (subject to Section B4)
Out of scope:
- Third-party services (Vercel infrastructure, Resend, PostHog, Anthropic, OpenAI, etc.). Please report to the vendor directly.
- Social engineering of Mycelium employees, contractors, or pilot customers
- Physical attacks on Mycelium offices or hardware
- Denial-of-service or rate-limit testing without prior written authorization
B4. Safe harbor
Research conducted in line with this policy is considered authorized and Mycelium will not pursue civil action or report it to law enforcement. Researchers must avoid accessing or modifying customer data, must stop testing immediately on request, and must give Mycelium reasonable time to remediate before public disclosure.
B5. Recognition
Researchers who follow this policy and submit valid vulnerability reports are acknowledged in the public CHANGELOG and (with the researcher’s consent) in any published security advisory. Cash bounties are not currently offered; we expect to launch a paid program after the SOC 2 Type II audit completes.
B6. Privacy and data subject inquiries
For privacy questions or data-subject requests under GDPR, UK GDPR, CCPA, or LGPD, write to privacy@mycelium-ai.co.
Mycelium · founded 2026