Mehdi Akiki
Published on

A fallback model must not get wider permissions

Authors
  • Mehdi Akiki avatar
    Name
    Mehdi Akiki
    Twitter

Article · Through the layers

The primary model request fails. The router selects a fallback client. That client was initialized for an internal administrator demo and has a broader tool context.

The user asked the same question. The application now has different authority because one upstream request failed.

Availability logic must not become an authorization upgrade. Keep the actor and capabilities owned by the workflow, outside model-client defaults.

Change the transport, preserve the authority

A model client supplies generated output. The tool gateway decides whether the resulting request is allowed. Both the primary and fallback path should use the same trusted operation context.

This Rust sketch shows the ownership direction:

fn fallback_route(
    gateway: &mut Gateway,
    context: &mut Context,
    primary: Result<Call, &str>,
    fallback: Call,
) -> Result<Output, ToolError> {
    let call = match primary {
        Ok(call) => call,
        Err("provider_unavailable") => fallback,
        Err(_) => return Err(ToolError::Transport),
    };
    gateway.execute(context, call)
}

The context is not read from generated output or replaced by a fallback client's default user. The real implementation must also handle outputs with no tool call, invalid arguments, and multiple calls according to the workflow's contract.

The authorization-through-tool-calls article explains the identity path. Here the failure route deserves the same attention as the normal route.

Preserve more than the actor ID

Keep tenant scope, target restrictions, enabled tools, approval bindings, deadlines, and remaining budgets. A fallback must not silently restart an operation with a fresh unlimited attempt count.

Model capabilities can differ. If the fallback cannot produce the required structured result or meet a safety requirement, fail or degrade within a tested contract. Do not remove validation to make the fallback answer usable.

External model routing may also change where data is processed. That requires its own product and data-handling decision; identical tool permissions do not settle every routing concern.

OWASP's authorization guidance supports enforcing permission at each request boundary. The fallback's output is another untrusted proposal to that same boundary.

Force the fallback in the test

Use a primary transport that fails in a controlled way. Let the fallback propose an operation forbidden to the current actor. Assert denial, unchanged storage, and zero forbidden external calls.

Then propose an allowed operation and confirm it uses the correct account reference and constraints. Repeat with a revoked grant and an exhausted budget.

Inspect the actual dispatch path rather than only comparing two configuration structs. A router can preserve the right fields and still call an older privileged helper downstream.

Fallback is optional when the contract cannot hold

Some operations should return a clear temporary failure rather than use a less capable path. That is a product choice, not an availability defect by definition.

Record which transport was used and why, without logging secret credentials or full private content. The operator should be able to explain the route and its outcome.

A model failure must not grant the user new authority. Test that where effects happen, including the fallback path that normally runs only during an outage.

Force the failure route with the same context object

The Rust reproduction feeds a controlled provider-unavailable result into the fallback router. The fallback proposes a refund while the current context belongs to a read-only support lookup. The router passes that proposal to the existing gateway context. It does not create another actor or select an administrative default.

The gateway denies the refund. Repeating the fallback proposal still leaves zero effects. An allowed read under the same fallback route returns account A's invoice and records only account A's credential reference. These observations test execution rather than merely comparing two configuration values.

Next, the test exhausts the operation's remaining call budget. A fallback read is refused without resetting it. Another case revokes the actor after context construction; the fallback read is also refused. Keeping the same object matters because authority includes the policy revision, account, workflow, deadline, approval bindings, and remaining budget, not just a user name copied into a new request.

The test implementation uses synthetic generated calls. It does not contact primary or fallback model providers. It therefore makes no claim about their output compatibility, regional data processing, or relative answer quality. Those are separate release questions when selecting real transports.

The fallback predicate is deliberately narrow. Only the controlled provider-unavailable case chooses the alternative proposal. Other failures return a typed failure. A production router should classify its own documented transport errors and avoid turning invalid arguments or policy refusal into another chance with wider tools.

Keep the gateway outside provider-specific clients. If one client cannot express the structured call contract, return a bounded degraded result or temporary failure. Preserving the user's authority is a condition of using the fallback, not an optional setting to remove when availability is under pressure.