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-rls

v0.1.2

Published

The CI gate for Supabase RLS. Fails your build if a table ships without Row Level Security or a policy is permissive (USING (true)).

Readme

Airlock RLS — the CI gate for Supabase

test License: MIT

Scanners check that a policy exists. Airlock proves it works — and blocks the merge if it doesn't.

Airlock fails your CI when a table ships without Row Level Security, or when a policy is permissive (USING (true) / WITH CHECK (true) — the always-true rules that let every API role through and quietly defeat tenant isolation).

It's not another scanner you read a report from. It's a gate: the migration doesn't reach production with an exposed table.


Why this exists

The #1 Supabase security footgun is shipping a table to the public schema with RLS off, or a policy that's technically present but logically wide open. Native advisors and free scanners tell you after the fact. Airlock runs in CI and fails the build, so the leak never merges.

What it checks

Airlock audits the logic of your policies, not just their presence.

Fails the build (exposure):

  1. rls_disabled — a table with RLS off; every row exposed to the API roles.
  2. permissive_true — a policy USING (true) / WITH CHECK (true).
  3. anon_unscoped — an anon-readable policy that doesn't scope to the user (no auth.uid()) — everyone reads every row, even when it isn't literally true.
  4. anon_read_leak (DAST) — with an anon key, Airlock actually reads each table over the REST API; a returned row is a proven leak, not an inference.

Warns (worth a review):

  • authenticated_unscoped — any logged-in user reads all rows (role-only check).
  • write_unchecked — an INSERT/UPDATE policy with no WITH CHECK guard.
  • public_bucket — a public storage bucket.
  • security_definer — a function that runs as its owner and can bypass RLS.

Intentionally-public policies (a status page, a contact form) can be waved through with an allow-list, so the gate stays honest without crying wolf.

The DAST pass (prove it, don't infer it)

Give Airlock a project URL and an anon key and it runs the dynamic check the static scanners can't — it reads each table as an anonymous attacker would:

airlock "$SUPABASE_DB_URL" --url "$SUPABASE_URL" --anon-key "$SUPABASE_ANON_KEY"

Use it as a GitHub Action (the gate)

Add your Supabase Postgres connection string as a repo secret named SUPABASE_DB_URL (a read-only role is enough — the audit only reads pg_tables and pg_policies), then drop this in .github/workflows/rls-gate.yml:

name: RLS Gate
on: [push, pull_request]

jobs:
  rls-gate:
    runs-on: ubuntu-latest
    steps:
      - uses: mateuszingano/airlock-rls@v1
        with:
          db-url: ${{ secrets.SUPABASE_DB_URL }}
          # allow: public_read,status_select   # optional
          # schema: public                      # optional

If any table is exposed or any policy is permissive, the job exits non-zero and the merge is blocked. A full example lives in examples/rls-gate.yml.

Action inputs

| Input | Required | Default | Description | | -------------- | -------- | ---------- | ------------------------------------------------------------------ | | db-url | yes | — | Postgres connection string for the project to audit. | | allow | no | '' | Comma-separated policy names that are permissive on purpose. | | schema | no | public | Schema to audit. | | node-version | no | 20 | Node.js version used to run the audit. |


Use it as a CLI (local / any CI)

# via npx (no install)
SUPABASE_DB_URL=postgresql://... npx airlock-rls

# or install it
npm i -D airlock-rls
SUPABASE_DB_URL=postgresql://... npx airlock

# pass the URL directly
airlock postgresql://postgres:[email protected]:54322/postgres

# machine-readable output
airlock --json

Get the URL from supabase status (local) or your project's connection string.

Options

--allow <names>    Policy names to treat as intentionally permissive
                   (also read from $RLS_AUDIT_ALLOW).
--schema <name>    Schema to audit (default: public).
--json             Print the result as JSON instead of a report.
-h, --help         Show help.
-v, --version      Show the version.

Exit codes

| Code | Meaning | | ---- | --------------------------------------------------- | | 0 | Passed — no exposure found. | | 1 | Failed — at least one exposed table or permissive policy. | | 2 | Usage / connection error (bad args, no URL, DB unreachable). |


Use it as a library

import { audit } from 'airlock-rls'

const result = await audit({ dbUrl: process.env.SUPABASE_DB_URL, schema: 'public' })
if (!result.passed) {
  console.error(`${result.problems} issue(s)`, result.tablesWithoutRls, result.permissive)
}

audit() returns { schema, tablesWithoutRls, permissive, allowed, problems, passed } and never exits the process — you decide how to report.


What's free vs. paid

Airlock is open core. This gate — the CLI and the GitHub Action — is free and MIT-licensed, forever. Paid tiers (coming) add the pieces a one-shot CI run can't cover:

  • Logic audit — deeper policy analysis beyond always-true.
  • Continuous monitoring — a scheduled run that alerts on drift when a new migration reopens a hole between CI runs.

Development

npm install
npm test        # unit tests, no database required
npm start -- --help

The audit logic (src/audit.mjs) is framework-free and split into a pure core (buildResult, tested without a DB) and the thin audit() that talks to Postgres.

License

MIT © ZINGUI