Skip to content

fix(storage): enforce upstream host equality to harden against SSRF#227

Merged
notque merged 2 commits into
masterfrom
fix/storage-ssrf-host-validation
May 28, 2026
Merged

fix(storage): enforce upstream host equality to harden against SSRF#227
notque merged 2 commits into
masterfrom
fix/storage-ssrf-host-validation

Conversation

@notque
Copy link
Copy Markdown
Contributor

@notque notque commented May 27, 2026

Summary

  • Tighten the Prometheus storage client's outbound URL validation to enforce host equality with the configured upstream (and federate URL when distinct), making the SSRF safety property explicit to CodeQL go/request-forgery and to future maintainers.
  • Stop tracking build/coverprofile.out and add it to .gitignore — the Makefile rewrites it on every make check, polluting diffs and security-review hook output.

Why

CodeQL flagged httpClient.Do(req) in pkg/storage/prometheus.go as a request-forgery sink. The code was already structurally safe — mapURL() rewrites Scheme/Host/User to the trusted upstream before any request goes out — but isValidURL() only checked scheme + non-empty host, so the safety property was implicit. A future change to mapURL() could regress that invariant silently.

This PR replaces isValidURL with a method on *prometheusStorageClient that validates the post-rewrite URL host against promCli.url.Host and promCli.federateURL.Host. Anything else is rejected before the HTTP call. Defense-in-depth against future regressions, and the property is now provable to a static analyzer.

Changes

File Change
pkg/storage/prometheus.go Replace isValidURL with (*prometheusStorageClient).validateUpstreamURL. Use slices.Contains for the allow-list check (golangci-lint v2 modernize requirement).
pkg/storage/prometheus_test.go Add TestValidateUpstreamURL (8 table cases) + TestSendToPrometheusRejectsUntrustedHost integration test confirming the validator short-circuits before any HTTP call.
CHANGELOG.md New ### Security entry under ## [Unreleased].
.gitignore Ignore build/coverprofile.out.
build/coverprofile.out Removed from index (git rm --cached). Working copy preserved; future make check runs regenerate it.

Test plan

  • make generate && make check passes clean (golangci-lint: 0 issues, all 5 packages green, build succeeds)
  • TestValidateUpstreamURL covers: trusted upstream, trusted federate, foreign host rejected, localhost rejected, empty host rejected, unknown scheme rejected, ftp scheme rejected, malformed URL rejected
  • TestSendToPrometheusRejectsUntrustedHost confirms validation short-circuits before HTTP dispatch
  • Branch rebased onto current master (post PR chore(deps): bump prometheus/prometheus to v0.311.3 (security) #226 prometheus v0.311.3 bump); CHANGELOG keeps both Security entries
  • CodeQL re-scan in CI shows the go/request-forgery finding cleared

notque added 2 commits May 27, 2026 13:44
CodeQL go/request-forgery flags httpClient.Do(req) in sendToPrometheus()
because the taint tracker cannot prove that mapURL() rewrites Host/Scheme
to the configured upstream before the request leaves the process. The
prior isValidURL() only checked scheme + non-empty host.

Replace it with validateUpstreamURL(), a method on prometheusStorageClient
that additionally rejects any host other than promCli.url.Host or, when
distinct, promCli.federateURL.Host. The mapURL() rewrite still does the
real work; this validator turns the safety property into something both a
static analyzer and future maintainers can see explicitly.

Add table-driven tests covering: trusted upstream, trusted federate,
foreign host, localhost, empty host, unknown scheme, ftp scheme,
malformed URL — plus an integration test confirming sendToPrometheus
short-circuits before any HTTP call when given an untrusted host.

No behavior change for legitimate traffic. mapURL() is unmodified.
The Makefile (generated by go-makefile-maker) writes build/coverprofile.out
on every `make check` run, but the file was both tracked in git and missing
from .gitignore. Every test run polluted the diff with thousands of lines
of regenerated coverage data, and security-review hooks flagged it as
noise.

Add the path to .gitignore and remove the tracked copy from the index.
The working copy on disk is preserved and will be regenerated by future
test runs.
@github-actions
Copy link
Copy Markdown

Merging this branch will increase overall coverage

Impacted Packages Coverage Δ 🤖
github.com/SAP-cloud-infrastructure/maia/pkg/storage 76.25% (+3.35%) 👍

Coverage by file

Changed files (no unit tests)

Changed File Coverage Δ Total Covered Missed 🤖
github.com/SAP-cloud-infrastructure/maia/pkg/storage/prometheus.go 71.95% (+7.02%) 410 (+25) 295 (+45) 115 (-20) 👍

Please note that the "Total", "Covered", and "Missed" counts above refer to code statements instead of lines of code. The value in brackets refers to the test coverage of that file in the old version of the code.

Changed unit test files

  • github.com/SAP-cloud-infrastructure/maia/pkg/storage/prometheus_test.go

Copy link
Copy Markdown

@ibakshay ibakshay left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

LGTM!

@notque notque merged commit 5c9e27b into master May 28, 2026
7 checks passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants