Harden the read path, forwarded headers and security docs
Caddy Manager CI build / docker (push) Failing after 1m2s
Caddy Manager CI build / docker (push) Failing after 1m2s
Found while investigating an unrelated Gitea compromise: CaddyManager itself was not involved, but reviewing it turned up three things worth closing. Reading a configuration was the only file operation that did not validate the name. Saving, renaming and deleting all reject `..`, `/` and `\`, so the read path was the one way to leave the configuration directory and pull in any `*.caddy` file on the host. The HTTP API happened to be covered, because GET checks the name against the directory listing first, but the UI calls the service directly and nothing stopped it. Forwarded headers were trusted from any peer. That is correct only while the container port is unreachable except through the proxy; the moment it is published, a caller dictates the scheme, host and client address the app believes in. Loopback and private space cover a proxy on a Docker network or on the host, which is the documented deployment, and ignore everyone else. The README never said that the `X-Api-Key` check guards `/api/*` and nothing else, so the UI - which rewrites Caddyfiles and holds the Docker socket - reads as protected when it is not. It now says so, and warns about the specific shape that bit us: a second hostname added for machine callers whose only extra directive is a `tls` line, which serves the unauthenticated UI to anyone who can resolve it. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
This commit is contained in:
@@ -40,6 +40,13 @@ public class CaddyService(
|
||||
/// <inheritdoc />
|
||||
public string GetCaddyConfigurationContent(string configurationName)
|
||||
{
|
||||
// Reading went unguarded while saving, renaming and deleting all validated the name, so a
|
||||
// caller could walk out of the configuration directory and read any *.caddy file on disk
|
||||
if (configurationName != CaddyGlobalConfigName && IsInvalidFileName(configurationName))
|
||||
{
|
||||
return string.Empty;
|
||||
}
|
||||
|
||||
var path = configurationName == CaddyGlobalConfigName
|
||||
? Path.Combine(Configurations.ConfigDir, CaddyGlobalConfigName)
|
||||
: Path.Combine(Configurations.ConfigDir, $"{configurationName}.caddy");
|
||||
|
||||
Reference in New Issue
Block a user