DEV Community

CRUD5th-273-
CRUD5th-273-

Posted on

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
Enter fullscreen mode Exit fullscreen mode

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

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

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
Enter fullscreen mode Exit fullscreen mode

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}`));
    }
  }
}
Enter fullscreen mode Exit fullscreen mode

6. Run It in CI

"scripts": {
  "validate:rbac": "node rbac-check.js"
}
Enter fullscreen mode Exit fullscreen mode

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

- name: Validate Hasura RBAC
  run: npm run validate:rbac
Enter fullscreen mode Exit fullscreen mode

7. Extending the Ruleset

  • 🔒 Block full-field exposure (columns: '*')
  • 🧪 Verify insert/update/delete have field filters
  • 📊 Export role × table permission matrix
  • 🧬 Compare across environments (dev vs prod metadata)

Final Thoughts

Hasura metadata is auditable. You just need to look.

By statically verifying your RBAC before deployment,

you eliminate entire classes of silent misconfiguration bugs — before they hit production.

Next:

  • Generate HTML or CSV access matrix
  • Integrate with Slack alerting
  • Build a VSCode plugin for live feedback on permission diffs

Fail fast. Trust static. Secure declaratively.

Top comments (0)