npm package discovery and stats viewer.

Discover Tips

  • General search

    [free text search, go nuts!]

  • Package details

    pkg:[package-name]

  • User packages

    @[username]

Sponsor

Optimize Toolset

I’ve always been into building performant and accessible sites, but lately I’ve been taking it extremely seriously. So much so that I’ve been building a tool to help me optimize and monitor the sites that I build to make sure that I’m making an attempt to offer the best experience to those who visit them. If you’re into performant, accessible and SEO friendly sites, you might like it too! You can check it out at Optimize Toolset.

About

Hi, 👋, I’m Ryan Hefner  and I built this site for me, and you! The goal of this site was to provide an easy way for me to check the stats on my npm packages, both for prioritizing issues and updates, and to give me a little kick in the pants to keep up on stuff.

As I was building it, I realized that I was actually using the tool to build the tool, and figured I might as well put this out there and hopefully others will find it to be a fast and useful way to search and browse npm packages as I have.

If you’re interested in other things I’m working on, follow me on Twitter or check out the open source projects I’ve been publishing on GitHub.

I am also working on a Twitter bot for this site to tweet the most popular, newest, random packages from npm. Please follow that account now and it will start sending out packages soon–ish.

Open Software & Tools

This site wouldn’t be possible without the immense generosity and tireless efforts from the people who make contributions to the world and share their work via open source initiatives. Thank you 🙏

© 2026 – Pkg Stats / Ryan Hefner

airlock-auth

v0.1.8

Published

The CI gate for Next.js auth mistakes. Fails your build when a server secret is exposed via NEXT_PUBLIC_*, and warns on mutating routes with no auth check and webhooks with no signature verification. No build required.

Readme

Auth Route Guard

The CI gate for Next.js auth mistakes. It scans your source and fails the build when a server secret is exposed via NEXT_PUBLIC_* — the leak that ships straight into the client bundle. It also warns on mutating route handlers with no auth check and webhook routes that never verify a signature. No build, no runtime, no database.

npx airlock-auth               # scans the current project
npx airlock-auth ./apps/web

What it flags

