Zum Inhalt springen
StartseiteStart
EN
WhatsAppMailPhone
← Alle Artikel
Dein Build entfernt die KI-Markierung aus dem Bild
Recht & Compliance

Dein Build entfernt die KI-Markierung aus dem Bild

Foto: jakobowens1 / Unsplash

sharp strippt beim Konvertieren nach WebP und AVIF standardmaessig alle Metadaten. Astro und Next.js rufen sharp genau so auf. Was dabei verloren geht und welche Konfiguration die Markierung rettet.

Eric MengeAutorEric MengeInhaber & Webentwickler bei EMIT Solution
Veröffentlicht
Lesezeitca. 9 Min.

Kurz gesagt

  • sharp entfernt beim Encoden standardmaessig alle Metadaten. Die offizielle Doku sagt woertlich, das Standardverhalten sei 'strip all metadata, including the removal of any ICC profile'. Genau dieses Standardverhalten nutzen Astro und Next.js.
  • Der Quellcode belegt es. In packages/astro/src/assets/services/sharp.ts taucht weder keepMetadata noch keepExif, keepXmp oder keepIccProfile auf. Next.js macht in packages/next/src/server/image-optimizer.ts denselben Aufruf ohne jede keep-Methode.
  • keepMetadata() rettet EXIF, ICC, XMP und IPTC, aber kein C2PA. libvips als Engine unter sharp kennt keine JUMBF-Boxen. Das Issue libvips#4420 dazu ist seit dem 15.03.2025 offen und auf Milestone 8.19 gesetzt.
  • Art. 50 Abs. 2 AI Act verpflichtet den Anbieter des KI-Systems, nicht dich als Seitenbetreiber. Fuer dich gilt Art. 50 Abs. 4, die sichtbare Offenlegung. Der Metadatenverlust ist trotzdem dein Problem, weil er die Nachweiskette zerstoert.

Du generierst ein Hero-Bild mit einem Bildmodell, laedst es herunter, legst es in src/assets/ und baust die Seite. Astro skaliert es auf drei Breiten, konvertiert nach WebP und legt die Dateien in dist/_astro/. Was danach im Browser ankommt, ist ein Bild ohne EXIF, ohne XMP, ohne ICC-Profil und ohne C2PA-Manifest. Die Markierung, die der Anbieter beim Generieren hineingeschrieben hat, ist weg. Nicht durch einen Fehler, sondern weil es so vorgesehen ist.

Das ist kein Astro-Problem. Es ist ein sharp-Problem, und sharp steckt in fast jedem JavaScript-Buildprozess, der Bilder anfasst.

Nahaufnahme einer LED-Videowand mit farbigem Pixelraster Foto: iantalmacs / Unsplash

Vier Metadatenblocks, vier verschiedene Schicksale

Bevor es an die Pipeline geht, lohnt sich die Unterscheidung, weil sie erklaert, warum eine Rettungsmassnahme nicht alles rettet.

EXIF ist der klassische Kamerablock. Kameramodell, Belichtung, Orientierung, Copyright. Bei KI-Bildern schreiben manche Anbieter hier Herkunftsangaben hinein.

XMP ist RDF/XML und der Ort, an dem die IPTC-Properties leben. Fuer KI-Kennzeichnung ist genau eine davon relevant: Iptc4xmpExt:DigitalSourceType mit dem kontrollierten Wert http://cv.iptc.org/newscodes/digitalsourcetype/trainedAlgorithmicMedia. Die Property stammt aus IPTC Photo Metadata Version 1.1 von 2009, aktueller Standardstand ist 2025.1 vom 26.11.2025. Das ist die schlankste maschinenlesbare Markierung, die es gibt, und die einzige, die ohne Signaturzertifikat auskommt.

ICC ist das Farbprofil. Mit KI-Kennzeichnung hat es nichts zu tun, es faellt aber im selben Schritt mit weg, und das sieht man dem Bild an.

