Development & Building with AI · Tutorial

How to build AI web tools without compromising security

A web tool that calls a language model inherits all the risks of an ordinary application and adds others. See what to protect, step by step, before putting it online.

Responsible editor:
Daniel Ferreira

Published · 7 min read

Originally published in Brazilian Portuguese.

Translated from the Portuguese original with AI assistance.

Also available in: Português

Key points

  • The model's API key stays on the server; the browser talks only to your backend, which applies authentication, limits and logging.
  • Treat the model's response as untrusted user input: never insert it as HTML, SQL or a command without handling it.
  • Prompt injection has no definitive fix; reduce the damage with least privilege, controls outside the model and human approval.

Building a web tool with artificial intelligence has become simple: a form, a call to a language model’s API and the response on screen. A text summarizer, a product description generator, an assistant that answers questions about a document.

The problem is that this simplicity hides risks. Some are old acquaintances of the web, such as credential leaks and cross-site scripting. Others are new, such as prompt injection. This tutorial organizes the defenses into seven steps, based on the OWASP Top 10 for LLM applications, the list of risks maintained by the generative AI security project of OWASP, a nonprofit foundation dedicated to software security.

The code snippets are illustrative. Adapt them to the language and provider you use and check their official documentation.

Step 1: the API key never goes to the browser

Every language model API is accessed with an API key, a password that identifies your account and generates charges with each use. Whoever has this key uses the model on your account.

The most common mistake in prototypes is putting the key in the page’s JavaScript. Any visitor opens the browser’s developer tools and copies it. Google’s documentation on Gemini API keys is explicit: do not put the key directly in web or mobile apps and never commit it to version control systems such as Git. For browser applications, the recommendation is to have an intermediary server that makes the API calls.

The minimum architecture looks like this:

  1. The browser sends the user’s text to your backend.
  2. The backend checks who the user is, applies limits and only then calls the model’s API, with the key stored in an environment variable.
  3. The backend returns to the browser only what it needs to display.

If the key leaks anyway, rotate it immediately. When the provider allows it, restrict the key to certain APIs or origins, which the same Google guide points to as a way to reduce the damage. How to store secrets in a publishing process is detailed in how to set up automated deployment securely.

Step 2: the system prompt is not a vault

The system prompt is the fixed instruction you send to the model before the user’s message: “you are an assistant that summarizes contracts; answer in English.” It is tempting to put security rules and even credentials there.

OWASP advises against both. The page on system prompt leakage says it should not be considered a secret or used as a security control. Keys, passwords and database addresses stay out of the prompt. Critical controls, such as privilege separation and authorization checks, should not be delegated to the model.

In practice: assume that any persistent user will manage to read your system prompt. Write it so that this causes no harm.

Step 3: treat the model’s output as untrusted input

This is the point that most surprises those coming from ordinary web development. The model’s response can be influenced by the user. So it deserves the same distrust as a form field.

OWASP calls this risk improper output handling. One of the scenarios described: a manipulated prompt makes the model return JavaScript, which runs in another user’s browser when the response is displayed. This is XSS (cross-site scripting), the injection of code into a page viewed by others. The same page lists other dangers: passing the output unchecked to a system command, to eval, to an SQL query or to the construction of file paths.

The defenses are those of the classic web, applied to a new sender:

  • Display text as text. The OWASP XSS prevention cheat sheet recommends replacing innerHTML with textContent, which inserts the content without interpreting it as HTML.
  • If you need formatting, sanitize. When the response comes in Markdown and you convert it to HTML, run the result through a sanitizer before inserting it into the page. OWASP recommends DOMPurify and warns that modifying the HTML after sanitizing it can void the protection.
  • Never build SQL with the response. Use parameterized queries, as with any external input.
  • Validate the format. If you asked for a JSON with three fields, check in code that exactly that came back before using it.
// Illustrative: display the response as plain text
output.textContent = modelResponse;

// If the response is HTML (for example, converted Markdown), sanitize first
output.innerHTML = DOMPurify.sanitize(convertedHtml);

As an extra layer, configure a Content Security Policy (CSP). According to MDN documentation, it is a set of instructions the site sends to the browser to limit what the page’s code can do, with the main goal of mitigating XSS. MDN itself notes that CSP is defense in depth: it complements sanitization, it does not replace it.

Step 4: limit usage before the bill arrives

Every call to the model costs money. An open tool, with no limits, can be used by bots until the budget runs out. OWASP describes this scenario in unbounded consumption, which includes so-called denial of wallet: a high volume of requests made to exploit pay-per-use billing and generate unsustainable costs.

The measures OWASP recommends are ordinary engineering:

  • Maximum input size. Reject texts above a reasonable limit for your tool.
  • Rate limiting per user, per IP address or per session, within a time window.
  • Per-user quotas and a maximum execution time per call.
  • Monitoring of consumption, with alerts for unusual patterns.

Add to that a spending cap in the provider’s dashboard, when available. To understand why long texts cost more, see how tokens work in AI models.

Step 5: reduce the damage of prompt injection

