Preserve final dynamic secret tags in inspection
This commit is contained in:
@@ -8,7 +8,7 @@ Comparison baseline: `0dd16aa8a935645ae2847d32edd86461dc4618f0`
|
|||||||
Scope: the completed [implementation plan](implementation.md) and
|
Scope: the completed [implementation plan](implementation.md) and
|
||||||
[interface remediation roadmap](interface-remediation.md). This report records
|
[interface remediation roadmap](interface-remediation.md). This report records
|
||||||
remaining defects and newly introduced scalability problems; it does not change
|
remaining defects and newly introduced scalability problems; it does not change
|
||||||
the selected architecture or public contracts. All eight findings below are open.
|
the selected architecture or public contracts. Completion is recorded per finding.
|
||||||
|
|
||||||
## Architecture And Review Evidence
|
## Architecture And Review Evidence
|
||||||
|
|
||||||
@@ -59,7 +59,12 @@ deferred contributor-trace and branch-local replay experiments remain separate.
|
|||||||
|
|
||||||
## 1. Final Dynamic Field Tags Can Be Lost From Redaction
|
## 1. Final Dynamic Field Tags Can Be Lost From Redaction
|
||||||
|
|
||||||
Priority: P1. Status: open.
|
Priority: P1. Status: complete.
|
||||||
|
|
||||||
|
Completion: final inventory/sensitivity is registered unconditionally before
|
||||||
|
freezing. `TestFinalDynamicTagRedactsEqualValuedResolution` failed before the fix
|
||||||
|
and passes afterward, protecting effective and historical public rendering.
|
||||||
|
Validation: the full ordinary suite and `git diff --check` passed.
|
||||||
|
|
||||||
Relevant code: `resolveLoadInput` in
|
Relevant code: `resolveLoadInput` in
|
||||||
[load_result.go](../../internal/engine/load_result.go), lines 56–73;
|
[load_result.go](../../internal/engine/load_result.go), lines 56–73;
|
||||||
|
|||||||
53
facade_dynamic_redaction_test.go
Normal file
53
facade_dynamic_redaction_test.go
Normal file
@@ -0,0 +1,53 @@
|
|||||||
|
package configkit_test
|
||||||
|
|
||||||
|
import (
|
||||||
|
"bytes"
|
||||||
|
"strings"
|
||||||
|
"testing"
|
||||||
|
|
||||||
|
configkit "gitea.maximumdirect.net/eric/ConfigKit"
|
||||||
|
)
|
||||||
|
|
||||||
|
func TestFinalDynamicTagRedactsEqualValuedResolution(t *testing.T) {
|
||||||
|
t.Parallel()
|
||||||
|
type classified struct {
|
||||||
|
Token string `yaml:"token" configkit:"secret"`
|
||||||
|
}
|
||||||
|
type configuration struct {
|
||||||
|
Value any `yaml:"value"`
|
||||||
|
}
|
||||||
|
const canary = "DYNAMIC-TAG-CANARY"
|
||||||
|
result, err := configkit.Load(configkit.Bytes("input", []byte("value: {token: "+canary+"}\n")), configkit.Options[configuration]{
|
||||||
|
Definition: configkit.Definition[configuration]{
|
||||||
|
Resolve: func(c *configuration, _ *configkit.Resolution) error {
|
||||||
|
// No canonical value changes, so no normalization record is needed.
|
||||||
|
c.Value = classified{Token: canary}
|
||||||
|
return nil
|
||||||
|
},
|
||||||
|
},
|
||||||
|
})
|
||||||
|
if err != nil {
|
||||||
|
t.Fatal(err)
|
||||||
|
}
|
||||||
|
var effective bytes.Buffer
|
||||||
|
if err := result.WriteEffective(&effective); err != nil {
|
||||||
|
t.Fatal(err)
|
||||||
|
}
|
||||||
|
if strings.Contains(effective.String(), canary) || !strings.Contains(effective.String(), "[REDACTED]") {
|
||||||
|
t.Fatal("effective rendering did not preserve the final dynamic tag")
|
||||||
|
}
|
||||||
|
explanation, found := result.Explain(configkit.Keys("value", "token"))
|
||||||
|
if !found || !explanation.Effective.Redacted || strings.Contains(explanation.Effective.YAML, canary) {
|
||||||
|
t.Fatal("effective explanation did not preserve the final dynamic tag")
|
||||||
|
}
|
||||||
|
if len(explanation.History) == 0 {
|
||||||
|
t.Fatal("source history is absent")
|
||||||
|
}
|
||||||
|
for _, change := range explanation.History {
|
||||||
|
for _, value := range []configkit.ValueSnapshot{change.Before, change.After} {
|
||||||
|
if value.Present && (!value.Redacted || strings.Contains(value.YAML, canary)) {
|
||||||
|
t.Fatal("historical explanation did not preserve the final dynamic tag")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -150,6 +150,9 @@ func freezeMetadataHistory(m *Metadata, final *document.Node) {
|
|||||||
panic("configkit: metadata history prefix is not ready")
|
panic("configkit: metadata history prefix is not ready")
|
||||||
}
|
}
|
||||||
builder.model.final = final
|
builder.model.final = final
|
||||||
|
// A resolver may change dynamic field tags without changing canonical values
|
||||||
|
// or producing a resolution snapshot. Always observe the final classification.
|
||||||
|
builder.observeDocument(final, historyPhaseResolution)
|
||||||
m.historyModel = builder.freeze()
|
m.historyModel = builder.freeze()
|
||||||
m.historyBuilder = nil
|
m.historyBuilder = nil
|
||||||
}
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user