{"slug": "building-secure-llm-applications-a-practical-14-point-checklist-based-on-owasp", "title": "Building Secure LLM Applications: A Practical 14-Point Checklist Based on OWASP 2026", "summary": "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.", "body_md": "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.\n\nThe 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-.\n\nI 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!\n\nI 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/).\n\nI 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:\n\n### \n  \n  \n  The checklist\n\n#### \n  \n  \n  Access:\n\n1- Least Privilege / Minimize Scope, Tools & Permissions and enforce that\n\n2- Server-Side, Chunk/Tenant-Level Access Control for Retrieval (RAG/Vector)\n\n3- Human-in-the-Loop Approval for High-Impact/Irreversible Actions\n\n4- Sandboxing & Isolation of Untrusted Data/Tools/Execution\n\n#### \n  \n  \n  Input:\n\n5- No Secrets or Sensitive Data in Prompts/Hidden Context\n\n6- Input Validation & Sanitization\n\n7- Trust-Boundary Separation & Provenance Labeling\n\n#### \n  \n  \n  Output:\n\n8- Output Schema Validation, Encoding & Safe Rendering\n\n9- Grounding / Verify-Before-Act (“Claim-Check-Act”)\n\n#### \n  \n  \n  Ops:\n\n10- Rate Limiting, Quotas & Spending/Resource Caps\n\n11- Know & Verify What You’re Building On\n\n12- Test Your Defenses Like an Attacker Would\n\n13- Monitoring, Logging, Anomaly Detection & Incident Response\n\n14- Protect Your Data Wherever It Goes\n\n## \n  \n  \n  Access:\n\n### \n  \n  \n  1- Least Privilege / Minimize Scope, Tools & Permissions and enforce that\n\n**Why:**\n\nThis 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.\n\nNarrowing 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.\n\n**Examples of what this could look like:**\n\n- 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.\n- 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.\n- 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.\n\n### \n  \n  \n  2- Server-Side, Chunk/Tenant-Level Access Control for Retrieval (RAG/Vector)\n\n**Why:**\n\nThis 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.\n\nThis 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.\n\n**Examples of what this could look like:**\n\n- 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.\n- 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.\n\n### \n  \n  \n  3- Human-in-the-Loop Approval for High-Impact/Irreversible Actions\n\n**Why:**\n\nLLMs 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.\n\nRequiring 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.\n\n**Examples of what this could look like:**\n\n- 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.\n- 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.\n\n### \n  \n  \n  4- Sandboxing & Isolation of Untrusted Data/Tools/Execution\n\n**Why:**\n\nThis 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.\n\nLLMs 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.\n\n**Examples of what this could look like:**\n\n- 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.\n- \n**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.\n- \n**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).\n\n## \n  \n  \n  Input:\n\n### \n  \n  \n  5- No Secrets or Sensitive Data in Prompts/Hidden Context\n\n**Why:**\n\nIncluding secrets, credentials, or API keys in your system prompts can introduce two major problems:\n\n- If you're using an LLM through an external API, you're exposing that sensitive information to the API provider.\n- 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.\n\nThe 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.\n\n**Examples of what this could look like:**\n\n- 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.\n- 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.\n\n### \n  \n  \n  6- Input Validation & Sanitization\n\n**Why:**\n\nThis 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.\n\nYou 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.\n\n**Examples of what this could look like:**\n\n- 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.\n- 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.\n- Use regex or pattern matching together with an intent classifier as a cheap first-pass filter for obvious suspicious inputs. For example:\n  - Catch known jailbreak phrases such as \"ignore previous instructions\".\n  - Detect suspicious long Base64-looking strings or unexpected character encodings.\n  - Flag suspicious invisible characters, using normalization that takes the languages supported by your application into account.\n\n### \n  \n  \n  7- Trust-Boundary Separation & Provenance Labeling\n\n**Why:**\n\nThis 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.\n\nThe 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.\n\nBy 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.\n\n**Examples of what this could look like:**\n\n- When a RAG system retrieves documents to answer a question, wrap the retrieved content in clearly defined tags (e.g., `<retrieveddocument source=\"...\">...</retrieveddocument>` ). Instruct the model to treat anything inside those tags as data to reference, not as instructions to follow.\n- Track where every piece of retrieved content came from, including its source, ingestion time, and trust level. This way, if a document later turns out to be malicious, you can identify and invalidate everything retrieved from that source instead of having no idea where the problematic content came from.\n\n## \n  \n  \n  Output:\n\n### \n  \n  \n  8- Output Schema Validation, Encoding & Safe Rendering\n\n**Why:**\n\nThis practice is important for several reasons:\n\n- Even if malicious instructions manage to get past your input defenses, you can still catch some of the damage at the output stage. For example, you can check whether the response contains sensitive information or something outside the intended use of your application.\n- LLMs can hallucinate incomplete, incorrect, or nonsensical outputs. Validating the response allows you to catch these issues and retry instead of passing incorrect data to the next step in your pipeline.\n- You should never assume that an LLM's output is automatically safe to render or execute. It might contain something that looks like a script tag, an SQL fragment, or a shell command. Passing that output directly to a browser, database, or shell without proper validation and encoding could lead to vulnerabilities such as XSS, SQL injection, or command execution, regardless of why the model generated it.\n\n**Examples of what this could look like:**\n\n- Validate the LLM's output against a strict schema (e.g., an expected JSON structure with specific fields) in your application code before using it. If the response doesn't match the schema, reject it and retry instead of passing invalid data downstream.\n- Before rendering LLM output on a web page, apply context-aware encoding:\n  - Use HTML encoding when inserting text into an HTML context.\n  - Use JavaScript encoding when inserting data into a JavaScript context, so that unexpected characters or script-like content cannot execute as code.\n- When interacting with a database, use predefined parameterized queries and pass model-generated values as parameters. Don't let the model generate SQL that gets executed directly.\n\n### \n  \n  \n  9- Grounding / Verify-Before-Act (\"Claim-Check-Act\")\n\n**Why:**\n\nLLMs can confidently produce incorrect information, and sometimes that information can be outdated even if it was correct at some point.\n\nThis practice is about making sure that important claims are checked against a real and reliable source before your application treats them as facts or takes action based on them.\n\nIn other words, don't let the LLM's confidence be the only reason you trust something!\n\n**Examples of what this could look like:**\n\n- Imagine an agent is about to approve a refund because it believes that a customer's order was never delivered. Before taking that action, it should check the actual shipment and tracking records instead of relying only on the LLM's interpretation of the customer's message.\n- A support bot answering \"What's our return policy?\" should retrieve the latest policy document and base its answer on the actual content. It shouldn't simply rely on what it remembers from its training data, which could be outdated or incorrect.\n\n## \n  \n  \n  Ops:\n\n### \n  \n  \n  10- Rate Limiting, Quotas & Spending/Resource Caps\n\n**Why:**\n\nThis practice becomes especially important when your other defenses against prompt injection and unintended behavior fail.\n\nAn LLM-powered application can consume a lot of resources, especially when agents are involved. A single request might trigger repeated model calls, recursive tool calls, or an endless loop. Without proper limits, a relatively small issue can turn into a very expensive one.\n\nHaving strict and reasonable limits on how much your application can consume helps contain the damage before it gets out of control.\n\n**Examples of what this could look like:**\n\n- If a request triggers a loop that keeps calling the model repeatedly, a hard spending cap per API key or user can stop it. A simple alert might not be enough because someone may not notice it until the damage has already been done.\n- A single agent stuck in a recursive tool-calling loop can burn through its budget in seconds, even if you already have a spending limit per user. Add a maximum number of steps or recursion depth to each agent run to catch this specific failure mode.\n\n### \n  \n  \n  11- Know & Verify What You're Building On\n\n**Why:**\n\nWhen building an LLM-powered application, you're not only relying on your own code. You're also relying on external models, packages, datasets, and sometimes websites.\n\nThe problem is that these resources can be tampered with. A model might contain a backdoor, a dataset might have been poisoned, or an external package might introduce malicious behavior.\n\nYou need to verify the resources you're building on instead of assuming that everything publicly available is trustworthy.\n\n**Examples of what this could look like:**\n\n- Be careful when choosing models from platforms such as Hugging Face. A model might contain hidden biases, backdoors, or other malicious features, so verify that its publisher is trustworthy before using it in your application.\n- Public datasets on Kaggle and other platforms can be modified or poisoned between versions. A model fine-tuned on such data might learn hidden biases or backdoors without you noticing, so pay attention to where your training data comes from and which versions you're using.\n- If your application uses external websites as data sources (e.g., a RAG pipeline that retrieves information from the web), vet those websites just as you would vet a supplier. Check their reputation, terms of service, and track record instead of treating every website as equally trustworthy.\n\n### \n  \n  \n  12- Test Your Defenses Like an Attacker Would\n\n**Why:**\n\nYou might need a friend to help you with this one!\n\nTesting your application only against attacks you're already familiar with can give you a false sense of security. Your defenses might look great on paper but fail when someone discovers a different way to attack your system.\n\nYou need to approach testing from the attacker's perspective. Try to understand how someone could manipulate your application, bypass its defenses, or exploit the way its different components interact. That's how you find out whether your security measures actually work instead of simply assuming they do.\n\n**Examples of what this could look like:**\n\n- Test your prompt-injection filters against attacks that already know your rules and how your filters work. If your defenses still hold up even when the attacker knows exactly how they operate, that's a good sign.\n- Before using a third-party model, test it yourself with prompts related to your actual use case. Don't rely only on generic benchmarks, because a model can perform well on popular benchmarks and still fail at the specific task you need it for.\n- Regularly test deployed models using unusual or suspicious inputs to check for hidden backdoors. A backdoor might remain completely silent during normal usage and only activate when it encounters a specific trigger, so ordinary testing might never reveal it.\n\n### \n  \n  \n  13- Monitoring, Logging, Anomaly Detection & Incident Response\n\n**Why:**\n\nYou need to know what's happening inside your application, and you need to be able to investigate what happened when something goes wrong.\n\nKeeping proper logs and monitoring your system helps you understand its behavior, identify performance degradation, and catch suspicious activities before they turn into bigger problems.\n\nFor example, an unusual spike in tool calls or resource consumption might be an early sign of an attack or a recursive loop. If you're monitoring these patterns, you have a chance to intervene before the damage gets worse.\n\n**Examples of what this could look like:**\n\n- A sudden and unexplained spike in tool calls or token usage from a single session could indicate a recursive loop or an attack in progress. Detecting this in real time gives you a chance to stop it before the costs or damage pile up.\n- An unusually high number of almost identical queries against a retrieval or embedding endpoint could indicate that someone is trying to extract your underlying data. Monitoring these patterns can help you detect an extraction attempt that might not be obvious from looking at a single request.\n\n### \n  \n  \n  14- Protect Your Data Wherever It Goes\n\n**Why:**\n\nYour application's data needs protection, not just when it's stored, but also when it's being transferred or processed.\n\nYou should encrypt sensitive information wherever possible and use confidential computing when you need to protect data during processing. These measures work alongside access controls and input/output validation; they don't replace them.\n\n**Examples of what this could look like:**\n\n- Enable encryption for stored data, including chat histories, uploaded documents, and backups.\n- Use HTTPS to secure connections between your application, the model API, and external tools.\n- Store encryption keys in a dedicated key manager instead of hardcoding them in your application.\n\nIf you got to the end of the article, then I want to congratulate you and say thank you so much! I know that's a lot of points to go through :) \n\nI hope you enjoyed reading this article, and more importantly, I hope this checklist can be useful whenever you're building an LLM-powered application.\n\nAnd if you have any additional points you'd like to add, or any comments or feedback, please let me know! I'd love to hear them.\n\nMy LinkedIn: [Aml Hassan Esmail | LinkedIn](https://www.linkedin.com/)\n\nMy email: [aml.hassan.esmil@gmail.com](mailto:aml.hassan.esmil@gmail.com)", "url": "https://wpnews.pro/news/building-secure-llm-applications-a-practical-14-point-checklist-based-on-owasp", "canonical_source": "https://dev.to/aml_hasaan_esmail/building-secure-llm-applications-a-practical-14-point-checklist-based-on-owasp-2026-1ah5", "published_at": "2026-11-02 17:48:59+00:00", "updated_at": "2026-11-02 17:54:28.698747+00:00", "lang": "en", "topics": ["ai-safety", "large-language-models", "ai-agents", "ai-tools"], "entities": ["OWASP", "OWASP GenAI"], "also_reported_by": [], "alternates": {"html": "https://wpnews.pro/news/building-secure-llm-applications-a-practical-14-point-checklist-based-on-owasp", "markdown": "https://wpnews.pro/news/building-secure-llm-applications-a-practical-14-point-checklist-based-on-owasp.md", "text": "https://wpnews.pro/news/building-secure-llm-applications-a-practical-14-point-checklist-based-on-owasp.txt", "jsonld": "https://wpnews.pro/news/building-secure-llm-applications-a-practical-14-point-checklist-based-on-owasp.jsonld"}}