Skip to content

Confidentiality Add-Ons

Two separate installable distributions, neither imported by anyinfer core nor a dependency of it. anyinfer_confidential implements Tier 1 (SealedTemplate) and Tier 2 (the AnyInfer Relay); anyinfer_shared holds the one composite type both sides report into. See the confidentiality tiers guide for what each tier guarantees, what it costs, and the ceiling it states.

from anyinfer_confidential import TemplateVault, seal_template
from anyinfer_confidential.relay import Relay, RelayRegistry, load_registry
from anyinfer_shared import ConfidentialityReport

Tier 1 — Sealed Templates

anyinfer_confidential.seal_template

seal_template(
    plaintext: str,
    *,
    key: bytes,
    template_id: str,
    key_id: str,
) -> EncryptedTemplate

Encrypt one template's plaintext into a shippable EncryptedTemplate asset.

This is the build-time step (anyinfer-confidential seal): plaintext never reaches the shipped asset, only its ciphertext does.

Parameters:

Name Type Description Default
plaintext str

The template source (e.g. containing {slot} placeholders).

required
key bytes

A 256-bit AES-GCM key, from generate_key().

required
template_id str

Identifier this template will be looked up by at render time.

required
key_id str

Identifier for key, so a TemplateVault's KeyRing can select the right key without guessing — required for key rotation to work at all.

required

Returns:

Type Description
EncryptedTemplate

The sealed asset, safe to write to disk or bundle into a client build.

anyinfer_confidential.EncryptedTemplate dataclass

EncryptedTemplate(
    template_id: str,
    key_id: str,
    nonce: bytes,
    ciphertext: bytes,
    sealed_at: str,
)

One sealed template asset — safe to ship inside a client bundle as-is.

Attributes:

Name Type Description
template_id str

Caller-assigned identifier, unique within one vendor's asset set.

key_id str

Which key in the TemplateVault's KeyRing decrypts this asset — the hook key rotation is built on: re-sealing under a new key_id invalidates nothing already shipped until the old key is dropped from the ring.

nonce bytes

The AES-GCM nonce used for this specific seal (never reused per key).

ciphertext bytes

The encrypted template text, including the GCM authentication tag.

sealed_at str

ISO-8601 timestamp of when this asset was sealed, for audit trails.

to_dict

to_dict() -> dict[str, Any]

A JSON-safe mapping — the on-disk asset format.

from_dict classmethod

from_dict(data: dict[str, Any]) -> EncryptedTemplate

Load an asset previously written by to_dict.

to_json

to_json() -> str

Serialize to the JSON asset format seal_template()'s CLI step writes.

from_json classmethod

from_json(text: str) -> EncryptedTemplate

Parse the JSON asset format.

anyinfer_confidential.TemplateVault

TemplateVault(
    *,
    key_ring: KeyRing,
    license_public_key: bytes,
    license_blob: bytes,
    revocation_checker: RevocationChecker | None = None,
    revocation_fail_closed: bool = False,
)

Decrypts a SealedTemplate on demand, renders once, and discards the plaintext.

No template cache in this class ever holds decrypted plaintext longer than one render() call — that is the whole confidentiality property Tier 1 offers, and it is enforced structurally (the decrypted buffer is a local variable, best-effort-zeroed before the call returns), not by convention.

Construct a vault bound to one deployment's license and key material.

Parameters:

Name Type Description Default
key_ring KeyRing

Decryption keys, keyed by key_id.

required
license_public_key bytes

The vendor's Ed25519 public key, for verifying license_blob's signature.

required
license_blob bytes

The signed, time-boxed entitlement blob for this deployment (see license.py). Validated locally on every render() call — no network access is required for the baseline guarantee.

required
revocation_checker RevocationChecker | None

Optional online revocation check (deny-list by license id). When None (the default), revocation checking is off and only the offline blob's signature and expiry gate decryption — the deployment works air-gapped.

None
revocation_fail_closed bool

