DEV Community

Cover image for Securing LLM Features in Cloud Apps: Four Controls to Build First
James Sanderson
James Sanderson

Posted on

Securing LLM Features in Cloud Apps: Four Controls to Build First

You can have a spotless cloud security posture, every bucket private, every role scoped, every log shipped, and still ship an AI feature that hands one customer's contract to another. Cloud based security services watch infrastructure. A support chatbot, a document assistant or an agent that takes actions introduces risk inside your application logic, where those tools cannot see.

Businessperson holding a padlock icon over a cloud network diagram

This post covers the four controls we treat as non-negotiable when adding LLM features to a cloud product, in the order we build them.

The threat model in one paragraph

Three things go wrong most often. Prompt injection: instructions hidden in user input, an uploaded PDF or a fetched web page cause the model to ignore its system prompt. Retrieval leakage: a RAG pipeline returns chunks the current user is not entitled to see, and the model faithfully summarises them. Over-permissioned tools: an agent holding a broad API key can be talked into doing anything that key allows. Add shadow AI (staff pasting code into unapproved tools) and unvetted third-party models or plugins, and you have the full picture.

The key insight is that you cannot prompt your way out of any of these. Instructions to the model are a hint, not a control.

Control 1: Enforce access at the retrieval layer

If a chunk reaches the context window, assume it can reach the answer. So authorisation has to happen before retrieval returns, as a pre-filter on the query, not as a post-filter on generated text.

Store tenant and ACL metadata on every chunk at ingestion time, then apply it inside the vector query:

async function retrieveContext(user: AuthUser, query: string) {
  const embedding = await embed(query);

  return vectorStore.search({
    vector: embedding,
    topK: 8,
    // Pre-filter: never let the index return what the user can't see
    filter: {
      tenantId: { $eq: user.tenantId },
      allowedGroups: { $in: user.groups },
      classification: { $ne: "restricted" },
    },
  });
}
Enter fullscreen mode Exit fullscreen mode

Two details matter. The tenantId must come from the authenticated session, never from the request body. And ACL changes in the source system need to propagate to chunk metadata, or a revoked user keeps reading through the assistant.

Control 2: Least-privilege tools for agents

Every tool you give an agent is an identity with permissions. Scope each one to the narrowest action, bind it to the calling user's rights, and require human approval for anything with side effects.

const tools = {
  lookupOrder: {
    scope: "orders:read",
    sideEffects: false,
    run: (user, { orderId }) => orders.getForTenant(user.tenantId, orderId),
  },
  issueRefund: {
    scope: "refunds:create",
    sideEffects: true,
    maxAmount: 100,
    requiresApproval: true,
    run: (user, args) => refunds.create(user.tenantId, args),
  },
};

async function invokeTool(user, name, args) {
  const tool = tools[name];
  if (!tool || !user.scopes.includes(tool.scope)) throw new Forbidden(name);
  validateArgs(tool, args); // schema + business limits, e.g. maxAmount
  if (tool.requiresApproval) return queueForHumanApproval(user, name, args);
  return tool.run(user, args);
}
Enter fullscreen mode Exit fullscreen mode

Note what is absent: a single service key with write access to everything. The model chooses which tool to call; your code decides whether that call is allowed.

Control 3: Treat model output as untrusted input

Anything the model produces that drives an action should be validated like data from the public internet. Parse structured output against a strict schema, reject unknown fields, check values against business rules, and never pass raw model text into a shell, SQL query or URL fetcher. For content pulled from untrusted sources, such as uploaded files or web pages, keep it clearly delimited from instructions and strip anything that looks like markup the renderer might execute.

This does not stop injection from being attempted. It stops a successful injection from turning into a consequential action.

Data encryption concept with a digital lock over flowing binary data

Control 4: Log every prompt, retrieval and tool call

When something goes wrong, and eventually it will, you need to reconstruct exactly what the model saw and did. Log a structured event per interaction:

{
  "requestId": "req_7f3a",
  "userId": "u_1842",
  "tenantId": "t_091",
  "model": "provider/model-version",
  "retrievedChunkIds": ["doc_44#3", "doc_44#4"],
  "toolCalls": [{ "name": "lookupOrder", "allowed": true }],
  "blocked": false,
  "latencyMs": 1840
}
Enter fullscreen mode Exit fullscreen mode

Store full prompts and completions separately with tighter access and a defined retention period, since they may contain personal data. These logs also feed your normal detection stack: a spike in denied tool calls from one tenant is exactly the kind of signal a SIEM or MDR provider should see.

Where the rest of cloud security fits

These four controls sit alongside, not instead of, the usual infrastructure layer: short-lived credentials for the services calling model APIs, secrets in a secrets manager rather than environment files, a WAF in front of the chat endpoint with rate limits, and vetting of third-party models and plugins with the same rigour as any other dependency.

The full guide, covering service categories, costs, identity and a 90-day roadmap, is on the TechCirkle blog: Cloud Based Security Services: What to Buy, What to Build, and Where AI Actually Helps.

We build these guardrails into every LLM integration project by default, because retrofitting them after launch costs far more than designing them in.

Frequently Asked Questions

Can a strong system prompt prevent prompt injection?

No. System prompts reduce casual misuse, but a determined injection can override them. Real protection comes from limiting what the model can reach and do: filtered retrieval, scoped tools, validated outputs and human approval for consequential actions.

Why filter at retrieval rather than filtering the model's answer?

Once restricted content is inside the context window, the model can paraphrase, summarise or leak it in ways a post-filter will not reliably catch. Filtering before retrieval returns means the model never sees data the user is not entitled to.

How should agent tools handle authentication?

Each tool call should run with the permissions of the user on whose behalf the agent acts, scoped to a single action. Avoid shared service keys with broad write access, because one manipulated instruction then inherits all of it.

What should be logged for an LLM feature?

At minimum the user, tenant, model version, retrieved chunk IDs, tool calls with their allow or deny result, and whether anything was blocked. Keep full prompts and completions in a separate, more tightly controlled store with defined retention.

Do posture management tools detect LLM-specific risks?

Generally not. They check infrastructure configuration, so they can confirm your model endpoint is not public, but they cannot tell whether your retrieval pipeline leaks data between users or whether an agent's tools are over-permissioned.

Top comments (0)