JUMBF ist die Box, in der ein C2PA-Manifest liegt. Signiert, mit Zertifikatskette, mit Hash ueber den Bildinhalt. Die C2PA-Spezifikation 2.2 fuehrt JPEG, PNG, WebP und GIF als Non-BMFF-Formate mit general box hash, HEIF und AVIF als BMFF-basierte Formate mit BMFF-based hash. WebP und AVIF koennen ein Manifest also tragen. Ob es beim Konvertieren mitkommt, ist eine andere Frage.

Und dann gibt es noch eine fuenfte Ebene, die keine Metadaten sind: pixelbasierte Wasserzeichen wie SynthID. Google beschreibt sie als darauf ausgelegt, Zuschnitte, Filter und verlustbehaftete Kompression zu ueberstehen. Weil sie im Bildinhalt sitzen und nicht in einem Header, sind sie konstruktionsbedingt robuster gegen Reencoding als jeder Metadatenblock. Eine Garantie fuer eine konkrete Buildpipeline ist das nicht, und pruefen kann man sie nur ueber Googles eigene Werkzeuge, nicht mit exiftool.

Was sharp beim Encoden macht

Die sharp-Dokumentation ist an dieser Stelle unmissverstaendlich. Zu keepMetadata heisst es: “The default behaviour, when keepMetadata is not used, is to convert to the device-independent sRGB colour space and strip all metadata, including the removal of any ICC profile.” Bei toBuffer und toFile steht zusaetzlich: “By default all metadata will be removed, which includes EXIF-based orientation.”

Alles. Nicht selektiv, nicht formatabhaengig. Wer nichts konfiguriert, bekommt ein nacktes Bild.

Seit sharp 0.33.0 gibt es dafuer Gegenmittel: keepMetadata(), keepExif(), keepIccProfile(), withExif(), withExifMerge() und withIccProfile(). XMP kam spaeter dazu, keepXmp() und withXmp() erst mit 0.34.3 vom 10.07.2025. Aktuell ist 0.35.3 vom 01.07.2026.

// sharp: die drei Zeilen, um die es geht
await sharp(input)
  .resize({ width: 1600 })
  .keepMetadata()      // seit 0.33.0: EXIF, ICC, XMP, IPTC
  .webp({ quality: 82 })
  .toFile('out.webp');

// feiner dosiert:
await sharp(input)
  .keepXmp()           // seit 0.34.3
  .keepIccProfile()    // seit 0.33.0
  .webp({ quality: 82 })
  .toBuffer();

Zwei Formatgrenzen stehen in der Doku und sind wichtig. withXmp() ist ausdruecklich auf PNG, JPEG, WebP und TIFF beschraenkt, AVIF ist nicht gelistet. Und keepExif() traegt den Hinweis, dass EXIF fuer TIFF-Ausgabe nicht unterstuetzt wird. Wer AVIF ausliefert, sollte nicht davon ausgehen, dass keepMetadata() dort dasselbe leistet wie bei WebP.

Hand haelt eine SD-Speicherkarte vor einer Kameratasche Foto: introspectivedsgn / Unsplash

Der Beweis steht im Astro-Quellcode

packages/astro/src/assets/services/sharp.ts ist ueberschaubar. Die Kette sieht so aus:

// packages/astro/src/assets/services/sharp.ts (main branch, gekuerzt)
const result = sharp(inputBuffer, {
  failOn: 'none',
  pages: -1,
  limitInputPixels: config.service.config.limitInputPixels,
});

// always call rotate to adjust for EXIF data orientation
result.rotate();

// ... resize() / flatten() ...

if (outputFormat === 'webp') {
  result.webp(encoderOptions as WebpOptions | undefined);
} else if (outputFormat === 'avif') {
  result.avif(encoderOptions as AvifOptions | undefined);
}

({ data, info } = await result.toBuffer({ resolveWithObject: true }));
// kein keepMetadata(), kein keepExif(), kein keepXmp(), kein keepIccProfile()

Kein einziger keep-Aufruf in der ganzen Datei. Das Standardverhalten von sharp greift also voll durch. Standardausgabeformat ist WebP, festgelegt als DEFAULT_OUTPUT_FORMAT = 'webp' in packages/astro/src/assets/consts.ts.

