Reject validator changes to secret classification
This commit is contained in:
@@ -243,7 +243,9 @@ public-name representation, JSON marshals only the name, and projection, metadat
|
|||||||
histories, diagnostics, effective output,
|
histories, diagnostics, effective output,
|
||||||
and resolution snapshots never retain the credential. Validation remains read-only:
|
and resolution snapshots never retain the credential. Validation remains read-only:
|
||||||
in addition to ordinary projected-value comparison, ConfigKit checks the private
|
in addition to ordinary projected-value comparison, ConfigKit checks the private
|
||||||
resolved state against this load's lookup result. Applications must keep their
|
resolved state against this load's lookup result. Validator mutation checks also
|
||||||
|
reject changes to projected field sensitivity, even when a replacement dynamic
|
||||||
|
concrete type preserves the same textual values. Applications must keep their
|
||||||
validation messages value-free and must not include `Value()` in diagnostics.
|
validation messages value-free and must not include `Value()` in diagnostics.
|
||||||
|
|
||||||
For example, a typed pipeline field is declared as:
|
For example, a typed pipeline field is declared as:
|
||||||
|
|||||||
@@ -2,18 +2,18 @@
|
|||||||
|
|
||||||
## Status and scope
|
## Status and scope
|
||||||
|
|
||||||
Status: in progress. Review baseline: `f42ecc70c330a399aa6dada87658694402a515ec`.
|
Status: complete. All nine findings have been addressed. Review baseline: `f42ecc70c330a399aa6dada87658694402a515ec`.
|
||||||
This review covers the initial implementation against the
|
This review covers the initial implementation against the
|
||||||
[architecture policy](../policy/architecture.md), [configuration contract](../config.md),
|
[architecture policy](../policy/architecture.md), [configuration contract](../config.md),
|
||||||
and [implementation plan](implementation.md). The package boundaries and principal
|
and [implementation plan](implementation.md). The package boundaries and principal
|
||||||
lifecycle are in place, but the findings below must be addressed before claiming
|
lifecycle were in place at review time; the original findings below are retained
|
||||||
that the initial target is fully satisfied. No production code was changed during
|
with completion evidence. No production code was changed during the original
|
||||||
this review. All findings below were reproduced through the public API or local
|
review; subsequent remediation is recorded here. All findings below were reproduced through the public API or local
|
||||||
validation tools, with the additional affected call sites identified by inspection.
|
validation tools, with the additional affected call sites identified by inspection.
|
||||||
|
|
||||||
Priorities: P1 means high-impact confidentiality, isolation, or availability;
|
Priorities: P1 means high-impact confidentiality, isolation, or availability;
|
||||||
P2 means a correctness or validation gap that also requires remediation.
|
P2 means a correctness or validation gap that also requires remediation.
|
||||||
Each item remains open until its regression checks and the relevant baseline pass.
|
Each item below records its completed correction and validation evidence.
|
||||||
Keep current contracts and implementation evidence accurate when fixing these issues;
|
Keep current contracts and implementation evidence accurate when fixing these issues;
|
||||||
do not change the agreed semantics simply to accommodate the implementation.
|
do not change the agreed semantics simply to accommodate the implementation.
|
||||||
|
|
||||||
@@ -21,7 +21,10 @@ do not change the agreed semantics simply to accommodate the implementation.
|
|||||||
|
|
||||||
Status: complete. Typed projections now retain concrete field sensitivity; final,
|
Status: complete. Typed projections now retain concrete field sensitivity; final,
|
||||||
baseline and pre-resolution projections feed historical/dependency classification.
|
baseline and pre-resolution projections feed historical/dependency classification.
|
||||||
Dynamic nested, replaced, deleted and introduced-value regressions pass. Full tests
|
Dynamic nested, replaced, deleted and introduced-value regressions pass. A final
|
||||||
|
audit additionally verified that validators cannot change dynamic field sensitivity
|
||||||
|
through equal-text type replacement; the added regression failed before the fix
|
||||||
|
and now passes. Full tests
|
||||||
pass with the temporary canonical-TMPDIR workaround for issue 8; vet, build and
|
pass with the temporary canonical-TMPDIR workaround for issue 8; vet, build and
|
||||||
whitespace checks pass.
|
whitespace checks pass.
|
||||||
|
|
||||||
@@ -285,3 +288,24 @@ precedence, separate explicit bootstrap, and application-owned domain lifecycle
|
|||||||
remain appropriate. Repair these boundary and consistency defects within that
|
remain appropriate. Repair these boundary and consistency defects within that
|
||||||
architecture. Re-run focused regressions, the normal baseline and race suites,
|
architecture. Re-run focused regressions, the normal baseline and race suites,
|
||||||
local documentation checks, and applicable release gates before closing this roadmap.
|
local documentation checks, and applicable release gates before closing this roadmap.
|
||||||
|
|
||||||
|
## Remediation completion
|
||||||
|
|
||||||
|
All nine findings are corrected and committed in individual units of work, with an
|
||||||
|
additional issue-1 audit correction for validator sensitivity mutation. The normal
|
||||||
|
macOS baseline no longer requires a TMPDIR workaround. The historical review and
|
||||||
|
intermediate validation notes above retain the conditions that applied at the time.
|
||||||
|
|
||||||
|
The complete source checker is validated locally with:
|
||||||
|
|
||||||
|
```sh
|
||||||
|
CONFIGKIT_BASELINE_TOOLCHAIN=go1.25.5 CONFIGKIT_NEWER_TOOLCHAIN=go1.26.0 /bin/bash scripts/release-check.sh
|
||||||
|
```
|
||||||
|
|
||||||
|
This uses the available cached toolchains, runs both native baselines and the race
|
||||||
|
suite, checks module hygiene, formatting, documentation and example boundaries,
|
||||||
|
and cross-builds Linux/macOS on amd64/arm64. Native execution during remediation
|
||||||
|
was macOS/arm64; new native Linux evidence and selection of current supported patch
|
||||||
|
releases remain maintainer release gates. No release, tag, push or downstream
|
||||||
|
application migration was performed. Retire this completed roadmap during release
|
||||||
|
preparation according to the documentation policy.
|
||||||
|
|||||||
@@ -226,3 +226,25 @@ func TestDynamicConcreteTagsRemainSecretThroughHistory(t *testing.T) {
|
|||||||
})
|
})
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func TestValidatorCannotChangeDynamicSensitivityWithoutChangingText(t *testing.T) {
|
||||||
|
type sensitive struct {
|
||||||
|
Token string `yaml:"token" configkit:"secret"`
|
||||||
|
}
|
||||||
|
type cfg struct {
|
||||||
|
Payload any `yaml:"payload"`
|
||||||
|
}
|
||||||
|
_, err := Load(Bytes("base", []byte("payload: {token: disposable}\n")), Options[cfg]{Definition: Definition[cfg]{Validate: func(c *cfg, v *Validation) {
|
||||||
|
c.Payload = sensitive{Token: c.Payload.(map[string]any)["token"].(string)}
|
||||||
|
}}})
|
||||||
|
var failure *LoadError
|
||||||
|
if !errors.As(err, &failure) {
|
||||||
|
t.Fatal("validator changed inspection sensitivity without a mutation error")
|
||||||
|
}
|
||||||
|
for _, d := range failure.Diagnostics() {
|
||||||
|
if d.Code == CodeValidateMutation {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
}
|
||||||
|
t.Fatal("missing validation mutation diagnostic")
|
||||||
|
}
|
||||||
|
|||||||
23
secret.go
23
secret.go
@@ -139,3 +139,26 @@ func schemaSecretRoot(schema *schemaDescriptor, path Path) (Path, bool) {
|
|||||||
}
|
}
|
||||||
return Path{}, false
|
return Path{}, false
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Dynamic type replacement can change classification even when projected text
|
||||||
|
// is equal. Validators must preserve this inspection metadata as well as values.
|
||||||
|
func projectedSensitivity(root *document.Node) map[string]Path {
|
||||||
|
result := make(map[string]Path)
|
||||||
|
var visit func(*document.Node, Path)
|
||||||
|
visit = func(node *document.Node, path Path) {
|
||||||
|
if node == nil {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
if node.Sensitive {
|
||||||
|
result[path.String()] = path.clone()
|
||||||
|
}
|
||||||
|
for _, entry := range node.Mapping {
|
||||||
|
visit(entry.Value, path.Key(entry.Key))
|
||||||
|
}
|
||||||
|
for i, child := range node.Sequence {
|
||||||
|
visit(child, path.Index(i))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
visit(root, Path{})
|
||||||
|
return result
|
||||||
|
}
|
||||||
|
|||||||
@@ -112,6 +112,18 @@ func runValidation[T any](configuration *T, validator func(*T, *Validation), sch
|
|||||||
for _, delta := range compareDocumentTrees(before, after) {
|
for _, delta := range compareDocumentTrees(before, after) {
|
||||||
state.diagnostics = append(state.diagnostics, validationMutationDiagnostic(delta.path))
|
state.diagnostics = append(state.diagnostics, validationMutationDiagnostic(delta.path))
|
||||||
}
|
}
|
||||||
|
beforeSensitivity := projectedSensitivity(before)
|
||||||
|
afterSensitivity := projectedSensitivity(after)
|
||||||
|
for pointer, path := range beforeSensitivity {
|
||||||
|
if _, exists := afterSensitivity[pointer]; !exists {
|
||||||
|
state.diagnostics = append(state.diagnostics, validationMutationDiagnostic(path))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
for pointer, path := range afterSensitivity {
|
||||||
|
if _, exists := beforeSensitivity[pointer]; !exists {
|
||||||
|
state.diagnostics = append(state.diagnostics, validationMutationDiagnostic(path))
|
||||||
|
}
|
||||||
|
}
|
||||||
for _, path := range changedSecretStates(reflect.ValueOf(*configuration), schema.root, lookups) {
|
for _, path := range changedSecretStates(reflect.ValueOf(*configuration), schema.root, lookups) {
|
||||||
state.diagnostics = append(state.diagnostics, validationMutationDiagnostic(path))
|
state.diagnostics = append(state.diagnostics, validationMutationDiagnostic(path))
|
||||||
}
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user