LOTA Threat Model
This document states what LOTA is designed to protect, what the current implementation enforces, and what remains outside its boundary.
Security objectives
LOTA provides a hardware-backed attestation substrate for Linux hosts. It is NOT a behavioral cheat detector.
The protected properties are:
the attesting key is bound to a manufacturer-certified TPM through TPM 2.0 credential activation,
the verifier accepts only AIK certificates issued by the deployment's attestation CA,
the TPM quote is fresh and bound to verifier-provided nonce material,
firmware and Secure Boot state are pinned through PCR 0, PCR 1, and PCR 7,
Secure Boot enablement and the kernel command line are enforced machine-independently from the quote-bound TPM event log,
LOTA's boot commitment is bound through PCR14,
the agent binary and runtime-protected executable image are measured and bound into attestation or token material,
kernel-side enforcement gates executable mappings, ptrace, kernel module loading, and protected process mutation through BPF LSM programs,
release artifacts can be rebuilt and checked against signed hashes.
An integrator can build game-specific or fleet-specific policy on top of these properties. LOTA does not inspect gameplay behavior, memory signatures, network patterns, or user input.
Trust boundaries
Host
The host runs a TPM 2.0 device, Linux with BPF LSM support, SELinux enforcing mode for the packaged policy, kernel lockdown, module signature enforcement, IMA appraisal, fs-verity, and the LOTA agent.
The IMA appraisal requirement pins the kernel's appraisal mode
(ima_appraise=enforce|fix on the command line); the appraisal content --
file signatures and the rule set -- is distribution or operator-supplied (see
../operator/production-bringup/index).
LOTA ships no xattr-signing pipeline, and its own binaries are integrity-bound through fs-verity and the PCR14 boot commitment independent of IMA.
The agent is privileged. It owns TPM interaction, BPF LSM loading, runtime measurement, local IPC, D-Bus status, and attestation report construction.
Attestation CA
The attestation CA verifies the EK certificate chain, runs credential activation against the TPM, and issues a short-lived AIK certificate. Verifiers trust the CA certificate, not an agent-asserted public key. The EK reaches the CA and stops there: the attestation report carries no EK certificate field at all, so an attestation cannot be linked back to the hardware even by the party verifying it.
The enrollment request also carries the manufacturer intermediates the device read out of its own TPM, because a firmware TPM stores certificates that exist nowhere else. They arrive from a peer that has proved nothing yet, and the CA treats them accordingly: the frame bounds their count and size before a single one is allocated, an element that does not parse is dropped, and they are used as path material only. A supplied certificate can complete a route to a root the operator pinned; it can never become one, so a host that presents its own self-signed root is refused exactly as a host presenting nothing is.
One key per publisher, and it is the template that makes it so. A TPM primary
is derived from the hierarchy seed and the creation template, so two profiles
that present the same template receive the same key however many persistent
handles they occupy -- and the per-profile userAuth takes no part in that
derivation. The agent therefore puts the publisher's own identity, the SHA-256
of its CA anchor's SubjectPublicKeyInfo, into the template's unique field.
Distinct per publisher, reproduced exactly when that publisher re-enrols, and
absent on a host that answers to nobody, whose key is unchanged.
What that separation does not cover is the evidence. Two publishers who each
run a verifier receive the same firmware and Secure Boot measurements and the
same event log, and the log names the disk this host boots from, so comparing
what they each legitimately received tells them it is one machine. Identity is
unlinkable; evidence is not, and the product says so where a player can act on
it -- at the consent prompt, in --list-publishers and in the player
documentation.
The fork the design already had is the answer to it. A publisher whose titles only check tokens receives no report, no PCR values and no event log, so for them the separation is complete; a publisher who runs a verifier trades that away for a verifier's judgement. Nothing in the protocol can give two verifiers a per-publisher view of PCR 14 while both still verify it, so this is a disclosure to state plainly.
That is the only AIK trust model. A report with no AIK certificate is rejected at verification, and the certificate-backed AIK store refuses to record a bare public key at all, so an AIK cannot become trusted by being seen first. The anchorless stores exist for unit tests and for out-of-band provisioning that loads a store from material the operator already trusts.
The CA key is a high-value fleet secret. It is loaded as a crypto.Signer,
so both an on-disk PKCS#8 PEM (the explicit dev-only fallback) and a PKCS#11
token (HSM or SoftHSM, built with the pkcs11 tag) are supported. Production
deployments hold the key in an HSM; the on-disk path is logged loudly and is
never the production default. See ca-key.
Verifier
The verifier validates AIK certificates, TPM quotes, PCR policy, event logs, boot commitments, runtime protection digests, token nonces, revocations, and ban state.
The default store is SQLite for single-node deployments. A Postgres backend
(selected with --pg-dsn) holds the baseline, used-nonce, revocation, ban,
audit and attestation state in a shared database, so several verifier
instances behind a load balancer share enforcement state and any instance
answers for a client enrolled by any peer. See
../operator/ha-deployment for the
supported topologies.
SDK consumer
The game, anti-cheat service, or relying server consumes LOTA status and token verification results. It remains responsible for gameplay policy and behavioral detection.
The relying party's evidence is that token and nothing else. The verifier answers an attesting host with a verdict and a deadline, and issues no bearer credential a backend could check in place of the signature: a service asking whether a client is trustworthy verifies the TPM-signed token the client presents, so what it accepts is bound to the hardware rather than to a string that can be copied.
Server SDK's VerifyToken enforces token freshness: a token whose validUntil
is more than DefaultMaxTokenAge (plus MaxClockSkew) in the future is rejected,
so a misconfigured or compromised agent cannot mint an effectively immortal token.
The token carries no issued-at field, so issuers must size validUntil within that
window. The agent enforces this where the interval is read: an attest_interval
past DefaultMaxTokenAge is refused by the config parser and by
--attest-interval, because every token such an agent mints would be rejected by
every relying party while the agent itself kept running and reporting success.
Active threats
Threat |
LOTA control |
Residual risk |
|---|---|---|
Software-only fake attester |
Enrollment requires TPM 2.0 credential activation.
The CA issues an AIK certificate only after proving the AIK and EK live in the same TPM.
|
A verifier must run with the production CA trust root and certificate requirement enabled. |
Replayed attestation |
Verifier challenges are one-time nonces with expiry and used-nonce
tracking.
The TPM quote covers a binding nonce.
|
Clock and storage availability are operational dependencies for replay tracking. |
Agent-asserted report metadata |
The attestation binding nonce covers hardware identity, signed flags,
kernel hash, agent hash, and IOMMU status before verification of
|
Metadata outside that binding must not be promoted to security decisions without extending the binding. |
Firmware or Secure Boot drift |
Production policy pins PCR 0, PCR 1, and PCR 7 for homogeneous fleets.
Diverse fleets enroll each device's PCR 0/1/7 on first use, but only when
the event-log Secure Boot gate is active and the report proves Secure
Boot enabled.
The per-device row then anchors rollback/consistency and any later drift
rejects.
|
Firmware updates require deliberate policy rotation.
Raw pins do not scale to diverse single-machine populations; the event-log
checks below cover those.
On the diverse-fleet path, firmware tampering that keeps Secure Boot enabled
and predates the device's first attestation is not caught at first use --
firmware trust there rests on the Secure Boot and command-line gates, not on
the PCR 0/1 values.
The agent additionally reports the ESRT System Firmware version (telemetry) so
a future self-service re-anchor can use it as a firmware anti-rollback signal;
being firmware-reported it is meaningful only in combination with an unchanged
PCR 7 keyset, not as an independent control.
A re-anchor archives the superseded PCR 0/1/7 row, so a re-baseline is auditable
after the fact; on an HA deployment the archive and the re-anchor bookkeeping
live in the shared Postgres store so every verifier instance sees the same history.
The per-device re-anchor interval (the main barrier against repeated downgrade
re-anchors on the low-firmware-assurance path) is re-checked inside the write
transaction under the row lock, so a burst of concurrent attestations for one
device cannot race the check and re-anchor more than once per window.
The re-anchor discriminator admits a drift only when it preserves the Secure Boot
root of trust: PK/KEK/db byte-identical by event-log replay, dbx append-only
(revocation can grow, never shrink), Secure Boot still enabled, and the firmware
version not rolled back; anything touching PK/KEK/db escalates to the operator.
A firmware version below the vendor's own anti-rollback floor (the ESRT
LowestSupported value) is firmware running below the lowest version it claims
to accept, and escalates to the operator on every path. A floor of zero means
the vendor declared none.
|
Secure Boot disabled to boot a tampered kernel |
The verifier reads the firmware-measured
SecureBoot variable from the
event log (PCR 7) and accepts it only when the log replay reproduces the
TPM-quoted PCR, so the value cannot be fabricated or stripped.The replay follows what the TPM did:
EV_NO_ACTION records are informational and are not extended, and
where the log records that firmware started the TPM above locality 0 --
a measured static root of trust does -- PCR 0 begins at that locality.
A log that misdescribes either still has to reproduce the quote-signed
PCR values, so neither is a place to hide.The agent-reported Secure Boot flag is telemetry only.
|
Kernels signed for Secure Boot (including operator/MOK-signed) pass.
Control rejects unsigned boots, not signed-but-malicious kernels.
|
Signed kernel sabotaged via command line ( |
The verifier checks the GRUB-measured kernel command line (PCR 8 event
log, quote-bound) against a machine-independent parameter denylist.
Non-zero quoted PCR 8 with no measured command line is rejected as a
truncated log.
|
GRUB-only today: systemd-boot/UKI hosts measure the command line into PCR
12 and skip this check (Secure Boot enforcement still applies).
Denylist coverage is enumerative, not semantic.
|
Agent binary drift |
PCR14 boot commitment and agent hash policy bind the agent image.
fs-verity protects the installed binary.
Between a package update and the next cold boot the file on disk and
the running image differ by construction, and the agent reports that
as
LOTA_FLAG_UPDATE_PENDING. It is not a tamper signal and
carries no verdict: PCR 14 still commits to the running build, the
report still names it, and the divergence resolves at the reboot the
flag exists to announce. An offline swap of the on-disk file is a
different question and is answered by fs-verity plus the boot
commitment, not by this flag. |
Replacing the agent binary requires cold reboot, fs-verity re-enable, policy update, and re-attestation. |
Modified, non-enforcing agent (self-compiled client) |
Agent self-hash is bound into PCR 14, and
agent_hashes in the policy
pins the official hash so a verifier rederives PCR 14 only for that
binary.Different hash fails the match.
On the diverse-fleet path PCR 14 is dynamic and TOFU'd, so the verifier
refuses a
require_secureboot policy with empty agent_hashes
(advisory kernel_hashes do not substitute) unless --allow-unpinned-agent
is set.The per-device pin is established once, on the client's first
attestation: a stored baseline whose
agent_hash is absent is
refused rather than adopted from the report presenting it.It re-opens for one reason only, a package update, and only toward a
build
agent_hashes already names. The reported hash is not taken
on trust: PCR 14 is rederived from it and matched against the quoted
register first, so it is the binary that actually booted. The pin
then moves, the outgoing hash is archived, and one client may move
at most once per 24 hours.With an empty
agent_hashes there is no authority to appeal to and
the pin never re-opens; the drift is refused and an operator decides.Official hash comes from the reproducible signed release.
|
Without a pinned |
Early PCR14 tamper |
Initramfs PCR14 lock runs before normal userspace.
udev and SELinux restrict TPM device access.
systemd ordering starts the agent before login-capable targets.
|
PCR14 is OS-writable by the PC Client Profile.
Userspace cannot make that race impossible on every platform.
|
Runtime image substitution |
BPF LSM gates executable mmap and mprotect for protected processes
against the fs-verity allow-list.
The agent re-measures file-backed executable mappings from the kernel side.
A digest read from an inode is cached against that inode's device,
number, size and modification time, all read from the descriptor the
measurement holds open; fs-verity makes the contents behind such an
inode immutable, and any other file is a different key.
|
Anonymous executable memory and JIT code are not measured as modules.
Intended bound is W^X plus policy enforcement.
An object the kernel holds no fs-verity digest for is absent from the
measurement and reported as missing coverage, never folded in.
|
ptrace or process mutation |
BPF LSM hooks protect the agent and protected PIDs, including
__ptrace_may_access where available. Both refuse a read and an
attach alike, so nothing reads a protected process's /proc or
attaches a debugger to it.block_ptrace, on by default, extends the refusal to an attach on
any other task. |
Reading another process's
/proc is left to the kernel outside the
protected set. Same-uid access to it is ordinary, yama's
ptrace_scope covers the attach case, and refusing it for every
task denies lsof, ps, profilers and crash handlers without
protecting anything this project owns.Hook availability and verifier behavior must be validated on the
target kernel.
|
Ending a protected process |
lota-agent --terminate-protected is the only route: the LSM passes
no signal to a protected task from anything but itself, the agent or
the kernel, and the agent relays only SIGTERM and SIGKILL, only
for a caller kill(2) would have allowed.Every relayed termination is journalled, and the host reports
PROTECTED_TERMINATED in its status word and in every token it
issues until it reboots.The protected set the token carries names who is left, so a publisher
that knows which of its processes belongs there sees which one went.
|
The owner of a protected process can end it, which the LSM alone would
have refused. The bound is that no route to it avoids the agent, so
the termination is reported rather than silent.
The sticky bit says a protected process was ended on this host, not
which one -- pairing it with the expected protected set is the relying
party's policy.
Ending the agent is not this path:
--shutdown poisons PCR 14, and
resuming is a reboot. |
Kernel module or memory-only load |
Kernel lockdown, module signature enforcement, and BPF LSM gates reject
unsafe load paths.
With
strict_modules, a module load has to name a file in the
fs-verity allowlist, in either form the kernel accepts: the image
itself, and the compressed file modprobe hands the kernel on a
distribution that ships .ko.xz or .ko.zst. The allowlist
therefore has to name the compressed files, since that is what such a
host presents. A purpose the object does not recognise -- what a later
kernel adds -- is refused. |
Kernel vulnerability or disabled production gate is outside LOTA's software boundary. |
DMA attack |
The agent reports IOMMU state and production policy can require it. |
Platform firmware and hardware must actually expose and enable the IOMMU. |
EK certificate spoofing |
The CA verifies EK certificate chains against pinned manufacturer roots, with bundled intermediates as path material for the common leaf-intermediate-root manufacturer shape. |
A deployment must ship verified roots and the issuing intermediates for the supported TPM vendors or narrow supported hardware accordingly. |
Revoked or factorable endorsement key |
Enrollment checks the EK certificate against operator-loaded manufacturer
CRLs (SIGHUP-refreshable).
Revoked EK, or one whose issuer has only stale CRLs, is refused before
credential activation.
An EK whose RSA modulus carries the ROCA (CVE-2017-15361) fingerprint is
rejected outright, independent of CRL coverage.
|
Issuer with no configured CRL is not CRL-checked:
Operators must load the feed for every manufacturer that publishes one.
The intrinsic-weakness check covers only ROCA; other key-generation flaws
need their manufacturer revocation feed.
|
Supply-chain artifact swap |
Reproducible builds and cosign-signed |
Consumers must verify the signed manifest and rebuild with the documented toolchain. |
Remote MITM |
Enrollment and verifier communication use TLS, with examples using explicit CA material rather than disabled verification. |
Operators must provision and rotate TLS certificates correctly. |
STRIDE mapping
STRIDE class |
LOTA treatment |
|---|---|
Spoofing |
TPM credential activation, CA-issued AIK certificates, TLS server authentication, mTLS demo for SDK-server integration. |
Tampering |
PCR policy, PCR14 boot commitment, fs-verity, signed BPF objects, BPF LSM gates, SELinux confinement. |
Repudiation |
Verifier logs attestation decisions, nonce use, baseline changes, and AIK
state.
Release artifacts are signed through Sigstore.
|
Information disclosure |
Verifiers receive CA-issued pseudonyms rather than EK certificates.
Reports should not expose EK material after enrollment.
|
Denial of service |
Rate limits and nonce limits bound challenge pressure.
The attestation listener bounds concurrent connections
(
--max-connections, default 256), so a client stampede cannot
spend unbounded TLS handshakes and report verifications.Local enforcement may intentionally fail closed when production gates are missing.
|
Elevation of privilege |
LOTA reduces post-boot tamper paths through lockdown, module signing, BPF
LSM, SELinux, and ptrace restrictions.
It does not replace the kernel's own privilege boundary.
|
Explicit non-goals
Detecting cheat behavior by scanning memory, input, game state, or network patterns.
Supporting hosts that intentionally disable lockdown, module signature enforcement, SELinux enforcing mode, fs-verity, IMA appraisal, or TPM access control required by production policy.
Proving the runtime integrity of the kernel. Measured boot binds the kernel image that was loaded (see "Root of trust" below); it cannot prove that a kernel which booted clean stays honest, because a kernel compromised at runtime (a 0-day, a signed-but-vulnerable driver, a DMA write) produces the same boot measurements and sits inside the TCB that produces the agent's measurements. A measurement produced by layer N cannot bootstrap trust in layer N.
Measuring anonymous executable memory as a trusted module set.
Making PCR14 immutable from userspace on platforms where the TPM profile leaves it OS-writable.
Replacing operator key management, CA key ceremony, or release governance.
Root of trust: static measured boot (SRTM), not DRTM
LOTA roots its boot measurements in the platform's Static Root of Trust for Measurement (SRTM): the firmware measures itself, the Secure Boot policy, and the boot chain into the TPM, and the boot chain measures the kernel image, command line, and initrd before the kernel runs. LOTA pins PCR 0/1/7 (firmware, platform config, Secure Boot) and PCR 14 (its own boot commitment) today; the kernel-image PCRs from the same SRTM (PCR 4, and PCR 8/9 on GRUB or 11/12/13 on systemd-boot/UKI) are available to deployments that also pin them. Because these measurements are taken before the kernel executes and are extended into hardware PCRs, a compromised kernel cannot forge them after the fact. The practical trust anchor for "a trusted kernel booted" is PCR 7: it reflects the Secure Boot signing chain and stays constant across kernel updates, so a fleet trusts the distribution's signing key without maintaining a per-kernel hash.
The kernel_hashes value an --export-policy run writes comes from one of
those same registers, read at export time; the exported document names which
register it was, and which kernel the exporting host had booted. It is not a
hash of a kernel image, and no configuration key redirects it at one, so a
policy that is to describe a kernel is exported from a host booted into that
kernel. It stays advisory either way: the register is read and reported by code
running on the kernel it describes, and on a GRUB host it moves when nothing
about the kernel has, because grubenv is measured into it and rewritten
every boot.
Pinning these registers is not optional. A report whose pcr_mask omits
PCR 0, 1 or 7 is refused before any baseline is consulted or written, and no
configuration accepts one. This closes the downgrade an attacker would
otherwise ask for: an agent that simply declined to quote the firmware and
Secure Boot registers would bypass the pin while still presenting a
well-formed, correctly signed report.
All of it presumes a UEFI firmware, and the verifier proves that rather
than assuming it: a report is refused unless its event log carries the
firmware's own measurement of the EFI global SecureBoot variable into a
quote-authenticated PCR 7. Legacy BIOS/CSM has no EFI variables to measure,
so it cannot produce that evidence and cannot attest. The check is about the
firmware interface, not the Secure Boot setting -- whether Secure Boot must
be enabled stays a policy question (require_secureboot). PCR 14 is not
usable as the UEFI signal: it holds the shim MOK state, so it is zero both on
BIOS and on a UEFI host that boots without shim (own PK/KEK/db, a directly
signed systemd-boot or UKI), and that host attests normally.
The PCR 14 chain is mandatory on the same terms. A report must declare both
the initramfs lock and the agent boot commitment; the verifier derives the
expected PCR 14 as the lock value with the commitment chained on top and has
no second derivation to fall back on. A host without the 90lota dracut
module therefore does not attest: without the initramfs lock, PCR 14 stays
OS-writable between the kernel handoff and the agent's first extend, and any
code running in that window could seed the value the baseline would pin. The
agent refuses to build such a report locally, so the missing module is named
on the host rather than surfacing as a remote rejection.
What the commitment binds, and what it does not
The commitment is SHA-256(tag || agent_hash), chained onto the initramfs
lock value. It names the agent binary that took the boot and the platform
baseline that binary chained onto, and nothing else.
Within a boot, PCR 14 cannot be reset from userspace. The agent extends the commitment once; a second agent, a different build, or any other writer moves the register away from the value the reported hash derives, and the verifier refuses. The agent refuses locally on the same comparison, and an operator-requested shutdown poisons the register deliberately so a pause is visible rather than silent.
Across a boot, the register is cleared by the hardware reset and rebuilt in a fixed order: the firmware measures its own state, the initramfs lock helper extends before any userspace, and the agent extends next. A commitment already present when the agent starts after a cold boot means something ran before it, which the agent refuses by name.
Freshness is the verifier's nonce over a quote signed in that session, not the register's contents. A recorded PCR 14 value proves nothing on its own, because there is no way to present it without a fresh signature over it, and the value is expected to repeat: the same host running the same agent build has the same commitment every boot, which is what makes a per-client baseline comparable at all.
What is out of scope is a local root that runs before the agent in the same boot. Such an attacker can extend the commitment themselves and, having reached the boot state the AIK auth is sealed to, quote it -- and that was equally true when the counters were in the digest, since they could be predicted. The layers that address that attacker are Secure Boot, the IMA appraisal floor, fs-verity on the agent binary and the ordering that puts the agent ahead of every login-capable target; PCR 14 records which binary committed, it does not prove that a process is still alive.
A TPM that restarts across S3 restores every PCR unchanged, and the derivation does not move with the counters, so the register matches on both sides with no candidate scan and no skew window to configure.
Dynamic Root of Trust for Measurement (DRTM) -- Intel TXT, AMD SKINIT, driven on Linux by the TrenchBoot / Secure Launch project -- would re-measure the kernel from a CPU-rooted late launch into PCR 17-22, removing the firmware and bootloader from the trusted computing base. LOTA does not adopt DRTM, for two reasons that are structural, not temporary:
It is not universal. DRTM requires specific hardware and firmware (Intel TXT with a chipset-signed SINIT ACM, or AMD SKINIT, plus IOMMU, plus firmware enablement that consumer boards frequently hide or omit) and bleeding-edge kernel and bootloader support. LOTA targets every machine with a TPM 2.0 and a recent Linux, including ordinary gaming hosts; a feature gated on server-class platform configuration cannot be part of that baseline.
It cannot be validated without that hardware. DRTM relies on real CPU instructions and signed ACMs that swtpm and the usual emulators do not provide, so the path cannot be exercised in CI or on a developer workstation.
DRTM also does not remove the need for a reference value: it relocates the root of trust but still produces a measurement that an operator must compare against a pinned value or a signature, so it does not lower the maintenance burden it is sometimes assumed to. Runtime kernel integrity therefore remains out of scope; deployments that require it must add a layer below the kernel (DRTM, a measuring hypervisor, or a confidential-computing TEE) outside LOTA.
Validation status
The software paths are covered by local build, unit, fuzz, and integration tests as documented in ../contributor/development/index.
Hardware TPM validation remains required for release claims that depend on physical TPM behavior. swtpm validation is useful for protocol and regression coverage, but it is not a substitute for running the full enrollment, attestation, and runtime protection path on target hardware.
Multi-tenancy and tenant scoping
A single verifier can serve several isolated tenants -- enterprise fleets or
individual game titles -- without one tenant seeing or affecting another. The
tenant is not self-asserted by the host: the attestation CA assigns it at
enrollment and writes it into the AIK certificate subject
(OrganizationalUnit). The verifier reads the tenant only after it has
verified the certificate chain, so a host cannot forge or change its own
tenancy. A certificate with no organizational unit maps to the reserved
default tenant; a certificate with a malformed or ambiguous (multiple)
organizational unit is rejected fail-closed before any state is written.
Tenancy scopes state and enforcement, not the cryptographic root of trust, which is per device regardless of tenant:
Hardware bans are strictly per tenant. A hardware identity banned in one tenant is untouched in every other; there is no cross-tenant or global ban tier. The ban store keys on
(tenant, hardware_id)and attestation checks only the ban recorded in the attesting client's own tenant.Revocations, baselines, the audit log, the attestation log, and session tokens all carry the tenant so the operator surface can scope them.
PCR policy can be bound per tenant. A signed policy may name a tenant; a client whose certificate carries that tenant is verified against the bound policy, with the active policy as the fallback for unbound tenants. The binding travels inside the signed policy document, so a signed policy authenticates its own scope.
What the audit trail can account for
Every operator action that changes a client's trust state -- revoke, unrevoke, ban, unban, reanchor, delete -- requires an actor and a reason, at the CLI and again at the API, and both reach the audit row. One rule, six verbs. An action that could be taken anonymously is one the trail cannot account for, so there is no verb that takes trust away or gives it back without naming who did it and why.
The direction is the point. Imposing a restriction is the reversible,
conservative act; lifting one is what a fleet most wants signed, and so is
delete, which removes a client's trust state outright and for a long time
asked for the least of any verb. A trail that attributed a ban but not the
unban, or a revoke but not the deletion, would name the cautious half of every
pair and leave the consequential half to a row that names nobody.
This is an accountability property and not an access control: an admin key can still take every one of these actions. What the requirement buys is that the log stays evidence -- a reader can tell an operator-initiated change from any other, and can name the operator.
The monitoring API enforces the same boundary. API keys are scoped: an
operator key names a role (reader or admin, admin implies reader) and a
tenant set (or * for every tenant), loaded from a file of key hashes and
reloaded on SIGHUP. A scoped key sees only its tenants' clients, bans,
revocations, audit and attestation entries; a request that names a client or
resource outside the key's tenant set is answered as if it did not exist (404),
never 403, so the key cannot even probe another tenant's namespace. The
fleet-wide surfaces that carry no tenant dimension are withheld from scoped
keys entirely: /api/v1/stats omits the fleet-wide counters and /metrics
is refused. Environment keys (LOTA_ADMIN_API_KEY /
LOTA_READER_API_KEY) are global-scope by construction: the variable carries
a key and nothing else, so there is nowhere to express a tenant list and the
principal it authenticates is unscoped. Delegating a tenant therefore means
issuing a key in the key file, not narrowing an environment key. See
../operator/multi-tenancy for configuration.
Operational requirements
Production deployments must:
install the agent, systemd units, SELinux policy, udev TPM labeling, IMA policy, and signed BPF object,
enroll each host through the attestation CA (enrollment requires an RSA endorsement key, the TCG EK template H-1 that every TPM 2.0 ships, and an RSA AIK; an ECC EK is refused at the start of the ceremony),
configure verifiers with the CA root and a production PCR policy,
maintain EK root bundles for the supported TPM vendors,
verify release manifests before shipping binaries,
validate BPF LSM hook attachment on the target kernel and distribution,
document operator recovery for AIK rotation, policy rotation, and legitimate binary updates.
See ../operator/production-bringup/index, policies/README.rst, and selinux/README.rst for the deployment details.