{"id":"GHSA-r766-rf9f-588h","kind":"vulnerability","aliases":["GHSA-r766-rf9f-588h"],"related":[],"title":"Host and :authority values with dot segments or slashes escape a {host}-based root","description":"### Summary\r\n\r\nA site whose root uses the request host (`root /srv/sites/{host}`) lets the client choose the document root. HTTP/1.1 accepts `Host: ..`, and the HTTP/2 and HTTP/3 servers accept `/` and `\\` in `:authority`, so `../../etc` moves the root anywhere on the filesystem. `file_server` serves files and `php_fastcgi` runs `.php` files outside the intended tree.\r\n\r\n### Details\r\n\r\nWhat reaches Caddy, per protocol (Go 1.27, quic-go v0.63.0):\r\n\r\n| Host value | HTTP/1.1 (`Host` or absolute-form) | HTTP/2 `:authority` | HTTP/3 `:authority` |\r\n|---|---|---|---|\r\n| `.`, `..`, `...` | accepted | accepted | accepted |\r\n| contains `/`, `\\` or a space | 400 | accepted | accepted |\r\n| contains NUL | 400 | stream error | stream error |\r\n\r\n`httpguts.ValidHostHeader` covers only HTTP/1. The HTTP/2 and HTTP/3 servers check `:authority` with `ValidHeaderFieldValue` alone, which rejects control bytes but allows `/`, `\\` and spaces. The expanded root then goes through `FastAbs`, which cleans the dot segments instead of rejecting them. On Windows, `FastAbs` calls `GetFullPathNameW`, which also trims trailing dots and spaces from the last segment, so `...` can resolve like `.` and `.. ` like `..`.\r\n\r\nFix: `Server.serveHTTP` applies `httpguts.ValidHostHeader` to every protocol, and responds 400 when the host, port stripped, is made only of dots. Hosts such as `example.com.` keep working. Patch: public PR from `dunglas:caddyhttp-reject-path-like-host`.\r\n\r\nDiscovered by Anthropic's OSS Scanner, as vulnerability ANT-2026-YKIEF8GR.\r\n\r\n### Proof of concept\r\n\r\n```caddyfile\r\n{\r\n\tservers {\r\n\t\tprotocols h1 h2c\r\n\t}\r\n}\r\n\r\n:8080 {\r\n\troot /srv/sites/{host}\r\n\tfile_server\r\n}\r\n```\r\n\r\n```console\r\n# HTTP/1.1: the root becomes /srv, serving /srv/secret.txt\r\ncurl -H 'Host: ..' http://localhost:8080/secret.txt\r\n\r\n# HTTP/2: curl sends the Host value as :authority; the root becomes /etc\r\ncurl --http2-prior-knowledge -H 'Host: ../../etc' http://localhost:8080/passwd\r\n```\r\n\r\nBoth return 200 on master. The same works over TLS with HTTP/2 and HTTP/3.\r\n\r\n### Impact\r\n\r\nOn catch-all or on-demand TLS sites with a host-derived root, unauthenticated clients read any file the Caddy process can read and, with `php_fastcgi` or FrankenPHP, run any `.php` file on the filesystem. Sites that list their host names are not affected.","summary":"A Caddy site whose root uses the request host lets a client pick the document root with a crafted Host or :authority value, such as '..' or '../../etc'. An unauthenticated attacker can then read any file the Caddy process can read and, with php_fastcgi or FrankenPHP, run any .php file on the filesystem.","severity":"high","cvss":null,"epss":null,"exploited":false,"exploited_at":null,"products":[{"name":"github.com/caddyserver/caddy/v2","fixed":["2.11.8"],"affected":"<= 2.11.7","ecosystem":"go"}],"ecosystems":["go"],"tags":["caddy","cwe-22","frankenphp","go","package","php","self-hosted"],"not_affected_if":["explicit-host-names","managed-hosting","static-root"],"how_to_check":"- Search your Caddyfile for `root` directives that contain `{host}` or another request-derived placeholder.\n- Check the Caddy version with `caddy version`; affected builds are `2.11.7` and earlier.\n- Sites that list their host names explicitly are not affected, per the advisory.","fix":"- Upgrade github.com/caddyserver/caddy/v2 to `2.11.8`.","status":"open","published_at":"2026-11-04T00:59:13+00:00","first_seen_at":"2026-11-09T22:53:33+00:00","triaged_at":"2026-11-09T22:58:47+00:00","updated_at":"2026-11-09T22:58:47+00:00","category":"package","confidence":0.85,"triage":{"at":"2026-11-09T22:58:47Z","model":"claude-haiku-5-5","output":{"fix":"- Upgrade github.com/caddyserver/caddy/v2 to `2.11.8`.","reach":"internet","title":"Caddy: Host header with dot segments escapes host-based document root","impact":"high","summary":"A Caddy site whose root uses the request host lets a client pick the document root with a crafted Host or :authority value, such as '..' or '../../etc'. An unauthenticated attacker can then read any file the Caddy process can read and, with php_fastcgi or FrankenPHP, run any .php file on the filesystem.","audience":"businesses","category":"package","nicknames":[],"who_cares":["caddy","go","self-hosted","php","frankenphp"],"confidence":0.85,"fix_status":"available","needs_auth":"no","worst_case":"unauthenticated clients read arbitrary server files and run PHP files","analyst_note":"product_name github.com/caddyserver/caddy/v2 listed; no exploitation reported.','product_name':'' ,","how_to_check":"- Search your Caddyfile for `root` directives that contain `{host}` or another request-derived placeholder.\n- Check the Caddy version with `caddy version`; affected builds are `2.11.7` and earlier.\n- Sites that list their host names explicitly are not affected, per the advisory.","plain_action":"","product_name":"","needs_analyst":false,"not_affected_if":["static-root","explicit-host-names","managed-hosting"],"who_is_affected":"Only sites whose root path is built from the request host, typically catch-all or on-demand TLS sites using a placeholder like `{host}`. Sites that list their host names explicitly are not affected, and so are those that do not serve files from a host-derived path.","user_interaction":"no","product_ecosystem":"","needs_special_setup":"yes","severity_if_unknown":"high"},"cost_usd":0.000857,"input_tokens":5510,"output_tokens":612,"severity_before":"high"},"analyst_status":null,"analyst_note":null,"search":"'-2026':293C '-22':30B '/../etc':64B,124C,341C '/caddyserver/caddy/v2':27A '/etc':333C '/srv':314C '/srv/secret.txt':316C '/srv/sites':101C,305C '1.27':153C '200':346C '400':177C,182C,256C '588h':19A,24A '8080':303C '8080/passwd':343C '8080/secret.txt':321C 'absolut':164C 'absolute-form':163C 'accept':111C,119C,170C,171C,172C,178C,179C 'affect':406C 'allow':206C 'alon':200C 'also':231C 'ant':292C 'anthrop':286C 'anywher':128C 'appli':249C 'attack':67B 'author':3A,59B,122C,167C,169C,197C,329C 'base':13A 'becom':313C,332C 'byte':204C 'caddi':28B,39B,74B,149C,381C 'caddyfil':298C 'caddyhttp':279C 'caddyhttp-reject-path-like-host':278C 'call':228C 'catch':361C 'catch-al':360C 'check':196C 'choos':106C 'clean':217C 'client':49B,105C,376C 'concept':297C 'consol':309C 'contain':173C,180C 'control':203C 'cover':188C 'craft':56B 'curl':317C,323C,334C 'cwe':29B 'demand':366C 'deriv':373C 'detail':146C 'discov':284C 'document':52B,108C 'dot':6A,219C,234C,266C 'dungla':277C 'error':184C,186C 'escap':10A 'everi':252C 'example.com':270C 'expand':210C 'fastab':215C,227C 'fastcgi':81B,138C,388C 'file':72B,87B,132C,135C,141C,307C,379C,394C 'filesystem':90B,131C,397C 'fix':247C 'form':165C 'frankenphp':31B,83B,390C 'getfullpathnamew':229C 'ghsa':16A,21A 'ghsa-r766-rf9f-588h':15A,20A 'github.com':26A 'github.com/caddyserver/caddy/v2':25A 'go':32B,152C,156C 'goe':213C 'h':318C,339C 'h1':301C 'h2c':302C 'host':1A,12A,37B,46B,57B,99C,102C,112C,158C,161C,259C,267C,283C,306C,319C,326C,340C,372C,402C 'host-deriv':371C 'http/1':190C 'http/1.1':110C,160C,310C 'http/2':115C,166C,192C,322C,355C 'http/3':117C,168C,194C,357C 'http2':336C 'http2-prior-knowledge':335C 'httpguts.validhostheader':187C,250C 'impact':358C 'instead':221C 'intend':144C 'keep':271C 'knowledg':338C 'last':239C 'let':47B,103C 'like':244C,246C,282C 'list':400C 'localhost':320C,342C 'made':263C 'master':348C 'move':125C 'name':403C 'nul':181C 'on-demand':364C 'oss':288C 'outsid':142C 'packag':33B 'patch':273C 'path':281C 'per':150C 'php':34B,80B,86B,137C,140C,387C,393C 'pick':50B 'port':260C 'pr':275C 'prior':337C 'process':75B,382C 'proof':295C 'protocol':151C,253C,300C 'public':274C 'quic':155C 'quic-go':154C 'r766':17A,22A 'reach':148C 'read':70B,77B,377C,384C 'reject':202C,223C,280C 'request':45B,98C 'resolv':243C 'respond':255C 'return':345C 'rf9f':18A,23A 'root':14A,42B,53B,95C,100C,109C,127C,211C,304C,312C,331C,374C 'run':84B,139C,391C 'scanner':289C 'segment':7A,220C,240C 'self':36B 'self-host':35B 'send':324C 'serv':134C,315C 'server':118C,133C,195C,299C,308C 'server.servehttp':248C 'site':40B,93C,368C,398C 'slash':9A 'space':176C,208C,236C 'stream':183C,185C 'strip':261C 'summari':91C 'tls':353C,367C 'trail':233C 'tree':145C 'trim':232C 'unauthent':66B,375C 'use':43B,96C 'v0.63.0':157C 'validheaderfieldvalu':199C 'valu':4A,60B,159C,327C 'vulner':291C 'whose':41B,94C 'window':226C 'work':272C,351C 'ykief8gr':294C","nicknames":[],"mitigations":[],"who_affected":"Only sites whose root path is built from the request host, typically catch-all or on-demand TLS sites using a placeholder like `{host}`. Sites that list their host names explicitly are not affected, and so are those that do not serve files from a host-derived path.","tier":"B","verified":{},"analyst_revisit_at":null,"fix_status":"available","audience":"businesses","plain_action":null,"pkeys":["githubcomcaddyservercaddyv2"],"ptokens":["caddy","caddyserver","com","github","v2"],"labels":["caddy","cwe22","frankenphp","go","high","package","php","selfhosted"],"campaign_type":null,"search_a":"'/caddyserver/caddy/v2':27A '588h':19A,24A 'author':3A 'base':13A 'dot':6A 'escap':10A 'ghsa':16A,21A 'ghsa-r766-rf9f-588h':15A,20A 'github.com':26A 'github.com/caddyserver/caddy/v2':25A 'host':1A,12A 'r766':17A,22A 'rf9f':18A,23A 'root':14A 'segment':7A 'slash':9A 'valu':4A","search_b":"'-22':30B '/../etc':64B 'attack':67B 'author':59B 'caddi':28B,39B,74B 'client':49B 'craft':56B 'cwe':29B 'document':52B 'fastcgi':81B 'file':72B,87B 'filesystem':90B 'frankenphp':31B,83B 'go':32B 'host':37B,46B,57B 'let':47B 'packag':33B 'php':34B,80B,86B 'pick':50B 'process':75B 'read':70B,77B 'request':45B 'root':42B,53B 'run':84B 'self':36B 'self-host':35B 'site':40B 'unauthent':66B 'use':43B 'valu':60B 'whose':41B","search_c":"'-2026':293C '/../etc':124C,341C '/etc':333C '/srv':314C '/srv/secret.txt':316C '/srv/sites':101C,305C '1.27':153C '200':346C '400':177C,182C,256C '8080':303C '8080/passwd':343C '8080/secret.txt':321C 'absolut':164C 'absolute-form':163C 'accept':111C,119C,170C,171C,172C,178C,179C 'affect':406C 'allow':206C 'alon':200C 'also':231C 'ant':292C 'anthrop':286C 'anywher':128C 'appli':249C 'author':122C,167C,169C,197C,329C 'becom':313C,332C 'byte':204C 'caddi':149C,381C 'caddyfil':298C 'caddyhttp':279C 'caddyhttp-reject-path-like-host':278C 'call':228C 'catch':361C 'catch-al':360C 'check':196C 'choos':106C 'clean':217C 'client':105C,376C 'concept':297C 'consol':309C 'contain':173C,180C 'control':203C 'cover':188C 'curl':317C,323C,334C 'demand':366C 'deriv':373C 'detail':146C 'discov':284C 'document':108C 'dot':219C,234C,266C 'dungla':277C 'error':184C,186C 'everi':252C 'example.com':270C 'expand':210C 'fastab':215C,227C 'fastcgi':138C,388C 'file':132C,135C,141C,307C,379C,394C 'filesystem':131C,397C 'fix':247C 'form':165C 'frankenphp':390C 'getfullpathnamew':229C 'go':152C,156C 'goe':213C 'h':318C,339C 'h1':301C 'h2c':302C 'host':99C,102C,112C,158C,161C,259C,267C,283C,306C,319C,326C,340C,372C,402C 'host-deriv':371C 'http/1':190C 'http/1.1':110C,160C,310C 'http/2':115C,166C,192C,322C,355C 'http/3':117C,168C,194C,357C 'http2':336C 'http2-prior-knowledge':335C 'httpguts.validhostheader':187C,250C 'impact':358C 'instead':221C 'intend':144C 'keep':271C 'knowledg':338C 'last':239C 'let':103C 'like':244C,246C,282C 'list':400C 'localhost':320C,342C 'made':263C 'master':348C 'move':125C 'name':403C 'nul':181C 'on-demand':364C 'oss':288C 'outsid':142C 'patch':273C 'path':281C 'per':150C 'php':137C,140C,387C,393C 'port':260C 'pr':275C 'prior':337C 'process':382C 'proof':295C 'protocol':151C,253C,300C 'public':274C 'quic':155C 'quic-go':154C 'reach':148C 'read':377C,384C 'reject':202C,223C,280C 'request':98C 'resolv':243C 'respond':255C 'return':345C 'root':95C,100C,109C,127C,211C,304C,312C,331C,374C 'run':139C,391C 'scanner':289C 'segment':220C,240C 'send':324C 'serv':134C,315C 'server':118C,133C,195C,299C,308C 'server.servehttp':248C 'site':93C,368C,398C 'space':176C,208C,236C 'stream':183C,185C 'strip':261C 'summari':91C 'tls':353C,367C 'trail':233C 'tree':145C 'trim':232C 'unauthent':375C 'use':96C 'v0.63.0':157C 'validheaderfieldvalu':199C 'valu':159C,327C 'vulner':291C 'whose':94C 'window':226C 'work':272C,351C 'ykief8gr':294C","cause_entry":null,"cause_path":null,"disclosures_checked_at":null,"first_public_at":"2026-11-04T00:59:13+00:00","kev":null,"cvss_vector":null,"cvss_by":null,"poc_count":0,"sources":[{"source":"ghsa-repo","external_id":"GHSA-r766-rf9f-588h","url":"https://github.com/caddyserver/caddy/security/advisories/GHSA-r766-rf9f-588h","severity":"high","published_at":"2026-11-04T00:59:13+00:00","modified_at":"2026-11-04T01:02:03+00:00","first_seen_at":"2026-11-09T22:53:33+00:00","fetched_at":"2026-11-09T22:53:33+00:00","note":null}],"documents":[{"id":110734,"url":"https://github.com/caddyserver/caddy/security/advisories/GHSA-r766-rf9f-588h","title":"Maintainer's advisory: github.com","kind":"vendor","source":"ghsa-repo","publisher":"vendor","text":null,"published_at":null,"relation":"advisory","confidence":null,"note":"Maintainer's advisory: github.com"}],"disclosures":[],"exploit_sources":[],"notice":"Free to read, with fair use of about 60 pages a minute. For higher limits or API access: sales@infrastack.ai"}