Authgear: Anonymous Project Configuration Disclosure via Path Traversal

An anonymous request to the static asset endpoint reads the complete project configuration including OAuth clients and the tenant's hook source through a traversal path.

Advisory ID: TP-2026-121
Product: Authgear (open-source authentication and single sign-on platform)
Vulnerability type: Path traversal (CWE-22)
CVE: requested (GitHub CNA), assignment pending
CVSS 3.1: 7.5 (High) · CVSS:3.1/AV:N/AC:L/PR:N/UI:N/S:U/C:H/I:N/A:N
Affected versions: < 2026-10-08.0
Vendor advisory: GHSA-c7wp-6c82-59rm
Fixed in: 2026-10-08.0
Reported: 7 October 2026

Summary

Authgear is an open-source authentication and single sign-on platform whose AuthUI serves static assets under /static/. The backing filesystem resolves paths against the application's whole resource registry instead of the asset directory alone, and a .. segment in the path leads out of the asset directory. The integrity check that should reject unknown file names is inverted and is skipped by any 8-character hash in the file name. A single anonymous GET request therefore reads the complete authgear.yaml project configuration with OAuth clients and identity provider configuration, plus hook source code authored by the tenant.

Root cause

pkg/util/httputil/file_server.go:193 normalises the request path, and the prefix test against the assets directory at pkg/util/httputil/file_server.go:219 fails for a traversal path, so the request falls through to the serveOther fallback (pkg/util/httputil/file_server.go:128). That branch opens the path through Open at pkg/auth/handler/webapp/app_static_assets.go:60, which splits the name at :66 and calls Resources.Resolve at :72 against the complete descriptor registry, which has no notion of the static subtree. The md5 comparison at pkg/auth/handler/webapp/app_static_assets.go:89-95 runs only inside if !web.LookLikeAHash(hashInPath), and pkg/lib/web/static_asset_resolver.go:128-130 defines that predicate as len(s) == 8, so any 8-character string bypasses the check. The route chain at pkg/auth/routes.go:81-85 carries only project root, CORS and public origin, with no session or authentication middleware. Only authgear.secrets.yaml stays out of reach, because the effective-file view of the secret descriptor returns ErrEffectiveSecretConfig (pkg/lib/config/configsource/resources.go:486).

Proof of Concept

GET /static/..%2fauthgear.deadbeef.yaml        (anonymous)
-> 200, complete authgear.yaml (id, http.public_origin, oauth.clients, redirect_uris, ...)

GET /static/..%2fdeno%2f<hook>.deadbeef.ts     -> 200, hook source returned verbatim
GET /static/..%2fauthgear.<32 chars>.yaml      -> 404
GET /static/..%2fauthgear.secrets.deadbeef.yaml -> 500

deadbeef has exactly 8 characters and therefore counts as a hash, which skips the content comparison. The response is byte-identical to the file on the server's disk.

Impact

  • Complete OAuth client registry and identity provider configuration of the deployment readable without an account.
  • Source of every resource the tenant has authored, such as Deno hooks, publicly retrievable.
  • Client ids and redirect URIs give the exact parameters for follow-on attacks against the deployment's SSO.
  • One anonymous GET request, no user interaction, stock configuration.

References

Is Something Like This in Your Software?

Our team found this vulnerability in the course of its work. Have your applications tested by the same specialists, with a penetration test from turingpoint.