templates/mail/.agents/skills/security/SKILL.md
Data security model, user/org scoping, and auth patterns. Use when adding tables with user data, implementing multi-user features, setting up A2A cross-app calls, or reviewing data access patterns.
npx skillsauth add BuilderIO/agent-native securityInstall 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.
In production, the framework enforces data isolation at the SQL level. Agents and users can only see and modify data they own. This is automatic — you don't write WHERE clauses yourself.
owner_email)Every table with user-specific data must have an owner_email text column.
import { table, text, integer } from "@agent-native/core/db/schema";
export const notes = table("notes", {
id: text("id").primaryKey(),
title: text("title").notNull(),
content: text("content"),
owner_email: text("owner_email").notNull(), // REQUIRED for user data
});
What happens automatically:
db-query creates temporary views with WHERE owner_email = <current user>db-exec INSERT statements get owner_email auto-injecteddb-exec UPDATE/DELETE statements are scoped to the current user's rowsAGENT_USER_EMAIL (set from the auth session)org_id)For multi-user apps where teams share data, add an org_id column:
export const projects = table("projects", {
id: text("id").primaryKey(),
name: text("name").notNull(),
owner_email: text("owner_email").notNull(), // who created it
org_id: text("org_id").notNull(), // which org it belongs to
});
When both columns are present, queries are scoped by both: WHERE owner_email = ? AND org_id = ?.
The org_id comes from AGENT_ORG_ID which is automatically set from the user's active organization in Better Auth.
Run pnpm action db-check-scoping to verify all tables have proper ownership columns. Use --require-org for multi-org apps.
The framework uses Better Auth for authentication. It's always on by default — users create an account on first visit.
Environment variables:
BETTER_AUTH_SECRET — signing key (auto-generated if not set)GOOGLE_CLIENT_ID + GOOGLE_CLIENT_SECRET — enable Google OAuthGITHUB_CLIENT_ID + GITHUB_CLIENT_SECRET — enable GitHub OAuthAUTH_MODE=local — disable auth for solo local dev (escape hatch)Better Auth's organization plugin is built-in. Every app supports:
org_idThe active organization ID flows from session.orgId → AGENT_ORG_ID → SQL scoping automatically.
For simple deployments, set ACCESS_TOKEN or ACCESS_TOKENS (comma-separated) as environment variables. This provides a shared token for all users — no per-user identity.
When apps call each other via A2A, they need to verify identity. Set the same A2A_SECRET on all apps that need to trust each other:
# On both apps
A2A_SECRET=your-shared-secret-at-least-32-chars
How it works:
A2A_SECRET containing sub: "[email protected]"AGENT_USER_EMAIL from the verified sub claimWithout A2A_SECRET, A2A calls are unauthenticated (fine for local dev, not production).
owner_email. No exceptions.db-query/db-exec.owner_email for persistent user data.AGENT_USER_EMAIL environment variable.tools
Public booking flow — the state machine, animations, and URL/app-state sync.
tools
Trigger-based automations — reminders, follow-ups, webhooks — across the booking lifecycle.
tools
Team event types, round-robin assignment, collective bookings, host weights, and no-show calibration.
development
The pure `computeAvailableSlots` function — inputs, outputs, invariants, and debugging guide.