release: a proof policy with any data after the object is an error (H1)
Decoder.More() is false before a stray '}' or ']', so {"schema":1}} parsed as a policy with no requirements. A second Decode must now return io.EOF; anything else is a parse error and the gate stays closed.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2 files changed,
+14
−1
internal/release/proofpolicy.go+4 −1internal/release/proofpolicy_test.go+10 −0
diff --git a/internal/release/proofpolicy.go b/internal/release/proofpolicy.go| index b4382d023..b1c25d317 100644 |
| --- a/internal/release/proofpolicy.go |
| +++ b/internal/release/proofpolicy.go |
| @@ -19,6 +19,7 @@ import ( |
| "encoding/json" |
| "errors" |
| "fmt" |
| + "io" |
| "os" |
| "path/filepath" |
| "time" |
| @@ -77,7 +78,9 @@ func ParseProofPolicy(b []byte) (*ProofPolicy, error) { |
| if err := dec.Decode(&p); err != nil { |
| return nil, fmt.Errorf("does not parse: %v", err) |
| } |
| - if dec.More() { |
| + // More() is false before a stray '}' or ']', so it cannot prove the end |
| + // of input; only a second Decode returning io.EOF does. |
| + if err := dec.Decode(&json.RawMessage{}); err != io.EOF { |
| return nil, fmt.Errorf("does not parse: data after the policy object") |
| } |
| if p.Schema != 1 { |
diff --git a/internal/release/proofpolicy_test.go b/internal/release/proofpolicy_test.go| index 908af8e24..176860466 100644 |
| --- a/internal/release/proofpolicy_test.go |
| +++ b/internal/release/proofpolicy_test.go |
| @@ -66,9 +66,19 @@ func TestProofPolicyUnparseableIsAnError(t *testing.T) { |
| "relative key": `{"schema":1,"prove":{"apps":["console"],"prover":"dev","after":"15m","key_file":"proving.key"}}`, |
| "no apps": `{"schema":1,"prove":{"apps":[],"prover":"dev","after":"15m","key_file":"/k"}}`, |
| "trailing junk": `{"schema":1} {}`, |
| + "trailing }": `{"schema":1}}`, |
| + "trailing ]": `{"schema":1}]`, |
| + "trailing word": `{"schema":1} x`, |
| } { |
| if _, err := ParseProofPolicy([]byte(body)); err == nil { |
| t.Fatalf("%s: parsed, want an error", name) |
| } |
| } |
| } |
| + |
| +// Trailing whitespace is still the end of input. |
| +func TestProofPolicyTrailingWhitespaceParses(t *testing.T) { |
| + if _, err := ParseProofPolicy([]byte("{\"schema\":1}\n\n \t")); err != nil { |
| + t.Fatal(err) |
| + } |
| +} |