.agents/skills/bknd-assign-permissions/SKILL.md
Use when assigning permissions to roles in Bknd. Covers permission syntax (simple strings, extended format), permission effects (allow/deny), policies with conditions, entity-specific permissions, and fine-grained access control patterns.
npx skillsauth add cameronapak/cultivate-fellowship bknd-assign-permissionsInstall this skill globally with one command. Works with Claude Code, Cursor, and Windsurf.
3 of 9 scanners reported clean
Some scanners were skipped, did not run, or reported a non-clean status. Review each row below.
Configure detailed permissions for roles using simple strings, extended format with effects, and conditional policies.
auth: { enabled: true })guard: { enabled: true })UI steps: Admin Panel > Auth > Roles > Select role
Note: Permission assignment requires code mode. UI is read-only.
Assign basic permissions as string array:
import { serve } from "bknd/adapter/bun";
import { em, entity, text } from "bknd";
const schema = em({
posts: entity("posts", { title: text().required() }),
});
serve({
connection: { url: "file:data.db" },
config: {
data: schema.toJSON(),
auth: {
enabled: true,
guard: { enabled: true },
roles: {
editor: {
implicit_allow: false,
permissions: [
"data.entity.read", // Read any entity
"data.entity.create", // Create in any entity
"data.entity.update", // Update any entity
// No delete permission
],
},
},
},
},
});
| Permission | Filterable | Description |
|------------|------------|-------------|
| data.entity.read | Yes | Read entity records |
| data.entity.create | Yes | Create new records |
| data.entity.update | Yes | Update existing records |
| data.entity.delete | Yes | Delete records |
| data.database.sync | No | Sync database schema |
| data.raw.query | No | Execute raw SELECT |
| data.raw.mutate | No | Execute raw INSERT/UPDATE/DELETE |
Filterable means you can add conditions/filters via policies.
Use objects for explicit allow/deny effects:
{
roles: {
moderator: {
implicit_allow: false,
permissions: [
{ permission: "data.entity.read", effect: "allow" },
{ permission: "data.entity.update", effect: "allow" },
{ permission: "data.entity.delete", effect: "deny" }, // Explicit deny
],
},
},
}
| Effect | Description |
|--------|-------------|
| allow | Grant the permission (default) |
| deny | Explicitly block the permission |
Deny overrides allow - useful when implicit_allow: true but you want to block specific actions.
Add policies for fine-grained control:
{
roles: {
content_editor: {
implicit_allow: false,
permissions: [
{
permission: "data.entity.read",
effect: "allow",
policies: [
{
description: "Only read posts and comments",
condition: { entity: { $in: ["posts", "comments"] } },
effect: "allow",
},
],
},
{
permission: "data.entity.create",
effect: "allow",
policies: [
{
condition: { entity: { $in: ["posts", "comments"] } },
effect: "allow",
},
],
},
],
},
},
}
{
description?: string, // Human-readable (optional)
condition?: ObjectQuery, // When policy applies
effect: "allow" | "deny" | "filter",
filter?: ObjectQuery, // Row filter (for effect: "filter")
}
| Effect | Purpose |
|--------|---------|
| allow | Grant when condition met |
| deny | Block when condition met |
| filter | Apply row-level filter to results |
| Operator | Description | Example |
|----------|-------------|---------|
| $eq | Equal | { entity: { $eq: "posts" } } |
| $ne | Not equal | { entity: { $ne: "users" } } |
| $in | In array | { entity: { $in: ["posts", "comments"] } } |
| $nin | Not in array | { entity: { $nin: ["users", "secrets"] } } |
| $gt | Greater than | { age: { $gt: 18 } } |
| $gte | Greater or equal | { level: { $gte: 5 } } |
| $lt | Less than | { count: { $lt: 100 } } |
| $lte | Less or equal | { priority: { $lte: 3 } } |
Reference runtime context with @variable:
| Placeholder | Description |
|-------------|-------------|
| @user.id | Current user's ID |
| @user.email | Current user's email |
| @user.role | Current user's role |
| @entity | Current entity name |
| @id | Current record ID |
Example - user can only update their own profile:
{
permissions: [
{
permission: "data.entity.update",
effect: "allow",
policies: [
{
condition: { entity: "users", "@id": "@user.id" },
effect: "allow",
},
],
},
],
}
Grant different permissions per entity:
{
roles: {
blog_author: {
implicit_allow: false,
permissions: [
// Full CRUD on posts
{
permission: "data.entity.read",
effect: "allow",
policies: [{ condition: { entity: "posts" }, effect: "allow" }],
},
{
permission: "data.entity.create",
effect: "allow",
policies: [{ condition: { entity: "posts" }, effect: "allow" }],
},
{
permission: "data.entity.update",
effect: "allow",
policies: [{ condition: { entity: "posts" }, effect: "allow" }],
},
{
permission: "data.entity.delete",
effect: "allow",
policies: [{ condition: { entity: "posts" }, effect: "allow" }],
},
// Read-only on categories
{
permission: "data.entity.read",
effect: "allow",
policies: [{ condition: { entity: "categories" }, effect: "allow" }],
},
],
},
},
}
{
roles: {
viewer: {
implicit_allow: false,
permissions: ["data.entity.read"],
},
},
}
{
roles: {
contributor: {
implicit_allow: false,
permissions: [
"data.entity.read",
"data.entity.create",
"data.entity.update",
{ permission: "data.entity.delete", effect: "deny" },
],
},
},
}
{
roles: {
admin: {
implicit_allow: true, // Allow all by default
permissions: [
// But deny raw database access
{ permission: "data.raw.query", effect: "deny" },
{ permission: "data.raw.mutate", effect: "deny" },
],
},
},
}
{
roles: {
content_manager: {
implicit_allow: false,
permissions: [
// Content entities: full CRUD
{
permission: "data.entity.read",
effect: "allow",
policies: [{
condition: { entity: { $in: ["posts", "pages", "comments", "media"] } },
effect: "allow",
}],
},
{
permission: "data.entity.create",
effect: "allow",
policies: [{
condition: { entity: { $in: ["posts", "pages", "comments", "media"] } },
effect: "allow",
}],
},
{
permission: "data.entity.update",
effect: "allow",
policies: [{
condition: { entity: { $in: ["posts", "pages", "comments", "media"] } },
effect: "allow",
}],
},
{
permission: "data.entity.delete",
effect: "allow",
policies: [{
condition: { entity: { $in: ["posts", "pages", "comments"] } }, // No media delete
effect: "allow",
}],
},
],
},
},
}
{
roles: {
user: {
implicit_allow: false,
permissions: [
// Can read most entities
"data.entity.read",
// But never access secrets entity
{
permission: "data.entity.read",
effect: "deny",
policies: [{
condition: { entity: "secrets" },
effect: "deny",
}],
},
],
},
},
}
For complex role definitions:
// helpers/permissions.ts
type EntityPermission = "read" | "create" | "update" | "delete";
function entityPermissions(
entities: string[],
actions: EntityPermission[]
) {
const permMap: Record<EntityPermission, string> = {
read: "data.entity.read",
create: "data.entity.create",
update: "data.entity.update",
delete: "data.entity.delete",
};
return actions.map((action) => ({
permission: permMap[action],
effect: "allow" as const,
policies: [{
condition: { entity: { $in: entities } },
effect: "allow" as const,
}],
}));
}
// Usage
{
roles: {
blog_author: {
implicit_allow: false,
permissions: [
...entityPermissions(["posts", "comments"], ["read", "create", "update"]),
...entityPermissions(["categories", "tags"], ["read"]),
],
},
},
}
Test permission assignments:
1. Login as user with role:
curl -X POST http://localhost:7654/api/auth/password/login \
-H "Content-Type: application/json" \
-d '{"email": "[email protected]", "password": "password123"}'
2. Test allowed permission:
curl http://localhost:7654/api/data/posts \
-H "Authorization: Bearer <token>"
# Should return 200 with data
3. Test denied permission:
curl -X DELETE http://localhost:7654/api/data/posts/1 \
-H "Authorization: Bearer <token>"
# Should return 403 Forbidden
4. Test entity-specific permission:
# If only posts/comments allowed:
curl http://localhost:7654/api/data/users \
-H "Authorization: Bearer <token>"
# Should return 403 if users entity not in allowed list
Problem: Changed permissions but old behavior persists
Fix: Restart server - role config is loaded at startup:
# Stop and restart
bknd run
Problem: Deny effect not blocking access
Fix: Check policy condition - deny only applies when condition matches:
// WRONG - no condition, may not match
{ permission: "data.entity.delete", effect: "deny" }
// CORRECT - simple deny at permission level
{
permissions: [
"data.entity.read",
"data.entity.create",
// Don't include delete at all
],
}
Problem: Entity-specific permission not working
Fix: Verify entity name matches exactly:
// WRONG - entity name case matters
{ condition: { entity: "Posts" } }
// CORRECT - use exact entity name
{ condition: { entity: "posts" } }
Problem: Confusing behavior with multiple policies
Fix: Understand evaluation order - first matching policy wins:
{
policies: [
// More specific first
{ condition: { entity: "secrets" }, effect: "deny" },
// General fallback last
{ effect: "allow" },
],
}
Problem: @user.id appearing literally in filter
Fix: Variables only work in filter and condition fields:
// CORRECT usage
{
condition: { "@id": "@user.id" }, // Works
filter: { user_id: "@user.id" }, // Works
}
DO:
$in operator for multiple entitiesimplicit_allowDON'T:
data.raw.* to non-admin roles (SQL injection risk)implicit_allow: true with deny policies (confusing)development
Use btca (Better Context App) to efficiently query and learn from the bknd backend framework. Use when working with bknd for (1) Understanding data module and schema definitions, (2) Implementing authentication and authorization, (3) Setting up media file handling, (4) Configuring adapters (Node, Cloudflare, etc.), (5) Learning from bknd source code and examples, (6) Debugging bknd-specific issues
development
Use when configuring webhook integrations in Bknd. Covers receiving incoming webhooks via HTTP triggers, sending outgoing webhooks with FetchTask, event-triggered webhooks on data changes, signature verification, retry patterns, and async processing.
development
Use when encountering Bknd errors, getting error messages, something not working, or needing quick fixes. Covers error code reference, quick solutions, and common mistake patterns.
tools
Use when writing tests for Bknd applications, setting up test infrastructure, creating unit/integration tests, or testing API endpoints. Covers in-memory database setup, test helpers, mocking, and test patterns.