AID-H-037.002: Thinking-Budget & Reasoning Compute Bounding

Version 1.20261015 · Actionable sub-technique

Set, enforce, and audit model-specific reasoning or thinking-token budgets so attackers cannot coerce a model into unbounded hidden reasoning, decision paralysis, or excessive paid compute. The control defines budget tiers by request intent, risk tier, user trust, and workflow criticality; clamps or rejects excessive requested reasoning effort; and emits evidence for downstream anomaly detection.

Pillars: app, infra

Lifecycle: building, operation

Scope boundary

Owns reasoning-effort and hidden thinking-token budget tiers, clamping or denial, and enforcement evidence for model-specific reasoning compute. It does not own general request quotas, infrastructure throttling, anomaly detection, or ordinary invocation settings.

AID-I-003: AID-H-037.002 bounds model-specific reasoning or thinking compute; AID-I-003 owns general request rate limits, concurrency, service quotas, resource controls, and denial-of-wallet throttling. Reasoning budgets should feed but not duplicate the authoritative general quota system.

AID-D-005.007: AID-H-037.002 prevents excessive configured reasoning effort before or during invocation; AID-D-005.007 detects observed token, tool-use, parameter, and cost spikes. Detection reports abuse but does not set the reasoning budget.

AID-H-033.006: AID-H-037.002 owns reasoning-effort and thinking-token fields; AID-H-033.006 owns general sampling, output-token, log-probability, and provider safety-profile resolution. Both are enforced at invocation but have distinct policy dimensions and evidence.

Implementation guidance

AID-H-037.002-G001

Enforce reasoning-specific budget tiers at the AI gateway and emit budget evidence for downstream cost-spike and availability monitoring.
Concept:

Reasoning-capable models may expose controls such as reasoning effort or thinking-budget tokens. Treat those settings as security-relevant. The gateway should choose a budget from a reviewed policy, clamp or reject caller-supplied overrides, and emit an event that downstream cost-spike detection can correlate with latency and billing.

Step 1: Define and bind reasoning budget tiers

Load this policy only after verifying its signed immutable bytes. Pass the SHA-256 digest of those exact verified bytes into the gate; the resulting decision and evidence event carry both the policy version and digest. Unknown intent classes fail closed instead of falling through to a default tier.

# File: reasoning_security/thinking_budget_policy.yaml
policy_version: "2026.07"
intent_tiers:
  low_risk_lookup: "low_risk_lookup"
  standard_request: "standard"
  high_impact_review: "high_impact_review"
  public_request: "untrusted_public"
tiers:
  low_risk_lookup:
    max_thinking_tokens: 256
    max_reasoning_effort: "low"
    allowed_user_trust: ["employee", "service"]
  standard:
    max_thinking_tokens: 1024
    max_reasoning_effort: "medium"
    allowed_user_trust: ["employee", "partner", "service"]
  high_impact_review:
    max_thinking_tokens: 4096
    max_reasoning_effort: "high"
    allowed_user_trust: ["employee"]
  untrusted_public:
    max_thinking_tokens: 512
    max_reasoning_effort: "low"
    allowed_user_trust: ["anonymous", "untrusted"]
deny:
  client_unbounded_budget: true
  unknown_reasoning_effort: true
Step 2: Validate policy, then clamp or reject requests
# File: reasoning_security/thinking_budget_gate.py
from __future__ import annotations

from dataclasses import dataclass
from typing import Callable, Literal


ReasoningEffort = Literal["low", "medium", "high"]
EFFORT_ORDER = {"low": 1, "medium": 2, "high": 3}
POLICY_FIELDS = {"policy_version", "intent_tiers", "tiers", "deny"}
TIER_FIELDS = {
    "max_thinking_tokens", "max_reasoning_effort", "allowed_user_trust",
}
REQUIRED_DENIALS = {"client_unbounded_budget", "unknown_reasoning_effort"}
HEX = set("0123456789abcdef")
ReserveQuota = Callable[[str, int], str]


@dataclass(frozen=True)
class BudgetDecision:
    decision: str
    request_id: str
    intent_class: str
    tier: str
    thinking_tokens: int
    reasoning_effort: ReasoningEffort | None
    policy_version: str
    policy_sha256: str
    quota_receipt_id: str | None
    reasons: list[str]


