BeyondTrust/POV

← Back to dashboard

Design — the demo cells we do not have, and the roles they would serve

Audience: contributor · Profile: demo · Read this when: you are deciding what to build after the network cell, or wondering why a role you expected to find has no page.

Status: Design note, v1. Nothing here is built. It records a measurement, a definition, and three candidates in the order they are worth doing — so the next person to ask "what else could we demo?" starts from evidence rather than from a brainstorm. Corrected after review, twice. (1) An earlier draft called the Windows endpoint story blocked. It is not — EPM for Windows is a shipping product; what is missing is the dashboard integration. (2) It also called itops the weakest persona shipped and made "give it cloud cards" a sequenced work item. Measured, that is three of five cards needing a local VMware install and two working on cloud — a single missing card, not a project. §5 carries both corrections and §7 no longer lists the second. (3) §5b called the Workload Lab consumer blocked on the SPIFFE bridge. It is not — Workload Credentials authenticates a workload identity with no stored PAT, which is the whole point of the product, and §5b now says so. Depends on: the two cells that exist — OT Demo Cell and Network Demo Cell — whose shared shape §2 extracts.


1. The measurement

Nine personas ship (cloudops, devops, hypervisor, itops, ot, dba, security, sre, netadmin). Cross-referencing every requires_flags / requires_any_flag in services/personas.py against feature_flags.flags() leaves twelve flags no persona card names at all:

cloud_unmanaged_discovery_enabled   mcp_server_enabled          skytap_enabled
cost_explorer_enabled               portainer_enabled           vm_spend_cap_enabled
entitle_registration_enabled        pov_cloud_enabled           vm_suspend_schedule_enabled
entitle_user_jit_enabled            pov_environments_enabled    workload_lab_enabled

Strip the three POV-profile flags — personas.applies() answers false on a POV instance, so those are not persona territory by design — and the rest cluster into two groups plus some noise:

Group Flags Reading
Cost and governance cost_explorer, vm_spend_cap, vm_suspend_schedule, cloud_unmanaged_discovery A coherent role with nobody telling its story.
Non-human principals mcp_server No persona owns the thing that acts on its own.
Noise portainer, workload_lab, entitle_registration, entitle_user_jit Covered adjacently — sre and devops cards reach the same surfaces through other flags.

A flag with no card is not automatically a gap. It is a prompt to ask whether a role is missing, and these two survive that question.

Since written: the finops persona (§4) claimed five of these — cloud_unmanaged_discovery, cost_explorer, vm_spend_cap, vm_suspend_schedule and entitle_user_jit — taking the list from twelve to seven. The snippet in Verification reproduces the current list; the block above is kept as the measurement this note was written from, not as live state.

One reading changed in the doing. entitle_user_jit was filed as noise above; it is not. It grants time-boxed access to the dashboard itself, including administrator, which turns out to be the governance role's strongest card rather than an adjacent one — "nobody is a standing admin of the thing that spends the money". entitle_registration stays orphaned and stays noise: registration happens at deploy time, by the thing being deployed, which is a devops concern.

Since updated again: the aiops persona (§3) claimed mcp_server_enabled, leaving six. Of those, three are POV-profile flags that are not persona territory by design, so the substantive remainder is entitle_registration, portainer and workload_lab — the last of which is derived and deliberately unclaimable, since a card must name the constituent lab flag its target tab actually needs.

2. What a demo cell is, and what it is not

Both shipped cells share a shape, and naming it is the load-bearing part of this note — because it is what disqualifies most ideas.

A cell is a place whose shape is the argument. The OT cell's subnet has no egress, and the air gap is the demo. The network cell's device has no inbound rule and no useradd, and being unreachable by normal means is the demo. In both, the environment is not scenery for a feature tour — it is the premise, and the PAM layers are the resolution.

Two consequences follow, and both have teeth:

Role: AI / agent platform engineer. Owns: the things that act without a person at the keyboard.

devops and sre are humans running pipelines and clusters. Nothing in the registry owns the non-human principal itself, and mcp_server_enabled is named by zero cards.

The premise: an autonomous worker in an egress-controlled subnet that holds no standing credential. It fetches one per task, every action is recorded, and the grant can be pulled mid-run. The absence of a standing credential is the demo, exactly as the air gap is the OT cell's.

