Skip to content

feat(audit): complete audit log system#152

Merged
BillChirico merged 18 commits intomainfrom
feat/audit-log
Mar 1, 2026
Merged

feat(audit): complete audit log system#152
BillChirico merged 18 commits intomainfrom
feat/audit-log

Conversation

@BillChirico
Copy link
Collaborator

Audit Log System

Complete implementation of audit logging for all admin actions in the web dashboard with attribution.

What's Included

  1. Migration (015_audit_logs.cjs): Creates audit_logs table with guild_id, user_id, action, target_type, target_id, details (jsonb), ip_address, created_at. Indexed on (guild_id, created_at DESC).

  2. Audit Middleware (src/api/middleware/auditLog.js): Non-blocking Express middleware that intercepts POST/PUT/PATCH/DELETE on authenticated routes. Captures user, action, target, IP, and request body. Computes before/after config diffs on config updates. Fire-and-forget DB insert.

  3. API Route (src/api/routes/auditLog.js): GET /api/v1/guilds/:id/audit-log — paginated (limit/offset), filterable by action, userId, startDate, endDate. Rate-limited, guild admin auth required.

  4. API Router Integration: Mounted audit log route and middleware in src/api/index.js.

  5. Dashboard Page (web/src/app/dashboard/audit-log/page.tsx): Searchable table with action/user/date filters, expandable detail rows, and pagination.

  6. Dashboard API Proxy (web/src/app/api/guilds/[guildId]/audit-log/route.ts): Next.js API route proxying to bot API.

  7. Sidebar Navigation: Added "Audit Log" entry with ClipboardList icon.

  8. Config: Added auditLog: { enabled: true, retentionDays: 90 } to config.json. Added 'auditLog' to SAFE_CONFIG_KEYS.

  9. Retention Cleanup: Integrated into scheduler — purges entries older than retentionDays every 6 hours (360th tick).

  10. Tests: 29 tests covering API route (auth, pagination, filtering, error handling) and middleware (action derivation, config diff, non-blocking behavior, enable/disable).

Test Results

  • 127 test files, 2674 tests passed (0 failed, 1 skipped pre-existing)
  • All lint checks pass (biome)

Closes #123

Copilot AI review requested due to automatic review settings February 28, 2026 21:41
@claude
Copy link

claude bot commented Feb 28, 2026

Claude finished @BillChirico's task in 2m 37s —— View job


Reviewing PR #152 — Audit Log System

  • Read AGENTS.md and project conventions
  • Review all 17 changed files
  • Post inline comments for issues found
  • Post top-level summary with verdict

Found 3 warnings, 1 nitpick. Review submitted with request-changes.

  1. 🟡 getConfig() without guild ID for enabled check (src/api/middleware/auditLog.js:154)
  2. 🟡 No type validation on req.query.action/req.query.userId — can be arrays (src/api/routes/auditLog.js:54-63)
  3. 🟡 AGENTS.md and README.md not updated with new files/tables/config
  4. 🔵 Comment examples mismatch actual action format (src/api/middleware/auditLog.js:27-29)

@coderabbitai
Copy link
Contributor

coderabbitai bot commented Feb 28, 2026

Warning

Rate limit exceeded

@BillChirico has exceeded the limit for the number of commits that can be reviewed per hour. Please wait 5 minutes and 53 seconds before requesting another review.

⌛ How to resolve this issue?

After the wait time has elapsed, a review can be triggered using the @coderabbitai review command as a PR comment. Alternatively, push new commits to this PR.

We recommend that you space out your commits to avoid hitting the rate limit.

🚦 How do rate limits work?

CodeRabbit enforces hourly rate limits for each developer per organization.

Our paid plans have higher rate limits than the trial, open-source and free plans. In all cases, we re-allow further reviews after a brief timeout.

Please see our FAQ for further information.

📥 Commits

Reviewing files that changed from the base of the PR and between 8f211c8 and d2ab87f.

📒 Files selected for processing (17)
  • AGENTS.md
  • README.md
  • config.json
  • migrations/015_audit_logs.cjs
  • src/api/index.js
  • src/api/middleware/auditLog.js
  • src/api/routes/auditLog.js
  • src/api/utils/configAllowlist.js
  • src/api/utils/configValidation.js
  • src/modules/scheduler.js
  • tests/api/middleware/auditLog.test.js
  • tests/api/routes/auditLog.test.js
  • tests/api/routes/guilds.test.js
  • tests/api/utils/configValidation.test.js
  • web/src/app/api/guilds/[guildId]/audit-log/route.ts
  • web/src/app/dashboard/audit-log/page.tsx
  • web/src/components/layout/sidebar.tsx
📝 Walkthrough

Walkthrough

Implements an audit logging system for tracking admin actions, including a database migration, Express middleware to capture mutating requests, an API endpoint with filtering, a web dashboard page, and automatic retention policy management.

Changes