def require_sha256(value: object, field: str) -> str:
    if (
        not isinstance(value, str)
        or len(value) != 64
        or set(value) - HEX
        or value == "0" * 64
    ):
        raise ValueError(f"{field} must be a nonzero lowercase SHA-256 digest")
    return value


def validate_policy(
    policy: dict, policy_sha256: str, provider_capabilities: dict,
) -> tuple[str, dict[str, str], dict[str, dict]]:
    if not isinstance(policy, dict) or set(policy) != POLICY_FIELDS:
        raise ValueError("verified thinking-budget policy schema mismatch")
    version = policy["policy_version"]
    if not isinstance(version, str) or not version.strip() or len(version) > 64:
        raise ValueError("policy_version must be a bounded nonempty string")
    require_sha256(policy_sha256, "policy_sha256")

    intent_tiers = policy["intent_tiers"]
    tiers = policy["tiers"]
    deny = policy["deny"]
    if (
        not isinstance(intent_tiers, dict)
        or not intent_tiers
        or any(
            not isinstance(intent, str) or not intent
            or not isinstance(tier_name, str) or not tier_name
            for intent, tier_name in intent_tiers.items()
        )
        or not isinstance(tiers, dict)
        or not tiers
        or set(intent_tiers.values()) != set(tiers)
    ):
        raise ValueError("intent_tiers must map exactly onto the tier population")
    if (
        not isinstance(deny, dict)
        or set(deny) != REQUIRED_DENIALS
        or any(deny[name] is not True for name in REQUIRED_DENIALS)
    ):
        raise ValueError("policy must explicitly deny unbounded budgets and unknown effort")
    if (
        not isinstance(provider_capabilities, dict)
        or set(provider_capabilities)
        != {"max_thinking_tokens", "reasoning_efforts"}
    ):
        raise ValueError("verified provider-capability schema mismatch")
    provider_maximum = provider_capabilities["max_thinking_tokens"]
    provider_efforts = provider_capabilities["reasoning_efforts"]
    if (
        type(provider_maximum) is not int
        or provider_maximum <= 0
        or not isinstance(provider_efforts, list)
        or not provider_efforts
        or len(provider_efforts) != len(set(provider_efforts))
        or any(effort not in EFFORT_ORDER for effort in provider_efforts)
    ):
        raise ValueError("verified provider capabilities are invalid")

    for tier_name, tier in tiers.items():
        if not isinstance(tier_name, str) or not tier_name:
            raise ValueError("tier name must be nonempty")
        if not isinstance(tier, dict) or set(tier) != TIER_FIELDS:
            raise ValueError(f"thinking-budget tier schema differs: {tier_name}")
        maximum_tokens = tier["max_thinking_tokens"]
        maximum_effort = tier["max_reasoning_effort"]
        allowed_trust = tier["allowed_user_trust"]
        if (
            type(maximum_tokens) is not int
            or maximum_tokens <= 0
            or maximum_tokens > provider_maximum
            or maximum_effort not in EFFORT_ORDER
            or maximum_effort not in provider_efforts
            or not isinstance(allowed_trust, list)
            or not allowed_trust
            or len(allowed_trust) != len(set(allowed_trust))
            or any(not isinstance(item, str) or not item for item in allowed_trust)
        ):
            raise ValueError(f"tier is invalid or provider-incompatible: {tier_name}")
    return version, intent_tiers, tiers


