fix(auth-guard): lazy provider + enforce secret minimum length at startup#439
Conversation
The generated app registers the auth provider from its init() hook, before the env-contract validation runs. The hook called authguard.MustAuthProvider(), which constructed Sessions eagerly and panicked when GOWDK_AUTH_SESSION_SECRET was unset or shorter than the required 32 bytes — so a secret misconfiguration crashed the binary with a stack trace at startup instead of returning the generated env-contract error. Replace MustAuthProvider with a lazy AuthProvider that resolves Sessions inside Principal on the first request, and point the generated hook at it. A missing or short secret now surfaces as the clean "GOWDK_AUTH_SESSION_SECRET is required" startup error (verified by running the built binary with the secret unset).
There was a problem hiding this comment.
💡 Codex Review
Here are some automated review suggestions for this pull request.
Reviewed commit: 78a279179c
ℹ️ About Codex in GitHub
Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you
- Open a pull request for review
- Mark a draft as ready
- Comment "@codex review".
If Codex has suggestions, it will comment; otherwise it will react with 👍.
Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".
| // the clean startup error. Deferring construction to Principal keeps secret | ||
| // misconfiguration on the normal validation path. | ||
| func AuthProvider() gowdkauth.Provider { | ||
| return gowdkauth.ProviderFunc(func(request *http.Request) (*gowdkauth.Principal, error) { |
There was a problem hiding this comment.
Validate short session secrets before serving
When GOWDK_AUTH_SESSION_SECRET is set but shorter than the 32-byte minimum, this lazy provider means Sessions() is no longer constructed during generated-app initialization. I checked the generated env contract in internal/appgen/source_env.go, and it only rejects empty required secrets, so a non-empty short secret now lets the binary start and only fails the first login/guarded request with a session error instead of the documented startup failure; keep a post-env startup validation for Sessions() or teach the env contract about the minimum length before deferring construction.
Useful? React with 👍 / 👎.
Follow-up to the lazy auth provider: deferring Sessions() construction meant a present-but-too-short GOWDK_AUTH_SESSION_SECRET no longer failed at init, and the env contract only checked for empty secrets — so a weak signing key let the binary start and only failed the first guarded request. Add SecretEnv.MinBytes and enforce it in both the build-time env validation (gowdk.EnvConfig.Validate) and the generated runtime contract (internal/appgen validateEnvContract), parse it from executable and AST configs (internal/project), register the new short_secret diagnostic, and set MinBytes: 32 on the auth-guard example's session and CSRF secrets. A short secret now fails fast with "GOWDK_AUTH_SESSION_SECRET must be at least 32 bytes" at startup (verified by running the built binary). Tests added for both the build-time validation and the generated contract.
CodeQL flagged the unchecked int64->int conversion when parsing SecretEnv.MinBytes from the AST config path. Route it through a new parseInt helper that clamps out-of-range values to the platform int range so a malformed config value cannot wrap into a small or negative length on 32-bit platforms. Add a LoadConfigFile regression test that enforces MinBytes end-to-end through the AST path.
Summary
Fixes the init-time crash in
examples/auth-guardand the follow-up regression it exposed: a misconfigured session secret should fail at startup with a clean error, never an init-time panic and never a deferred first-request failure.Commit 1 — defer session setup out of the init hook
The generated app registers the auth provider from its
init()hook, before env-contract validation runs. The hook calledauthguard.MustAuthProvider(), which builtSessionseagerly and panicked whenGOWDK_AUTH_SESSION_SECRETwas unset/short — crashing with a stack trace instead of the clean startup error.MustAuthProvider()with a lazyAuthProvider()returning agowdkauth.ProviderFuncthat resolvesSessions()insidePrincipalon first use.authguard.AuthProvider().Commit 2 — enforce
SecretEnvminimum length (build + startup)Deferring construction meant a present-but-too-short secret no longer failed at init, and the env contract only rejected empty secrets — so a weak key let the binary start and only failed the first guarded request (flagged in review).
SecretEnv.MinBytes; enforce it in build-time validation (gowdk.EnvConfig.Validate) and the generated runtime contract (internal/appgenvalidateEnvContract).MinBytesfrom both executable and AST configs (internal/project); register the newshort_secretdiagnostic.MinBytes: 32on the auth-guard example's session and CSRF secrets.Verification
go test ./...— all packages pass; new tests ininternal/publicapi(build-timeValidate) andinternal/appgen(generated contract).make build+ run the built binary:GOWDK_AUTH_SESSION_SECRET is required but is not set, exits.GOWDK_AUTH_SESSION_SECRET must be at least 32 bytes, exits (1).