Skip to content

feat(audit-log): add persistent audit log system with comprehensive route instrumentation#3242

Merged
waleedlatif1 merged 10 commits intostagingfrom
audit-log
Feb 18, 2026
Merged

feat(audit-log): add persistent audit log system with comprehensive route instrumentation#3242
waleedlatif1 merged 10 commits intostagingfrom
audit-log

Conversation

@waleedlatif1
Copy link
Collaborator

Summary

  • Add audit_log table to Drizzle schema with indexes for workspace, actor, resource, and action queries
  • Create recordAudit() fire-and-forget utility with type-safe AuditAction and AuditResourceType enums
  • Instrument 53 API routes across security, lifecycle, and management operations
  • Add auditMock to @sim/testing package and update all affected test files
  • Fix MCP domain check to allow env var references in URLs

Type of Change

  • New feature

Testing

  • Added unit tests for recordAudit, AuditAction, and AuditResourceType
  • Updated 14 test files with audit mock
  • All tests passing

Checklist

  • Code follows project style guidelines
  • Self-reviewed my changes
  • Tests added/updated and passing
  • No new warnings introduced
  • I confirm that I have read and agree to the terms outlined in the Contributor License Agreement (CLA)

@vercel
Copy link

vercel bot commented Feb 18, 2026

The latest updates on your projects. Learn more about Vercel for GitHub.

1 Skipped Deployment
Project Deployment Actions Updated (UTC)
docs Skipped Skipped Feb 18, 2026 8:54am

Request Review

@greptile-apps
Copy link
Contributor

greptile-apps bot commented Feb 18, 2026

Greptile Summary

This PR adds a comprehensive audit logging system across the platform, instrumenting 53 API routes with fire-and-forget audit trail recording. It also includes a significant rework of the MCP domain validation logic to properly handle environment variable references in URLs.

Key changes:

  • New audit_log table with nullable FKs (ON DELETE SET NULL), four indexes for workspace/actor/resource/action queries, and a Drizzle schema definition with matching migration
  • recordAudit() utility in lib/audit/log.ts — fire-and-forget with IP extraction from headers and silent error swallowing
  • Type-safe AuditAction and AuditResourceType enums (though all 53 call sites already use the enums correctly, not string literals)
  • auditMock added to @sim/testing package, with 14 test files updated to mock the new module
  • MCP domain check refactored: removed auto-allow for platform hostname, added hasEnvVarInHostname() for RFC 3986-aware authority parsing, and added post-resolution validation in McpService.resolveConfigEnvVars() and the test-connection route
  • Client-side MCP settings component updated with matching hasEnvVarInHostname() logic and improved domain-blocked UI behavior
  • checkChatAccess and checkFormAccess utilities extended to return workspaceId, eliminating redundant DB queries

Minor concerns:

  • The auditMock manually duplicates all enum values from the source — it will silently drift if new actions are added
  • description || null change in chat/form creation routes is schema-correct but is a behavioral change from the previous description || ''

Confidence Score: 4/5

  • This PR is safe to merge — audit logging is fire-and-forget with no impact on request paths, and MCP domain validation improvements close a real security gap.
  • Score of 4 reflects a well-structured, large-scale change with thorough test coverage. The audit system's fire-and-forget design means failures won't break any user-facing functionality. The MCP domain check rework is well-tested with 63 test cases. Minor deductions for: manually duplicated mock enum values that may drift, and the behavioral change from '' to null for description defaults.
  • packages/testing/src/mocks/audit.mock.ts (manual enum duplication risk), apps/sim/app/api/chat/route.ts and apps/sim/app/api/form/route.ts (description default change from '' to null)

Important Files Changed

