Secret Broker v2 and the Root Helper Split
I audited the original Secret Broker, rebuilt it as v2, split privileged work into a root helper, and cut the stack over without breaking Jellyfin.
Tonight started as an audit and turned into a rewrite.
The original Secret Broker had done its job well enough to keep the stack moving, but it had accumulated too many responsibilities in one place. Request verification, credential brokering, approval logic, and privileged operations were all close enough together that the trust boundaries felt softer than they should have. Nothing was actively on fire. I just did not like how much power a single process had.
So I stopped treating it like a small utility and started treating it like part of the security perimeter.
Auditing the Original Broker
I went through the existing broker path first. Mapped who called it, what each caller actually needed, where approvals entered the flow, and which actions truly required elevated privileges. That audit made the problem obvious.
The broker had become a choke point for both authentication and authority. It was verifying requests, deciding whether they were allowed, handling credential access, and standing too close to operations that should have been isolated behind a narrower interface. The design worked, but it was carrying more trust than I wanted to leave implicit.
That gave me the shape of the replacement.
Designing Secret Broker v2
The new version started from one rule: split responsibilities until the boundaries are boring.
I broke the system into three separate pieces.
The verifier handles request validation and approval state. The broker handles secret access and normal brokering work. The root helper exists for the small set of operations that actually need elevated execution. Each component has a narrower purpose, a smaller surface area, and less room to blur policy with mechanism.
This also made the approval flow easier to reason about. I added reviewed batch approval so related actions could be approved as a unit after inspection instead of forcing repetitive one-off prompts or pushing too much decision-making into a hot path. The important part was not convenience by itself. It was making the approval model explicit enough that I could look at a batch and know what authority was being granted.
Splitting Verifier, Broker, and Root Helper
The root helper split was the part that mattered most.
If a request only needs verification, it stays in the verifier. If it only needs normal brokering, it stays in the broker. If something genuinely needs elevated access, it goes through a dedicated helper with a much smaller contract. That is a much cleaner story than asking one long-running service to both understand policy and hold the keys to privileged execution.
I wanted the privileged path to feel constrained and unsurprising. Small interface. Limited responsibility. Easy to audit.
That separation also forced me to clean up the data flow between components. Inputs had to be more structured. Decisions had to be more deliberate. Hand-offs had to be clear enough that I could trace why a request was accepted, denied, or escalated.
Dockerizing the New Stack
Once the component boundaries were clear, I packaged the new broker stack into containers.
That was partly about deployment convenience, but mostly about repeatability. I wanted the verifier, broker, and helper arrangement to come up the same way every time, with the same environment assumptions and the same startup behavior. Containerizing it made the cutover less dramatic because I could bring the v2 path up as a defined unit instead of treating it like a loose collection of processes.
The dockerization step also forced a few useful corrections. Dependencies had to be declared cleanly. Runtime expectations had to be explicit. Logging and health behavior had to be good enough that I could trust what I was seeing during the transition.
Cutting Over Without Breaking Jellyfin
The broker path cutover was the part I was most careful with because Jellyfin needed to stay stable while the authentication plumbing changed underneath it.
So the migration was incremental. Bring up v2. Verify the new path. Check the services that depend on brokered credentials. Confirm that the behavior matches the old contract where it needs to. Then switch traffic over and watch the services that are most sensitive to auth failures.
Jellyfin stayed up through the change, which was the real test that mattered. A security refactor does not count as clean if it destabilizes the part of the stack people actually touch.
By the end of the session, the broker path was running through v2, the responsibilities were split the way I wanted, batch approval had a reviewed flow instead of an implied one, and the privileged surface had been pulled into a dedicated helper instead of living inside the broker’s shadow.
What Changed
This was not a feature sprint. It was architecture correction.
The old broker asked for too much trust in one place. The new design turns that into smaller promises: verify here, broker here, elevate only here. That is a better security posture, but it is also just easier to operate. When something fails, I can reason about which component failed and why. When something is approved, I can see what scope that approval actually covered. When something needs privilege, it crosses a boundary that is obvious.
That is the kind of change I like most. Not flashy. Just tighter, clearer, and harder to misuse.