Audit logs are tenant-scoped
Why it matters
Audit logs that are not scoped by tenant become a cross-tenant intelligence source: a tenant admin can query another organization's activity history, exposing who is active, what features they use, and what actions their users take. CWE-285 and NIST AU-9 both address this; SOC 2 CC7.2 requires that audit records be protected from unauthorized access. Beyond privacy, unscoped audit logs mean a legitimate tenant admin cannot be confident that the logs they see reflect only their organization's actions.
Severity rationale
Low because exploiting unscoped audit logs requires tenant-admin privileges, limiting the attacker surface, but exposure of another tenant's activity trail is a direct SOC 2 CC7.2 control failure.
Remediation
Add organizationId as a required field on every audit log write and as a mandatory filter on every read:
// Write — include tenant context on every event
await db.auditLog.create({
data: {
organizationId: session.user.organizationId,
userId: session.user.id,
action: 'document.deleted',
resourceId: docId,
createdAt: new Date()
}
})
// Read — always scope to the requesting tenant
const logs = await db.auditLog.findMany({
where: { organizationId: session.user.organizationId },
orderBy: { createdAt: 'desc' },
take: 100
})
If the audit log table was created without an organizationId column, add it via migration and backfill from the related resource's organizationId before removing the global-read path.
Detection
-
ID:
audit-logs-tenant-scoped -
Severity:
low -
What to look for: Examine audit log storage and retrieval — activity logs, event logs, admin action logs. Check whether audit log records include a tenant identifier and whether the audit log retrieval API scopes results to the requesting tenant. Look for patterns where audit logs are stored in a central table without tenant isolation, or where a tenant admin can query audit events from other tenants.
-
Pass criteria: Count all audit log write locations. Audit log records include at least 1 tenant identifier. The audit log retrieval API filters by the requesting user's tenant. A tenant admin can see their own tenant's logs but not other tenants' logs.
-
Fail criteria: Audit logs are stored without a tenant identifier. The audit log API returns results across all tenants without scoping to the requester's tenant.
-
Skip (N/A) when: No audit logging system is detected. Signal: no audit_log or activity_log table in the schema, no event logging code for user/admin actions.
-
Detail on fail: Example:
"GET /api/audit-logs in src/app/api/audit-logs/route.ts returns all audit log records with no organizationId filter. Any tenant admin can see activity from all tenants." -
Remediation: Add tenant scoping to audit log queries:
const logs = await db.auditLog.findMany({ where: { organizationId: session.user.organizationId }, orderBy: { createdAt: 'desc' }, take: 100 })
External references
- cwe · CWE-285 — Improper Authorization
- nist:rev5 · AU-9 — Protection of Audit Information
- soc2:2017 · CC7.2 — Monitors System Components
Taxons
History
- 2026-04-18·v1.0.0·Initial import from saas-multi-tenancy·automated