Source identity
| Field | Recorded value |
|---|---|
| Source type | GitHub source |
| Original record | Janady13/missionmatch / internal/modules/org/service_test.go |
| Date | Not specified in source |
| Repository | Janady13/missionmatch |
| Revision | 720d47fae476b0d38477ae4401697d372b1ea2bc |
| Branch | feature/opportunity-to-outcome |
| Path or archive member | internal/modules/org/service_test.go |
| Original SHA-256 | f0b802faa5833f35f49b2f86fc6435e775a966ab1815b4a463d392f52758495b |
| Git blob SHA | 9d02f311518a36c25c610809075991929401d054 |
| Reading-copy SHA-256 | f0b802faa5833f35f49b2f86fc6435e775a966ab1815b4a463d392f52758495b |
Every recorded occurrence
These 2 records contain identical original source bytes. Their paths and revisions remain part of the archive.
| Original record | Branch or archive | Revision / identifier | Original location |
|---|---|---|---|
| Janady13/missionmatch / internal/modules/org/service_test.go | feature/opportunity-to-outcome | 720d47fae476b0d38477ae4401697d372b1ea2bc | Open original |
| Janady13/missionmatch / internal/modules/org/service_test.go | feature/aioe-upgrade | 2178e7298d0e5d9af83afa12402fd587f0ba6ed8 | Open original |
Recovered source transcript
Download complete source text ↓183 lines · 6.2 KB
package org
import (
"bytes"
"encoding/base64"
"image"
"image/color"
"image/jpeg"
"image/png"
"strings"
"testing"
)
// pngOf builds a real PNG of the requested size so the tests exercise the actual decoder rather than a
// fixture that might drift from what the decoder accepts.
func pngOf(t *testing.T, w, h int) []byte {
t.Helper()
img := image.NewRGBA(image.Rect(0, 0, w, h))
img.Set(0, 0, color.RGBA{R: 1, G: 2, B: 3, A: 255})
var buf bytes.Buffer
if err := png.Encode(&buf, img); err != nil {
t.Fatalf("encode png: %v", err)
}
return buf.Bytes()
}
func jpegOf(t *testing.T, w, h int) []byte {
t.Helper()
img := image.NewRGBA(image.Rect(0, 0, w, h))
var buf bytes.Buffer
if err := jpeg.Encode(&buf, img, nil); err != nil {
t.Fatalf("encode jpeg: %v", err)
}
return buf.Bytes()
}
// validate mirrors exactly what SetLogo does before it touches the database, so these tests can prove the
// validation without a pool. If SetLogo changes, this must change with it.
func validate(raw []byte) error {
if len(raw) == 0 {
return ErrLogoFormat
}
if len(raw) > MaxLogoBytes {
return ErrLogoTooLarge
}
cfg, format, err := image.DecodeConfig(bytes.NewReader(raw))
if err != nil {
return ErrLogoFormat
}
if _, ok := formatMIME[format]; !ok {
return ErrLogoFormat
}
if cfg.Width <= 0 || cfg.Height <= 0 || cfg.Width > MaxLogoDimension || cfg.Height > MaxLogoDimension {
return ErrLogoTooBig
}
return nil
}
// TestRealImagesAreAccepted. Both permitted formats must round trip.
func TestRealImagesAreAccepted(t *testing.T) {
if err := validate(pngOf(t, 64, 64)); err != nil {
t.Errorf("a real png was rejected: %v", err)
}
if err := validate(jpegOf(t, 64, 64)); err != nil {
t.Errorf("a real jpeg was rejected: %v", err)
}
}
// TestHostileUploadsAreRejected is the security proof. Every case here is a real attack shape: a payload
// whose declared type or file extension claims one thing while the bytes are something else. The server
// never trusts the declared type, so all of them must fail on content.
func TestHostileUploadsAreRejected(t *testing.T) {
cases := []struct {
name string
body []byte
}{
{
// The important one. An SVG served from the application origin can carry a script element and
// become stored cross site scripting against every user in the organization.
name: "svg with a script payload",
body: []byte(`<svg xmlns="http://www.w3.org/2000/svg"><script>alert(document.cookie)</script></svg>`),
},
{
name: "html document renamed to an image",
body: []byte(`<html><body><script>fetch('/api/candidates')</script></body></html>`),
},
{
// A polyglot: valid PNG magic bytes followed by markup, betting the server checks only the
// first eight bytes. DecodeConfig parses the header structure, so a truncated PNG fails.
name: "png magic bytes then html",
body: append([]byte{0x89, 'P', 'N', 'G', 0x0d, 0x0a, 0x1a, 0x0a}, []byte(`<script>alert(1)</script>`)...),
},
{name: "empty body", body: []byte{}},
{name: "plain text", body: []byte("not an image at all")},
{
name: "gif is a real image but is not permitted",
body: mustDecodeB64(t, "R0lGODlhAQABAIAAAAAAAP///yH5BAEAAAAALAAAAAABAAEAAAIBRAA7"),
},
}
for _, c := range cases {
t.Run(c.name, func(t *testing.T) {
if err := validate(c.body); err == nil {
t.Fatalf("%s was ACCEPTED, it must be rejected", c.name)
}
})
}
}
// TestOversizeIsRejectedBeforeDecoding proves the cheap length check runs first, so a hostile upload
// never reaches the decoder.
func TestOversizeIsRejectedBeforeDecoding(t *testing.T) {
big := make([]byte, MaxLogoBytes+1)
if err := validate(big); err != ErrLogoTooLarge {
t.Errorf("oversize error = %v, want ErrLogoTooLarge", err)
}
}
// TestDimensionBombIsRejected. DecodeConfig reads only the header, so a small file can declare enormous
// dimensions. Without the bound a downstream resize would try to allocate that much memory.
func TestDimensionBombIsRejected(t *testing.T) {
over := pngOf(t, MaxLogoDimension+1, 4)
if err := validate(over); err != ErrLogoTooBig {
t.Errorf("oversized dimension error = %v, want ErrLogoTooBig", err)
}
edge := pngOf(t, MaxLogoDimension, 4)
if err := validate(edge); err != nil {
t.Errorf("a logo exactly at the dimension bound was rejected: %v", err)
}
}
// TestFormatAllowlistIsTheAuthority pins that adding a format is a deliberate edit to the map, not a side
// effect of which decoders happen to be linked into the binary.
func TestFormatAllowlistIsTheAuthority(t *testing.T) {
if len(formatMIME) != 2 {
t.Fatalf("format allowlist has %d entries, want exactly png and jpeg", len(formatMIME))
}
for _, want := range []string{"png", "jpeg"} {
if _, ok := formatMIME[want]; !ok {
t.Errorf("format %q missing from the allowlist", want)
}
}
for _, forbidden := range []string{"gif", "svg", "webp", "bmp", "tiff"} {
if _, ok := formatMIME[forbidden]; ok {
t.Errorf("format %q is in the allowlist and must not be", forbidden)
}
}
}
// TestETagIsContentAddressed. Deriving the validator from the bytes means a rollback to a previous logo
// still busts the cache, which a timestamp based validator would not.
func TestETagIsContentAddressed(t *testing.T) {
a, b := pngOf(t, 8, 8), pngOf(t, 9, 9)
if etagFor(a) == etagFor(b) {
t.Error("different images produced the same etag")
}
if etagFor(a) != etagFor(a) {
t.Error("the same image produced different etags")
}
if !strings.HasPrefix(etagFor(a), `"`) || !strings.HasSuffix(etagFor(a), `"`) {
t.Errorf("etag %s is not a quoted string as HTTP requires", etagFor(a))
}
}
// TestMaxLogoBytesMatchesTheMigration guards the two limits drifting apart. The database CHECK in
// migration 0046 is the last line of defense and must agree with the application.
func TestMaxLogoBytesMatchesTheMigration(t *testing.T) {
if MaxLogoBytes != 524288 {
t.Errorf("MaxLogoBytes = %d but migration 0046 checks octet_length <= 524288", MaxLogoBytes)
}
if MaxDisplayName != 120 {
t.Errorf("MaxDisplayName = %d but migration 0046 checks length between 1 and 120", MaxDisplayName)
}
}
func mustDecodeB64(t *testing.T, s string) []byte {
t.Helper()
b, err := base64.StdEncoding.DecodeString(s)
if err != nil {
t.Fatalf("decode fixture: %v", err)
}
return b
}