From 9f074ac15bec0e089067c46b09d4d5119651acc1 Mon Sep 17 00:00:00 2001 From: Kristoffer Dalby Date: Wed, 14 Oct 2026 19:27:56 +0000 Subject: [PATCH] policy/v2: keep comments in a policy set through the v2 API hujson.Parse aliases its input, so Standardize blanked the comments in the caller's buffer, which api/v2 then stored. --- hscontrol/apiv2_acl_test.go | 13 +++++-------- hscontrol/policy/v2/types.go | 5 ++++- 2 files changed, 9 insertions(+), 9 deletions(-) diff --git a/hscontrol/apiv2_acl_test.go b/hscontrol/apiv2_acl_test.go index def9af82..8f21ed52 100644 --- a/hscontrol/apiv2_acl_test.go +++ b/hscontrol/apiv2_acl_test.go @@ -113,21 +113,18 @@ func TestAPIv2ACLSetHuJSONWithComments(t *testing.T) { api := registerAPIV2(t, app) // Line comment + trailing comma: valid HuJSON, invalid strict JSON. The - // server accepts it (the SDK sends the policy file this way) and standardizes - // it on store; the ACL content survives even though comments are blanked. + // server accepts it (the SDK sends the policy file this way) and stores it + // as written, so a client re-reading it sees no diff. policy := "{\n // allow all\n \"acls\": [{\"action\":\"accept\",\"src\":[\"*\"],\"dst\":[\"*:*\"]}],\n}" set := setACL(t, api, policy, "application/hujson") require.Equalf(t, http.StatusOK, set.Code, "body: %s", set.Body) - - // Server-side: stored, parseable, ACL intact. - stored := storedPolicy(t, app) - assert.Contains(t, stored, `"action":"accept"`) + assert.Equal(t, policy, storedPolicy(t, app)) // GET round-trips the stored form and its content-addressed ETag. raw := api.Get("/api/v2/tailnet/-/acl", "Accept: application/hujson") - assert.Equal(t, stored, raw.Body.String()) - assert.Equal(t, policyETagOf(stored), raw.Header().Get("Etag")) + assert.Equal(t, policy, raw.Body.String()) + assert.Equal(t, policyETagOf(policy), raw.Header().Get("Etag")) } func TestAPIv2ACLETagChangesOnChangeStableOnNoop(t *testing.T) { diff --git a/hscontrol/policy/v2/types.go b/hscontrol/policy/v2/types.go index 4844b072..0235a72e 100644 --- a/hscontrol/policy/v2/types.go +++ b/hscontrol/policy/v2/types.go @@ -1,6 +1,7 @@ package v2 import ( + "bytes" "errors" "fmt" "iter" @@ -3129,7 +3130,9 @@ func unmarshalPolicy(b []byte) (*Policy, error) { var policy Policy - ast, err := hujson.Parse(b) + // The AST aliases its input and Standardize blanks comments in place; + // callers store b as written. + ast, err := hujson.Parse(bytes.Clone(b)) if err != nil { return nil, fmt.Errorf("parsing HuJSON: %w", err) }