MCPInstall

build-audit-logs

Build or review audit trails in TypeScript/JavaScript apps using evlog (pipelines, typed actions, denials, retention, compliance-style reviews). For application code, not for extending the evlog package.

Installs
2.8K
Last 24h
+26
Trending rank
#4764
Security
Not audited

Install

Works with every agent the skills CLI supports.

npx skills add https://evlog.dev --skill build-audit-logs
  1. 01

    Install

    Run the command in your project. The CLI asks which agents to install for.

  2. 02

    Describe the task

    Ask your agent for something this skill covers. It loads build-audit-logs automatically when relevant.

  3. 03

    Review the result

    The agent follows the SKILL.md steps, scripts and references bundled with the skill.

SKILL.md

The instructions your agent reads when the skill is triggered.

Build or Review an Audit System with evlog

For application developers who either need to add an audit trail to their product, or who already have one and want it reviewed. Walks through the design calls, the end-to-end implementation, and a review checklist for an existing setup.

This skill assumes the audit lives in your app. To extend the evlog package itself (new audit helper, new drain wrapper), see the contributor skills under .agents/skills/.

Quick reference: call-site cheat sheet

When you already know the system is wired and just need to remember the API:

SituationHelper
Inside a request handler, action succeededlog.audit({ action, actor, target, outcome: 'success' })
Inside a request handler, AuthZ deniallog.audit.deny('reason', { action, actor, target })
Standalone job / script / CLI (no request)audit({ action, actor, target, outcome })
Auto-record success / failure / denied for a functionwithAudit({ action, target }, fn)
Recording a state changeadd changes: auditDiff(before, after)
Centralised typed action vocabularydefineAuditCatalog('billing', { INVOICE_REFUND: { target: 'invoice' } }), or defineAuditAction('invoice.refund', { target: 'invoice' }) for one-offs
Asserting audits in testsmockAudit(), then assertAudit() or toIncludeAuditOf()

AuditFields schema (always provide action, actor, outcome; target strongly recommended; the rest is filled in for you):

interface AuditFields {
  action: string                                  // 'invoice.refund'
  actor: { type: 'user' | 'system' | 'api' | 'agent', id: string, email?, displayName?, model?, tools?, reason?, promptId? }
  outcome: 'success' | 'failure' | 'denied'
  target?: { type: string, id: string, [k: string]: unknown }
  reason?: string
  changes?: { before?: unknown, after?: unknown, patch?: AuditPatchOp[] }
  causationId?: string
  correlationId?: string
  version?: number                                // defaults to AUDIT_SCHEMA_VERSION
  idempotencyKey?: string                         // auto-derived from action+actor+target+timestamp
  context?: { requestId?, traceId?, ip?, userAgent?, tenantId?, ... }   // filled by auditEnricher
  signature?: string                              // added by signed(drain, { strategy: 'hmac' })
  prevHash?: string                               // added by signed(drain, { strategy: 'hash-chain' })
  hash?: string                                   // added by signed(drain, { strategy: 'hash-chain' })
}

What "audit logging" actually means

An audit log answers a forensic question: who did what, on which resource, when, from where, with which outcome. That's a different shape from observability logs, which is why the operational rules differ:

Audit logObservability log
Question"Who tried to do what, was it allowed?""How did this request behave?"
SamplingNever (force-keep)Often (head + tail)
Retention1 – 7 years (compliance)30 – 90 days
MutabilityAppend-only, tamper-evidentMutable, lossy
AudienceAuditors, security, legalEngineers
StorageOften dedicated (separate dataset / DB)Shared with telemetry

evlog ships the audit layer as a thin extension of its wide-event pipeline (a typed audit field on BaseWideEvent plus a few helpers and drain wrappers). The point is that you compose with the primitives the app already uses: same drains, same enrichers, same redact, same framework integration. There is no parallel system to maintain.

Mental model

log.audit(...) ──► sets event.audit ──► force-keep ──► auditEnricher ──► redact ──► every drain
                                                                                  └─► auditOnly(signed(fsDrain))
Building blockRoleRequired?
log.audit() / audit() / withAudit()Sets event.audit and force-keeps the eventYes
auditEnricher()Auto-fills event.audit.context (req / trace / ip / ua / tenantId)Recommended
auditOnly(drain)Filters the drain to events with event.audit setRecommended
signed(drain, ...)Adds tamper-evident integrity (HMAC or hash-chain)Optional (compliance)
auditRedactPresetStrict PII preset for audit eventsRecommended
mockAudit()Captures audit events in testsYes (in tests)

Design calls before writing code

Make these explicit and write them down somewhere a security reviewer can find. Without a written rule, the system can't be audited. Auditors look for the policy first, then the enforcement.

1. Where do audits live?

DrainUse whenTrade-offs
FS (evlog/fs + signed)Self-hosted, simple, you control the diskManual rotation/backup; single-process unless you persist hash-chain state externally
Dedicated Axiom datasetYou already use AxiomEasy queries, separate retention/billing; cost scales with volume
Postgres / Neon / AuroraYou want SQL queries, joins with app dataNeed a schema, indexes, retention job; idempotency key prevents duplicates
S3 + Object LockAppend-only WORM compliance (HIPAA / FINRA)Read latency; pair with a queryable mirror (Athena)
Multiple drainsDifferent audiences (engineers ↔ legal)Use auditOnly per drain; drains fail in isolation by design

Rule of thumb. Pick at least two: a queryable one (Axiom / Postgres) for day-to-day forensics + an append-only one (FS journal with hash-chain, or S3 Object Lock) as the compliance artefact. The two-drain pattern protects against vendor outages and admin mistakes on the queryable side.

2. Do you need integrity (signed)?

Yes if any of:

  • A compliance framework requires tamper-evidence (SOC2 CC7, HIPAA §164.312(c)(1), PCI 10.5).
  • The drain is mutable by engineers / admins.
  • You may need to prove to a regulator that no events were modified after the fact.

Skip if:

  • Drain is already WORM (S3 Object Lock, BigQuery append-only, Postgres with row-level immutability + monitored DDL).
  • You're prototyping.

Strategies:

  • 'hmac': per-event signature; quick to verify; rotate secret annually and embed a key id (extend AuditFields).
  • 'hash-chain': sequence integrity; deleting a row breaks the chain forward; persist state.{load,save} if you run multiple processes (Redis is the typical store).

3. Multi-tenancy?

If the app is multi-tenant, tenant isolation on every audit event is non-negotiable. A query that mixes tenants is a privacy incident. Wire it once in the enricher:

auditEnricher({ tenantId: ctx => resolveTenant(ctx) })

