feat(privacy): strip EXIF/XMP/IPTC from image uploads by default (#109)
CI / Build & Quality Checks (push) Canceled after 0s
CI / Trigger Desktop Build (push) Canceled after 0s
CI / Secret scan (gitleaks) (push) Canceled after 0s
CI / Docker image build & smoke test (push) Canceled after 0s
CI / Playwright smoke (e2e) (push) Canceled after 0s
CI / Build & Quality Checks (push) Canceled after 0s
CI / Trigger Desktop Build (push) Canceled after 0s
CI / Secret scan (gitleaks) (push) Canceled after 0s
CI / Docker image build & smoke test (push) Canceled after 0s
CI / Playwright smoke (e2e) (push) Canceled after 0s
Metadata was only dropped as a side effect of opt-in compression, so a phone photo carried its GPS fix, camera model and timestamp into the room and the media store. utils/stripImageMetadata.ts now removes it at the container level, without touching pixels: JPEG drops APP1/APP13/COM (writing back a minimal EXIF holding only Orientation when it isn't 1, so sideways-stored photos still display upright), PNG drops eXIf and the text chunks XMP lives in, WebP drops EXIF/XMP and clears the VP8X flags. Other types pass through. Applied before encryption on every composer path (attach, paste, drop, share target) and to user/room avatar picks; GIF upload is excluded. Setting → General → Privacy "Remove Photo Metadata Before Sending", default on. The upload card says "Photo metadata removed". Unit tests on generated fixtures with a GPS IFD (JPEG orientation 6, JPEG + comment, PNG with eXIf + XMP, WebP with EXIF); verified end to end: the bytes stored by Synapse decode fine and carry only Orientation. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01PPmy3tPq869XDW4njjVaKA
This commit is contained in:
@@ -15,6 +15,8 @@ import {
|
||||
} from 'folds';
|
||||
import { Room } from 'matrix-js-sdk';
|
||||
import { useAtomValue, useStore } from 'jotai';
|
||||
import { useSetting } from '../../../state/hooks/settings';
|
||||
import { settingsAtom } from '../../../state/settings';
|
||||
import { Page, PageContent, PageHeader } from '../../../components/page';
|
||||
import { useMatrixClient } from '../../../hooks/useMatrixClient';
|
||||
import { useMediaAuthentication } from '../../../hooks/useMediaAuthentication';
|
||||
@@ -53,6 +55,7 @@ export function Share() {
|
||||
const [shared, setShared] = useState<Shared | null | undefined>(undefined);
|
||||
const [query, setQuery] = useState('');
|
||||
const [busy, setBusy] = useState(false);
|
||||
const [stripImageMetadata] = useSetting(settingsAtom, 'stripImageMetadata');
|
||||
|
||||
useEffect(() => {
|
||||
readSharedPayload()
|
||||
@@ -80,7 +83,7 @@ export function Share() {
|
||||
if (shared.files.length) {
|
||||
store.set(roomIdToUploadItemsAtomFamily(room.roomId), {
|
||||
type: 'PUT',
|
||||
item: await filesToUploadItems(room, shared.files),
|
||||
item: await filesToUploadItems(room, shared.files, stripImageMetadata),
|
||||
});
|
||||
}
|
||||
const text = shareDraftText(shared.payload);
|
||||
@@ -95,7 +98,7 @@ export function Share() {
|
||||
setBusy(false);
|
||||
}
|
||||
},
|
||||
[shared, busy, navigateRoom, store],
|
||||
[shared, busy, navigateRoom, store, stripImageMetadata],
|
||||
);
|
||||
|
||||
return (
|
||||
|
||||
Reference in New Issue
Block a user