Why it is first:

Scope it honestly. The MCP server at /mcp is read-only access to dashboard data (MCP Server). It demonstrates an AI client reading an estate; it is not an agent with privileged access to infrastructure, and a card implying otherwise would be overclaiming. The cell is new work that can reuse the MCP server as one surface, not a wiring exercise over it.

The worker is baked into the image, the same way §5's EPM agent and the OT cell's DMZ broker are. The deploy paths have no user-data hook, so anything that must be running at first boot is baked at Packer time; anything needing a fresh secret — a registration token, a workload identity — is a post-deploy job, because a token baked into an image has expired before the image is used. That is not a constraint peculiar to this cell: it is the rule both shipped cells follow, and it is why provisioners/ exists.

Open question, now answered: the worker drives this dashboard's own MCP server. It reads the estate on a loop and names, in one line, the SPIFFE ID it proved and the token it spent. Reading rather than acting, deliberately: the argument does not need the blast radius to be frightening, it needs nobody to be able to say what the blast radius is.

Shipped as the agent cell — agent-demo-cell.md, examples/playbooks/agent/, services/agentcell_service.py, and the aiops persona. Three things the build settled that this section had guessed at:

4. Candidate B — cloud governance (a persona, deliberately not a cell) — BUILT

Role: FinOps / cloud governance. Owns: privileged infrastructure nobody is accounting for.

Four orphan flags sit together — cost_explorer, vm_spend_cap, vm_suspend_schedule, cloud_unmanaged_discovery — and admission_control / resource_expiry are touched only lightly by cloudops and security. "What privileged thing exists that nobody onboarded, and what is it costing?" is a real role with real machinery behind it.

It gets no cell, by §2. The story is the estate, not a place. Standing up an environment to demonstrate discovery would be staging the answer — you would be discovering the thing you just built, which proves nothing about a real estate.

So: a persona whose cards point at existing surfaces. Cheap, and it closes five orphan flags at once.

Shipped as finops — label FinOps / cloud governance, page at personas/finops.md. Five cards: unmanaged discovery, the spend cap, Entitle user JIT, the costs page, and suspend schedules.

Two things the build changed about the analysis above:

Counter-argument that no longer applies, kept for the record: cloud_unmanaged_discovery might have belonged to security as a missing card rather than to a new role. It went to finops because the discovery story is about what is accumulating, not who has access — but a security card pointing at the same listing would not be wrong.

5. Candidate C — a Windows endpoint cell

Role: itops already exists. This is a cell for an existing persona.

The gap is sharper than "no cloud VDI" — cloud VDI is built and reasonably mature (Virtual Desktops, preview). The gap is that "remove local admin from a Windows endpoint and elevate per-application" — among the most recognisable demos BeyondTrust has — has no home in this dashboard, because epml_enabled is the only EPM flag in the registry and integrations/beyondtrust/epml.md is EPM for Linux.

To be clear about what that is and is not. EPM for Windows is a shipping BeyondTrust product and there is nothing preventing its use — what is missing is the dashboard integration, not the capability. This is a build, not a wall, and it is a smaller build than it looks, because every surrounding piece is already here:

Piece Already exists
Windows image preparation provisioners/beyondtrust/bt-ready-windows11-vdi.ps1 (multi-session AVD) and bt-ready-windows.ps1 (Server Core), both run as Packer PowerShell provisioners.
Windows post-deploy configuration runners/ansible-winrm is the dashboard's default runner image, carrying pywinrm + the NTLM backend precisely so a WinRM target works out of the box on every runner.
The agent-activation pattern EPM-L already does exactly this — see below.
Windows seats with credential injection Azure, today.

Bake the agent, activate it after

The agent goes into the image, and this is a settled pattern rather than a new idea. EPM-L states the constraint and the resolution in one sentence (integrations/beyondtrust/epml.md):

Because the package is installed at build time but activation can't be — a token expires hours after issue, so one baked into an image is already dead — activation is a post-deploy step.

So: the EPM agent is baked by the Packer provisioner, and a short-lived installation token is minted server-side at execution time, bound to a run variable and scrubbed from the job output. Nothing expired or sensitive is left in the image or the job record. The OT cell's DMZ broker bakes the Entitle agent's chart on the same principle and installs it afterwards with its own job (ot_agent_install_job_id).

