[cueweb/docs] Add CueWeb Audit web action audit system#2461
Conversation
Record who performed which state-changing action, when, against which target, on which facility, and with what outcome, surfaced on a new admin-only page reachable from the top menu and left sidebar (Admin -> CueWeb Audit). Implementation: - Capture every mutating action at the single gateway chokepoint (handleRoute in app/utils/gateway_server.ts -> auditGatewayCall in lib/audit.ts): classify endpoint, extract target, resolve actor and facility, sanitize params, skip read-only calls. Sign in/out captured via NextAuth events in lib/auth.ts. - Persist to an append-only JSONL store (lib/audit-store.ts), mirroring the facility-store pattern so CueWeb stays stateless. Configurable via CUEWEB_AUDIT_STORE; size-bounded via CUEWEB_AUDIT_MAX_RECORDS. - Admin-gated read API (app/api/admin/audit) and SSR page (app/admin/audit) with a filterable, paginated, mobile-friendly table: search, actor/category/result/time filters, page navigation with rows-per-page matching Monitor Jobs, auto-refresh, expandable details, CSV export. - Reuse the optional group-authorization gate: add /admin and /api/admin to ADMIN_PATH_PREFIXES; isGateActive/isEffectiveAdmin show the page to everyone when no group authorization is configured, else restrict to CUEWEB_ADMIN_GROUPS. Hide admin-only menus from non-admins in the header, sidebar, and mobile nav. - Document the feature across all eight CueWeb docs sections and add unit tests for the store/query logic and the effective-admin gating.
|
No actionable comments were generated in the recent review. 🎉 ℹ️ Recent review info⚙️ Run configurationConfiguration used: defaults Review profile: CHILL Plan: Pro Run ID: 📒 Files selected for processing (9)
✅ Files skipped from review due to trivial changes (6)
🚧 Files skipped from review as they are similar to previous changes (3)
📝 WalkthroughWalkthroughAdds an append-only JSONL audit trail to CueWeb. A new ChangesCueWeb Audit Trail
Sequence Diagram(s)sequenceDiagram
participant Browser
participant AuditTable
participant AuditAPIRoute as GET /api/admin/audit
participant readAudit
participant JSONL_Store as JSONL File
rect rgba(100, 149, 237, 0.5)
note over Browser,JSONL_Store: Read audit records with filtering
Browser->>AuditTable: apply filter / change page
AuditTable->>AuditAPIRoute: fetch with query params (seq guard)
AuditAPIRoute->>AuditAPIRoute: isGateActive? → check isEffectiveAdmin → 403 if unauthorized
AuditAPIRoute->>readAudit: readAudit(filters + pagination)
readAudit->>JSONL_Store: readFile → parse JSONL lines
JSONL_Store-->>readAudit: parsed AuditRecords
readAudit-->>AuditAPIRoute: AuditPage { records newest-first, total }
AuditAPIRoute-->>AuditTable: { records, total, facets }
AuditTable-->>Browser: render table, pagination, filters
end
rect rgba(144, 238, 144, 0.5)
note over Browser,JSONL_Store: Write audit on mutating gateway call
Browser->>AuditAPIRoute: mutating CueWeb action
AuditAPIRoute->>auditGatewayCall: handleRoute success/error
auditGatewayCall->>auditGatewayCall: parse endpoint, skip reads, sanitize body
auditGatewayCall->>auditGatewayCall: resolve actor/facility/target
auditGatewayCall->>JSONL_Store: recordAudit → writeChain → appendFile JSON line
JSONL_Store->>JSONL_Store: trim if retained records exceed cap
end
Estimated code review effort🎯 4 (Complex) | ⏱️ ~60 minutes Possibly related issues
Possibly related PRs
Suggested reviewers
🚥 Pre-merge checks | ✅ 4 | ❌ 1❌ Failed checks (1 warning)
✅ Passed checks (4 passed)
✏️ Tip: You can configure your own custom pre-merge checks in the settings. ✨ Finishing Touches🧪 Generate unit tests (beta)
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. Comment |
There was a problem hiding this comment.
Actionable comments posted: 10
🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.
Inline comments:
In `@cueweb/app/admin/audit/audit-table.tsx`:
- Around line 47-50: The PAGE_SIZE_OPTIONS array includes 10000 as a maximum
page size option, but the backend hard-caps the limit at 5000 through the
readAudit function. This mismatch causes pagination calculations to be incorrect
when datasets exceed 5000 rows, potentially hiding data. Remove 10000 from the
PAGE_SIZE_OPTIONS array to ensure all selectable page sizes align with the
backend maximum limit of 5000.
- Around line 120-123: The esc function in the audit-table.tsx file currently
escapes CSV special characters but does not protect against spreadsheet formula
injection. Update the esc function to also detect and escape cells that begin
with formula prefix characters (=, +, @, or -) by prepending a single quote to
neutralize them as formulas. Add this check after converting the value to a
string in the esc function, before or alongside the existing CSV escaping logic
for quotes and newlines.
In `@cueweb/app/api/admin/audit/route.ts`:
- Around line 50-53: The num helper function incorrectly converts missing query
parameters to 0 by calling Number on null. Fix this by checking if sp.get(key)
returns a value before converting it to a number; if the parameter is missing
(returns null), return undefined immediately rather than passing null to Number,
which coerces it to 0 and breaks the intended defaults in readAudit.
In `@cueweb/app/utils/use_menu_registry.ts`:
- Around line 56-57: The isAdmin assignment currently defaults to true when the
session is unresolved, causing admin menu items to display prematurely. Modify
the line where isAdmin is assigned to first check the status property returned
by useSession(). When status equals "loading", set isAdmin to false to hide
admin commands while the session resolves. For other status values, keep the
current logic of reading isAdmin from the session data with a false default
instead of true.
In `@cueweb/components/ui/app-sidebar.tsx`:
- Around line 212-216: In app-sidebar.tsx, the useSession() hook call should be
updated to destructure both the session data and the status field. Modify the
isAdmin assignment to check if the session is still in a "loading" status, and
if so, default to false (or a value that hides admin-only entries) instead of
true. Only default to true when the session is fully authenticated or
unauthenticated, not while loading. Apply the same pattern to the other three
navigation components mentioned (app-header.tsx, mobile-nav-sheet.tsx, and
use_menu_registry.ts) to ensure consistent protection of admin-only navigation
across all surfaces.
In `@cueweb/lib/audit-store.ts`:
- Around line 124-144: The issue is that doRecordAudit() synchronously calls
trimIfNeeded() after every append, which reads and potentially rewrites the
entire JSONL file for each audit record once the capacity is reached, blocking
the request path and degrading latency as the audit history grows. Move the
trimming logic out of the critical request path by either implementing log
rotation/segmentation (writing new records to a different file when the current
one reaches capacity) or scheduling trimIfNeeded() to run asynchronously outside
of the doRecordAudit() execution, decoupling compaction from request handling.
- Around line 96-99: In the maxRecords() function, add a check to handle blank
or whitespace-only values of the CUEWEB_AUDIT_MAX_RECORDS environment variable.
Before or after converting the raw string value with Number(), verify that the
trimmed environment variable is not an empty string, and if it is blank or only
whitespace, return the default 50000 instead of allowing Number("") to evaluate
to 0 and bypass the validation logic.
- Around line 223-228: The readAuditFacets function is calling readAudit with a
limit of 5000, which causes it to miss actors and categories from older retained
records when building the filter dropdowns. Remove the limit parameter from the
readAudit call within the readAuditFacets function so that it retrieves all
retained audit records instead of just the most recent 5000, ensuring all actors
and categories are included in the returned facets.
In `@cueweb/lib/audit.ts`:
- Around line 94-96: The SECRET_KEY regex pattern in audit.ts is too narrow and
misses sensitive fields like apiKey, accessKey, privateKey, cookie, session, and
jwt. Expand the regex pattern to include these additional sensitive field name
variations alongside the existing pass/secret/token/credential/authorization
patterns to ensure all sensitive data is properly redacted before being
persisted to the audit trail.
In `@docs/_docs/other-guides/cueweb.md`:
- Around line 337-373: The compound adjectives "sign in" and "sign out" in the
CueWeb Audit section need to be hyphenated when used as noun modifiers. Locate
the phrase "sign in and sign out events" in the documentation and change it to
"sign-in and sign-out events", then check for any additional instances of these
compound adjectives modifying nouns in the same vicinity and apply the
hyphenation consistently.
🪄 Autofix (Beta)
Fix all unresolved CodeRabbit comments on this PR:
- Push a commit to this branch (recommended)
- Create a new PR with the fixes
ℹ️ Review info
⚙️ Run configuration
Configuration used: defaults
Review profile: CHILL
Plan: Pro
Run ID: 2b108c95-5b33-4a8e-bee2-af9d744a2d9c
⛔ Files ignored due to path filters (4)
docs/assets/images/cueweb/cueweb_admin_cueweb_audit.pngis excluded by!**/*.pngdocs/assets/images/cueweb/cueweb_admin_cueweb_audit_dark.pngis excluded by!**/*.pngdocs/assets/images/cueweb/cueweb_admin_cueweb_audit_menu.pngis excluded by!**/*.pngdocs/assets/images/cueweb/cueweb_admin_cueweb_audit_menu_dark.pngis excluded by!**/*.png
📒 Files selected for processing (24)
cueweb/.env.examplecueweb/app/__tests__/lib/audit-store.test.tscueweb/app/__tests__/lib/authz-admin.test.tscueweb/app/admin/audit/audit-table.tsxcueweb/app/admin/audit/page.tsxcueweb/app/api/admin/audit/route.tscueweb/app/utils/gateway_server.tscueweb/app/utils/menus.tscueweb/app/utils/use_menu_registry.tscueweb/components/ui/app-header.tsxcueweb/components/ui/app-sidebar.tsxcueweb/components/ui/mobile-nav-sheet.tsxcueweb/lib/audit-store.tscueweb/lib/audit.tscueweb/lib/auth.tscueweb/lib/authz.tsdocs/_docs/concepts/cueweb-rest-gateway.mddocs/_docs/developer-guide/cueweb-development.mddocs/_docs/getting-started/deploying-cueweb.mddocs/_docs/other-guides/cueweb.mddocs/_docs/quick-starts/quick-start-cueweb.mddocs/_docs/reference/cueweb.mddocs/_docs/tutorials/cueweb-tutorial.mddocs/_docs/user-guides/cueweb-user-guide.md
|
Note: Initially capture only actions performed through CueWeb. Future work can introduce a Cuebot-backed persistent audit database covering all clients (CueGUI, cueman, pycue, and CueWeb), with the page designed so the storage backend can later be replaced behind a single read function. I might add the full Cuebot-backed persistent audit database in this PR or in a separate PR. |
Follow-up fixes to the CueWeb Audit feature from code review. Security: - Harden CSV export against spreadsheet formula injection: prefix cells starting with = + - @ (or a leading tab/CR) with a single quote so they are treated as literal text (audit-table.tsx). - Broaden secret redaction before persisting request details to also drop apiKey / accessKey / privateKey / cookie / session / jwt fields (audit.ts). - Hide admin-only menus while the NextAuth session is still loading (status === "loading" -> isAdmin false) so they don't flash to non-admins before the session resolves; applied across all four nav surfaces (use_menu_registry, app-header, app-sidebar, mobile-nav-sheet). The "everyone is admin when no auth is configured" default is preserved. Correctness: - Cap the audit table's largest page size at 5000 to match readAudit's read limit, so a larger page can't silently hide rows (audit-table.tsx). - Treat a missing/blank limit/offset query param as undefined so readAudit uses its defaults instead of clamping to 1 (api/admin/audit/route.ts). - Treat a blank CUEWEB_AUDIT_MAX_RECORDS as unset (falls back to the 50000 default) instead of disabling retention; explicit 0 still removes the cap (audit-store.ts). - Build filter-dropdown facets from the whole retained trail via a shared readAllRecords() helper, so older-only actors/categories are included (audit-store.ts). Performance: - Amortize trimming: track the record count in memory and only rewrite the log once it grows past cap + slack, keeping the per-request audit write off the full read+rewrite path (audit-store.ts). Docs: - Hyphenate "sign-in / sign-out events" in the CueWeb system guide.
# Conflicts: # cueweb/app/utils/gateway_server.ts
66acc9c
into
AcademySoftwareFoundation:master
Related Issues
Main issue:
Issues related to this PR:
Summarize your change.
Record who performed which state-changing action, when, against which target, on which facility, and with what outcome, surfaced on a new admin-only page reachable from the top menu and left sidebar (Admin -> CueWeb Audit).
Implementation:
LLM usage disclosure
Parts of this solution's implementation were developed with assistance from Claude Opus.