Privilege-Stripped External Execution and Inventory-Scoped Trampolines

  • Name: privilege-stripped external execution and inventory-scoped trampolines

  • Category: integration-boundary reference / router-and-plugin hardening note / external-execution privilege-stripping design / positive-design companion to callback and arbitrary-call exploits

  • Summary: Protocols often need flexible external execution: swap routers, strategy hooks, helper adapters, market-specific plugins, migration modules, or keeper-selected execution legs. The recurring mistake is to let that flexibility run directly from a privileged contract identity such as a vault, asset manager, rebalancer, order executor, or helper that already holds cross-account permissions and ambient balances. Once external code runs from that identity, every allowed callback, selector, sibling call, standing approval, and leftover balance owned by the privileged contract becomes part of the extension surface. Healthy systems split the roles. The privileged contract decides what one leg is allowed to do, funds only that leg’s inventory, and then hands execution to a deliberately unprivileged trampoline or sandbox whose identity carries no broader rights. The router or plugin should see the trampoline as caller, not the vault / manager / keeper that opened the leg.

  • Why this note exists:

    • callback-frame-truth-security-object explains what exact in-flight frame should have been committed, and temporary-capability-closure-before-external-delivery explains when a broad temporary capability should already have been dead.
    • A separate recurring design question is architectural: if we still need flexible external execution, what identity should that code actually run as?
    • Arcadia’s post-incident RouterTrampoline fix is the clean motivating example. The protocol still wanted flexible router support, but the real repair was not ban routing; it was stop letting initiator-shaped routing bytes execute from the rebalancer's privileged identity.
    • Clober preserves the negative plugin cousin just as clearly: a user-installed strategy hook sat directly on the vault burn boundary, so the strategy surface was not merely advisory logic; it had become live withdrawal-time authority.

The six joints a healthy privilege-stripped execution design should keep fused

  1. Privileged planner separated from unprivileged executor

    • The contract that authenticates the user, account, market, or rebalance frame should not be the same identity that arbitrary router or plugin code sees as msg.sender.
    • The planner decides the leg. The executor performs the leg with materially weaker rights.
  2. Inventory scoped to one leg

    • External execution should receive only the exact input assets and allowances needed for that one step.
    • All balances sitting in the manager / vault / helper is not a legitimate inventory model once untrusted code can run.
  3. No inherited cross-account or cross-module authority

    • The executor should not already be whitelisted as asset manager, operator, fee claimer, rescue role, router admin, or callback-trusted helper anywhere else in the system.
    • If the external leg can reach other protocol modules, it should do so only as a minimally privileged inventory holder, not as a governance- or account-level authority.
  4. Return path bound to declared outputs, not ambient balances

    • The planner should settle from measured leg-local deltas or explicit returned amounts, not from whatever token balance increased on the privileged contract after the call.
    • The point of the trampoline is not only caller-identity stripping. It is also balance-bucket isolation.
  5. Approvals and permissions die with the leg

    • One-shot approvals, one-step transfer authority, and temporary inventory should be cleared or naturally exhausted after the call.
    • A good trampoline leaves behind no reusable standing power worth attacking later.
  6. Extension semantics constrained before execution

    • Privilege stripping is not a substitute for route commitment, selector control, or asset-compatibility review.
    • The planner still has to decide which router, plugin, market, selector set, and asset semantics are allowed. The trampoline only ensures those choices do not inherit extra authority by accident.

Three external-execution promotions healthy systems should not collapse

  1. An external leg exists and may run somewhere

    • The protocol may genuinely need a router, plugin, strategy, or adapter hop for this stage.
    • That only answers some outside execution is part of the plan. It does not yet answer which contract identity should perform that hop or which balances that hop is allowed to touch.
  2. The protocol publishes one weak executor identity and one leg-local inventory bucket

    • A healthy system then answers the stronger question what exact caller identity and asset bucket are we willing to expose to this external endpoint for this one leg?
    • This is already a meaningful promotion above an external leg exists, but it is still not the same thing as any outputs from that leg may now mutate parent-vault accounting or cross-account state however they want.
  3. Parent-side settlement spends against that executor’s outputs

    • The planner may finally answer the still stronger question which returned assets, deltas, or settlement effects from this weak executor are allowed to count as money-good protocol state right now?
    • This still deserves its own review boundary. We ran the route through a trampoline is not yet the same thing as the parent vault, manager, or account system may trust every resulting balance change as canonical settlement truth.

The recurring design target is to keep three questions separate: does this route need an external leg at all?, what exact weak identity and inventory bucket should run it?, and what exact outputs from that leg may the parent system honor afterward?