That matters here for the reason it mattered to the network cell: the deploy paths have no user-data hook, so anything that must be present at first boot is baked, and anything needing a fresh secret is a post-deploy job. Both halves already have a precedent to copy.

What a Windows EPM integration would need

Roughly the EPM-L integration's shape (services/epml_sync_service.py, api/epml.py, epml_sync job): list and build agent packages, sync them to the asset backend, issue installation tokens. A second product on the same Pathfinder gateway rather than a new subsystem.

What itops can and cannot tell today

An earlier draft of this section called itops the weakest persona shipped and treated "give it cloud cards" as a work item. That was overstated, and the correction matters because the claim was steering what to build next.

Measured rather than asserted — itops ships five cards:

Card Needs Reachable on a cloud-only instance
Least privilege on a Linux endpoint epml_enabled yes — no cloud or hypervisor requirement
Support a user on a virtual desktop, recorded vdesktops_enabled, pra_enabled yes — Virtual Desktops provisions on all three clouds
Rotate a workstation local-admin password password_safe_enabled, vmware_enabled no
Power a workstation on and off without RDP remote_agents_enabled, vmware_enabled no
Remote support with no VPN pra_enabled, vmware_enabled no

So three of five need VMware Workstation — a local desktop hypervisor, and _DEMO_ONLY — and two work on cloud without it. "Three cards need a local VMware install" is the accurate statement. "The persona is mostly unavailable" is not: the two that work are the EPM and the recorded-support stories, which are the two an IT audience came for.

What is genuinely thin is narrower: the Windows endpoint story on cloud. Azure is the only cloud with Windows seats and credential injection, and the VDI card above does not lean on that specifically. One card about an Azure Windows seat with credential injection into a Remote RDP jump item would add something real.

That is one card, not a project, and it does not belong in the ordering below as a peer of the cells. It is worth doing whenever someone is next in personas.py anyway.

5b. The Workload Lab's consumer, and the static secret it removes

Corrected three times. An earlier draft called this "the cheapest remaining item". A second called it blocked — reasoning that every unbuilt tab vaults its credential where a consumer would need another credential to reach it, and concluding that the only way out was the SPIFFE bridge §3 records as unresolved. That conclusion was wrong, and wrong in the direction that stops work happening: it reasoned about Password Safe without checking what Workload Credentials is for.

The third correction is smaller and sharper, and it is recorded here because it is the one that makes the mechanism general rather than clever. Two things the second draft got wrong:

  1. It left vague what Workload Credentials serves, waving at "its own static store, a dynamic credential, or a bt_safe:// reference". The concrete answer is better: WC holds the Password Safe API client id and secret, and the workload uses that pair to call Password Safe.
  2. It treated the identity as an Azure application identity. It is not Azure-only.

The chain that needs no static secret

services/workload_credentials_service states its half in its own docstring: "Two auth modes, and the second one stores nothing." wlc_auth_mode is either pat — a stored token — or workload (spelled entra before §5c), where the platform vouches for the machine and no PAT exists:

  1. the workload asks its platform for its own identity token;
  2. it presents that to Workload Credentials in place of a PAT, with X-BT-Service-Name naming which registered Workload Identity it satisfies;
  3. WC hands back the Password Safe API client id and secret;
  4. the workload signs in to Password Safe with that pair (POST Auth/Connect/Token + SignAppIn, exactly as services/ps_api_service._sign_in does) and requests the credential it actually needs.

Everything the workload is configured with — site id, service name, audience, base URL, the two WC secret names, the account id — is non-secret. Nothing is stored on the host.

Why the fourth step is the point, not an extra hop

Password Safe authenticates an application with a client-credentials pair. That pair is a standing credential and always was, so the question was never whether one exists — it is where it lives. Putting it in Workload Credentials makes WC a bootstrap for the vault rather than a second vault beside it, and that distinction is what makes the shape general: the workload can then reach anything Password Safe governs, not merely what somebody remembered to copy into WC.

And what Password Safe governs, it governs here too. The retrieval is a recorded request with a duration and a reason (POST Requests → GET Credentials/{id} → PUT Requests/{id}/Checkin), it can be made to require approval, and the credential behind it rotates on its own schedule. A PAT in a file has none of those properties and never will.

