Decode native diff paths without revalidation

This commit is contained in:
2026-11-07 16:00:28 +00:00
parent c4b5bcdbc2
commit e950f61c7f
4 changed files with 23 additions and 41 deletions

View File

@@ -1,6 +1,6 @@
# Consolidated audit catalog # Consolidated audit catalog
Status: **4 distinct open findings: 4 P3**, consolidated from 30 Status: **3 distinct open findings: 3 P3**, consolidated from 30
numbered entries in seven audit documents. Reviewed audit state: numbered entries in seven audit documents. Reviewed audit state:
`db732e3a1de55c844f77870e1c6db683c2685d5d`. `db732e3a1de55c844f77870e1c6db683c2685d5d`.
@@ -507,6 +507,9 @@ construction becomes linear.
## F23 — P3: Concise diff rendering validates already-valid native paths ## F23 — P3: Concise diff rendering validates already-valid native paths
**Complete.** The private decoder accepts ConfigKit's native path and performs
single-pass token decoding. Focused diff, token and snapshot tests pass.
ckconfig's private pointer decoder validates arbitrary strings even though its ckconfig's private pointer decoder validates arbitrary strings even though its
sole production caller supplies the canonical string of an immutable ConfigKit sole production caller supplies the canonical string of an immutable ConfigKit
`Path`. Tests sustain malformed-input branches that this boundary cannot reach. `Path`. Tests sustain malformed-input branches that this boundary cannot reach.

View File

@@ -1,6 +1,6 @@
# Audit remediation implementation plan # Audit remediation implementation plan
Status: in progress; Stages 1–20 complete. Status: in progress; Stages 1–21 complete.
Baseline: `d492c7747175c01a53261206c2b4356e53453621`. Before this sprint, Baseline: `d492c7747175c01a53261206c2b4356e53453621`. Before this sprint,
intervening changes recorded decisions and retired the completed older intervening changes recorded decisions and retired the completed older
@@ -662,6 +662,11 @@ record these outcomes separately.
## Stage 21 — Decode native diff paths without revalidation ## Stage 21 — Decode native diff paths without revalidation
**Complete.** Concise diff adaptation decodes tokens from native ConfigKit paths
without revalidating arbitrary strings. Focused diff, pointer and snapshot tests
pass offline in the aligned Go 1.27.0 workspace with `-mod=readonly`, including
root, empty, Unicode and escaped keys.
Findings: F23. Source: ckconfig `diff_report.go` and diff report tests. Findings: F23. Source: ckconfig `diff_report.go` and diff report tests.
1. Change the private token helper to accept configkit.Path. Split its canonical 1. Change the private token helper to accept configkit.Path. Split its canonical

View File

@@ -8,39 +8,17 @@ import (
configkit "go.maximumdirect.net/configkit" configkit "go.maximumdirect.net/configkit"
) )
func decodeJSONPointer(pointer string) ([]string, error) { func decodeJSONPointer(path configkit.Path) []string {
pointer := path.String()
if pointer == "" { if pointer == "" {
return nil, nil return nil
} }
if pointer[0] != '/' {
return nil, fmt.Errorf("decode JSON pointer %q: path must start with /", pointer)
}
parts := strings.Split(pointer[1:], "/") parts := strings.Split(pointer[1:], "/")
decoder := strings.NewReplacer("~1", "/", "~0", "~")
for index, part := range parts { for index, part := range parts {
var decoded strings.Builder parts[index] = decoder.Replace(part)
decoded.Grow(len(part))
for offset := 0; offset < len(part); offset++ {
if part[offset] != '~' {
decoded.WriteByte(part[offset])
continue
}
if offset+1 >= len(part) {
return nil, fmt.Errorf("decode JSON pointer %q: incomplete escape in token %d", pointer, index)
}
offset++
switch part[offset] {
case '0':
decoded.WriteByte('~')
case '1':
decoded.WriteByte('/')
default:
return nil, fmt.Errorf("decode JSON pointer %q: invalid escape in token %d", pointer, index)
}
}
parts[index] = decoded.String()
} }
return parts, nil return parts
} }
func placeReportValue(root any, path []string, value any) (any, error) { func placeReportValue(root any, path []string, value any) (any, error) {
@@ -135,10 +113,7 @@ func renderDiffReport(differences []configkit.Difference) (any, error) {
if !difference.Right.Present { if !difference.Right.Present {
continue continue
} }
tokens, err := decodeJSONPointer(path) tokens := decodeJSONPointer(difference.Path)
if err != nil {
return nil, fmt.Errorf("difference at %q: %w", path, err)
}
value, err := commandkit.DecodeYAMLReport(strings.NewReader(difference.Right.YAML)) value, err := commandkit.DecodeYAMLReport(strings.NewReader(difference.Right.YAML))
if err != nil { if err != nil {
return nil, fmt.Errorf("difference at %q: %w", path, err) return nil, fmt.Errorf("difference at %q: %w", path, err)

View File

@@ -3,6 +3,8 @@ package ckconfig
import ( import (
"reflect" "reflect"
"testing" "testing"
configkit "go.maximumdirect.net/configkit"
) )
func TestDecodeJSONPointerPreservesSemanticTokens(t *testing.T) { func TestDecodeJSONPointerPreservesSemanticTokens(t *testing.T) {
@@ -16,21 +18,18 @@ func TestDecodeJSONPointerPreservesSemanticTokens(t *testing.T) {
{pointer: "/0/../a.b/50%", want: []string{"0", "..", "a.b", "50%"}}, {pointer: "/0/../a.b/50%", want: []string{"0", "..", "a.b", "50%"}},
{pointer: "/a~1b/c~0d", want: []string{"a/b", "c~d"}}, {pointer: "/a~1b/c~0d", want: []string{"a/b", "c~d"}},
{pointer: "/~01", want: []string{"~1"}}, {pointer: "/~01", want: []string{"~1"}},
{pointer: "/café", want: []string{"café"}},
} }
for _, test := range tests { for _, test := range tests {
got, err := decodeJSONPointer(test.pointer) path, err := configkit.ParsePath(test.pointer)
if err != nil { if err != nil {
t.Fatalf("decode %q: %v", test.pointer, err) t.Fatal(err)
} }
got := decodeJSONPointer(path)
if !reflect.DeepEqual(got, test.want) { if !reflect.DeepEqual(got, test.want) {
t.Errorf("decode %q = %#v, want %#v", test.pointer, got, test.want) t.Errorf("decode %q = %#v, want %#v", test.pointer, got, test.want)
} }
} }
for _, pointer := range []string{"missing-leading-slash", "/bad~", "/bad~2escape"} {
if _, err := decodeJSONPointer(pointer); err == nil {
t.Errorf("decode %q unexpectedly succeeded", pointer)
}
}
} }
func TestPlaceReportValue(t *testing.T) { func TestPlaceReportValue(t *testing.T) {