Skip to main content

adding-audit-events

Use when adding or changing an audited Nango endpoint, or deciding one shouldn't be audited - covers the vocabulary table, metadata typing, spec placement, mount ordering, and the webapp filter list

Source facts

Repository
NangoHQ/nango
Last source activity
August 31, 2026 at 07:37
Detected SKILL.md language
English
Stars
12,607
Forks
1,429

Install options

The review-first prompt is selected by default. You can switch to a direct command or download a local copy.

Review the source files

Read SKILL.md and any companion files shown by SkillsMP before deciding whether to install.

Showing SKILL.md

SKILL.md
Source instructions · Read-only preview
name
adding-audit-events
description
Use when adding or changing an audited Nango endpoint, or deciding one shouldn't be audited - covers the vocabulary table, metadata typing, spec placement, mount ordering, and the webapp filter list
# Adding Audit Events `AuditEventTable` in `packages/types/lib/audit-trail/event.ts` is the source of truth: it maps each resource to its actions and the metadata each may carry. The event union, the metadata lookup, the per-resource action lists and the webapp filters all derive from it. Add the action there first and let the compiler tell you what else to touch. The trail is **control-plane only** — configuration, state and authentication. Runtime traffic (records, proxy, sync execution) is data plane and stays out; audit it and you get millions of rows a month. ## Workflow 1. **Vocabulary** — in `AuditEventTable`, under the resource: `<action>: <MetadataShape>;` or `<action>: never;` when the action carries no metadata. 2. **Metadata shape**, if any — `packages/types/lib/audit-trail/metadata.ts`. Name it for what it holds, never for one action: `integration.created` and `integration.deleted` share `IntegrationProviderMetadata` because both record only the provider. 3. **Endpoint type** — every customer-facing endpoint declares one: - `Audit: AuditPolicy<'<resource>', '<action>', 'account' | 'environment'>` - `Audit: { kind: 'no-audit'; reason: '<why>' }` — use `'data-plane operation'` for runtime traffic. 4. **Spec** — `packages/server/lib/middleware/audit/<resource>.middleware.ts`: ```ts export const auditThingDone = auditable<PostThing>({ policy: Audit.auditable({ resource: 'thing', action: 'done', scope: 'environment' }), target: (req) => makeTarget('thing', nonEmptyString(req.body.id)), metadata: (req) => omitUndefined<ThingDoneMetadata>({ … }) }); ``` Mounted middleware first, in the vocabulary's action order, private spec then public for the same action; multi-action emitters last; helpers below all of them. 5. **Barrel** — add the export to `audit/index.ts`. Hand-written on purpose: it declares what has scope beyond the folder, so never `export *`. 6. **Mount** — `routes.public.ts` / `routes.private.ts`, **before `withScope`**: `.post(apiAuth, auditThingDone, withScope('…'), handler)`. After the scope check, denials are lost. 7. **Test** — `<resource>.middleware.unit.test.ts`, scaffolding from `./testing.js`. 8. **Webapp** — add the action to `actionsByResource` in `packages/webapp/src/pages/Audit/constants.ts`. A type check pins it to the vocabulary, so omitting it is a build error. Labels are derived. 9. **New kind of target** — add it to `AuditTargetType`. ## Gotchas - **Resolvers run before zod.** `req.body` / `params` / `query` are raw at that point, whatever the endpoint type says. Use the guards in `input.ts`: `nonEmptyString`, `positiveInt`, `param`, `query`, `bodyField`. - **`target` and `metadata` resolve before `next()`**, so a value the handler generates isn't available yet. Use `targetFromResponse` / `metadataFromResponse`. - **A 403 means no handler ran**, so anything only the handler knows is missing from exactly the rows an auditor cares about most. Prefer reading from the request. - **`scope: 'account'` nulls the event's environment**, whatever `res.locals` holds. - **On the MCP path only** (`defineManagementMcpTool`), a stray metadata key *alongside* a valid one is accepted — the audit type is a union over the vocabulary. A stray key alone, a wrong type, and metadata on a `never` action all fail. - **Grep `input.ts` and `lookups.ts` before adding a helper.** The target and metadata builders usually exist, and near-identical `…Target` functions get flagged in review. ## Review Checklist - [ ] `npm run ts-build` clean — it catches a missing webapp entry, metadata that doesn't fit the action, and metadata on an action declared `never` - [ ] Unit test asserts the common fields (`accountId`, `environment`, `actor`, `outcome`), not only the one it is named for - [ ] Test break-checked: remove the target or a metadata key and confirm it goes red - [ ] Audit middleware sits before `withScope` on every new mount - [ ] Denial and failure paths still identify the event — check what a 403 records, not just the 200
View on GitHub