Strip metadata from processed images (closes #82) #145
@@ -111,6 +111,21 @@ with a private address can choose the address it is counted by through its own
|
|||||||
its own address is trusted too. Setting `trusted_proxies` to the proxy's own
|
its own address is trusted too. Setting `trusted_proxies` to the proxy's own
|
||||||
address closes this.
|
address closes this.
|
||||||
|
|
||||||
|
### Image Metadata
|
||||||
|
|
||||||
|
pixa decodes and re-encodes every image it serves, and removes all metadata from
|
||||||
|
the output: EXIF (GPS position, camera make, model and serial number, capture
|
||||||
|
time, embedded thumbnail), XMP, IPTC and the ICC colour profile. This cannot be
|
||||||
|
turned off.
|
||||||
|
|
||||||
|
- The `orig` format means the source's own format, not the source's bytes: an
|
||||||
|
`orig` image is re-encoded and stripped like any other.
|
||||||
|
- An image with an EXIF orientation is turned upright first, so it displays the
|
||||||
|
same without the tag; a requested size applies to the upright image.
|
||||||
|
- An image with an ICC profile is converted to sRGB first, since clients show an
|
||||||
|
image with no profile as sRGB. Colours outside sRGB, such as the most
|
||||||
|
saturated ones in a Display P3 photo, are clipped.
|
||||||
|
|
||||||
### Source Hosts
|
### Source Hosts
|
||||||
|
|
||||||
Source hosts may be allowlisted in the configuration. Non-allowlisted
|
Source hosts may be allowlisted in the configuration. Non-allowlisted
|
||||||
|
|||||||
@@ -30,6 +30,12 @@ exhaustion
|
|||||||
|
|
||||||
# Completed Steps
|
# Completed Steps
|
||||||
|
|
||||||
|
- 2026-09-28 strip metadata from processed images (closes #82): every output is
|
||||||
|
exported with govips' `StripMetadata`, so it carries no EXIF, XMP, IPTC or ICC
|
||||||
|
profile; the image is first turned upright with `AutoRotate` (before sizes are
|
||||||
|
worked out) and, when it has an ICC profile, converted to sRGB; the `orig`
|
||||||
|
format is re-encoded and stripped like any other, as pixa never serves the
|
||||||
|
source bytes; there is no setting to keep metadata; documented in `README.md`.
|
||||||
- 2026-09-28 rate limit the login form (closes #66): `POST /` is limited to 5
|
- 2026-09-28 rate limit the login form (closes #66): `POST /` is limited to 5
|
||||||
attempts per minute per client address, and an attempt over the limit is
|
attempts per minute per client address, and an attempt over the limit is
|
||||||
refused with 429 and a `Retry-After` header; the address is the one
|
refused with 429 and a `Retry-After` header; the address is the one
|
||||||
@@ -251,7 +257,6 @@ exhaustion
|
|||||||
|
|
||||||
# Future Steps
|
# Future Steps
|
||||||
|
|
||||||
- P1: strip EXIF and other metadata from processed images (privacy)
|
|
||||||
- P2: security
|
- P2: security
|
||||||
- referer blacklist
|
- referer blacklist
|
||||||
- per-IP rate limiting on the image routes
|
- per-IP rate limiting on the image routes
|
||||||
|
|||||||
@@ -161,6 +161,13 @@ func (p *ImageProcessor) Process(
|
|||||||
}
|
}
|
||||||
defer img.Close()
|
defer img.Close()
|
||||||
|
|
||||||
|
// Turn the image upright now: encode strips the EXIF orientation tag,
|
||||||
|
// and sizes below must be worked out on the upright image.
|
||||||
|
err = img.AutoRotate()
|
||||||
|
if err != nil {
|
||||||
|
return nil, fmt.Errorf("failed to auto-rotate: %w", err)
|
||||||
|
}
|
||||||
|
|
||||||
// Get original dimensions
|
// Get original dimensions
|
||||||
origWidth := img.Width()
|
origWidth := img.Width()
|
||||||
origHeight := img.Height()
|
origHeight := img.Height()
|
||||||
@@ -404,6 +411,21 @@ func (p *ImageProcessor) encode(
|
|||||||
return nil, fmt.Errorf("%w: %s", ErrUnsupportedOutputFormat, format)
|
return nil, fmt.Errorf("%w: %s", ErrUnsupportedOutputFormat, format)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Stripping drops the ICC profile as well, and clients show an image
|
||||||
|
// with no profile as sRGB, so convert to sRGB first. "srgb" names
|
||||||
|
// libvips' built-in profile; govips' own sRGB path variable is set on
|
||||||
|
// first use but read without a lock, so concurrent requests race on it.
|
||||||
|
if img.HasICCProfile() {
|
||||||
|
err := img.TransformICCProfileWithFallback("srgb", "srgb")
|
||||||
|
if err != nil {
|
||||||
|
return nil, fmt.Errorf("failed to convert to sRGB: %w", err)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Drop EXIF, XMP, IPTC and the ICC profile. govips ignores this for
|
||||||
|
// GIF, which carries none of them.
|
||||||
|
params.StripMetadata = true
|
||||||
|
|
||||||
output, _, err := img.Export(¶ms)
|
output, _, err := img.Export(¶ms)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
|
|||||||
Reference in New Issue
Block a user