Cohort / File(s) Summary
Configuration & Database
config.json, migrations/015_audit_logs.cjs
Added audit log config fields (auditLog.enabled, auditLog.retentionDays, ai.threadMode); created new audit_logs table with indexing on (guild_id, created_at DESC).
Backend API Integration
src/api/index.js, src/api/middleware/auditLog.js, src/api/routes/auditLog.js
Introduced audit log middleware to intercept and log mutating requests (POST/PUT/PATCH/DELETE) with action derivation, config diff computation, and non-blocking DB insertion; added paginated GET endpoint with filtering by action, userId, and date range.
Backend Services
src/api/utils/configAllowlist.js, src/modules/scheduler.js
Extended SAFE_CONFIG_KEYS to include 'auditLog'; added periodic purge task for expired audit logs (6-hour intervals, default 90-day retention).
Backend Tests
tests/api/middleware/auditLog.test.js, tests/api/routes/auditLog.test.js
Added comprehensive test coverage for middleware action derivation and config diffing; validated route authentication, pagination, filtering, and error handling.
Web Frontend
web/src/app/api/guilds/[guildId]/audit-log/route.ts, web/src/app/dashboard/audit-log/page.tsx, web/src/components/layout/sidebar.tsx
Created Next.js proxy route to forward audit log requests to bot API; added client-side dashboard page with paginated table, filters, expandable details, and user search with debouncing; added sidebar navigation link.

Possibly related PRs

  • PR #104: Modifies src/modules/scheduler.js to add periodic maintenance tasks, similar to the purge audit logs task added here.
  • PR #108: Extends src/api/utils/configAllowlist.js with new config keys, addressing the same allowlist pattern used for audit log config exposure.
  • PR #60: Establishes the Web Dashboard infrastructure; this PR extends it with audit log UI and API proxy routes.
🚥 Pre-merge checks | ✅ 4 | ❌ 1

❌ Failed checks (1 warning)

Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 33.33% which is insufficient. The required threshold is 80.00%. Write docstrings for the functions missing them to satisfy the coverage threshold.
✅ Passed checks (4 passed)
Check name Status Explanation
Title check ✅ Passed The title 'feat(audit): complete audit log system' clearly and specifically describes the main change: implementing a complete audit logging system.
Description check ✅ Passed The description is comprehensive and directly related to the changeset, detailing all components of the audit log system including migrations, middleware, API routes, dashboard UI, config changes, and tests.
Linked Issues check ✅ Passed The PR fully implements all coding requirements from issue #123: migration with audit_logs table, Express middleware for mutating requests, GET /api/v1/guilds/:id/audit-log endpoint with pagination/filtering, dashboard page, sidebar navigation, config diff logging, retention policy, and comprehensive tests.
Out of Scope Changes check ✅ Passed All changes align with the linked issue #123 objectives. Modifications to config.json, configAllowlist.js, scheduler.js, sidebar.tsx, and test files directly support the audit log feature and do not introduce unrelated functionality.

✏️ Tip: You can configure your own custom pre-merge checks in the settings.

✨ Finishing Touches
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Post copyable unit tests in a comment
  • Commit unit tests in branch feat/audit-log

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands and usage tips.

Copy link

@claude claude bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Review Summary

2 critical, 3 warnings found.

🔴 Critical

  1. Audit middleware is dead code (src/api/index.js:62): Placed after all route handlers, so it never executes. Express stops processing layers once a route sends a response. Must be moved before the route mounts.

  2. Sensitive data logged in plain text (src/api/middleware/auditLog.js:176-178): Raw req.body is stored in the audit log. Config updates containing API keys (triage.classifyApiKey, triage.respondApiKey) are stored unmasked.

🟡 Warning

  1. Config diff includes unmasked secrets (src/api/middleware/auditLog.js:182-185): computeConfigDiff operates on full config including sensitive fields. Use maskSensitiveFields() before diffing.

  2. React Fragment missing key (web/src/app/dashboard/audit-log/page.tsx:361): Shorthand <> in .map() doesn't support keys. Use <Fragment key={entry.id}>.

  3. Documentation not updated (migrations/015_audit_logs.cjs): AGENTS.md Key Files table, Database Tables section, and README config reference need updating per project conventions.


Prompt to fix all issues
Fix the following issues in the volvox-bot repo on branch feat/audit-log:

1. FILE: src/api/index.js
   ISSUE: The audit log middleware on line 62 is placed AFTER all route handlers, making it dead code.
   FIX: Move `router.use(auditLogMiddleware());` to BEFORE the first authenticated route mount (line 33, before the `/config` route). Keep it after the public routes (health, community, auth). Remove the old placement at line 60-62 and its comment.

2. FILE: src/api/middleware/auditLog.js
   ISSUE: Lines 176-178 store raw req.body in audit log, which can include sensitive API keys.
   FIX: Import `SENSITIVE_FIELDS` and `MASK` from `../utils/configAllowlist.js`. Before assigning `details.body = req.body`, deep-clone the body and mask any fields matching SENSITIVE_FIELDS paths. Use the same masking logic as `maskSensitiveFields()` in configAllowlist.js.

3. FILE: src/api/middleware/auditLog.js
   ISSUE: Lines 182-185 compute config diff on unmasked config objects, leaking sensitive field values.
   FIX: Import `maskSensitiveFields` from `../utils/configAllowlist.js`. Apply it to both beforeConfig and afterConfig before calling `computeConfigDiff()`:
   `const diff = computeConfigDiff(maskSensitiveFields(beforeConfig), maskSensitiveFields(afterConfig));`
   Also apply it to the beforeConfig capture on line 162: `beforeConfig = maskSensitiveFields(structuredClone(config));`