| Rule | Level | Catches | |------|-------|---------| | public_secret | fail / warn | a server secret exposed via NEXT_PUBLIC_*. Fails the build when the name can NEVER be legitimately public: service_role, any *_SECRET, private/signing/encryption/master key, a connection string for a database or broker — recognized by an engine/alias token (POSTGRES, MYSQL, MONGO, REDIS, CLICKHOUSE, KAFKA, ORACLE, TURSO, plus the stack aliases DB, DIRECT, KV that catch SUPABASE_DB_URL, Prisma's DIRECT_URL, Vercel KV_URL) beside a URL/URI/CONNECTION/DSN suffix. A descriptor in between (POSTGRES_DOCS_URL, REDIS_STATUS_URL) is a public link, not a DSN, and is not flagged. This engine list is curated, not exhaustive — an exotic engine (db2, hbase, firebird…) or an unusually-named connection string may read clean; use --fail-on warn or --allow per your stack. An LLM-provider key in either the OPENAI_KEY or the canonical OPENAI_API_KEY spelling, a Stripe SK/RK, a GitHub PAT, a GCP SA_KEY, a password. Warns when a vendor might ship the name public: a generic *_API_KEY / *_TOKEN, a bare DATABASE_URL (Firebase's is public), a bare ACCESS_KEY (Unsplash's is public). Use --fail-on warn to gate on those too. A known-public vendor key (Supabase anon, Stripe publishable, Paddle client token, Firebase, PostHog…) is exempt — never flagged. A credential trailed by a config word (SERVICE_ROLE_KEY_ROTATION, _MAX) keeps its severity; trailed by a pointer word we cannot resolve (SERVICE_ROLE_KEY_URL, CRON_SECRET_ENDPOINT) it drops to warn. | | unauth_mutation | warn | a mutating route with no auth check — App Router route.ts and Pages Router pages/api | | unauth_server_action | warn | a Server Action ('use server') that writes to the DB with no auth check | | unverified_webhook | warn | a webhook route that never verifies a signature |

By default only fail findings break the build; warnings are printed for review. Most rules here emit warn — including an unauthenticated mutation — so without gating on them they can only ever be printed. Use --fail-on warn (or its alias --strict) to make warnings break the build too, and the fail-on input to do the same in the Action.

No false build breaks by design. The line between fail and warn is the NOUN, not a vendor list: a name that can never be public (service_role, any *_SECRET, a connection string, an LLM key) fails, while a name a vendor might legitimately ship public (a generic *_API_KEY, a bare DATABASE_URL or ACCESS_KEY) warns — so Alchemy, Infura, Weglot and the like never break a real project's CI on first run. Names that are public on purpose — ANON_KEY, PUBLISHABLE, MAPBOX, Google Maps / Firebase client config, web-push VAPID, TURNSTILE, analytics / site keys, and public client tokens/keys of common SDKs (Paddle CLIENT_TOKEN, Stream, Algolia, LiveKit, Liveblocks public key, Segment write key, Sentry DSN) — are not. A strong secret word (PRIVATE, SECRET, SIGNING, ENCRYPTION, or an ADMIN_KEY/ADMIN_TOKEN / SERVER_KEY/SERVER_TOKEN) is always flagged, even on such a vendor — so FIREBASE_PRIVATE_KEY, PADDLE_CLIENT_SECRET, ALGOLIA_ADMIN_KEY, FIREBASE_ADMIN_TOKEN and FCM_SERVER_KEY are caught, while FIREBASE_API_KEY, PADDLE_CLIENT_TOKEN and a plain ADMIN_URL/SERVER_URL are not. (A public analytics write key like SEGMENT_WRITE_KEY is intentionally NOT barred — those are public by design.) Read-only GET handlers are ignored, and a webhook is judged on its signature check, not on "missing auth".

Spelling-insensitive on the suffix. Only the NEXT_PUBLIC_ prefix is matched exactly (Next.js inlines that spelling and no other); the suffix is canonicalized before matching, so every spelling of the same name folds to one verdict — camelCase (serviceRoleKey), snake (SERVICE_ROLE_KEY), kebab, and even ALL-CAPS glued with no separators at all (SERVICEROLEKEY, SECRETKEY, APIKEY) are treated the same. A secret can't dodge the gate by changing case or dropping its underscores.

What it does not cover yet

Auth Route Guard covers the three highest-signal Next.js mistakes. These are not checked yet — review them yourself (or lean on runtime auth + the Airlock Monitor):

  • Authorization correctness — it checks that an auth call is present, not that it's right. IDOR, tenant scoping and role checks are still on you.
  • Auth via middleware only — a route guarded by middleware.ts that never references auth in its own file may warn; use --auth-fn or allow-list it.
  • Secrets exposed by other means — only NEXT_PUBLIC_* names are flagged, not a secret hardcoded in client code or shipped some other way.
  • Read handlers (GET) — a GET that leaks data without auth is not flagged; only mutations are.
  • How a trailing word is read. The suffix is parsed as English, and silence must be earned — the default when anything is uncertain is a warning, never a pass. Concretely:
    • A never-public credential (SERVICE_ROLE, any *_SECRET, private/master key, a connection string, an LLM key) followed by a config word still fails: SERVICE_ROLE_KEY_MAX, STRIPE_WEBHOOK_SIGNING_SECRET_MODE, SERVICE_ROLE_KEY_ROTATION all break the build.
    • A true pointer word (URL, DOCS, HEADER, NAME, ENDPOINT) right after such a credential is ambiguous, not proof it points away — there is no safe NEXT_PUBLIC_SERVICE_ROLE_KEY_URL — so it warns rather than passes. After a name that can be public (API_KEY_HEADER) a pointer clears. Either way a pointer never breaks the build.
    • The known floor: a bare PRIVATE/PASSWORD/TOKEN followed by a config word reads as a config name and is cleared — PASSWORD_MIN_LENGTH, PRIVATE_BETA, TOKEN_REFRESH_INTERVAL, and as a cost DB_PASSWORD_FLAG. Only those three words soften; CREDENTIALS, PASS, PAT and every hard word (SECRET, SIGNING…) do not, so DB_CREDENTIALS_FLAG and CRON_SECRET_ROTATION warn rather than pass.
  • A known-public vendor key is never flagged. Supabase anon, Stripe publishable, Paddle client token, Firebase, PostHog, Mixpanel and the rest are allow-listed by design — a browser key that is public by the vendor's own docs must not break a build. An unknown vendor's *_API_KEY (Alchemy, Infura, Weglot…) warns, not fails — a generic API key may legitimately be a public browser key, so it is surfaced without breaking CI. Gate on it with --fail-on warn, or silence one with --allow NEXT_PUBLIC_THAT_NAME.
  • Pages Router method dispatch we cannot parse — a pages/api handler that writes and never mentions req.method is flagged (it answers every verb). One that does consult req.method in a shape the matcher doesn't recognize (['POST'].includes(req.method), a dispatch table) stays silent. That is a coverage gap, not a verdict — recognized shapes are ===/!==/case, with or without .toUpperCase().
  • Server Action write coverage — each exported action is judged on its own (an auth call in one action no longer clears another). The write signal covers Supabase/knex (.from(...).insert/update/delete), Drizzle (db.insert(...)), Prisma (prisma.x.create/update/delete) and executed raw SQL — a Supabase .rpc('...') write (ambiguous: read or write) or another ORM's .create() is not matched. The write must sit inside the exported action: a write extracted into a module-level helper declared above the exports, then called from an action, is not attributed to it (the write is judged per exported segment). Keep the DB call in the action, or gate the helper itself.
  • Route auth is judged per HANDLER. A GET that calls getUser() next to a naked POST in the same route.ts does not clear the POST — each exported handler is sliced out and judged on its own. (An earlier release judged this per file; that was a real false negative and it is closed.)
  • Directories the walker skips are reported in skipped, never silently omitted. node_modules and dot-directories (.next, .git, .turbo, .vercel) are skipped at any depth. dist, build and coverage are skipped only at the project root — inside app/, they are legitimate route segments, and app/api/build/route.ts is a plausible privileged endpoint that must be checked.
  • Files over 1 MB are skipped and reported, on the assumption that they are generated or minified.

How code is read. Comments, string literals, template literals and regex literals are neutralized before any rule runs, so a signal that only appears inside one of them never counts as real code. If a file ends inside an unterminated string, template or block comment, everything after the opener was unreadable — so that file is reported as unparsable and fails the gate. It is never reported as clean: a security gate must not answer "clean" about text it could not read. (This detection existed but was never wired to a caller in earlier releases, so the promise was true of the code and false of the product.) Type annotations are ignored rather than parsed: this is a tokenizer, not a TypeScript compiler, which is why the package still has zero dependencies.

In CI (GitHub Actions)

- uses: mateuszingano/airlock-auth@v1
  with:
    dir: .
    # allow: /api/health,rule:unauth_mutation

@v1 tracks the latest v0.x release. Pin a specific tag (e.g. @v0.1.8) to lock the version, or run npx --yes airlock-auth . in a step.

Allow-listing intentional cases

Some routes are public on purpose (a health check, a public read). Silence a finding by route path, env name, or rule:

airlock-auth --allow "/api/health,rule:unauth_mutation"
# or: AUTH_GUARD_ALLOW=/api/health airlock-auth

Matching is deliberately precise so one loose token can't hide unrelated leaks:

  • rule:<name> silences a whole rule (e.g. rule:unauth_mutation).
  • A secret (fail) needs the exact env name — --allow key will not silence every secret whose name contains "key"; pass the full NEXT_PUBLIC_…_KEY.
  • A route (warn) matches by path — a token starting with / (/api/health) silences that route.

Oversized files (generated/minified, > 1 MB) are skipped for bounded scan time and listed in the report's skipped — never dropped silently.

Exit codes

0 passed · 1 an exposed secret was found · 2 usage error.


Part of ShipSealed — ship apps that don't leak. MIT licensed.