Threats tagged 'go'
730 threats
Most recently published first
| Severity | CVSS | Signals | Threat | Source | Published | Score and star |
|---|---|---|---|---|---|---|
| Medium | 5.8 | EPSS 0.3% | CVE-2026-45709axllent mailpit · Server-Side Request Forgery (SSRF) Medium5.8EPSS 0.3%CVE Database V5CVE-2026-45709 ## Summary The fix for GHSA-6jxm-fv7w-rw5j (CVE-2026-23845, "Server-Side Request Forgery (SSRF) via HTML Check API"), shipped in mailpit `v1.28.3`, hardened `internal/htmlcheck/css.go::downloadCSSToBytes` with a 5MB size cap, a `text/css` content-type check, login-info stripping in `isValidURL`, and an opt-in `--block-remote-css-and-fonts` config flag — but **did not add the IP-filtering dialer that the same codebase already uses on the two sister SSRF endpoints** (the proxy handler and link-check). At HEAD `8bc966e61834a24c48b4465da418f75e73be0afd` (2026-05-06), `internal/htmlcheck/css.go::newSafeHTTPClient` is mis-named — it builds an `http.Client` whose `Transport.DialContext` calls `net.Dialer.DialContext` directly with no IP allowlisting. As a result, the SSRF originally reported by Bao Anh Phan still permits the server to dial: - loopback (`127.0.0.0/8`, `::1`), - private (`10.0.0.0/8`, `172.16.0.0/12`, `192.168.0.0/16`, `fc00::/7`), - link-local incl. **cloud IMDS** (`169.254.0.0/16`, especially `169.254.169.254`), - CGNAT (`100.64.0.0/10`), - and any other reserved/multicast range, — provided the target replies with `HTTP/200` and a content-type beginning with `text/css`. With redirect-following (`CheckRedirect` allows redirects to any `isValidURL` URL with no IP filter), an attacker-controlled public site can redirect mailpit's request into the private network without ever appearing in the email's HTML. In the default mailpit deploy (no UI auth, no SMTP auth, port 1025/8025 exposed), this is an unauthenticated, network-reachable SSRF triggered by sending an HTML email and then issuing one HTTP `GET` to `/api/v1/message/{id}/html-check`. ## Affected versions - `internal/htmlcheck/css.go` at HEAD `8bc966e61834a24c48b4465da418f75e73be0afd` (2026-05-06). - All versions `>= v1.28.3` (the version that shipped the GHSA-6jxm fix). Versions `<= v1.28.2` are vulnerable to the original GHSA-6jxm; versions `>= v1.28.3` carry the still-vulnerable variant described here. ## The incomplete fix The original GHSA-6jxm fix added size+content-type+login-info hardening to `downloadCSSToBytes`. But the dialer it uses still has no `safeDialContext`. The companion `linkcheck` and `proxy` handlers in the same codebase have all-three protections: size cap, content-type/redirect filter, **AND** a `safeDialContext` that runs `tools.IsInternalIP(ip.IP)` per resolved address — same pattern the htmlcheck dialer should adopt. Side-by-side at HEAD `8bc966e`: | File | Function | `safeDialContext` (IP filter)? | TOCTOU-safe (dial-by-IP)? | |---|---|---|---| | `internal/linkcheck/status.go::safeDialContext` line 140-163 | dial check | YES | YES (resolved IP joined with port) | | `server/handlers/proxy.go::safeDialContext` line 393-415 | dial check | YES | YES | | `internal/htmlcheck/css.go::newSafeHTTPClient` line 275-310 | dial check | **NO** | n/a | The mis-named `newSafeHTTPClient` reads: ```go // internal/htmlcheck/css.go:275-310 func newSafeHTTPClient() *http.Client { dialer := &net.Dialer{ Timeout: 5 * time.Second, KeepAlive: 30 * time.Second, } tr := &http.Transport{ Proxy: nil, DialContext: func(ctx context.Context, network, address string) (net.Conn, error) { return dialer.DialContext(ctx, network, address) // no IP filter }, ... } client := &http.Client{ Transport: tr, Timeout: 15 * time.Second, CheckRedirect: func(req *http.Request, via []*http.Request) error { if len(via) >= 3 { return errors.New("too many redirects") } if !isValidURL(req.URL.String()) { return errors.New("invalid redirect URL") } return nil }, } return client } ``` `isValidURL` only rejects non-http(s) and userinfo URLs — it does NOT reject internal IPs. Compare `linkcheck/status.go::safeDialContext`: ```go ips, err := net.DefaultResolver.LookupIPAddr(ctx, host) ... if !config.AllowInternalHTTPRequests { for _, ip := range ips { if tools.IsInternalIP(ip.IP) { return nil, fmt.Errorf("blocked request to %s (%s): private/reserved address", host, ip) } } } return dialer.DialContext(ctx, network, net.JoinHostPort(ips[0].IP.String(), port)) ``` That's the protection htmlcheck is missing. ## Reachability chain (default deploy) ``` Listen() # config/config.go:36 SMTPListen = "[::]:1025" ↓ SMTP server # internal/smtpd/main.go:222-249 AuthRequired: false, AuthHandler: nil ↓ attacker injects HTML body with <link rel="stylesheet" href="...attacker.com/redirect.css"> ↓ storage.Store(...) ↓ Listen() # server/server.go HTTPListen ↓ attacker sends GET /api/v1/message/{id}/html-check apiv1.HTMLCheck # server/apiv1/other.go:18 ↓ no UI auth in default deploy (auth.UICredentials == nil) htmlcheck.RunTests(msg.HTML) | CVE Database V5 | ||
| Medium | 5.7 | EPSS 0.3% | Medium5.7EPSS 0.3%GCVE DatabaseCVE-2026-54246 Skipper is an HTTP router and reverse proxy for service composition. Prior to 0.27.13, the routesrv component serves cluster-wide control-plane data without application-layer authentication through /routes, /routes/{zone}, /swarm/redis/shards, and /swarm/valkey/shards. The handlers registered in routesrv/routesrv.go, eskipBytes.ServeHTTP in routesrv/eskipbytes.go, RedisHandler.ServeHTTP in routesrv/redishandler.go, and ValkeyHandler.ServeHTTP in routesrv/valkeyhandler.go only restrict HTTP methods and do not authenticate callers. A compromised or otherwise attacker-controlled pod that can reach routesrv on the Kubernetes cluster network can bypass its namespace-scoped RBAC and read Ingress and RouteGroup backend URLs, filter-chain and OAuth or OIDC path details, and Redis or Valkey shard addresses across namespaces. NetworkPolicy can restrict reachability but does not remove the missing-authentication condition, and the established impact is confidentiality exposure rather than direct integrity or availability loss. This issue is fixed in version 0.27.13. | GCVE Database | ||
| High | 8.2 | High8.2GCVE Database An incomplete fix in Skipper before version 0.27.26 allows an oversized HTTP request body to bypass Open Policy Agent (OPA) deny-on-presence Rego policies. When a request declares a Content-Length larger than the configured max body size, OPA receives an empty parsed body, causing deny policies to fail open while the full payload reaches the upstream service. This can lead to unauthorized requests being allowed based on body content. The issue affects deployments using opaAuthorizeRequestWithBody with deny-on-presence policies. Mitigation involves updating policies to explicitly check the truncated_body attribute to block or deny oversized bodies. | GCVE Database | |||
| High | 7.5 | EPSS 0.8% | High7.5EPSS 0.8%GCVE DatabaseCVE-2026-50274 The kubernetes-dns-node-cache package version 1.26.8-r6 addresses two vulnerabilities including CVE-2026-50274. This vulnerability is classified under CWE-770 and affects versions prior to 1.26.8-r6, including versions less than or equal to 1.24.1 and less than 2.8.1. The issue has been assigned a high severity rating. A patch is available to remediate the vulnerability. | GCVE Database | ||
| Medium | 5.9 | EPSS 0.7% | Medium5.9EPSS 0.7%GCVE DatabaseCVE-2026-49835 Sigstore Timestamp Authority is a service for issuing RFC 3161 timestamps. Prior to 2.1.0, the global wrapMetrics middleware records raw HTTP request path r.URL.Path and raw HTTP request method r.Method as Prometheus labels for latency and request count metric vectors before routing, allowing an unauthenticated remote attacker to issue requests with random paths such as /api/v1/timestamp/<uuid> or random HTTP methods and create unbounded permanent time-series entries that exhaust memory. This issue is fixed in version 2.1.0. | GCVE Database | ||
| High | 8.9 | EPSS 0.6% | CVE-2026-54526argoproj argo-workflows · Improper Access Control High8.9EPSS 0.6%CVE Database V5CVE-2026-54526 Argo Workflows is an open source container-native workflow engine for orchestrating parallel jobs on Kubernetes. Prior to 3.7.15 and 4.0.6, the allow-list fix for CVE-2026-31892 is incomplete because workflow/util/merge.go ValidateUserOverrides and SanitizeUserWorkflowSpec walk only the top-level fields of WorkflowSpec via reflection, and WorkflowSpec.ArtifactGC is allow-listed wholesale; the struct behind that field, WorkflowLevelArtifactGC, has a PodSpecPatch sub-field whose contents flow unmodified into util.ApplyPodSpecPatch on the artifact-GC pod, the same sink the original fix closed for WorkflowSpec.PodSpecPatch, so a user submitting a Workflow under templateReferencing: Strict or Secure (against a referenced WorkflowTemplate that declares an output artifact and setting spec.artifactGC.strategy: OnWorkflowCompletion) can still inject an arbitrary strategic merge patch into the artifact-GC pod, including hostPath volumes, privileged: true, arbitrary image and command, and hostNetwork: true, defeating the stated purpose of Strict/Secure reference mode. This issue is fixed in versions 3.7.15 and 4.0.6. | CVE Database V5 | ||
| Low | 2.9 | EPSS 0.3% | Low2.9EPSS 0.3%GCVE DatabaseCVE-2026-54450 ToolHive is a utility designed to simplify the deployment and management of Model Context Protocol (MCP) servers. Prior to 0.29.1, networking.IsPrivateIP in pkg/networking/utilities.go omits the IPv6 NAT64 prefixes 64:ff9b::/96 and 64:ff9b:1::/48, so NAT64 addresses embedding private, loopback, or link-local IPv4 targets are classified as public and allowed. The most direct attacker-controlled path begins when an external OAuth client supplies a client_id URL that CIMDStorageDecorator.GetClient routes through FetchClientMetadataDocument in pkg/oauthproto/cimd/fetch.go; protectedDialerControl in pkg/networking/http_client.go and validateHost in pkg/skills/gitresolver/reference.go share the defective classification but use operator-controlled or user-controlled destinations. On a ToolHive host behind a NAT64/DNS64 gateway, the gateway translates an allowed address such as 64:ff9b:1::a9fe:a9fe to 169.254.169.254, permitting blind probing of internal TCP or TLS reachability. The attacker-controlled CIMD path requires HTTPS, verifies certificates, and does not reflect response bodies, so the established impact is an internal reachability oracle rather than metadata credential exfiltration; the webhook client is not affected because it does not use this IP guard. This issue is fixed in version 0.29.1. | GCVE Database | ||
| Medium | 6.3 | EPSS 0.5% | CVE-2026-54452Blocklist · safeurl is Missing IPv6 CIDR Ranges Medium6.3EPSS 0.5%GCVE DatabaseCVE-2026-54452 safeurl is a server-side request forgery protection library. Prior to 0.2.4, the privateNetworks list in ip.go omits the IPv6 ranges 64:ff9b:1::/48, 5f00::/16, 3fff::/20, and 100:0:0:1::/64. When an application enables IPv6 with EnableIPv6(true), an attacker-controlled destination in one of these ranges is not recognized as non-public and can pass the SSRF destination check, potentially allowing access to resources hosted within the omitted ranges. IPv6 is disabled by default, and configurations that retain EnableIPv6(false) are not exposed to this bypass. This issue is fixed in version 0.2.4. | GCVE Database | ||
| Medium | 4.7 | EPSS 0.4% | CVE-2026-58196ToolHive · SSRF in remote MCP server authentication discovery (host-side, bypasses container isolation) Medium4.7EPSS 0.4%GCVE DatabaseCVE-2026-58196 ToolHive is a utility designed to simplify the deployment and management of Model Context Protocol (MCP) servers. Prior to 0.31.0, remote.Handler.Authenticate in pkg/auth/remote/handler.go invokes discovery.DetectAuthenticationFromServer in pkg/auth/discovery/discovery.go, whose host-side HTTP clients trust remote-server-controlled authentication discovery destinations, follow redirects without host or scheme restrictions, and do not consistently block private addresses. A malicious or compromised remote MCP server can place a resource_metadata URL in WWW-Authenticate for ParseWWWAuthenticate to extract, after which FetchResourceMetadata, OIDC issuer discovery, and well-known discovery can issue GET requests to link-local, RFC1918, or other internal services outside the server's container. The user connects to a server that the user intends to use, but the server controls the internal destination; this path does not depend on the separate NAT64 guard omission because the affected clients did not invoke IsPrivateIP, and it differs from the DCR resolver path that already refused redirects. The demonstrated primitive reaches internal-only HTTP services and reachability or error oracles, and can retrieve AWS metadata credentials where IMDSv1 accepts an unauthenticated GET, while IMDSv2 and GCP metadata prerequisites are not satisfied by the demonstrated request. This vulnerability is fixed in 0.31.0. | GCVE Database | ||
| High | 7.5 | EPSS 0.7% | CVE-2026-50285github.com/pomerium/pomerium · Pomerium Pre-Auth Memory Exhaustion via Unbounded zstd Decompression in HPKE Callback High7.5EPSS 0.7%GCVE DatabaseCVE-2026-50285 Pomerium versions 0.32.6 and 0.32.7 contain a vulnerability in the HPKE V2 URL decode path where attacker-controlled zstd compressed data is decompressed without any size limit. The proxy's /.pomerium/callback endpoint is reachable without authentication and processes attacker-crafted HPKE-encrypted payloads before validating the sender's identity. Because the HPKE receiver public key is publicly available, an attacker can send a decompression bomb that causes unbounded memory allocation, potentially crashing or degrading the proxy process. This results in a denial-of-service condition with no confidentiality or integrity impact. | GCVE Database | ||
| Medium | 4.3 | EPSS 0.3% | Medium4.3EPSS 0.3%GCVE DatabaseCVE-2026-54495 The open-feature-operator allows cross-namespace referencing of FeatureFlagSource and InProcessConfiguration resources via a documented annotation syntax. This behavior enables the operator to resolve and materialize the referenced resource's spec contents cluster-wide into workloads, potentially exposing sensitive inline data such as environment variables and bearer tokens. This issue affects multi-tenant Kubernetes clusters that treat namespaces as trust boundaries, allowing tenant-to-tenant disclosure of sensitive spec data. Single-tenant clusters are not impacted. The operator's cluster-wide RBAC scope and cross-namespace resolution are intentional and documented, with namespace-as-trust-boundary not part of the current security model. Mitigations include avoiding plaintext secrets in CR spec fields and restricting create permissions via RBAC where feasible. A future release plans to introduce cluster-scoped CRDs to remove implicit cross-namespace resolution. | GCVE Database | ||
| Medium | 5.8 | EPSS 0.3% | CVE-2026-62314TecharoHQ anubis · Improper Access Control Medium5.8EPSS 0.3%CVE Database V5CVE-2026-62314 Any HTTP client can bypass Anubis bot protection on the default configuration by adding a single request header. No challenge needs to be solved. Affected versions: v1.22.0 through v1.25.0 (introduced in commit d1d631a, PR #1015) The root cause is in `lib/policy/checker.go`, `PathChecker.Check()`: ```go func (pc *PathChecker) Check(r *http.Request) (bool, error) { originalUrl := r.Header.Get("X-Original-URI") if originalUrl != "" { if pc.regexp.MatchString(originalUrl) { return true, nil } } if pc.regexp.MatchString(r.URL.Path) { return true, nil } return false, nil } ``` The header value comes directly from the client request. The middleware chain never strips it. In reverse proxy mode an attacker fully controls it. The default policy imports `data/common/keep-internet-working.yaml`, which contains path-only ALLOW rules with no other conditions: ```yaml - name: well-known path_regex: ^/\.well-known/.*$ action: ALLOW ``` When `X-Original-URI` matches one of those regexes, the rule fires as ALLOW and the request is forwarded upstream without any challenge or JWT check. # Proof of concept Normal request, gets challenged: ``` curl -s https://anubis.techaro.lol/ | grep -o "<title>.*</title>" ``` Bypass, gets upstream content: ``` curl -s -H "X-Original-URI: /.well-known/x" https://anubis.techaro.lol/ | grep -o "<title>.*</title>" ``` The first command returns the Anubis challenge page title. The second returns the real site title directly, with no cookie set and no challenge issued. Verified live against anubis.techaro.lol. # Suggested fix The fix should probably be to strip the header from incoming client requests in the middleware chain before policy evaluation. In `auth_request` mode the header is set by the proxy after Anubis processes the middleware, so stripping it at ingress does not break that deployment mode. | CVE Database V5 | ||
| Medium | 6.9 | EPSS 0.3% | Medium6.9EPSS 0.3%GCVE DatabaseCVE-2026-53935 Cilium is a networking, observability, and security solution. Prior to 1.17.16, from 1.18.2 to 1.18.9, and from 1.19.0 to 1.19.3, users with the ability to create CiliumLocalRedirectPolicies can specify arbitrary ClusterIPs via addressMatcher, enabling hijacking traffic to Services in any namespace and bypassing namespace scoping enforced by serviceMatcher; deleting such a policy can also corrupt Cilium internal service state and stop service translation for the affected Service. This issue is fixed in versions 1.17.16, 1.18.10, and 1.19.4. | GCVE Database | ||
| Medium | 5.8 | EPSS 0.4% | CVE-2026-55187axllent mailpit · Server-Side Request Forgery (SSRF) Medium5.8EPSS 0.4%CVE Database V5CVE-2026-55187 ## Summary The remediation shipped in mailpit v1.29.2 for [GHSA-mpf7-p9x7-96r3](https://github.com/axllent/mailpit/security/advisories/GHSA-mpf7-p9x7-96r3) (CVE-2026-27808) is incomplete. The `tools.IsInternalIP` deny-list relies on Go's stdlib classification helpers (`IsLoopback`, `IsPrivate`, `IsLinkLocalUnicast`, `IsLinkLocalMulticast`, `IsUnspecified`, `IsMulticast`) plus an inline CGNAT range, but those helpers do **not** match two classes of IPv6 address that should be blocked for SSRF purposes: 1. **IPv6 forms that embed an IPv4 destination via documented translation mechanisms** — 6to4, NAT64, IPv4-compatible IPv6, ISATAP, or (in older Go versions) IPv4-mapped IPv6. These let an attacker reach internal IPv4 destinations by supplying an IPv6 literal that encodes the desired IPv4. 2. **IPv6 prefixes that fall outside the narrow private/loopback/link-local ranges Go's stdlib classifies** — specifically the deprecated site-local prefix `fec0::/10` (RFC 3879/4291) and the documentation prefix `2001:db8::/32` (RFC 3849). The first is still routable on dual-stack hosts and is cited as a bypass form in [CVE-2026-44430](https://advisories.gitlab.com/golang/github.com/modelcontextprotocol/registry/CVE-2026-44430/); the second should never appear in real network traffic and is safe to block as fail-safe behavior. Together these gaps let the Link Check API be coerced into dialing internal destinations that the v1.29.2 fix was intended to block. This is the same bug class as [GHSA-56c3-vfp2-5qqj / CVE-2026-44430 (MCP Registry)](https://advisories.gitlab.com/golang/github.com/modelcontextprotocol/registry/CVE-2026-44430/) and [GHSA-86m8-88fq-xfxp / CVE-2026-45741 (Gotenberg)](https://advisories.gitlab.com/golang/github.com/gotenberg/gotenberg/v8/CVE-2026-45741/) — projects that, like mailpit, built their SSRF deny-list around Go's stdlib `Is*` family and discovered the resulting bypass post-disclosure. The underlying ecosystem-wide issue is tracked upstream at [**golang/go#79925**](https://github.com/golang/go/issues/79925), which proposes extending `net.IP.IsPrivate` to handle these IPv6 transition forms. Until that lands, every Go project that wants comprehensive SSRF protection has to implement the decoding itself — which is exactly the gap that produced this advisory and the three CVEs in adjacent projects cited above. ## Affected versions - mailpit `v1.29.2` and later HEAD — the GHSA-mpf7-p9x7-96r3 fix is in place but [`tools.IsInternalIP`](https://github.com/axllent/mailpit/blob/a68499fa4e8874d414921fbd520e181dc92a39d7/internal/tools/net.go#L25-L34) does not cover the IPv6 forms enumerated below. - Pre-`v1.29.2` versions remain vulnerable to the original advisory. ## Vulnerable code [`internal/tools/net.go` L25-L34](https://github.com/axllent/mailpit/blob/a68499fa4e8874d414921fbd520e181dc92a39d7/internal/tools/net.go#L25-L34) — `IsInternalIP`: ```go func IsInternalIP(ip net.IP) bool { return ip.IsLoopback() || ip.IsPrivate() || ip.IsLinkLocalUnicast() || ip.IsLinkLocalMulticast() || ip.IsUnspecified() || ip.IsMulticast() || cgnatRange.Contains(ip) } ``` [`internal/linkcheck/status.go` L140-L163](https://github.com/axllent/mailpit/blob/a68499fa4e8874d414921fbd520e181dc92a39d7/internal/linkcheck/status.go#L140-L163) — `safeDialContext` calls `IsInternalIP` on resolved IPs before dialing, but only blocks when one of the seven predicates above fires. For each of the following bypass forms, `net.IP.IsLoopback`, `IsPrivate`, `IsLinkLocalUnicast`, `IsLinkLocalMulticast`, `IsUnspecified`, `IsMulticast`, and the CGNAT range check all return `false` — so the dial proceeds: **IPv4-embedded-in-IPv6 forms** (each carries an IPv4 destination via a documented translation prefix): | Bypass IPv6 literal | Decoded IPv4 destination | RFC | |---|---|---| | `64:ff9b::a9fe:a9fe` | `169.254.169.254` (AWS / GCP / Azure metadata) | RFC 6052 — NAT64 well-known prefix | | `64:ff9b:1::a9fe:a9fe` | `169.254.169.254` | RFC 8215 — NAT64 local-use | | `2002:a9fe:a9fe::` | `169.254.169.254` | RFC 3056 — 6to4 | | `::a9fe:a9fe` | `169.254.169.254` | RFC 4291 §2.5.5.1 — IPv4-compatible IPv6 | | `64:ff9b::7f00:1` | `127.0.0.1` | RFC 6052 (loopback via NAT64) | | `2002:0a00:0001::` | `10.0.0.1` | RFC 3056 (RFC 1918 via 6to4) | | `<any-prefix>:5efe:<ipv4>` | `<ipv4>` (e.g. `2001:db8::5efe:7f00:1` → `127.0.0.1`) | RFC 5214 — ISATAP | **Direct IPv6 prefixes not classified by the stdlib `Is*` family:** | Bypass IPv6 literal | What it is | RFC | |---|---|---| | `fec0::1` (any address in `fec0::/10`) | Deprecated site-local — still routable on dual-stack hosts | RFC 3879 (deprecation) / RFC 4291 §2.5.7 | | `2001:db8::1` (any address in `2001:db8::/32`) | Documentation prefix — should never appear on the wire | RFC 3849 | `IsInternalIP` returns `false` for every entry in both tables. The original advisory's stated mitigations *do* hold against the embedded-IPv4 forms in the narrow | CVE Database V5 | ||
| Critical | 9.0 | Critical9.0GCVE Database ## Description A vulnerability was discovered in TSDProxy where it forwards its internal per-process authentication token to all proxied backend services. When `identityHeaders` is enabled (the default), tsdproxy injects `x-tsdproxy-auth-token` into every upstream HTTP request alongside user identity headers. This token is the same secret used by the management HTTP server to trust forwarded Tailscale identity claims. A backend that receives this token can replay it from localhost to the management port with an arbitrary `x-tsdproxy-id` value, bypassing Tailscale authentication entirely. The token is forwarded unconditionally: `ProviderUserMiddleware` always calls `WhoisNewContext` regardless of whether the user is authenticated. In the `ReverseProxy.Rewrite` function, `WhoisFromContext` returns `ok=true` even for zero-value `Whois{}` (unauthenticated or Funnel requests). The `HeaderAuthToken` is set for every request when `identityHeaders=true`. The attack requires the backend to reach `127.0.0.1:8080`. This holds in: (1) non-Docker deployments where tsdproxy and a backend run on the same host, (2) Docker host-network-mode containers, (3) containers sharing tsdproxy's network namespace. ## Affected files - `internal/proxymanager/port.go:123-132` - `internal/core/admin.go:160-182` ```go // port.go: auth token forwarded regardless of user authentication state if identityHeaders { if user, ok := model.WhoisFromContext(r.In.Context()); ok { // ok=true even for empty Whois{} stored by ProviderUserMiddleware r.Out.Header.Set(consts.HeaderAuthToken, core.ProxyAuthToken()) // token sent to backend } } // admin.go: management port trusts x-tsdproxy-id from localhost when token is valid func ResolveWhois(r *http.Request) model.Whois { if IsLocalhost(r.RemoteAddr) { return model.Whois{ ID: r.Header.Get(consts.HeaderID), // attacker-controlled after stealing token } } return model.Whois{} } ``` ## Steps to reproduce 1. Deploy tsdproxy on a host (non-Docker) with a backend at `http://localhost:3000`. 2. Make a request through the Tailscale proxy. The backend receives `x-tsdproxy-auth-token` in the request headers. 3. From the host, replay the token to the management API: ```bash # Capture token from backend headers (e.g., via a header-reflection endpoint) TOKEN=$(curl -s http://localhost:3000/debug/headers | python3 -c "import sys,json; d=json.load(sys.stdin); print(d['headers'].get('X-Tsdproxy-Auth-Token',''))") # Replay from localhost to gain admin access curl -H "x-tsdproxy-auth-token: $TOKEN" \ -H "x-tsdproxy-id: attacker" \ http://127.0.0.1:8080/api/v1/proxies # Returns full proxy list with admin access ``` ## Fix Remove `HeaderAuthToken` from the outgoing backend request, and guard identity-header injection on `user.ID != ""`: ```go // port.go: only inject headers for actually authenticated users if identityHeaders { if user, ok := model.WhoisFromContext(r.In.Context()); ok && user.ID != "" { r.Out.Header.Set(consts.HeaderID, user.ID) // HeaderAuthToken should NOT be forwarded to backends } } ``` ## Impact An attacker with code execution in any backend proxied by tsdproxy (on the same host) gains full management API control: restart or pause all proxied services (DoS), enumerate all proxy configurations and backend network topology, and trigger webhook deliveries (SSRF via configured webhook URLs). ## Credits Reported by Vishal Shukla (@shukla304 / @therawdev). ## Sponsorship This audit is from an AI-assisted research agent at [sechub.dev](https://sechub.dev). Running it on OSS projects is free for maintainers. | GCVE Database | |||
| High | 8.3 | EPSS 0.2% | High8.3EPSS 0.2%GCVE DatabaseCVE-2026-54174 CVE-2026-54174 is a vulnerability in chainguard.dev/apko where the package manager did not verify the integrity of the data section (actual package files) during installation. While the control section hash was verified against the signed APKINDEX, the data section hash was not, allowing an attacker who can compromise a mirror, cache, or perform a man-in-the-middle attack to substitute arbitrary file contents without detection. This flaw was fixed in versions 1.2.9 and 0.50.4. | GCVE Database | ||
| Critical | 9.3 | EPSS 0.8% | CVE-2026-54088File Browser · Command Injection via Authentication Hook Shell Substitution (Pre-Authentication RCE) Critical9.3EPSS 0.8%GCVE DatabaseCVE-2026-54088 ## Overview The Hook Authentication feature in File Browser allows administrators to delegate login verification to an external shell command. User-supplied credentials (username and password) are interpolated into this command string using `os.Expand` without sanitization. An **unauthenticated remote attacker** can inject shell metacharacters in the username or password field at the login screen, causing the server to execute arbitrary OS commands before any authentication takes place. This is a **critical pre-authentication RCE**. ## Affected Location - **File:** `auth/hook.go` - **Function:** `HookAuth.RunCommand` ## CVSS v4.0 | Metric | Value | Rationale | |---|---|---| | Attack Vector (AV) | Network (N) | Exploitable via the login endpoint over HTTP from any network | | Attack Complexity (AC) | Low (L) | Single crafted HTTP request; no preparation needed | | Attack Requirements (AT) | None (N) | No race condition or special timing required | | Privileges Required (PR) | **None (N)** | **No account required — pre-authentication attack** | | User Interaction (UI) | None (N) | Fully automated; no victim action needed | | Vulnerable System Confidentiality (VC) | High (H) | Full read access to server filesystem and env | | Vulnerable System Integrity (VI) | High (H) | Arbitrary file write/modification | | Vulnerable System Availability (VA) | High (H) | Can kill processes, exhaust resources | | Subsequent System Confidentiality (SC) | None (N) | No direct impact on downstream systems assumed | | Subsequent System Integrity (SI) | None (N) | — | | Subsequent System Availability (SA) | None (N) | — | **Vector String:** `CVSS:4.0/AV:N/AC:L/AT:N/PR:N/UI:N/VC:H/VI:H/VA:H/SC:N/SI:N/SA:N` **Base Score: 9.3 (Critical)** > **Note:** `PR:None` is the critical differentiator from vulnerabilities 01 and 02. Because the injection point is the unauthenticated login endpoint, no account or session is required. A single HTTP request to the login API is sufficient to achieve RCE. ## CWE | ID | Name | Role | |---|---|---| | [CWE-78](https://cwe.mitre.org/data/definitions/78.html) | Improper Neutralization of Special Elements used in an OS Command ('OS Command Injection') | Primary — attacker-supplied credentials embedded in shell command string via `os.Expand` | | [CWE-88](https://cwe.mitre.org/data/definitions/88.html) | Improper Neutralization of Argument Delimiters in a Command ('Argument Injection') | Secondary — `$USERNAME`/`$PASSWORD` expansion injects additional shell commands | | [CWE-306](https://cwe.mitre.org/data/definitions/306.html) | Missing Authentication for Critical Function | Secondary — OS command execution is reachable before any authentication is verified | ## Technical Details `HookAuth.RunCommand` builds the authentication command and substitutes credential values using `os.Expand`: ```go // auth/hook.go envMapping := func(key string) string { switch key { case "USERNAME": return a.Cred.Username // directly from the HTTP login request body case "PASSWORD": return a.Cred.Password // directly from the HTTP login request body default: return os.Getenv(key) } } for i, arg := range command { if i == 0 { continue } command[i] = os.Expand(arg, envMapping) // no escaping applied } ``` `os.Expand` performs plain text substitution. There is no escaping, quoting, or validation of the credential values before they are embedded into the command string. If an admin has configured the hook authentication command as: ``` sh -c "test $USERNAME = 'admin'" ``` ...and an attacker submits the username `; id #` at the login screen, the expanded command becomes: ```sh sh -c "test ; id # = 'admin'" ``` The `;` terminates the `test` expression and the shell executes `id`. The `#` comments out the remainder, preventing a syntax error. The attacker's command runs with the privileges of the File Browser process — **without needing a valid account or password**. ## Attack Scenario / Reproduction Steps 1. Admin enables Hook Authentication and sets the command to: ``` sh -c "test $USERNAME = 'admin'" ``` 2. An unauthenticated attacker sends a login request (e.g., via `curl` or the web UI) with: - **Username:** `; id #` - **Password:** (any value) 3. The server executes: ```sh sh -c "test ; id # = 'admin'" ``` 4. The `id` command runs on the server, confirming pre-authentication RCE. No account is needed. The attacker does not need to know any valid credentials. A single request is sufficient. ## Impact An unauthenticated remote attacker can execute arbitrary OS commands on the server under the privilege level of the File Browser process. This is the most severe class of vulnerability in this codebase: - **No authentication required** — exposed to the entire internet if the service is public-facing. - **Single request** — no setup, no enumeration, no prior foothold. - Full server compromise: data exfiltration, persistent backdoor in | GCVE Database | ||
| Critical | 9.1 | EPSS 0.6% | Critical9.1EPSS 0.6%GCVE DatabaseCVE-2026-54089 ## Summary When FileBrowser is configured with proxy authentication (`auth.method=proxy`), any unauthenticated attacker who can reach the server directly can impersonate **any user - including admin** - by sending a single forged HTTP header. No credentials are required. Additionally, specifying a non-existent username causes the server to **automatically create a new user account**, providing an account creation primitive with no authorization. **This is an already known issue that has been documented in the documentation for several years, but has not been documented as a vulnerability before.** ## Severity **HIGH** - CVSS 3.1: **8.1** (AV:N/AC:L/PR:N/UI:N/S:U/C:H/I:H/A:N) ## Affected Component - **File:** [`auth/proxy.go`](https://github.com/filebrowser/filebrowser/blob/main/auth/proxy.go), lines 21-28 - **CWE:** [CWE-287](https://cwe.mitre.org/data/definitions/287.html) (Improper Authentication), [CWE-290](https://cwe.mitre.org/data/definitions/290.html) (Authentication Bypass by Spoofing) - **Affected versions:** All versions supporting `auth.method=proxy` ## Prerequisite: Proxy Auth Must Be Enabled This vulnerability is **NOT exploitable on default configuration** (`auth.method=json`). It requires the administrator to have configured proxy authentication mode. However, this is a **common production deployment pattern** - many organizations run FileBrowser behind a reverse proxy that handles SSO/LDAP/OAuth authentication: - **nginx** + Authelia / Authentik - **Traefik** + OAuth2 Proxy - **Caddy** + forward_auth - **Apache** + mod_auth_ldap In these setups, the proxy authenticates the user and passes the username via HTTP header (e.g., `X-Remote-User`). FileBrowser trusts this header to identify the user. | Deployment Scenario | Exploitable? | |---|---| | Default install (`auth.method=json`) | **No** — JSON auth uses password verification | | `auth.method=proxy` + FileBrowser only reachable via proxy (bound to `127.0.0.1` or firewalled) | **No** - attacker cannot reach the server directly | | `auth.method=proxy` + FileBrowser port exposed to network | **Yes - full admin takeover** | The third scenario is common because: - Docker containers publish ports to `0.0.0.0` by default (e.g., `-p 8085:80`) - Administrators expose the port for debugging, monitoring, or health checks - Cloud deployments may have misconfigured security groups or load balancers - Internal networks often lack strict micro-segmentation The core issue is that the **code itself has zero defensive checks** — no trusted IP validation, no shared secret, no origin verification. The entire security model relies on network-level isolation, which is fragile and not documented as a hard requirement. ## Root Cause The `ProxyAuth.Auth()` function unconditionally trusts the value of an HTTP request header (configured via `auth.header`, e.g. `X-Remote-User`) to determine the authenticated user's identity. There are **three distinct problems** in this code: ### Problem 1: No Origin Validation The function reads the header from **any** HTTP request regardless of source IP. It does not verify that the request originated from a trusted reverse proxy. Any client on the network can set arbitrary HTTP headers. **File: [`auth/proxy.go`](https://github.com/filebrowser/filebrowser/blob/main/auth/proxy.go), lines 21-28:** ```go func (a ProxyAuth) Auth(r *http.Request, usr users.Store, setting *settings.Settings, srv *settings.Server) (*users.User, error) { username := r.Header.Get(a.Header) // <-- reads attacker-controlled header, no origin check user, err := usr.Get(srv.Root, username) if errors.Is(err, fberrors.ErrNotExist) { return a.createUser(usr, setting, srv, username) } return user, err // <-- returns the user object, no password verification } ``` There is no call to verify `r.RemoteAddr` against a list of trusted proxy IPs, no shared secret validation, and no signature check on the header value. ### Problem 2: No Password Verification Unlike JSON auth (`auth/json.go`) which validates the password via bcrypt, the proxy auth path returns the user object directly from the database based solely on the header value. The `loginHandler` in `http/auth.go` then mints a valid JWT for this user: **File: [`http/auth.go`](https://github.com/filebrowser/filebrowser/blob/main/http/auth.go), lines 121-137:** ```go func loginHandler(tokenExpireTime time.Duration) handleFunc { return func(w http.ResponseWriter, r *http.Request, d *data) (int, error) { auther, err := d.store.Auth.Get(d.settings.AuthMethod) // ... user, err := auther.Auth(r, d.store.Users, d.settings, d.server) // No additional verification — if auther.Auth() returns a user, a JWT is minted return printToken(w, r, d, user, tokenExpireTime) // <-- signs and returns JWT } } ``` ### Problem 3: Automatic User Creation If the username in the header doesn't exist in the database, `createUser()` is called | GCVE Database | ||
| Critical | 9.3 | EPSS 0.5% | CVE-2026-54072Authorizer · Unvalidated redirect_uri in /authorize leaks OAuth2 tokens to attacker-controlled URL Critical9.3EPSS 0.5%GCVE DatabaseCVE-2026-54072 The /authorize endpoint in github.com/authorizerdev/authorizer does not validate the redirect_uri parameter against allowed origins, allowing an attacker to supply a malicious redirect URI. When response_type=token or id_token, the server appends OAuth2 tokens as query parameters and redirects the user to the attacker-controlled URL. An unauthenticated attacker can obtain the client_id from a public GraphQL endpoint. This enables token theft if a logged-in user clicks a crafted link, allowing the attacker to impersonate the user for the token lifetime. | GCVE Database | ||
| High | 8.2 | EPSS 0.2% | CVE-2026-53657lima · Incorrect Default Permissions High8.2EPSS 0.2%CVE Database V5CVE-2026-53657 CVE-2026-53657 is a vulnerability in lima-vm's lima product versions prior to 2.1.3. When running with the qemu driver and the guest agent enabled, an arbitrary user inside the VM can access the guest agent socket (/run/lima-guestagent.sock), potentially allowing execution of arbitrary commands with root privileges inside the VM. This issue does not affect the vz driver, which uses vsocks instead of Unix sockets. The vulnerability is patched in version 2.1.3. The default user in the VM can already run commands as root via sudo, so this behavior is not considered a vulnerability by design. Workarounds include using the vz driver or disabling the guest agent. | CVE Database V5 |
Page 23 of 37