Merge remote-tracking branch 'origin/main'

This commit is contained in:
pulse-triage[bot]
2026-09-04 02:34:06 +01:00
5 changed files with 138 additions and 1 deletions
@@ -10166,6 +10166,38 @@
"kind": "file"
}
]
},
{
"id": "telemetry-test-binary-production-pings",
"summary": "Go test binaries reported themselves to the production telemetry receiver as live installations. pkg/server tests boot the real server through Run() with the version literal \"test-version\", which internal/updates normalizes to 0.0.0-test-version, and each test runs against its own t.TempDir(), so every run minted a fresh install ID. The startup ping waits two minutes and never fired in a short test, but the service-health failure reporter added on 2026-08-29 sends synchronously from a deferred handler as soon as Run() returns an error, so every test run that exercised a startup failure posted one ping. The receiver recorded 317 single-ping installs between 2026-08-29 and 2026-09-03, 311 from linux/amd64 hosts (dominated by the autonomous maintainer fleet running ad-hoc go test, not GitHub Actions, which ran twice in the final 24h) and 3 from a maintainer workstation. The canonical clean denominator excludes single-ping installs and was unaffected, but raw install counts and the operator-evidence Patrol blocked-cause read counted them as real installations. Resolved by refusing production-endpoint sends from a test binary in internal/telemetry, opting the server tests out of telemetry, and putting the operator-evidence read on the production-ping basis. Note the receiver cannot filter these on version_is_development: the emitter sets that flag only for git build metadata or a prerelease of exactly dev or dev.*, so 0.0.0-test-version arrives with it clear and only version_is_published_release excludes it.",
"owner": "project-owner",
"status": "triaged",
"recorded_at": "2026-09-03",
"lane_ids": [
"L14"
],
"subsystem_ids": [
"security-privacy"
],
"proposed_resolution": "lane-expansion",
"coverage_impact": 3,
"evidence": [
{
"repo": "pulse",
"path": "internal/telemetry/telemetry.go",
"kind": "file"
},
{
"repo": "pulse",
"path": "pkg/server/server.go",
"kind": "file"
},
{
"repo": "pulse",
"path": "pkg/server/server_test.go",
"kind": "file"
}
]
}
],
"candidate_lanes": [
@@ -445,6 +445,15 @@ the `white_label` branding entitlement.
runs per event so runtime mock toggles take effect immediately — because a
mock-mode snapshot describes the synthetic fixture fleet rather than a real
installation.
A Go test binary is the same kind of suppression boundary: while
`testing.Testing()` reports true, `internal/telemetry` must not post to the
production receiver on any path, including the synchronous service-health
failure event `pkg/server.Run` sends from its deferred handler. A test that
boots the real server runs against a throwaway data directory, so it mints
a fresh install ID on every run and the receiver counts it as a distinct
live installation. The guard compares the resolved endpoint against
`productionPingEndpoint`, so tests that redirect `pingEndpoint` at a local
server keep asserting on real ping content.
Pulse Intelligence external-agent/MCP telemetry may expose only content-free
adapter-origin usage and capability-class counters for context, event
stream, provisioning, operator state, finding, and action requests. It must
+26 -1
View File
@@ -80,6 +80,14 @@
// While mock/demo fixture mode is enabled, outbound pings are suppressed
// entirely: a mock-mode boot (e2e, CI, qual runs, demo containers) would
// otherwise report the synthetic fixture fleet as a real installation.
//
// # Test binaries
//
// Pings to the production endpoint are suppressed inside a Go test binary for
// the same reason: a test that boots the real server is not an installation,
// and its throwaway data directory mints a new install ID on every run. Tests
// that need to assert on ping content redirect pingEndpoint to a local server,
// which the guard deliberately allows.
package telemetry
import (
@@ -96,6 +104,7 @@ import (
"runtime"
"strings"
"sync"
"testing"
"time"
"github.com/google/uuid"
@@ -104,12 +113,19 @@ import (
"github.com/rs/zerolog/log"
)
// productionPingEndpoint is the live receiver for outbound usage telemetry.
const productionPingEndpoint = "https://license.pulserelay.pro/v1/telemetry/ping"
// pingEndpoint is the URL that receives outbound usage telemetry pings.
// It is a var (not const) so that tests can redirect it to a local server.
var pingEndpoint = "https://license.pulserelay.pro/v1/telemetry/ping"
var pingEndpoint = productionPingEndpoint
var errInstallIDUnavailable = errors.New("telemetry install id unavailable")
// errProductionEndpointUnderTest reports a ping suppressed because a test
// binary tried to reach the live receiver.
var errProductionEndpointUnderTest = errors.New("telemetry: refusing to post to the production endpoint from a test binary")
const (
// heartbeatInterval is the base interval between daily pings.
// Each cycle adds random jitter of ±maxHeartbeatJitter to prevent
@@ -1731,6 +1747,15 @@ func buildPingAt(cfg Config, event string, now time.Time) (Ping, error) {
// send posts a ping to the telemetry endpoint. Errors are observable in debug
// logs but never affect normal Pulse operation.
func send(ctx context.Context, ping Ping) error {
// A test binary is not a real installation. Any test that boots the real
// server (pkg/server.Run and anything like it) runs against a throwaway
// data directory, so it mints a fresh install ID per run and would be
// counted as a distinct live install. Telemetry's own tests redirect
// pingEndpoint at a local server and are unaffected by this guard.
if testing.Testing() && pingEndpoint == productionPingEndpoint {
return errProductionEndpointUnderTest
}
body, err := json.Marshal(ping)
if err != nil {
return err
+59
View File
@@ -2,6 +2,7 @@ package telemetry
import (
"encoding/json"
"errors"
"fmt"
"io"
"io/fs"
@@ -1366,3 +1367,61 @@ func TestTelemetryPrivacyDocsDiscloseSetupChoiceAndPayloadChanges(t *testing.T)
}
}
}
// The production telemetry receiver must be unreachable from a Go test binary.
// A test that boots the real server runs against a throwaway data directory,
// so it mints a fresh install ID on every run and lands at the receiver as a
// distinct live installation. Regression guard for the 317 single-ping
// 0.0.0-test-version installs that pkg/server tests reported between
// 2026-08-29 and 2026-09-03.
func TestSendRefusesProductionEndpointUnderTest(t *testing.T) {
if pingEndpoint != productionPingEndpoint {
t.Fatalf("pingEndpoint = %q, want the production endpoint by default", pingEndpoint)
}
err := send(context.Background(), Ping{Event: "startup"})
if !errors.Is(err, errProductionEndpointUnderTest) {
t.Fatalf("send() to the production endpoint = %v, want errProductionEndpointUnderTest", err)
}
}
// SendServiceHealthEvent is the path pkg/server.Run takes when startup fails,
// and it sends synchronously rather than after the two-minute startup delay,
// which is why the failing server tests reported and the passing ones did not.
func TestSendServiceHealthEventRefusesProductionEndpointUnderTest(t *testing.T) {
cfg := Config{
Version: "test-version",
DataDir: t.TempDir(),
Enabled: true,
}
err := SendServiceHealthEvent(context.Background(), cfg, "startup", ServiceHealthObservation{
Observed: true,
FailureCategory: ServiceHealthFailureListener,
})
if !errors.Is(err, errProductionEndpointUnderTest) {
t.Fatalf("SendServiceHealthEvent() = %v, want errProductionEndpointUnderTest", err)
}
}
// A redirected endpoint is how telemetry's own tests assert on ping content,
// so the guard must not block it.
func TestSendAllowsRedirectedEndpointUnderTest(t *testing.T) {
var received atomic.Int32
ts := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
received.Add(1)
w.WriteHeader(http.StatusOK)
}))
defer ts.Close()
origEndpoint := pingEndpoint
pingEndpoint = ts.URL
defer func() { pingEndpoint = origEndpoint }()
if err := send(context.Background(), Ping{Event: "startup"}); err != nil {
t.Fatalf("send() to a redirected endpoint: %v", err)
}
if got := received.Load(); got != 1 {
t.Fatalf("redirected endpoint received %d pings, want 1", got)
}
}
+12
View File
@@ -294,6 +294,9 @@ func waitForHTTPStatus(t *testing.T, url string, want int) {
func TestServerRun_Shutdown(t *testing.T) {
// Setup minimal environment
tmpDir := t.TempDir()
// Run() reports startup failures over outbound telemetry; a test boot is
// not an installation, so opt this process out at the config layer too.
t.Setenv("PULSE_TELEMETRY", "false")
t.Setenv("PULSE_DATA_DIR", tmpDir)
t.Setenv("PULSE_CONFIG_PATH", tmpDir)
t.Setenv("BIND_ADDRESS", "127.0.0.1")
@@ -325,6 +328,9 @@ func TestServerRun_Shutdown(t *testing.T) {
}
func TestServerRunFailsFastWhenFrontendPortIsAlreadyBound(t *testing.T) {
// Run() reports startup failures over outbound telemetry; a test boot is
// not an installation, so opt this process out at the config layer too.
t.Setenv("PULSE_TELEMETRY", "false")
listener, err := net.Listen("tcp", "127.0.0.1:0")
if err != nil {
t.Fatalf("listen: %v", err)
@@ -347,6 +353,9 @@ func TestServerRunFailsFastWhenFrontendPortIsAlreadyBound(t *testing.T) {
}
func TestServerRunKeepsFrontendWhenMetricsPortConflicts(t *testing.T) {
// Run() reports startup failures over outbound telemetry; a test boot is
// not an installation, so opt this process out at the config layer too.
t.Setenv("PULSE_TELEMETRY", "false")
port := availableTCPPort(t)
tmpDir := t.TempDir()
@@ -381,6 +390,9 @@ func TestServerRunKeepsFrontendWhenMetricsPortConflicts(t *testing.T) {
func TestServerRun_RejectsWildcardTrustedProxyCIDR(t *testing.T) {
tmpDir := t.TempDir()
// Run() reports startup failures over outbound telemetry; a test boot is
// not an installation, so opt this process out at the config layer too.
t.Setenv("PULSE_TELEMETRY", "false")
t.Setenv("PULSE_DATA_DIR", tmpDir)
t.Setenv("PULSE_CONFIG_PATH", tmpDir)
t.Setenv("PULSE_TRUSTED_PROXY_CIDRS", "0.0.0.0/0")