def enforce_budget(
    *, request_id: str, authenticated_context: dict, policy: dict,
    policy_sha256: str, provider_capabilities: dict,
    requested_tokens: int | None, requested_effort: str | None,
    reserve_quota: ReserveQuota,
) -> BudgetDecision:
    policy_version, intent_tiers, tiers = validate_policy(
        policy, policy_sha256, provider_capabilities
    )
    if not isinstance(request_id, str) or not request_id:
        raise ValueError("request_id is required")
    context = authenticated_context if isinstance(authenticated_context, dict) else {}
    intent_class = context.get("intent_class")
    user_trust = context.get("user_trust")

    def block(tier_name: str, reason: str) -> BudgetDecision:
        return BudgetDecision(
            "block", request_id, str(intent_class or ""), tier_name,
            0, None, policy_version, policy_sha256, None, [reason],
        )

    if (
        not isinstance(intent_class, str) or not intent_class
        or not isinstance(user_trust, str) or not user_trust
    ):
        return block("", "authoritative_context_missing")
    tier_name = intent_tiers.get(intent_class)
    if tier_name is None:
        return block("", "intent_not_mapped")
    tier = tiers[tier_name]
    if user_trust not in tier["allowed_user_trust"]:
        return block(tier_name, "user_trust_not_allowed")
    if requested_tokens is not None and (
        type(requested_tokens) is not int or requested_tokens <= 0
    ):
        return block(tier_name, "invalid_token_budget")
    if requested_effort is not None and (
        not isinstance(requested_effort, str)
        or requested_effort not in EFFORT_ORDER
    ):
        return block(tier_name, "unknown_reasoning_effort")

    maximum_tokens = tier["max_thinking_tokens"]
    maximum_effort = tier["max_reasoning_effort"]
    requested = maximum_tokens if requested_tokens is None else requested_tokens
    effective_tokens = min(requested, maximum_tokens)
    effective_effort = requested_effort or maximum_effort
    reasons: list[str] = []
    if requested > maximum_tokens:
        reasons.append("thinking_budget_clamped")
    if EFFORT_ORDER[effective_effort] > EFFORT_ORDER[maximum_effort]:
        effective_effort = maximum_effort
        reasons.append("reasoning_effort_clamped")
    quota_receipt_id = reserve_quota(request_id, effective_tokens)
    if not isinstance(quota_receipt_id, str) or not quota_receipt_id:
        raise RuntimeError("quota reservation returned no receipt")
    return BudgetDecision(
        "allow", request_id, intent_class, tier_name,
        effective_tokens, effective_effort, policy_version, policy_sha256,
        quota_receipt_id, reasons,
    )
Step 3: Emit policy-bound budget evidence
# File: reasoning_security/budget_event.py
from __future__ import annotations

import hashlib
import json
from dataclasses import asdict
from typing import Callable

from reasoning_security.thinking_budget_gate import BudgetDecision


WriteEvidence = Callable[[str, bytes], str]
ReadEvidence = Callable[[str], bytes]


def emit_reasoning_budget_event(
    *, event_id: str, subject_pseudonym: str, provider_model: str,
    decision: BudgetDecision, write: WriteEvidence, read: ReadEvidence,
) -> dict:
    if any(
        not isinstance(value, str) or not value
        for value in (event_id, subject_pseudonym, provider_model)
    ):
        raise ValueError("event identity and provider model are required")
    event = {
        "schema_version": "aidefend.reasoning-budget-event.v1",
        "event_id": event_id,
        "subject_pseudonym": subject_pseudonym,
        "provider_model": provider_model,
        **asdict(decision),
    }
    payload = (
        json.dumps(event, sort_keys=True, separators=(",", ":"), allow_nan=False)
        + "\n"
    ).encode("utf-8")
    receipt_id = write(event_id, payload)
    if not isinstance(receipt_id, str) or not receipt_id or read(receipt_id) != payload:
        raise RuntimeError("reasoning-budget evidence readback mismatch")
    return {
        "receipt_id": receipt_id,
        "event_sha256": hashlib.sha256(payload).hexdigest(),
        "outcome": "PASS",
    }
Step 4: Run the policy fixture
# File: tests/test_thinking_budget_gate.py
from copy import deepcopy

from reasoning_security.thinking_budget_gate import enforce_budget


