# LLM governance: a working framework

> LLM governance as five enforced layers: access, data boundaries, spend, visibility, change discipline, and the point in the request path where each control actually runs.

- Canonical: https://secondstack.ai/blog/llm-governance/
- Published: 2026-09-09
- Author: Oxana Astapova

---

Most enterprise AI governance in 2026 is a document. Usually a good one: it lists the approved tools, forbids pasting confidential data into public chatbots, and asks everyone to watch their API spend. Then it goes up on the intranet, and it governs nothing, because no request on its way to a model provider ever passes through a PDF.

The fix is not a better document. It is to take each rule the document states, find the point in the request path where a system can check it, and put a mechanism there.

## What is LLM governance?

LLM governance is the set of controls that decide who in an organization can use which models and tools, with what data, at what spend, and with what record of it. The controls that hold are enforced technically, at the gateway, the identity provider and the admin plane, not stated in a policy document.

It is the operational subset of AI governance. Model risk, evaluation and retention policy sit above it and belong to a different committee. Five layers cover the part that has to hold on the request path:

1. **Access.** Who reaches which models and tools. Enforced by your identity provider and the gateway's virtual keys.
2. **Data boundaries.** What may leave the perimeter. Enforced by guardrails that screen every request before it goes out.
3. **Spend.** What it may cost, and what happens at the limit. Enforced by budgets on the key.
4. **Visibility.** One record of who used what, at what cost. Enforced by routing all traffic through one gateway that logs it.
5. **Change discipline.** How anything new gets added. Enforced by default-deny in the admin plane.

<p class="ss-pullquote">The policy lives on the intranet. The request goes to the provider's API. Nothing on that path reads the intranet.</p>

A control answers two questions a policy cannot: where is the rule checked, and what happens when it trips. "Don't paste customer data into chatbots" answers neither. "Outbound prompts pass a PII check before egress, and a hit blocks the request" answers both. Here is where each of the five checks runs:

<figure style="margin:2rem 0">
<div style="overflow-x:auto;-webkit-overflow-scrolling:touch">
<svg viewBox="0 0 940 360" fill="none" xmlns="http://www.w3.org/2000/svg" style="width:100%;height:auto;min-width:640px;display:block" role="img" aria-label="Request-path diagram. Chat, IDEs and scripts, and agents each authenticate with their own virtual key and cross one gateway plane inside your infrastructure: SecondGuard screens what goes out, then the gateway checks the key and model grants, enforces hard budgets and logs every request, before it reaches provider APIs outside the perimeter. ControlTower below carries usage attribution and the configure-to-enable step for new models and providers.">
  <defs>
    <marker id="gv-ah" markerWidth="7" markerHeight="5" refX="7" refY="2.5" orient="auto"><path d="M0,0 L7,2.5 L0,5" fill="rgba(47,181,244,0.8)"/></marker>
    <linearGradient id="gv-gw-bg" x1="0" y1="0" x2="1" y2="0"><stop offset="0%" stop-color="rgba(228,92,255,0.18)"/><stop offset="100%" stop-color="rgba(228,92,255,0.08)"/></linearGradient>
    <filter id="gv-glow"><feGaussianBlur stdDeviation="4" result="b"/><feMerge><feMergeNode in="b"/><feMergeNode in="SourceGraphic"/></feMerge></filter>
  </defs>
  <rect x="4" y="12" width="636" height="336" rx="8" fill="rgba(255,255,255,0.04)" stroke="rgba(255,255,255,0.14)" stroke-width="0.75" stroke-dasharray="4 3"/>
  <text x="322" y="38" text-anchor="middle" fill="rgba(255,255,255,0.76)" font-size="14.5" font-weight="600" font-family="Inter,sans-serif" letter-spacing="0.08em">YOUR INFRASTRUCTURE</text>
  <g>
    <rect x="28" y="64" width="138" height="42" rx="5" fill="rgba(255,255,255,0.05)" stroke="rgba(255,255,255,0.18)" stroke-width="0.75"/>
    <text x="97" y="80" text-anchor="middle" fill="rgba(255,255,255,0.92)" font-size="15" font-weight="500" font-family="Inter,sans-serif">Chat</text>
    <text x="97" y="99" text-anchor="middle" fill="rgba(255,255,255,0.6)" font-size="12.5" font-family="'Geist Mono',monospace">virtual key</text>
    <rect x="28" y="136" width="138" height="42" rx="5" fill="rgba(255,255,255,0.05)" stroke="rgba(255,255,255,0.18)" stroke-width="0.75"/>
    <text x="97" y="152" text-anchor="middle" fill="rgba(255,255,255,0.92)" font-size="15" font-weight="500" font-family="Inter,sans-serif">IDEs &amp; scripts</text>
    <text x="97" y="171" text-anchor="middle" fill="rgba(255,255,255,0.6)" font-size="12.5" font-family="'Geist Mono',monospace">virtual key</text>
    <rect x="28" y="208" width="138" height="42" rx="5" fill="rgba(255,255,255,0.05)" stroke="rgba(255,255,255,0.18)" stroke-width="0.75"/>
    <text x="97" y="224" text-anchor="middle" fill="rgba(255,255,255,0.92)" font-size="15" font-weight="500" font-family="Inter,sans-serif">Agents</text>
    <text x="97" y="243" text-anchor="middle" fill="rgba(255,255,255,0.6)" font-size="12.5" font-family="'Geist Mono',monospace">virtual key</text>
  </g>
  <line x1="166" y1="85" x2="296" y2="120" stroke="rgba(47,181,244,0.55)" stroke-width="1.25" marker-end="url(#gv-ah)"/>
  <line x1="166" y1="157" x2="296" y2="157" stroke="rgba(47,181,244,0.55)" stroke-width="1.25" marker-end="url(#gv-ah)"/>
  <line x1="166" y1="229" x2="296" y2="194" stroke="rgba(47,181,244,0.55)" stroke-width="1.25" marker-end="url(#gv-ah)"/>
  <rect x="298" y="64" width="212" height="186" rx="10" fill="url(#gv-gw-bg)" stroke="rgba(228,92,255,0.68)" stroke-width="1.5" filter="url(#gv-glow)"/>
  <text x="404" y="96" text-anchor="middle" fill="rgba(228,92,255,1)" font-size="17" font-weight="700" font-family="Inter,sans-serif" letter-spacing="-0.02em">Gateway plane</text>
  <text x="318" y="127" fill="rgba(255,255,255,0.82)" font-size="14.5" font-family="Inter,sans-serif">SecondGuard on egress</text>
  <text x="318" y="153" fill="rgba(255,255,255,0.82)" font-size="14.5" font-family="Inter,sans-serif">key + model grants</text>
  <text x="318" y="179" fill="rgba(255,255,255,0.82)" font-size="14.5" font-family="Inter,sans-serif">hard budgets</text>
  <text x="318" y="205" fill="rgba(255,255,255,0.82)" font-size="14.5" font-family="Inter,sans-serif">request log</text>
  <line x1="404" y1="250" x2="404" y2="272" stroke="rgba(255,255,255,0.35)" stroke-width="1" stroke-dasharray="3 3"/>
  <rect x="264" y="270" width="280" height="66" rx="6" fill="rgba(255,255,255,0.05)" stroke="rgba(255,255,255,0.18)" stroke-width="0.75"/>
  <text x="404" y="291" text-anchor="middle" fill="rgba(255,255,255,0.92)" font-size="14.5" font-weight="600" font-family="Inter,sans-serif">ControlTower</text>
  <text x="404" y="310" text-anchor="middle" fill="rgba(255,255,255,0.62)" font-size="13" font-family="Inter,sans-serif">attribution &amp; analytics</text>
  <text x="404" y="327" text-anchor="middle" fill="rgba(255,255,255,0.62)" font-size="13" font-family="Inter,sans-serif">configure-to-enable</text>
  <line x1="510" y1="85" x2="700" y2="85" stroke="rgba(47,181,244,0.45)" stroke-width="1.25" marker-end="url(#gv-ah)"/>
  <line x1="510" y1="157" x2="700" y2="157" stroke="rgba(47,181,244,0.55)" stroke-width="1.25" marker-end="url(#gv-ah)"/>
  <line x1="510" y1="229" x2="700" y2="229" stroke="rgba(47,181,244,0.45)" stroke-width="1.25" marker-end="url(#gv-ah)"/>
  <text x="575" y="148" text-anchor="middle" fill="rgba(255,255,255,0.6)" font-size="12.5" font-family="'Geist Mono',monospace">metered</text>
  <g>
    <rect x="690" y="12" width="246" height="336" rx="8" fill="rgba(255,255,255,0.03)" stroke="rgba(255,255,255,0.14)" stroke-width="0.75" stroke-dasharray="4 3"/>
    <text x="813" y="38" text-anchor="middle" fill="rgba(255,255,255,0.76)" font-size="13" font-weight="600" font-family="Inter,sans-serif" letter-spacing="0.06em">OUTSIDE THE PERIMETER</text>
    <rect x="702" y="64" width="212" height="42" rx="5" fill="rgba(255,255,255,0.05)" stroke="rgba(255,255,255,0.18)" stroke-width="0.75"/>
    <text x="808" y="90" text-anchor="middle" fill="rgba(255,255,255,0.92)" font-size="15" font-weight="500" font-family="Inter,sans-serif">Anthropic API</text>
    <rect x="702" y="136" width="212" height="42" rx="5" fill="rgba(255,255,255,0.05)" stroke="rgba(255,255,255,0.18)" stroke-width="0.75"/>
    <text x="808" y="162" text-anchor="middle" fill="rgba(255,255,255,0.92)" font-size="15" font-weight="500" font-family="Inter,sans-serif">OpenAI API</text>
    <rect x="702" y="208" width="212" height="42" rx="5" fill="rgba(255,255,255,0.05)" stroke="rgba(255,255,255,0.18)" stroke-width="0.75"/>
    <text x="808" y="234" text-anchor="middle" fill="rgba(255,255,255,0.92)" font-size="15" font-weight="500" font-family="Inter,sans-serif">Gemini API</text>
    <text x="808" y="284" text-anchor="middle" fill="rgba(255,255,255,0.6)" font-size="13" font-family="Inter,sans-serif">metered on the way out,</text>
    <text x="808" y="302" text-anchor="middle" fill="rgba(255,255,255,0.6)" font-size="13" font-family="Inter,sans-serif">billed to your own accounts</text>
  </g>