4. FILE: web/src/app/dashboard/audit-log/page.tsx
   ISSUE: Line 361 uses shorthand Fragment `<>` inside `.map()` which doesn't support keys. React will warn.
   FIX: Add `import { Fragment } from 'react';` to the imports (line 5). Replace `<>` on line 361 with `<Fragment key={entry.id}>` and `</>` on line 406 with `</Fragment>`. Remove the `key={entry.id}` from the inner TableRow on line 362.

5. FILES: AGENTS.md, README.md
   ISSUE: New files and database table not documented.
   FIX: In AGENTS.md, add to Key Files table:
   - `src/api/middleware/auditLog.js` | Audit log middleware — intercepts mutating requests, records non-blocking audit entries with config diffs
   - `src/api/routes/auditLog.js` | Audit log API route — paginated, filterable audit log retrieval
   - `web/src/app/dashboard/audit-log/page.tsx` | Audit log dashboard page — searchable table with filters and expandable details
   - `web/src/app/api/guilds/[guildId]/audit-log/route.ts` | Next.js API route — proxies audit log requests to bot API
   - `migrations/015_audit_logs.cjs` | Migration — creates `audit_logs` table
   Add to Database Tables section:
   - `audit_logs` | Admin action audit trail — guild_id, user_id, action, target, details (JSONB), IP, timestamp. Indexed on (guild_id, created_at DESC)
   In README.md, add auditLog to the config reference section with `enabled` (boolean) and `retentionDays` (number, default 90).

Copy link
Contributor

Copilot AI left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

Implements an end-to-end audit logging feature for admin actions in the bot API and surfaces it in the web dashboard (API endpoint + UI page), including retention cleanup and configuration toggles.

Changes:

  • Adds audit_logs persistence (migration), a read API (GET /guilds/:id/audit-log), and a Next.js proxy route for the dashboard.
  • Introduces an Express audit logging middleware intended to record mutating admin requests, plus scheduler-based retention purging.
  • Adds a new dashboard page and navigation entry to browse/filter audit log entries.

Reviewed changes

Copilot reviewed 12 out of 12 changed files in this pull request and generated 6 comments.

Show a summary per file
File Description
src/api/index.js Wires in the audit log router and attempts to apply the audit middleware.
src/api/middleware/auditLog.js Adds middleware to derive actions/targets and write audit log entries.
src/api/routes/auditLog.js Adds paginated/filterable audit log listing endpoint.
src/modules/scheduler.js Adds periodic cleanup to purge expired audit log entries.
src/api/utils/configAllowlist.js Adds auditLog to the safe config allowlist.
migrations/015_audit_logs.cjs Creates audit_logs table + index.
web/src/app/api/guilds/[guildId]/audit-log/route.ts Proxies audit log reads from the dashboard to the bot API.
web/src/app/dashboard/audit-log/page.tsx Adds the audit log table UI with filters, expansion, and pagination.
web/src/components/layout/sidebar.tsx Adds “Audit Log” navigation item.
tests/api/routes/auditLog.test.js Adds tests for the new audit log API route (auth/filtering/pagination/errors).
tests/api/middleware/auditLog.test.js Adds unit tests for the audit middleware/action derivation/diff behavior.
config.json Adds auditLog config and includes unrelated string/emoji encoding changes.

💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.

Copy link
Contributor

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 8

🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Inline comments:
In `@migrations/015_audit_logs.cjs`:
- Around line 29-31: The existing index idx_audit_logs_guild_created on
audit_logs(guild_id, created_at DESC) doesn’t help queries that delete by
created_at alone; add a standalone index on created_at (e.g., CREATE INDEX IF
NOT EXISTS idx_audit_logs_created_at ON audit_logs(created_at DESC)) to support
the scheduled retention purge that deletes by created_at without guild_id so it
avoids full-table scans.

In `@src/api/middleware/auditLog.js`:
- Around line 139-140: The audit middleware is calling getConfig() without a
guild ID, causing configDiff to be computed from the global snapshot instead of
the guild-merged snapshot; update all places where getConfig() is used in this
file (e.g., the calls feeding configDiff and any comparisons around configDiff)
to call getConfig(guildId) using the guild id from the request params or
payload, and when applying runtime updates use setConfigValue(path, value,
guildId) so the change targets the guild-scoped config; ensure variables like
configDiff and any subsequent diff logic operate on the returned guild-specific
config object.
- Around line 176-178: The middleware currently assigns raw req.body into
details.body which can leak secrets; instead deep-clone req.body, walk its
object tree (handling arrays) and redact values for a blacklist of sensitive
keys (e.g., "password", "token", "apiKey", "authorization", "access_token",
"refresh_token", "secret") using case-insensitive matching, replacing values
with a constant like "[REDACTED]", then set details.body = redactedClone; ensure
you do not mutate the original req.body and perform this logic where
details.body is assigned in the auditLog middleware.

In `@src/modules/scheduler.js`:
- Around line 221-227: retentionDays from config is used directly in pool.query
with make_interval which can cause SQL errors if non-numeric; validate and
coerce it before use (e.g., parseInt/Number and ensure
Number.isInteger(retentionDays) && retentionDays > 0), fall back to the default
(90) or return early if invalid, and then pass the validated numeric
retentionDays into pool.query for the DELETE on audit_logs to guarantee the SQL
parameter is always a positive integer.