What sound design looks like

  • External-leg admission, weak execution, and parent-side settlement stay distinct

    • Healthy systems do not flatten we need this route leg, this exact weak shell may execute it, and the parent system may now honor these returned assets or deltas into one ambient router call succeeded story.
    • The planner first decides that the leg is allowed at all, then chooses one intentionally weak executor identity plus one scoped inventory bucket, and only later settles from the explicit outputs that shell was supposed to produce.
  • The privileged contract commits the leg, then steps back

    • A vault, manager, or router first authenticates actor, target account, stage, allowed external endpoint, input asset, maximum input amount, and expected output asset set.
    • It then funds a narrower execution shell with only the assets needed for that leg.
  • The external endpoint sees a weak identity

    • The router, strategy, or adapter should interact with a helper that is not already trusted elsewhere as a broad operator.
    • If that endpoint tries to call sibling protocol surfaces, those surfaces should see an ordinary inventory holder rather than the privileged planner.
  • Allowance scope is leg-local

    • Approvals should be exact-amount or tightly bounded per call, ideally reset immediately after use.
    • safeApprove(router, type(uint256).max) from a manager or vault is usually the architectural smell this note is trying to kill.
  • Inventory isolation is explicit

    • The helper owns only the input tokens for the current leg and only for the current call.
    • If the external endpoint misbehaves, it can at worst redirect or stall that scoped inventory, not exercise unrelated account-manager power or sweep the planner’s other assets.
  • Planner-side settlement consumes only declared output buckets

    • After the leg, the planner measures or receives only the tokens it expected this trampoline to handle.
    • Outputs from one leg should not be confused with unrelated deposits, rewards, or parked balances.
  • Extension failure degrades to bounded loss or revert, not privilege spillover

    • The clean failure mode is this swap leg failed or this plugin misused its own tiny inventory.
    • The dirty failure mode is this router leg inherited asset-manager identity, cross-account permissions, or vault-wide balances.

Useful positive design shapes

1. Dedicated router trampoline

  • The planner transfers tokenIn to a helper contract dedicated to external routing.
  • The helper grants only the router-level spend needed for that call and then executes the router.
  • The router never sees the planner’s identity or its wider approvals.
  • Arcadia’s RouterTrampoline is the clearest current corpus example of this design move.

2. Per-plugin vaultlets or sub-accounts

  • Instead of letting strategies execute inside the parent vault’s full identity, give each strategy or market-specific extension its own narrowly funded sub-account.
  • Parent-vault settlement then pulls back outputs or reconciles against explicit sub-account boundaries.
  • This is the architectural opposite of Clober’s burnHook design, where attacker-installed strategy code sat directly on the withdrawal boundary of the main vault.

3. Selector-pinned execution shells

  • If flexible execution is unavoidable but the extension still needs callbacks, the helper should only expose one or a few tightly pinned selectors for that leg.
  • The external endpoint should not be able to turn one approved swap leg into generic helper can call arbitrary sibling functions now.

4. Pull-back settlement instead of ambient-balance inference

  • The planner should reconcile from explicit returned assets or measured helper-local deltas.
  • This avoids repeating the Penpie-style mistake where a balance increase inside a watched contract was mistaken for proof of the intended source.

5. Disposable or epoch-scoped helper identities

  • For especially sensitive flows, use helpers whose permissions are per-call, per-order, or per-epoch rather than permanent and reusable.
  • A helper that survives forever with broad standing approvals eventually becomes its own privileged surface.

Design questions worth asking first

  • What identity does the external router, strategy, or plugin actually see as caller?
  • If that identity is compromised or simply behaves adversarially, what other protocol rights does it already possess outside this one leg?
  • Which assets can the external leg move: only this leg’s input inventory, or every asset currently sitting in the parent manager / vault / router?
  • Does settlement later consume explicit leg-local outputs, or does it reason over ambient parent-contract balances?
  • If the external endpoint calls back into the protocol, does it arrive as a weak inventory holder or as a pre-trusted asset manager / operator / order executor?
  • Which approvals survive after the external call finishes or reverts?
  • Could two different legs accidentally share the same helper identity and therefore the same residual allowances or parked balances?

Red flags that usually mean privilege stripping is the missing control

  • Raw router.call(data) or strategy.someHook(...) executes directly from a contract already whitelisted elsewhere as manager, operator, or router.
  • The same contract both authenticates account-level authority and exposes arbitrary external execution.
  • The protocol funds an external leg by approving a router against the parent vault / manager balance rather than by moving exact inventory into a weaker shell.
  • A plugin system is described as just strategy logic or just swap routing even though it runs during mint, burn, withdraw, liquidation, or rebalance paths.
  • Settlement later infers success from parent-contract balance changes rather than from helper-local accounting.
  • The helper or trampoline accumulates idle balances, standing approvals, or cross-module roles over time.
  • The only defense story is the router is allowlisted or the strategy implements the right interface, even though the deeper problem is that the endpoint inherits the wrong identity if it ever misbehaves.

