Some checks failed
CI / Test (Go 1.24.x, ubuntu-latest) (push) Successful in 1m53s
CI / Code Quality (push) Failing after 26s
CI / Security Scan (push) Failing after 11s
CI / Test Coverage (push) Successful in 1m13s
CI / Benchmarks (push) Failing after 10m22s
CI / Build CLI (push) Failing after 8s
Benchmarks / Run Benchmarks (push) Failing after 10m13s
Release / Test (push) Successful in 55s
Release / Build (amd64, darwin, ) (push) Failing after 12s
Release / Build (amd64, linux, ) (push) Failing after 6s
Release / Build (amd64, windows, .exe) (push) Failing after 12s
Release / Build (arm64, darwin, ) (push) Failing after 12s
Release / Build (arm64, linux, ) (push) Failing after 12s
Release / Release (push) Has been skipped
CI / Test (Go 1.24.x, macos-latest) (push) Has been cancelled
CI / Test (Go 1.24.x, windows-latest) (push) Has been cancelled
Move hosting from GitHub to private Gitea instance.
285 lines
8.6 KiB
Go
285 lines
8.6 KiB
Go
package renderer
|
|
|
|
import (
|
|
"strings"
|
|
"testing"
|
|
|
|
"gitea.dockr.co/kev/go-jdenticon/internal/engine"
|
|
)
|
|
|
|
// TestSVGRenderer_SecurityValidation tests defense-in-depth color validation
|
|
// This test addresses SEC-06 from the security report by verifying that
|
|
// the SVG renderer properly validates color inputs and prevents injection attacks.
|
|
func TestSVGRenderer_SecurityValidation(t *testing.T) {
|
|
tests := []struct {
|
|
name string
|
|
color string
|
|
expectInSVG bool
|
|
description string
|
|
}{
|
|
{
|
|
name: "valid_hex_color_3_digit",
|
|
color: "#f00",
|
|
expectInSVG: true,
|
|
description: "Valid 3-digit hex color should be rendered",
|
|
},
|
|
{
|
|
name: "valid_hex_color_6_digit",
|
|
color: "#ff0000",
|
|
expectInSVG: true,
|
|
description: "Valid 6-digit hex color should be rendered",
|
|
},
|
|
{
|
|
name: "valid_hex_color_8_digit",
|
|
color: "#ff0000ff",
|
|
expectInSVG: true,
|
|
description: "Valid 8-digit hex color with alpha should be rendered",
|
|
},
|
|
{
|
|
name: "injection_attempt_script",
|
|
color: "\"><script>alert('xss')</script><path fill=\"#000",
|
|
expectInSVG: false,
|
|
description: "Script injection attempt should be blocked",
|
|
},
|
|
{
|
|
name: "injection_attempt_svg_element",
|
|
color: "#f00\"/><use href=\"#malicious\"/><path fill=\"#000",
|
|
expectInSVG: false,
|
|
description: "SVG element injection attempt should be blocked",
|
|
},
|
|
{
|
|
name: "malformed_hex_no_hash",
|
|
color: "ff0000",
|
|
expectInSVG: false,
|
|
description: "Hex color without # should be rejected",
|
|
},
|
|
{
|
|
name: "valid_hex_color_4_digit_rgba",
|
|
color: "#ff00",
|
|
expectInSVG: true,
|
|
description: "Valid 4-digit RGBA hex color should be rendered",
|
|
},
|
|
{
|
|
name: "malformed_hex_invalid_length_5",
|
|
color: "#ff000",
|
|
expectInSVG: false,
|
|
description: "Invalid 5-character hex color should be rejected",
|
|
},
|
|
{
|
|
name: "malformed_hex_invalid_chars",
|
|
color: "#gggggg",
|
|
expectInSVG: false,
|
|
description: "Invalid hex characters should be rejected",
|
|
},
|
|
{
|
|
name: "empty_color",
|
|
color: "",
|
|
expectInSVG: false,
|
|
description: "Empty color string should be rejected",
|
|
},
|
|
{
|
|
name: "xml_entity_injection",
|
|
color: "#ff0000<script>",
|
|
expectInSVG: false,
|
|
description: "XML entity injection attempt should be blocked",
|
|
},
|
|
{
|
|
name: "path_data_injection",
|
|
color: "#f00\" d=\"M0 0L100 100Z\"/><script>alert('xss')</script><path fill=\"#000",
|
|
expectInSVG: false,
|
|
description: "Path data injection attempt should be blocked",
|
|
},
|
|
}
|
|
|
|
for _, tt := range tests {
|
|
t.Run(tt.name, func(t *testing.T) {
|
|
renderer := NewSVGRenderer(100)
|
|
|
|
// Test BeginShape validation
|
|
renderer.BeginShape(tt.color)
|
|
|
|
// Add some path data to ensure the color would be rendered if valid
|
|
points := []engine.Point{
|
|
{X: 10, Y: 10},
|
|
{X: 50, Y: 10},
|
|
{X: 50, Y: 50},
|
|
{X: 10, Y: 50},
|
|
}
|
|
renderer.AddPolygon(points)
|
|
renderer.EndShape()
|
|
|
|
// Generate SVG output
|
|
svgOutput := renderer.ToSVG()
|
|
|
|
if tt.expectInSVG {
|
|
// Verify valid colors are present in the output
|
|
if !strings.Contains(svgOutput, `fill="`+tt.color+`"`) {
|
|
t.Errorf("Expected valid color %s to be present in SVG output, but it was not found.\nSVG: %s", tt.color, svgOutput)
|
|
}
|
|
|
|
// Ensure the path element is present for valid colors
|
|
if !strings.Contains(svgOutput, "<path") {
|
|
t.Errorf("Expected path element to be present for valid color %s, but it was not found", tt.color)
|
|
}
|
|
} else {
|
|
// Verify invalid/malicious colors are NOT present in the output
|
|
// Special handling for empty string since it's always "contained" in any string
|
|
if tt.color != "" && strings.Contains(svgOutput, tt.color) {
|
|
t.Errorf("Expected invalid/malicious color %s to be rejected, but it was found in SVG output.\nSVG: %s", tt.color, svgOutput)
|
|
}
|
|
|
|
// For invalid colors, no path should be rendered with that color
|
|
if strings.Contains(svgOutput, `fill="`+tt.color+`"`) {
|
|
t.Errorf("Expected invalid color %s to be rejected from fill attribute, but it was found", tt.color)
|
|
}
|
|
}
|
|
|
|
// Verify the SVG is still well-formed XML
|
|
if !strings.HasPrefix(svgOutput, "<svg") {
|
|
t.Errorf("SVG output should start with <svg tag")
|
|
}
|
|
if !strings.HasSuffix(svgOutput, "</svg>") {
|
|
t.Errorf("SVG output should end with </svg> tag")
|
|
}
|
|
})
|
|
}
|
|
}
|
|
|
|
// TestSVGRenderer_BackgroundColorValidation tests background color validation
|
|
func TestSVGRenderer_BackgroundColorValidation(t *testing.T) {
|
|
tests := []struct {
|
|
name string
|
|
bgColor string
|
|
opacity float64
|
|
expectInSVG bool
|
|
description string
|
|
}{
|
|
{
|
|
name: "valid_background_color",
|
|
bgColor: "#ffffff",
|
|
opacity: 1.0,
|
|
expectInSVG: true,
|
|
description: "Valid background color should be rendered",
|
|
},
|
|
{
|
|
name: "invalid_background_injection",
|
|
bgColor: "#fff\"/><script>alert('bg')</script><rect fill=\"#000",
|
|
opacity: 1.0,
|
|
expectInSVG: false,
|
|
description: "Background color injection should be blocked",
|
|
},
|
|
{
|
|
name: "malformed_background_color",
|
|
bgColor: "not-a-color",
|
|
opacity: 1.0,
|
|
expectInSVG: false,
|
|
description: "Invalid background color format should be rejected",
|
|
},
|
|
}
|
|
|
|
for _, tt := range tests {
|
|
t.Run(tt.name, func(t *testing.T) {
|
|
renderer := NewSVGRenderer(100)
|
|
renderer.SetBackground(tt.bgColor, tt.opacity)
|
|
|
|
svgOutput := renderer.ToSVG()
|
|
|
|
if tt.expectInSVG {
|
|
// Verify valid background colors are present
|
|
if !strings.Contains(svgOutput, `<rect`) {
|
|
t.Errorf("Expected background rectangle to be present for valid color %s", tt.bgColor)
|
|
}
|
|
if !strings.Contains(svgOutput, `fill="`+tt.bgColor+`"`) {
|
|
t.Errorf("Expected valid background color %s to be present in SVG", tt.bgColor)
|
|
}
|
|
} else {
|
|
// Verify invalid background colors are rejected
|
|
if strings.Contains(svgOutput, tt.bgColor) {
|
|
t.Errorf("Expected invalid background color %s to be rejected, but found in: %s", tt.bgColor, svgOutput)
|
|
}
|
|
}
|
|
})
|
|
}
|
|
}
|
|
|
|
// TestSVGRenderer_MultipleInvalidColors tests behavior with multiple invalid colors
|
|
func TestSVGRenderer_MultipleInvalidColors(t *testing.T) {
|
|
renderer := NewSVGRenderer(100)
|
|
|
|
maliciousColors := []string{
|
|
"\"><script>alert(1)</script><path fill=\"#000",
|
|
"#invalid-color",
|
|
"javascript:alert('xss')",
|
|
"#ff0000\"/><use href=\"#malicious\"/>",
|
|
}
|
|
|
|
// Try to add shapes with all malicious colors
|
|
for _, color := range maliciousColors {
|
|
renderer.BeginShape(color)
|
|
points := []engine.Point{{X: 0, Y: 0}, {X: 50, Y: 50}}
|
|
renderer.AddPolygon(points)
|
|
renderer.EndShape()
|
|
}
|
|
|
|
svgOutput := renderer.ToSVG()
|
|
|
|
// Verify none of the malicious colors appear in the output
|
|
for _, color := range maliciousColors {
|
|
if strings.Contains(svgOutput, color) {
|
|
t.Errorf("Malicious color %s should not appear in SVG output, but was found: %s", color, svgOutput)
|
|
}
|
|
}
|
|
|
|
// Verify the SVG is still valid and doesn't contain path elements for rejected colors
|
|
pathCount := strings.Count(svgOutput, "<path")
|
|
if pathCount > 0 {
|
|
t.Errorf("Expected no path elements for invalid colors, but found %d", pathCount)
|
|
}
|
|
|
|
// Ensure SVG structure is intact
|
|
if !strings.Contains(svgOutput, `<svg xmlns="http://www.w3.org/2000/svg"`) {
|
|
t.Errorf("SVG should still have proper structure even with all invalid colors")
|
|
}
|
|
}
|
|
|
|
// TestSVGRenderer_ValidAndInvalidColorMix tests mixed valid/invalid colors
|
|
func TestSVGRenderer_ValidAndInvalidColorMix(t *testing.T) {
|
|
renderer := NewSVGRenderer(100)
|
|
|
|
// Add valid color
|
|
renderer.BeginShape("#ff0000")
|
|
renderer.AddPolygon([]engine.Point{{X: 0, Y: 0}, {X: 25, Y: 25}})
|
|
renderer.EndShape()
|
|
|
|
// Add invalid color
|
|
renderer.BeginShape("\"><script>alert('xss')</script><path fill=\"#000")
|
|
renderer.AddPolygon([]engine.Point{{X: 25, Y: 25}, {X: 50, Y: 50}})
|
|
renderer.EndShape()
|
|
|
|
// Add another valid color
|
|
renderer.BeginShape("#00ff00")
|
|
renderer.AddPolygon([]engine.Point{{X: 50, Y: 50}, {X: 75, Y: 75}})
|
|
renderer.EndShape()
|
|
|
|
svgOutput := renderer.ToSVG()
|
|
|
|
// Valid colors should be present
|
|
if !strings.Contains(svgOutput, `fill="#ff0000"`) {
|
|
t.Errorf("Valid color #ff0000 should be present in output")
|
|
}
|
|
if !strings.Contains(svgOutput, `fill="#00ff00"`) {
|
|
t.Errorf("Valid color #00ff00 should be present in output")
|
|
}
|
|
|
|
// Invalid color should be rejected
|
|
if strings.Contains(svgOutput, "script") {
|
|
t.Errorf("Invalid color with script injection should be rejected")
|
|
}
|
|
|
|
// Should have exactly 2 path elements (for the 2 valid colors)
|
|
pathCount := strings.Count(svgOutput, "<path")
|
|
if pathCount != 2 {
|
|
t.Errorf("Expected exactly 2 path elements for valid colors, got %d", pathCount)
|
|
}
|
|
}
|