This is the suite solving a new problem with a combination of old and new. Password Safe (old) holds and governs the secret. Workload Credentials (new) brokers the way in against an identity the platform vouches for. The workload holds nothing. Neither product does this alone, and the seam between them is exactly what a competitor holding one of the two cannot show.

The identity is not Azure-only

Federating a non-human identity against an OIDC issuer is available in all three clouds, and from SPIRE besides. The worker takes --identity-platform:

Platform Where the token comes from Note
azure IMDS, or IDENTITY_ENDPOINT/IDENTITY_HEADER where the runtime injects them the branch the dashboard's own client uses
gcp the metadata server's instance/service-accounts/default/identity returns the token as plain text, not JSON
aws the projected token at AWS_WEB_IDENTITY_TOKEN_FILE (IRSA) or AWS_CONTAINER_AUTHORIZATION_TOKEN_FILE (Pod Identity) EC2's IMDS issues SigV4 credentials and a signed identity document, not an OIDC JWT — plain EC2 needs an issuer, see below
spire a JWT-SVID from the agent already on the host needs no cloud at all, and the Workload Lab already publishes the trust domain as an OIDC issuer (spire_lab_service, OIDC_PORT)
file any other projected token on disk the Kubernetes ServiceAccount token every cluster mounts

The spire row is the one that ties this back to the lab. A bare-metal host has no cloud metadata service to ask, and the lab's OIDC discovery provider is exactly the issuer that answers for it — so the same mechanism covers the estate's clouds and the hardware sitting in a rack, which is the shape a network or OT cell actually has.

auto detects by marker — an injected env var or a projected file — and refuses rather than guessing when the host declares nothing. No marker distinguishes a bare Azure VM from a bare GCE one, and 169.254.169.254 is both their metadata addresses; probing means a request that has to time out to say no, on the host most likely to be neither.

What was built

The worker gained two more token sources and a platform selector.

--token-source What it holds What the vault sees
file (default) a 0600 PAT on disk — a static secret, honestly labelled nothing
wlc nothing nothing — WC serves the PAT from its own store
ps nothing a recorded credential request, with a duration and a check-in

The install play refuses a wlc or ps worker missing any of its non-secret configuration, naming each value — there is no reason to be vague about something that is not a credential — and it removes any token left behind by a previous file-sourced install, because "nothing is stored on this host" must not be contradicted by a file in /etc.

One bug was fixed along the way: the unit's ExecStart was built from backslash continuations with {% if %} blocks between them, and the file branch ended without a trailing backslash — silently truncating the command so --spiffe-socket and --interval never reached the worker. It is one folded line now, with no continuations to get wrong.

The link from the earlier draft stays: an agent is still made answerable for one Workload Lab credential, and that remains a governance record rather than a capability. What changed is that the worker can hold its own credential without one being left on a disk for it.

Still unproven

Three named gaps, none of them a property of the design:

So file stays the default, and every line the worker logs names which mode produced its token.

5c. The dashboard's own client, and the hosting page that settles it

§5b built five identity platforms into the agent worker and recorded, honestly, that the dashboard's own Workload Credentials client still had one. That asymmetry is now closed, and the argument that closed it is worth keeping because it is stronger than the one §5b used.

§5b argued from capability — OIDC federation for non-human identities is available in all three clouds, which is true and is not quite a reason to build anything. The reason is in docs/operations/cloud-hosting.md, which has said all along that this dashboard runs as a managed container on Azure Container Apps, GCP Cloud Run or AWS ECS. Wiring only Azure left two of the three documented hosting options unable to use the mode that stores nothing — not because the mechanism belonged to Azure, but because nothing here asked the other platforms for a token. A feature the install guide offers and the auth path cannot serve is a gap with a date on it, not a preference.

The module's own comment had the reasoning the wrong way round: "Only the Azure one is wired here, because the thing being authenticated is an Azure-hosted container." The container is Azure-hosted in the reference install. The page describing the other two was already written.

What changed

Before After
Mode entra workload (entra still accepted on read)
Platform implicit wlc_identity_platform — azure, gcp, aws, file
Audience wlc_entra_resource wlc_identity_audience (old key read behind it)
Pathfinder registration Azure Entra ID Azure Entra ID, or Custom IDP for the rest