Abstellen laesst sich das ueber die Konfiguration nicht. Das ist der komplette Spielraum, den Astro fuer sharp anbietet:

export interface SharpImageServiceConfig {
  limitInputPixels?: SharpOptions['limitInputPixels'];
  kernel?: ResizeOptions['kernel'];
  jpeg?: JpegOptions;
  png?: PngOptions;
  webp?: WebpOptions;
  avif?: AvifOptions;
}

Encoder-Optionen und eine Pixelgrenze. Sonst nichts.

Next.js macht denselben Aufruf

Wer glaubt, das sei eine Astro-Eigenheit, findet in packages/next/src/server/image-optimizer.ts dieselbe Struktur:

// packages/next/src/server/image-optimizer.ts (canary, gekuerzt)
const transformer = sharp(buffer, {
  limitInputPixels,
  sequentialRead: sequentialRead ?? undefined,
})
  .timeout({ seconds: timeoutInSeconds ?? 7 })
  .rotate();

if (contentType === AVIF) {
  transformer.avif({
    quality: Math.max(Math.round(quality * (50 / 80)), 1),
    effort: 3,
  });
} else if (contentType === WEBP) {
  transformer.webp({ quality });
}

const optimizedBuffer = await transformer.toBuffer();

Kein keepMetadata, kein withMetadata, kein keepIccProfile. Der Unterschied zu Astro ist nur, wann es passiert. Astro konvertiert beim Build, Next.js beim ersten Request an den Optimizer. Das Ergebnis ist identisch. Auch Next.js konvertiert per Default nach WebP, AVIF muss explizit ueber images.formats aktiviert werden.

Das Muster ist nicht auf Node beschraenkt. Pillow listet exif, xmp und icc_profile fuer WebP und AVIF als optionale Save-Parameter, die uebergeben werden muessen. Eine automatische Uebernahme sieht die Doku nicht vor. Wer ein PNG per Pillow nach WebP schiebt und diese Parameter nicht setzt, wirft dasselbe weg wie sharp.

Bildschirm mit farbig hervorgehobenem SVG-Quellcode Foto: florianolv / Unsplash

keepMetadata rettet C2PA nicht

Hier hoert der einfache Weg auf. keepMetadata() deckt laut Doku EXIF, ICC, XMP und IPTC ab. C2PA steht nicht auf dieser Liste, und zwar aus einem harten Grund: libvips, die Engine unter sharp, kann JUMBF nicht.

Das Issue libvips#4420 mit dem Titel “add C2PA support” hat Maintainer John Cupitt am 15.03.2025 selbst angelegt. Es ist offen und auf Milestone 8.19 gesetzt. Ausgangspunkt war Discussion #4233, die Tim Bray am 31.10.2024 eroeffnet hatte. Solange das so bleibt, ist ein C2PA-Manifest nach jedem sharp-Durchlauf verschwunden, egal wie du konfigurierst.

Die C2PA-Spezifikation kennt das Problem und antwortet mit soft bindings und Durable Content Credentials. Sinngemaess: wenn ein Manifest aus einem Asset entfernt wird, eine Kopie aber in einem Provenance Store liegt, koennen Manifest und Asset ueber soft bindings wieder zugeordnet werden. Ob das bei Google oder OpenAI in der Praxis funktioniert, ist eine Frage der Implementierung, nicht der Spezifikation. Ich wuerde mich nicht darauf verlassen.

Auch OpenAI raeumt die Fragilitaet selbst ein. Bilder aus ChatGPT, Codex und der API tragen C2PA-Metadaten und ein SynthID-Wasserzeichen, aber die meisten Social-Plattformen entfernen Metadaten beim Upload, und ein Screenshot genuegt ohnehin. Google hat am 20.11.2025 angekuendigt, dass Bilder aus Nano Banana Pro in der Gemini-App, in Vertex AI und in Google Ads C2PA-Metadaten eingebettet bekommen. Fuer alle Gemini-Bildmodelle belegt ist bisher nur das SynthID-Wasserzeichen.