POLICY_SHA256 = "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa"
POLICY = {
    "policy_version": "2026.07",
    "intent_tiers": {
        "low_risk_lookup": "low_risk_lookup",
        "standard_request": "standard",
        "high_impact_review": "high_impact_review",
        "public_request": "untrusted_public",
    },
    "tiers": {
        "low_risk_lookup": {
            "max_thinking_tokens": 256,
            "max_reasoning_effort": "low",
            "allowed_user_trust": ["employee", "service"],
        },
        "standard": {
            "max_thinking_tokens": 1024,
            "max_reasoning_effort": "medium",
            "allowed_user_trust": ["employee", "partner", "service"],
        },
        "high_impact_review": {
            "max_thinking_tokens": 4096,
            "max_reasoning_effort": "high",
            "allowed_user_trust": ["employee"],
        },
        "untrusted_public": {
            "max_thinking_tokens": 512,
            "max_reasoning_effort": "low",
            "allowed_user_trust": ["anonymous", "untrusted"],
        },
    },
    "deny": {
        "client_unbounded_budget": True,
        "unknown_reasoning_effort": True,
    },
}
CAPABILITIES = {
    "max_thinking_tokens": 8192,
    "reasoning_efforts": ["low", "medium", "high"],
}


def reserve(request_id: str, tokens: int) -> str:
    assert request_id == "req-2026-0719-0042"
    assert 0 < tokens <= CAPABILITIES["max_thinking_tokens"]
    return "quota-receipt-0042"


def decide(**overrides):
    values = {
        "request_id": "req-2026-0719-0042",
        "authenticated_context": {
            "intent_class": "high_impact_review",
            "user_trust": "employee",
        },
        "policy": POLICY,
        "policy_sha256": POLICY_SHA256,
        "provider_capabilities": CAPABILITIES,
        "requested_tokens": 8192,
        "requested_effort": "high",
        "reserve_quota": reserve,
    }
    values.update(overrides)
    return enforce_budget(**values)


overflow = decide()
assert overflow.decision == "allow"
assert overflow.thinking_tokens == 4096
assert overflow.reasons == ["thinking_budget_clamped"]
assert overflow.policy_version == "2026.07"
assert overflow.policy_sha256 == POLICY_SHA256
assert decide(requested_tokens=None).thinking_tokens == 4096
assert decide(requested_tokens=-1).reasons == ["invalid_token_budget"]
assert decide(requested_tokens="unbounded").reasons == ["invalid_token_budget"]
assert decide(requested_effort="extreme").reasons == ["unknown_reasoning_effort"]
assert decide(authenticated_context={
    "intent_class": "high_impact_review", "user_trust": "anonymous",
}).reasons == ["user_trust_not_allowed"]

for denial in ("client_unbounded_budget", "unknown_reasoning_effort"):
    invalid_policy = deepcopy(POLICY)
    invalid_policy["deny"][denial] = False
    try:
        decide(policy=invalid_policy)
    except ValueError as exc:
        assert "explicitly deny" in str(exc)
    else:
        raise AssertionError(f"disabled deny flag was accepted: {denial}")

print("PASS")

Run python tests/test_thinking_budget_gate.py. The fixture proves exact YAML/Python schema parity, policy digest/version propagation, bounded defaulting, overflow clamping, invalid-input and trust denial, and fail-closed rejection when a required deny flag is disabled.

Operational notes

Action: make reasoning effort a gateway-controlled field, not a free-form client parameter. Clamp excessive requests, block invalid effort levels, and log every budget decision with enough context for cost and availability monitoring.

Keep the action safely bounded

Reconcile the complete population of every eligible reasoning request, requested/effective budget decision, provider attempt, and actual usage record; reject expired, revoked, or stale policy and asset bindings under the policy-defined freshness window.

Verify safely

A separately credentialed verifier independently replays policy/tier selection, provider translation, quota and actual usage with forged intent/trust, absent/negative/overflow budget, unknown effort, route failover, concurrency, provider drift, and missing-usage fixtures, reads back effective state from the enforcement system, exercises positive, negative, boundary, stale-evidence, and dependency-failure fixtures, and reconciles every eligible item.

Open-source tools

Commercial / hosted tools

Threat mappings

MITRE ATLAS

MAESTRO

OWASP LLM Top 10 2026

OWASP ML Top 10 2023

OWASP Top 10 for Agentic Applications 2026

NIST Adversarial Machine Learning 2025

Cisco Integrated AI Security and Safety Framework

Google Secure AI Framework 2.0 - Risks

Databricks AI Security Framework 3.0

AID-H-037.002: Thinking-Budget & Reasoning Compute Bounding | AIDEFEND

Loading interactive view…

A Practical Knowledge Base for AI Security Defenses

Version