In `@tests/api/middleware/auditLog.test.js`:
- Around line 111-240: Add two regression tests in the existing middleware
suite: one that verifies getConfig is invoked with the guild ID when handling a
guild config update (spy/mock the getConfig function used by auditLogMiddleware
and assert it was called with '123' for a request to
'/api/v1/guilds/123/config'), and another that ensures sensitive keys in
details.body are redacted before the DB insert (configure req.body with keys
like password, token, apiKey, invoke the middleware, trigger the 'finish'
listener, and assert req.app.locals.dbPool.query was called with a
parameter/argument where details.body has those sensitive values
replaced/removed or replaced with a redaction marker). Ensure both tests use the
existing helpers (createMockReq/createMockRes), call auditLogMiddleware(),
simulate response finish via res._listeners.finish(), and assert against
getConfig and dbPool.query calls accordingly.

In `@tests/api/routes/auditLog.test.js`:
- Around line 173-189: Add a test that asserts invalid date query params are
ignored by the audit-log handler: call authed(request(app).get(...)) with
startDate=not-a-date (and optionally endDate invalid), mock mockPool.query to
return count and rows, assert res.status is 200, then inspect
mockPool.query.mock.calls[0] to ensure the SQL does NOT contain 'created_at >='
or 'created_at <=' (same approach as the existing 'should filter by date range'
test), referencing the existing test helpers authed, request, app and
mockPool.query to locate where to add the new case.

In `@web/src/app/dashboard/audit-log/page.tsx`:
- Line 88: Change the searchTimerRef declaration to use an explicit timer type
instead of inferring from an undefined initial value: update the symbol
searchTimerRef to e.g. useRef<NodeJS.Timeout | undefined>(undefined) (or, if you
prefer browser numeric IDs, useRef<number | null>(null)) and keep subsequent
clearTimeout/calls compatible with that type.
- Around line 358-408: The fragment that wraps the pair of rows inside
entries.map (the JSX surrounding TableRow and the conditional details TableRow)
needs a unique key to satisfy React list keys: replace the shorthand fragment
<>...</> with a keyed fragment (e.g., <Fragment key={entry.id}>...</Fragment>)
or use <React.Fragment key={entry.id}> if you prefer, and add the necessary
import for Fragment from 'react' (or reference React). Update the fragment
around the main row and the expanded details row in the entries.map callback so
the outermost element has key={entry.id}.

ℹ️ Review info

Configuration used: Organization UI

Review profile: ASSERTIVE

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between 665b74e and 8f211c8.

📒 Files selected for processing (12)
  • config.json
  • migrations/015_audit_logs.cjs
  • src/api/index.js
  • src/api/middleware/auditLog.js
  • src/api/routes/auditLog.js
  • src/api/utils/configAllowlist.js
  • src/modules/scheduler.js
  • tests/api/middleware/auditLog.test.js
  • tests/api/routes/auditLog.test.js
  • web/src/app/api/guilds/[guildId]/audit-log/route.ts
  • web/src/app/dashboard/audit-log/page.tsx
  • web/src/components/layout/sidebar.tsx
📜 Review details
⏰ Context from checks skipped due to timeout of 90000ms. You can increase the timeout in your CodeRabbit configuration to a maximum of 15 minutes (900000ms). (4)
  • GitHub Check: Agent
  • GitHub Check: Greptile Review
  • GitHub Check: Docker Build Validation
  • GitHub Check: claude-review
🧰 Additional context used
📓 Path-based instructions (6)
**/*.js

📄 CodeRabbit inference engine (AGENTS.md)

**/*.js: Use ESM modules only — import/export, never require()
Always use node: protocol for Node.js builtin imports (e.g., import { readFileSync } from 'node:fs')

Files:

  • src/api/utils/configAllowlist.js
  • tests/api/routes/auditLog.test.js
  • src/api/routes/auditLog.js
  • tests/api/middleware/auditLog.test.js
  • src/modules/scheduler.js
  • src/api/middleware/auditLog.js
  • src/api/index.js
**/*.{js,ts,tsx}

📄 CodeRabbit inference engine (AGENTS.md)

**/*.{js,ts,tsx}: Always use semicolons
Use single quotes — enforced by Biome
Use 2-space indentation — enforced by Biome

Files:

  • src/api/utils/configAllowlist.js
  • tests/api/routes/auditLog.test.js
  • src/api/routes/auditLog.js
  • web/src/app/dashboard/audit-log/page.tsx
  • tests/api/middleware/auditLog.test.js
  • src/modules/scheduler.js
  • web/src/components/layout/sidebar.tsx
  • src/api/middleware/auditLog.js
  • src/api/index.js
  • web/src/app/api/guilds/[guildId]/audit-log/route.ts
src/**/*.js

📄 CodeRabbit inference engine (AGENTS.md)

