An image is checked before it is decoded
An upload went straight to libvips: whatever loader recognised the bytes ran (an SVG sent as image/png was rasterised), nothing bounded how many pixels it would decode to (a small PNG could decode to gigabytes, three times over), a GIF was loaded frame by frame and never resized, and all of it ran inside the request with nothing limiting how many at once. A GIF was typed gifv but stayed a .gif, which a gifv player can't play; its metadata was kept; colours lost their ICC profile without being converted; HEIC was advertised but the bundled libvips can't decode it. Now: - only libvips' JPEG, PNG, GIF, WebP and HEIF loaders ever run on an upload (every other loader is blocked); - the header alone says how big an image would decode, refused above Media:MaxPixels (40 MP) or MaxFrames; - a still image is shrunk on load, turned by its orientation and brought into sRGB (thumbnail), then written without metadata, a profile picture the same way; - an animated GIF becomes a looping silent H.264 mp4 typed gifv, as on Mastodon (PostMedia.Kind keeps it a gifv), and a remote GIF is an image; - processing runs Media:Concurrency at a time, and uploads have their own rate limit per credential; - HEIC and HEIF are no longer offered. Tests: only the upload formats load, the header tells the size, an SVG posing as a PNG and an image too large are refused before decoding, an animated GIF becomes a gifv and a still one an image, HEIC isn't advertised. The media scenarios against the pasture (GoToSocial, Mastodon, Misskey, Akkoma, Pixelfed, Smithereen, Vernissage, Castopod, PeerTube) pass: 329 checks. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01LsXgEaXee4GCU1hwYgPJXw
This commit is contained in:
1 parent
65938bb2a0
commit
e4f9d0b61e
14 files changed
+284
-55
No files matched your search
@@ -15,12 +15,15 @@ namespace PrivaPub.Infrastructure
|
||||
public int AccountsPerMinute { get; set; } = 10;//sign-ups, sign-ins and recoveries per client address
|
||||
public int InboxBurst { get; set; } = 300;//deliveries a sending origin may make at once
|
||||
public int InboxPerTenSeconds { get; set; } = 50;//and the rate it earns them back
|
||||
public int UploadsBurst { get; set; } = 30;//uploads (media, profile pictures) a session may make at once
|
||||
public int UploadsPerMinute { get; set; } = 10;//and the rate it earns them back
|
||||
}
|
||||
|
||||
public static class RateLimiting
|
||||
{
|
||||
public const string Accounts = "accounts";
|
||||
public const string Inbox = "inbox";
|
||||
public const string Uploads = "uploads";
|
||||
|
||||
static RateLimitOptions Limits(HttpContext context) => context.RequestServices.GetRequiredService<IOptions<RateLimitOptions>>().Value;
|
||||
|
||||
@@ -59,8 +62,27 @@ namespace PrivaPub.Infrastructure
|
||||
ReplenishmentPeriod = TimeSpan.FromSeconds(10),
|
||||
QueueLimit = 0
|
||||
}));
|
||||
// per session: the limiter runs before authentication, so the credential sent stands for whoever sends it
|
||||
options.AddPolicy(Uploads, context => RateLimitPartition.GetTokenBucketLimiter(
|
||||
Credential(context.Request) ?? "anonymous:" + context.Connection.RemoteIpAddress,
|
||||
_ => new TokenBucketRateLimiterOptions
|
||||
{
|
||||
TokenLimit = Limits(context).UploadsBurst,
|
||||
TokensPerPeriod = Limits(context).UploadsPerMinute,
|
||||
ReplenishmentPeriod = TimeSpan.FromMinutes(1),
|
||||
QueueLimit = 0
|
||||
}));
|
||||
});
|
||||
|
||||
// a hash of the credential, never the credential itself
|
||||
static string Credential(HttpRequest request)
|
||||
{
|
||||
var authorization = request.Headers.Authorization.ToString();
|
||||
return string.IsNullOrEmpty(authorization)
|
||||
? default
|
||||
: Convert.ToHexStringLower(System.Security.Cryptography.SHA256.HashData(System.Text.Encoding.UTF8.GetBytes(authorization)))[..32];
|
||||
}
|
||||
|
||||
static string SenderOrigin(HttpRequest request)
|
||||
{
|
||||
var signature = request.Headers["Signature"].ToString();
|
||||
|
||||
Reference in new issue
Block a user