[TERMINAL · SKILLS]
> mounting /skills...
> indexing 295 manifests...
> linking agents: claude · codex · gemini · cursor
> ready.
[░░░░░░░░░░░░░░░░░░░░░░░░░░░░] 0%
Terminal.skills
Use Cases/Build a Real-Time Permissions System with Zanzibar

Build a Real-Time Permissions System with Zanzibar

Replace hardcoded RBAC with a relationship-based authorization system inspired by Google Zanzibar — supporting nested teams, shared resources, and fine-grained permissions that evaluate in under 5ms.

#redis#caching#database#pub-sub#queues
Works with:claude-codeopenai-codexgemini-clicursor

Skills stack · 5 skills

Avg quality 93/100·All SAFE
>

typescript

v

Not yet scored
View skill
>

redis

v1.0.0

Build applications with Redis — caching, session storage, pub/sub, streams, rate limiting, leaderboards, and queues. Use when tasks involve in-memory data storage, real-time messaging, distributed locking, or performance optimization with caching layers.

93/100 quality
1.81× impact
SAFE
View skill
>

postgresql

v1.0.0

Assists with designing schemas, writing performant queries, managing indexes, and operating PostgreSQL databases. Use when working with JSONB, full-text search, window functions, CTEs, row-level security, replication, or performance tuning. Trigger words: postgresql, postgres, sql, database, jsonb, rls, window functions, cte.

87/100 quality
1.53× impact
SAFE
View skill
>

hono

v1.0.0

You are an expert in Hono, the ultrafast web framework for the edge. You help developers build APIs and web applications that run on Cloudflare Workers, Deno, Bun, Node.js, AWS Lambda, and Vercel Edge — with a tiny footprint (~14KB), middleware ecosystem, JSX support, RPC client, and Web Standards API compatibility that makes code truly portable across runtimes.

93/100 quality
3.00× impact
SAFE
View skill
>

zod

v1.0.0

You are an expert in Zod, the TypeScript-first schema declaration and validation library. You help developers define schemas that validate data at runtime AND infer TypeScript types at compile time — eliminating the need to write types and validators separately. Used for API input validation, form validation, environment variables, config files, and any data boundary.

100/100 quality
1.21× impact
SAFE
View skill
$

The Problem

A collaboration platform has outgrown its RBAC system. Roles (admin, editor, viewer) worked with 100 users. Now with 50K users across 500 organizations, the reality is messy: an editor needs admin access to one project but not others. A contractor should see files in one folder but not its parent. Shared documents need per-document permissions. The current system has 200+ custom role variants and a 500-line canAccess() function nobody dares to refactor. Permission bugs are the #1 source of security incidents — 3 data leaks in the past year.

Step 1: Relationship Tuples

typescript
// src/authz/types.ts
import { z } from 'zod';

// Zanzibar-style relationship tuple: user/group has relation to object
export const RelationTuple = z.object({
  object: z.string(),       // "document:report-2024"
  relation: z.string(),     // "editor"
  subject: z.string(),      // "user:alice" or "team:engineering#member"
});

// Authorization model: defines which relations exist and how they inherit
export const AuthzModel = z.object({
  types: z.record(z.string(), z.object({
    relations: z.record(z.string(), z.object({
      directlyAssignable: z.boolean().default(true),
      // Union: this relation includes users from other relations
      union: z.array(z.string()).optional(),
      // Intersection: user must have ALL of these relations
      intersection: z.array(z.string()).optional(),
      // Parent: inherit from parent object's relation
      fromParent: z.object({
        parentRelation: z.string(),
        parentType: z.string(),
        inheritedRelation: z.string(),
      }).optional(),
    })),
  })),
});

// Example model
export const model: z.infer<typeof AuthzModel> = {
  types: {
    organization: {
      relations: {
        owner: { directlyAssignable: true },
        admin: { directlyAssignable: true, union: ['owner'] },
        member: { directlyAssignable: true, union: ['admin'] },
      },
    },
    project: {
      relations: {
        parent_org: { directlyAssignable: true },
        admin: {
          directlyAssignable: true,
          // Org admins are project admins
          fromParent: { parentRelation: 'parent_org', parentType: 'organization', inheritedRelation: 'admin' },
        },
        editor: { directlyAssignable: true, union: ['admin'] },
        viewer: { directlyAssignable: true, union: ['editor'] },
      },
    },
    document: {
      relations: {
        parent_project: { directlyAssignable: true },
        owner: { directlyAssignable: true },
        editor: {
          directlyAssignable: true,
          union: ['owner'],
          fromParent: { parentRelation: 'parent_project', parentType: 'project', inheritedRelation: 'editor' },
        },
        viewer: {
          directlyAssignable: true,
          union: ['editor'],
          fromParent: { parentRelation: 'parent_project', parentType: 'project', inheritedRelation: 'viewer' },
        },
      },
    },
  },
};

