Skip to main content

Framework-agnostic linter and testing toolkit for Postgres Row-Level Security.

Project description

pgrls

Framework-agnostic linter and testing toolkit for Postgres Row-Level Security.

Status: 0.0.6 — twelve rules (SEC001–SEC010, PERF001, HYG001) across error, warning, and info severities. Text, JSON, and SARIF output for CI integrations. The test / diff commands are on the roadmap below.

Install

pip install pgrls

Requires Python 3.11+.

Usage

Point pgrls at any Postgres database:

export DATABASE_URL="postgres://user:pass@host:5432/db"
pgrls lint

Or pass the URL directly:

pgrls lint --database-url "postgres://user:pass@host:5432/db"

Limit the scan to specific schemas:

pgrls lint --schemas public,tenant

Point at a non-default config file, or pick an output format:

pgrls lint --config ./config/pgrls.toml --format text    # human-readable (default)
pgrls lint --config ./config/pgrls.toml --format json    # machine-readable for CI
pgrls lint --config ./config/pgrls.toml --format sarif   # GitHub Code Scanning

Example output

Text (default):

  ERROR  SEC001  public.users
         Table public.users does not have row-level security enabled.
         Add ENABLE ROW LEVEL SECURITY or include the table in
         [lint.rules.SEC001].allowlist if it is a public reference table.

pgrls: 1 error.

JSON (--format json):

{
  "violations": [
    {
      "rule_id": "SEC001",
      "severity": "error",
      "title": "RLS not enabled on table",
      "message": "Table public.users does not have row-level security enabled. Add ENABLE ROW LEVEL SECURITY or include the table in [lint.rules.SEC001].allowlist if it is a public reference table.",
      "location": "public.users"
    }
  ],
  "summary": { "errors": 1, "warnings": 0, "infos": 0, "total": 1 }
}

The JSON shape is the public CI contract — top-level keys, per-violation keys, and summary keys are stable across releases. Pipe through jq to filter, count, or transform; ship to a dashboard; upload as a build artifact.

SARIF (--format sarif) emits a SARIF v2.1.0 document. GitHub Code Scanning, Azure DevOps, and other static-analysis aggregators consume it directly — see the GitHub Actions recipe below for the upload step that puts findings inline on PRs.

Exit code is 1 when any violation meets or exceeds fail_on (default warning).

Configuration

Drop a pgrls.toml next to your project. See pgrls.example.toml in the repo for a fully commented version.

[database]
url = "$DATABASE_URL"
schemas = ["public"]

[lint]
disable = []
fail_on = "warning"

[lint.rules.SEC001]
allowlist = ["countries", "currencies"]

Rules

pgrls lint ships these rules:

ID Severity Catches
SEC001 error Tables in scanned schemas with RLS disabled
SEC002 error Tables with RLS enabled but FORCE ROW LEVEL SECURITY off
SEC003 error Permissive policies granted to PUBLIC
SEC004 error Inverted auth check (Lovable CVE pattern) in USING
SEC005 warning Policy expression has no own-column reference
SEC006 error INSERT/UPDATE/ALL policies with no WITH CHECK
SEC007 info All policies on a table are permissive (no RESTRICTIVE floor)
SEC008 warning Policy USING clause is constant true
SEC009 warning RLS enabled but no policies defined (silent deny-all)
SEC010 warning Policy USING clause is constant false (deny-all anti-pattern)
PERF001 warning Auth function called per-row in policy USING (unwrapped)
HYG001 error Policies referencing columns that don't exist on the table

For canonical SQL fixes per rule, see AGENTS.md. For per-rule configuration options (allowlists, etc.), see pgrls.example.toml.

For per-release changes, see CHANGELOG.md.

CI integration

pgrls is designed to live in your CI alongside any other linter. It needs a Postgres database with your schema applied; it then connects, introspects, and exits non-zero if any rule at or above fail_on (default warning) fires.

pre-commit

# .pre-commit-config.yaml
repos:
  - repo: https://github.com/pgrls/pgrls
    rev: v0.0.6
    hooks:
      - id: pgrls-lint
        # pgrls hits a real database, so most teams scope this to
        # `pre-push` rather than every commit.
        stages: [pre-push]
        args:
          - --database-url=$DATABASE_URL
          - --config=pgrls.toml

GitHub Actions

# .github/workflows/pgrls.yml
name: pgrls
on: [push, pull_request]
jobs:
  lint:
    runs-on: ubuntu-latest
    services:
      postgres:
        image: postgres:16-alpine
        env:
          POSTGRES_USER: ci
          POSTGRES_PASSWORD: ci
          POSTGRES_DB: ci
        ports: ["5432:5432"]
        options: >-
          --health-cmd pg_isready
          --health-interval 10s
          --health-retries 5
    env:
      DATABASE_URL: postgres://ci:ci@localhost:5432/ci
    steps:
      - uses: actions/checkout@v4
      - uses: actions/setup-python@v5
        with:
          python-version: "3.11"
      - run: pip install pgrls
      - name: Apply schema
        run: psql "$DATABASE_URL" -v ON_ERROR_STOP=1 -f migrations/all.sql
      - name: Lint RLS
        run: pgrls lint --format sarif > pgrls.sarif
      - name: Upload SARIF for code scanning
        uses: github/codeql-action/upload-sarif@v3
        if: always()
        with:
          sarif_file: pgrls.sarif

The SARIF upload puts findings inline on the PR as code-scanning alerts — no extra dashboard plumbing. Use --format json instead of --format sarif if you want to pipe to jq, build your own dashboard, or keep the report as a build artifact.

Roadmap

  • More lint rules. Continued expansion of the SEC / PERF / HYG catalog. Markdown output. Polished error messages.
  • pgrls test. Code-first RLS test DSL for Python, TypeScript, and Go.
  • pgrls diff. Semantic policy diff between branches with DANGEROUS / BREAKING / SAFE classification.

License

MIT — see LICENSE.

Project details


Download files

Download the file for your platform. If you're not sure which to choose, learn more about installing packages.

Source Distribution

pgrls-0.0.6.tar.gz (94.1 kB view details)

Uploaded Source

Built Distribution

If you're not sure about the file name format, learn more about wheel file names.

pgrls-0.0.6-py3-none-any.whl (36.2 kB view details)

Uploaded Python 3

File details

Details for the file pgrls-0.0.6.tar.gz.

File metadata

  • Download URL: pgrls-0.0.6.tar.gz
  • Upload date:
  • Size: 94.1 kB
  • Tags: Source
  • Uploaded using Trusted Publishing? No
  • Uploaded via: twine/6.2.0 CPython/3.11.14

File hashes

Hashes for pgrls-0.0.6.tar.gz
Algorithm Hash digest
SHA256 200471ec8d653a5f30c79995f678055f179ac8d35f5c335a47993b175f9c9259
MD5 37fbce4f8505158d30128c59682de03d
BLAKE2b-256 6a2b66e7df0199758690365345f0260f6daaeebe5559f43c3deb4c41034cfb9c

See more details on using hashes here.

File details

Details for the file pgrls-0.0.6-py3-none-any.whl.

File metadata

  • Download URL: pgrls-0.0.6-py3-none-any.whl
  • Upload date:
  • Size: 36.2 kB
  • Tags: Python 3
  • Uploaded using Trusted Publishing? No
  • Uploaded via: twine/6.2.0 CPython/3.11.14

File hashes

Hashes for pgrls-0.0.6-py3-none-any.whl
Algorithm Hash digest
SHA256 d4092f700579a5852307f8c24428cfd5fab11865fc60f7133fceaef03d3df6dc
MD5 aa890690876d167a41e1de2e9d017f3e
BLAKE2b-256 ab6654efeabe66926428dee9b40dcc68123c7342d2f4104d1aade5119ca433e2

See more details on using hashes here.

Supported by

AWS Cloud computing and Security Sponsor Datadog Monitoring Depot Continuous Integration Fastly CDN Google Download Analytics Pingdom Monitoring Sentry Error logging StatusPage Status page