Filename Overview
apps/sim/lib/audit/log.ts New fire-and-forget audit logging utility with AuditAction/AuditResourceType enums, recordAudit function, IP extraction, and error swallowing. Well-structured core module.
packages/db/schema.ts Adds audit_log table with nullable workspace_id/actor_id FK columns, appropriate indexes, and ON DELETE SET NULL behavior. Schema is sound.
packages/testing/src/mocks/audit.mock.ts Audit mock module duplicating AuditAction/AuditResourceType values as plain objects with a vi.fn() for recordAudit. Functional but manually duplicated from source.
apps/sim/lib/mcp/domain-check.ts Refactored to add env var detection in hostname, removed platform hostname auto-allow, added post-resolution validation. Uses createEnvVarPattern() for fresh regex instances per call.
apps/sim/lib/mcp/service.ts Adds post-resolution domain validation via validateMcpDomain() after env vars are resolved. Small, targeted change.
apps/sim/app/workspace/[workspaceId]/w/components/sidebar/components/settings-modal/components/mcp/mcp.tsx Client-side hasEnvVarInHostname and isDomainAllowed functions added, domain-blocked logic updated to not block when URL is empty. Properly creates new RegExp for global patterns.
apps/sim/app/api/chat/route.ts Audit instrumentation for chat deploy. Changes description default from '' to null which is more correct for the nullable schema column but may affect downstream consumers.
apps/sim/app/api/chat/utils.ts Extended checkChatAccess return type to include workspaceId from the workflow join, eliminating the need for redundant DB queries in route handlers.
apps/sim/app/api/form/utils.ts Extended checkFormAccess return type to include workspaceId, mirroring the chat utils change.
apps/sim/app/api/form/route.ts Audit instrumentation for form creation. Changes description default from '' to null, same concern as chat route.
apps/sim/app/api/workflows/[id]/deploy/route.ts Audit instrumentation for workflow deploy/undeploy. Extracts session from validateWorkflowPermissions for the DELETE handler. Uses enums correctly.
apps/sim/app/api/workspaces/[id]/route.ts Audit for workspace deletion. Uses workspaceId: null since workspace is deleted, but audit_log FK has ON DELETE SET NULL so this is correct.
apps/sim/app/api/organizations/[id]/invitations/route.ts Audit for org invitations. Bulk invitations fire N separate recordAudit calls in a loop which may create a burst of DB writes for large batches.
apps/sim/app/api/workspaces/[id]/permissions/route.ts Audit for batch permission updates fires N separate recordAudit calls in a loop, similar pattern to org invitations.
apps/sim/app/api/mcp/servers/test-connection/route.ts Adds post-resolution domain validation for MCP test connections, closing the env var domain bypass security gap.

Flowchart

%%{init: {'theme': 'neutral'}}%%
flowchart TD
    A[API Route Handler] -->|Success path| B[recordAudit]
    B --> C{Build audit entry}
    C --> D[Extract IP from headers]
    C --> E[Set workspaceId / actorId]
    C --> F[Set action / resourceType]
    D --> G[db.insert audit_log]
    E --> G
    F --> G
    G -->|.then| H[Log debug]
    G -->|.catch| I[Log error silently]
    A -->|Return response| J[HTTP Response]

    subgraph MCP Domain Validation
        K[URL input] --> L{Has env var in hostname?}
        L -->|Yes| M[Bypass domain check]
        L -->|No| N{Domain in allowlist?}
        N -->|Yes| O[Allow]
        N -->|No| P[Reject]
        M --> Q[Validate after env var resolution]
        Q --> N
    end
Loading

Last reviewed commit: 6d406c1

Copy link
Contributor

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

84 files reviewed, 5 comments

Edit Code Review Agent Settings | Greptile

… remove redundant queries

- Make audit_log.workspace_id nullable with ON DELETE SET NULL (logs survive workspace/user deletion)
- Make audit_log.actor_id nullable with ON DELETE SET NULL
- Replace all 53 routes' string literal action/resourceType with AuditAction.X and AuditResourceType.X enums
- Fix empty workspaceId ('') → null for OAuth, form, and org routes to avoid FK violations
- Remove redundant DB queries in chat manage route (use checkChatAccess return data)
- Fix organization routes to pass workspaceId: null instead of organizationId
@waleedlatif1
Copy link
Collaborator Author

@cursor review

@waleedlatif1
Copy link
Collaborator Author

@greptile

Copy link
Contributor

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

85 files reviewed, 2 comments

Edit Code Review Agent Settings | Greptile

@waleedlatif1
Copy link
Collaborator Author

@greptile

@waleedlatif1
Copy link
Collaborator Author

@cursor review

Copy link
Contributor

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