src/**/*.js: NEVER use console.log, console.warn, console.error, or any console.* method in src/ files — always use Winston logger instead: import { info, warn, error } from '../logger.js'
Pass structured metadata to Winston logger: info('Message processed', { userId, channelId })
Use custom error classes from src/utils/errors.js and always log errors with context before re-throwing
Use getConfig(guildId?) from src/modules/config.js to read config; use setConfigValue(path, value, guildId?) to update at runtime
Use safeSend() utility for outgoing Discord messages to sanitize mentions and enforce allowedMentions
Use splitMessage() utility for messages exceeding Discord's 2000-character limit
Add tests for all new code with mandatory 80% coverage threshold on statements, branches, functions, and lines; run pnpm test before every commit

Files:

  • src/api/utils/configAllowlist.js
  • src/api/routes/auditLog.js
  • src/modules/scheduler.js
  • src/api/middleware/auditLog.js
  • src/api/index.js
web/**/*.{tsx,ts}

📄 CodeRabbit inference engine (AGENTS.md)

Use next/image Image component with appropriate layout and sizing props in Next.js components

Files:

  • web/src/app/dashboard/audit-log/page.tsx
  • web/src/components/layout/sidebar.tsx
  • web/src/app/api/guilds/[guildId]/audit-log/route.ts
web/src/**/*.{tsx,ts}

📄 CodeRabbit inference engine (AGENTS.md)

Use Zustand store (zustand) for state management in React components; implement fetch-on-demand pattern in stores

Files:

  • web/src/app/dashboard/audit-log/page.tsx
  • web/src/components/layout/sidebar.tsx
  • web/src/app/api/guilds/[guildId]/audit-log/route.ts
src/modules/*.js

📄 CodeRabbit inference engine (AGENTS.md)

src/modules/*.js: Register event handlers in src/modules/events.js by importing handler functions and calling client.on() with config parameter
Check config.yourModule.enabled before processing in module event handlers
Prefer per-request getConfig() pattern in new modules over reactive onConfigChange() wiring; only add onConfigChange() listeners for stateful resources that cannot re-read config on each use

Files:

  • src/modules/scheduler.js
🧠 Learnings (3)
📚 Learning: 2026-02-27T16:24:15.054Z
Learnt from: CR
Repo: VolvoxLLC/volvox-bot PR: 0
File: AGENTS.md:0-0
Timestamp: 2026-02-27T16:24:15.054Z
Learning: Applies to src/db.js : Database connection requires PostgreSQL (via pg driver with raw SQL, no ORM); the bot works without a database but config persistence requires PostgreSQL

Applied to files:

  • migrations/015_audit_logs.cjs
📚 Learning: 2026-02-27T16:24:15.054Z
Learnt from: CR
Repo: VolvoxLLC/volvox-bot PR: 0
File: AGENTS.md:0-0
Timestamp: 2026-02-27T16:24:15.054Z
Learning: Applies to src/index.js : The tempban scheduler runs on a 60s interval and catches up on missed unbans after restart; started in index.js startup and stopped in graceful shutdown

Applied to files:

  • src/modules/scheduler.js
📚 Learning: 2026-02-27T16:24:15.054Z
Learnt from: CR
Repo: VolvoxLLC/volvox-bot PR: 0
File: AGENTS.md:0-0
Timestamp: 2026-02-27T16:24:15.054Z
Learning: Applies to src/**/*.js : NEVER use console.log, console.warn, console.error, or any console.* method in src/ files — always use Winston logger instead: import { info, warn, error } from '../logger.js'

Applied to files:

  • src/modules/scheduler.js
🧬 Code graph analysis (5)
tests/api/routes/auditLog.test.js (1)
src/api/server.js (1)
  • createApp (27-87)
tests/api/middleware/auditLog.test.js (1)
src/api/middleware/auditLog.js (3)
  • deriveAction (20-58)
  • computeConfigDiff (78-92)
  • auditLogMiddleware (131-220)
src/modules/scheduler.js (2)
src/modules/config.js (2)
  • err (94-94)
  • getConfig (282-313)
src/db.js (1)
  • getPool (142-147)
src/api/middleware/auditLog.js (4)
src/api/routes/members.js (1)
  • after (163-163)
src/commands/memory.js (1)
  • targetId (350-350)
src/logger.js (1)
  • info (230-232)
src/modules/config.js (2)
  • err (94-94)
  • getConfig (282-313)
src/api/index.js (1)
src/api/middleware/auditLog.js (1)
  • auditLogMiddleware (131-220)
🪛 GitHub Check: CodeQL
src/api/routes/auditLog.js

[failure] 41-41: Missing rate limiting
This route handler performs authorization, but is not rate-limited.

src/api/index.js

[failure] 55-55: Missing rate limiting
This route handler performs authorization, but is not rate-limited.
This route handler performs authorization, but is not rate-limited.

🔇 Additional comments (12)
web/src/components/layout/sidebar.tsx (1)

56-60: Nice sidebar integration.

The new Audit Log entry cleanly reuses the existing navigation structure and active-route behavior.

src/api/utils/configAllowlist.js (1)

28-28: Allowlist update looks correct.

Adding 'auditLog' to SAFE_CONFIG_KEYS is consistent with the new audit configuration surface.

src/modules/scheduler.js (1)

202-205: Good maintenance cadence hook.

Running audit-log retention cleanup on a throttled scheduler interval is a solid non-intrusive approach.

config.json (1)

264-267: Audit log defaults are sensible.

The new auditLog.enabled and auditLog.retentionDays defaults are clear and operationally reasonable.

