Skip to content

thv run ignores --oidc-insecure-allow-http and --thv-ca-bundle — both dropped in createOIDCConfig (same as #1470) #6522

Description

@mapyr

Summary

On CLI thv run, two OIDC flags are accepted but do nothing:

  • --oidc-insecure-allow-http ("Allow HTTP (non-HTTPS) OIDC issuers for local
    development/testing")
  • --thv-ca-bundle ("Path to CA certificate bundle for ToolHive HTTP operations
    (JWKS, OIDC discovery, etc.)")

So there is no working way to point thv run at a local OIDC issuer — plain HTTP,
or HTTPS with a self-signed / private CA. Valid token gets 401 at request time.

One root cause: createOIDCConfig() in cmd/thv/app/run_flags.go builds the
auth.TokenValidatorConfig used for the auth middleware, but never sets
InsecureAllowHTTP or CACertPath. It only sets AllowPrivateIP. Same class as
#1470 ("Pass allowPrivateIP into createOIDCConfig") — that PR fixed
AllowPrivateIP in this function, the other two fields were left out.

Confirmed on v0.46.0 (commit c6c425a924) and still on main (6b40bf3d).

Why

CLI builds OIDC config two times:

  1. WithOIDCConfig(...) (pkg/runner/config_builder.go) gets all fields, including
    ThvCABundle and InsecureAllowHTTP. This fills the top-level
    RunConfig.OIDCConfig.
  2. setupOIDCConfiguration()createOIDCConfig() (cmd/thv/app/run_flags.go)
    builds a second TokenValidatorConfig. This one goes to
    WithMiddlewareFromFlags(...), and it is what builds the auth middleware
    config. Here InsecureAllowHTTP and CACertPath are missing.
    setupOIDCConfiguration also passes only runFlags.JWKSAllowPrivateIP, not
    runFlags.InsecureAllowHTTP and not runFlags.ThvCABundle.

At runtime the proxy rebuilds the auth middleware from the middleware config
(auth.CreateMiddlewarejson.Unmarshal(params)GetAuthenticationMiddleware
NewTokenValidator). So it gets InsecureAllowHTTP:false / CACertPath:"", and
OIDC discovery fails in networking.ValidatingTransport.

Proof from the run config

The proxy reads ~/Library/Application Support/toolhive/runconfigs/<name>.json.
OIDC config is there two times and they do not match. The one the proxy uses has
the fields dropped:

top-level  oidc_config.InsecureAllowHTTP           = true    # from WithOIDCConfig
MIDDLEWARE oidc_config.InsecureAllowHTTP (used)     = false   <-- dropped

top-level  oidc_config.CACertPath                   = /path/to/ca.pem
MIDDLEWARE oidc_config.CACertPath (used)            = ""      <-- dropped

           oidc_config.AllowPrivateIP (both)        = true    # survives, it is wired (#1470)

AllowPrivateIP survives but the other two do not. That is the tell — it is the
only one of the three that createOIDCConfig sets.

Repro A — --oidc-insecure-allow-http does nothing

  1. Serve any OIDC issuer over HTTP on localhost (discovery + JWKS), for
    example http://localhost:8099 with issuer = http://localhost:8099.
  2. thv run --name t --transport stdio \
      --oidc-issuer http://localhost:8099 --oidc-audience test \
      --oidc-insecure-allow-http=true --jwks-allow-private-ip=true \
      <image>
    
  3. POST /mcp with a valid Authorization: Bearer <token> (token iss/aud
    match).

Expected: HTTP is allowed for discovery (flag is set), token validates.
Actual: HTTP 401:

{"error":"invalid_token","error_description":"Invalid token: OIDC discovery failed:
 ... Get \"http://localhost:8099/.well-known/openid-configuration\": the supplied
 URL ... is not HTTPS scheme"}

Repro B — --thv-ca-bundle does nothing

  1. Serve the same issuer over HTTPS with a self-signed CA cert
    (basicConstraints=critical,CA:TRUE, SAN DNS:localhost).
  2. thv run --name t --transport stdio \
      --oidc-issuer https://localhost:8099 --oidc-audience test \
      --jwks-allow-private-ip=true --thv-ca-bundle /path/to/ca.pem \
      <image>
    
  3. POST /mcp with a valid Bearer token.

Expected: CA bundle is trusted for discovery / JWKS, token validates.
Actual: HTTP 401; proxy log:

oidc discovery failed after retries ... Get
"https://localhost:8099/.well-known/openid-configuration":
 tls: failed to verify certificate: x509: certificate signed by unknown authority

The --oidc-jwks-url "bypass" from #2288 does not help here either. Issuer
validation and the JWKS fetch use the same client, built without these fields.

Environment

  • thv v0.46.0 (Commit c6c425a924fac51c86cbade15d0e720e29a600ab), Homebrew
  • Runtime: OrbStack (Docker 29.4.0), macOS · Transport: --transport stdio
  • Also checked main @ 6b40bf3d in source — same createOIDCConfig.

Suggested fix

Same as #1470, just for the other two fields:

  • createOIDCConfig(...) — add insecureAllowHTTP bool and caCertPath string
    params, set InsecureAllowHTTP / CACertPath on the returned
    TokenValidatorConfig.
  • setupOIDCConfiguration(...) — pass runFlags.InsecureAllowHTTP and
    runFlags.ThvCABundle into createOIDCConfig.

(AuthTokenFile / --jwks-auth-token-file looks dropped by the same function
too — maybe wire it in the same change.)

Related, but not duplicates

Happy to open a PR for the CLI path.

Activity

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

No one assigned

    Labels

    authenticationbugSomething isn't workingcliChanges that impact CLI functionalityoidcp1Medium

    Type

    No type

    Projects

    No projects

      Milestone

      No milestone

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions