Initial release: Go Jdenticon library v0.1.0
- Core library with SVG and PNG generation - CLI tool with generate and batch commands - Cross-platform path handling for Windows compatibility - Comprehensive test suite with integration tests
This commit is contained in:
284
internal/renderer/svg_security_test.go
Normal file
284
internal/renderer/svg_security_test.go
Normal file
@@ -0,0 +1,284 @@
|
||||
package renderer
|
||||
|
||||
import (
|
||||
"strings"
|
||||
"testing"
|
||||
|
||||
"github.com/ungluedlabs/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)
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user