When a revocation_checker is set and its check fails (e.g. network unreachable), the recommended default (False) degrades to the last cached good answer rather than refusing to render — a transient network failure degrading to "offline mode" is the expected degraded state for a feature whose baseline guarantee is already offline. Set True only when a deployment's security posture requires guaranteed revocation over availability; this is a real tradeoff, not a bug either way (see DESIGN.md §30.2).

False

renders_may_block property

renders_may_block: bool

Whether render can block on something other than CPU.

Exposed as a capability rather than left for a caller to infer from a private attribute, because exactly one caller needs it and the answer decides whether that caller offloads to a thread. The crypto path — license verification, AES-GCM decryption, formatting — is sub-millisecond and belongs on the event loop; a network-backed revocation checker does not, since one synchronous round trip there stalls every concurrent request in the process.

render

render(template: EncryptedTemplate, **slots: object) -> str

Decrypt template, render it against slots, and discard the plaintext.

Raises:

Type Description
LicenseError

The bound license is missing, malformed, unsigned by the expected key, or expired.

RevokedLicenseError

Online revocation checking is enabled and the license id is on the deny-list (or, under revocation_fail_closed, the check could not be completed at all).

TemplateDecryptionError

template.key_id has no provisioned key, or decryption failed (wrong key, or the ciphertext was tampered with — GCM's authentication tag catches this).

anyinfer_confidential.KeyRing

KeyRing(keys: dict[str, bytes] | None = None)

Maps key_id to decryption key — the mechanism key rotation is built on.

A compromised historical build's key is removed from the ring (or simply not provisioned to new deployments); templates still sealed under that key_id then stop decrypting, which is the intended effect of rotation, not a bug to work around.

add

add(key_id: str, key: bytes) -> None

Provision one key under its id.

get

get(key_id: str) -> bytes | None

The key for key_id, or None if it was never provisioned or was dropped.

anyinfer_confidential.generate_key

generate_key() -> bytes

Generate a fresh 256-bit AES-GCM key for sealing templates.

Licensing

The license gate is enforcement in TemplateVault.render's code path, not a cryptographic lock on the ciphertext — the guide's Tier 1 section states the ceiling precisely.

anyinfer_confidential.generate_signing_keypair

generate_signing_keypair() -> tuple[bytes, bytes]

Generate an Ed25519 keypair for license issuance.

Returns:

Type Description
bytes

(private_key_bytes, public_key_bytes), both raw 32-byte encodings. The private

bytes

key is the vendor's signing secret — it never ships in a client bundle; only

tuple[bytes, bytes]

public_key_bytes does, for verify_license.

anyinfer_confidential.issue_license

issue_license(
    deployment_id: str,
    *,
    private_key: bytes,
    valid_days: int,
) -> bytes

Issue a signed, time-boxed license blob for one deployment.

Parameters:

Name Type Description Default
deployment_id str

Identifier the issued license is bound to.

required
private_key bytes

The vendor's Ed25519 private key (generate_signing_keypair()'s first element).

required
valid_days int

How many days from now the license remains valid.

required

Returns:

Type Description
bytes

An opaque signed blob: pass it to the deployment for TemplateVault to consume.

anyinfer_confidential.verify_license

verify_license(
    blob: bytes, *, public_key: bytes
) -> LicenseBlob

Verify a license blob's signature and expiry.

Raises:

Type Description
LicenseError

The blob is malformed, its signature does not verify against public_key, or it has expired.

anyinfer_confidential.LicenseBlob dataclass

LicenseBlob(
    deployment_id: str, issued_at: str, expires_at: str
)

A verified license's contents, returned by verify_license on success.

Attributes:

Name Type Description
deployment_id str

The vendor-assigned identifier this license was issued for — the key RevocationChecker deny-lists are keyed on.

issued_at str

ISO-8601 issuance timestamp.

expires_at str

ISO-8601 expiry timestamp; verify_license rejects an expired blob.

anyinfer_confidential.license.RevocationChecker

Bases: Protocol

Checks whether a deployment's license has been revoked.

Returns True (not revoked), False (revoked), or raises when the check could not be completed at all (network unreachable, etc.) — TemplateVault treats a raised exception the same as an unreachable check, never as "not revoked."