web/src/app/api/guilds/[guildId]/audit-log/route.ts (1)

36-44: Proxy parameter forwarding is well-scoped.

Restricting forwarded query params to an allowlist keeps the proxy surface tight and predictable.

src/api/routes/auditLog.js (2)

41-41: Rate limiting is correctly applied — static analysis false positive.

The CodeQL warning about missing rate limiting is incorrect. The auditRateLimit middleware (defined at line 16) is properly applied to this route handler as the first middleware in the chain.


1-110: Well-structured audit log API implementation.

Clean implementation with proper parameterized queries, parallel database calls for efficiency, and correct input validation. The rate limiter, auth checks, and error handling are all in place.

src/api/index.js (2)

60-62: Audit middleware placement is correct.

Placing auditLogMiddleware() after all route mounts ensures it intercepts mutating requests across all authenticated routes. The middleware checks res.on('finish') so it captures outcomes without blocking.


53-55: Comment is factually incorrect — auditLogRouter is mounted after guildsRouter, not before.

The comment states "mounted before guilds catch-all" but the actual mounting order is:

  • Line 48: guildsRouter under /guilds
  • Line 55: auditLogRouter under /guilds

Additionally, guildsRouter does not have a catch-all route. Its routes are all explicit (e.g., /:id, /:id/channels, /:id/roles, etc.). The current mount order works because guildsRouter lacks a route matching /:id/audit-log, allowing auditLogRouter to handle it. However, the comment's explanation of why this ordering matters is misleading.

tests/api/routes/auditLog.test.js (1)

1-231: Comprehensive test coverage for the audit log route.

Good coverage of authentication, pagination, filtering, and error handling scenarios. The mock setup is clean and tests properly verify SQL query construction.

web/src/app/dashboard/audit-log/page.tsx (2)

94-101: Good cleanup patterns for debounce timer and abort controller.

Proper cleanup of the timeout in the debounce effect and aborting in-flight requests on unmount prevents memory leaks and race conditions.

Also applies to: 103-107


265-271: Good accessibility implementation.

The input has proper aria-label, and table rows support keyboard navigation with Enter/Space handlers. This ensures the expandable rows are accessible to keyboard users.

Also applies to: 362-372

@greptile-apps
Copy link

greptile-apps bot commented Feb 28, 2026

Greptile Summary

Complete audit logging system for tracking all admin actions in the web dashboard with attribution and automatic retention cleanup.

Key Changes:

  • Database migration creates audit_logs table with indexed guild_id and created_at columns
  • Non-blocking middleware intercepts POST/PUT/PATCH/DELETE requests, captures user/action/target/IP, and computes before/after config diffs
  • Paginated API endpoint with filtering by action, userId, and date range
  • React dashboard page with expandable detail rows, debounced search, and proper locale handling
  • Automatic purge of entries older than configurable retention period (default 90 days)
  • 29 comprehensive tests covering middleware behavior, route filtering, pagination, and error cases

Issues Found:

  • maskSensitiveFields in configAllowlist.js only covers two triage API keys, not general sensitive fields (password, token, apiKey, secret). The audit middleware uses this function to sanitize request bodies, so credentials in API requests will be logged in plain text. This should be expanded before merge.

Strengths:

  • All database queries use parameterized statements
  • Proper authentication and authorization checks
  • Non-blocking audit writes don't impact request latency
  • Fire-and-forget pattern with promise error handling
  • Rate limiting on audit log endpoint (30 req/min)
  • Test coverage validates auth, filtering, pagination, and edge cases

Confidence Score: 4/5

  • This PR is safe to merge with one security issue that should be addressed
  • Comprehensive implementation with excellent test coverage (29 tests), proper auth, parameterized queries, and non-blocking design. However, maskSensitiveFields only covers two triage API keys, allowing passwords/tokens in request bodies to be logged unmasked. This is a security concern that should be fixed before merge.
  • src/api/utils/configAllowlist.js requires expanding SENSITIVE_FIELDS to prevent credential leakage in audit logs

Important Files Changed

Filename Overview
migrations/015_audit_logs.cjs Creates audit_logs table with proper indexes for guild_id and created_at queries
src/api/middleware/auditLog.js Non-blocking audit middleware with action derivation and config diff tracking; insufficient sensitive field masking
src/api/routes/auditLog.js Paginated audit log endpoint with proper filtering, auth, rate limiting, and error handling
src/api/utils/configAllowlist.js Added auditLog to SAFE_CONFIG_KEYS; maskSensitiveFields only covers two triage API keys
web/src/app/dashboard/audit-log/page.tsx Filterable audit log dashboard with proper locale handling, debounced search, and expandable rows

Sequence Diagram

