.agents/skills/security/SKILL.md
Secure coding practices for agent-native apps: input validation, SQL injection, XSS, secrets, data scoping, and auth. Use when writing any action, route, or component that touches user data or external input.
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.
Use the framework's security primitives everywhere. Never bypass them.
Use defineAction with a Zod schema: for every action. The framework validates input automatically and returns clear 400 errors for HTTP callers and structured error results for agent tool calls.
export default defineAction({
schema: z.object({
email: z.string().email(),
role: z.enum(["admin", "member"]),
limit: z.coerce.number().int().min(1).max(100).default(25),
}),
run: async (args) => { /* args is fully typed and validated */ },
});
The legacy parameters: field (plain JSON Schema) has no runtime validation — do not use it for new code.
Never concatenate user input into SQL strings. Use Drizzle ORM's query builder (always safe) or parameterized queries:
// Safe — Drizzle ORM
await db.select().from(users).where(eq(users.email, args.email));
// Safe — parameterized raw SQL
await client.execute({ sql: "SELECT * FROM users WHERE id = ?", args: [id] });
// NEVER do this
await client.execute(`SELECT * FROM users WHERE id = '${id}'`);
dangerouslySetInnerHTML, innerHTML, eval(), or document.write() with user-controlled content.react-markdown..env only (gitignored).oauth_tokens store via saveOAuthTokens().settings, application_state, source code, or action responses sent to the client./api/ routes, always call getSession(event) and reject requests without a session:import { getSession } from "@agent-native/core/server";
export default defineEventHandler(async (event) => {
const session = await getSession(event);
if (!session) throw createError({ statusCode: 401 });
// ...
});
In production, the framework automatically restricts all agent SQL queries to the current user's data using temporary views. This is enforced at the SQL level — the agent cannot bypass it.
owner_email)Every template table with user data must have an owner_email text column:
owner_email via schema introspectionWHERE owner_email = <current user> before each queryowner_email into INSERT statementsThe current user is resolved from AGENT_USER_EMAIL (set automatically from the session).
org_id)For multi-org apps, tables also need org_id:
WHERE org_id = <current org> is added (in addition to owner_email if present)org_id is auto-injected into INSERT statementsEnable org scoping in the agent-chat plugin:
createAgentChatPlugin({
resolveOrgId: async (event) => {
const ctx = await getOrgContext(event);
return ctx.orgId;
},
});
| Column | Purpose | Required |
| ------------- | ----------------------- | ------------------------------- |
| owner_email | Per-user data isolation | Yes, for all user-facing tables |
| org_id | Per-org data isolation | Yes, for multi-org apps |
Run pnpm action db-check-scoping to verify. Use --require-org for multi-org apps.
defineAction with a Zod schema:dangerouslySetInnerHTML with user content.env only, not committedowner_email columngetSession and reject unauthenticated requestsstoring-data — SQL patterns and the agent's db toolsactions — defineAction with Zod schema validationauthentication — Auth modes, sessions, and org contexttools
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.