Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
12 changes: 9 additions & 3 deletions arazzo/nodes.go
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,10 @@ import (
"github.com/pb33f/go-yaml"
)

// maxSchemaExponent is the largest absolute decimal exponent math/big.Rat
// SetString accepts. One past this value makes JSON Schema minimum checks panic.
const maxSchemaExponent = 1_000_000

func object(v any) map[string]any { m, _ := v.(map[string]any); return m }
func array(v any) []any { a, _ := v.([]any); return a }
func text(v any) string { s, _ := v.(string); return s }
Expand Down Expand Up @@ -144,12 +148,14 @@ func (n *normalizer) value(node *yaml.Node, path string, depth int) (any, error)
}
// JSON's number grammar rejects NaN, infinity and YAML-only spellings.
if json.Valid([]byte(clean)) && len(clean) > 0 && (clean[0] == '-' || clean[0] >= '0' && clean[0] <= '9') {
// Schema integer and numeric checks use arbitrary-precision numbers.
// Charge exponent expansion before those checks allocate 10^exponent.
// Schema numeric checks parse with big.Rat. SetString rejects a
// decimal exponent above 1_000_000, then panics comparing minimum
// or maximum. The byte budget is much larger, so it does not
// stop those values. Charge both limits before validation.
if i := strings.IndexAny(clean, "eE"); i >= 0 {
exponent := strings.TrimLeft(clean[i+1:], "+-")
growth, err := strconv.Atoi(exponent)
if err != nil || growth > n.limits.MaxBytes-n.bytes {
if err != nil || growth > maxSchemaExponent || growth > n.limits.MaxBytes-n.bytes {
return nil, &Error{Kind: ErrorLimit, Cause: fmt.Errorf("numeric exponent exceeds expanded byte limit")}
}
n.bytes += growth
Expand Down
9 changes: 9 additions & 0 deletions arazzo/validate_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -335,6 +335,10 @@ func TestFoundationCapsBeforeCompilation(t *testing.T) {
}{
{strings.Replace(foundationYAML("1.1.0"), " steps:", " inputs: {type: string, pattern: 'a very long pattern'}\n steps:", 1), func() Limits { l := DefaultLimits(); l.MaxPatternBytes = 4; return l }()},
{strings.Replace(foundationYAML("1.1.0"), "operationId: read", "operationId: read, requestBody: {payload: 1e99999999999999}", 1), DefaultLimits()},
// big.Rat.SetString accepts a decimal exponent of 1000000 and rejects 1000001.
// The byte budget is 16MiB, so this value used to reach schema minimum checks and panic.
{strings.Replace(foundationYAML("1.1.0"), "operationId: read", "operationId: read, onFailure: [{name: again, type: retry, retryAfter: 1e1000001}]", 1), DefaultLimits()},
{strings.Replace(foundationYAML("1.1.0"), "operationId: read", "operationId: read, onFailure: [{name: again, type: retry, retryAfter: 1e-1000001}]", 1), DefaultLimits()},
{strings.Replace(foundationYAML("1.1.0"), " steps:", " x-a: {x-b: {x-c: {x-d: value}}}\n steps:", 1), func() Limits { l := DefaultLimits(); l.MaxDepth = 4; return l }()},
}
for i, tt := range tests {
Expand All @@ -344,6 +348,11 @@ func TestFoundationCapsBeforeCompilation(t *testing.T) {
t.Fatalf("case %d: %+v %v", i, r, err)
}
}
accepted := strings.Replace(foundationYAML("1.1.0"), "operationId: read", "operationId: read, onFailure: [{name: again, type: retry, retryAfter: 1e1000000}]", 1)
r, err := ValidateBytes(context.Background(), []byte(accepted), "input.yaml")
if err != nil || !r.Valid() {
t.Fatalf("exponent 1000000 is representable: %+v %v", r, err)
}
}

// cloneNodeGraph snapshots caller-owned node graphs for immutability tests,
Expand Down
Loading