feat: wave-3 image + document surfaces — segmentation, colorize, face restore, OCR (ADR-0023) #17

Merged
steve merged 2 commits from feat/wave3-image-doc-surfaces into main 2026-07-16 23:24:33 +00:00
4 changed files with 20 additions and 4 deletions
Showing only changes of commit 966ea16166 - Show all commits
+1 -1
View File
@@ -238,7 +238,7 @@ func TestInterpolateRejectsBadArgs(t *testing.T) {
}
func TestUpstreamPathRejectsSeparators(t *testing.T) {
for _, bad := range []string{"", "a/b", "a?b", "a#b"} {
for _, bad := range []string{"", "a/b", "a?b", "a#b", "a%2Fb", "%2e%2e", "a%b"} {
if _, err := upstreamPath(bad, "/x"); err == nil {
t.Errorf("upstreamPath(%q) succeeded; want error", bad)
}
+4 -1
View File
@@ -14,6 +14,7 @@ package llamaswap
import (
"context"
"fmt"
"math"
"net/http"
"strconv"
@@ -45,7 +46,9 @@ func (m *segmentationModel) Segment(ctx context.Context, req imagegen.Segmentati
if req.Prompt == "" {
return nil, fmt.Errorf("%w: segmentation requires a prompt", llm.ErrUnsupported)
}
Review

🟠 NaN passes segmentation threshold validation and is sent to backend

correctness, error-handling, security · flagged by 4 models

  • provider/llamaswap/segment.go:48 — Segmentation threshold validation lets NaN slip through. In Go, NaN < 0 and NaN > 1 are both false, so a Threshold of NaN passes the [0,1] guard and is serialized as "NaN" to the backend. It should be rejected alongside negatives and values greater than 1. Add a math.IsNaN(req.Threshold) check (or a < 0 || > 1 || != req.Threshold NaN test) before the path is built.

🪰 Gadfly · advisory

🟠 **NaN passes segmentation threshold validation and is sent to backend** _correctness, error-handling, security · flagged by 4 models_ - `provider/llamaswap/segment.go:48` — Segmentation threshold validation lets `NaN` slip through. In Go, `NaN < 0` and `NaN > 1` are both `false`, so a `Threshold` of `NaN` passes the `[0,1]` guard and is serialized as `"NaN"` to the backend. It should be rejected alongside negatives and values greater than 1. Add a `math.IsNaN(req.Threshold)` check (or a `< 0 || > 1 || != req.Threshold` NaN test) before the path is built. <sub>🪰 Gadfly · advisory</sub>
if req.Threshold < 0 || req.Threshold > 1 {
// NaN fails every comparison, so it would sail through a bare range
// check and reach the upstream as the literal string "NaN".
if math.IsNaN(req.Threshold) || req.Threshold < 0 || req.Threshold > 1 {
return nil, fmt.Errorf("%w: segmentation threshold must be in [0,1], got %g", llm.ErrUnsupported, req.Threshold)
}
path, err := upstreamPath(m.id, "/v1/segment")
1
@@ -3,6 +3,7 @@ package llamaswap
import (
"context"
"errors"
"math"
"net/http"
"net/http/httptest"
"testing"
@@ -95,6 +96,16 @@ func TestSegmentRejectsBadArgs(t *testing.T) {
imagegen.SegmentationRequest{Image: imagegen.Image{Data: []byte{1}}, Prompt: "dog", Threshold: 1.5}); !errors.Is(err, llm.ErrUnsupported) {
t.Errorf("threshold 1.5: err = %v, want ErrUnsupported", err)
}
for name, bad := range map[string]float64{
"NaN": math.NaN(),
"+Inf": math.Inf(1),
"-Inf": math.Inf(-1),
} {
if _, err := sg.Segment(context.Background(),
imagegen.SegmentationRequest{Image: imagegen.Image{Data: []byte{1}}, Prompt: "dog", Threshold: bad}); !errors.Is(err, llm.ErrUnsupported) {
t.Errorf("threshold %s: err = %v, want ErrUnsupported", name, err)
}
}
}
func TestSegmentRejectsNonImageResponse(t *testing.T) {
+4 -2
View File
@@ -16,12 +16,14 @@ import (
//
// Why reject rather than escape: same rationale as Unload — model ids
// legitimately contain ":" but never path-structure characters, and escaping
// would mask a config error instead of surfacing it.
// would mask a config error instead of surfacing it. '%' is rejected too:
// ids never legitimately carry percent-escapes, and %2F/%2E%2E would decode
// back into path structure on the server side.
func upstreamPath(model, rest string) (string, error) {
if strings.TrimSpace(model) == "" {
return "", fmt.Errorf("llama-swap: upstream call requires a model id")
}
if strings.ContainsAny(model, "/?#") || strings.Contains(model, "..") {
if strings.ContainsAny(model, "/?#%") || strings.Contains(model, "..") {
return "", fmt.Errorf("llama-swap: invalid model id %q for upstream call (contains a path separator)", model)
}
if !strings.HasPrefix(rest, "/") {