Skip to content

fix: NANNY_SESSION_TOKEN is an environment variable, and only that - #126

Merged
evansibok merged 2 commits into
mainfrom
ev/session-token-is-an-env-var
Sep 2, 2026
Merged

fix: NANNY_SESSION_TOKEN is an environment variable, and only that#126
evansibok merged 2 commits into
mainfrom
ev/session-token-is-an-env-var

Conversation

@evansibok

Copy link
Copy Markdown
Contributor

Withdraws a feature shipped in 0.6.1 that could not work. Releases as 0.6.2.

What broke

0.6.1 let the governor read NANNY_SESSION_TOKEN as a path to a file. Both SDKs kept reading the variable itself:

fn session_token() -> String {
    std::env::var("NANNY_SESSION_TOKEN").unwrap_or_default()   // Rust
}
def _token() -> str:
    return os.environ.get("NANNY_SESSION_TOKEN", "")           # Python

So a deployment following 0.6.1's own guides set a path on both sides, the governor compared the file's contents against the literal path the joiner sent, and every governed call was refused. The worked two-container example in guides/governance-server.mdx described a configuration that cannot work.

Nothing caught it because the change was only ever tested from the governor's side.

Why withdrawn rather than finished on the other two sides

The token is a single opaque string that both ends read from the same variable. A second form for either end to interpret is only a way for them to disagree about what the value is, and this is the disagreement.

Certificates take a path for reasons a token does not share: they are multi-line PEM, and the governor watches their directory so a rotation reloads without a restart. Neither applies to 64 characters of hex.

It was also generalised rather than needed. An environment variable crosses hosts perfectly well, which is what a multi-host deployment actually required. The arguments for a path were /proc/<pid>/environ exposure, which is a posture preference rather than a blocker, and rotation, which was already solved by the other half of the 0.6.1 change.

Rotation is unaffected

That was always the part that made rotation possible, and it stays. The governor accepts a set, newline-separated in the variable, so a rotation sets both tokens, moves the joiners, then drops the old one, and nothing has to change at the same instant. 0.6.1 described the set and the path as inseparable, "ship together or not at all". They never were.

NANNY_SESSION_TOKEN=0123456789abcdef0123456789abcdef
fedcba9876543210fedcba9876543210

Also removed

resolve_secret and its Windows path handling, unused now, and the dirs dependency they required.

Docs

guides/governance-server.mdx no longer shows a path for the token: the .env example, the two-container example and the rotation procedure all use the value. Certificates continue to take a path or inline PEM, which is unchanged and correct.

Verification

cargo test --workspace: 16 suites, 0 failures. cargo clippy --workspace --all-targets: clean. pytest: 148 passed. mypy --strict: clean. ruff check: clean.

The four path-form tests are deleted rather than adapted. Three token tests move from a file to the variable and keep asserting what matters: several tokens are accepted, one weak entry in the set is still refused, and a whitespace-only value still means mint one.

0.6.1's changelog entry is corrected in place rather than left claiming a form that is being withdrawn one release later.

Version bumped to 0.6.2 across the workspace, the five internal crate pins and the Python SDK. Not tagged — that is a maintainer action.

0.6.1 let the governor read the token as a path to a file. Both SDKs kept
reading the variable itself, and nothing checked that the two ends agreed.
So a deployment following 0.6.1's own guides set a path on each side, the
governor compared the file's contents against the literal path the joiner
sent, and every governed call was refused. The guides described a
configuration that cannot work.

Withdrawn rather than completed on the other two sides. The token is one
opaque string that both ends read from the same variable, and a second
form for either end to interpret is only a way for them to disagree about
what the value is. Certificates take a path for reasons a token does not
share: they are multi-line PEM, and the governor watches their directory
to reload them without restarting.

It was also generalised from those certificates rather than needed. An
environment variable crosses hosts perfectly well, which is what the
deployment actually required; the argument for a path was /proc exposure
and rotation, and the second of those was already solved by the other
half of the change.

Rotation is unaffected. The governor still accepts a set, newline
separated in the variable, so a rotation sets both tokens, moves the
joiners, then drops the old one, and nothing has to change at the same
instant. That was always the part that made rotation possible; the path
was bolted onto it and described as inseparable, which it never was.

`resolve_secret` and its Windows path handling go with it, unused now,
along with the `dirs` dependency they needed.
@evansibok
evansibok merged commit 3dc08db into main Sep 2, 2026
10 checks passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant