safe_image 0.1.0 → 0.2.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- checksums.yaml +4 -4
- data/CHANGELOG.md +158 -0
- data/README.md +475 -281
- data/SECURITY.md +27 -7
- data/lib/safe_image/discourse_compat.rb +267 -98
- data/lib/safe_image/fonts/DEJAVU-LICENSE +187 -0
- data/lib/safe_image/fonts/DejaVuSans.ttf +0 -0
- data/lib/safe_image/ico.rb +286 -0
- data/lib/safe_image/image_magick_backend.rb +39 -3
- data/lib/safe_image/imagemagick_policy/policy.xml +8 -1
- data/lib/safe_image/jpegli_backend.rb +3 -1
- data/lib/safe_image/native.rb +371 -1
- data/lib/safe_image/processor.rb +23 -69
- data/lib/safe_image/remote.rb +15 -3
- data/lib/safe_image/runner.rb +1 -1
- data/lib/safe_image/sandbox.rb +42 -16
- data/lib/safe_image/svg_metadata.rb +59 -29
- data/lib/safe_image/version.rb +1 -1
- data/lib/safe_image/vips_backend.rb +57 -0
- data/lib/safe_image/vips_glue.rb +361 -0
- data/lib/safe_image.rb +143 -36
- metadata +30 -14
- data/ext/safe_image_native/extconf.rb +0 -8
- data/ext/safe_image_native/safe_image_native.c +0 -392
checksums.yaml
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
---
|
|
2
2
|
SHA256:
|
|
3
|
-
metadata.gz:
|
|
4
|
-
data.tar.gz:
|
|
3
|
+
metadata.gz: e078245fa2e4fb0707b0d477ad0c4b41f31423905ed0dc84f964cd648a5a032f
|
|
4
|
+
data.tar.gz: 9155876c2761ac9c6afb2b4da4a97af8b6b7e035ffee553c72dacda6166c1048
|
|
5
5
|
SHA512:
|
|
6
|
-
metadata.gz:
|
|
7
|
-
data.tar.gz:
|
|
6
|
+
metadata.gz: 2f98d64d3b9665a156c27cbcef898cd148e6b559c73c03ba757b1147d2e47f1733d5a6e0ebc7d5c038cb989f874d67747175cd1d094cdf9677f98550cfe635be
|
|
7
|
+
data.tar.gz: ae1008885bf83da844145369cd929edd12c0af238fd0d975e2c078ef95b4d7cc301f1fed2da5cdd815d520591938acc144a2bd79d4bd2573336a2c9eb1078e5b
|
data/CHANGELOG.md
ADDED
|
@@ -0,0 +1,158 @@
|
|
|
1
|
+
# Changelog
|
|
2
|
+
|
|
3
|
+
All notable changes to this project will be documented in this file.
|
|
4
|
+
|
|
5
|
+
The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.1.0/),
|
|
6
|
+
and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html).
|
|
7
|
+
|
|
8
|
+
## [0.2.0] - 2026-06-10
|
|
9
|
+
|
|
10
|
+
The host's whole image-processing posture is now decided in one place, once,
|
|
11
|
+
at boot. There is no per-call backend fidelity and no automatic routing
|
|
12
|
+
between backends.
|
|
13
|
+
|
|
14
|
+
### Added
|
|
15
|
+
|
|
16
|
+
- **`SafeImage.configure!(backend:, landlock:, max_pixels:)` is mandatory**:
|
|
17
|
+
every operation before it raises the new `SafeImage::NotConfiguredError`.
|
|
18
|
+
`backend:` (`:vips` or `:imagemagick`) picks the decoder for all untrusted
|
|
19
|
+
bytes; `landlock:` decides sandboxing; `max_pixels:` sets the default
|
|
20
|
+
decompression-bomb ceiling (128MP, still overridable per call). Validation
|
|
21
|
+
is eager — a missing libvips, ImageMagick binary, or Landlock support fails
|
|
22
|
+
at boot, not on the first request. Calling `configure!` again replaces the
|
|
23
|
+
configuration atomically, so initializer reloads are safe.
|
|
24
|
+
- `SafeImage.config` (frozen current configuration) and
|
|
25
|
+
`SafeImage.configured?`.
|
|
26
|
+
|
|
27
|
+
### Changed
|
|
28
|
+
|
|
29
|
+
- **Backend selection is strict.** `:auto` is gone; nothing ever falls back
|
|
30
|
+
from libvips to ImageMagick. A format the configured backend cannot decode
|
|
31
|
+
(e.g. ICO transform input on `:vips`, HEIC on a libvips build without
|
|
32
|
+
libheif) raises `UnsupportedFormatError`. Pure-Ruby format handling (SVG
|
|
33
|
+
metadata/sanitising, ICO metadata) still works on either backend.
|
|
34
|
+
- **cjpegli is availability-driven**, like the optimizer tools: installed
|
|
35
|
+
means used for JPEG output on the `:vips` backend, absent means libvips
|
|
36
|
+
encodes. It is no longer a per-call or configuration choice.
|
|
37
|
+
- Sandbox worker processes inherit the parent's backend and pixel-ceiling
|
|
38
|
+
configuration through the request payload (landlock is forced off inside
|
|
39
|
+
the worker, so sandboxed operations never nest).
|
|
40
|
+
- `dominant_color` on the `:imagemagick` backend now decodes ICO through
|
|
41
|
+
ImageMagick; the pure-Ruby ICO decoder serves the `:vips` backend.
|
|
42
|
+
|
|
43
|
+
### Removed
|
|
44
|
+
|
|
45
|
+
- Per-call `backend:` keyword on `resize`, `crop`, `downsize`, `convert`,
|
|
46
|
+
`thumbnail`, `letter_avatar`, `fix_orientation` and `dominant_color`.
|
|
47
|
+
- Per-call `encoder:` keyword on `convert`, `convert_to_jpeg`, `thumbnail`,
|
|
48
|
+
`resize`, `crop` and `downsize`.
|
|
49
|
+
- `execution:` keyword on `thumbnail` (`:sandbox` / `:sandbox_if_available`)
|
|
50
|
+
— sandboxing is decided by `configure!(landlock:)`.
|
|
51
|
+
- `SafeImage.enable_sandbox!`, `SafeImage.disable_sandbox!`,
|
|
52
|
+
`SafeImage.sandbox_enabled?` and `SafeImage.sandbox_call` —
|
|
53
|
+
`SafeImage.sandbox_available?` remains as the pre-configuration probe.
|
|
54
|
+
|
|
55
|
+
## [0.1.1] - 2026-06-10
|
|
56
|
+
|
|
57
|
+
### Added
|
|
58
|
+
|
|
59
|
+
- `SafeImage.dominant_color` and `SafeImage.remote_dominant_color`: alpha-weighted
|
|
60
|
+
average colour as an `RRGGBB` hex string, computed natively through libvips,
|
|
61
|
+
with an ImageMagick histogram backend (`backend: :imagemagick`) matching the
|
|
62
|
+
command Discourse runs.
|
|
63
|
+
- Pure-Ruby ICO support: directory parsing for `probe`/`frame_count`,
|
|
64
|
+
largest-entry favicon extraction for `convert_favicon_to_png` (embedded PNG
|
|
65
|
+
payloads are sanitised by re-encoding; legacy DIB payloads decode 1/4/8/24/32bpp
|
|
66
|
+
with the AND mask), and decompression-bomb caps enforced from the container
|
|
67
|
+
and IHDR headers before any decode.
|
|
68
|
+
- Native GIF decode through libvips' bundled libnsgif loader (first frame,
|
|
69
|
+
matching the ImageMagick `[0]` semantics) and GIF output through cgif.
|
|
70
|
+
- JPEG XL support end to end: native libvips loader/saver, ImageMagick coder
|
|
71
|
+
and policy allowlisting, remote content-type/extension handling, and a
|
|
72
|
+
committed test fixture.
|
|
73
|
+
- Native letter avatars rendered through libvips' Pango text support with the
|
|
74
|
+
glyph blended in one linear transform; the gem now bundles DejaVu Sans (see
|
|
75
|
+
`lib/safe_image/fonts/DEJAVU-LICENSE`) so the default font renders
|
|
76
|
+
identically on every host with no font packages installed.
|
|
77
|
+
- Header-only native metadata reads: `frame_count`/`animated?` from the
|
|
78
|
+
n-pages field and `orientation` from the orientation field — no pixel decode
|
|
79
|
+
and no `identify` subprocess for natively supported formats.
|
|
80
|
+
- `fix_orientation` lossless tier: MCU-aligned JPEGs are transformed with
|
|
81
|
+
`jpegtran` (zero generation loss) when installed, falling back to a libvips
|
|
82
|
+
re-encode with a new `quality:` keyword (default 95).
|
|
83
|
+
- Native `convert` tier: decode through the allowlisted loaders,
|
|
84
|
+
auto-orient, flatten transparency onto white for JPEG targets (matching the
|
|
85
|
+
ImageMagick path), re-encode; default JPEG quality 92 when unspecified.
|
|
86
|
+
- `backend:` keyword (`:auto` / `:vips` / `:imagemagick`) across
|
|
87
|
+
`resize`, `crop`, `downsize`, `convert`, `thumbnail`, `letter_avatar`,
|
|
88
|
+
`fix_orientation` and `dominant_color`. `:auto` prefers the native path and
|
|
89
|
+
uses ImageMagick only for capabilities libvips cannot serve; `:vips` fails
|
|
90
|
+
closed; `:imagemagick` pins the compatibility pipeline.
|
|
91
|
+
- Graceful operation without libvips: the new `SafeImage::VipsUnavailableError`
|
|
92
|
+
(a subclass of `UnsupportedFormatError`) makes `backend: :auto` route through
|
|
93
|
+
ImageMagick on hosts without the library, while explicit `backend: :vips`
|
|
94
|
+
calls fail closed. `SafeImage::VipsGlue.available?` reports the state.
|
|
95
|
+
- `docker/run.sh`: containerised validation against Debian bookworm's packaged
|
|
96
|
+
libvips 8.14 with no toolchain installed.
|
|
97
|
+
|
|
98
|
+
### Changed
|
|
99
|
+
|
|
100
|
+
- **The compiled C extension is gone.** libvips is now bound at runtime
|
|
101
|
+
through a minimal Fiddle binding (`SafeImage::VipsGlue`) that exposes only
|
|
102
|
+
the operations the gem invokes. Nothing compiles at gem install time; the
|
|
103
|
+
only gem dependencies are `fiddle` and `rexml`. Minimum libvips is 8.13
|
|
104
|
+
(Debian bookworm's 8.14 package is tested); `SAFE_IMAGE_LIBVIPS` overrides
|
|
105
|
+
the library name.
|
|
106
|
+
- All transform defaults are now native-first: `resize`, `crop`, `downsize`,
|
|
107
|
+
`convert` and `thumbnail` default to `backend: :auto` (previously
|
|
108
|
+
ImageMagick for the first three and `:vips` fail-closed for `thumbnail`).
|
|
109
|
+
Pin `backend: :imagemagick` where byte-similar output with previously
|
|
110
|
+
generated thumbnails matters.
|
|
111
|
+
- The default letter avatar font is now `DejaVu-Sans` (bundled), and the
|
|
112
|
+
native renderer centres the glyph's ink box optically; the ImageMagick
|
|
113
|
+
path's baseline placement (which could clip descenders) remains available
|
|
114
|
+
via `backend: :imagemagick`. Regenerate cached avatars when switching.
|
|
115
|
+
- `convert_favicon_to_png` extracts the largest ICO entry rather than the
|
|
116
|
+
last one, and `probe` on ICO reports the largest entry's dimensions from a
|
|
117
|
+
pure-Ruby directory parse.
|
|
118
|
+
- libvips' GLib warnings about rejected input (e.g. "Not a PNG file") are
|
|
119
|
+
suppressed by default — failures already surface as exceptions; set
|
|
120
|
+
`SAFE_IMAGE_VIPS_WARNINGS=1` to restore them for debugging.
|
|
121
|
+
|
|
122
|
+
### Fixed
|
|
123
|
+
|
|
124
|
+
- `resize`/`crop`/`downsize`/`convert` with `optimize: true` no longer raise
|
|
125
|
+
for output formats the optimizer tools cannot handle (GIF, JPEG XL); the
|
|
126
|
+
optimize pass is skipped instead.
|
|
127
|
+
- In-place `fix_orientation` writes through a sibling tempfile and renames,
|
|
128
|
+
so libvips never streams its input into itself.
|
|
129
|
+
- Garbage EXIF orientation tags clamp to the valid 1–8 range instead of
|
|
130
|
+
leaking raw values.
|
|
131
|
+
- The Landlock sandbox grants read access to Ruby's `libdir`, so workers no
|
|
132
|
+
longer fail to start under `--enable-shared` Rubies installed outside the
|
|
133
|
+
default read roots (e.g. GitHub Actions' hostedtoolcache builds). Sandbox
|
|
134
|
+
failures now include the child's stderr in the error message.
|
|
135
|
+
|
|
136
|
+
### Security
|
|
137
|
+
|
|
138
|
+
- The bundled ImageMagick `policy.xml` gained write-only `HISTOGRAM`/`INFO`
|
|
139
|
+
coders (for the dominant-colour backend) and the `JXL` coder, plus a
|
|
140
|
+
regression test asserting the policy parses completely — ImageMagick's
|
|
141
|
+
hand-rolled tokenizer silently truncates the file on a stray backtick or
|
|
142
|
+
apostrophe in a comment.
|
|
143
|
+
- The libjxl loader/saver are deliberately re-enabled from libvips'
|
|
144
|
+
untrusted-operation block: JPEG XL is part of the supported input surface,
|
|
145
|
+
and inputs still pass extension routing, pixel caps and (optionally) the
|
|
146
|
+
Landlock sandbox.
|
|
147
|
+
- The Fiddle binding doubles as an operation allowlist, and a leak-loop test
|
|
148
|
+
guards GObject reference handling.
|
|
149
|
+
|
|
150
|
+
## [0.1.0] - 2026-06-09
|
|
151
|
+
|
|
152
|
+
Initial release: hardened image-processing boundary for untrusted uploads.
|
|
153
|
+
Probing and metadata helpers, thumbnails/resize/crop/downsize/convert through
|
|
154
|
+
a native libvips fast path with an ImageMagick compatibility backend under a
|
|
155
|
+
restrictive bundled policy, JPEG/PNG optimisation (jpegoptim/oxipng/pngquant),
|
|
156
|
+
optional Jpegli encoding, allowlist-based SVG sanitising, SSRF-hardened remote
|
|
157
|
+
fetching with DNS pinning, symlink-safe path handling, 128MP default pixel
|
|
158
|
+
caps, and optional atomic Landlock sandboxing.
|