Access control for RAG and AI agents

GuideLast reviewed Oct 16, 2026By Himanshu Ramchandani
Short answer

The AI should never see or do more than the user it's acting for. Pass the user's identity through every layer, filter retrieval by the user's permissions, call tools with the user's scoped credentials rather than a shared admin key, deny by default for new tools and data sources, and log which data and actions were used for whom. A prompt that says "only show what the user is allowed to see" isn't access control.

Common mistake

An internal agent runs with a service account that can read every database and every document. The prompt tells it to respect permissions. Any user can now ask it for anything, and a clever question, or an injected instruction, gets past the prompt.

Access control has to be enforced in code, before data reaches the model, because the model can be talked into anything that's in its context.

Identity propagation

The user's identity has to travel with the request:

  1. The front end authenticates the user (for example with OAuth2 and short-lived tokens).
  2. The API passes the user ID, roles and tenant to every downstream call.
  3. Retrieval uses them to filter documents.
  4. Tools use them to call systems as that user.
  5. Logs record them with every step.

Retrieval

  • Sync permissions from the source systems (who can read which document) into chunk metadata at ingestion.
  • Apply a mandatory filter on every query, built from the authenticated identity, never from the user's message or the model's output.
  • Re-sync when permissions change, including when someone leaves the company.
  • Test it, because a missing filter on one code path exposes everything.

See metadata filtering and access control.

Tools

  • Call tools with per-user or per-role tokens, so the source system enforces its own permissions.
  • If a shared service account is unavoidable, check the user's permission in your code before every call.
  • Deny by default: a new tool or data source is off until someone decides who can use it.

A permission test

DOCS = [
    {"id": "hr-salaries-2026", "roles": {"hr"}, "text": "Salary bands for 2026..."},
    {"id": "leave-policy", "roles": {"hr", "employee"}, "text": "Employees get 24 days of leave..."},
]


def retrieve(query: str, user_roles: set[str]) -> list[str]:
    return [d["id"] for d in DOCS if d["roles"] & user_roles]


def test_employee_cannot_see_salaries() -> None:
    results = retrieve("what are the salary bands", {"employee"})
    assert "hr-salaries-2026" not in results, "employee retrieved HR-only document"
    assert "leave-policy" in results
    print("permission test passed:", results)


test_employee_cannot_see_salaries()

Write one of these for your most sensitive data source, proving a low-privilege user can't get its content through the AI, and run it in CI.

Agents acting for users

When an agent acts on someone's behalf, it should hold that person's permissions, not more. For actions with external effects, add an approval step even when the user has the permission, because the action may have been triggered by injected content. See agent permissions.

Measuring it

  • Permission tests in CI, passing on every release.
  • Zero findings in periodic access reviews.
  • Every retrieved document and tool call in the audit log linked to a user.

From the floor

In the HR query bot, every request was tied to an authenticated user: we used OAuth2 with access tokens that expire after 30 minutes and hashed passwords, and retrieval was limited to what that user's role could see, because an HR assistant that shows salary data to every employee is a data breach.

Sources