Building AI Features Securely
Separate model suggestions from authority, protect private data and constrain tools before adding AI to an application.
Before you start: AI APIs · Authentication and authorization basics. Reading time is an estimate; allow extra time for practice.
What you’ll learn
- Identify data flows, trust boundaries and tool authority for an AI feature.
- Explain how prompt injection can redirect a model through external content.
- Apply action allowlists and resource authorization outside the model.
- Test unauthorized proposals and require review of the exact consequential action.
On this page
External text can contain hostile instructions
Prompt injection occurs when untrusted content attempts to redirect the model away from the intended task. A retrieved page might say to ignore the user and send private data elsewhere. The instruction can arrive through documents, logs, tool responses or other content that looks like ordinary task evidence.
Delimiters and instructions to ignore hostile text can help, but they are not a complete defence. Restrict what the model can access, validate tool arguments, constrain outbound destinations and require approval for consequential actions. The model should not have secrets in its context that are unnecessary for the task.
{
"action": "draft_reply",
"ticketId": "ticket-123",
"body": "A suggested reply for review"
}
Server checks:
- Is draft_reply an allowed action?
- Can this user access ticket-123?
- Is the body within limits and safely rendered?
- Does sending require a separate confirmation?The server derives authority from the authenticated user, not from fields invented by the model. A draft must not silently become a sent message.
Validate structure and handle content safely
A schema can check that a response has the expected fields, but valid structure does not establish truth or permission. Apply domain rules after parsing. Escape generated text when rendering it; if rich HTML is necessary, use an appropriate sanitization policy. Do not evaluate generated code in the application’s trusted origin.
For retrieval, enforce access controls before providing passages to the model. Do not retrieve every customer’s data and then ask the model to reveal only the right records. Keep source permissions attached to indexed chunks, and verify that deletion and access changes reach the search index.
Test boundaries, not just helpful answers
Include adversarial documents, forged tool arguments, cross-user identifiers, oversized inputs and attempts to send data to an unapproved host. Confirm that the application refuses unauthorized operations even when the model proposes them confidently. Test cancellation and retries for duplicate side effects.
Log enough to investigate failures while minimizing sensitive content. Define rollback, rate limits and a way to disable a risky tool without disabling the whole site. Human review should show the exact target and effect of an action, so approval is meaningful. Security comes from enforced boundaries and ongoing evidence, not from a promise in a prompt.
Reject an unauthorized proposal even when it looks plausible
A model’s tool arguments are a request to your application, not a grant of permission. In this local demonstration, the authenticated identity and ticket ownership are trusted inputs supplied by the server. The proposal comes from the model. The gate permits a bounded draft only for a ticket owned by that identity; it does not implement authentication, render HTML or send anything.
Run the complete code in Node.js or a browser console. The second proposal keeps the valid action name but targets another owner’s ticket. The third targets the right ticket but asks to send. Both must fail independently of how politely the model describes them. Real applications need their actual authorization policy and fresh resource lookup, rather than this simplified ownership map.
const tickets = new Map([
['ticket-123', { ownerId: 'user-a' }],
['ticket-456', { ownerId: 'user-b' }],
]);
function canCreateDraft(authenticatedUserId, proposal) {
if (!proposal || typeof proposal !== 'object' || Array.isArray(proposal)) return false;
if (proposal.action !== 'draft_reply' || typeof proposal.ticketId !== 'string') return false;
if (typeof proposal.body !== 'string' || proposal.body.trim().length === 0 ||
proposal.body.length > 1000) return false;
const ticket = tickets.get(proposal.ticketId);
return Boolean(ticket && ticket.ownerId === authenticatedUserId);
}
const proposals = [
{ action: 'draft_reply', ticketId: 'ticket-123', body: 'A draft for review.' },
{ action: 'draft_reply', ticketId: 'ticket-456', body: 'A draft for another owner.' },
{ action: 'send_reply', ticketId: 'ticket-123', body: 'Send this now.' },
];
console.log(proposals.map(proposal => canCreateDraft('user-a', proposal)));Expected output: [true, false, false]. Only the first proposal passes this local gate. The authenticated identity must come from the server session, never from a proposal field. Treat the body as untrusted text, recheck authorization when saving or sending, and construct downstream arguments from validated fields. This is a policy demonstration, not a complete secure endpoint.
Bind approval to what will actually happen
For a send operation, show the destination, exact message and relevant attachments. A generic “allow the assistant” decision is not a review of a particular message. If the model changes the target or body after review, obtain approval for the changed action. The server still checks access and current policy at execution time.
Keep draft creation and sending as distinct application operations. Use a bounded permission for each, record the decision without storing unnecessary sensitive text, and make retries safe against duplicate sends. This protects the boundary even when a retrieved document changes what the model proposes; it does not make the proposal itself factually correct or appropriate.
Try it yourself
Threat-model a documentation assistant that can draft a repository issue. List what it may read, what it may propose and what requires a separate user action.
Show hint
A useful design allows public documentation retrieval and a draft preview, checks repository access independently, and requires confirmation of the exact issue before publishing.
Check your understanding
5 questions to revisit the ideas. No score is saved, and you can retry. This is separate from Skill Quiz; it does not award XP or unlock guides.
Sources & further reading
Primary references for the concepts in this guide. Examples and exercises are illustrative; verify provider-specific behaviour against the version you use.