sequenceDiagram
    participant User
    participant Dashboard
    participant NextAPI as Next.js API
    participant BotAPI as Bot API
    participant Middleware as Audit Middleware
    participant DB as PostgreSQL
    
    User->>Dashboard: View audit log page
    Dashboard->>NextAPI: GET /api/guilds/:id/audit-log
    NextAPI->>NextAPI: authorizeGuildAdmin
    NextAPI->>BotAPI: GET /guilds/:id/audit-log
    BotAPI->>BotAPI: requireGuildAdmin
    BotAPI->>DB: SELECT with filters
    DB-->>BotAPI: Return entries + total
    BotAPI-->>NextAPI: JSON response
    NextAPI-->>Dashboard: Proxy response
    Dashboard-->>User: Display audit table
    
    Note over User,DB: Admin performs mutating action
    User->>Dashboard: Update config/member/etc
    Dashboard->>NextAPI: PUT/POST/PATCH/DELETE
    NextAPI->>BotAPI: Forward request
    BotAPI->>Middleware: Intercept mutating request
    Middleware->>Middleware: Derive action, extract guild
    Middleware->>BotAPI: Call next() (non-blocking)
    BotAPI->>BotAPI: Process request
    BotAPI-->>Middleware: Response finished
    Middleware->>Middleware: maskSensitiveFields
    Middleware->>DB: INSERT audit_logs (fire-and-forget)
    BotAPI-->>NextAPI: Response
    NextAPI-->>Dashboard: Response
    
    Note over DB: Scheduler cleanup (every 6 hours)
    DB->>DB: DELETE old entries > retentionDays
Loading

Last reviewed commit: d2ab87f

Copy link

@greptile-apps greptile-apps bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

15 files reviewed, 3 comments

Edit Code Review Agent Settings | Greptile

@coveralls
Copy link

coveralls commented Feb 28, 2026

Coverage Status

coverage: 90.276% (-0.09%) from 90.365%
when pulling d2ab87f on feat/audit-log
into 702d1ba on main.

Copy link

@claude claude bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Review Summary

5 warnings, 1 nitpick remaining after the fixes in 4dbcdc2.

The middleware ordering, React Fragment key, and rate limiting issues from the earlier round are fixed. However, several issues remain that should be addressed before merge.

🟡 Warnings

  1. Query string pollutes action derivation and target extraction (src/api/middleware/auditLog.js:168, 191, 217-220): req.originalUrl includes the query string. deriveAction() and the target extraction split on / without stripping ?..., producing corrupted action names (e.g., config?limit=25.update) and target IDs. Use req.path for all path-parsing; keep req.originalUrl only in details.path.

  2. getConfig() called without guild ID (src/api/middleware/auditLog.js:154, 201): Per project conventions, getConfig(guildId) should be used to check guild-specific auditLog.enabled overrides and to capture correct before/after config snapshots for diffs.

  3. Missing standalone created_at index (migrations/015_audit_logs.cjs:28-31): The retention purge query deletes by created_at alone without guild_id. The composite index (guild_id, created_at DESC) won't be used, causing sequential scans.

  4. retentionDays not validated (src/modules/scheduler.js:221): Non-numeric config values would cause repeated SQL errors every 6-hour cleanup cycle. Coerce and validate before passing to make_interval.

  5. Missing auditLog validation schema (src/api/utils/configValidation.js): The auditLog key is in SAFE_CONFIG_KEYS (writable via API) but has no type validation in CONFIG_SCHEMA, allowing invalid values to be persisted.

  6. Documentation not updated (AGENTS.md, README): Per project convention "Keep docs up to date — this is non-negotiable", AGENTS.md Key Files, Database Tables, and README config reference need updating.

🔵 Nitpick

  1. maskSensitiveFields is config-specific (src/api/middleware/auditLog.js:195): Only handles known config paths. General-purpose sensitive field names (password, token, secret) in non-config request bodies are stored unredacted.
Prompt to fix all issues
Fix the following issues in the volvox-bot repo on branch feat/audit-log:

1. FILE: src/api/middleware/auditLog.js
   ISSUE: req.originalUrl includes query strings, corrupting action names and target IDs.
   FIX: On line 167-168, change `extractGuildId(req.originalUrl || req.path)` to `extractGuildId(req.path)` and `deriveAction(req.method, req.originalUrl || req.path)` to `deriveAction(req.method, req.path)`.
   On lines 217-220, change `(req.originalUrl || req.path)` to `req.path` for pathSegments extraction.
   Keep `req.originalUrl` only on line 191 in `details.path` for display purposes.

2. FILE: src/api/middleware/auditLog.js
   ISSUE: getConfig() called without guild ID on lines 154 and 201.
   FIX: Extract guildId earlier (before the config check) using `const guildId = extractGuildId(req.path);`. Then change line 154 from `const config = getConfig();` to `const config = getConfig(guildId || undefined);`. Change line 201 from `const afterConfig = getConfig();` to `const afterConfig = getConfig(guildId || undefined);`. Move the existing guildId assignment on line 167 to use the already-extracted value.

3. FILE: migrations/015_audit_logs.cjs
   ISSUE: Missing standalone created_at index for retention purge.
   FIX: After the existing CREATE INDEX statement (line 31), add:
   await pool.query(`CREATE INDEX IF NOT EXISTS idx_audit_logs_created_at ON audit_logs(created_at);`);

4. FILE: src/modules/scheduler.js
   ISSUE: retentionDays not validated before SQL use.
   FIX: Change line 221 from `const retentionDays = config.auditLog?.retentionDays ?? 90;` to:
   const parsed = Number(config.auditLog?.retentionDays ?? 90);
   const retentionDays = Number.isFinite(parsed) && parsed > 0 ? parsed : 90;