Failure-prevention checklist

  • Review flexible routing in three passes instead of one: does this stage need an external leg at all, what exact weak executor identity plus leg-local inventory bucket may run it, and what exact returned outputs may the parent system settle from afterward.
  • Separate the contract that authenticates privileged intent from the contract that performs flexible external execution.
  • Ensure routers, plugins, strategies, and adapters see an identity with no cross-account manager role, no governance role, no rescue role, and no broad callback-authenticated status elsewhere in the system.
  • Fund external execution by transferring exact input inventory into the helper or by using leg-local escrow, not by exposing the parent contract’s whole balance sheet.
  • Grant only exact or tightly bounded allowances to the external endpoint, and clear them immediately after the call when feasible.
  • Keep helper identities inventory-scoped and role-thin. If a helper starts accumulating ambient balances or broader permissions, it has stopped being a trampoline and started being a new privileged surface.
  • Bind allowed router / plugin / selector / asset semantics before the call; the helper should not become an excuse for generic arbitrary execution.
  • Reconcile outputs from helper-local deltas or explicit returned amounts, not from parent-contract ambient balances.
  • Test malicious routers and plugins that attempt sibling calls back into the protocol, approval reuse, residual-balance theft, selector pivoting, and cross-leg contamination.
  • Review whether legacy manager or vault allowlists accidentally trust the helper identity too much over time.
  • Add operational telemetry for helpers holding unexpected balances, stale allowances, or repeated failed external calls that suggest an extension is probing for broader authority.

Audit prompts

  • Which of the three questions does this control actually answer: some external leg exists, this exact weak executor identity and inventory bucket may run it, or the parent system may now trust these outputs as canonical settlement effects?
  • What exact identity does external code run under, and what other protocol surfaces already trust that identity?
  • If the chosen router or plugin became adversarial tomorrow, what is the maximum set of assets and permissions it could reach through that caller identity alone?
  • Are input assets moved into a leg-local bucket first, or does the external endpoint spend against the parent contract’s balance or approvals directly?
  • After the external call, does the protocol settle from helper-local outputs or from ambient balance changes on a privileged contract?
  • Which approvals, token balances, or roles remain on the helper after success, revert, partial fill, or early exit?
  • Can helper identity be reused across unrelated accounts, routes, or strategies in a way that lets one leg consume leftovers from another?
  • Does any callback or sibling call path treat the helper as if it were the original privileged planner?
  • If the helper is supposed to be unprivileged, where is that fact enforced and regression-tested?
  • What is the blast radius if the helper’s code path is exploited: one leg’s inventory, one account’s scoped assets, or every asset reachable from the parent manager / vault?
  • Could a simpler architecture eliminate the need for dynamic external execution at this privilege boundary altogether?

Lessons that should already be standard

  • Flexible routing is not the same thing as privileged routing. A protocol can support many external endpoints without letting them inherit its crown jewels.
  • An external leg exists, this exact weak executor may run it, and the parent system may now honor these returned outputs are different authority claims. Treat them as separate promotions, not one router call completed event.
  • msg.sender is part of the asset flow. If the wrong contract identity performs the external leg, you already changed the economic meaning of every later callback and sibling call.
  • Inventory scoping and identity scoping are the same control seen from two angles. One answers which balances can move; the other answers what else can this caller do while moving them.
  • Allowlisting a router or plugin does not solve privilege inheritance. The deeper question is what the allowlisted endpoint becomes capable of because of the caller identity it receives.
  • A helper that keeps broad roles, ambient balances, or standing approvals is not a trampoline; it is a second vault wearing a fake mustache.
  • The clean design target is not the router can only trade approved tokens; it is the router can only touch this one leg's inventory and cannot be mistaken for a privileged protocol actor anywhere else.

Negative exemplar cluster: what unstripped external execution looks like

  • Arcadia Finance is the clean external-router exemplar: initiator-shaped swapData executed from RebalancerSpot, so the router leg inherited a caller identity already trusted as asset manager by other accounts. Arcadia’s later RouterTrampoline fix is the positive-design anchor for this note.
  • Clober is the attacker-installed strategy-hook cousin: a user-created pool bound arbitrary strategy code directly into the burn path of a reserve-bearing vault instead of forcing strategy logic to run from a weak inventory-scoped shell.
  • Penpie is the harvest-accounting cousin: permissionless market registration let a hostile endpoint participate directly in a balance-sensitive publication path, and the protocol inferred truth from ambient contract balance changes instead of from source-bound leg-local accounting.

How to use this note with the exploit corpus

Internal linkages

Control surface