</svg>
</div>
</figure>

## The five layers, and where each one is enforced

### 1. Access: who reaches which models and tools

The failure is the key that outlives its reason: a contractor sets up a provider key for a pilot, leaves, and the key stays, because rotating it means finding out which nine scripts break.

The control is identity. People sign in through SSO, their team decides which models they can reach, and every person and application gets its own virtual key instead of a copy of the provider's key. In SecondStack, directory groups from your identity provider map to teams. Models are granted to the organization, to named teams or to named people, separately for chat and for API access, and nothing is reachable until it is granted. Provider keys stay inside the gateway, encrypted, and no one gets a copy.

Deprovisioning is where this layer is tested. Sync jobs reconcile the directory, the admin plane and the gateway on a schedule, so when someone leaves, their AI access leaves with them. Nothing new on the offboarding checklist.

### 2. Data boundaries: what may leave the perimeter

This failure is quieter: a support engineer pastes a full customer record into a chat to draft a polite reply. The policy forbade it, and nothing was positioned to notice.

The control is a guardrail in the request path that screens prompts, attachments, tool results and replayed history before they reach the provider. Because it sits in front of the gateway, it covers chat, IDE plugins and cron jobs alike. In SecondStack this is SecondGuard: detectors for secrets and personal data run in parallel, the strictest verdict wins, and the verdict is block, mask in place, or log. Policies attach globally, to one model, or to a tag on a set of models, so external providers can carry a stricter posture than internal ones. [What it catches, and when to mask rather than block](/blog/llm-guardrails/), is its own post.

### 3. Spend: what it may cost, and what happens at the limit

The failure is the job nobody watched: a batch enrichment script pointed at the flagship model, still running when the office lights come back on.

Two different problems hide under one label. A runaway script is a technical fault, and a hard cap on its key is the only mechanism it respects. A team drifting past its monthly envelope is a management question, and freezing everyone because one estimate was wrong is the wrong answer to it. So: hard caps on the keys that scripts and agents use, and for teams a high safety-net cap with threshold alerts that reach the person who can tell a launch from a bug. In SecondStack, budgets exist at every level from a single key to the organization. Any figure you enter is a hard cap, and alerts fire on the way up to it. [Which cap belongs at which level](/blog/llm-cost-management/) is its own post.

### 4. Visibility: one queryable record of everything

The failure: the CFO asks what the company spent on LLMs last quarter, by team, and the answer is four provider invoices.

The control is one point that all traffic crosses, with attribution. Every request through the [LLM gateway](/blog/what-is-an-llm-gateway/) carries a virtual key, so the record knows who sent it, to which model, how many tokens, at what cost, and ControlTower turns that into usage per user, team, key and model. Administrative changes get an append-only history that the database itself refuses to rewrite, exportable to whatever your security team already reads. That history is what an access review actually needs. All of it lands in your own PostgreSQL.

### 5. Change discipline: how anything new gets added

The failure is the tool nobody approved: a new provider or an MCP plugin in a team's workflow, because one person had a card and a key.

The control is default-deny with an explicit enable step. In SecondStack a model or provider becomes reachable only after an admin configures it in ControlTower and grants it, and it takes effect on the next gateway deploy. Anyone can build an MCP connector for themselves, but only an admin can turn one on for the whole organization. What no software supplies is an owner and a cadence: one admin owns the catalog, and new models and tools land on a fixed schedule rather than on request, so "not yet" comes with a date.

## Is a blocklist governance? Is a certificate?

Three things wear the governance label without earning it.

**A blocklist** moves the traffic rather than the risk, onto personal devices where none of the five layers can see anything. It has a place and a ceiling, both covered in [detecting shadow AI](/blog/shadow-ai-detection/).

**A certificate** attests that a process exists and is audited. ISO 27001 applies to SecondStack through T1A, our implementation partner, and procurement will rightly ask for it. It does not sit in the request path, so it belongs beside the five layers, not instead of them.

**A committee** is good at deciding what the rules should be. A board that also tries to enforce them, approving use cases one meeting at a time, teaches teams to ship first and ask later. Let people set policy, and let machines enforce it.

## Start with the gateway, tune the guardrails second

Put a gateway you control in front of the traffic first. That one component carries spend, visibility and the key half of access on day one, and nobody's workflow changes. Guardrails come second, and need time in log-only mode before anyone gets blocked. Change discipline arrives with the admin plane and survives only if it has an owner and a cadence.

Through all five layers, hold one test: the governed path has to be more convenient than the ungoverned one. If it is slower than a personal ChatGPT account, people use the personal account, and governance ends up covering the traffic that matters least. That is why the five layers here share one identity and one database you own. Hold every vendor to the convenience test. Ours included.