Drei Konfigurationen, die die Markierung ueberleben lassen

Der public-Bypass. Der einfachste Weg. Astros Doku ist da eindeutig: Dateien im public/-Ordner werden immer unveraendert ausgeliefert oder kopiert, ohne jede Verarbeitung, und Bilder dort werden nie optimiert. Wer das Bild vorher selbst korrekt skaliert und komprimiert, bekommt exakt die Bytes ausgeliefert, die er hineingelegt hat, inklusive Manifest. Der Preis ist, dass du responsive Varianten und Hashing selbst bauen musst. Fuer eine Handvoll gekennzeichneter Bilder ist das der pragmatischste Weg. In Next.js entspricht dem der unoptimized-Prop:

import Image from 'next/image';

<Image src="/ki/hero.webp" width={1600} height={900} alt="..." unoptimized />

// oder global in next.config.js:
module.exports = { images: { unoptimized: true } };

Ein eigener Image-Service. Astro erlaubt das ueber image.service.entrypoint. Ein lokaler Service muss getURL(), parseURL() und transform() implementieren und { data, format } zurueckgeben. Der Trick besteht darin, den eingebauten sharp-Service zu spreaden und nur transform() zu ersetzen.

// astro.config.mjs
import { defineConfig } from 'astro/config';

export default defineConfig({
  image: {
    service: {
      entrypoint: './src/image-service-keepmeta.ts',
      config: { limitInputPixels: false },
    },
  },
});
// src/image-service-keepmeta.ts
// Konstruktion auf Basis der dokumentierten Astro-Image-Service-API.
import type { LocalImageService } from 'astro';
import sharpService from 'astro/assets/services/sharp';
import sharp from 'sharp';

const service: LocalImageService = {
  ...sharpService,
  async transform(inputBuffer, options, imageConfig) {
    const format = options.format ?? 'webp';
    const pipeline = sharp(inputBuffer, { failOn: 'none', pages: -1 })
      .rotate()
      .keepMetadata();

    if (options.width || options.height) {
      pipeline.resize({
        width: options.width ? Math.round(options.width) : undefined,
        height: options.height ? Math.round(options.height) : undefined,
        withoutEnlargement: true,
      });
    }

    const { data, info } = await pipeline
      .toFormat(format, { quality: Number(options.quality) || 82 })
      .toBuffer({ resolveWithObject: true });

    return { data: new Uint8Array(data), format: info.format };
  },
};

export default service;

Das rettet XMP, IPTC, ICC und den EXIF-Block. Eine Einschraenkung gibt es trotzdem. Das rotate() ohne Winkel orientiert das Bild anhand der EXIF-Daten und entfernt dabei laut sharp-Doku den Orientation-Tag selbst. Dieses eine Feld kommt also auch mit keepMetadata() nicht durch, was konsistent ist, weil die Drehung danach in den Pixeln steckt. C2PA rettet das alles weiterhin nicht.

Markierung selbst setzen statt hoffen. Wenn die Herkunftsmarkierung des Anbieters ohnehin nicht durchkommt, kannst du die IPTC-Property selbst schreiben. Das ist unabhaengig vom Quellbild und funktioniert deterministisch.

const xmp = `<?xml version="1.0"?>
<x:xmpmeta xmlns:x="adobe:ns:meta/">
  <rdf:RDF xmlns:rdf="http://www.w3.org/1999/02/22-rdf-syntax-ns#">
    <rdf:Description rdf:about=""
      xmlns:Iptc4xmpExt="http://iptc.org/std/Iptc4xmpExt/2008-02-29/">
      <Iptc4xmpExt:DigitalSourceType>http://cv.iptc.org/newscodes/digitalsourcetype/trainedAlgorithmicMedia</Iptc4xmpExt:DigitalSourceType>
    </rdf:Description>
  </rdf:RDF>
</x:xmpmeta>`;

