Skip to content

[BUG] Portainer AllowInsecureHttp logs no runtime warning while plaintext HTTP is in use #276

Closed

Description

@LarsLaskowski

Summary

DockerUpdateGuard:DockerInstances:<n>:Portainer:AllowInsecureHttp: true lets the Portainer integration send the API token, or username/password and the returned JWT, over plaintext http. It is only a validation switch today: nothing is logged at runtime while it is active. .squad/project.md requires the transport relaxations SkipCertificateValidation and AllowInsecureHttp to "stay opt-in and keep their runtime warnings", and SkipCertificateValidation already logs one. AllowInsecureHttp needs the same kind of warning, once per Docker instance.

Area

  • docs/areas/container-actions.md (Transport security)
  • Security area (.squad/project.md): TLS and transport relaxations

Current behavior

  • Validation accepts an http Portainer BaseUrl only with AllowInsecureHttp: true (src/DockerUpdateGuard/Configuration/DockerUpdateGuardOptionsValidator.cs:337-340). There is no other use of the flag at runtime: PortainerClient.CreateAuthenticatedClientAsync (src/DockerUpdateGuard/Portainer/PortainerClient.cs:134-187) sets BaseAddress to the http URL and sends X-API-Key or posts the credentials to /api/auth without a log entry.
  • For comparison, SkipCertificateValidation logs a warning once per instance name per process lifetime: src/DockerUpdateGuard/Docker/DockerInstanceClient.cs:287-292 with _certificateValidationWarningInstances (line 34) and DockerInstanceCertificateValidationDisabled (EventId 3106, src/DockerUpdateGuard/Docker/DockerInstanceClientLogging.cs:90-93).
  • docs/areas/container-actions.md:32 states: "It is a validation switch: no runtime warning is logged while it is active."

Scenario: an operator sets BaseUrl: http://portainer:9000 plus AllowInsecureHttp: true for a quick test and forgets it. Nothing in the logs reveals that Portainer credentials have been sent unencrypted ever since.

Expected behavior

  • When a Portainer request is about to be made for a Docker instance whose Portainer BaseUrl uses the http scheme (which validation only allows with AllowInsecureHttp: true), a warning is logged once per Docker instance name per process lifetime, e.g. "Portainer for Docker instance {DockerInstanceName} uses plaintext HTTP (AllowInsecureHttp); API token, credentials and session token are transmitted unencrypted".
  • The warning names the Docker instance (and may name the Portainer base URL), never the token, username, password or JWT.
  • The warning is logged regardless of whether the call is a capability check or an action, and regardless of its outcome.
  • AllowInsecureHttp stays opt-in, default false; https URLs produce no warning, also when AllowInsecureHttp is true but unused.

Acceptance criteria

  • Two capability checks / actions for the same instance with an http Portainer URL log exactly one warning.
  • Two different instances with http Portainer URLs log one warning each.
  • An https Portainer URL with AllowInsecureHttp: true logs no warning.
  • The warning text contains no secret (asserted in the test with a known token/password value).
  • Unit tests (MSTest, naming per docs/UNIT_TESTS.md) in PortainerClientTests, e.g. PortainerClientGetCapabilityWithInsecureHttpLogsWarningOncePerInstance, PortainerClientExecuteActionWithHttpsDoesNotLogInsecureWarning; follow the pattern of DockerInstanceClientCreateHttpClientWhenSkipCertificateValidationLogsWarningOncePerInstance (src/Tests/DockerUpdateGuard.Tests/DockerInstanceClientTests.cs:652).
  • README (README.md:187, AllowInsecureHttp row) says that a warning is logged at runtime while it is active, like the SkipCertificateValidation row.
  • The area document docs/areas/container-actions.md describes the new behavior and its Known gaps entry for this issue (currently Open questions item 4) is removed; docs/ARCHITECTURE.md, Security posture, stays consistent.

