All insights

AI

Guardrails, Not Gates: Governing Generative AI Without Killing Velocity

Heavy approval boards make AI safe by making it useless, and teams route around them. A paved-road approach encodes governance into the platform so the safe path is also the fast path. What that looks like in practice.

Root Digit Security · AI Governance6 min read

Guardrails for generative systems are usually discussed as content filtering. That is the smaller half. The larger half is architectural: what the model is permitted to do, what its output is trusted to mean, and which actions require a human. Systems that get the architecture right survive a jailbreak with an embarrassing transcript. Systems that get it wrong survive it with an incident.

Prompt injection is a structural property, not a bug to be patched

A language model receives one undifferentiated token stream. Your instructions and the user's data occupy the same channel, with no mechanism that marks one as privileged. This is not an implementation defect — it is how the architecture works, and it is why prompt injection has no clean solution at the model layer.

Indirect injection is the serious variant. The attacker never speaks to your system at all: they place instructions in content your system will later retrieve — a web page, a PDF, a support ticket, a calendar invite — and your own retrieval step delivers the payload into the context with full apparent legitimacy.

The controls that do work are outside the model. Treat every model output as untrusted input to whatever consumes it. Constrain what the model can reach, so a successful injection has nothing valuable to reach for. And require human confirmation for actions that cannot be undone.

Least privilege applied to tools

An agent with broad tool access is an attacker's remote execution primitive once injection succeeds. The exposure is not what the agent normally does — it is the full union of what every tool can do, invoked in any order the attacker can induce.

  • Scope each tool to the minimum it needs. A ticket-reading tool reads the requesting user's tickets, not the ticket system.
  • Carry the end user's identity through to the tool call. An agent acting with service-account privileges is a privilege escalation waiting for a prompt.
  • Separate read from write. Retrieval and drafting need no write access at all; the write step is where confirmation belongs.
  • Make irreversible actions — sending, paying, deleting, publishing, granting — require explicit human approval showing the exact action, not a summary of it.
  • Rate-limit and budget-cap per session, so a loop induced by injection is bounded in blast radius and cost.

The design test is direct: assume the model has been fully compromised and is executing the attacker's instructions. What can it do? If the answer includes anything you would not accept, the guardrail belongs in the permission model, not the prompt.

Output is untrusted, and the vulnerabilities are the classic ones

Model output flowing unvalidated into a downstream system reintroduces every injection class the industry already knows. Generated SQL executed directly is SQL injection with extra steps. Generated markup rendered as HTML is cross-site scripting. Generated shell commands are command injection. Generated URLs fetched server-side are server-side request forgery.

Every existing control applies unchanged: parameterised queries, contextual output encoding, allow-lists, schema validation on structured output, and no path from generated text to an interpreter. None of this is new work — it is applying known practice to a source of input that people have not yet learned to distrust.

class Transfer(BaseModel):
    account_id: str = Field(pattern=r'^ACC[0-9]{10}$')
    amount: Decimal = Field(gt=0, le=10_000)
    currency: Literal['USD', 'EUR', 'GBP']

def handle(raw: str, user: User) -> Result:
    try:
        req = Transfer.model_validate_json(raw)      # shape
    except ValidationError as e:
        return Result.reject('malformed', e)

    if not user.owns(req.account_id):                # authority
        audit.flag('cross-account attempt', user, req)
        return Result.reject('unauthorised')

    return pending_confirmation(req)                 # human approves the action
Validate structure and semantics separately. Schema conformance proves the shape; the authorisation check proves the caller may do it. A model can emit a perfectly well-formed request for an account that is not the user's.

Layered filtering, and the arithmetic that makes it worth doing

Independent controls multiply. Three layers each catching 90% of attempts leave one in a thousand, provided their failures are genuinely uncorrelated.

Pbypass  =  ∏i   ( 1 − ri )    only if the layers fail independently

r_i — detection rate of layer i. The independence assumption is where this usually breaks: three classifiers fine-tuned from the same base model share blind spots, so their combined rate is far closer to the best single layer than the product suggests.

Independence is bought by using different mechanisms rather than different models. A deterministic policy check, a classifier, and a permission boundary fail for different reasons. Three variations on the same classifier fail together.

LayerMechanismCatches
Input policyDeterministic rules, PII detectionKnown patterns, secrets in prompts
Input classifierTrained detectorNovel jailbreaks, prohibited requests
System designInstruction placement, structured contextNaive direct injection
Output classifierTrained detectorHarmful or non-compliant generations
Grounding checkCitation verification against sourceUnsupported claims, fabrication
Permission boundaryScoped tokens, per-user authorisationEverything above, when it fails
Human confirmationExplicit approval of the concrete actionIrreversible consequences
A layered arrangement. Note that the last two are not content filters at all, and they are the layers that hold when the filters are bypassed.

Grounding verification for anything factual

Where a system answers from retrieved documents, verifying that each claim is supported by the retrieved text is both a quality control and a safety control. The practical implementation decomposes the answer into atomic claims and checks each for entailment against the cited passage — an inexpensive natural language inference task.

Unsupported claims are then flagged, removed, or the answer regenerated with an explicit instruction to answer only from the provided context. The measurable outcome is that the system's refusals become trustworthy: a system that declines when the corpus lacks the answer is more useful than one that always answers, because its answers can be relied upon without checking.

Red-team continuously, and keep the transcripts

Jailbreak techniques evolve faster than release cycles. A guardrail suite validated at launch decays, and the decay is invisible without ongoing adversarial testing. Maintain an evaluation set of attack prompts, extend it with every new technique reported publicly and every real attempt observed in your own logs, and run it on every deployment as a gate rather than a report.

Log every input, retrieval, tool call and output with enough fidelity to reconstruct a session. When an incident occurs — and over a long enough horizon one will — the question will be exactly what the model was given and exactly what it did. Systems that cannot answer that question end up disabled while the investigation runs, which is usually a more expensive outcome than the incident itself.

Explore how Root Digit can support your team

From discovery workshops to production deployment, our engineers and consultants partner with you across the lifecycle of your AI, robotics, and IoT initiatives.

Cookie Policy

We use cookies to enhance your browsing experience, serve personalized content, and analyze our traffic. By clicking "Accept All", you consent to our use of cookies. You can also choose "Necessary Only" to limit cookies to essential website functions only. Learn more