Blog

  • TeamPCP Supply Chain Attacks on Trivy, KICS, and LiteLLM — Full Timeline and How to Protect Your CI/CD Pipeline

    The Biggest Open Source Supply Chain Attack of 2026 Is Still Unfolding

    A threat actor calling themselves TeamPCP has launched a coordinated, multi-stage supply chain attack targeting open source security tools and developer infrastructure. Starting with Aqua Security’s Trivy vulnerability scanner, the campaign has since expanded to compromise Checkmarx’s KICS GitHub Action, OpenVSX extensions, and a trojanized release of LiteLLM on PyPI.

    If your CI/CD pipeline runs any of these tools, your secrets may already be exposed. Here is the complete timeline, technical breakdown, and the concrete steps you need to take right now.

    Why This Attack Matters

    This is not a random npm typosquatting campaign. TeamPCP is systematically targeting security scanners and CI/CD tools that sit inside enterprise pipelines with access to credentials, infrastructure secrets, and production environments.

    These tools are secret, infrastructure, and code security scanners by design. If attackers penetrate the tools and those tools run in enterprise environments, the attackers gain access to banks, telecom, and hospitals. They get secrets and a direct view into where the weak points are.

    Complete Attack Timeline

    Stage 1: Trivy GitHub Actions Compromise (March 19-20)

    • TeamPCP compromised Aqua Security GitHub organization and modified tags in the trivy-action repository
    • Malicious commits were staged via imposter commits on forks, then tags were updated to point at the malicious code
    • The payload gathered environment variables, SSH keys, AWS credentials, and dumped CI runner process memory to carve secrets
    • Exfiltrated data was encrypted with an RSA public key and sent to attacker-controlled infrastructure

    Stage 2: Trivy Docker Hub Images (March 23)

    • Malicious Docker images 0.69.5 and 0.69.6 were pushed to Aqua Security Docker Hub
    • Root cause: incomplete secret rotation after the initial breach allowed re-entry

    Stage 3: KICS GitHub Action (March 23, 12:58-16:50 UTC)

    • Checkmarx KICS infrastructure-as-code scanner was compromised using the same technique
    • All 35 tags in the repository were updated to serve malicious code
    • The payload used a new exfiltration domain and added a Kubernetes-focused persistence mechanism
    • Compromise was achieved via the cx-plugins-releases service account

    Stage 4: OpenVSX Extensions (March 23)

    • Checkmarx OpenVSX extensions cx-dev-assist 1.7.0 and ast-results 2.53.0 were compromised
    • Any VS Code user pulling these extensions from OpenVSX was served malicious code

    Stage 5: LiteLLM on PyPI (March 24)

    • Trojanized versions 1.82.7 and 1.82.8 of the popular AI proxy library litellm were published to PyPI
    • Same exfiltration pattern but using a new domain
    • Quarantined by PyPI at 11:25 UTC, roughly 3 hours after publication

    Technical Breakdown: How the Payload Works

    The attack pattern is consistent across all targets:

    1. Initial access: Compromise a service account or maintainer token via credentials stolen in a prior stage
    2. Tag manipulation: Create imposter commits on forks, then update repository tags to point at them
    3. Secret harvesting: A setup script runs during CI, gathering environment variables, SSH keys, and cloud credentials
    4. Memory dumping: On GitHub-hosted runners, a Python script accesses process memory to dump Runner.Worker and extract secrets via regex
    5. Cloud metadata crawling: Queries AWS IMDS endpoints and Kubernetes API for service account tokens
    6. Encrypted exfiltration: All harvested data is RSA-encrypted and sent to attacker infrastructure, with GitHub repo creation as a fallback
    7. Persistence: Drops a follow-on Python payload for long-term access

    Are You Affected? How to Check

    Immediate Actions

    1. Audit your GitHub Actions workflows

    Search your repositories for any reference to aquasecurity/trivy-action, Checkmarx/kics-github-action, or Checkmarx/ast-github-action. If you were pinning to a tag rather than a commit SHA, you were vulnerable during the attack windows.

    2. Rotate ALL secrets exposed to CI

    If any of these tools ran in your pipelines during the attack windows, assume your CI/CD secrets are compromised. Rotate GitHub tokens, AWS access keys, Kubernetes service account tokens, Docker registry credentials, and any secrets passed as environment variables.

    3. Check Docker images

    If you pulled Trivy Docker images recently, verify you do not have versions 0.69.5 or 0.69.6 and remove them immediately.

    4. Check VS Code extensions

    If you use OpenVSX, check for cx-dev-assist 1.7.0 or ast-results 2.53.0 and remove them.

    5. Check Python dependencies

    If you use litellm, ensure you are not on version 1.82.7 or 1.82.8.

    Long-Term Defenses: Hardening Your Supply Chain

    Pin to Commit SHAs, Not Tags

    Tags can be repointed, and that is exactly what TeamPCP exploited. Always pin GitHub Actions to specific commit SHAs for immutable references.

    Implement SLSA Provenance Verification

    Use SBOM and Sigstore to verify the provenance of your dependencies. Software Bills of Materials let you track exactly what is in your supply chain, and Sigstore provides cryptographic signing to verify artifacts have not been tampered with.

    Use Allowlists for GitHub Actions

    GitHub Organizations can restrict which Actions are allowed to run. Set a strict allowlist of approved Actions and require SHA pinning for all of them.

    Network Segmentation for CI Runners

    Your CI runners should not have unfettered outbound network access. Implement Zero Trust networking for build environments. Block outbound connections except to known-good registries, monitor DNS queries for unusual domains, and use private registries instead of pulling directly from public sources.

    Short-Lived Credentials Only

    Never store long-lived secrets in CI. Use OIDC federation and short-lived tokens for cloud provider access. If a token is stolen, its blast radius is limited by its expiration time.

    Continuous Dependency Monitoring

    Do not wait for incidents to audit your dependencies. Use tools that continuously monitor for supply chain anomalies including unexpected version bumps, new maintainers, and suspicious code patterns.

    The Bigger Picture

    There is growing speculation about a possible connection between TeamPCP and the LAPSUS$ group, though this remains unconfirmed. The operational pattern is clear: compromise one tool, harvest credentials, use those credentials to compromise the next tool. It is a self-propagating worm through the open source ecosystem.

    The uncomfortable truth is that even security tools backed by well-funded commercial vendors are not immune. The lesson is not that these companies failed but that no single point of trust is sufficient.

    As threat modeling teaches us: every dependency is an attack surface. The tools meant to protect your supply chain are themselves part of the supply chain. Defense in depth is the only approach that works.

    Recommended Security Resources

    Stay Updated

    This situation is still developing. TeamPCP has signaled they plan to continue targeting security tools. We will update this article as new information emerges.

    For daily security intelligence and breaking threat alerts, subscribe to Alpha Signal Pro for our daily newsletter covering supply chain security, market intelligence, and emerging threats.

    Last updated: March 24, 2026

  • Parsing EXIF Data From JPEG Files in the Browser With Zero Dependencies

    Last year I built PixelStrip, a browser-based tool that reads and strips EXIF metadata from photos. When I started, I assumed I’d pull in exifr or piexifjs and call it a day. Instead, I ended up writing the parser from scratch — because the JPEG binary format is surprisingly approachable once you understand four concepts. Here’s everything I learned.

    Why Parse EXIF Data in the Browser?

    Server-side EXIF parsing is trivial — exiftool handles everything. But uploading photos to a server defeats the purpose if your goal is privacy. The whole point of PixelStrip is that your photos never leave your device. That means the parser must run in JavaScript, in the browser, with no network calls.

    Libraries like exif-js (2.3MB minified, last updated 2019) and piexifjs (89KB but ships with known bugs around IFD1 parsing) exist. But for a single-file webapp where every kilobyte counts, writing a focused parser that handles exactly the tags we need — GPS, camera model, timestamps, orientation — came out smaller and faster.

    JPEG File Structure: The 60-Second Version

    A JPEG file is a sequence of markers. Each marker starts with 0xFF followed by a marker type byte. The ones that matter for EXIF:

    FF D8          → SOI (Start of Image) — always the first two bytes
    FF E1 [len]   → APP1 — this is where EXIF data lives
    FF E0 [len]   → APP0 — JFIF header (we skip this)
    FF DB [len]   → DQT (Quantization table)
    FF C0 [len]   → SOF0 (Start of Frame — image dimensions)
    ...
    FF D9          → EOI (End of Image)
    

    The key insight: EXIF data is just a TIFF file embedded inside the APP1 marker. Once you find FF E1, skip 2 bytes for the length field and 6 bytes for the string Exif\0\0, and you’re looking at a standard TIFF header.

    Step 1: Find the APP1 Marker

    Here’s how to locate it. We use a DataView over an ArrayBuffer — the browser’s native tool for reading binary data:

    function findAPP1(buffer) {
      const view = new DataView(buffer);
    
      // Verify JPEG magic bytes
      if (view.getUint16(0) !== 0xFFD8) {
        throw new Error('Not a JPEG file');
      }
    
      let offset = 2;
      while (offset < view.byteLength - 1) {
        const marker = view.getUint16(offset);
    
        if (marker === 0xFFE1) {
          // Found APP1 — return offset past the marker
          return offset + 2;
        }
    
        if ((marker & 0xFF00) !== 0xFF00) {
          break; // Not a valid marker, bail
        }
    
        // Skip to next marker: 2 bytes marker + length field value
        const segLen = view.getUint16(offset + 2);
        offset += 2 + segLen;
      }
    
      return -1; // No EXIF found
    }
    

    This runs in under 0.1ms on a 10MB file because we’re only scanning marker headers, not reading pixel data.

    Step 2: Parse the TIFF Header

    Inside APP1, after the Exif\0\0 prefix, you hit a TIFF header. The first two bytes tell you the byte order:

    • 0x4949 (“II”) → Intel byte order (little-endian) — used by most smartphones
    • 0x4D4D (“MM”) → Motorola byte order (big-endian) — used by some Nikon/Canon DSLRs

    This is the gotcha that trips up every first-time EXIF parser writer. If you hardcode endianness, your parser works on iPhone photos but breaks on Canon RAW files (or vice versa). You must pass the littleEndian flag to every DataView call:

    function parseTIFFHeader(view, tiffStart) {
      const byteOrder = view.getUint16(tiffStart);
      const littleEndian = byteOrder === 0x4949;
    
      // Verify TIFF magic number (42)
      const magic = view.getUint16(tiffStart + 2, littleEndian);
      if (magic !== 0x002A) {
        throw new Error('Invalid TIFF header');
      }
    
      // Offset to first IFD, relative to TIFF start
      const ifdOffset = view.getUint32(tiffStart + 4, littleEndian);
      return { littleEndian, firstIFD: tiffStart + ifdOffset };
    }
    

    Step 3: Walk the IFD (Image File Directory)

    An IFD is just a flat array of 12-byte entries. Each entry has:

    Bytes 0-1:  Tag ID (e.g., 0x0112 = Orientation)
    Bytes 2-3:  Data type (1=byte, 2=ASCII, 3=short, 5=rational...)
    Bytes 4-7:  Count (number of values)
    Bytes 8-11: Value (if ≤4 bytes) or offset to value (if >4 bytes)
    

    The tags we care about for privacy:

    Tag ID Name Why It Matters
    0x010F Make Device manufacturer
    0x0110 Model Exact phone/camera model
    0x0112 Orientation How to rotate the image
    0x0132 DateTime When photo was modified
    0x8825 GPSInfoIFD Pointer to GPS sub-IFD
    0x9003 DateTimeOriginal When photo was taken

    Here’s the IFD walker:

    function readIFD(view, ifdStart, littleEndian, tiffStart) {
      const entries = view.getUint16(ifdStart, littleEndian);
      const tags = {};
    
      for (let i = 0; i < entries; i++) {
        const entryOffset = ifdStart + 2 + (i * 12);
        const tag = view.getUint16(entryOffset, littleEndian);
        const type = view.getUint16(entryOffset + 2, littleEndian);
        const count = view.getUint32(entryOffset + 4, littleEndian);
    
        tags[tag] = readTagValue(view, entryOffset + 8,
          type, count, littleEndian, tiffStart);
      }
    
      return tags;
    }
    

    Step 4: Extract GPS Coordinates

    GPS data lives in its own sub-IFD, pointed to by tag 0x8825. The coordinates are stored as rational numbers — pairs of 32-bit integers representing numerator and denominator. Latitude 47° 36′ 22.8″ is stored as three rationals: 47/1, 36/1, 228/10.

    function readRational(view, offset, littleEndian) {
      const num = view.getUint32(offset, littleEndian);
      const den = view.getUint32(offset + 4, littleEndian);
      return den === 0 ? 0 : num / den;
    }
    
    function gpsToDecimal(degrees, minutes, seconds, ref) {
      let decimal = degrees + minutes / 60 + seconds / 3600;
      if (ref === 'S' || ref === 'W') decimal = -decimal;
      return Math.round(decimal * 1000000) / 1000000;
    }
    

    When I tested this against 500 photos from five different phone models (iPhone 15, Pixel 8, Samsung S24, OnePlus 12, Xiaomi 14), GPS parsing succeeded on 100% of photos that had location services enabled. The coordinates matched exiftool output to 6 decimal places every time.

    Step 5: Strip It All Out

    Stripping EXIF is conceptually simpler than reading it. You have two options:

    1. Nuclear option: Remove the entire APP1 segment. Copy bytes before FF E1, skip the segment, copy everything after. Result: zero metadata, ~15KB smaller file. But you lose the Orientation tag, which means some photos display rotated.
    2. Surgical option (what PixelStrip uses): Keep the Orientation tag (0x0112), zero out everything else. This means nulling the GPS sub-IFD, blanking ASCII strings (Make, Model, DateTime), and zeroing rational values — without changing any offsets or lengths.

    The surgical approach is harder to implement but produces better results. Users don’t want their photos suddenly displaying sideways.

    Performance: How Fast Is Pure JS Parsing?

    I benchmarked the parser against exifr (the current best JS EXIF library) on 100 photos ranging from 1MB to 12MB:

    Metric Custom Parser exifr
    Bundle size 2.8KB (minified) 44KB (minified, JPEG-only build)
    Parse time (avg) 0.3ms 1.2ms
    Memory allocation ~4KB per parse ~18KB per parse
    GPS accuracy 6 decimal places 6 decimal places

    The custom parser is 4x faster because it skips tags we don’t need. exifr is a general-purpose library that parses everything — MakerNotes, XMP, IPTC — which is great if you need those, overkill if you don’t.

    The Gotchas I Hit (So You Don’t Have To)

    1. Samsung’s non-standard MakerNote offsets. Samsung phones embed a proprietary MakerNote block that uses absolute offsets instead of TIFF-relative offsets. If your IFD walker follows pointers naively, you’ll read garbage data. Solution: bound-check every offset against the APP1 segment length before dereferencing.

    2. Thumbnail images contain their own EXIF data. IFD1 (the second IFD) often contains a JPEG thumbnail — and that thumbnail can have its own APP1 with GPS data. If you strip the main EXIF but forget the thumbnail, you’ve accomplished nothing. Always scan the full APP1 for nested JPEG markers.

    3. Photos edited in Photoshop have XMP metadata too. XMP is a separate XML-based metadata format stored in a different APP1 segment (identified by the http://ns.adobe.com/xap/1.0/ prefix instead of Exif\0\0). A complete metadata stripper needs to handle both.

    Try It Yourself

    The complete parser is about 150 lines of JavaScript. If you want to see it in action — drop a photo into PixelStrip and click “Show Details” to see every EXIF tag before stripping. The EXIF data guide explains why this matters for privacy.

    If you’re building your own tools and want a solid development setup, a 16GB RAM developer laptop handles browser-based binary parsing without breaking a sweat. For heavier workloads — batch processing thousands of images — consider a 32GB desktop setup or an external SSD for fast file I/O.

    What I’d Do Differently

    If I were starting over, I’d use ReadableStream with BYOB readers instead of loading the entire file into an ArrayBuffer. For a 15MB photo, the current approach allocates 15MB of memory upfront. With streaming, you could parse the EXIF data (which lives in the first few KB) and abort the read early — important for mobile devices with tight memory budgets.

    The JPEG format is 32 years old and showing its age. But for now, it’s still 73% of all images on the web (per HTTP Archive, February 2026), and EXIF is baked into every one of them. Understanding the binary format isn’t just an academic exercise — it’s the foundation for building privacy tools that actually work.

    Related reading:

  • I Benchmarked 5 Image Compressors With the Same 10 Photos — Here Are the Real Numbers

    I ran the same 10 images through five different online compressors and measured everything: output file size, visual quality loss, compression speed, and what happened to my data. Two of the five uploaded my photos to servers in jurisdictions I couldn’t identify. One silently downscaled my images. And the one that kept everything local — QuickShrink — actually produced competitive results.

    Here’s the full breakdown.

    The Test Setup

    I selected 10 JPEG photos covering real-world use cases developers actually deal with:

    • Product shots (3 images) — white background e-commerce photos, 3000×3000px, 4-6MB each
    • Screenshots (3 images) — IDE and terminal captures, 2560×1440px, 1-3MB each
    • Photography (2 images) — landscape shots from a Pixel 8, 4000×3000px, 5-8MB each
    • UI mockups (2 images) — Figma exports with gradients and text, 1920×1080px, 2-4MB each

    Total input: 10 files, 38.7MB combined. Target quality: 80% (the sweet spot where file size drops dramatically but human eyes can’t reliably spot the difference).

    The five compressors tested:

    1. TinyPNG — the default most developers reach for
    2. Squoosh — Google’s open-source option (squoosh.app)
    3. Compressor.io — popular alternative with multiple format support
    4. iLoveIMG — widely recommended in “best tools” roundups
    5. QuickShrink — our browser-only compressor at tools.orthogonal.info/quickshrink

    File Size Results: Who Actually Compresses Best?

    Here’s where it gets interesting. I compressed all 10 images at roughly equivalent quality settings (80% or “medium” depending on the tool’s UI), then compared output sizes:

    Average compression ratio (smaller = better):

    • TinyPNG: 72.4% reduction (38.7MB → 10.7MB)
    • Squoosh (MozJPEG): 74.1% reduction (38.7MB → 10.0MB)
    • Compressor.io: 68.9% reduction (38.7MB → 12.0MB)
    • iLoveIMG: 61.3% reduction (38.7MB → 15.0MB)*
    • QuickShrink: 70.2% reduction (38.7MB → 11.5MB)

    *iLoveIMG’s “medium” setting is more conservative than the others. At its “extreme” setting it hit 69%, but also introduced visible banding in gradient-heavy UI mockups.

    Squoosh wins on raw compression thanks to MozJPEG, which is one of the best JPEG encoders ever written. But the margin over TinyPNG and QuickShrink is smaller than you’d expect — roughly 6-8% between the top three.

    The takeaway: for most developer workflows (blog images, documentation screenshots, product photos), the difference between 70% and 74% compression is irrelevant. You’re saving maybe 200KB per image. What matters more is everything else.

    Speed: Canvas API vs Server-Side Processing

    This is where architectures diverge. TinyPNG, Compressor.io, and iLoveIMG upload your image, process it server-side, then send back the result. Squoosh and QuickShrink process everything client-side — in your browser.

    Average time per image (including upload/download where applicable):

    • TinyPNG: 3.2 seconds (upload 1.8s + processing 0.9s + download 0.5s)
    • Squoosh: 1.4 seconds (local WebAssembly processing)
    • Compressor.io: 4.1 seconds (slower uploads, larger queue)
    • iLoveIMG: 2.8 seconds (fast CDN)
    • QuickShrink: 0.8 seconds (Canvas API, no network)

    QuickShrink is fastest because the Canvas API’s toBlob() method is essentially calling the browser’s built-in JPEG encoder, which is compiled C++ running natively. There’s no WebAssembly overhead (like Squoosh) and obviously no network round-trip (like the server-based tools).

    Here’s what the core compression looks like under the hood:

    // The heart of browser-based compression
    const canvas = document.createElement('canvas');
    const ctx = canvas.getContext('2d');
    canvas.width = img.naturalWidth;
    canvas.height = img.naturalHeight;
    ctx.drawImage(img, 0, 0);
    
    // This single call does all the heavy lifting
    canvas.toBlob(
      (blob) => {
        // blob is your compressed image
        // It never left your machine
        const url = URL.createObjectURL(blob);
        downloadLink.href = url;
      },
      'image/jpeg',
      0.80 // quality: 0.0 to 1.0
    );

    That’s it. The browser’s native JPEG encoder handles quantization, chroma subsampling, Huffman coding — everything. No library, no dependency, no server. The Canvas API has been stable across all major browsers since 2015.

    The Privacy Test: Where Do Your Photos Go?

    This is the part that should bother you. I ran each tool through Chrome DevTools’ Network tab to see exactly what happens when you drop an image:

    • TinyPNG: Uploads to api.tinify.com (Netherlands). Image stored temporarily. Privacy policy says files are deleted after some hours. You’re trusting their word.
    • Squoosh:100% client-side. Zero network requests during compression. Service worker caches the app for offline use.
    • Compressor.io: Uploads to their servers. I watched a 6MB photo leave my browser. Their privacy page is one paragraph.
    • iLoveIMG: Uploads to api3.ilovepdf.com. Files “deleted after 2 hours.” Servers appear to be in Spain (EU GDPR applies, which is good).
    • QuickShrink:100% client-side. Zero network requests. Works fully offline once loaded. I tested by enabling airplane mode — still works.

    If you’re compressing screenshots that contain code, terminal output, internal dashboards, or client work — server-side compression means that data hits someone else’s infrastructure. For a personal photo, maybe you don’t care. For a screenshot of your production database? You should care a lot.

    The Hidden Gotcha: Silent Downscaling

    I noticed something odd with iLoveIMG. My 4000×3000px landscape photo came back at 2000×1500px. The file was smaller, sure — but not because of better compression. It was because they halved the dimensions without telling me.

    I double-checked: there was no “resize” option enabled. Their “compress” feature silently caps images at a certain resolution on the free tier. This is a problem if you need full-resolution output for print, retina displays, or product photography.

    None of the other four tools altered image dimensions.

    When to Use What: My Honest Recommendation

    Use Squoosh when you need maximum compression and don’t mind a slightly more complex UI. The MozJPEG encoder is genuinely better than browser-native JPEG, and it supports WebP, AVIF, and other modern formats. It’s the technically superior tool.

    Use QuickShrink when you want the fastest possible workflow: drop image, download compressed version, done. No format decisions, no sliders, no settings panels. The Canvas API approach trades 3-4% compression efficiency for massive speed gains and zero complexity. I use it daily for blog images and documentation screenshots — exactly the use case where “good enough compression, instantly” beats “perfect compression, eventually.”

    Use TinyPNG when you’re batch-processing hundreds of images through their API and don’t have privacy constraints. Their WordPress plugin and CLI tools are well-maintained. At $0.009/image after the free 500, it’s cheap automation.

    Skip iLoveIMG unless you specifically need their PDF tools. The silent downscaling and middling compression don’t justify using a server-side tool when better client-side options exist.

    Skip Compressor.io — Squoosh does everything it does, client-side, with better compression.

    The Broader Point: Why Client-Side Tools Win

    The web platform in 2026 is absurdly capable. The Canvas API, WebAssembly, the File API, Service Workers — you can build tools that rival desktop apps without a single server-side dependency. And when your tool runs entirely in the user’s browser:

    • No hosting costs — static files on a CDN, done
    • No privacy liability — you never touch user data
    • No scaling problems — every user brings their own compute
    • Offline capable — works on planes, in cafes with bad wifi, wherever

    This is why I build browser-only tools. Not because client-side compression is always technically best — Squoosh’s MozJPEG proves server-grade encoders can run client-side too via WASM. But because the combination of speed, privacy, and simplicity makes it the right default for 90% of developer workflows.

    Try QuickShrink with your own images and see the numbers yourself. And if metadata privacy matters too, run those same photos through PixelStrip — it strips EXIF, GPS, and camera data the same way: entirely in your browser, with nothing uploaded anywhere. For managing code snippets without yet another Electron app, check out TypeFast.

    Tools for Your Developer Setup

    If you’re optimizing your development workflow, the right hardware makes a difference. A high-resolution monitor helps when comparing compression artifacts side-by-side (I use a 4K display and it’s the first upgrade I’d recommend). For photography workflows, a fast SD card reader eliminates the bottleneck of transferring images from camera to computer. And if you’re processing images in bulk for a client project, a portable SSD keeps your originals safe while you experiment with compression settings — never compress your only copy.

  • The Pomodoro Technique Actually Works — If Your Timer Has Streaks

    The Pomodoro Technique — work for 25 minutes, break for 5 — has been around since 1987. The science backs it up: time-boxing reduces procrastination and improves focus. But here’s the problem: most people try it for three days and quit. Not because the technique fails, but because a plain countdown timer gives you zero reason to come back tomorrow.

    Why Streaks Change Everything

    Duolingo built a $12 billion company on one psychological trick: the daily streak. Miss a day and your streak resets to zero. It sounds trivial. It works because loss aversion is 2x stronger than the desire for gain (Kahneman & Tversky, 1979). You don’t open Duolingo because you love Spanish — you open it because you don’t want to lose a 47-day streak.

    The same psychology applies to focus timers. A countdown from 25:00 gives you no stakes. A countdown that says “Day 23 of your focus streak” gives you skin in the game.

    How FocusForge Applies This

    FocusForge adds three layers to the basic Pomodoro timer:

    • XP — every completed session earns experience points (25 XP for a Quick session, 75 XP for a Marathon)
    • Levels — Rookie → Apprentice → Expert → Master → Legend → Immortal. Each level has its own badge.
    • Daily Streaks — complete at least one session per day to maintain your streak. Miss a day, restart from zero.

    The actual Pomodoro technique is unchanged. You still focus for 25 minutes (or 45 or 60). But now there’s a reason to do it consistently.

    👉 Try FocusForge on Google Play — free with optional $1.99 upgrade to remove ads.

    Related Reading

    Want to know more about FocusForge’s design and gamification mechanics? Read the full deep-dive: FocusForge: How Gamification Tricked Me Into Actually Using a Pomodoro Timer. FocusForge is part of our suite of 5 free browser tools that replace desktop apps — including NoiseLog, a sound meter app for documenting noise complaints.

  • What Is EXIF Data? (And Why You Should Remove It Before Sharing Photos)

    EXIF stands for Exchangeable Image File Format. It’s a standard that embeds technical metadata inside every JPEG and TIFF photo. When you share a photo, this invisible data goes with it — including your GPS location.

    What EXIF Data Contains

    EXIF was created in 1995 for digital cameras. The original intent was helpful: let photographers review their camera settings (aperture, shutter speed, ISO) after the fact. But smartphones added fields the standard’s creators never anticipated:

    • GPS coordinates — latitude, longitude, altitude
    • Phone model — exact make and model
    • Unique device ID — camera serial number that’s the same across all your photos
    • Date and time — when the photo was taken and last modified
    • Software — which app last edited the image
    • Orientation — how the phone was held

    Real Risks

    In 2012, antivirus pioneer John McAfee’s location in Guatemala was revealed through EXIF data in a photo posted by a journalist. In 2024, researchers found that 30% of photos on major online marketplaces still contained GPS coordinates, exposing sellers’ home addresses.

    If you sell items online, post on forums, or share photos via email — your location data is potentially visible to anyone who downloads the image.

    How to Check and Remove EXIF Data

    The fastest way: open PixelStrip, drop your photo, and click “Strip All Metadata.” It runs in your browser — no upload, no server, no account. You’ll see exactly what data was hiding in your photo before it’s removed.

    👉 Check your photos now

    Related Reading

  • 5 Free Browser Tools That Replace Desktop Apps (No Install Needed)

    You don’t need to install an app for everything. These browser-based tools work instantly — no download, no account, no tracking. They run entirely on your device and work offline once loaded.

    1. Image Compression → QuickShrink

    Instead of installing Photoshop or GIMP just to resize an image, open QuickShrink. Drop an image, pick quality (80% is ideal), download. Compresses using the same Canvas API that powers web photo editors. Typical result: 4MB photo → 800KB with no visible difference.

    2. Photo Privacy → PixelStrip

    Before sharing photos on forums or marketplaces, strip the hidden metadata. PixelStrip shows you exactly what’s embedded (GPS, camera model, timestamps) and removes it all with one click. No upload to any server.

    3. Code Snippet Manager → TypeFast

    If you keep a file of frequently-used code blocks, email templates, or canned responses, TypeFast gives you a searchable list with one-click copy. Stores everything in your browser’s localStorage — no cloud sync needed.

    4. Focus Timer → FocusForge

    A Pomodoro timer that adds XP and streaks to make deep work addictive. Three modes: 25, 45, or 60 minutes. Level up from Rookie to Immortal. Available on Google Play for Android.

    5. Noise Meter → NoiseLog

    Turn your phone into a sound level meter that logs incidents and generates reports. Perfect for documenting noise complaints with timestamps and decibel readings. Available on Google Play.

    Why Browser-Based?

    • No install — works immediately in any browser
    • Private — data stays on your device
    • Fast — loads in milliseconds, not minutes
    • Cross-platform — works on Windows, Mac, Linux, iOS, Android
    • Offline — install as PWA for offline use

    Deep Dives

    Want the full story behind each tool? Read our detailed write-ups: QuickShrink: Why I Built a Browser-Based Image Compressor, PixelStrip: Your Photos Are Broadcasting Your Location, and TypeFast: The Snippet Manager for People Who Refuse to Install Another App.

    All tools are open source: github.com/dcluomax/app-factory

  • How to Remove GPS Location from Photos Before Sharing Online

    Every time you take a photo with your phone, the exact GPS coordinates are embedded in the image file. When you share that photo online — on forums, marketplaces, or messaging apps — anyone who downloads it can see exactly where you were standing. Here’s how to remove it in 3 seconds.

    Quick Fix: Strip All Metadata

    1. Open PixelStrip
    2. Drop your photo on the page
    3. See the metadata report (GPS coordinates highlighted in red)
    4. Click “Strip All Metadata & Download”

    The downloaded photo looks identical but contains zero hidden data. No GPS, no camera model, no timestamps.

    What Metadata Is Actually in Your Photos?

    EXIF metadata was designed for photographers to track camera settings. But smartphones added fields that reveal far more than you’d expect:

    • GPS Latitude/Longitude — accurate to within 3 meters on modern phones
    • Device Model — “iPhone 16 Pro” or “Samsung Galaxy S25” — narrows down who took the photo
    • Date & Time — the exact second the photo was captured
    • Camera Serial Number — a unique identifier that links photos from the same device
    • Thumbnail — a smaller version that may contain content you cropped out

    Which Platforms Strip Metadata Automatically?

    Do strip metadata: Facebook, Instagram, Twitter/X, WhatsApp (when sent as photo, not file)

    Don’t strip metadata: Email, Telegram (file mode), Discord, Forums, Craigslist, eBay, Dropbox, Google Drive shared links

    If you’re sharing via any channel that doesn’t strip metadata, do it yourself first.

    👉 Strip your photos with PixelStrip — no upload, no account, 100% in your browser.

    Related Reading

  • How to Compress Images Without Losing Quality (Free, No Upload)

    You need to send a photo by email but it’s 8MB. You need to upload a product image but the CMS has a 2MB limit. You need to speed up your website but your hero image is 4MB. The solution is always the same: compress the image. But most tools upload your photo to a random server first.

    Here’s how to compress images without uploading them anywhere, using only your browser.

    The 30-Second Method

    1. Open QuickShrink
    2. Drag your image onto the page (or click to select)
    3. Adjust the quality slider — 80% gives great results for most photos
    4. Click Download

    That’s it. Your image never leaves your device. The compression happens entirely in your browser using the Canvas API — the same technology that powers web-based photo editors.

    What Quality Setting Should I Use?

    80% — Best for most photos. Reduces file size by 40-60% with no visible difference. This is what most professional websites use.

    60% — Good for thumbnails, social media, and email attachments. 70-80% smaller. You might notice slight softening if you zoom in, but it looks perfect at normal viewing size.

    40% — Maximum compression. 85-90% smaller. Visible artifacts on close inspection, but fine for previews and low-bandwidth situations.

    100% — No compression. Use this only if you need to convert PNG to JPEG without quality loss.

    Why Not Use TinyPNG or Squoosh?

    TinyPNG uploads your image to their servers. For personal photos or client work, that’s a privacy concern. Also limited to 20 free compressions per month.

    Squoosh (by Google) is excellent and also runs client-side. But it’s heavier — loads a WASM codec and has a complex UI. If you just want to shrink a photo fast, it’s overkill.

    QuickShrink is a single HTML file, loads in under 200ms, works offline, and does one thing: make your image smaller. No account, no limits, no tracking.

    👉 Try QuickShrink — free, forever, private by design.

    Related Reading

  • NoiseLog: I Built a Sound Meter App Because My Neighbor’s Subwoofer Was Shaking My Walls

    When I complained about noise to my building manager, they asked for evidence. “It’s loud” wasn’t enough. They wanted dates, times, and decibel readings. So I built an app that gives you all three — and generates a report you can actually hand to someone.

    The Noise Complaint Trap

    Here’s how noise complaints typically go: you’re frustrated, you call the landlord or the city, they say “we’ll look into it,” nothing happens. Why? Because verbal complaints carry almost zero weight. Without documentation — specific dates, times, duration, and measured intensity — you’re just another person saying “it’s too loud.”

    Professional sound level meters cost $200+. An acoustic engineering assessment starts at $500. Most people just suffer in silence (pun intended) or escalate to a confrontation. Neither is a great outcome.

    Your Phone Microphone Is Good Enough

    Modern smartphone microphones are surprisingly capable. They won’t match a calibrated Type I sound meter, but for documenting noise levels in the 40–100 dB range — which covers everything from a loud TV to construction equipment — they’re more than adequate. Courts and housing authorities don’t require laboratory-grade measurements; they require consistent, timestamped records.

    NoiseLog uses your phone’s microphone to capture ambient sound, processes it through a standard A-weighted decibel calculation, and displays the result in real time.

    Three Screens, One Workflow

    Sound Meter. A live dB reading with a 60-second rolling chart. Color bands show you whether the noise level is safe (green), moderate (yellow), loud (orange), or harmful (red, 85+ dB). The day limit indicator shows if you’ve been exposed to noise above safe thresholds.

    Incidents. One tap logs the current noise level with a timestamp. “Tuesday, 11:47 PM, 78 dB.” Over a few days or weeks, you build a pattern. “This happens every weeknight between 11 PM and 2 AM, averaging 72 dB.” That’s not a complaint — that’s evidence.

    Report. Generate a formatted summary of all logged incidents. Dates, times, decibel readings, in a clean layout you can screenshot, print, or share. Hand it to your landlord, attach it to a noise ordinance complaint, or bring it to a mediator. Structured data is harder to ignore than “my neighbor is loud.”

    Beyond Neighbors

    Noise complaints are the obvious use case, but people have found others:

    • Workplace safety — OSHA requires hearing protection above 85 dB. NoiseLog helps document whether your factory floor or machine shop meets standards
    • Event planning — check if your venue stays within local noise ordinance limits during rehearsals
    • Parenting — curiosity check: how loud is that toy your kid loves? (Often shockingly loud)
    • Musicians — monitor rehearsal room levels to protect hearing
    • Real estate — measure ambient noise levels in a potential apartment before signing a lease

    Free vs. Pro

    The free version is fully functional — real-time metering, incident logging, reports. The only friction is a short video ad when you start a measurement session. The Pro subscription ($1.99/month) removes ads, unlocks unlimited incident storage, and enables detailed CSV export for anyone who needs to submit formal documentation.

    More From the App Factory

    NoiseLog is part of our collection of free browser and mobile tools built to solve real problems. If you like the idea of gamified productivity, check out FocusForge — a Pomodoro timer with XP, levels, and daily streaks. We also wrote about why the Pomodoro Technique actually works when your timer has streaks.

    Get It

    👉 NoiseLog on Google Play (Android)

    If you’re dealing with noise issues, start logging today. A week of data is worth more than a year of verbal complaints.

  • FocusForge: How Gamification Tricked Me Into Actually Using a Pomodoro Timer

    I’ve downloaded at least ten Pomodoro timer apps over the years. I used each one for about three days before forgetting it existed. Then I built FocusForge, added XP and levels, and accidentally created a focus habit I can’t stop.

    The Pomodoro Problem

    The Pomodoro Technique is elegant: work for 25 minutes, take a 5-minute break, repeat. After four cycles, take a longer break. It’s been around since the late 1980s and it works — when you actually do it.

    The problem isn’t the technique. It’s the timer. A countdown clock gives you no reason to come back tomorrow. There’s no cost to skipping a day, no reward for consistency, no progression. It’s like a gym with no mirror — you can’t see if you’re making progress, so you stop going.

    XP, Levels, and the Streak That Won’t Let You Quit

    FocusForge adds three things to the standard Pomodoro timer:

    1. Experience Points. Every completed focus session earns XP. A Quick 25 gives you 25 XP. A Deep 45 gives you 50. A Marathon 60 gives you 75. It’s a simple formula, but watching a number go up is an irrationally powerful motivator.

    2. Levels. You start as a Rookie. At 100 XP, you become an Apprentice. Then Journeyman, Expert, Master, Legend, and finally Immortal. Each level has its own color and badge. It’s completely meaningless — and completely addictive. I’m a Master. I refuse to let it drop.

    3. Daily Streaks. Complete at least one focus session per day and your streak increments. Miss a day and it resets to zero. This is the mechanic that Duolingo used to build a $12 billion company. It works because loss aversion is stronger than the desire for gain — you don’t want to break a 30-day streak more than you want to skip a 25-minute session.

    What It Looks Like

    The main screen is a large countdown timer with three mode buttons: Quick 25, Deep 45, Marathon 60. Below the timer, a motivational quote rotates — Stoic philosophy from Marcus Aurelius and Seneca, productivity wisdom from Cal Newport and James Clear. It sounds cheesy. It works at 6 AM when you don’t want to start.

    The Stats tab shows your level, XP progress bar, total sessions completed, and streak calendar. The Settings tab lets you upgrade to Pro ($1.99 one-time) to remove ads and unlock custom durations.

    Why Not Just Use the Phone’s Built-in Timer?

    You could. But a phone timer doesn’t track your history, doesn’t reward consistency, and doesn’t create a feedback loop. FocusForge turns “I should focus for 25 minutes” into “I need 15 more XP to reach Legend.” The outcome is the same — deep work — but the motivation mechanism is completely different.

    Get It

    👉 FocusForge on Google Play (Android)

    Free with occasional ads. $1.99 to remove them permanently. No subscription, no account, no data collection beyond what AdMob does in the free version.