Implementation notes

  • PortainerClient is registered as a singleton (src/DockerUpdateGuard/ServiceCollectionExtensions.cs:104), so an instance-level ConcurrentDictionary<string, byte> (or a static one like DockerInstanceClient's) keyed by instance name with TryAdd is sufficient. Prefer an instance field over a static so tests stay isolated, unless the Lead prefers symmetry with DockerInstanceClient.
  • Add a source-generated LoggerMessage to src/DockerUpdateGuard/Portainer/PortainerClientLogging.cs with the next free EventId in the 33xx range.
  • Decide on the scheme check from the parsed BaseUrl (Uri.Scheme == Uri.UriSchemeHttp), not from the flag alone.
  • Optional (Lead's call): also log the warning once at startup for every enabled instance with an http Portainer URL, so it is visible even if Portainer is never contacted. If chosen, still keep it to once per instance.
  • No configuration or migration impact. Must not change: validation of http without AllowInsecureHttp still fails startup; secrets never logged (.squad/project.md).

Related


Generated by Claude Code

Activity

  1. LarsLaskowski commented on Oct 24, 2026

    @LarsLaskowski
    OwnerAuthor

    Squad working record

    Tier standard. The plan is specs/issue-276/plan.md at https://github.com/LarsLaskowski/DockerUpdateGuard/blob/edbd34bef9077330b456fe5eea3d6e9905a8fce7/specs/issue-276/plan.md

    Summary: A LoggerMessage (EventId 3311, Warning) is added to PortainerClient. It is logged once per Docker instance name per process, before the first Portainer request, when the parsed BaseUrl scheme is http. It names only the instance. Decision record 0006 weighs the first-request warning against a startup warning, both, and every request. The plan challenge raised one minor objection (the area document must say the warning appears only once a caller of PortainerClient exists, #280), and Security approved the plan. Acceptance criteria AC1–AC5 are pinned by tests in PortainerClientTests. Pull request: #312.

    log.md
    Date Step Member Result
    2026-10-24 14:45 1 Intake Orchestrator bug: label bug, user request 'Squad issue'; open; tier by Lead
    2026-10-24 14:49 2 Plan Lead DONE: tier standard, areas touched (TLS relaxations, container actions, secrets, logging); record 0006 Proposed (opus/medium · 106,159 tokens · 25 tool uses · 201 s)
    2026-10-24 14:50 2 Plan challenge Devil's Advocate 0 major, 1 minor (area doc wording); appended as Challenge notes (opus/medium · 46,813 tokens · 6 tool uses · 43 s)
    2026-10-24 14:50 3 Plan security review Security APPROVED, no findings (opus/medium · 44,102 tokens · 5 tool uses · 58 s)
    2026-10-24 14:50 4 Skeleton Orchestrator skipped: tests use only existing public API and filter EventId 3311 by number; the changed signature is private
    2026-10-24 14:53 5 Tests first Tester 5 tests; AC1/2/4/5 fail on current code (confirmed), AC3 pin passes; no deferred (sonnet/medium · 96,707 tokens · 13 tool uses · 167 s)
    2026-10-24 14:56 6 Implement Dev fix + docs done; build/test/coverage/analyzer green per report (sonnet/medium · 84,906 tokens · 10 tool uses · 172 s)
    2026-10-24 14:58 7 Code check Orchestrator format, build, test 587+21, coverage 100% new/84.9% overall, analyzer 0, scope, decision PASS; main up to date; Sonar gate NOT CHECKED (no SONAR_TOKEN), left to step 11
    2026-10-24 14:59 8 Review Reviewer round 1 APPROVE on de061ce, no findings (opus/medium · 54,428 tokens · 9 tool uses · 50 s)
    2026-10-24 14:59 9 PR approval Orchestrator approved by checklist; record 0006 Accepted
    2026-10-24 15:00 10 Pull request Orchestrator working record posted; draft PR 312 opened and watched
    2026-10-24 15:00 12 Wrap-up Orchestrator no lessons

    Squad run metrics

    Member Model/effort Launches Tokens Tool uses Seconds
    Dev sonnet/medium 1 84,906 10 172
    Devil's Advocate opus/medium 1 46,813 6 43
    Lead opus/medium 1 106,159 25 201
    Reviewer opus/medium 1 54,428 9 50
    Security opus/medium 1 44,102 5 58
    Tester sonnet/medium 1 96,707 13 167
    Total 6 433,115 68 691
    Member Wall time (s) Not covered by launches (s) Gate runs
    Orchestrator 900 252 1

    Generated by Claude Code

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

No one assigned

    Labels

    bugSomething isn't workingsecuritySecurity issue

    Projects

    No projects

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions