Skip to content

[cueweb/docs] Add CueWeb Audit web action audit system#2461

Merged
ramonfigueiredo merged 3 commits into
AcademySoftwareFoundation:masterfrom
ramonfigueiredo:cueweb-audit
Jun 23, 2026
Merged

[cueweb/docs] Add CueWeb Audit web action audit system#2461
ramonfigueiredo merged 3 commits into
AcademySoftwareFoundation:masterfrom
ramonfigueiredo:cueweb-audit

Conversation

@ramonfigueiredo

@ramonfigueiredo ramonfigueiredo commented Jun 23, 2026

Copy link
Copy Markdown
Collaborator

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:

  • 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.

LLM usage disclosure

Parts of this solution's implementation were developed with assistance from Claude Opus.

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.
@coderabbitai

coderabbitai Bot commented Jun 23, 2026

Copy link
Copy Markdown
Contributor

Review Change Stack

No actionable comments were generated in the recent review. 🎉

ℹ️ Recent review info
⚙️ Run configuration

Configuration used: defaults

Review profile: CHILL

Plan: Pro

Run ID: 5e9457a2-0247-443f-8c59-9f0c6c63fa34

📥 Commits

Reviewing files that changed from the base of the PR and between c184fc9 and 3244b03.

📒 Files selected for processing (9)
  • cueweb/app/utils/gateway_server.ts
  • docs/_docs/concepts/cueweb-rest-gateway.md
  • docs/_docs/developer-guide/cueweb-development.md
  • docs/_docs/getting-started/deploying-cueweb.md
  • docs/_docs/other-guides/cueweb.md
  • docs/_docs/quick-starts/quick-start-cueweb.md
  • docs/_docs/reference/cueweb.md
  • docs/_docs/tutorials/cueweb-tutorial.md
  • docs/_docs/user-guides/cueweb-user-guide.md
✅ Files skipped from review due to trivial changes (6)
  • docs/_docs/quick-starts/quick-start-cueweb.md
  • docs/_docs/developer-guide/cueweb-development.md
  • docs/_docs/other-guides/cueweb.md
  • docs/_docs/user-guides/cueweb-user-guide.md
  • docs/_docs/getting-started/deploying-cueweb.md
  • docs/_docs/reference/cueweb.md
🚧 Files skipped from review as they are similar to previous changes (3)
  • cueweb/app/utils/gateway_server.ts
  • docs/_docs/tutorials/cueweb-tutorial.md
  • docs/_docs/concepts/cueweb-rest-gateway.md

📝 Walkthrough

Walkthrough

Adds an append-only JSONL audit trail to CueWeb. A new lib/audit-store.ts module handles serialized file writes and filtered reads with retention trimming. lib/audit.ts hooks into handleRoute in gateway_server.ts to record every mutating gateway call and captures sign-in/sign-out via NextAuth events. lib/authz.ts gains isGateActive/isEffectiveAdmin helpers; lib/auth.ts sets session.isAdmin. All navigation surfaces (header, sidebar, mobile nav, menu registry) filter adminOnly entries by session. A new GET /api/admin/audit route and AuditTable client component provide a filterable, paginated, CSV-exportable admin UI with auto-refresh polling and expandable detail rows.

Changes

CueWeb Audit Trail