__call__

__call__(deployment_id: str) -> bool

Return whether deployment_id is currently entitled (not revoked).

Tier 2 — The Relay

build_app requires a token-to-tenant mapping and has no unauthenticated mode: the response body is the decrypted, assembled prompt.

anyinfer_confidential.relay.Relay

Relay(
    *,
    vault: TemplateVault,
    registry: RelayRegistry,
    pacing: PacingPool | None = None,
    admission: AdmissionController | None = None,
)

Assembles (and optionally forwards) one request per call, retaining nothing.

Every handle() call is independent: no cache, no session, no store of prior requests. The zero-retention contract is structural, not a policy applied on top — there is simply nowhere in this class that a request or response body could persist past the return.

Bind a relay to its vault and routes.

Parameters:

Name Type Description Default
vault TemplateVault

Decrypts and renders this deployment's sealed templates.

required
registry RelayRegistry

Tenant-scoped routes.

required
pacing PacingPool | None

Optional pacing state shared across calls. None — the default — is bit-for-bit today's behaviour: no pooled limiter, no bookkeeping, no extra await. Supply one to make provider pacing work at all in forward mode, where a per-call client otherwise paces every request against an empty bucket. Holds timing metadata only; see PacingPool.

None
admission AdmissionController | None

Optional per-tenant concurrency bounds. None and an all-defaults TenantLimits are both inert.

None

handle async

handle(
    *,
    tenant_id: str,
    routing_key: str,
    slots: dict[str, object],
    mode: Literal["assemble", "forward"] = "assemble",
    provider_settings: ProviderSettings | None = None,
) -> RelayResult

Assemble a request, and optionally forward it.

Parameters:

Name Type Description Default
tenant_id str

Which vendor's route namespace to resolve routing_key against.

required
routing_key str

Selects the RelayRoute to assemble.

required
slots dict[str, object]

Non-proprietary slot-fill values the client supplies; these are the only caller-controlled content that reaches the template render.

required
mode Literal['assemble', 'forward']