85 files reviewed, 2 comments

Edit Code Review Agent Settings | Greptile

@greptile-apps
Copy link
Contributor

greptile-apps bot commented Feb 18, 2026

Additional Comments (2)

apps/sim/app/api/workspaces/[id]/environment/route.ts
Missing audit log for environment variable deletion

The PUT handler (line 160) records an audit log when environment variables are updated, but the DELETE handler does not record one after deleting environment variables. Deleting secrets/environment variables is a security-sensitive operation that should be tracked in the audit trail for consistency.

Consider adding a recordAudit call after the successful upsert on line 237, similar to the one in the PUT handler:

recordAudit({
  workspaceId,
  actorId: userId,
  actorName: session?.user?.name,
  actorEmail: session?.user?.email,
  action: AuditAction.ENVIRONMENT_UPDATED,
  resourceType: AuditResourceType.ENVIRONMENT,
  resourceId: workspaceId,
  description: `Deleted environment variables`,
  metadata: { keysDeleted: keys },
  request,
})

apps/sim/app/workspace/[workspaceId]/w/components/sidebar/components/settings-modal/components/mcp/mcp.tsx
Fetch failure silently disables domain restriction UI

When the fetch to /api/settings/allowed-mcp-domains fails, the catch handler sets allowedMcpDomains to null, which means "no restriction configured." This means a network error, server outage, or 401 response will silently disable the client-side domain validation UI — the user won't see any domain restriction warnings even when the server has an allowlist configured.

The server-side enforcement in service.ts still blocks disallowed domains, so this is not a security hole, but users may be confused when they save an MCP server URL that appears valid in the UI but is silently filtered out on the backend.

Consider either showing an error state when the fetch fails, or initializing with an empty array [] (which would block all URLs) as a fail-closed default:

      .catch(() => setAllowedMcpDomains([]))

…eck, form workspaceId

- Only bypass MCP domain check when env var is in hostname/authority, not path/query
- Add post-resolution validateMcpDomain call in test-connection endpoint
- Match client-side isDomainAllowed to same hostname-only bypass logic
- Return workspaceId from checkFormAccess, use in form audit logs
- Add 49 comprehensive domain-check tests covering all edge cases
@waleedlatif1
Copy link
Collaborator Author

@cursor review

@waleedlatif1
Copy link
Collaborator Author

@greptile

Copy link
Contributor

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

87 files reviewed, 5 comments

Edit Code Review Agent Settings | Greptile

- Remove /g flag from module-level ENV_VAR_PATTERN to avoid lastIndex state
- Create fresh regex instances per call in server-side hasEnvVarInHostname
- Fix authority extraction to terminate at /, ?, or # per RFC 3986
- Prevents bypass via https://evil.com?token={{SECRET}} (no path)
- Add test cases for query-only and fragment-only env var URLs (53 total)
@waleedlatif1
Copy link
Collaborator Author

@greptile

@waleedlatif1
Copy link
Collaborator Author

@cursor review

…Name/Email, fix misleading action

- Wrap recordAudit body in try/catch so nanoid() or header extraction can't throw
- Accept string | null for actorName and actorEmail (session.user.name can be null)
- Normalize null -> undefined before insert to match DB column types
- Fix org members route: ORG_MEMBER_ADDED -> ORG_INVITATION_CREATED (sends invite, not adds member)
Copy link
Contributor

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

87 files reviewed, 1 comment

Edit Code Review Agent Settings | Greptile

Copy link

@cursor cursor bot left a comment

Choose a reason for hiding this comment

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

Cursor Bugbot has reviewed your changes and found 1 potential issue.

Bugbot Autofix is OFF. To automatically fix reported issues with Cloud Agents, enable Autofix in the Cursor dashboard.

Copy link
Contributor

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

87 files reviewed, 3 comments

Edit Code Review Agent Settings | Greptile

@waleedlatif1 waleedlatif1 merged commit e37b4a9 into staging Feb 18, 2026
3 checks passed
@waleedlatif1 waleedlatif1 deleted the audit-log branch February 18, 2026 08:54
@waleedlatif1 waleedlatif1 restored the audit-log branch February 18, 2026 17:49
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.

1 participant

Comments