Per-door ACL — least privilege per integration
When your MongoDB ETL scraper, your Postgres BI tool, your S3 archive job, and your HTTP app all connect to one database, how do you grant each integration the least privilege it needs — and nothing more? In a traditional database the answer is "you can't, really" — every connection from the same user shares one principal, and an attacker who compromises the read-only scraper can write if the shared user can.
Relata solves this by giving every wire door a distinct Cedar principal. A request arriving over the S3 door is principal s3-client; over pgwire it's pgwire-client; over MongoDB it's mongo-client. One Cedar policy can say "the S3 door may read Person but not write it" — and that holds regardless of which credentials the S3 client used, because the principal is the door, not the user.
Why this matters: turn "this read-only S3 scraper can't write even if compromised" into a one-liner, and make every audit-log row forensically attributable to the protocol that wrote it.
The 11 door principals
Every request is classified into exactly one door principal before the ACL evaluates:
| Door | Cedar principal |
|---|---|
| HTTP REST | http-client |
| gRPC | grpc-client |
| PostgreSQL wire (psql, psycopg2, pgvector) | pgwire-client |
| MongoDB wire | mongo-client |
| Redis RESP | redis-client |
| Neo4j HTTP (Cypher) | neo4j-client |
| Bolt | bolt-client |
| ClickHouse HTTP / native | clickhouse-client |
| S3-compatible | s3-client |
| Arrow Flight | flight-client |
MCP (/mcp) | mcp-client |
| Server-internal | system |
The door principal is independent of the user principal (the bearer token's identity) and the session principal — Cedar sees all three, so a policy can express "the S3 door acting for user U in tenant T."
Writing per-door policies
The door principal is exposed in Cedar as principal == User::"<door>-client". Combine with the user, action, and resource as usual:
// The S3 door may read Person and S3Object but never write Person.
permit(
principal == User::"s3-client",
action == Action::"read",
resource in Resource::"Person"
);
permit(
principal == User::"s3-client",
action == Action::"read",
resource in Resource::"S3Object"
);
// No permit clause for s3-client + Action::"write" + Person → denied by default.// pgwire gets full Person read/write — your BI tool's psql connection.
permit(
principal == User::"pgwire-client",
action in [Action::"read", Action::"write"],
resource in Resource::"Person"
);// The Mongo door is write-only (ingest only, no exfiltration).
permit(
principal == User::"mongo-client",
action == Action::"write",
resource in Resource::"MongoDocument"
);Deny-wins: an explicit forbid overrides any permit, so a compliance lock-down is one line:
// No door may read the `ssn` column on Person, regardless of who asks.
forbid(
principal,
action == Action::"read",
resource == Resource::"Person.ssn"
);Faster than Cedar — the env-var grant shorthand
For common cases, skip the policy file entirely and use the RELATA_ACL_GRANT env var (now targets ALL_DOOR_ROLES):
# Person: read for every door, write only for pgwire + http
RELATA_ACL_GRANT="Person:read+write" RELATA_ACL_GRANT_PGWIRE="Person:write" \
relata serveThis compiles into Cedar at startup; you can mix and match with hand-written policy files.
Audit attribution — the forensic payoff
Every governed write/flip carries the door principal in the audit log as the purpose field — so a later investigation can answer "did this row come in over Mongo, S3, or HTTP?":
# Every row the S3 door touched:
curl 'http://127.0.0.1:9090/audit/entries?purpose=s3-client&limit=50' \
-H 'Authorization: Bearer <token>'-- Which doors have written to Person in the last 24h?
SELECT purpose AS door, COUNT(*) AS writes
FROM _audit
WHERE resource_type = 'Person'
AND system_at > now() - INTERVAL '24' hours
GROUP BY purpose;This is tested in production paths (crates/relata-cli/tests/serve_hardening.rs — the s3-client door role is asserted to flow through governed_get into the audit log on every S3 door read).
Tips & takeaways
- Default to least privilege per door. A new integration gets a new door principal with
readonly; escalate towriteonly when the integration needs it. This is the single biggest containment win for the "compromised scraper" threat. - The door principal is the first factor, not the only one. Combine with user principal (who) and tenant (where) for full ABAC —
principal == User::"s3-client" && resource.tenant == "org-acme". - Use
forbidfor compliance lock-downs. Cell-levelforbidonPerson.ssnsurvives any futurepermitsomeone adds — deny-wins is your safety net. - Audit by door to spot anomalies. "Why is the Redis door writing to
Person?" is a one-query check once every door's writes are tagged. - Door principals +
RELATA_TENANCY_MODE=multi. In multi-tenant mode the door principal still applies within each tenant's scope — the per-tenant policy can override per-door defaults.
See also
- Auth & Security — bearer tokens, OIDC, mTLS, the full ACL model
- Governance — Cedar-inspired ABAC, PURPOSE tracking, cell masking
- Compatibility & Doors — the 13 wire doors this principal set covers
- Deploying Protocol Doors — door bind/publish/enable wiring
- Audit & Provenance — the audit chain that records every door's writes