"assemble" returns the rendered prompt for the client to send itself — no provider credential touches this process at all. "forward" additionally dispatches the request server-side using provider_settings, a short-lived, non-persisted credential the caller supplies fresh on every call (mirroring anyinfer.credentials' resolver pattern — nothing here stores it).

'assemble'
provider_settings ProviderSettings | None

Required when mode is "forward"; ignored otherwise.

None

Raises:

Type Description
RelayError

mode="forward" was requested without provider_settings, or the routing key does not resolve for this tenant.

admission

admission() -> AdmissionController

This relay's admission controller, for provisioning and for reading budgets.

anyinfer_confidential.relay.RelayRoute dataclass

RelayRoute(
    routing_key: str,
    template: EncryptedTemplate,
    target: str,
)

One vendor-configured orchestration route.

Attributes:

Name Type Description
routing_key str

What a client request selects this route by.

template EncryptedTemplate

The sealed template this route renders.

target str

The anyinfer target string (provider:model) a "forward" mode request dispatches to.

anyinfer_confidential.relay.RelayResult dataclass

RelayResult(
    assembled_prompt: str,
    generation_text: str | None,
    target: str,
    latency_ms: float,
)

What one Relay call returns — held in memory only, never written to disk here.

Attributes:

Name Type Description
assembled_prompt str

The rendered prompt text, present for both modes (a caller in "forward" mode may still want it for its own transient display).

generation_text str | None

The provider's response text, only in "forward" mode.

target str

The target the request was (or would be) dispatched to.

latency_ms float

Time spent inside Relay.handle, for metadata-only telemetry — never the prompt or response content.

anyinfer_confidential.relay.RelayRegistry

RelayRegistry()

Tenant-scoped route storage — the structural boundary multi-tenant isolation rests on.

set_limits

set_limits(tenant_id: str, limits: TenantLimits) -> None

Record one tenant's admission bounds alongside its routes.

Held here rather than only on the controller because this file is the provisioning path: a deployment that describes its tenants in one document should describe their capacity there too, not in a second script that has to be kept in step.

limits

limits() -> dict[str, TenantLimits]

Every provisioned tenant's bounds, for a Relay to install.

register

register(tenant_id: str, route: RelayRoute) -> None

Provision one route under one tenant.

resolve

resolve(tenant_id: str, routing_key: str) -> RelayRoute

Look up a route, scoped strictly to tenant_id.

Raises:

Type Description
RelayError

No route named routing_key is provisioned for tenant_id — deliberately the same error whether the route does not exist at all or exists only under a different tenant, so a probing client cannot use the error to enumerate other tenants' routing keys.

anyinfer_confidential.relay.load_registry

load_registry(path: str | Path) -> RelayRegistry

Build a RelayRegistry from a JSON provisioning file.

The registry is in-memory and has no persistence of its own, which left every deployment hand-writing the same registration loop. This is that loop, so a self-hosted relay can be provisioned from a file under configuration management rather than from a bespoke script.

The file holds sealed templates — ciphertext — so it is not itself secret material and can live in a config repository. Decryption still requires the deployment's TemplateVault, its key ring, and a valid license.

Expected shape::

{
  "tenants": {
    "acme": {
      "limits": {"max_in_flight": 8, "max_waiting": 32},
      "routes": [
        {
          "routing_key": "summarize",
          "target": "anthropic:claude-sonnet-4-5",
          "template": { ... EncryptedTemplate.to_json() payload ... }
        }
      ]
    }
  }
}

A tenant may also map directly to a bare list of routes, which is the original shape and means routes-only. Both are supported permanently: this file is a provisioning input under configuration management, and silently requiring a rewrite of every deployment's file to add an optional block would be a poor trade for the tidier grammar.

Parameters:

Name Type Description Default
path str | Path

The JSON provisioning file.

required

Returns:

Type Description
RelayRegistry

A registry with every listed route provisioned under its tenant, and every stated

RelayRegistry

limit recorded on it.

Raises:

Type Description
RelayError

The file is malformed, or a route entry is missing a required field.

anyinfer_confidential.app.build_app

build_app(
    relay: Relay,
    *,
    tokens: Mapping[str, str],
    max_request_bytes: int = DEFAULT_MAX_REQUEST_BYTES,
) -> Any

Build a Starlette app exposing relay at POST /v1/relay/assemble.

A vendor's own script constructs the bound Relay (its TemplateVault and RelayRegistry are deployment-specific) and serves the result with any standard ASGI server, e.g. uvicorn.run(build_app(my_relay, tokens=my_tokens), ...) — there is no bundled zero-configuration entry point, since the route registry always needs real provisioning first.

Parameters:

Name Type Description Default
relay Relay

The bound Relay to serve.

required
max_request_bytes int

Refuse a request body larger than this with 413. Enforced while reading rather than from content-length, which is absent on a chunked request and forgeable on any other. Pass 0 to disable. The default is deliberately small: this endpoint takes a routing key and a slot mapping, so a body approaching it is already anomalous. Exposure is post-auth only, but one misbehaving tenant must not be able to exhaust the process that is assembling other tenants' prompts.

DEFAULT_MAX_REQUEST_BYTES
tokens Mapping[str, str]

Maps bearer token to the tenant_id it authenticates. Required, and required non-empty: an empty mapping would serve decrypted prompt IP to anyone who can reach the port. Issue one long, random token per tenant (secrets.token_urlsafe(32)) and rotate by replacing the mapping and rebuilding the app. Terminate TLS in front of this app — a bearer token on a plaintext connection is readable by anything on the path.

required

Raises:

Type Description
ValueError

tokens is empty.

Note

mode="forward" is not reachable over HTTP. Forwarding needs short-lived provider credentials that this endpoint deliberately does not accept on the wire; a forward-mode request is answered with 400. Call Relay.handle in-process for that mode.

Composing a Report

anyinfer_shared.ConfidentialityReport dataclass

ConfidentialityReport(
    template_sealed: bool | None = None,
    relay_used: bool | None = None,
    relay_self_hosted: bool | None = None,
    execution_attested: bool | None = None,
    cpu_tee: CpuTeeKind | None = None,
    model_verified: bool | None = None,
    notes: tuple[str, ...] = (),
)

A composite record of every confidentiality guarantee that applied to one call.

Every field is optional: a caller using only one tier's package leaves the other tier's fields None rather than fabricating a false negative. None always means "not evaluated," never "not confidential" — a caller distinguishing "we didn't check" from "we checked and it failed" reads the difference directly off which fields are populated versus False.

Attributes:

Name Type Description
template_sealed bool | None

Tier 1 — the prompt was assembled from a SealedTemplate whose plaintext was decrypted only immediately before rendering, never persisted.

relay_used bool | None

Tier 2 — request assembly happened inside an AnyInfer Relay deployment rather than in this process.

relay_self_hosted bool | None

Whether the Relay instance was self-hosted by the caller's own organization, when relay_used is True; None when relay_used is not True or the deployment mode was not reported.

execution_attested bool | None

Tier 3 — mirrors anyinfer.local.attestation.ConfidentialExecutionStatus.end_to_end for the local backend that ran this call.

cpu_tee CpuTeeKind | None

The detected CPU TEE family backing execution_attested, when known.

model_verified bool | None

Tier 4 — the executing model's weights were checked against a vendor-signed manifest inside the same attested boundary execution_attested reports on.

notes tuple[str, ...]

Human-readable caveats — e.g. why a field could not be evaluated — attributable to a specific tier without inventing new typed fields for every possible caveat.

from_status classmethod

from_status(
    status: _ExecutionStatus,
    *,
    template_sealed: bool | None = None,
    relay_used: bool | None = None,
    relay_self_hosted: bool | None = None,
    include_detail: bool = True,
) -> ConfidentialityReport

Compose a report from a core attestation status plus the Tier 1-2 facts.

The one demonstrated producer of this type. Tier 3 and Tier 4 come off status — the object anyinfer.local.attestation.confidential_execution_status() returns — while Tiers 1 and 2 are facts only the calling application knows, because whether a prompt came from a sealed template or through a Relay is not something the local runtime can observe.

status is taken structurally, so this package still never imports anyinfer.

Parameters:

Name Type Description Default
status _ExecutionStatus

A ConfidentialExecutionStatus (or anything with the same shape).

required
template_sealed bool | None

Tier 1 — did this call render from a SealedTemplate? Leave None if Tier 1 was not evaluated; False means it was and did not apply.

None
relay_used bool | None

Tier 2 — was assembly done by an AnyInfer Relay?

None
relay_self_hosted bool | None

Whether that Relay was the caller's own deployment.

None
include_detail bool

Carry status.detail into notes, which is where the reason a tier did not hold survives — a bare False loses it.

True

Returns:

Type Description
ConfidentialityReport

A populated report. Tier 3/4 fields always reflect status; Tier 1/2 fields

ConfidentialityReport

reflect exactly what the caller passed, None included.

to_dict

to_dict() -> dict[str, Any]

A plain JSON-safe mapping, for logging or a compliance-mapping artifact.

from_dict classmethod

from_dict(data: dict[str, Any]) -> ConfidentialityReport

Round-trip counterpart to to_dict; unknown keys are ignored (forward-safe).

Errors

anyinfer_confidential.ConfidentialError

Bases: Exception

Base class for every error this package raises.

anyinfer_confidential.SealError

Bases: ConfidentialError

A sealed-template asset is malformed or could not be produced.

anyinfer_confidential.TemplateDecryptionError

Bases: ConfidentialError

A sealed template could not be decrypted: wrong key, or tampered ciphertext.

anyinfer_confidential.LicenseError

Bases: ConfidentialError

A license blob is missing, malformed, unsigned by the expected key, or expired.

anyinfer_confidential.RevokedLicenseError

Bases: ConfidentialError

Revocation checking found the license revoked, or a fail-closed check failed.

anyinfer_confidential.relay.RelayError

Bases: Exception

Base class for Relay-specific errors (unknown route, wrong tenant, etc.).