When debugging or auditing GraphQL APIs — especially in Hasura —

the key question is often:

“Which roles can access which fields on which types?”

Answering this by hand is slow, error-prone, and unscalable.

This guide shows how to auto-generate a Role × Field access matrix, export it as CSV/HTML, and visualize it interactively.


1. Objective: Matrix Output

Example table:

Role Table Field SELECT INSERT UPDATE DELETE
user users id
user users email
public posts title
admin users password_hash

2. Data Source: Hasura Metadata

Permissions live in:

metadata/
├── tables/
│   ├── users.yaml
│   ├── posts.yaml

Each file contains permissions by role:

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

3. CLI Generator Script (Node.js)

Install dependencies:

npm install yaml glob chalk fs

Script outline:

// rbac-matrix.js
import fs from 'fs';
import yaml from 'yaml';
import glob from 'glob';

const matrix = [];

const files = glob.sync('./metadata/tables/*.yaml');
for (const file of files) {
  const table = file.split('/').pop().replace('.yaml', '');
  const doc = yaml.parse(fs.readFileSync(file, 'utf8'));

  ['select', 'insert', 'update', 'delete'].forEach(action => {
    const perms = doc[`${action}_permissions`] || [];
    perms.forEach(p => {
      const role = p.role;
      const cols = p.permission?.columns || [];
      cols.forEach(col => {
        const row = matrix.find(r => r.role === role && r.table === table && r.field === col);
        if (row) {
          row[action.toUpperCase()] = '✅';
        } else {
          matrix.push({
            role,
            table,
            field: col,
            SELECT: '',
            INSERT: '',
            UPDATE: '',
            DELETE: '',
            [action.toUpperCase()]: '✅'
          });
        }
      });
    });
  });
}

const csv = [
  ['Role', 'Table', 'Field', 'SELECT', 'INSERT', 'UPDATE', 'DELETE'],
  ...matrix.map(r => [r.role, r.table, r.field, r.SELECT, r.INSERT, r.UPDATE, r.DELETE])
].map(row => row.join(',')).join('\n');

fs.writeFileSync('rbac-matrix.csv', csv);
console.log('✅ RBAC matrix written to rbac-matrix.csv');

4. Optional: HTML Table Renderer

Convert CSV → HTML table with filters (use DataTables.js or React):

id="rbac">
  


  $('#rbac').DataTable();

Use for:

  • Visual review by security teams
  • Review in PRs or audit sessions

5. CI Integration

Add as a script in package.json:

"scripts": {
  "rbac:matrix": "node rbac-matrix.js"
}

Then include in CI for drift detection / snapshot comparison.


Final Thoughts

Field-level RBAC is a security asset — but only if it’s visible.

This matrix gives devs, security, and auditors a shared source of truth.

What’s visible becomes improvable.

In future posts:

  • Visual diff between dev/prod RBAC matrices
  • Mapping role → field → query impact
  • Live explorer for GraphQL access previews

Render the matrix. Detect the drift. Own the access graph.