await sharp(input)
  .withXmp(xmp)                 // seit 0.34.3, nur PNG/JPEG/WebP/TIFF
  .withExifMerge({ IFD0: { Copyright: 'EMIT Solution' } })
  .webp({ quality: 82 })
  .toFile('out.webp');

Den Namespace-URI vor dem Einsatz gegen die aktuelle IPTC-Spezifikation gegenpruefen. Und wer C2PA wirklich braucht, signiert nach dem Build neu, mit c2patool aus dem c2pa-rs-Projekt. Dessen Formatliste enthaelt avif, webp, png, jpg, tiff, heic, gif, svg und jxl, PDF nur lesend. Dafuer brauchst du ein Signaturzertifikat.

Nachpruefen statt hoffen

Der wichtigste Schritt kommt zum Schluss, und er dauert zehn Sekunden. Schau in das Build-Artefakt hinein, nicht in die Quelldatei.

# Was steckt wirklich noch im ausgelieferten Bild
exiftool -a -G1 dist/_astro/bild.hash.webp

# C2PA getrennt pruefen, exiftool sieht die JUMBF-Box nicht als solche
c2patool dist/_astro/bild.hash.webp

# Manifest nach dem Build wieder anbringen (Signaturzertifikat noetig)
c2patool bild.webp --manifest manifest.json --output bild.signed.webp

Dass stille Parameterverwerfung in Bildpipelines real ist, habe ich bei mir selbst dokumentiert. Am 19.07.2026 fand ich in meinem Bildgenerierungs-Tool einen Bug, bei dem die Bildkonfiguration als generationConfig statt als config an ai.models.generateContent() uebergeben wurde. Das @google/genai-SDK verwirft das Feld stillschweigend ohne Fehlermeldung. Folge: jedes bis dahin erzeugte Bild kam als etwa 1K heraus, typisch 1365x768 oder 1026x1024, obwohl 2K oder 4K angefordert war. Nach dem Fix liefert --size 4K bei 16:9 tatsaechlich 5504x3072. Die Lehre steht seitdem in meiner Doku: bei Verdacht auf ignorierte Parameter die Ausgabedatei nachmessen, nicht der CLI-Ausgabe vertrauen. Fuer Metadaten gilt dasselbe.

Nebenbei: meine eigene Bildpipeline fuer den Blog verliert die Metadaten schon einen Schritt vor Astro. Das generierte PNG wird per Pillow auf 1600px Breite nach WebP konvertiert, ohne exif-, xmp- oder icc_profile-Parameter. Der Verlust passiert also nicht erst im Framework, sondern im eigenen Skript.

Wer wofuer haftet

Hier muss man sauber trennen, weil im Netz einiges durcheinandergeht.

Art. 50 Abs. 2 AI Act (VO (EU) 2024/1689) adressiert ausdruecklich Anbieter von KI-Systemen. Im Wortlaut: “Providers of AI systems, including general-purpose AI systems, generating synthetic audio, image, video or text content, shall ensure that the outputs of the AI system are marked in a machine-readable format and detectable as artificially generated or manipulated.” Adressat ist der provider, also OpenAI, Google, Adobe. Nicht der Entwickler und nicht der Seitenbetreiber. Aus Art. 50 Abs. 2 folgt keine eigene Pflicht, im ausgelieferten Bild eine maschinenlesbare Markierung vorzuhalten.

Was am Betreiber haengt, ist Art. 50 Abs. 4 Unterabs. 1: die Offenlegungspflicht fuer KI-generierte oder manipulierte Bilder und Videos, die real wirken. Das ist eine sichtbare Kennzeichnung fuer Menschen, keine Metadatenfrage. Ich loese das in Kundenprojekten ueber einen Info-Button direkt am Bild, der per Klick ein Popover mit Erklaerungstext oeffnet, positiv formuliert statt als Warnung. Wie das aufgebaut ist, habe ich in KI-Kennzeichnung als Vertrauenssignal beschrieben. Der Bussgeldrahmen fuer Verstoesse gegen Art. 50 liegt nach Art. 99 Abs. 4 bei bis zu 15 Millionen Euro oder 3 Prozent des Jahresumsatzes, die Pflicht greift ab 02.08.2026.