Prompt injection is when a text manages to change the model’s behavior in an unintended way. OWASP distinguishes two types on the page about prompt injection: direct, written by the user, and indirect, which arrives hidden in external content processed by the model, such as a web page or an uploaded file.

The same page acknowledges that it is unclear whether foolproof prevention methods exist, given the probabilistic nature of generative models. In other words: you do not eliminate the risk, you limit what a successful attack can do.

The measures listed by OWASP include:

  • defining the role and limits of the task in the system prompt;
  • requiring output formats and validating them in code;
  • clearly separating and marking untrusted external content;
  • giving the application least privilege and running sensitive functions in code, not through the model;
  • requiring human approval for high-risk operations;
  • running regular adversarial tests, treating the model as an untrusted user.

If your tool reads documents submitted by the public, indirect injection is the main scenario. The article how to build a chatbot with your own documents covers this case.

Step 6: give the model only the tools it needs

Many tools evolve from “answers questions” to “takes actions”: sending email, querying the database, fetching a URL. That is where prompt injection stops being an embarrassment and becomes an incident.

OWASP calls the problem excessive agency and points to three causes: too much functionality, too many permissions and too much autonomy. The recommendations:

  • offer only the necessary functions and avoid generic ones, such as running system commands or fetching any URL;
  • use least privilege: read-only database user, restricted scopes, actions performed in the context of the user who requested them;
  • ask for human confirmation for high-impact actions;
  • perform authorization checks in the system that executes the action, not by asking the model whether the action is allowed.

The topic is covered in depth in what AI agents are and how they carry out tasks.

Step 7: take care of data and AI-generated code

Two fronts tend to be left for later, and they should not be.

Personal data. Everything the user types is sent to the model provider. The OWASP page on sensitive information disclosure recommends filtering sensitive data before it reaches the model, applying access control, advising users not to submit sensitive information and publishing clear retention and usage rules. It also warns that restrictions written in the prompt can be bypassed by injection, so they cannot be the only control. For obligations in Brazil, see data privacy when using AI tools. Be careful with logs too: storing full prompts is useful for debugging, but creates a copy of users’ data.

Code written with AI assistants. If you use AI to program the tool, review the dependencies it suggests. A study presented at USENIX Security 2025, We Have a Package for You!, tested 16 code-generating models and found an average rate of non-existent packages of at least 5.2% in commercial models and 21.7% in open-source ones, with 205,474 unique invented names. The risk is that someone registers a package with that name and malicious code. Before installing, confirm that the package exists, who maintains it and whether it is what you were looking for. The article AI in programming discusses the limits of these assistants.

Key takeaways

  • API key only on the server. The browser talks to your backend, which authenticates, limits and logs.
  • The system prompt does not keep secrets or perform access control.
  • The model’s response is untrusted input: text as text, sanitized HTML, parameterized SQL, validated format.
  • Limit size, frequency and cost before opening the tool to the public.
  • Prompt injection has no definitive cure; least privilege and human approval reduce the damage.
  • Protect users’ data and check every dependency suggested by AI.

Sources

  1. Using Gemini API keys, Google AI for Developers. Accessed October 8, 2026
  2. Content Security Policy (CSP), MDN Web Docs. Accessed October 8, 2026
  3. OWASP Top 10 for LLM Applications 2025, OWASP GenAI Security Project. Accessed October 8, 2026
  4. LLM01:2025 Prompt Injection, OWASP GenAI Security Project. Accessed October 8, 2026
  5. LLM02:2025 Sensitive Information Disclosure, OWASP GenAI Security Project. Accessed October 8, 2026
  6. LLM05:2025 Improper Output Handling, OWASP GenAI Security Project. Accessed October 8, 2026
  7. LLM06:2025 Excessive Agency, OWASP GenAI Security Project. Accessed October 8, 2026
  8. LLM07:2025 System Prompt Leakage, OWASP GenAI Security Project. Accessed October 8, 2026
  9. LLM10:2025 Unbounded Consumption, OWASP GenAI Security Project. Accessed October 8, 2026
  10. Cross Site Scripting Prevention Cheat Sheet, OWASP Cheat Sheet Series. Accessed October 8, 2026
  11. We Have a Package for You! A Comprehensive Analysis of Package Hallucinations by Code Generating LLMs, arXiv (Spracklen et al.; USENIX Security 2025). Published June 12, 2024 · Accessed October 8, 2026

Related reading

Development & Building with AI · Explainer

Astro Content Collections: content at scale

When a site grows from a few pages to hundreds of articles, the problem is no longer writing but keeping everything consistent. See how Astro solves this with typed collections.

Development & Building with AI · Explainer

Astro: why use it for sites with thousands of pages

When a site has thousands of content pages, every architectural decision is multiplied. Based on the official documentation and dated field data, see what Astro does well in this scenario and where its limits are.

Development & Building with AI · Analysis

Astro vs. Next.js: architecture and performance compared

Astro and Next.js both generate HTML on the server, but they start from different premises: one was born for content sites, the other for React applications. With official documentation and dated field data, see what that changes in practice.