5. FILE: src/api/utils/configValidation.js
   ISSUE: Missing auditLog schema in CONFIG_SCHEMA.
   FIX: Add to CONFIG_SCHEMA (after the last entry, before the closing brace):
   auditLog: {
     type: 'object',
     properties: {
       enabled: { type: 'boolean' },
       retentionDays: { type: 'number' },
     },
   },

6. FILES: AGENTS.md, README.md
   ISSUE: New files and database table not documented.
   FIX: In AGENTS.md Key Files table, add entries for:
   - src/api/middleware/auditLog.js | Audit log middleware — intercepts mutating requests, records non-blocking audit entries
   - src/api/routes/auditLog.js | Audit log API route — paginated, filterable audit log retrieval
   - web/src/app/dashboard/audit-log/page.tsx | Audit log dashboard page with filters, pagination, expandable details
   - web/src/app/api/guilds/[guildId]/audit-log/route.ts | Next.js API route — proxies audit log requests to bot API
   - migrations/015_audit_logs.cjs | Migration — creates audit_logs table
   In AGENTS.md Database Tables section, add:
   - audit_logs | Admin action audit trail — guild_id, user_id, action, target, details (JSONB), IP, timestamp
   In README.md config reference section, add auditLog with enabled (boolean) and retentionDays (number, default 90).

@greptile-apps
Copy link

greptile-apps bot commented Feb 28, 2026

Additional Comments (1)

src/api/utils/configValidation.js, line 144
missing schema for auditLog config section

auditLog is in SAFE_CONFIG_KEYS but has no validation schema here. add:

export const CONFIG_SCHEMA = {
  ai: {
    // ... existing ...
  },
  // ... other sections ...
  auditLog: {
    type: 'object',
    properties: {
      enabled: { type: 'boolean' },
      retentionDays: { type: 'number' },
    },
  },
};

Copilot AI review requested due to automatic review settings February 28, 2026 22:44
coderabbitai[bot]
coderabbitai bot previously approved these changes Feb 28, 2026
coderabbitai[bot]
coderabbitai bot previously approved these changes Feb 28, 2026
Pip Build added 13 commits February 28, 2026 18:18
Mount audit log route and middleware in the API router. Add 'auditLog'
to SAFE_CONFIG_KEYS for dashboard configurability. Add auditLog section
to config.json with enabled:true and retentionDays:90.

Refs #123
Purge audit log entries older than configured retentionDays every 6
hours (360th scheduler tick). Uses make_interval for safe parameterized
date arithmetic.

Refs #123
Add audit log dashboard page with searchable table, action/user/date
filters, expandable detail rows, and pagination. Add Next.js API proxy
route. Add 'Audit Log' entry with ClipboardList icon to sidebar nav.

Refs #123
API route tests cover auth, pagination, filtering (action, userId,
date range), combined filters, 503 on missing DB, and 500 on DB error.
Middleware tests cover action derivation, config diff computation,
non-blocking behavior, response status gating, and enable/disable.

Refs #123
…ing, React key prop, rate limiting

- Move auditLogMiddleware before routes so it actually executes (was dead code at the bottom)
- Add req._auditLogAttached flag to prevent duplicate execution across multiple /guilds mounts
- Import maskSensitiveFields from configAllowlist.js; mask req.body and configDiff before storing
- Reuse already-fetched config snapshot for beforeConfig (eliminates extra getConfig() call)
- Guard insertAuditEntry pool.query() with thenable check to prevent unhandled rejections in tests
- Fix React Fragment missing key prop: switch <> to <React.Fragment key={entry.id}> in .map()
- Note: rate limiting already applied at route level (auditRateLimit in auditLog.js)
- Update guilds test: account for one getConfig() call consumed by auditLogMiddleware
- Run pnpm format (fixes pre-existing formatting in configValidation.js, welcomeOnboarding.js)
- Extract cleanPath once (split on '?') to prevent query strings from
  corrupting action names, guild IDs, and target IDs
- Use getConfig(guildId) instead of global getConfig() for before/after
  config diff snapshots so the diff reflects the actual guild config
Copilot AI review requested due to automatic review settings February 28, 2026 23:18
coderabbitai[bot]
coderabbitai bot previously approved these changes Feb 28, 2026
Copy link
Contributor

Copilot AI left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Copilot encountered an error and was unable to review this pull request. You can try again by re-requesting a review.

@greptile-apps
Copy link

greptile-apps bot commented Mar 1, 2026

Additional Comments (1)

src/api/utils/configAllowlist.js, line 37
SENSITIVE_FIELDS only covers two triage API keys, but maskSensitiveFields is used by audit middleware to sanitize arbitrary request bodies. expand to include common sensitive field names (password, token, apiKey, secret, etc.) or the audit log will record unmasked credentials

export const SENSITIVE_FIELDS = new Set([
  'triage.classifyApiKey',
  'triage.respondApiKey',
  'password',
  'token',
  'apiKey',
  'secret',
  'clientSecret',
  'authorization',
  'apiSecret',
  'accessToken',
  'refreshToken',
]);

@BillChirico BillChirico disabled auto-merge March 1, 2026 00:55
@BillChirico BillChirico merged commit 92e7a42 into main Mar 1, 2026
14 of 15 checks passed
@BillChirico BillChirico deleted the feat/audit-log branch March 1, 2026 00:55
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

Web dashboard: audit log — track all admin actions with attribution

3 participants