Three details the build turned up:

Still unproven

Azure was run end to end on 2026-09-15 and remains the only platform that has been. GCP and AWS have unit-tested client paths and no live Pathfinder registration behind them. The hosting page and the integration page both say which is which.

5d. The consumer that asks permission

§5b said the Workload Lab's four credentials had no consumer, and that the hub called the candidates — "a pipeline, a broker or a cluster" — hypothetical.

That was not true of the Kubernetes tab, and the tab's own page says so. examples/playbooks/k8s/ci-deploy-with-ps-token.yml and ci-read-with-ps-token.yml ship with it and are "the first Kubernetes plays in this repo that authenticate with something they fetched themselves", with the 403 refusals written as in-play assertions precisely so they cannot be skipped. Reading §5b's framing onto that tab was a third correction in the same direction as the first two: an under-reading of what already existed.

What the agent actually adds

Not a consumer. A consumer that holds nothing in order to retrieve.

The shipped plays The agent cell
Who retrieves an Ansible run, as the dashboard a principal on its own host
What it holds to do so PASSWORD_SAFE_CLIENT_ID + secret, from the run's environment nothing — workload identity → Workload Credentials → the client pair
When operator-triggered on request, and only after a person approves
What it proves the RBAC scope the scope and that the retriever had no standing credential

workload_k8s_service is candid about the axis it loses on: "The vault authenticates whoever can retrieve. Anyone who can retrieve is the workload, as far as this mechanism can tell." The agent narrows that — what reaches the vault is no longer transferable — and does not close it. Saying it closes it would be the one way this demo becomes a lie.

The shape, and why approval-gated

A bounded episode rather than a loop: ask, wait, probe, release. One request/check-in pair in the audit trail with a person's approval in the middle, instead of a stream of retrievals nobody can point at.

The cell's existing closing beat is a revoke — pull the PAT and the worker stops. This one is better, because it happens before anything: an AI agent that asks for access to a production cluster and cannot proceed until somebody says yes.

Three traps, each already written down in this repo

What was built

kubernetes joins cloud in LINKABLE_MECHANISMS, with a new SPENDABLE_MECHANISMS keeping the two kinds of link apart — one confers accountability, the other confers access, and a response that read the same for both would mislead either way. The stale refusal text and the AgentCell comment that said no worker could spend any of them are corrected rather than left as fossils.

Superseded as a description of today. cloud was the accountability side of that split and is not any more — see §5f. The two sets still have separate names, and the reason is in this paragraph: they answer different questions, and the next mechanism will be linkable before it is spendable, which is the order all three arrived in.

The row records the request — id, state, timestamps, result — and never the credential. test_workload_lab_governance's no-credential-on-row rule now covers AgentCell too, rather than relying on that being obvious.

Why certificates is still not linkable

The refusal an operator gets is deliberately short, so the reasoning lives here. It is not that a certificate is unreachable in principle — it is that the bundle is a Secrets Safe file secret, and this worker only knows how to fetch a managed-account password. Three separate things stand between the two, and only the first is common to both certificate packages:

  1. A file secret has no content field to read. ps-cli secrets get projects a per-type field set, and the file one carries FileName and FileHash and nothing else — no Text, no Password. --decrypt cannot help, because it only adds a query parameter and there is no payload field for it to fill. The body comes from a separate call that takes only a GUID, so even the happy path is two round trips. See Password Safe → Troubleshooting.
  2. On the leaf package it is binary — which rules out ps-cli, not the API. bundle defaults to Pkcs12 for Certificate and PemBundle for Subordinate CA. The endpoint returns application/octet-stream and is byte-faithful; every route ps-cli offers is not, because both download-secret-file and raw print response.text. A PEM bundle is ASCII and survives that; a PKCS#12 does not. So the subordinate package needs a path nobody wrote, and the leaf package needs that path to bypass ps-cli and call the endpoint directly. That is a small ask of this worker specifically — it already speaks Password Safe REST for Auth/SignAppIn, Requests and Credentials and holds no static credential doing it, so one more GET is the same shape it is already built around, not a new capability.
  3. It wants BeyondInsight ≥ 26.1 regardless. Below 26.1.0.878, file secrets downloaded through the API came back larger than the original and did not match the web console's copy, so a non-human identity would have retrieved a corrupt bundle no matter how careful the client was. That floor is already a Certificate Lab prerequisite.

