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.
How this skill is triggered — by the user, by Claude, or both
Slash command
/cameronapak-bknd-skills:bknd-assign-permissionsThe summary Claude sees in its skill listing — used to decide when to auto-load this skill
Configure detailed permissions for roles using simple strings, extended format with effects, and conditional policies.
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)npx claudepluginhub joshuarweaver/cascade-code-general-misc-3 --plugin cameronapak-bknd-skillsGuides completion of development work by verifying tests, detecting environment, and presenting structured options for merge, PR, or cleanup.
Enforces test-driven development: write failing test first, then minimal code to pass. Use when implementing features or bugfixes.
Guides creation and editing of skills using test-driven development with pressure scenarios and subagents to verify agent compliance.