Audit logs that any authenticated user can query are a surveillance and privacy failure — they expose the financial activity of every customer to every employee who has a database login. PCI-DSS v4.0 Req-10.3.1 limits read access to audit log files to those with a job-related need — precisely the scope of this control. NIST 800-53 AU-9 requires that audit information is protected from unauthorized access, modification, and deletion. OWASP A01:2021 (Broken Access Control) covers this class directly — granting SELECT on transaction_logs to app_role without a compliance restriction means any developer querying the database has full visibility into the financial movements of all customers. CWE-285 (Improper Authorization) is the vulnerability. A disgruntled employee or compromised app credential can exfiltrate the complete transaction history of any user with a single query.
High because unrestricted read access to audit logs exposes the complete financial history of all users to any compromised or malicious credential that holds the application's standard database role.
Revoke SELECT from general roles and grant it only to a designated compliance role in db/migrations/:
REVOKE SELECT ON transaction_logs FROM app_role;
CREATE ROLE compliance_role;
GRANT SELECT ON transaction_logs TO compliance_role;
-- Row-level: users can only see their own rows
ALTER TABLE transaction_logs ENABLE ROW LEVEL SECURITY;
CREATE POLICY user_self_read ON transaction_logs
FOR SELECT TO app_role
USING (user_id = current_setting('app.current_user_id')::uuid);
In src/middleware/ or your route handlers, enforce the compliance role check before any audit log query:
app.get('/api/audit-logs', authenticate, requireRole('compliance'), async (req, res) => {
const logs = await db('transaction_logs').where({ user_id: req.query.userId });
res.json(logs);
});
ID: finserv-audit-trail.balance-reconciliation.access-control
Severity: high
What to look for: Enumerate all database roles that have SELECT permission on the audit log table. Count the total number of roles with read access and classify each as compliance-role or non-compliance-role. Quote the actual GRANT statements or RLS policies found. Examine all API endpoints that expose audit data and list all required role checks. Allowing any general authenticated user to read audit logs does not count as pass.
Pass criteria: Audit log tables have explicit read permissions granted only to at least 1 designated audit/compliance role (not general app roles). Count all API endpoints exposing audit data — at least 90% must require authentication with an audit/compliance role check. Report the ratio even on pass (e.g., "2 of 2 endpoints require compliance role").
Fail criteria: Audit log has unrestricted read access, or any general authenticated user can read audit logs, or fewer than 90% of audit endpoints enforce role restrictions, or no role-based access control is documented.
Skip (N/A) when: Never — audit log confidentiality is critical.
Detail on fail: "Audit logs are readable by all authenticated users — 3 of 3 roles have SELECT permission, 0 restricted to compliance." or "No role check on /api/audit-logs endpoint — 0 of 1 endpoints enforce role restriction.".
Remediation: Restrict audit log access to compliance roles (in db/migrations/ or src/middleware/):
-- Create compliance role
CREATE ROLE compliance_role;
-- Grant read-only access to audit logs
GRANT SELECT ON transaction_logs TO compliance_role;
REVOKE SELECT ON transaction_logs FROM app_role;
-- For user-specific reads, use RLS:
ALTER TABLE transaction_logs ENABLE ROW LEVEL SECURITY;
CREATE POLICY compliance_read ON transaction_logs
FOR SELECT
USING (current_user_role() = 'compliance_role');
In your API layer:
app.get('/api/audit-logs', authenticate, requireRole('compliance'), async (req, res) => {
const logs = await db.query('SELECT * FROM transaction_logs WHERE user_id = ?', [req.query.userId]);
res.json(logs);
});