feat: multi-arch Docker support, security hardening, and test improvements

Remove hardcoded --platform=linux/amd64 from Dockerfile so buildx produces
native arm64 images for Apple Silicon and Raspberry Pi. Add audit logging
for auth events, harden file storage with extension whitelists and
double-extension attack prevention, reject null-byte buffers in validation,
add data-testid attributes to all tool settings components, update
deployment docs with architecture notes and correct CI workflow references,
and fix unit test mock to match throwWithMessage error extraction.
This commit is contained in:
Siddharth Kumar Sah
2026-03-28 11:19:09 +08:00
parent 8f09c0678b
commit 6cfa3b0c38
48 changed files with 254 additions and 13 deletions
@@ -63,6 +63,7 @@ export function BarcodeReadSettings() {
<button
type="button"
data-testid="barcode-read-submit"
onClick={handleProcess}
disabled={!hasFile || processing}
className="w-full py-2.5 rounded-lg bg-primary text-primary-foreground font-medium disabled:opacity-50 disabled:cursor-not-allowed flex items-center justify-center gap-2"
@@ -92,6 +92,7 @@ export function BlurFacesSettings() {
) : (
<button
type="button"
data-testid="blur-faces-submit"
onClick={handleProcess}
disabled={!hasFile || processing}
className="w-full py-2.5 rounded-lg bg-primary text-primary-foreground font-medium disabled:opacity-50 disabled:cursor-not-allowed flex items-center justify-center gap-2"
@@ -105,6 +106,7 @@ export function BlurFacesSettings() {
<a
href={downloadUrl}
download
data-testid="blur-faces-download"
className="w-full py-2.5 rounded-lg border border-primary text-primary font-medium flex items-center justify-center gap-2 hover:bg-primary/5"
>
<Download className="h-4 w-4" />
@@ -142,6 +142,7 @@ export function BorderSettings() {
) : (
<button
type="button"
data-testid="border-submit"
onClick={handleProcess}
disabled={!hasFile || processing}
className="w-full py-2.5 rounded-lg bg-primary text-primary-foreground font-medium disabled:opacity-50 disabled:cursor-not-allowed flex items-center justify-center gap-2"
@@ -154,6 +155,7 @@ export function BorderSettings() {
<a
href={downloadUrl}
download
data-testid="border-download"
className="w-full py-2.5 rounded-lg border border-primary text-primary font-medium flex items-center justify-center gap-2 hover:bg-primary/5"
>
<Download className="h-4 w-4" />
@@ -124,6 +124,7 @@ export function BulkRenameSettings() {
<button
type="button"
data-testid="bulk-rename-submit"
onClick={handleProcess}
disabled={!hasFiles || processing || !pattern}
className="w-full py-2.5 rounded-lg bg-primary text-primary-foreground font-medium disabled:opacity-50 disabled:cursor-not-allowed flex items-center justify-center gap-2"
@@ -128,6 +128,7 @@ export function CollageSettings() {
<button
type="button"
data-testid="collage-submit"
onClick={handleProcess}
disabled={!hasFiles || processing}
className="w-full py-2.5 rounded-lg bg-primary text-primary-foreground font-medium disabled:opacity-50 disabled:cursor-not-allowed flex items-center justify-center gap-2"
@@ -140,6 +141,7 @@ export function CollageSettings() {
<a
href={downloadUrl}
download
data-testid="collage-download"
className="w-full py-2.5 rounded-lg border border-primary text-primary font-medium flex items-center justify-center gap-2 hover:bg-primary/5"
>
<Download className="h-4 w-4" />
@@ -58,6 +58,7 @@ export function ColorPaletteSettings() {
<div className="space-y-4">
<button
type="button"
data-testid="color-palette-submit"
onClick={handleProcess}
disabled={!hasFile || processing}
className="w-full py-2.5 rounded-lg bg-primary text-primary-foreground font-medium disabled:opacity-50 disabled:cursor-not-allowed flex items-center justify-center gap-2"
@@ -260,6 +260,7 @@ export function ColorSettings({ toolId, onPreviewFilter }: ColorSettingsProps) {
) : (
<button
type="submit"
data-testid={`${toolId}-submit`}
disabled={!hasFile || !hasChanges || processing}
className="w-full py-2.5 rounded-lg bg-primary text-primary-foreground font-medium disabled:opacity-50 disabled:cursor-not-allowed flex items-center justify-center gap-2"
>
@@ -272,6 +273,7 @@ export function ColorSettings({ toolId, onPreviewFilter }: ColorSettingsProps) {
<a
href={downloadUrl}
download
data-testid={`${toolId}-download`}
className="w-full py-2.5 rounded-lg border border-primary text-primary font-medium flex items-center justify-center gap-2 hover:bg-primary/5"
>
<Download className="h-4 w-4" />
@@ -89,6 +89,7 @@ export function CompareSettings() {
<button
type="button"
data-testid="compare-submit"
onClick={handleProcess}
disabled={!hasFile || !secondFile || processing}
className="w-full py-2.5 rounded-lg bg-primary text-primary-foreground font-medium disabled:opacity-50 disabled:cursor-not-allowed flex items-center justify-center gap-2"
@@ -101,6 +102,7 @@ export function CompareSettings() {
<a
href={downloadUrl}
download
data-testid="compare-download"
className="w-full py-2.5 rounded-lg border border-primary text-primary font-medium flex items-center justify-center gap-2 hover:bg-primary/5"
>
<Download className="h-4 w-4" />
@@ -164,6 +164,7 @@ export function ComposeSettings() {
<button
type="button"
data-testid="compose-submit"
onClick={handleProcess}
disabled={!hasFile || !overlayFile || processing}
className="w-full py-2.5 rounded-lg bg-primary text-primary-foreground font-medium disabled:opacity-50 disabled:cursor-not-allowed flex items-center justify-center gap-2"
@@ -176,6 +177,7 @@ export function ComposeSettings() {
<a
href={downloadUrl}
download
data-testid="compose-download"
className="w-full py-2.5 rounded-lg border border-primary text-primary font-medium flex items-center justify-center gap-2 hover:bg-primary/5"
>
<Download className="h-4 w-4" />
@@ -134,6 +134,7 @@ export function CompressSettings() {
) : (
<button
type="submit"
data-testid="compress-submit"
disabled={!hasFile || !canProcess || processing}
className="w-full py-2.5 rounded-lg bg-primary text-primary-foreground font-medium disabled:opacity-50 disabled:cursor-not-allowed flex items-center justify-center gap-2"
>
@@ -146,6 +147,7 @@ export function CompressSettings() {
<a
href={downloadUrl}
download
data-testid="compress-download"
className="w-full py-2.5 rounded-lg border border-primary text-primary font-medium flex items-center justify-center gap-2 hover:bg-primary/5"
>
<Download className="h-4 w-4" />
@@ -130,6 +130,7 @@ export function ConvertSettings() {
) : (
<button
type="submit"
data-testid="convert-submit"
disabled={!hasFile || processing}
className="w-full py-2.5 rounded-lg bg-primary text-primary-foreground font-medium disabled:opacity-50 disabled:cursor-not-allowed flex items-center justify-center gap-2"
>
@@ -142,6 +143,7 @@ export function ConvertSettings() {
<a
href={downloadUrl}
download
data-testid="convert-download"
className="w-full py-2.5 rounded-lg border border-primary text-primary font-medium flex items-center justify-center gap-2 hover:bg-primary/5"
>
<Download className="h-4 w-4" />
@@ -288,6 +288,7 @@ export function CropSettings({
) : (
<button
type="submit"
data-testid="crop-submit"
disabled={!hasFile || !hasSize || processing}
className="w-full py-2.5 rounded-lg bg-primary text-primary-foreground font-medium disabled:opacity-50 disabled:cursor-not-allowed flex items-center justify-center gap-2"
>
@@ -300,6 +301,7 @@ export function CropSettings({
<a
href={downloadUrl}
download
data-testid="crop-download"
className="w-full py-2.5 rounded-lg border border-primary text-primary font-medium flex items-center justify-center gap-2 hover:bg-primary/5"
>
<Download className="h-4 w-4" />
@@ -196,6 +196,7 @@ export function EraseObjectSettings({
) : (
<button
type="button"
data-testid="erase-object-submit"
onClick={handleProcess}
disabled={!hasFile || !hasStrokes || processing}
className="w-full py-2.5 rounded-lg bg-primary text-primary-foreground font-medium disabled:opacity-50 disabled:cursor-not-allowed flex items-center justify-center gap-2"
@@ -209,6 +210,7 @@ export function EraseObjectSettings({
<a
href={downloadUrl}
download
data-testid="erase-object-download"
className="w-full py-2.5 rounded-lg border border-primary text-primary font-medium flex items-center justify-center gap-2 hover:bg-primary/5"
>
<Download className="h-4 w-4" />
@@ -83,6 +83,7 @@ export function FaviconSettings() {
<button
type="button"
data-testid="favicon-submit"
onClick={handleProcess}
disabled={!hasFile || processing}
className="w-full py-2.5 rounded-lg bg-primary text-primary-foreground font-medium disabled:opacity-50 disabled:cursor-not-allowed flex items-center justify-center gap-2"
@@ -63,6 +63,7 @@ export function FindDuplicatesSettings() {
<button
type="button"
data-testid="find-duplicates-submit"
onClick={handleProcess}
disabled={!hasFiles || processing}
className="w-full py-2.5 rounded-lg bg-primary text-primary-foreground font-medium disabled:opacity-50 disabled:cursor-not-allowed flex items-center justify-center gap-2"
@@ -134,6 +134,7 @@ export function GifToolsSettings() {
) : (
<button
type="button"
data-testid="gif-tools-submit"
onClick={handleProcess}
disabled={!hasFile || processing}
className="w-full py-2.5 rounded-lg bg-primary text-primary-foreground font-medium disabled:opacity-50 disabled:cursor-not-allowed flex items-center justify-center gap-2"
@@ -146,6 +147,7 @@ export function GifToolsSettings() {
<a
href={downloadUrl}
download
data-testid="gif-tools-download"
className="w-full py-2.5 rounded-lg border border-primary text-primary font-medium flex items-center justify-center gap-2 hover:bg-primary/5"
>
<Download className="h-4 w-4" />
@@ -229,6 +229,7 @@ export function ImageToPdfSettings() {
<button
type="button"
data-testid="image-to-pdf-submit"
onClick={handleProcess}
disabled={!hasFiles || processing}
className="w-full py-2.5 rounded-lg bg-primary text-primary-foreground font-medium disabled:opacity-50 disabled:cursor-not-allowed flex items-center justify-center gap-2"
@@ -241,6 +242,7 @@ export function ImageToPdfSettings() {
<a
href={downloadUrl}
download
data-testid="image-to-pdf-download"
className="w-full py-2.5 rounded-lg border border-primary text-primary font-medium flex items-center justify-center gap-2 hover:bg-primary/5"
>
<Download className="h-4 w-4" />
@@ -80,6 +80,7 @@ export function InfoSettings() {
<div className="space-y-4">
<button
type="button"
data-testid="info-submit"
onClick={handleProcess}
disabled={!hasFile || processing}
className="w-full py-2.5 rounded-lg bg-primary text-primary-foreground font-medium disabled:opacity-50 disabled:cursor-not-allowed flex items-center justify-center gap-2"
@@ -191,6 +191,7 @@ export function OcrSettings() {
) : (
<button
type="button"
data-testid="ocr-submit"
onClick={handleProcess}
disabled={!hasFile || processing}
className="w-full py-2.5 rounded-lg bg-primary text-primary-foreground font-medium disabled:opacity-50 disabled:cursor-not-allowed flex items-center justify-center gap-2"
@@ -132,6 +132,7 @@ export function QrGenerateSettings() {
<button
type="button"
data-testid="qr-generate-submit"
onClick={handleGenerate}
disabled={!text || processing}
className="w-full py-2.5 rounded-lg bg-primary text-primary-foreground font-medium disabled:opacity-50 disabled:cursor-not-allowed flex items-center justify-center gap-2"
@@ -151,6 +152,7 @@ export function QrGenerateSettings() {
<a
href={downloadUrl ?? undefined}
download
data-testid="qr-generate-download"
className="w-full py-2.5 rounded-lg border border-primary text-primary font-medium flex items-center justify-center gap-2 hover:bg-primary/5"
>
<Download className="h-4 w-4" />
@@ -200,6 +200,7 @@ export function RemoveBgSettings() {
) : (
<button
type="button"
data-testid="remove-background-submit"
onClick={handleProcess}
disabled={!hasFile || processing}
className="w-full py-2.5 rounded-lg bg-primary text-primary-foreground font-medium disabled:opacity-50 disabled:cursor-not-allowed flex items-center justify-center gap-2"
@@ -213,6 +214,7 @@ export function RemoveBgSettings() {
<a
href={downloadUrl}
download
data-testid="remove-background-download"
className="w-full py-2.5 rounded-lg border border-primary text-primary font-medium flex items-center justify-center gap-2 hover:bg-primary/5"
>
<Download className="h-4 w-4" />
@@ -122,6 +122,7 @@ export function ReplaceColorSettings() {
) : (
<button
type="button"
data-testid="replace-color-submit"
onClick={handleProcess}
disabled={!hasFile || processing}
className="w-full py-2.5 rounded-lg bg-primary text-primary-foreground font-medium disabled:opacity-50 disabled:cursor-not-allowed flex items-center justify-center gap-2"
@@ -134,6 +135,7 @@ export function ReplaceColorSettings() {
<a
href={downloadUrl}
download
data-testid="replace-color-download"
className="w-full py-2.5 rounded-lg border border-primary text-primary font-medium flex items-center justify-center gap-2 hover:bg-primary/5"
>
<Download className="h-4 w-4" />
@@ -260,6 +260,7 @@ export function ResizeSettings() {
) : (
<button
type="submit"
data-testid="resize-submit"
disabled={!canProcess}
className="w-full py-2.5 rounded-lg bg-primary text-primary-foreground font-medium disabled:opacity-50 disabled:cursor-not-allowed flex items-center justify-center gap-2"
>
@@ -272,6 +273,7 @@ export function ResizeSettings() {
<a
href={downloadUrl}
download
data-testid="resize-download"
className="w-full py-2.5 rounded-lg border border-primary text-primary font-medium flex items-center justify-center gap-2 hover:bg-primary/5"
>
<Download className="h-4 w-4" />
@@ -105,6 +105,7 @@ export function RotateSettings({ onPreviewTransform }: RotateSettingsProps) {
<div className="flex items-center gap-2 mt-1">
<button
type="button"
data-testid="rotate-left"
onClick={rotateLeft}
className="flex-1 flex items-center justify-center gap-1.5 py-2.5 rounded-lg bg-muted text-muted-foreground hover:bg-primary hover:text-primary-foreground transition-colors text-sm font-medium"
title="Rotate 90° counter-clockwise"
@@ -149,6 +150,7 @@ export function RotateSettings({ onPreviewTransform }: RotateSettingsProps) {
</button>
<button
type="button"
data-testid="rotate-right"
onClick={rotateRight}
className="flex-1 flex items-center justify-center gap-1.5 py-2.5 rounded-lg bg-muted text-muted-foreground hover:bg-primary hover:text-primary-foreground transition-colors text-sm font-medium"
title="Rotate 90° clockwise"
@@ -193,6 +195,7 @@ export function RotateSettings({ onPreviewTransform }: RotateSettingsProps) {
<div className="flex gap-2 mt-1">
<button
type="button"
data-testid="rotate-flip-h"
onClick={() => setFlipH(!flipH)}
className={`flex-1 flex items-center justify-center gap-1.5 py-2.5 rounded-lg text-sm font-medium transition-colors ${
flipH
@@ -205,6 +208,7 @@ export function RotateSettings({ onPreviewTransform }: RotateSettingsProps) {
</button>
<button
type="button"
data-testid="rotate-flip-v"
onClick={() => setFlipV(!flipV)}
className={`flex-1 flex items-center justify-center gap-1.5 py-2.5 rounded-lg text-sm font-medium transition-colors ${
flipV
@@ -245,6 +249,7 @@ export function RotateSettings({ onPreviewTransform }: RotateSettingsProps) {
) : (
<button
type="submit"
data-testid="rotate-submit"
disabled={!hasFile || !hasChanges || processing}
className="w-full py-2.5 rounded-lg bg-primary text-primary-foreground font-medium disabled:opacity-50 disabled:cursor-not-allowed flex items-center justify-center gap-2"
>
@@ -129,6 +129,7 @@ export function SmartCropSettings() {
) : (
<button
type="button"
data-testid="smart-crop-submit"
onClick={handleProcess}
disabled={!hasFile || !canProcess || processing}
className="w-full py-2.5 rounded-lg bg-primary text-primary-foreground font-medium disabled:opacity-50 disabled:cursor-not-allowed flex items-center justify-center gap-2"
@@ -142,6 +143,7 @@ export function SmartCropSettings() {
<a
href={downloadUrl}
download
data-testid="smart-crop-download"
className="w-full py-2.5 rounded-lg border border-primary text-primary font-medium flex items-center justify-center gap-2 hover:bg-primary/5"
>
<Download className="h-4 w-4" />
@@ -118,6 +118,7 @@ export function SplitSettings() {
<button
type="button"
data-testid="split-submit"
onClick={handleProcess}
disabled={!hasFile || processing}
className="w-full py-2.5 rounded-lg bg-primary text-primary-foreground font-medium disabled:opacity-50 disabled:cursor-not-allowed flex items-center justify-center gap-2"
@@ -462,6 +462,7 @@ export function StripMetadataSettings() {
) : (
<button
type="submit"
data-testid="strip-metadata-submit"
disabled={!hasFile || processing}
className="w-full py-2.5 rounded-lg bg-primary text-primary-foreground font-medium disabled:opacity-50 disabled:cursor-not-allowed flex items-center justify-center gap-2"
>
@@ -474,6 +475,7 @@ export function StripMetadataSettings() {
<a
href={downloadUrl}
download
data-testid="strip-metadata-download"
className="w-full py-2.5 rounded-lg border border-primary text-primary font-medium flex items-center justify-center gap-2 hover:bg-primary/5"
>
<Download className="h-4 w-4" />
@@ -148,6 +148,7 @@ export function SvgToRasterSettings() {
<button
type="button"
data-testid="svg-to-raster-submit"
onClick={handleProcess}
disabled={!hasFile || processing}
className="w-full py-2.5 rounded-lg bg-primary text-primary-foreground font-medium disabled:opacity-50 disabled:cursor-not-allowed flex items-center justify-center gap-2"
@@ -160,6 +161,7 @@ export function SvgToRasterSettings() {
<a
href={downloadUrl}
download
data-testid="svg-to-raster-download"
className="w-full py-2.5 rounded-lg border border-primary text-primary font-medium flex items-center justify-center gap-2 hover:bg-primary/5"
>
<Download className="h-4 w-4" />
@@ -154,6 +154,7 @@ export function TextOverlaySettings() {
) : (
<button
type="button"
data-testid="text-overlay-submit"
onClick={handleProcess}
disabled={!hasFile || processing || !text}
className="w-full py-2.5 rounded-lg bg-primary text-primary-foreground font-medium disabled:opacity-50 disabled:cursor-not-allowed flex items-center justify-center gap-2"
@@ -166,6 +167,7 @@ export function TextOverlaySettings() {
<a
href={downloadUrl}
download
data-testid="text-overlay-download"
className="w-full py-2.5 rounded-lg border border-primary text-primary font-medium flex items-center justify-center gap-2 hover:bg-primary/5"
>
<Download className="h-4 w-4" />
@@ -77,6 +77,7 @@ export function UpscaleSettings() {
) : (
<button
type="button"
data-testid="upscale-submit"
onClick={handleProcess}
disabled={!hasFile || processing}
className="w-full py-2.5 rounded-lg bg-primary text-primary-foreground font-medium disabled:opacity-50 disabled:cursor-not-allowed flex items-center justify-center gap-2"
@@ -90,6 +91,7 @@ export function UpscaleSettings() {
<a
href={downloadUrl}
download
data-testid="upscale-download"
className="w-full py-2.5 rounded-lg border border-primary text-primary font-medium flex items-center justify-center gap-2 hover:bg-primary/5"
>
<Download className="h-4 w-4" />
@@ -122,6 +122,7 @@ export function VectorizeSettings() {
<button
type="button"
data-testid="vectorize-submit"
onClick={handleProcess}
disabled={!hasFile || processing}
className="w-full py-2.5 rounded-lg bg-primary text-primary-foreground font-medium disabled:opacity-50 disabled:cursor-not-allowed flex items-center justify-center gap-2"
@@ -134,6 +135,7 @@ export function VectorizeSettings() {
<a
href={downloadUrl}
download
data-testid="vectorize-download"
className="w-full py-2.5 rounded-lg border border-primary text-primary font-medium flex items-center justify-center gap-2 hover:bg-primary/5"
>
<Download className="h-4 w-4" />
@@ -146,6 +146,7 @@ export function WatermarkImageSettings() {
<button
type="button"
data-testid="watermark-image-submit"
onClick={handleProcess}
disabled={!hasFile || !watermarkFile || processing}
className="w-full py-2.5 rounded-lg bg-primary text-primary-foreground font-medium disabled:opacity-50 disabled:cursor-not-allowed flex items-center justify-center gap-2"
@@ -158,6 +159,7 @@ export function WatermarkImageSettings() {
<a
href={downloadUrl}
download
data-testid="watermark-image-download"
className="w-full py-2.5 rounded-lg border border-primary text-primary font-medium flex items-center justify-center gap-2 hover:bg-primary/5"
>
<Download className="h-4 w-4" />
@@ -160,6 +160,7 @@ export function WatermarkTextSettings() {
) : (
<button
type="button"
data-testid="watermark-text-submit"
onClick={handleProcess}
disabled={!hasFile || processing || !text}
className="w-full py-2.5 rounded-lg bg-primary text-primary-foreground font-medium disabled:opacity-50 disabled:cursor-not-allowed flex items-center justify-center gap-2"
@@ -172,6 +173,7 @@ export function WatermarkTextSettings() {
<a
href={downloadUrl}
download
data-testid="watermark-text-download"
className="w-full py-2.5 rounded-lg border border-primary text-primary font-medium flex items-center justify-center gap-2 hover:bg-primary/5"
>
<Download className="h-4 w-4" />