Documentation
¶
Overview ¶
Package brandimage is the one place that decides what counts as a brand mark: which formats Atlas accepts, whether a body really is one, and under what headers it is handed back to a browser.
There is more than one mark in an installation. ADR-0148 gave the instance its own — the operator's, shown on the sign-in screen and in the console. The portal added a second kind: a catalogue's, shown to the customer group that catalogue is for (ADR-0316). They are stored in different places and gated by different rules, and that is as it should be.
What must not differ is the check. An uploaded image is attacker-influenced content that this server persists and later serves to every browser that opens the page; the validation on the way in and the headers on the way out are the whole of the mitigation. Two copies of that pair is one copy that a future hardening will miss — so both marks read it from here, and a third will too.
It is deliberately a gate rather than a sanitiser. An SVG is a document format with scripting in it, and no byte-level inspection makes one safe; what makes it safe is Serve's response — nosniff pins the declared type, and a `default-src 'none'; sandbox` policy neutralises script even when the image is opened as a top-level document, which is the only context where an SVG's script would run at all (an <img> never runs it).
Index ¶
Constants ¶
This section is empty.
Variables ¶
var Mark = newSet(map[string]string{"image/png": "png", "image/svg+xml": "svg"})
Mark is what a brand mark may be: a raster PNG or a vector SVG. An instance's mark and a catalogue's are both marks, and both take this set.
var Photo = newSet(map[string]string{"image/png": "png", "image/jpeg": "jpg"})
Photo is what a picture of a person may be: PNG or JPEG.
**Deliberately not SVG**, and that is the one place the two sets differ in kind rather than in taste. An SVG is a document with scripting in it; Serve makes one inert, but a mark has a reason to be a vector — it is drawn, it is scaled, a designer delivers one — and a photograph does not. A picture of a person arrives from a camera or from a directory, and both give raster bytes. Accepting a format nothing needs, in the one place where the uploader is every account rather than an administrator, is widening the surface for nothing.
JPEG is here because that is what Microsoft Graph answers with for a user's photo. A set that refused it would have made the directory path impossible and said nothing about why.
var Picture = newSet(map[string]string{
"image/png": "png", "image/jpeg": "jpg", "image/svg+xml": "svg",
})
Picture is what a product in the catalogue may be shown as: a photograph or a vendor's mark, so PNG, JPEG **and** SVG.
It is the union of the two sets above, and the reason is who uploads it. Photo refuses SVG because a picture of a person arrives from a camera and the uploader there is every account; a product picture arrives from whoever maintains the catalogue — a product manager, the same kind of privileged author who sets a catalogue's mark — and half of what they have is a vector, because half of what a service catalogue sells is software with a logo rather than a thing with a photograph. A set that refused SVG would be answered by converting the logo to a raster, badly, once per product.
What makes that safe is Serve and nothing else: the bytes are validated as the type they claim, served under a sandbox policy with no script origin, and never inlined into a page.
Functions ¶
func NormalizeType ¶
NormalizeType canonicalises an upload's Content-Type to a bare, lowercase media type, dropping any parameters (e.g. "image/svg+xml; charset=utf-8").
func Serve ¶
func Serve(w http.ResponseWriter, contentType string, data []byte)
Serve writes a stored mark as the response, under the headers that make a hostile SVG inert. Callers have already decided who may see it; this decides only how it travels.
func Valid ¶
Valid reports whether the bytes look like the media type they were declared as: the PNG or JPEG magic for a raster image, well-formed UTF-8 containing an "<svg" root for a vector one. An unknown type is never valid.
It answers for every Set, and knowing a type here does not make a surface take it — the surface's own set decides that, and asks this only about what it already accepts.
Types ¶
type Set ¶
type Set struct {
// contains filtered or unexported fields
}
A Set is what one upload surface accepts: which media types, and the extension each is stored under.
There is a set per surface and one Valid for all of them, and the split is the point. Whether bytes really are the type they claim is a security question with one answer everywhere — two copies of it is one copy a future hardening will miss. *Which* types a surface accepts is a different question, and the surfaces genuinely differ: see Mark and Photo.