Building Secure LLM Applications: A Practical 14-Point Checklist Based on OWASP 2026 A developer published a 14-point security checklist for building LLM-powered applications, organized into access, input, output, and operations buckets and derived from the OWASP Top 10 for LLM Applications 2026. The checklist recommends enforcing least-privilege tool scopes in application code rather than trusting the model, adding server-side chunk- and tenant-level access control for RAG and vector retrieval, requiring human approval for irreversible actions, and sandboxing untrusted data and tools. I started writing this article while reading the OWASP Top 10 for LLM Applications 2026. I wanted to learn more about how to build secure LLM-powered applications, and that series of articles was my best friend while doing so. The OWASP Top 10 for LLM Applications 2026 is a collection of 10 articles that discuss 10 different security issues related to LLM-powered applications. Each article covers an issue and a list of strategies to help prevent it -with a very good list of references that you should defiantly check-. I was taking notes while reading that series to build a to-do list of elements to check every time I build an LLM-powered app, to make sure that my app follows the basic security rules. Once the list was done, I decided to share it so others could use it and maybe contribute to it I still highly recommend reading all ten articles to learn more about each security issue in detail. You can download the PDF containing the articles from here https://genai.owasp.org/resource/owasp-genai-llm-top-10-2026/ . I divided my list into 4 buckets with 14 points a lot of points, I know 😄 . I discuss every point below, but you can see the entire list without details here: The checklist Access: 1- Least Privilege / Minimize Scope, Tools & Permissions and enforce that 2- Server-Side, Chunk/Tenant-Level Access Control for Retrieval RAG/Vector 3- Human-in-the-Loop Approval for High-Impact/Irreversible Actions 4- Sandboxing & Isolation of Untrusted Data/Tools/Execution Input: 5- No Secrets or Sensitive Data in Prompts/Hidden Context 6- Input Validation & Sanitization 7- Trust-Boundary Separation & Provenance Labeling Output: 8- Output Schema Validation, Encoding & Safe Rendering 9- Grounding / Verify-Before-Act “Claim-Check-Act” Ops: 10- Rate Limiting, Quotas & Spending/Resource Caps 11- Know & Verify What You’re Building On 12- Test Your Defenses Like an Attacker Would 13- Monitoring, Logging, Anomaly Detection & Incident Response 14- Protect Your Data Wherever It Goes Access: 1- Least Privilege / Minimize Scope, Tools & Permissions and enforce that Why: This practice is useful for minimizing the damage that can be caused by prompt injection. Even if malicious instructions are sneaked into the LLM’s input, they won’t be as harmful because the LLM doesn’t have enough leverage to cause major damage. It also guards against unbounded consumption, as this practice limits how much damage a runaway loop or recursive call can actually do, even if it fires thousands of times. Narrowing access only helps if that boundary is actually enforced: if the LLM itself is trusted to decide whether a specific action is appropriate, a convincing injection or hallucination can still talk it into misusing the access it does have. That check needs to happen in your own application code every time a tool is called, not in the model’s judgment. Examples of what this could look like: - Replace broad tools e.g., a generic “run shell command” or “fetch any URL” tool with narrow, single-purpose ones, i.e., a tool that only writes to one designated file, not one that can run arbitrary commands. - Give the model’s DB connection a read-only role on only the tables it needs, not full read/write access to the whole schema. - Add a deterministic policy check in front of every tool call that re-validates the action’s arguments and authorization at execution time — don’t let the model’s decision to call the tool be the only thing standing between it and the action. 2- Server-Side, Chunk/Tenant-Level Access Control for Retrieval RAG/Vector Why: This is actually complementary to point 1. It's not enough to limit what the LLM can access through its tools; you also need to make sure that the retrieval system itself only returns information that the current user is allowed to see. This is especially important in multi-tenant applications, where different users or organizations share the same database. Without proper access control, one user might be able to retrieve another user's private information, even if the LLM itself doesn't have any direct access to it. Examples of what this could look like: - In a multi-tenant RAG application, include the tenant ID as part of the vector database query itself. For example, use a metadata filter or a WHERE tenant id = X clause to make sure that only documents belonging to the current tenant are retrieved. Don't retrieve documents for everyone and filter out the unauthorized ones afterward. - Apply access control at the chunk level, not just the document level. Imagine having a policy document that is mostly public but contains one confidential paragraph. That paragraph should be excluded from the index available to unauthorized users, rather than assuming that the entire document has the same access permissions. 3- Human-in-the-Loop Approval for High-Impact/Irreversible Actions Why: LLMs can hallucinate, misunderstand instructions, or be manipulated through prompt injection. And when an LLM-powered application has access to tools that can perform real-world actions, even a small mistake can have serious consequences. Requiring human approval before performing high-impact or irreversible actions adds another layer of protection. It gives someone the opportunity to review what the LLM is about to do before the action actually happens. Examples of what this could look like: - An agent that can send emails or post on social media on a user's behalf should draft the message and show it to the user first, requiring an explicit confirmation before sending or publishing it. - A coding or operations agent that can execute destructive commands e.g., deleting files or dropping database tables should pause and ask for developer confirmation before executing them. 4- Sandboxing & Isolation of Untrusted Data/Tools/Execution Why: This practice is especially important when your application allows code execution. You don't want generated code to have direct access to your production environment, sensitive files, or internal services. LLMs can generate incorrect code, suggest non-existent packages, or produce something malicious after being manipulated through prompt injection. Running that code in an isolated environment limits the damage it can cause, even if something goes wrong. Examples of what this could look like: - Restrict generated code to a fixed, pre-approved list of allowed packages and functions. You can validate the code using an AST check before execution, rather than simply relying on a runtime try/catch to catch errors after something has already run. - Isolate LLM-Generated Code Execution: Run generated code in an isolated subprocess with execution timeouts. For production-grade isolation, consider using a VM or microVM, or a user-space-kernel sandbox such as gVisor, with enforced CPU and memory limits. Keep in mind that subprocesses and standard containers still share the host's kernel, which means they can potentially be escaped or used to exhaust shared resources. - Restrict Sandbox Network Access: Only allow access to approved external APIs, and explicitly block requests to internal services, private IP addresses, and cloud metadata endpoints. This helps prevent attacks such as SSRF Server-Side Request Forgery . Input: 5- No Secrets or Sensitive Data in Prompts/Hidden Context Why: Including secrets, credentials, or API keys in your system prompts can introduce two major problems: - If you're using an LLM through an external API, you're exposing that sensitive information to the API provider. - If the system prompt gets leaked to the user which can happen through hallucinations or prompt injection , your secrets could end up in the wrong hands. The important thing here is that the LLM shouldn't need to know your secrets in the first place. Your application should handle sensitive information without exposing it to the model. Examples of what this could look like: - Instead of hardcoding an API key or database credential directly into your system prompt, store it in a secrets manager e.g., environment variables, HashiCorp Vault, or AWS Secrets Manager . Keep it entirely out of the text the model sees. - If the model needs to use a tool that requires authentication e.g., a database password , handle authentication in your application code or tool-calling layer. The LLM should only need to know that it can call a specific tool with certain arguments, not the actual credential value. 6- Input Validation & Sanitization Why: This practice is important for preventing attacks such as prompt injection, but it also helps prevent users from using your application in ways it wasn't designed for. You should have a way to understand what the user is trying to do and check whether that request falls within the intended use cases of your application before passing it to the LLM. Examples of what this could look like: - Imagine you're building a medical assistant that is only supposed to provide general medical information, not diagnose patients. If a user asks it to diagnose their symptoms, an intent classifier can detect that the request is outside the application's intended scope and block or redirect it before the model responds. - If your application accepts images, pair OCR with a text or intent classifier. This way, if someone uploads an image containing hidden malicious instructions, the extracted text gets scanned for injection attempts before reaching the model, just like regular text input. - Use regex or pattern matching together with an intent classifier as a cheap first-pass filter for obvious suspicious inputs. For example: - Catch known jailbreak phrases such as "ignore previous instructions". - Detect suspicious long Base64-looking strings or unexpected character encodings. - Flag suspicious invisible characters, using normalization that takes the languages supported by your application into account. 7- Trust-Boundary Separation & Provenance Labeling Why: This point is different from point 1. Point 1 is about limiting what the LLM can do even if it gets manipulated, while this point is about making that manipulation harder in the first place. The problem is that an LLM doesn't naturally understand the difference between instructions and data. Both are just tokens in its input. So, a malicious instruction hidden inside a retrieved document or a web page might look just like a legitimate command to the model. By keeping external content structurally separate from your system instructions and clearly labeling where that content comes from, you make it harder for the model to confuse untrusted data with instructions it should follow. Examples of what this could look like: - When a RAG system retrieves documents to answer a question, wrap the retrieved content in clearly defined tags e.g.,