Remove the eight explicit curve overrides so Go 1.27 honors tlsmlkem=0 across Server listeners, node links and outbound transports. Remove the unused shared curve option and add wire-level regression coverage. Document CA trust and TLS upgrade behavior, retain the investigation artifacts, and exclude their synthetic routes from the rebrand guard. The product compatibility baseline remains unchanged. Validation: focused race tests, HTTP tests, lint, compatibility guard positive/negative controls, and a fresh Linux build with three isolated OIDC integration scenarios all pass. Adversarial review: Claude Code Fable 5.1, max effort. Final verdict: APPROVE FOR COMMIT. Signed-off-by: Feng Ruohang <rh@vonng.com>
12 KiB
SILO stack: Go 1.27 compatibility audit
2026-09-09. Scope: the maintained Server, silo-pkg, mcli, and Console. This extends the OIDC #154 investigation to other paths using the same TLS configuration and to adjacent standard-library changes. It records local validation performed before commit. No pushes, issue comments, releases, or production changes were made during the audit.
Findings and changes
| Component | Finding | Change |
|---|---|---|
Server, baseline d1105bbb3d4a0afa33b3a4ac11b821235038ed0e |
Eight TLS configuration sites explicitly use the same ML-KEM-containing curve list, overriding tlsmlkem=0 in Go 1.27. |
Remove the eight assignments and obsolete TLSCurveIDs helper. Retain Go defaults in HTTP clients, replication, cloud client-certificate transport, both grid links, etcd, and the S3 listener. Add wire-level regression tests. |
silo-pkg, baseline a92c54d |
No built-in explicit PQ curve list. Web environment transport uses defaults; LDAP/OIDC accept caller configuration. | Add a real web-environment TLS regression test and document runtime-default selection. Keep the Go 1.26 library floor. |
mcli, baseline fcd5cad8 |
S3/Admin transport and alias/TOFU dialer use default curves. No instance of the Server's override bug found. | Add real S3/Admin transport and alias-dialer handshake tests; update Go/TLS upgrade notes. |
Console, baseline c103d08ec |
IdP, SILO/STS, Prometheus, and webhook clients share a transport using defaults. The HTTPS listener explicitly uses only P-256. | Add real IdP/SILO-client handshake tests; update Go/TLS upgrade notes. Retain the existing listener policy. |
The Server's general external HTTP transport is also used by OpenID discovery and JWKS, identity plugins, notification/lambda checks, audit/log webhooks, and S3 cloud backends. Fixing only the two OIDC callers would leave those other paths affected. The broader correction supersedes the earlier OIDC-only candidate.
The retained defaults follow Go's implementation rather than duplicating its
GODEBUG parser. Go 1.27 explicitly changed the interaction between manually
selected curves and the tlsmlkem/tlssecpmlkem default controls. See the
Go TLS release notes.
With no opt-out, default curves additionally include SecP256r1MLKEM768 and
SecP384r1MLKEM1024. With GODEBUG=tlsmlkem=0, hybrid exchanges are disabled for
default-configured TLS throughout the process. This is an intentional change
from the old fixed subset. GODEBUG=tlssecpmlkem=0 disables just the SecP
hybrids while retaining X25519MLKEM768. TLS versions, cipher-suite policy,
certificate and hostname checks, client certificates, proxies, and HTTP/2 choices are not
relaxed by this patch. No automatic fallback after a TLS error is introduced.
Reproduction and regression evidence
Before changing product code, the new Server test failed for all five tested
outbound constructors with tlsmlkem=0: general external HTTP, internode HTTP,
replication, cloud client certificates, and etcd. The server observed
[X25519MLKEM768 X25519 P256 P384 P521] in every case. Both TLS 1.2 and TLS 1.3
peers reproduced the problem. The inbound Server also accepted a PQ-only client
despite the same opt-out.
After the change, those tests pass on darwin/arm64 and linux/arm64. They exercise 20 outbound combinations (five constructors, two peer TLS versions, two debug settings), plus inbound classical/PQ-only peers with the opt-out enabled and disabled. The inbound opt-out case rejects a PQ-only peer while continuing to accept P-256. The etcd test exercises its actual TLS configuration, not an etcd cluster; the client-certificate test exercises construction/loading, not a full mutual-authentication service.
The mc, Console, and silo-pkg handshake tests pass without product code changes. All use a trusted synthetic certificate and inspect a real ClientHello. They check both disabling and retaining ML-KEM. Console also retains its existing unknown-CA, hostname, and endpoint-scoping regression checks.
A freshly source-built complete Linux Server then passed three isolated integration scenarios using the existing synthetic IdP fixture:
| Scenario | Result |
|---|---|
ML-KEM-intolerant IdP + tlsmlkem=0 |
Discovery/JWKS, IAM, Console, and authenticated Admin calls succeed; login 204 and bucket list 200. |
| Normal TLS 1.3 IdP, no opt-out | Same successful login, token exchange, STS, session, and bucket-list chain. |
| Add OIDC to a running Server | Actual Admin API accepts the provider under the compatibility setting. |
Both login scenarios reject modified JWT signatures and a wrong audience: no
session cookie is issued and authenticated bucket access returns 403. Login
currently reports these authentication failures as 500; that existing error
mapping is outside this TLS change. Curl in the same namespace returns HTTP/2
200. The containers use a pre-existing generic Debian image, --network none,
and no published ports; no Server or Console image was downloaded or used.
The fixture drives real Console HTTP APIs, not rendered browser interaction. This is a conditional interoperability reproduction, not proof of the actual customer ingress behavior. Grid's existing tests pass, but a production-style distributed TLS cluster, external cloud providers, and real etcd/LDAP/Keycloak deployments were not exercised. Disabling ML-KEM does not disable new ML-DSA signature offers and does not repair an ingress that rejects those offers.
Other Go changes checked
macOS root certificates: a confirmed upgrade-visible change
Using the same public synthetic CA and certs.GetRootCAs, fresh-process probes
produce the following results when SSL_CERT_FILE points at that CA and
SSL_CERT_DIR points at an empty directory:
| Compiler / main module | CA from environment trusted? | Explicit CA argument trusted? |
|---|---|---|
Go 1.26.5 / go 1.26.0 |
No | Yes |
Go 1.27.1 / go 1.26.0 |
No | Yes |
Go 1.27.1 / go 1.27.1 |
Yes | Yes |
The Go 1.26 module built by Go 1.27 carries
DefaultGODEBUG=...x509sslcertoverrideplatform=0; explicitly setting that option
to 1 enables the new behavior. A Go 1.27 application can explicitly set it to
0 to recover the previous platform behavior. The diagnostic source is
cert-roots.go. The consuming application's defaults
apply to library calls as well, so silo-pkg's older go directive does not
prevent the behavior in Server, mc, or Console.
This is expected standard-library behavior, not a reason to silently discard
configured CA variables or skip verification. Setting either variable replaces
Keychain trust with on-disk roots and Go's verifier; stale or incomplete paths
can break previously trusted connections. Unset inherited variables to restore
Keychain trust; explicit additional CAs still work. The three application
READMEs and the package README now document this. The package's Windows loader enumerates
the native ROOT store directly and does not call SystemCertPool; it does not
inherit this particular new setting. Windows behavior was reviewed in source,
not runtime-tested.
JSON, HTTP, timers, and other compatibility controls
- JSON: checked owned JSON error handling and exercised policy/condition,
config, and authentication tests.
quickuses typedSyntaxErrorandUnmarshalTypeError; no owned decision depending on changed standard JSON error text was found. silo-pkg's full suite passes with both Go compilers; mc's command and Console's API/auth suites pass under Go 1.27. No broadnojsonv2opt-out or serialization rewrite is justified by these results. - HTTP response closing: Go 1.27's standard-library drain is bounded at 256 KiB and 50 ms. mc's actual early-close/cancel regression passes, including the compressed S3 Select stream. Existing owned drain helpers can still have independent timeout concerns; those are not newly caused by this Go change.
- ALPN and custom connections: mc's TLS dialer returns a real
*tls.Conn; its deadline wrapper is on the TCP dial path. No new accidental HTTP/2 opt-in from the expandedConnectionStateinterface support was identified in these transports. Console keeps its existing transport policy. - Removed switches: no maintained runtime/config reliance on
tlsrsakex,tls3des,tls10server,x509keypairleaf, orasynctimerchanwas found. Existing explicit cipher policies continue to be explicit. Timer uses in package certificate reload and license refresh do not depend on buffered timer-channel length/capacity. Unix EOF error changes do not expose a matching owned error-type assumption on the reviewed Unix-socket paths. - Platform floor: application READMEs now identify macOS 13 as the minimum for Go 1.27 binaries. No Windows, old macOS, or PowerPC runtime claim is made.
Validation and delivery
- Server: focused TLS tests on macOS and Linux; macOS race run; crypto, HTTP,
grid, and all
internal/config/...tests; full Linux Server build and the three integration scenarios above. - silo-pkg:
make test(lint and full race suite) on Go 1.27.1; full suite on Go 1.26.5, preserving the library floor. - mc: complete
./cmdsuite, including the new TLS test and existing S3 Select early-cancel test. - Console: complete
./api/... ./pkg/...suites, including identity and STS validation and the new handshake tests.
Go lint reports zero issues in all four repositories. Server's optional spelling
check is skipped because typos is not installed. Its lint target used the
already-installed matching golangci-lint v2.13.1 after a redundant download was
stopped; mc's lint was rerun serially after the global linter lock prevented the
first concurrent attempt. These tooling retries did not require source changes.
An independent Fable 5.1 Max review reproduced the old-code failures and ran the
new TLS tests with the race detector. Its shuffled mc and Console suites passed.
One shuffled silo-pkg run failed the untouched certs.TestValidPairAfterWrite;
that test passed three plain reruns, the same shuffle seed, and the full certs
package rerun. The certs package runs in a separate test binary from the changed
env tests; this was classified as an existing timing flake.
The review also found that committing the diagnostic fixture made rebrand CI
count synthetic IdP paths as product routes. The guard now excludes
docs/investigations/; the product compatibility baseline is unchanged.
The full proposed file set passes the guard, and a negative control adding a
product route still fails. After the review corrections, a fresh Linux Server
build passed all three integration scenarios again; the new binary identity and
rerun results are recorded in the evidence file's post_review_validation.
All source and dependency choices remain those of the maintained PGSTY stack.
go.mod and go.sum are unchanged in every repository. The Server uses its
existing pinned Console/pkg/mc modules; the companion changes are tests and
documentation, so no replacement graph or unpublished dependency version is
needed to build the runtime fix.
Validation used separate worktrees for Server, silo-pkg, mc, and Console, leaving
the original repositories and their main branches untouched. Build identities,
fixture results, and command outcomes are recorded in
go127-stack-evidence.json. Absolute paths and branch
names in the captured evidence identify the environment at recording time.
The recorded module graph identifies the dependencies selected for those
builds; go.sum also retains checksums for unselected versions. Local image IDs
and temporary paths are historical evidence, not portable setup instructions.