Static RBAC Validation from Hasura Metadata: Build Your Own CLI Guardrail

Hasura's declarative RBAC is powerful — but dangerously quiet when misconfigured. You might think guest has no write access… until a policy-less table says otherwise. This guide walks you through building a CLI tool that parses Hasura metadata and verifies RBAC rules statically, before deployment. 1. Why Static Validation? Hasura permissions live in metadata/ as YAML or JSON. You can validate them without running the server — ideal for CI pipelines. Use cases: Detect tables with no select restrictions Ensure public role can’t write Enforce field-level allowlists Audit policy completeness per role 2. Directory Structure (exported via Hasura CLI) metadata/ ├── tables/ │ ├── user.yaml │ ├── invoice.yaml ├── actions/ ├── sources.yaml ├── version.yaml Each *.yaml under tables/ contains permissions by role: select_permissions: - role: user permission: columns: [id, email] filter: id: { _eq: X-Hasura-User-Id } 3. CLI Tool Overview We'll build a Node.js script that: Loads all tables/*.yaml Parses permissions per role Applies validation rules Outputs audit results or fails with nonzero exit code 4. Install Dependencies npm init -y npm install yaml fs glob chalk 5. Example CLI Logic import fs from 'fs'; import path from 'path'; import yaml from 'yaml'; import glob from 'glob'; import chalk from 'chalk'; const PERMIT_ROLES = ['admin', 'user']; const metadataPath = './metadata/tables'; const files = glob.sync(`${metadataPath}/*.yaml`); for (const file of files) { const content = fs.readFileSync(file, 'utf8'); const doc = yaml.parse(content); const table = `${doc.table.schema}.${doc.table.name}`; // Check for public access const publicInsert = doc.insert_permissions?.find(p => p.role === 'public'); if (publicInsert) { console.log(chalk.red(`❌ PUBLIC role has insert access on ${table}`)); process.exitCode = 1; } // Check for missing filters for (const role of PERMIT_ROLES) { const sel = doc.select_permissions?.find(p => p.role === role); if (sel && (!sel.permission.filter || Object.keys(sel.permission.filter).length === 0)) { console.log(chalk.yellow(`⚠️ Role ${role} has unfiltered SELECT on ${table}`)); } } } 6. Run It in CI "scripts": { "validate:rbac": "node rbac-check.js" } Then add to .github/workflows/ci.yml or GitLab CI pipeline: - name: Validate Hasura RBAC run: npm run validate:rbac 7. Extending the Ruleset

Mar 30, 2025 - 14:11
 0
Static RBAC Validation from Hasura Metadata: Build Your Own CLI Guardrail

Hasura's declarative RBAC is powerful — but dangerously quiet when misconfigured.

You might think guest has no write access… until a policy-less table says otherwise.

This guide walks you through building a CLI tool that parses Hasura metadata and verifies RBAC rules statically, before deployment.

1. Why Static Validation?

Hasura permissions live in metadata/ as YAML or JSON.

You can validate them without running the server — ideal for CI pipelines.

Use cases:

  • Detect tables with no select restrictions
  • Ensure public role can’t write
  • Enforce field-level allowlists
  • Audit policy completeness per role

2. Directory Structure (exported via Hasura CLI)

metadata/
├── tables/
│   ├── user.yaml
│   ├── invoice.yaml
├── actions/
├── sources.yaml
├── version.yaml

Each *.yaml under tables/ contains permissions by role:

select_permissions:
  - role: user
    permission:
      columns: [id, email]
      filter:
        id: { _eq: X-Hasura-User-Id }

3. CLI Tool Overview

We'll build a Node.js script that:

  • Loads all tables/*.yaml
  • Parses permissions per role
  • Applies validation rules
  • Outputs audit results or fails with nonzero exit code

4. Install Dependencies

npm init -y
npm install yaml fs glob chalk

5. Example CLI Logic

import fs from 'fs';
import path from 'path';
import yaml from 'yaml';
import glob from 'glob';
import chalk from 'chalk';

const PERMIT_ROLES = ['admin', 'user'];
const metadataPath = './metadata/tables';

const files = glob.sync(`${metadataPath}/*.yaml`);

for (const file of files) {
  const content = fs.readFileSync(file, 'utf8');
  const doc = yaml.parse(content);
  const table = `${doc.table.schema}.${doc.table.name}`;

  // Check for public access
  const publicInsert = doc.insert_permissions?.find(p => p.role === 'public');
  if (publicInsert) {
    console.log(chalk.red(`❌ PUBLIC role has insert access on ${table}`));
    process.exitCode = 1;
  }

  // Check for missing filters
  for (const role of PERMIT_ROLES) {
    const sel = doc.select_permissions?.find(p => p.role === role);
    if (sel && (!sel.permission.filter || Object.keys(sel.permission.filter).length === 0)) {
      console.log(chalk.yellow(`⚠️  Role ${role} has unfiltered SELECT on ${table}`));
    }
  }
}

6. Run It in CI

"scripts": {
  "validate:rbac": "node rbac-check.js"
}

Then add to .github/workflows/ci.yml or GitLab CI pipeline:

- name: Validate Hasura RBAC
  run: npm run validate:rbac

7. Extending the Ruleset