If the app uses Better Auth, auditEnricher can also bridge the authenticated session into audit.context. See the Better Auth integration (evlog/better-auth, https://www.evlog.dev/use-cases/better-auth/overview) for wiring identifyUser alongside the audit pipeline.

Then either (a) partition the audit dataset by audit.context.tenantId, or (b) one drain per tenant if hard isolation is required. Never query audits without a tenant filter.

4. Retention

Pick a window per drain and document it. Enforce at the drain layer, not in app code, because the drain already has audited mechanisms for it (lifecycle policies, DELETE jobs, dataset retention).

FrameworkTypical retention
SOC21 year minimum, 7 years recommended
HIPAA6 years
PCI DSS1 year (3 months immediately accessible)
GDPR"As long as necessary" (see "GDPR vs append-only" below)

How to enforce per drain:

  • FS: createFsDrain({ maxFiles }) + a daily compactor.
  • Postgres: DELETE FROM audit_events WHERE timestamp < now() - interval '7 years' on a cron.
  • Axiom / Datadog / Loki: dataset-level retention policy.

5. GDPR vs append-only

The right to be forgotten collides with audit immutability. Recommended pattern:

  1. Keep audit rows immutable and chain-verified.
  2. Encrypt PII fields (email, name, IP) with a per-actor key held outside the audit store.
  3. To "forget" a user, delete their key. The audit row stays, the chain stays valid, the PII becomes unreadable (crypto-shredding).

A built-in cryptoShredding helper is on the roadmap; until then, encrypt in a custom enricher.

Step-by-step buildout

Step 1: Wire the pipeline (one-time)

The wiring shape is the same in every framework: register auditEnricher() so event.audit.context gets requestId, traceId, ip, userAgent, and (if configured) tenantId automatically, then add a main drain plus an audit-only drain.

The minimal Nuxt / Nitro setup looks like this:

// server/plugins/evlog.ts
import { auditEnricher, auditOnly, signed } from 'evlog'
import { createAxiomDrain } from 'evlog/axiom'
import { createFsDrain } from 'evlog/fs'

export default defineNitroPlugin((nitroApp) => {
  const auditSink = auditOnly(
    signed(createFsDrain({ dir: '.audit/' }), { strategy: 'hash-chain' }),
    { await: true },
  )
  const main = createAxiomDrain({ dataset: 'logs' })

  nitroApp.hooks.hook('evlog:enrich', auditEnricher({
    tenantId: ctx => ctx.headers?.['x-tenant-id'],
  }))
  nitroApp.hooks.hook('evlog:drain', async (ctx) => {
    await Promise.all([main(ctx), auditSink(ctx)])
  })
})

For Hono, Express, Next.js, or standalone scripts / workers, see references/framework-wiring.md. The pattern is identical. Only the framework integration helper changes.

Step 2: Define the action vocabulary

Audits get queried and alerted on by audit.action. A typo is a missing alert, so centralise the list. For a bounded context with several actions, prefer a catalog: one prefix, typed keys, autocomplete on the wire format ${prefix}.${KEY}:

// app/audit/billing.ts
import { defineAuditCatalog } from 'evlog'

export const billingAudit = defineAuditCatalog('billing', {
  INVOICE_REFUND: { target: 'invoice' },
  PLAN_CHANGE:    { target: 'subscription' },
})

At the call site: log.audit(billingAudit.INVOICE_REFUND({ actor, target, outcome: 'success' })).

Add the opt-in declare module 'evlog' { interface RegisteredAuditCatalogs { billing: typeof billingAudit } } augmentation to get autocomplete everywhere. For one-off actions that don't fit a catalog, defineAuditAction still works:

// app/audit/actions.ts
import { defineAuditAction } from 'evlog'

export const InvoiceRefund = defineAuditAction('invoice.refund', { target: 'invoice' })
export const ApiKeyRevoke  = defineAuditAction('apiKey.revoke',  { target: 'apiKey' })

Catalog conventions and scaling recipes (folder per domain, npm packages per bounded context): https://www.evlog.dev/learn/catalogs

Naming conventions:

  • noun.verb (invoice.refund, not refundInvoice).
  • Past tense if the audit is logged after the fact (invoice.refunded); present tense when wrapped by withAudit() (which resolves the outcome itself).
  • Lowercase, dot-delimited, no spaces: for hand-written action ids (defineAuditAction, inline log.audit). Catalog entries follow the catalog convention instead: UPPER_SNAKE_CASE keys under a lowercase prefix, producing wire actions like billing.INVOICE_REFUND. That is intentional. Do not lowercase the keys.

Step 3: Instrument call sites

Three patterns, in order of preference:

A. Wrap the action with withAudit(). Pure audit-worthy actions (refund, delete, role change, password reset). Outcome resolution is automatic, so you can't forget to log a denial or failure:

import { withAudit, AuditDeniedError } from 'evlog'

export const refundInvoice = withAudit(
  {
    action: 'invoice.refund',
    target: ({ id }: { id: string }) => ({ type: 'invoice', id }),
  },
  async ({ id }, ctx) => {
    if (!ctx.actor) throw new AuditDeniedError('Anonymous refund denied')
    return db.invoices.refund(id)
  },
)

Outcome resolution:

  • fn resolves → outcome: 'success'.
  • fn throws AuditDeniedError (or any error with status === 403) → outcome: 'denied', error message becomes reason.
  • Any other thrown error → outcome: 'failure', then re-thrown.

B. Manual log.audit() inside a handler. Use it when the audit is one of several decisions in a larger handler, or when you need to emit before the action completes:

const log = useLogger(event)

if (!user.canRefund(invoice)) {
  log.audit.deny('Insufficient permissions', {
    action: 'invoice.refund',
    actor: { type: 'user', id: user.id },
    target: { type: 'invoice', id: invoice.id },
  })
  throw createError({ status: 403 })
}

const after = await db.invoices.refund(invoice.id)

log.audit({
  action: 'invoice.refund',
  actor: { type: 'user', id: user.id, email: user.email },
  target: { type: 'invoice', id: after.id },
  outcome: 'success',
  changes: auditDiff(invoice, after),
})

C. Standalone audit() for jobs / scripts. No request, no logger. Same shape, no context auto-fill:

import { audit } from 'evlog'

audit({
  action: 'cron.cleanup',
  actor: { type: 'system', id: 'cron' },
  target: { type: 'job', id: 'cleanup-stale-sessions' },
  outcome: 'success',
})

Step 4: Add denial coverage

Auditors care most about denials, because they're how you prove the policy is actually being enforced. Every authorisation check should have a paired log.audit.deny(). Pulling the deny into a single helper guarantees coverage parity with successes:

function authorize(actor, action, resource) {
  const allowed = policy.check(actor, action, resource)
  if (!allowed) {
    useLogger().audit.deny(`Policy denied ${action}`, {
      action,
      actor,
      target: { type: resource.type, id: resource.id },
    })
    throw createError({ status: 403 })
  }
}

Step 5: Redact

Apply auditRedactPreset (or merge it into the existing RedactConfig). It redacts HTTP auth headers and common credential field names (password, token, apiKey, cardNumber, cvv, ssn, plus cookie / set-cookie) at any nesting depth, including inside audit.changes.before / audit.changes.after:

import { initLogger, auditRedactPreset } from 'evlog'

initLogger({
  redact: {
    paths: [...(auditRedactPreset.paths ?? [])],
  },
})

Step 6: Test it

mockAudit() captures audit events for assertions without going through any drain. Make the denial test mandatory in code review, because untested denial paths are the most common cause of audit gaps:

import { mockAudit } from 'evlog'

it('refunds the invoice and records an audit', async () => {
  const captured = mockAudit()

  await refundInvoice({ id: 'inv_889' }, { actor: { type: 'user', id: 'u1' } })

  expect(captured.events).toHaveLength(1)
  expect(captured.toIncludeAuditOf({
    action: 'invoice.refund',
    target: { type: 'invoice', id: 'inv_889' },
    outcome: 'success',
  })).toBe(true)

  captured.restore()
})

it('denies refund for non-owners and records the denial', async () => {
  const captured = mockAudit()

  await expect(refundInvoice({ id: 'inv_889' }, { actor: null })).rejects.toThrow()

  expect(captured.toIncludeAuditOf({
    action: 'invoice.refund',
    outcome: 'denied',
  })).toBe(true)

  captured.restore()
})

Step 7: Production readiness checklist

Walk through this with a security stakeholder before declaring the system production-ready (the same checklist powers the review mode below):

  • auditEnricher is registered on every framework integration.
  • Every authorisation check has a paired log.audit.deny() (greppable).
  • Every mutating endpoint either uses withAudit() or calls log.audit() explicitly.
  • At least two drains: one queryable (Axiom / Postgres) and one tamper-evident (FS + signed hash-chain, or S3 Object Lock).
  • auditRedactPreset (or stricter) is in the global RedactConfig.
  • Retention policy is documented per drain and enforced at the drain layer.
  • Multi-tenant apps: tenantId is set on every audit event; queries always filter by tenant.
  • Hash-chain state.{load,save} persists across process restarts (file / Redis / Postgres).
  • HMAC secret rotation procedure is documented; keyId is embedded in AuditFields (extend via declare module).
  • Tests include a denial path for every privileged action.
  • Audit dataset access is itself logged. Meta-auditing matters.

Review an existing audit setup

When the user already has an audit system and wants it reviewed, work through the four passes below in order. Each pass tells you exactly what to grep, what to look for, and what to flag.

Pass 1: Pipeline wiring

Find where the logger is initialised and where drains / enrichers are registered:

rg -n "initLogger|defineNitroPlugin|createLogger|evlog:enrich|evlog:drain" --type ts
rg -n "auditEnricher|auditOnly|signed\(" --type ts

Flag if:

  • auditEnricher() is missing → event.audit.context is empty, no requestId / IP / tenant correlation.
  • An audit-only drain exists but is not wrapped in auditOnly(...) → main events leak into the audit dataset (privacy & cost incident).
  • Only one drain → no tamper-evident copy. Acceptable only if the single drain is WORM (S3 Object Lock, BigQuery append-only, Postgres immutable).
  • signed() is used without a persisted state while running multiple processes → hash-chain breaks across restarts / instances.
  • await: true is missing on the audit-only drain → events may be lost on crash.

Pass 2: Coverage (call sites)

Inventory every mutating action and every authorisation check:

rg -n "log\.audit\(|log\.audit\.deny\(|withAudit\(|^.*audit\(" --type ts
rg -n "createError\(.*403|throw .*Forbidden|status:\s*403|statusCode:\s*403" --type ts
rg -n "(?i)\b(delete|update|create|refund|grant|revoke|promote|demote|reset|impersonate)\b.*async\s+function|defineEventHandler" --type ts

On Nuxt, Nitro, Next.js, TanStack Start, Hono, Express, or Fastify, you can also run npx evlog map --no-write, whose audit / audit-coverage checks flag sensitive routes that log nothing. Useful as a second pass; keep the greps above for denials and actor shape, which the CLI does not fully cover.

For each match, check:

  • Mutating endpoint without a log.audit() or withAudit() → coverage gap.
  • 403 / Forbidden thrown without a paired log.audit.deny() → silent denial. This is the single most common gap.
  • actor: { type: 'user', id: 'cron' } or hard-coded actors in cron / queue handlers → wrong actor.type. Should be 'system', 'api', or 'agent'.
  • actor.id set to a session id or token instead of the stable user id → forensic ambiguity.
  • log.set({ audit: ... }) without using the helpers → bypasses force-keep, may be dropped by tail-sampling.
  • withAudit() action name in present tense (invoice.refund) is fine; manual log.audit() after the fact should use past tense (invoice.refunded).

Pass 3: Redaction & integrity

rg -n "auditRedactPreset|RedactConfig|paths:\s*\[" --type ts
rg -n "auditDiff\(" --type ts
rg -n "strategy:\s*['\"](?:hmac|hash-chain)" --type ts

Flag if:

  • auditRedactPreset is not merged into the global redact config → Authorization, Cookie, password, token, apiKey, cardNumber, cvv, ssn may leak through audit.changes.
  • auditDiff() is called on objects containing PII fields not listed in redactPaths → leak in the patch payload.
  • HMAC secret is hard-coded or read from process.env.SECRET without a rotation plan / keyId → events become unverifiable after rotation.
  • Hash-chain state is in-memory only → chain restarts each process boot, breaking continuity.

Pass 4: Tests

rg -n "mockAudit\(|toIncludeAuditOf\(" --type ts

Flag if:

  • No tests use mockAudit() → audit pipeline silently drifts unnoticed.
  • Tests only assert success outcomes → denial paths can rot. Every privileged action should have at least one denied-outcome test.
  • Tests assert against RegExp actions broadly → typos in audit.action slip through (an action typo is a missing alert in production).

Reporting the findings

Group findings by severity for the user:

  • P0 (blocker): missing auditOnly wrap on an audit drain, missing auditRedactPreset, denials not logged, no tamper-evident drain in a regulated context.
  • P1 (compliance gap): missing tenant isolation, hash-chain state not persisted, no HMAC rotation, no denial test coverage.
  • P2 (hygiene): action naming inconsistency, in-line actor objects (should use defineAuditAction), missing causationId / correlationId on chained operations.

Then map each finding to the relevant step in the buildout above (e.g. P0 → Step 5 redact, P1 → Step 7 checklist) so the fix is unambiguous.

Common pitfalls

  • Logging only successes. Auditors care most about denials. Pair log.audit() with log.audit.deny() on every negative branch of every check.
  • Leaking PII through changes. auditDiff() runs through RedactConfig, but only if the field paths are listed. Add password, token, apiKey once globally so it's never a per-call-site decision.
  • Treating audits as observability. Don't sample, downsample, or summarise audit events. Force-keep is on by default, don't disable it.
  • Conflating actor.id with the session id. actor.id is the stable user id (or system identity); correlate sessions via context.requestId / context.traceId.
  • Forgetting standalone jobs. Cron, queue workers, CLIs trigger audit-worthy actions too, use audit() or withAudit().
  • Faking the actor type. actor.type: 'user' for cron jobs gets flagged in audits. Use 'system', 'api', or 'agent' accurately.
  • Single global secret with no rotation. HMAC keys must rotate; without a keyId, old events become unverifiable after rotation.
  • One drain that fails takes audits down. Drains must fail in isolation. The default drain: [...] array does this; if you wrap in Promise.all, don't throw on a single rejection. Log it.

Glossary

  • Action: audit.action, the verb-on-noun identifier (invoice.refund).
  • Actor: who/what performed the action (user, system, api, agent).
  • Target: the resource the action was performed on.
  • Outcome: success, failure, or denied.
  • Idempotency key: auto-derived hash of action + actor + target + timestamp; safe retries across drains.
  • Causation id: id of the action that caused this one (admin action → system reactions).
  • Correlation id: shared by every action in one logical operation.
  • Hash-chain: each event's prevHash matches the previous event's hash, forming a verifiable sequence.

Reference

FAQ

What does the build-audit-logs skill do?

Build or review audit trails in TypeScript/JavaScript apps using evlog (pipelines, typed actions, denials, retention, compliance-style reviews). For application code, not for extending the evlog package.

How do I install build-audit-logs?

Run npx skills add https://evlog.dev --skill build-audit-logs in your project. The skills CLI detects the agents you use and copies the skill into the right folder. For Claude Code you can also place the skill folder at ~/.claude/skills/build-audit-logs/ (personal) or .claude/skills/build-audit-logs/ (project).

Which AI agents can use build-audit-logs?

Any agent that supports the open Agent Skills format (a folder with a SKILL.md file), including Claude Code, Codex, Cursor, GitHub Copilot, Gemini CLI, Windsurf, Cline, OpenCode and more.

Is build-audit-logs safe to install?

No partner security audit is available yet. Skills can run commands and scripts, so read the SKILL.md and any bundled files before installing.

Who maintains build-audit-logs?

It is published by evlog.dev in evlog.dev. It has 2,836 recorded installs.

More from evlog.dev

View all →

Review code for logging patterns and suggest evlog adoption. Optionally use the evlog CLI, shipped with the evlog package (evlog init to wire evlog, evlog agents to write the conventions into AGENTS.md, evlog map to score entry-point coverage, --baseline to gate regressions in CI) on Nuxt, Nitro, Next.js, TanStack Start, Hono, Express, and Fastify. Guides setup on those plus SvelteKit, React Router, NestJS, Express, Fastify, Elysia, oRPC, Effect, Cloudflare Workers, AWS Lambda, Astro, and standalone TypeScript. Detects console.log spam, unstructured errors, and missing context. Covers wide events, structured errors, drain adapters (Axiom, OTLP, HyperDX, Sazabi, PostHog, Sentry, Better Stack, Datadog, Loki, ClickHouse, NuxtHub, Memory), sampling, enrichers, and AI SDK integration.

DevOps & Cloud4.7K installs

analyze-logs

evlog.dev

Analyze application logs from the .evlog/logs/ directory. Use when debugging errors, investigating slow requests, understanding request patterns, or answering questions about application behavior. Reads structured NDJSON wide events written by evlog's file system drain.

Popular security skills

View category →

azure-compliance

microsoft/azure-skills

Run Azure compliance and security audits with azqr plus Key Vault expiration checks. Covers best-practice assessment, resource review, policy/compliance validation, and security posture checks. WHEN: compliance scan, security audit, BEFORE running azqr (compliance cli tool), Azure best practices, Key Vault expiration check, expired certificates, expiring secrets, orphaned resources, compliance assessment.

OfficialSecurity567K installs

minimal-run-and-audit

lllllllama/rigorpilot-skills

Rigor Run skill for README-first deep learning repo reproduction. Use when the task is specifically to capture or normalize evidence from the selected smoke test or documented inference or evaluation command and write standardized reprooutputs/ files, including patch notes when repository files changed. Do not use for training execution, initial repo intake, generic environment setup, paper lookup, target selection, hidden scientific-meaning changes, or end-to-end orchestration by itself.

Security477.4K installs

seo-audit

coreyhaines31/marketingskills

When the user wants to audit, review, or diagnose SEO issues on their site. Also use when the user mentions "SEO audit," "technical SEO," "why am I not ranking," "SEO issues," "on-page SEO," "meta tags review," "SEO health check," "my traffic dropped," "lost rankings," "not showing up in Google," "site isn't ranking," "Google update hit me," "page speed," "core web vitals," "crawl errors," "indexing issues," "stuck on page 2," "striking distance keywords," "title tag rewrite," "low CTR," "local SEO," "Google Business Profile," or "map pack." Use this even if the user just says something vague like "my SEO is bad" or "help with SEO" — start with an audit. For building pages at scale to target keywords, see programmatic-seo. For adding structured data, see schema. For AI search optimization, see ai-seo.

Security238.7K installs

firebase-security-rules-auditor

firebase/agent-skills

Audits Firebase (Firestore, Cloud Storage) security rules for vulnerabilities, privilege escalation, role bypasses, create vs update inconsistencies, resource exhaustion, type safety, size limits, and hasOnly ownership checks. Use when auditing/reviewing rules, running red-team rule assessments, or scoring against auditor checklists. Don't use for Firebase CLI (login, deploy), Auth, Crashlytics, Remote Config, or database queries.

OfficialSecurity139.1K installs

audit

pbakaus/impeccable

Run technical quality checks across accessibility, performance, theming, responsive design, and anti-patterns. Generates a scored report with P0-P3 severity ratings and actionable plan. Use when the user wants an accessibility check, performance audit, or technical quality review.

Security81.9K installs

competitors

coreyhaines31/marketingskills

When the user wants to create competitor comparison or alternative pages for SEO and buyer-facing use. Also use when the user mentions 'alternative page,' 'vs page,' 'competitor comparison,' 'comparison page,' '[Product] vs [Product],' '[Product] alternative,' 'competitive landing pages,' 'how do we compare to X,' 'competitor teardown,' 'audit our competitor pages,' 'are our comparison pages out of date,' or 'competitive asset audit.' Use this for any content that positions your product against competitors. Covers four formats: singular alternative, plural alternatives, you vs competitor, and competitor vs competitor. For auditing existing claims (not researching competitors from scratch, which is competitor-profiling; not technical SEO on these pages, which is seo-audit), use the asset audit here. For internal battle cards and sales-specific competitor docs, see sales-enablement.

Security79.3K installs