Aufgeschlagenes Gesetzbuch mit einem Holzstempel darauf Foto: markusspiske / Unsplash

Der freiwillige Teil ist trotzdem relevant. Der “Code of Practice on Transparency of AI-Generated Content” des EU AI Office wurde am 10.06.2026 final veroeffentlicht und deckt Art. 50 Abs. 2, 4 und 5 ab, geteilt in einen Abschnitt fuer Anbieter und einen fuer Betreiber. Measure 1.2 verlangt von Unterzeichnern, bestehende Metadaten-Markierungen zu erhalten und nicht absichtlich zu veraendern oder zu entfernen. Ein Build, der stillschweigend strippt, ist keine absichtliche Entfernung. Aber wer sich an dieser Linie orientiert, sollte wissen, dass sein Deployment sie unterlaeuft.

Der eigentliche Grund, das zu reparieren, ist praktischer Natur. Wer irgendwann belegen will, woher ein Bild stammt, braucht die Kette. Wenn sie im Buildschritt reisst, ist sie weg, und zwar unbemerkt, weil kein Tool Alarm schlaegt.

Wenn du wissen willst, was deine Pipeline aus deinen Bildern macht, schick mir eine URL zu einem gebauten Bild. Ein Blick mit exiftool und c2patool zeigt innerhalb von Minuten, ob EXIF, XMP und ICC noch drin sind und ob ein Manifest ueberlebt hat. Falls es sinnvoll ist, richte ich den Image-Service oder den public-Bypass dann direkt ein.

Häufige Fragen

Entfernt sharp wirklich alle Metadaten, wenn ich nichts konfiguriere?+

Ja. Die sharp-Doku zu keepMetadata formuliert es so, dass ohne diese Methode nach sRGB konvertiert und saemtliche Metadaten inklusive ICC-Profil entfernt werden. Der Hinweis bei toBuffer und toFile wiederholt das ausdruecklich, inklusive der EXIF-Orientierung. Das gilt fuer jedes Ausgabeformat, nicht nur fuer WebP.

Kann ich das in Astro ueber die astro.config.mjs abstellen?+

Nein. Das SharpImageServiceConfig-Interface von Astro exponiert nur limitInputPixels, kernel sowie die Encoder-Optionen fuer jpeg, png, webp und avif. Einen Schalter fuer Metadaten gibt es dort nicht. Wer das Verhalten aendern will, braucht entweder den public-Ordner oder einen eigenen Image-Service ueber image.service.entrypoint.

Ueberlebt ein C2PA-Manifest die Konvertierung nach WebP?+

Technisch koennte es das, denn die C2PA-Spezifikation 2.2 listet WebP und AVIF als unterstuetzte Formate. Praktisch nicht, wenn sharp konvertiert: libvips hat keine C2PA- oder JUMBF-Unterstuetzung, das entsprechende Issue ist seit Maerz 2025 offen. Auch keepMetadata() aendert daran nichts.

Verstosse ich gegen den AI Act, wenn mein Build die Markierung entfernt?+

Art. 50 Abs. 2 AI Act adressiert ausdruecklich Anbieter von KI-Systemen, also OpenAI, Google oder Adobe, nicht den Betreiber einer Website. Fuer Betreiber gilt Art. 50 Abs. 4 mit der sichtbaren Offenlegungspflicht. Der freiwillige Code of Practice des EU AI Office verlangt in Measure 1.2 von seinen Unterzeichnern allerdings, vorhandene Metadaten-Markierungen zu erhalten und nicht absichtlich zu entfernen.

Du willst mehr erfahren?

In einem kostenlosen Erstgespräch besprechen wir, wie du diese Themen für dein Unternehmen nutzen kannst. Kein Verkaufsgespräch, sondern eine ehrliche Einschätzung.

Kostenloses Erstgespräch vereinbaren