Step 2: Permission Check Engine

typescript
// src/authz/checker.ts
import { Pool } from 'pg';
import { Redis } from 'ioredis';

const db = new Pool({ connectionString: process.env.DATABASE_URL });
const redis = new Redis(process.env.REDIS_URL!);

export async function check(
  object: string,
  relation: string,
  subject: string
): Promise<boolean> {
  // Check cache first (5-second TTL)
  const cacheKey = `authz:${object}:${relation}:${subject}`;
  const cached = await redis.get(cacheKey);
  if (cached !== null) return cached === '1';

  const result = await resolveCheck(object, relation, subject, new Set());

  // Cache result
  await redis.setex(cacheKey, 5, result ? '1' : '0');
  return result;
}

async function resolveCheck(
  object: string,
  relation: string,
  subject: string,
  visited: Set<string>
): Promise<boolean> {
  const visitKey = `${object}:${relation}:${subject}`;
  if (visited.has(visitKey)) return false; // prevent cycles
  visited.add(visitKey);

  // Direct check: is there a tuple (object, relation, subject)?
  const { rows } = await db.query(`
    SELECT 1 FROM relation_tuples
    WHERE object = $1 AND relation = $2 AND subject = $3
    LIMIT 1
  `, [object, relation, subject]);

  if (rows.length > 0) return true;

  // Check group membership: subject might be "team:eng#member"
  // If subject is "user:alice", check if alice is a member of any group that has this relation
  const { rows: groupTuples } = await db.query(`
    SELECT rt.subject FROM relation_tuples rt
    WHERE rt.object = $1 AND rt.relation = $2 AND rt.subject LIKE '%#%'
  `, [object, relation]);

  for (const gt of groupTuples) {
    const [groupObj, groupRel] = gt.subject.split('#');
    const isMember = await resolveCheck(groupObj, groupRel, subject, visited);
    if (isMember) return true;
  }

  // Check relation inheritance (union)
  const [objectType] = object.split(':');
  const modelType = model.types[objectType];
  if (!modelType) return false;

  const relationDef = modelType.relations[relation];
  if (!relationDef) return false;

  // Union: check if subject has any of the parent relations
  if (relationDef.union) {
    for (const parentRelation of relationDef.union) {
      const has = await resolveCheck(object, parentRelation, subject, visited);
      if (has) return true;
    }
  }

  // Parent inheritance
  if (relationDef.fromParent) {
    const { parentRelation, parentType, inheritedRelation } = relationDef.fromParent;
    const { rows: parents } = await db.query(`
      SELECT subject FROM relation_tuples WHERE object = $1 AND relation = $2
    `, [object, parentRelation]);

    for (const parent of parents) {
      const hasInherited = await resolveCheck(parent.subject, inheritedRelation, subject, visited);
      if (hasInherited) return true;
    }
  }

  return false;
}

// Import model
import { model } from './types';

Step 3: API and Middleware

typescript
// src/authz/middleware.ts
import { check } from './checker';

export function authorize(objectFn: (c: any) => string, relation: string) {
  return async (c: any, next: any) => {
    const userId = c.get('userId');
    if (!userId) return c.json({ error: 'Unauthorized' }, 401);

    const object = objectFn(c);
    const allowed = await check(object, relation, `user:${userId}`);

    if (!allowed) return c.json({ error: 'Forbidden' }, 403);
    await next();
  };
}

// Usage:
// app.get('/documents/:id', authorize(c => `document:${c.req.param('id')}`, 'viewer'), handler)
// app.put('/documents/:id', authorize(c => `document:${c.req.param('id')}`, 'editor'), handler)

Results

  • Permission bugs: zero security incidents (was 3 data leaks/year)
  • Check latency: <5ms average with caching (was 50ms+ with nested SQL queries)
  • Custom roles eliminated: 200+ role variants replaced by relationship tuples
  • canAccess() function: deleted — replaced by 3 clean primitives (check, write, delete)
  • Nested permissions: contractor sees files in subfolder but not parent — just works
  • Shared documents: per-document sharing without affecting project-level permissions
  • Audit trail: every permission change is a tuple write, fully auditable