fix(install): trust Docker bridge for dashboard metadata

## Summary

Closes #2909.

The `persistent-docker` installer now discovers Docker's default bridge
gateway and passes the exact `/32` gateway CIDR to the proxy's dashboard
metadata allowlist when no explicit
`HEADROOM_PROXY_TRUSTED_DASHBOARD_CLIENT_CIDRS` value is configured.
This keeps the existing metadata gate intact while allowing the
first-party loopback-published container to see its own Recent Requests
and Per-Project Savings data. Explicit user configuration continues to
take precedence.

Both native wrappers (POSIX and PowerShell) use the same behavior, and
installer integration coverage verifies the generated Docker command.

## Validation

- `python -m pytest tests/test_install/test_native_installers.py -q -k
bash` (1 skipped on Windows because Bash is unavailable)
- PowerShell wrapper smoke test with the repository fake Docker shim:
verified `docker network inspect bridge` is called and
`HEADROOM_PROXY_TRUSTED_DASHBOARD_CLIENT_CIDRS=172.17.0.1/32` is passed
to `docker run`
- Explicit allowlist smoke test: verified an existing
`HEADROOM_PROXY_TRUSTED_DASHBOARD_CLIENT_CIDRS` is preserved without
adding a discovered default
- `git diff --check`

## Real behavior proof

Setup tested: Windows 11 host, PowerShell wrapper, repository fake
Docker shim (Docker CLI is not installed in this environment).

Exact command: `headroom.ps1 install apply --profile smoke --port 18999
--image fake/headroom:test`.

Observed result: the generated Docker invocation included `docker
network inspect bridge --format ...` and `--env
HEADROOM_PROXY_TRUSTED_DASHBOARD_CLIENT_CIDRS=172.17.0.1/32`, and the
installer completed successfully.

Not tested: a live Docker daemon/dashboard request on this host.

---------

Co-authored-by: Tejas Chopra <tejas@Tejass-MacBook-Pro.local>
This commit is contained in:
Suliman Abdulrazzaq 2026-08-12 00:25:29 +03:00 committed by GitHub
parent c85abf7a87
commit e044139001
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
3 changed files with 170 additions and 3 deletions

View file

@ -346,6 +346,29 @@ function Get-PersistentDockerArgs {
return ,$args.ToArray()
}
function Add-DashboardGatewayEnv {
param([System.Collections.Generic.List[string]]$ArgsList)
# This default is safe only because the published dashboard port is bound
# to the host loopback interface below. A host request published through
# Docker's default bridge reaches the
# container from the bridge gateway (for example, 172.17.0.1), not from
# 127.0.0.1. Trust only that exact gateway by default so the dashboard's
# metadata gate works for the first-party persistent Docker preset while
# preserving an explicitly configured allowlist.
if (Test-Path Env:HEADROOM_PROXY_TRUSTED_DASHBOARD_CLIENT_CIDRS) {
return
}
$gateway = (& docker network inspect bridge --format '{{(index .IPAM.Config 0).Gateway}}' 2>$null | Out-String).Trim()
if ($LASTEXITCODE -eq 0 -and $gateway) {
$ArgsList.Add('--env')
$ArgsList.Add("HEADROOM_PROXY_TRUSTED_DASHBOARD_CLIENT_CIDRS=$gateway/32")
} else {
Write-Warning 'Could not determine Docker bridge gateway; dashboard metadata remains restricted'
}
}
function Get-ManifestProxyArgs {
param(
[int]$Port,
@ -492,8 +515,9 @@ function Start-PersistentDockerInstall {
docker rm -f $containerName | Out-Null 2>$null
$dockerArgs = New-Object System.Collections.Generic.List[string]
$dockerArgs.AddRange([string[]]@('run','-d','--restart','unless-stopped','--name',$containerName,'-p',"$Port`:$Port"))
$dockerArgs.AddRange([string[]]@('run','-d','--restart','unless-stopped','--name',$containerName,'-p',"127.0.0.1`:$Port`:$Port"))
$dockerArgs.AddRange((Get-PersistentDockerArgs))
Add-DashboardGatewayEnv -ArgsList $dockerArgs
$dockerArgs.AddRange([string[]]@(
'--env',"HEADROOM_DEPLOYMENT_PROFILE=$Profile",
'--env','HEADROOM_DEPLOYMENT_PRESET=persistent-docker',

View file

@ -292,6 +292,29 @@ append_persistent_container_args() {
append_passthrough_envs "$1"
}
append_dashboard_gateway_env() {
local -n ref=$1
# This default is safe only because the published dashboard port is bound
# to the host loopback interface below. A host request published through
# Docker's default bridge reaches the
# container from the bridge gateway (for example, 172.17.0.1), not from
# 127.0.0.1. Trust only that exact gateway by default so the dashboard's
# metadata gate works for the first-party persistent Docker preset while
# preserving an explicitly configured allowlist.
if [[ -n "${HEADROOM_PROXY_TRUSTED_DASHBOARD_CLIENT_CIDRS+x}" ]]; then
return
fi
local gateway
gateway="$(docker network inspect bridge --format '{{(index .IPAM.Config 0).Gateway}}' 2>/dev/null || true)"
if [[ -n "${gateway}" ]]; then
ref+=(--env "HEADROOM_PROXY_TRUSTED_DASHBOARD_CLIENT_CIDRS=${gateway}/32")
else
warn "Could not determine Docker bridge gateway; dashboard metadata remains restricted"
fi
}
build_manifest_proxy_args() {
local -n out_args=$1
local port="$2"
@ -466,8 +489,9 @@ start_persistent_docker_install() {
docker rm -f "${container_name}" >/dev/null 2>&1 || true
args=(docker run -d --restart unless-stopped --name "${container_name}" -p "${port}:${port}")
args=(docker run -d --restart unless-stopped --name "${container_name}" -p "127.0.0.1:${port}:${port}")
append_persistent_container_args args
append_dashboard_gateway_env args
args+=(
--env "HEADROOM_DEPLOYMENT_PROFILE=${profile}"
--env "HEADROOM_DEPLOYMENT_PRESET=persistent-docker"