None of this is a structural objection of the kind cloud has — a certificate is a credential a workload legitimately spends, and an agent answerable for one is a coherent demo. It is genuinely unbuilt work, which is why the refusal says so rather than implying the tab is off-limits.

Still unproven

No Password Safe tenant, no cluster, no approver. The client paths are unit-tested against a fake gateway and that is all they are. Without an approval policy there is no wait, and the best beat silently does not happen — so the worker logs which path it took.

5e. The third control surface

§5d gave the agent a credential it must ask permission for. This gives it one nobody can take away, and the arc is the reason to build it at all:

Episode Credential How you take it away
the loop its MCP PAT revoke it — the worker stops mid-poll
cluster access a Workload Lab token gated at retrieval; once released it lives out its TTL
certificate use a PKCS#12 identity neither

docs/workload-lab/certificates.md states the third position plainly: "No revocation checking. The plugin consults neither CRLs nor OCSP. Short lifetimes are the mitigation, and that is a deliberate design position." So the closing beat is deliberately uncomfortable — disable the managed account, which revokes the certificate, run the agent again, and it works. It stops when the certificate expires, not when somebody takes it away. A room that has just watched a revoke kill an agent understands immediately why that matters.

The consumer correction, for the third time — and it was a smaller gap than stated

Like Kubernetes, this tab already had consumers: ci-fetch-cert.yml and nginx-mtls-endpoint.yml, with the page calling the step that runs them "the step usually skipped, and the only one that proves anything". They authenticate with a Password Safe client pair supplied to the run; the agent's contribution is the same narrow one it was for Kubernetes.

And my own refusal text overstated the barrier. It said the tab "writes a PKCS#12 into Secrets Safe rather than a managed-account password, and this worker has only the managed-account retrieval path". Half of a certificate identity is a managed-account password — the passphrase — and the worker could always fetch it. The gap was the bundle alone.

The retrieval decision, which took three passes to get right

Worth recording in full, because each wrong turn was confidently argued.

Pass one recommended a Secrets Safe REST client and rejected ps-cli, on the grounds that the binary "would need its own credential configuration, which is the standing secret this cell exists to argue against". False, and one grep settled it: secrets_backend_service._pscli_env maps PSCLI_CLIENT_ID / PSCLI_CLIENT_SECRET from the environment, and they are the same OAuth2 pair the worker already fetches from Workload Credentials. There is no second credential to configure.

Pass two therefore chose ps-cli secrets get -d — the path the repo already runs against a live tenant. Wrong about the secret: a file secret has no content field for get to return. -d cannot rescue it, because the flag only adds decrypt=true and there is nothing in the projection for it to fill.

Pass three moved to ps-cli secrets download-secret-file, the verb that does read a file attachment. Right about the verb and still wrong about the transport, which is the one that would have shipped: every route ps-cli offers decodes the body to text before the caller sees it — the library hands back response.text, and raw falls through its JSON parse to print response.text too. The endpoint itself is byte-faithful; ps-cli is not. A PEM bundle survives that. A .pfx is corrupted rather than refused.

That last one is the dangerous shape. The corruption is silent where it happens and loud three steps later, at the DER check, which reports "this does not look like a PKCS#12" — sending somebody to look for a wrong bundle in Secrets Safe when what is wrong is the transport. services/secrets_backend_service._read_bt_file_secret refuses binary payloads for exactly this reason and names the way out: call the endpoint directly.

So the worker calls GET Secrets-Safe/Secrets/{id}/file/download itself. Two calls, because the download takes an id and an operator knows a reference — resolve, then fetch.

And the costs the ps-cli route carried are simply gone, which is worth stating because the previous draft of this note argued both of them away at length:

What replaces them is smaller and better: Secrets Safe is part of Password Safe, so the session opened for the passphrase reaches the bundle unchanged. One sign-in, both halves of one identity.

The one place something touches disk

The bundle is a file secret, not text. An earlier draft had it coming back as a string and being held in memory, on the grounds that keeping a credential off disk was worth something. That was wrong about the secret, and it was also the weaker design: openssl needs a file to open a PKCS#12 and Python's ssl needs file paths for a client certificate, so a blob in memory would have been written out three lines later anyway.

So there is no version of this that stays off the filesystem, and the episode bounds it instead of pretending otherwise: one 0700 temporary directory, opened before anything is fetched, holding the bundle, the certificate and the key; 0600 on the key and on the bundle; the passphrase through PFXPASS rather than argv, mirroring ci-fetch-cert.yml; and the whole directory removed on the failure path too. The probe takes that directory as an argument rather than making its own, which is what makes "one guarded place" a fact rather than a manner of speaking. It is the one unavoidable exception to "nothing is stored on this host", and the code says so where somebody would otherwise find it and conclude the cell is careless about the thing it argues for.

The human has to be real, not assumed

The passphrase goes through §5d's approval-gated request, so a person decides before the agent gets an identity. But whether Password Safe actually asks anybody is the access policy's decision, not this code's — and on an auto-releasing policy the episode would fetch, probe and print a success line indistinguishable from the approved one. The operator would conclude a gate was in force; the audit trail would show a request nobody was asked about.

That is the one way this demo can mislead, so the worker refuses it: released on the first ask means no person was consulted, and the episode stops with exit code 5 naming the policy to change. It cannot create the gate — that is BeyondInsight's — but it can decline to pretend there was one. --no-require-approval opts out, deliberately loudly.

The same check now covers the cluster episode. §5d's page claims the agent "cannot authorise its own access", and on an auto-releasing policy that claim was equally untrue there — so this is a correctness fix to an existing statement rather than a new rule for one episode.

It matters most here, though, and for the reason this whole section is about: a certificate cannot be revoked out from under the agent. With the PAT you can change your mind afterwards; with the cluster token you can wait out a TTL you chose. Here the approval is the last decision anybody makes about that identity until it expires.

Still unproven

No CA, no Password Safe tenant, no mTLS endpoint. The probe is exercised against a generated CA, a real PKCS#12 and a local mutual-TLS server in tests/test_agentcell_cert_episode.py — which is considerably more than the other episodes get, and still not a live run. One shape is genuinely unverified: how Secrets-Safe/Secrets resolves the reference. The worker sends a reference containing / as path (with separator) and one without it as title, mirroring how the beyondtrust.secrets_safe lookup resolves folder/title. That is a reading of a documented endpoint rather than a guess at a flag — the previous draft's unverified value was a ps-cli output flag nobody had run — and the refusal names which of the two lookups came back empty, so a tenant that disagrees says so in one line.

And a floor §5d already records applies here too: below BeyondInsight 26.1.0.878, file secrets downloaded through the API came back larger than the original, so this episode would retrieve a corrupt bundle however careful the client is. It is already a Certificate Lab prerequisite, which is why it is not a new one — but it is the version this episode silently depends on, and the DER check is not a guard against it: a too-large bundle still starts 0x30.

The worker also checks the downloaded bytes are DER before treating them as a PKCS#12, and names PEM specifically when it sees it: PEM is a perfectly good file secret and a perfectly useless one here, and openssl would otherwise complain about the passphrase — sending somebody to debug the wrong half of a two-half identity.

5f. The fourth control surface, and the first one that costs money

§5e's arc gains a row, and it is the one nobody can argue with:

Episode Credential How you take it away
the loop its MCP PAT revoke it — the worker stops mid-poll
cluster access a Workload Lab token gated at retrieval; once released it lives out its TTL
certificate use a PKCS#12 identity neither
a cloud credential a short-lived AWS or Azure lease it expires, and on AWS nothing can shorten that

That last cell is not a gap. STS will not withdraw a credential it has already signed — workload_cloud_service refuses an AWS revoke at the click for exactly that reason — so the episode's closing beat is a real wait for real expiry, and the room watches the clock. Azure can release early, and its own limit is worth naming beside it: the release ends the ability to get another token, not one already issued. Same shape as §5d's "the approval gates retrieval, not use", which is the third time that distinction has mattered.

The consumer correction, for the fourth time — and this one was a misreading

The pattern is becoming the interesting thing about this document. Each of the three previous cells shipped with a refusal explaining why the next mechanism could not be spent, and each refusal turned out to be wrong in a different way: §5b's overstated a barrier that --token-source ps had already removed, §5d's was imprecise about which half of a certificate identity was out of reach.

This one was not wrong at all. It was about something else. The refusal rested on workload_cloud_service's own sentence — the minted credential "is returned to nobody" — and that sentence is still true, because it is about the dashboard. No route here returns a cloud credential, and none was added. It was being read as a statement about the mechanism, and the two are opposite: the worker calling Workload Credentials itself is precisely what keeps the dashboard out of the chain, so the abstinence the sentence describes is what makes the link spendable.

Worth recording as its own failure mode, because it is subtler than a stale claim: a true sentence, filed under the wrong subject, functioned as a refusal for three releases.

What is different about this one

Three things the other three episodes do not have, each of which needed saying in the product rather than only here:

The signer, and why it is hand-rolled

The worker is stdlib-only by rule — it runs on somebody else's VM, and every dependency is something the install play has to put there. The AWS CLI is a ~60 MB install per agent host; the shipped cloud play can use it because it runs on the dashboard's runner image, which is a different machine with a different budget.

The cost of that choice is a real risk and it is designed around rather than waved at: a wrong SigV4 signature fails as HTTP 403, which is exactly what a successful refusal looks like. Three defences, all needed:

  1. the allowed call runs first, so a broken signer ends the run saying the allowed call failed rather than being read as scope;
  2. the deny beat requires AccessDenied specifically — a 403 carrying SignatureDoesNotMatch or ExpiredToken is reported as refused for the wrong reason, a fourth outcome the sibling probe summaries did not need;
  3. the signing-key derivation, the one part with a published AWS test vector, is pinned to it.

Still unproven

No Workload Credentials tenant, no registered Workload Identity, no federation trust — the same three absences §5b records, now covering a metered call rather than a read, which is a sharper thing to have unverified. No generate from this worker has reached a live site, and no request this signer produced has reached AWS.

The registration remains the last manual step and there is no way around it: it is a GUI action in Pathfinder with no customer API, so nothing here can create one on an operator's behalf. That is not new to this episode — --token-source wlc needs the same one — but it is now the gate on a demo rather than on a convenience.

6. What is deliberately not proposed

7. Suggested order

  1. ~~Cloud governance persona~~ — done. Shipped as finops; see §4.
  2. ~~The agent cell~~ — done. Shipped with the aiops persona; see §3.
  3. Windows EPM integration, then the Windows endpoint cell — the last item on this note, and the biggest demo payoff per unit of new thinking. EPM-L is a working template to copy rather than a design to invent, and the image prep, the WinRM runner and the bake-then-activate pattern are all already here.

The agent cell was built before the Windows one despite this list's earlier ordering, and the reason is worth recording: the risk it carried was unproven infrastructure, which a preview flag and an honest "not yet run against live infrastructure" note handle. The Windows item's risk is a missing integration, which nothing but building it removes. Shipping the one whose risk could be bounded first was the cheaper order.

itops cloud cards are not on this list, and an earlier draft was wrong to put them there — see §5. The gap is one card, not a piece of work worth sequencing.

The Workload Lab consumer is not on this list, because it is built — see §5b. Note that the section had to be corrected twice, the second time reversing the first: it was called blocked on the SPIFFE bridge, and it is not. Workload Credentials' entra auth mode removes the static secret without that bridge, which is what the suite is for.

Verification

Nothing here is testable until something is built. What is checkable now, and what any of the above must keep true:

python tests/test_personas.py        # card targets, flags, clouds all resolve
python tests/test_persona_docs.py    # every persona has exactly one doc, titles match
python tests/test_persona_nav.py     # nav pins are real ids, within budget
python tests/test_docs_conventions.py

The orphan-flag measurement in §1 is reproducible:

python - <<'PY'
from web_dashboard.services import personas as P, feature_flags as ff
claimed = {f for p in P.all_personas() for c in p.use_cases
           for f in (*c.requires_flags, *c.requires_any_flag)}
print(sorted({k for k in ff.flags() if k.endswith("_enabled")} - claimed))
PY

Re-run it after adding a persona: the list should shrink by exactly the flags that persona's cards claim, and by nothing else.