[management,client] Normalize anonymize_level and sanity-check the bundle upload URL

Two review follow-ups.

The API validated anonymize_level after trimming and lowercasing but
persisted the value verbatim, so " default " passed as default yet reached
the client — which only lowercases — as an unrecognized value it resolves
to strict. Persist the normalized form so what was validated is what the
client parses.

The remote debug bundle job forwarded the management-supplied upload URL
to the uploader unchecked and logged it at info level, where it can leak a
host, credentials, or query tokens. Reject a malformed or non-https URL
before generating the bundle, and keep the URL out of the info-level line
while leaving the full parameters at debug. The accepted host is left
unrestricted for now, pending a decision on management-directed uploads.
This commit is contained in:
mlsmaycon
2026-08-11 02:16:22 +00:00
parent 725dc451ca
commit 5585ceec85
4 changed files with 92 additions and 3 deletions
+7 -2
View File
@@ -154,13 +154,18 @@ func validateAndBuildBundleParams(req api.WorkloadRequest, workload *Workload) e
}
// validate anonymize_level: omitted or empty defaults on the client;
// otherwise it must name a known level. An unknown value is rejected here
// rather than silently escalated, so a typo surfaces at job creation.
// rather than silently escalated, so a typo surfaces at job creation. The
// normalized (trimmed, lowercased) value is persisted so it matches what
// the client parses — the client only lowercases, so a stored " default "
// would otherwise resolve to strict.
if lvl := bundle.Parameters.AnonymizeLevel; lvl != nil {
switch strings.ToLower(strings.TrimSpace(*lvl)) {
normalized := strings.ToLower(strings.TrimSpace(*lvl))
switch normalized {
case "", anonymize.LevelDefaultString, anonymize.LevelStrictString:
default:
return fmt.Errorf("anonymize_level must be %q or %q, got %q", anonymize.LevelDefaultString, anonymize.LevelStrictString, *lvl)
}
bundle.Parameters.AnonymizeLevel = &normalized
}
workload.Parameters, err = json.Marshal(bundle.Parameters)
+19
View File
@@ -80,6 +80,7 @@ func TestNewJob_AnonymizeLevelValidation(t *testing.T) {
{name: "default", level: strPtr("default")},
{name: "strict", level: strPtr("strict")},
{name: "mixed case", level: strPtr("Strict")},
{name: "padded", level: strPtr(" default ")},
{name: "unknown", level: strPtr("verbose"), wantErr: true},
} {
t.Run(tc.name, func(t *testing.T) {
@@ -96,6 +97,24 @@ func TestNewJob_AnonymizeLevelValidation(t *testing.T) {
}
}
// TestNewJob_AnonymizeLevelNormalized verifies an accepted level is persisted
// trimmed and lowercased, so it reaches the client as a value the client's
// lowercase-only parser resolves correctly rather than escalating to strict.
func TestNewJob_AnonymizeLevelNormalized(t *testing.T) {
job, err := NewJob("user-1", "acc-1", "peer-1", newBundleJobRequest(t, api.BundleParameters{
BundleFor: false,
LogFileCount: 100,
Anonymize: true,
AnonymizeLevel: strPtr(" Default "),
}))
require.NoError(t, err, "a padded known level must be accepted")
req, err := job.ToStreamJobRequest()
require.NoError(t, err, "ToStreamJobRequest must succeed")
assert.Equal(t, "default", req.GetBundle().GetAnonymizeLevel(),
"the persisted level must be normalized so the client does not resolve it to strict")
}
// TestBuildStreamBundleResponse_OmittedFieldsMapToEmpty verifies that omitted
// optional fields map to the empty proto string, which the client resolves to
// its defaults (default anonymization level, default upload server).