fix(api): raise the OAuth code length cap so Microsoft sign-in works; guard wrong-cluster deploys; stop scan-containers passing silently - #628
Merged
Conversation
Microsoft/Entra sign-in failed on AWS with a 400 from request validation before
the handler ran:
POST /oauth2/token -> 400: doesn't match schema:
Error at "/code": maximum string length is 512
Entra v2.0 authorization codes routinely exceed 700 characters (the failing
request bodies were 872 bytes) and can pass 2000 with additional scopes/claims.
Google's are ~100, so only Microsoft was affected and the cap looked fine.
Raise maxLength to 8192 rather than removing it, so the input stays bounded, and
document the reason on the property so it is not tightened again.
Also align the two media types, which disagreed: the application/json variant
carried the pattern and the cap, while application/x-www-form-urlencoded
($ref TokenRequest) had NEITHER. Same field, same endpoint, different
validation. Both now carry both.
Verified before/after on a live cluster: an 800-char code went from the
validation 400 to reaching the handler (invalid_grant, correct for a fake code);
9000 is still rejected, so the bound is real.
Versions bumped to 1.6.0 (.version + OpenAPI info.version); api.go regenerated
with oapi-codegen v2.7.1 — the diff is the embedded spec blob plus two doc
comments, no structural change.
--- make dev-* now require an explicit CLUSTER ---
CLUSTER defaulted to docker-desktop, so `make dev-up` run against a k3s-hosted
server rebuilt and rolled a cluster nobody was watching while the real target
stayed stale, and `make dev-status` reported on a different cluster than the one
just deployed. Both happened today. There is now no default: every dev-* target
exits 2 with a copy-pasteable hint. The hint prints the current kube context but
states explicitly that it does NOT select the target, since that ambiguity is
what made the original mistake plausible. Applied to the whole dev-* family, not
just dev-up, because dev-nuke and dev-down make the same mistake unrecoverable.
--- deploy-aws.sh can no longer act on the wrong cluster ---
The script called bare kubectl, resolving against the GLOBAL current-context.
Anything switching context mid-run (a concurrent make dev-up) could retarget the
AWS workload apply at a local cluster, and `aws eks update-kubeconfig` mutated
the operator's kubeconfig as a side effect, silently leaving their shell pointed
at production after every deploy.
Both fixed: the deploy gets its own throwaway KUBECONFIG (so children can only
ever see EKS, and the operator's file is never touched), plus an identity
assertion before each mutating apply. The assertion compares the API SERVER URL
from the AWS API against the resolved kubeconfig, not the context name — names
are arbitrary local labels and prove nothing about which cluster answers.
Verified both directions: aborts against k3s (https://rp2:6443), passes against
the real EKS endpoint. The destroy path is covered too, since it deletes an
ingress.
--- make scan-containers no longer reports success without scanning ---
scan_image() initialized the CVE counts to 0 and only filled them in when grype
exited 0, with every grype call on check=False. So an image grype could not even
resolve — the common case being one never built locally, where it falls through
to Docker Hub and gets UNAUTHORIZED — kept counts at 0, passed both thresholds,
and returned True. `make scan-containers` printed "Found 0 critical and 0 high"
and "All scans completed", exit 0, for four images it had never read.
The JSON scan now runs first and is authoritative: a nonzero exit, unparseable
output, or missing `source` metadata is a hard failure, with the error naming
whether the image is absent locally (and how to build it) or the scanner itself
is broken. SBOM success is claimed only when a non-empty file actually landed,
and stale artifacts are removed first so last build's SBOM cannot look current.
The redundant first SARIF invocation is gone (it printed to nowhere and doubled
the error wall).
Now correctly exits nonzero, scanning tmi-server for real and reporting the
other four as unbuilt.
Refs #627
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01WnrVyySuyJFqcMxB8q3pNc
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Four changes, all traced to the same shape: tooling that reported success while doing the wrong thing, or nothing.
1. Microsoft sign-in (the user-facing bug)
Entra sign-in failed on AWS with a 400 from request validation, before the handler ran:
Entra v2.0 codes routinely exceed 700 chars (the failing bodies were 872 bytes) and can pass 2000 with extra scopes/claims. Google's are ~100, so only Microsoft was affected and the cap looked fine for a year.
maxLength→ 8192 (raised, not removed — the input stays bounded), with the reason documented on the property so it isn't tightened again.The two media types also disagreed with each other:
application/jsonpattern+maxLength: 512pattern+maxLength: 8192application/x-www-form-urlencodedpattern+maxLength: 8192Same field, same endpoint, different validation depending on how you encoded it.
Verified before/after on a live cluster:
invalid_grant✅Versions → 1.6.0 (
.version+ OpenAPIinfo.version).api.goregenerated withoapi-codegen v2.7.1; confirmed the 2859-line diff is the embedded spec blob plus two doc comments, no structural change.2.
make dev-*require an explicitCLUSTERCLUSTERdefaulted todocker-desktop. Both of these happened today:make dev-upagainst a k3s-hosted server rebuilt and rolled a cluster nobody was watching while the real target stayed stale, andmake dev-statusreported on a different cluster than the one just deployed.No default now — exit 2 with a copy-pasteable hint. The hint prints your current kube context but says explicitly that it does not select the target, since that exact ambiguity is what made the mistake plausible.
Applied to the whole
dev-*family, not justdev-up:dev-nukeanddev-downmake the same mistake unrecoverable.3.
deploy-aws.shcan no longer act on the wrong clusterIt called bare
kubectl, resolving against the global current-context. Two harms: a concurrentmake dev-upcould retarget the AWS workload apply at a local cluster, andaws eks update-kubeconfigmutated the operator's kubeconfig as a side effect — silently leaving your shell pointed at production after every deploy.KUBECONFIG(throwaway temp file,--kubeconfigpassed explicitly): children can only ever see EKS, and your kubeconfig is never touched.Verified both directions: aborts against k3s (
https://rp2:6443), passes against the real EKS endpoint. The destroy path is covered too, since it deletes an ingress.4.
make scan-containersno longer reports success without scanningThe worst of the four.
scan_image()initialized the CVE counts to0and only filled them inif returncode == 0, with every grype call oncheck=False. So an image grype couldn't even resolve — typically one never built locally, where it falls through to Docker Hub and getsUNAUTHORIZED— kept the counts at 0, passed both thresholds, and returnedTrue:A security control that reports success without scanning is worse than one that's switched off, because nobody goes looking for it.
The JSON scan now runs first and is authoritative: nonzero exit, unparseable output, or missing
sourcemetadata is a hard failure, and the error says whether the image is absent locally (with the build command) or the scanner itself is broken. SBOM success is claimed only when a non-empty file actually landed, and stale artifacts are deleted first so last build's SBOM can't look current. The redundant first SARIF invocation is gone — it printed nowhere and doubled the error wall.Now exits nonzero, scans
tmi-serverfor real (0 critical / 0 high, 497KB SBOM written), and reports the other four as unbuilt.Gates
make validate-openapi0 errors ·make lint0 issues ·make build-server(Version: 1.6.0) ·make test-unit2447 passed / 0 failed ·ruffclean on the changed file ·bash -ncleanNotes
Also disables the post-commit version hook (an explanatory no-op — it could never fire under PR-only merges) and rewrites
CLAUDE.md's versioning section to say versioning is manual until #627, listing the three values that must be bumped together.Known remaining gap, not addressed here: a grype run against a stale vulnerability DB still reports few findings, which is a weaker version of the same silent pass. During the earlier AWS deploy grype couldn't reach its DB at all. Worth gating on DB age separately.
🤖 Generated with Claude Code
https://claude.ai/code/session_01WnrVyySuyJFqcMxB8q3pNc