blog: republish 2 edited posts, dossier thumbnails 4-6, new post broker-pattern

Content: shipping-this-site + init-gating-gpu-readiness get their edited-workshop bodies (from the
NAS Published/ pass) - site frontmatter (hero/heroAlt) kept, only prose replaced.

Dossiers 004-006 (gpu-as-code, global-infra-modernisation, network-automation-fleet) were all
rendering the same generic pipeline thumbnail - added 3 tailored DossierThumb motifs (gpu die +
passthrough lanes, region globe, switch-fleet grid) via a new diagram: frontmatter field. Also
closed the dead-space gap above the tag row on short-argument cards with a faint on-theme grid
fill (::before, masked fade) instead of a flat void.

New post: broker-pattern ('An agent should never hold the key it's using') - SECURITY SERIES 02,
between secret-zero and workload-least-privilege. Hero de-watermarked from the NAS source (resized
to the calibrated 1600x1073 coordinate space, Jacobi-diffused out the Nano Banana sparkle at its
known center) via scripts/dewatermark-broker-pattern.mjs (reusable for the next 3 drafts). New
diagram/broker-pattern.svg in the house light-card palette. Inline top-of-body hero stripped per
the astro-static-site skill guard (frontmatter hero: is now the only render path).
This commit is contained in:
2026-07-03 21:12:52 +10:00
parent c162f2e4e7
commit 0d05aae721
11 changed files with 259 additions and 34 deletions
Binary file not shown.

After

Width:  |  Height:  |  Size: 237 KiB

+60
View File
@@ -0,0 +1,60 @@
<svg xmlns="http://www.w3.org/2000/svg" width="1180" height="430" viewBox="0 0 1180 430" role="img" aria-label="The broker-pattern flow: an untrusted agent asks a credential broker, which mints a scoped short-lived capability without exposing any credential; the agent hands that capability to a tool broker, which verifies it and performs the action using a credential it holds; any write is staged behind a human approval gate on a phone before it executes; the credential never leaves the trusted brokers.">
<rect width="1180" height="430" fill="#ffffff"></rect>
<!-- title -->
<text x="60" y="46" font-family="ui-sans-serif, system-ui, -apple-system, Segoe UI, Roboto, sans-serif" font-size="23" font-weight="700" fill="#0e1726">The broker pattern: ask, never hold</text>
<text x="61" y="70" font-family="ui-sans-serif, system-ui, -apple-system, Segoe UI, Roboto, sans-serif" font-size="13.5" fill="#64748b">the agent can only ask &#8212; every credential stays behind the brokers, every write waits on a human</text>
<!-- trust boundary: untrusted (left) vs trusted (right) -->
<rect x="36" y="196" width="230" height="164" rx="16" fill="#c026d3" fill-opacity="0.04" stroke="#c026d3" stroke-width="2.2" stroke-dasharray="7 6"></rect>
<text x="54" y="218" font-family="ui-sans-serif, system-ui, -apple-system, Segoe UI, Roboto, sans-serif" font-size="13" font-weight="700" fill="#c026d3">untrusted executor</text>
<rect x="298" y="196" width="846" height="164" rx="16" fill="#0c8fce" fill-opacity="0.04" stroke="#0c8fce" stroke-width="2.2" stroke-dasharray="7 6"></rect>
<text x="316" y="218" font-family="ui-sans-serif, system-ui, -apple-system, Segoe UI, Roboto, sans-serif" font-size="13" font-weight="700" fill="#0c8fce">trusted tier &#8212; holds every credential</text>
<!-- agent box -->
<rect x="56" y="236" width="188" height="100" rx="14" fill="#ffffff" stroke="#c026d3" stroke-width="1.8"></rect>
<text x="150" y="272" text-anchor="middle" font-family="ui-sans-serif, system-ui, -apple-system, Segoe UI, Roboto, sans-serif" font-size="15.5" font-weight="700" fill="#c026d3">agent</text>
<text x="150" y="298" text-anchor="middle" font-family="ui-monospace, 'SF Mono', Menlo, monospace" font-size="10.5" fill="#64748b">sandboxed &#183; assume compromised</text>
<text x="150" y="319" text-anchor="middle" font-family="ui-monospace, 'SF Mono', Menlo, monospace" font-size="10.5" fill="#0e1726">holds a capability, not a key</text>
<!-- credential broker -->
<rect x="320" y="236" width="196" height="100" rx="14" fill="#ffffff" stroke="#0e1726" stroke-width="1.6"></rect>
<text x="418" y="266" text-anchor="middle" font-family="ui-sans-serif, system-ui, -apple-system, Segoe UI, Roboto, sans-serif" font-size="14" font-weight="700" fill="#0e1726">credential broker</text>
<text x="418" y="290" text-anchor="middle" font-family="ui-monospace, 'SF Mono', Menlo, monospace" font-size="10.5" fill="#64748b">verifies the sandbox identity</text>
<text x="418" y="308" text-anchor="middle" font-family="ui-monospace, 'SF Mono', Menlo, monospace" font-size="10.5" fill="#0e1726">mints a scoped, one-time,</text>
<text x="418" y="324" text-anchor="middle" font-family="ui-monospace, 'SF Mono', Menlo, monospace" font-size="10.5" fill="#0e1726">expiring capability</text>
<!-- tool broker -->
<rect x="598" y="236" width="196" height="100" rx="14" fill="#ffffff" stroke="#0e1726" stroke-width="1.6"></rect>
<text x="696" y="266" text-anchor="middle" font-family="ui-sans-serif, system-ui, -apple-system, Segoe UI, Roboto, sans-serif" font-size="14" font-weight="700" fill="#0e1726">tool broker</text>
<text x="696" y="290" text-anchor="middle" font-family="ui-monospace, 'SF Mono', Menlo, monospace" font-size="10.5" fill="#64748b">checks signature &#183; scope &#183; nonce</text>
<text x="696" y="308" text-anchor="middle" font-family="ui-monospace, 'SF Mono', Menlo, monospace" font-size="10.5" fill="#0e1726">runs the tool with a credential</text>
<text x="696" y="324" text-anchor="middle" font-family="ui-monospace, 'SF Mono', Menlo, monospace" font-size="10.5" fill="#0e1726">it holds &#8212; never the agent</text>
<!-- human approval gate -->
<rect x="876" y="236" width="196" height="100" rx="14" fill="#ffffff" stroke="#7c3aed" stroke-width="1.8"></rect>
<text x="974" y="266" text-anchor="middle" font-family="ui-sans-serif, system-ui, -apple-system, Segoe UI, Roboto, sans-serif" font-size="14.5" font-weight="700" fill="#0e1726">human approval</text>
<text x="974" y="290" text-anchor="middle" font-family="ui-monospace, 'SF Mono', Menlo, monospace" font-size="10.5" fill="#64748b">every write is staged, not run</text>
<text x="974" y="308" text-anchor="middle" font-family="ui-monospace, 'SF Mono', Menlo, monospace" font-size="10.5" fill="#0e1726">one-time token &#183; fail-closed</text>
<text x="974" y="324" text-anchor="middle" font-family="ui-monospace, 'SF Mono', Menlo, monospace" font-size="10.5" fill="#0e1726">timeout = denied</text>
<!-- arrows -->
<path d="M244 286 H314" fill="none" stroke="#64748b" stroke-width="2.2" stroke-linecap="round"></path>
<path d="M304 280 L314 286 M304 292 L314 286" fill="none" stroke="#64748b" stroke-width="2.2" stroke-linecap="round"></path>
<path d="M516 286 H592" fill="none" stroke="#64748b" stroke-width="2.2" stroke-linecap="round"></path>
<path d="M582 280 L592 286 M582 292 L592 286" fill="none" stroke="#64748b" stroke-width="2.2" stroke-linecap="round"></path>
<path d="M794 286 H870" fill="none" stroke="#64748b" stroke-width="2.2" stroke-linecap="round"></path>
<path d="M860 280 L870 286 M860 292 L870 286" fill="none" stroke="#64748b" stroke-width="2.2" stroke-linecap="round"></path>
<!-- arrow labels -->
<rect x="253" y="255" width="52" height="19" rx="9.5" fill="#ffffff"></rect>
<text x="279" y="268" text-anchor="middle" font-family="ui-monospace, 'SF Mono', Menlo, monospace" font-size="10.5" fill="#64748b">asks</text>
<rect x="509" y="255" width="90" height="19" rx="9.5" fill="#ffffff"></rect>
<text x="554" y="268" text-anchor="middle" font-family="ui-monospace, 'SF Mono', Menlo, monospace" font-size="10.5" fill="#64748b">capability</text>
<rect x="786" y="255" width="78" height="19" rx="9.5" fill="#ffffff"></rect>
<text x="825" y="268" text-anchor="middle" font-family="ui-monospace, 'SF Mono', Menlo, monospace" font-size="10.5" fill="#64748b">stages write</text>
<!-- footer takeaway -->
<text x="60" y="398" font-family="ui-monospace, 'SF Mono', Menlo, monospace" font-size="11.5" fill="#64748b">The agent can only ask. Every credential, every write-authority, every scope decision lives at the broker &#8212; a compromised agent's worst case is a denied request.</text>
</svg>

After

Width:  |  Height:  |  Size: 6.6 KiB

+52
View File
@@ -37,6 +37,58 @@ const { kind = "generic" } = Astro.props;
<circle cx="310" cy="45" r="12" class="nd nd--m" />
<circle cx="310" cy="135" r="12" class="nd nd--m" />
</>
) : kind === "gpu" ? (
<>
{/* GPU die with passthrough lanes feeding two edge nodes */}
<rect x="150" y="55" width="100" height="70" rx="6" class="nd nd--c" />
<line x1="164" y1="55" x2="164" y2="40" class="ln ln--c" />
<line x1="186" y1="55" x2="186" y2="40" class="ln ln--c" />
<line x1="208" y1="55" x2="208" y2="40" class="ln ln--m" />
<line x1="230" y1="55" x2="230" y2="40" class="ln ln--m" />
<rect x="172" y="72" width="56" height="36" rx="3" fill="none" stroke="rgba(63,186,245,0.4)" stroke-width="1" />
<text x="200" y="94" text-anchor="middle" class="tx">GPU</text>
<line x1="250" y1="70" x2="330" y2="55" class="ln ln--c" />
<line x1="250" y1="110" x2="330" y2="125" class="ln ln--m" />
<rect x="330" y="40" width="30" height="26" rx="4" class="nd nd--dim" />
<rect x="330" y="112" width="30" height="26" rx="4" class="nd nd--m" />
<circle cx="290" cy="62" r="2.5" class="dot dot--c pulse" />
<circle cx="290" cy="117" r="2.5" class="dot dot--m pulse-slow" />
</>
) : kind === "globe" ? (
<>
{/* globe of regions — a modernised multi-region estate */}
<circle cx="200" cy="90" r="52" fill="none" stroke="rgba(63,186,245,0.35)" stroke-width="1" />
<ellipse cx="200" cy="90" rx="52" ry="20" fill="none" stroke="rgba(63,186,245,0.25)" stroke-width="1" />
<ellipse cx="200" cy="90" rx="20" ry="52" fill="none" stroke="rgba(63,186,245,0.25)" stroke-width="1" />
<line x1="148" y1="90" x2="252" y2="90" stroke="rgba(63,186,245,0.25)" stroke-width="1" />
<circle cx="176" cy="62" r="4" class="nd nd--c" />
<circle cx="228" cy="66" r="4" class="nd nd--m" />
<circle cx="182" cy="118" r="4" class="nd nd--dim" />
<circle cx="222" cy="114" r="4" class="nd nd--c" />
<circle cx="200" cy="90" r="3" class="dot dot--c pulse" />
<line x1="176" y1="62" x2="200" y2="90" class="ln ln--c" />
<line x1="228" y1="66" x2="200" y2="90" class="ln ln--m" />
<line x1="182" y1="118" x2="200" y2="90" class="ln ln--c" />
<line x1="222" y1="114" x2="200" y2="90" class="ln ln--m" />
</>
) : kind === "network-fleet" ? (
<>
{/* fleet of switches, single-pane managed */}
<rect x="170" y="30" width="60" height="24" rx="4" class="nd nd--c" />
<text x="200" y="47" text-anchor="middle" class="tx">NCM</text>
<line x1="200" y1="54" x2="200" y2="70" class="ln ln--c" />
<line x1="80" y1="70" x2="320" y2="70" class="ln ln--c" />
<line x1="100" y1="70" x2="100" y2="120" class="ln ln--dim" />
<line x1="160" y1="70" x2="160" y2="120" class="ln ln--dim" />
<line x1="240" y1="70" x2="240" y2="120" class="ln ln--m" />
<line x1="300" y1="70" x2="300" y2="120" class="ln ln--m" />
<rect x="82" y="120" width="36" height="18" rx="3" class="nd nd--dim" />
<rect x="142" y="120" width="36" height="18" rx="3" class="nd nd--dim" />
<rect x="222" y="120" width="36" height="18" rx="3" class="nd nd--m" />
<rect x="282" y="120" width="36" height="18" rx="3" class="nd nd--m" />
<circle cx="100" cy="70" r="2.5" class="dot dot--c pulse" />
<circle cx="300" cy="70" r="2.5" class="dot dot--m pulse-slow" />
</>
) : (
<>
{/* pipeline — commits flowing through gates to a fleet */}
+14
View File
@@ -79,12 +79,26 @@ const dossierNo = String(index).padStart(3, "0");
}
.dossier__body {
position: relative;
padding: 24px;
display: flex;
flex-direction: column;
gap: 12px;
flex: 1;
}
/* a faint on-theme grid fills the idle space above the chips/meta (the
margin-top:auto gap on shorter "argument" text) instead of a flat void */
.dossier__body::before {
content: "";
position: absolute;
inset: 68px 24px 64px;
background-image:
linear-gradient(var(--grid-line) 1px, transparent 1px),
linear-gradient(90deg, var(--grid-line) 1px, transparent 1px);
background-size: 22px 22px;
mask-image: linear-gradient(180deg, black, transparent 85%);
pointer-events: none;
}
.dossier__title {
margin: 0;
font-family: var(--font-display);
+74
View File
@@ -0,0 +1,74 @@
---
title: "An agent should never hold the key it's using"
date: 2026-07-03
summary: "You want an AI agent that can actually do things — call APIs, touch real data. You also don't fully trust it. The resolution isn't a better sandbox; it's making sure the agent never possesses a credential at all. A broker holds the keys, mints short-lived capabilities, and gates every write behind a human. Here's the pattern."
tags: ["security", "ai-agents", "architecture", "zero-trust", "homelab"]
draft: false
hero: "/blog/broker-pattern.webp"
heroAlt: "An untrusted agent reaches through a sealed gate to ask; on the far side, a guardian holds a ring of keys the agent can never touch."
---
The previous post put untrusted code in a hardware-isolated VM, and ended on a caveat: isolation contains an
*escape*, but it does nothing about an agent **misusing a tool it was legitimately given**. If you hand an AI
agent a database credential so it can be useful, a single bad decision — a prompt injection, a confused chain
of reasoning — spends that credential. The sandbox did its job perfectly and you still got robbed, through the
front door you built.
So the real question isn't "how do I isolate the agent?" It's "how does the agent get work done *without ever
holding a key*?"
## The agent holds a capability, not a credential
The pattern is to put every credential, every tool, and every model endpoint **behind a broker**, and give the
agent only a *capability to ask*. The agent never sees a token. It calls the broker; the broker holds the real
credential, decides whether the request is allowed, and — if it is — performs the action itself and returns the
result. The key never leaves the broker.
That one inversion changes the threat model completely. A fully compromised agent can now do exactly one thing:
**ask**. And asking is answered by something it can't reach, can't impersonate, and can't bypass.
<!-- DIAGRAM: untrusted sandbox → cred-broker (mint capability) → mcp-broker (verify + run tool with held cred) → human-approval gate → downstream. Agent never touches the credential. -->
![The two-tier broker flow: capability in, action out, credential never exposed](/diagrams/broker-pattern.svg)
## Two clusters, two brokers
The trust boundary is physical, not just logical. The **untrusted executor** (the Kata sandboxes) lives on one
cluster; the **trusted tier** (the brokers, the model gateway, the real credentials) lives on a *separate*
cluster. A total compromise of the executor still can't reach the brokers' secrets except across a policed
network link — there's no shared kernel, no shared API server, nothing to pivot through.
On the trusted side there are two brokers, deliberately split:
- A **credential broker** validates the sandbox's identity (a short-lived, signed token unique to the task) and
mints a **capability** — a cryptographically signed, scoped, single-use, expiring grant. Not a credential. A
*permission to ask for one specific thing*.
- A **tool broker** takes that capability, verifies the signature, the scope, and the one-time nonce, and only
then runs the requested tool — using a credential *it* holds. The result comes back; the credential doesn't.
## A human gates every write
Reads are one thing. For anything that *changes the world* — creating, deleting, sending — the broker doesn't
just decide on policy. It **stages** the action and pings a human: an Approve/Deny prompt on my phone, carrying
a one-time token bound to the exact task, method, and arguments. Tap approve and it executes; tap deny, or
ignore it, and it doesn't. The gate is **fail-closed**: a timeout is a denial, a replayed token is rejected, an
unknown method is rejected. The default, always, is *no*.
The load-bearing principle underneath all of it: **enforcement that has to survive a compromised agent lives at
the broker, never at the agent or the orchestrator.** Approval, scope, budgets, write-authority — none of it
lives anywhere the agent's reasoning can touch. The agent can be wrong, jailbroken, or outright hostile, and the
worst-case is still just *a request that gets refused*.
## Useful and safe at the same time
It's tempting to think you have to choose: give the agent real power and accept real risk, or lock it down so
hard it can't do anything. The broker pattern is how you get both. The agent is *useful* — it can call real
tools against real systems. It's *safe* — it never holds a key, every write waits on a human, and the moment
something goes wrong, the blast radius is a denied request, not a spent credential.
Run agents like you'd run any other untrusted input: assume it's compromised, and make sure that assumption is
*boring*.
*Live as the trusted tier of a two-cluster AI-agent platform: a credential broker and a tool broker holding the
keys, a phone-based human-approval gate on every write, and an agent that — by construction — never sees a
secret.*
+30 -17
View File
@@ -8,18 +8,20 @@ heroAlt: "A GPU glows behind a sealed checkpoint gate while a waiting pod-orb is
---
The most common way a GPU workload fails at the edge isn't the model, the driver, or the
network. It's timing. Kubernetes is eager — it will happily schedule your inference pod the
moment a node is `Ready`, which is often *before* the NVIDIA device plugin has advertised
`nvidia.com/gpu`. The pod starts, can't see a GPU, crash-loops, and now your rollout is
poisoned across the fleet.
The most common way a GPU workload fails at the edge isn't the model, the driver, or the network.
It's timing. Kubernetes is eager — it will happily schedule your inference pod the moment a node
reports `Ready`, which is often *before* the NVIDIA device plugin has advertised `nvidia.com/gpu`.
The pod starts, can't see a GPU, crash-loops and now your rollout is poisoned across the fleet,
on boxes nobody is standing next to.
The fix is to make readiness explicit. Don't trust node-`Ready`; gate on the GPU.
Node-`Ready` answers the wrong question. It says the kubelet is up. It says nothing about whether
the one piece of hardware your workload exists to use is actually there yet. So stop trusting it:
make GPU readiness explicit, and gate on that.
## Gate the schedule, not just the start
## Gate the schedule, then gate the start
A resource request is the first line — a pod that *requests* a GPU won't schedule until the
plugin advertises capacity:
The first gate is free — a resource request. A pod that *requests* a GPU won't schedule until the
device plugin advertises capacity:
```yaml
resources:
@@ -27,9 +29,10 @@ resources:
nvidia.com/gpu: 1
```
But on a single-GPU edge node that's recovering from a reboot, you still want a hard check
before the workload does anything expensive. An init container that blocks until the device
is real keeps the main container honest:
That handles the common case. But on a single-GPU edge node recovering from a power cut, there's a
window where the plugin has advertised the device and the driver is still finding its feet — and
you don't want an expensive model load to be the thing that discovers it. So the second gate is an
init container that blocks until the device is demonstrably real, and fails loudly if it never is:
```bash
#!/usr/bin/env bash
@@ -45,11 +48,21 @@ echo "GPU never became ready" >&2
exit 1
```
Two gates, two failure modes closed: the scheduler can't place the pod before capacity exists, and
the workload can't start before the hardware answers. Note the bound — an init gate that waits
forever isn't a gate, it's a hang. Two and a half minutes, then fail loud and let the platform
retry. Fail-closed, never fail-quiet.
## Why this is the win
Once readiness is gated, the whole class of "pod started before the GPU" failures disappears
and it disappears *the same way on every node*. That consistency is the real prize at the
edge, where no one is standing next to the box to nurse a bad rollout.
Once readiness is gated, the entire class of "pod started before the GPU" failures disappears
and it disappears *the same way on every node*. That consistency is the real prize at the edge.
A fix that requires a human to notice, shell in, and nurse a bad rollout doesn't scale past the
first dozen sites; a gate that makes every node converge identically after every reboot does.
The principle generalises: at the edge, **design the dependency, don't hope for it**. The GPU
is just the first dependency worth gating; egress paths and model artifacts are next.
## The principle
At the edge, **design the dependency — don't hope for it**. Anything your workload cannot run
without deserves an explicit, bounded, fail-loud gate between it and the scheduler's optimism.
The GPU is just the first dependency worth naming; egress paths and model artifacts are next,
and they want the same treatment.
+25 -16
View File
@@ -8,25 +8,28 @@ heroAlt: "A glowing data container travels a luminous rail from a small server r
---
This site is a static Astro build, but how it gets to you is the interesting part. It's
served from my homelab Kubernetes cluster over a Cloudflare Tunnel, deployed the same way I'd
ship anything else: as an immutable image, pinned by digest, reconciled by GitOps.
This site is a static Astro build, and that's the least interesting thing about it. What matters
is how it reaches you: served from my homelab Kubernetes cluster over a Cloudflare Tunnel, shipped
the way I'd ship anything I actually cared about — an immutable image, pinned by digest, reconciled
by GitOps. No special case for "it's just a website."
## The pipeline
1. The site is built and baked into a hardened `nginx-unprivileged` image.
2. The image is pushed to a **self-hosted public Gitea registry** — deliberately separate
from the private instance that holds my infrastructure code.
3. The image digest is pinned in a private `home-ops` repo.
4. **ArgoCD** reconciles that repo onto the cluster.
5. A **Cloudflare Tunnel** exposes exactly one service — this site — outbound-only.
The build is baked into a hardened `nginx-unprivileged` image and pushed to a **self-hosted public
Gitea registry** — deliberately a separate instance from the private one holding my infrastructure
code, so the public artifact and the private estate never share a trust boundary. The image digest
is then pinned in a private `home-ops` repo, **ArgoCD** reconciles that repo onto the cluster, and
a **Cloudflare Tunnel** exposes exactly one service — this site — outbound-only.
No open ports. No server runtime. No registry credential on the cluster, because the public
package is anonymous-pull and the image holds nothing secret.
Follow the chain and notice what's missing. No open ports: the tunnel dials out. No server runtime:
the output is static files behind nginx. No registry credential on the cluster: the public package
is anonymous-pull, and the image holds nothing secret to protect. Every link in the pipeline is
either immutable, declarative, or absent.
## Security as acceptance criteria
The interesting constraint was treating security as a checklist to *pass*, not a vibe:
The discipline that made it work was treating security as a checklist to *pass*, not a vibe to
gesture at. The site didn't ship until every box was ticked:
```text
[x] Static output — no server runtime to attack
@@ -36,8 +39,14 @@ The interesting constraint was treating security as a checklist to *pass*, not a
[x] Outbound-only tunnel, single hostname, no catch-all
```
## Why bother
A checklist sounds bureaucratic until you notice what it changes: each item is a claim you can
verify, and a failing item blocks the ship. "Pretty secure" isn't a state you can test for.
`grep` finding zero secrets in the bundle is.
Because the site *is* the argument. A platform engineer's portfolio should demonstrate the
discipline it's advertising — and "it's a static page" is no excuse to skip the rigour. The
deployment story is part of the work.
## The principle
The site *is* the argument. A platform engineer's portfolio should demonstrate the discipline it
advertises, and "it's a static page" is no excuse to skip the rigour — it's the cheapest possible
place to practise it. If the pipeline behind a brochure site is immutable, verified, and
zero-trust, that's not overkill. That's the standard, rehearsed where the stakes are low so it
holds where they aren't.
@@ -7,6 +7,7 @@ period: "2019 2025"
stack: ["VMware / vSphere", "Azure (Blob, AVS)", "Microsoft 365", "SD-WAN", "Aruba ClearPass", "Palo Alto / FortiGate", "Veeam"]
featured: false
order: 40
diagram: "globe"
---
## Problem
+1
View File
@@ -7,6 +7,7 @@ period: "2025 Present"
stack: ["GPU passthrough", "ESXi", "DCGM Exporter", "Prometheus", "Bash", "Watchdogs"]
featured: false
order: 30
diagram: "gpu"
---
## Problem
@@ -7,6 +7,7 @@ period: "2019 2022"
stack: ["Unimus (NCM)", "NetBox (IPAM / SoT)", "Config backup & DR", "Bulk config push", "Credential vaulting + rotation", "Multi-vendor switching"]
featured: false
order: 45
diagram: "network-fleet"
links:
- label: "Unimus"
href: "https://unimus.net"
+1 -1
View File
@@ -2,6 +2,6 @@
// The security series threads the zero-trust arc across posts as they publish.
export const series: Record<string, { name: string; number: number }> = {
"secret-zero": { name: "SECURITY SERIES", number: 1 },
"broker-pattern": { name: "SECURITY SERIES", number: 2 },
"workload-least-privilege": { name: "SECURITY SERIES", number: 3 },
// broker-pattern → 02 when it publishes (currently a draft in the workshop)
};