Layer / File(s) Summary
Audit store types, write, and read
cueweb/lib/audit-store.ts
Defines AuditCategory, AuditResult, AuditRecord, AuditQuery, AuditPage types; implements in-process serialized JSONL appends via writeChain, retention trimming via trimIfNeeded, readAudit with AND-combined filtering and pagination (newest-first), readAuditFacets for dropdowns, and auditStorePath.
Audit capture layer
cueweb/lib/audit.ts
Implements auditGatewayCall (parse endpoint → skip reads → sanitize body → resolve actor/facility/target → recordAudit success/error) and auditAuthEvent for sign-in/sign-out; both are non-throwing with console.error fallback.
Authorization gate helpers and session setup
cueweb/lib/authz.ts, cueweb/lib/auth.ts
authz.ts adds /admin and /api/admin to ADMIN_PATH_PREFIXES, exports isGateActive() and isEffectiveAdmin(), broadens getUserGroups param type. auth.ts sets session.isAdmin and session.groups in session callback, records sign-in/sign-out audit events via recordAudit in authOptions.events.
Gateway hook and admin navigation filtering
cueweb/app/utils/gateway_server.ts, cueweb/app/utils/menus.ts, cueweb/components/ui/*
Hooks auditGatewayCall into handleRoute success and error paths. Adds adminOnly?: boolean to NavMenu/NavGroup, registers Admin→CueWeb Audit menu entry, and filters adminOnly items in app-header, app-sidebar, mobile-nav-sheet, use_menu_registry using session.isAdmin (forced false during loading).
Audit API route
cueweb/app/api/admin/audit/route.ts
Forced-dynamic GET route with conditional 403 authz check when gate is active. Parses filter params (actor, category, result, since, until, search) and pagination (limit, offset), calls readAudit and readAuditFacets, returns { records, total, facets }.
Audit admin page and AuditTable
cueweb/app/admin/audit/page.tsx, cueweb/app/admin/audit/audit-table.tsx
Server component with forced-dynamic, gate-aware authz redirect, SSR-fetched initial data. Client component manages filter state, debounced page-0 reload on filter/page-size changes with sequence guard (prevents stale overwrites), optional 10s auto-refresh polling, expandable detail rows (error/details/endpoint), pagination controls, and CSV export with formula-injection neutralization.
Unit tests
cueweb/app/__tests__/lib/audit-store.test.ts, cueweb/app/__tests__/lib/authz-admin.test.ts
Covers audit-store (path, empty reads, write order, filtering, pagination, facets) and authz gate helpers (isAdminPath, isGateActive, isEffectiveAdmin with case-insensitive group matching across inactive/active gate scenarios).
Documentation and configuration
cueweb/.env.example, docs/_docs/*
Adds CUEWEB_AUDIT_STORE and CUEWEB_AUDIT_MAX_RECORDS to .env.example; documents audit across concepts, developer guide, deploying, quick-start, reference, tutorials, user guide, and other guides covering schema, access control, persistence, API, and UI behavior.

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
Loading

Estimated code review effort

🎯 4 (Complex) | ⏱️ ~60 minutes

Possibly related issues

  • AcademySoftwareFoundation/OpenCue#2462: This PR implements the exact CueWeb Audit feature requested, including append-only JSONL store, gateway-level capture, admin-gated UI, filtering/pagination/CSV export, and authorization integration.

Possibly related PRs

  • AcademySoftwareFoundation/OpenCue#2431: Established the group-based authorization system (isGateActive, isEffectiveAdmin, ADMIN_PATH_PREFIXES) that this PR extends to gate the new CueWeb Audit routes and uses for session.isAdmin.
  • AcademySoftwareFoundation/OpenCue#2448: Both PRs modify navigation/menu infrastructure; this PR adds adminOnly flag to NavMenu/NavGroup alongside the retrieved PR's dynamic enabled-plugins injection.
  • AcademySoftwareFoundation/OpenCue#2459: Both PRs instrument the same handleRoute gateway chokepoint to add side effects—this PR adds auditGatewayCall auditing, while the retrieved PR adds MetricsService recording.

Suggested reviewers

  • lithorus
  • DiegoTavares

🐇 Hops through audit trails of JSONL dreams,
Each action logged—actor, time, and schemes,
Admins filter, paginate, and export with glee,
Newest-first records for all to see,
Append-only truth, serialized so fine!

🚥 Pre-merge checks | ✅ 4 | ❌ 1

❌ Failed checks (1 warning)

Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 58.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
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check ✅ Passed The title accurately describes the main change: introduction of a CueWeb Audit web action audit system across components, pages, APIs, libraries, and documentation.
Linked Issues check ✅ Passed Check skipped because no linked issues were found for this pull request.
Out of Scope Changes check ✅ Passed Check skipped because no linked issues were found for this pull request.

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

✨ Finishing Touches
🧪 Generate unit tests (beta)
  • Create PR with unit tests

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.

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

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

📥 Commits

Reviewing files that changed from the base of the PR and between 14bc5ae and 8bd5f90.

⛔ Files ignored due to path filters (4)
  • docs/assets/images/cueweb/cueweb_admin_cueweb_audit.png is excluded by !**/*.png
  • docs/assets/images/cueweb/cueweb_admin_cueweb_audit_dark.png is excluded by !**/*.png
  • docs/assets/images/cueweb/cueweb_admin_cueweb_audit_menu.png is excluded by !**/*.png
  • docs/assets/images/cueweb/cueweb_admin_cueweb_audit_menu_dark.png is excluded by !**/*.png
📒 Files selected for processing (24)
  • cueweb/.env.example
  • cueweb/app/__tests__/lib/audit-store.test.ts
  • cueweb/app/__tests__/lib/authz-admin.test.ts
  • cueweb/app/admin/audit/audit-table.tsx
  • cueweb/app/admin/audit/page.tsx
  • cueweb/app/api/admin/audit/route.ts
  • cueweb/app/utils/gateway_server.ts
  • cueweb/app/utils/menus.ts
  • cueweb/app/utils/use_menu_registry.ts
  • cueweb/components/ui/app-header.tsx
  • cueweb/components/ui/app-sidebar.tsx
  • cueweb/components/ui/mobile-nav-sheet.tsx
  • cueweb/lib/audit-store.ts
  • cueweb/lib/audit.ts
  • cueweb/lib/auth.ts
  • cueweb/lib/authz.ts
  • docs/_docs/concepts/cueweb-rest-gateway.md
  • docs/_docs/developer-guide/cueweb-development.md
  • docs/_docs/getting-started/deploying-cueweb.md
  • docs/_docs/other-guides/cueweb.md
  • docs/_docs/quick-starts/quick-start-cueweb.md
  • docs/_docs/reference/cueweb.md
  • docs/_docs/tutorials/cueweb-tutorial.md
  • docs/_docs/user-guides/cueweb-user-guide.md

Comment thread cueweb/app/admin/audit/audit-table.tsx
Comment thread cueweb/app/admin/audit/audit-table.tsx
Comment thread cueweb/app/api/admin/audit/route.ts
Comment thread cueweb/app/utils/use_menu_registry.ts Outdated
Comment thread cueweb/components/ui/app-sidebar.tsx Outdated
Comment thread cueweb/lib/audit-store.ts
Comment thread cueweb/lib/audit-store.ts
Comment thread cueweb/lib/audit-store.ts Outdated
Comment thread cueweb/lib/audit.ts Outdated
Comment thread docs/_docs/other-guides/cueweb.md
@ramonfigueiredo

Copy link
Copy Markdown
Collaborator Author

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.
@ramonfigueiredo ramonfigueiredo self-assigned this Jun 23, 2026
@ramonfigueiredo ramonfigueiredo changed the title [cueweb] Add CueWeb Audit web action audit system [cueweb/docs] Add CueWeb Audit web action audit system Jun 23, 2026
# Conflicts:
#	cueweb/app/utils/gateway_server.ts
@ramonfigueiredo ramonfigueiredo merged commit 66acc9c into AcademySoftwareFoundation:master Jun 23, 2026
27 checks passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants