Admin can view but not modify tenant data without an audit trail
Why it matters
Platform admin routes that can write tenant data without logging are invisible to both the tenant and to compliance auditors. CWE-778 (Insufficient Logging) and OWASP A09 (Security Logging and Monitoring Failures) both apply; SOC 2 CC6.8 explicitly requires that privileged actions be logged with who, what, and when. The practical risk is that an insider threat — a rogue admin or compromised admin account — can modify or delete tenant data with no forensic trail, making incident response and regulatory defense impossible.
Severity rationale
Low because admin routes are access-controlled, but an unlogged admin write operation eliminates any forensic trail for insider-threat scenarios or compliance audits.
Remediation
Wrap every admin write route in a logging function that captures the actor, action, affected tenant, and resource before returning:
// src/lib/admin-audit.ts
async function adminUpdateUser(
adminId: string,
targetUserId: string,
changes: Partial<User>
) {
const updated = await db.user.update({
where: { id: targetUserId },
data: changes
})
await db.adminAuditLog.create({
data: {
adminUserId: adminId,
action: 'user.update',
targetUserId,
targetOrganizationId: updated.organizationId,
changes: JSON.stringify(changes), // redact sensitive fields like passwords
timestamp: new Date()
}
})
return updated
}
In src/app/api/admin/, require this wrapper pattern for every POST, PATCH, PUT, and DELETE handler. Read-only GET handlers do not require the same trail but should still be access-controlled.
Detection
-
ID:
admin-view-no-modify-without-trail -
Severity:
low -
What to look for: Examine admin or super-admin routes — routes accessible only to platform administrators (not tenant admins). Check whether admin actions that modify tenant data (editing records, overriding settings, impersonating users, deleting data on behalf of a tenant) are logged with sufficient detail to reconstruct what was done and by whom. Look for admin route handlers that perform write operations without any audit log creation.
-
Pass criteria: Enumerate all admin endpoints. At least 100% of admin write operations against tenant data must create an audit log entry that includes: the admin's user ID, the action taken, the tenant affected, the resource modified, and a timestamp. Admin view-only access (read operations) does not require the same logging, but read access to sensitive tenant data should at minimum be access-controlled.
-
Fail criteria: Admin write operations on tenant data execute without creating any audit trail. An admin can modify a tenant's data and there is no record of the action in any log or table.
-
Skip (N/A) when: No admin or super-admin routes are detected. Signal: no admin route group, no admin role check in any route handler, no "impersonate" or "act as tenant" functionality.
-
Detail on fail: Example:
"POST /api/admin/users/[id] can update any user's data but writes no audit log entry. No record is kept of admin modifications to tenant user accounts." -
Remediation: Wrap admin write operations with audit logging:
async function adminUpdateUser(adminId: string, targetUserId: string, changes: Partial<User>) { const updated = await db.user.update({ where: { id: targetUserId }, data: changes }) // Always log admin mutations await db.adminAuditLog.create({ data: { adminUserId: adminId, action: 'user.update', targetUserId, targetOrganizationId: updated.organizationId, changes: JSON.stringify(changes), // consider redacting sensitive fields timestamp: new Date() } }) return updated }
External references
- cwe · CWE-778 — Insufficient Logging
- owasp:2021 · A09 — Security Logging and Monitoring Failures
- nist:rev5 · AU-2 — Event Logging
- soc2:2017 · CC6.8 — Implements Controls to Prevent or Detect Unauthorized Access
Taxons
History
- 2026-04-18·v1.0.0·Initial import from saas-multi-tenancy·automated