#2502·go2rtc

Credentials in source query strings are not masked in /api/streams or the log

Author: ajplotkinCreated Sep 15, 2026Updated Sep 15, 2026

#1744 masks values that come from env:/${VAR}/CREDENTIALS_DIRECTORY, and #2051 strips ://user:pass@ userinfo from /api/streams and log output. Neither covers a credential written literally in a query string, which is how several sources carry them:

  • nest:?client_id=&client_secret=&refresh_token=
  • ring:?refresh_token= / password=
  • tuya: client_secret= / password=
  • hass: token=, webtorrent: pwd=, roborock: key=, xiaomi legacy password=

Connection.URL is populated from the raw source string per backend (pkg/nest/client.go:120, pkg/rtsp/client.go:78, internal/http/http.go:57, internal/exec/exec.go:276) and marshalled unfiltered (pkg/webrtc/conn.go:150, pkg/rtsp/producer.go:112). creds.SecretResponse wraps the writer at internal/streams/api.go:14, but the replacer only knows values that were registered, and the userinfo regexp does not match ?key=value. The same string reaches the log through log.Warn().Err(err).Str("url", p.url) at internal/streams/producer.go:170 on every Start() error at the default info level, and at Debug/Trace on lines 152, 181, 185 and 260.

On my instance (1.9.14 base, Nest configured with literal values) each of four connected streams shows .producers[0].url of length 431 beginning nest:?client.

Frigate users are the common case, since Frigate generates the go2rtc config with literal values, and a Nest refresh_token plus client_secret stays valid until revoked from the Google account's third-party access page. /api/config already returns the whole file, so this is not new exposure to someone with API access. The cost is that /api/streams output and logs are what people paste into issues here. In #2351 a contributor suppressed his fatal-error log to keep the URL out of the line 170 warning and then had no timestamp for the event he was testing.

Proposed fix, in pkg/creds/secrets.go next to userinfoRegexp:

([?&](client_secret|refresh_token|access_token|token|password|pass|pwd|api_key|key)=)[^&#\s"']+  ->  $1***

applied in SecretString/SecretWrite, so /api/streams, /api/streams.dot, ws errors and the log writer all pick it up with no per-site changes. client_id, project_id, device_id, host and path stay visible for diagnosis. Alternatively each handler can creds.AddSecret() the values it parses in Dial, reusing the existing replacer. Happy to send either as a PR.