Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
77 commits
Select commit Hold shift + click to select a range
d0d9e89
build: bump source/target to Java 17
valandi May 13, 2026
0b3077e
Revert "build: bump source/target to Java 17"
valandi May 13, 2026
495077f
build: bump source/target to Java 11
valandi May 13, 2026
137e60c
build: merge META-INF/services in shade transformer
valandi May 13, 2026
72a7921
feat: add Logger listener fan-out for live log streaming
valandi May 13, 2026
8f56e89
feat: add TestExecutor completion listener for live per-test status
valandi May 13, 2026
2053350
build: add Jetty 11 and java-keyring deps
valandi May 13, 2026
5eb5a73
feat(gui): add LogRedactor for SSE log scrubbing
valandi May 13, 2026
ff79822
feat(gui): add SourcePathValidator for /api/run input
valandi May 13, 2026
9034884
feat(gui): add GuiToken (per-launch bearer token)
valandi May 13, 2026
544893d
feat(gui): add SecretsStore (java-keyring + in-memory backend)
valandi May 13, 2026
3862c54
feat(gui): add RunState and SseEvent types
valandi May 13, 2026
8789844
feat(gui): add RunStream SSE broker with per-client bounded queue
valandi May 13, 2026
0d9bf37
feat(gui): add TokenAuthFilter (Bearer token + Host/Origin)
valandi May 13, 2026
e15738f
feat(gui): add NativePathChooser for native file/folder dialogs
valandi May 13, 2026
d9eb530
feat(gui): add RunController (status/run/cancel + listener wiring)
valandi May 13, 2026
7e19c0f
feat(gui): add IndexHtmlServlet (injects per-launch token)
valandi May 13, 2026
cc76337
feat(gui): add GuiLauncher (cross-platform browser open)
valandi May 13, 2026
cd66b4a
feat(gui): add GuiServer (Jetty boot, handler wiring) + boot IT
valandi May 13, 2026
08a4ad7
feat: wire --gui flag in ImageTester.main
valandi May 13, 2026
3a11c61
feat(gui): scaffold React + Vite + Tailwind frontend
valandi May 13, 2026
e2fc889
feat(gui): frontend types + API/SSE clients
valandi May 13, 2026
5a73db0
feat(gui): SetupCard, StatusPane, TestRow components + tests
valandi May 14, 2026
2de06cf
chore(gui): remove stray .js files committed by tsc -b in Task 20
valandi May 14, 2026
85ec86c
feat(gui): App.tsx wires components, state, SSE
valandi May 14, 2026
e28c3f7
build(gui): build frontend via frontend-maven-plugin and bake into JAR
valandi May 14, 2026
7c5ce26
test(gui): shade smoke test for SDK service-loader integrity
valandi May 14, 2026
08bca0b
build(gui): jpackage gui-installers profile for per-OS installers
valandi May 14, 2026
8b0f50e
ci(gui): build and upload per-OS installers via gui-installers profile
valandi May 14, 2026
6249e95
ImageTester UI stage one
valandi May 15, 2026
7082c26
Merge branch 'main' into feat/gui-v1
valandi May 25, 2026
e664ddb
@
valandi Jun 5, 2026
580238e
Merge branch 'main' into feat/gui-v1
valandi Jun 25, 2026
cab6a19
feat: add Logger.printHeartbeat for liveness updates
valandi Jun 25, 2026
5bb43f8
feat: emit liveness heartbeat for in-flight tests past 30s grace
valandi Jun 25, 2026
fdfd76b
fix: make nanoTimeSource_ volatile for cross-thread visibility
valandi Jun 25, 2026
81919e7
fix: resolve watermark merge conflict in favor of main's implementation
valandi Jun 25, 2026
d39096e
refactor: extract RunConfigFactory from main() for CLI/GUI reuse
valandi Jun 26, 2026
45f1b3b
feat: add RunRequest payload and argv translator for GUI options
valandi Jun 26, 2026
605cd11
test: lock GUI argv → Config parity with CLI
valandi Jun 26, 2026
6c38283
feat: RunController.start accepts full RunRequest via RunConfigFactory
valandi Jun 26, 2026
21a738c
fix: exercise production builder in -dv test (review C1)
valandi Jun 26, 2026
82b2eec
feat: route -rwo to watermark-out mode with cleaned-files completion
valandi Jun 26, 2026
728d55d
feat: synchronous option validation; /api/run returns 400 on bad options
valandi Jun 26, 2026
8f7a5f8
feat: options registry for GUI option controls
valandi Jun 26, 2026
dd41ada
feat: options state, persistence, and run payload builder
valandi Jun 26, 2026
6e654aa
feat: generic scalar option control
valandi Jun 26, 2026
65e0522
feat: composite option controls (regions, proxy, properties, image cut)
valandi Jun 26, 2026
d95b9da
feat: tabbed options drawer with control dispatch
valandi Jun 26, 2026
83d44a7
feat: wire options drawer into app with persistence and live status pane
valandi Jun 26, 2026
aed61d0
fix: show logs by default in status pane (matches always-visible-logs…
valandi Jun 26, 2026
3033876
docs: restore onCancel rationale comment (review M-2)
valandi Jun 26, 2026
1a09edb
feat: render watermark-out cleaned-files result in status pane
valandi Jun 26, 2026
8c2cfd4
fix: rwauto-without-rwo auto-clean, surface run errors, tidy watermar…
valandi Jun 26, 2026
b39ded1
Adding new options for GUI
valandi Jul 1, 2026
f6d29b6
Merge branch 'main' into feat/gui-v1
valandi Jul 2, 2026
46b9310
feat(gui): expand options as full-width band below setup/status
valandi Jul 2, 2026
9f1d93d
fix(gui): emit run-started for live status; add -rt/-rf options
valandi Jul 2, 2026
e4e3d95
docs(gui): customer-facing release notes template for demo releases
valandi Jul 2, 2026
b13d764
docs: add GUI download section pointing at GitHub Releases
valandi Jul 2, 2026
ec4607e
ci(gui): release workflow — unsigned per-OS installers to GitHub pre-…
valandi Jul 2, 2026
2070420
fix(gui): ship Windows MSI instead of exe wrapper
valandi Jul 2, 2026
156823c
fix(build): vendor EyesUtilities in project-local Maven repo
valandi Jul 2, 2026
5d22a27
fix(gui): declare @types/node so frontend build passes on clean check…
valandi Jul 2, 2026
f621f49
ci(gui): fail-fast off so one OS failure cannot cancel other installers
valandi Jul 2, 2026
947ca14
ci(gui): run installer build under bash on all runners
valandi Jul 2, 2026
33a542e
fix(gui): remove placeholder index.html that shipped in CI installers
valandi Jul 2, 2026
5398a5b
fix(gui): force overwrite on frontend bundle copy; pin exec-maven-plu…
valandi Jul 2, 2026
718ec6e
fix(gui): pass GUI api key to EyesFactory; focus native file dialog o…
valandi Jul 2, 2026
8bc30ce
docs(gui): explain macOS keychain prompt in release notes
valandi Jul 2, 2026
d3566f1
ci(gui): macos-13 retired from hosted fleet; use macos-15-intel for x…
valandi Jul 2, 2026
b6c129b
Crop mark detector and GUI updates
valandi Jul 10, 2026
f470941
Merge branch 'main' into feat/gui-v1
valandi Jul 10, 2026
53cd69a
Fix broken tests
valandi Jul 10, 2026
d39fb1a
fix(pii-guard): portable OOXML scan, CRLF-safe allowlist, sample-emai…
valandi Jul 10, 2026
f0b9342
feat(gui): trim checkbox, per-tab option badges, active-option chips
valandi Jul 10, 2026
5388fc0
ci(release): restore build-jars job, macos-15-intel runner, fail-fast…
valandi Jul 10, 2026
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
109 changes: 109 additions & 0 deletions .githooks/pii-guard.sh
Original file line number Diff line number Diff line change
@@ -0,0 +1,109 @@
#!/usr/bin/env bash
# Blocks customer data from entering this public repo.
#
# Checks (staged files on commit, whole tree in CI):
# 1. Known customer file names (e.g. PermWat) are rejected outright.
# 2. Every binary fixture must be listed in .github/fixture-allowlist.txt —
# adding one forces a reviewable allowlist edit in the same change.
# 3. OOXML files (xlsx/docx/pptx) are unzipped and their XML scanned for
# email addresses outside the allowed domains.
# 4. Text additions are scanned for email addresses outside allowed domains.
#
# Note: an email rasterized into an image cannot be detected here — that is
# what the allowlist review step is for.
#
# Usage: pii-guard.sh --staged | --all

set -u
MODE="${1:---staged}"
cd "$(git rev-parse --show-toplevel)" || exit 1

ALLOWLIST=".github/fixture-allowlist.txt"
EMAIL_RE='[A-Za-z0-9._%+-]+@[A-Za-z0-9.-]+\.[A-Za-z]{2,}'
ALLOWED_DOMAINS_RE='@(applitools\.com|example\.com|proxy\.local|([A-Za-z0-9-]+\.)*noreply\.github\.com|anthropic\.com)$'
BINARY_EXT_RE='\.(pdf|xlsx?|docx?|pptx?|png|jpe?g|gif|bmp|ico|zip|jar|ps)$'
OOXML_EXT_RE='\.(xlsx|docx|pptx)$'
BLOCKED_NAME_RE='permwat|black_card'
# Exact addresses allowed anywhere: fake identities from public sample datasets.
ALLOWED_EMAILS='bob@msn.com'

fail=0
err() { printf 'PII-GUARD: %s\n' "$1" >&2; fail=1; }

# OOXML scanning needs unzip; a guard that cannot scan must fail, not pass.
command -v unzip >/dev/null 2>&1 || { err "'unzip' not found — cannot scan OOXML files"; exit 1; }

ALLOWED_EMAILS_TMP=$(mktemp) || exit 1
ALLOWLIST_TMP=$(mktemp) || exit 1
trap 'rm -f "$ALLOWED_EMAILS_TMP" "$ALLOWLIST_TMP"' EXIT
printf '%s\n' $ALLOWED_EMAILS > "$ALLOWED_EMAILS_TMP"
# Strip CRs: Windows checkouts render the allowlist with CRLF, which breaks
# exact-line matching. Missing allowlist -> empty file -> fail closed.
tr -d '\r' < "$ALLOWLIST" > "$ALLOWLIST_TMP" 2>/dev/null || true

if [ "$MODE" = "--staged" ]; then
files=$(git diff --cached --name-only --diff-filter=ACMR)
else
files=$(git ls-files)
fi

# --- 1 & 2 & 3: per-file checks -------------------------------------------
while IFS= read -r f; do
[ -z "$f" ] && continue
lower=$(printf '%s' "$f" | tr '[:upper:]' '[:lower:]')

if printf '%s' "$lower" | grep -qE "$BLOCKED_NAME_RE"; then
err "'$f' matches a known customer-data name and must never be committed"
continue
fi

if printf '%s' "$lower" | grep -qE "$BINARY_EXT_RE"; then
if ! grep -qxF "$f" "$ALLOWLIST_TMP"; then
err "binary fixture '$f' is not in $ALLOWLIST — verify it contains no customer data (open any embedded images!), then add its path"
fi
fi

if printf '%s' "$lower" | grep -qE "$OOXML_EXT_RE"; then
tmp=$(mktemp) || exit 1
if [ "$MODE" = "--staged" ]; then
git show ":$f" > "$tmp" 2>/dev/null || cp "$f" "$tmp"
else
cp "$f" "$tmp"
fi
# Extract XML-ish entries one by one: unzip wildcard patterns do not
# match subdirectory entries on all platforms (Windows Info-ZIP), which
# made this scan silently pass. Never rely on '*.xml'.
hits=$(unzip -Z1 "$tmp" 2>/dev/null | grep -Ei '\.(xml|rels|vml)$' \
| while IFS= read -r entry; do unzip -p "$tmp" "$entry" 2>/dev/null; done \
| grep -aoE "$EMAIL_RE" | grep -vE "$ALLOWED_DOMAINS_RE" | grep -vxFf "$ALLOWED_EMAILS_TMP" | sort -u)
rm -f "$tmp"
if [ -n "$hits" ]; then
err "'$f' embeds non-allowlisted email(s): $(printf '%s' "$hits" | tr '\n' ' ')"
fi
fi
done <<EOF
$files
EOF

# --- 4: text scan -----------------------------------------------------------
if [ "$MODE" = "--staged" ]; then
text_hits=$(git diff --cached --diff-filter=ACMR -U0 \
| grep -E '^\+[^+]' \
| grep -oE "$EMAIL_RE" | grep -vE "$ALLOWED_DOMAINS_RE" | grep -vxFf "$ALLOWED_EMAILS_TMP" | sort -u)
else
text_hits=$(git grep -IhoE "$EMAIL_RE" -- . 2>/dev/null \
| grep -vE "$ALLOWED_DOMAINS_RE" | grep -vxFf "$ALLOWED_EMAILS_TMP" | sort -u)
fi
if [ -n "$text_hits" ]; then
err "non-allowlisted email address(es) in text: $(printf '%s' "$text_hits" | tr '\n' ' ')"
fi

if [ "$fail" -ne 0 ]; then
{
echo 'PII-GUARD: commit blocked.'
echo 'PII-GUARD: if this is genuinely clean synthetic data, add binaries to .github/fixture-allowlist.txt'
echo 'PII-GUARD: or extend the allowed-domain list in .githooks/pii-guard.sh — in the same reviewed change.'
} >&2
exit 1
fi
exit 0
3 changes: 3 additions & 0 deletions .githooks/pre-commit
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
#!/bin/sh
# Enable with: git config core.hooksPath .githooks
exec "$(git rev-parse --show-toplevel)/.githooks/pii-guard.sh" --staged
55 changes: 55 additions & 0 deletions .github/fixture-allowlist.txt
Original file line number Diff line number Diff line change
@@ -0,0 +1,55 @@
# Binary files approved for this public repository.
# .githooks/pii-guard.sh (pre-commit + CI) rejects any tracked binary not
# listed here. Before adding a file: open it, including every embedded image
# (xlsx/docx media can rasterize emails and names), and confirm it contains
# no customer data. Sanitize first if in doubt — see
# TestData/xlsx-header-watermark.xlsx for an example of a scrubbed fixture.
TestData/a/wikipedia.png
TestData/b/Lorem1.pdf
TestData/b/c/JustPDF/Lorem2.pdf
TestData/b/c/JustPDF/Lorem3.pdf
TestData/b/c/JustPostscript/Lorem2.ps
TestData/b/c/d/wikihow.png
TestData/b/c/d/wikiquote.png
TestData/b/c/googleforgoogle.png
TestData/diffs/actual/lorem_20.pdf
TestData/diffs/base/lorem_20.pdf
TestData/image-T0MP130031-01.png
TestData/jpegs/alphabetic/a.jpg
TestData/jpegs/alphabetic/b.jpg
TestData/jpegs/alphabetic/c.jpg
TestData/jpegs/alphabetic/d.jpg
TestData/jpegs/mixed/1.jpg
TestData/jpegs/mixed/2.jpg
TestData/jpegs/mixed/3.jpg
TestData/jpegs/mixed/4.jpg
TestData/jpegs/mixed/a.jpg
TestData/jpegs/mixed/b.jpg
TestData/jpegs/mixed/c.jpg
TestData/jpegs/mixed/d.jpg
TestData/jpegs/numeric/1.jpg
TestData/jpegs/numeric/2.jpg
TestData/jpegs/numeric/3.jpg
TestData/jpegs/numeric/4.jpg
TestData/multi-format/Accessing user tickets in Zendesk (1).docx
TestData/multi-format/Copy of Dealing with sensitive data in support.pptx
TestData/multi-format/contributing-to-docs (2).pptx
TestData/multi-format/sample.docx
TestData/multi-format/sample.pptx
TestData/multi-format/sample.ps
TestData/multi-format/test_sample_xlsx_Small.xlsx
TestData/xlsx-header-watermark.xlsx
gui-launch.png
jars/test_jbig.pdf
libs/com/applitools/eyesutilities/EyesUtilities/1.6.6/EyesUtilities-1.6.6.jar
orig_page_fullsize.png
orig_policy_doc.png
src/test/resources/fixtures/corrupted.pdf
src/test/resources/fixtures/empty.pdf
src/test/resources/fixtures/password-protected.pdf
src/test/resources/fixtures/sample.png
src/test/resources/fixtures/valid-1-page.pdf
src/test/resources/fixtures/valid-10-page.pdf
src/test/resources/fixtures/valid-2-page.pdf
src/test/resources/watermark/PDF_with_watermark.pdf
src/test/resources/watermark/PDF_without_watermark.pdf
40 changes: 40 additions & 0 deletions .github/release-template.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,40 @@
ImageTester GUI — visual testing for images, PDFs & documents, powered by Applitools Eyes. Download the installer for your machine, double-click it, and the app opens in your browser. Nothing else to install.

## Which file do I download?

| Your machine | File |
|---|---|
| Windows | `ImageTester-{{VERSION}}-Windows.msi` |
| Mac with Apple Silicon | `ImageTester-{{VERSION}}-macOS-AppleSilicon.dmg` |
| Mac with Intel | `ImageTester-{{VERSION}}-macOS-Intel.dmg` |
| Linux (Debian/Ubuntu) | `ImageTester-{{VERSION}}-Linux.deb` |

Not sure which Mac you have? Apple menu → **About This Mac**: "Chip: Apple M…" means Apple Silicon; "Processor: Intel…" means Intel.

## Command line (CLI jars)

Prefer the CLI? Grab a jar and run it with Java 11+: `java -jar ImageTester_{{VERSION}}.jar -k <api-key> [options]`

| Environment | File |
|---|---|
| Any (largest, bundles every platform) | `ImageTester_{{VERSION}}.jar` |
| Windows x64 | `ImageTester_{{VERSION}}_Windows.jar` |
| Mac Intel | `ImageTester_{{VERSION}}_Mac.jar` |
| Mac Apple Silicon | `ImageTester_{{VERSION}}_MacArm.jar` |
| Linux x64 | `ImageTester_{{VERSION}}_Linux.jar` |
| Alpine Linux | `ImageTester_{{VERSION}}_Alpine.jar` |
| Linux ARM | `ImageTester_{{VERSION}}_Arm.jar` |

<!-- UNSIGNED-START -->
## First launch

This demo build isn't code-signed yet (signed builds are planned), so your computer will warn you the first time you run it:

- **Windows** shows "Windows protected your PC". Click **More info**, then **Run anyway**.
- **macOS** blocks the first launch. Open **System Settings → Privacy & Security**, scroll down, click **Open Anyway** next to ImageTester, and confirm.
- **macOS** may also ask to use "confidential information stored in Applitools ImageTester in your keychain" — that's the app reading back the API key it saved securely in your Keychain. Click **Always Allow**. (Unsigned builds re-ask after each update; signed builds won't.)
<!-- UNSIGNED-END -->

## Using the app

Launching ImageTester opens a page in your browser. Paste your Applitools API key, choose the file or folder to test, and click **Run test**. Results stream in live, with a link to your results on the Applitools dashboard.
41 changes: 41 additions & 0 deletions .github/workflows/ci.yml
Original file line number Diff line number Diff line change
Expand Up @@ -5,12 +5,20 @@ on:
branches: [main]
pull_request:
branches: [main]
workflow_dispatch:

concurrency:
group: ${{ github.workflow }}-${{ github.ref }}
cancel-in-progress: true

jobs:
pii-guard:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- name: Scan tracked files for customer data
run: bash .githooks/pii-guard.sh --all

unit-tests:
runs-on: ubuntu-latest

Expand Down Expand Up @@ -45,3 +53,36 @@ jobs:
env:
APPLITOOLS_API_KEY: ${{ secrets.APPLITOOLS_API_KEY }}
run: mvn test --batch-mode -Peyes-tests

gui-installers:
needs: unit-tests
strategy:
fail-fast: false
matrix:
os: [ubuntu-latest, windows-latest, macos-latest]
runs-on: ${{ matrix.os }}

steps:
- uses: actions/checkout@v4

- uses: actions/setup-java@v4
with:
distribution: temurin
java-version: 17
cache: maven

- uses: actions/setup-node@v4
with:
node-version: 20

- name: Build installer
# bash on every OS: Windows' default pwsh splits -Dowasp.skip=true at the dot
shell: bash
run: mvn -Pgui-installers -DskipTests -Dowasp.skip=true clean verify --batch-mode

- name: Upload installer
uses: actions/upload-artifact@v4
with:
name: imagetester-installer-${{ matrix.os }}
path: target/installers/*
if-no-files-found: error
53 changes: 51 additions & 2 deletions .github/workflows/release.yml
Original file line number Diff line number Diff line change
Expand Up @@ -24,6 +24,9 @@ jobs:
java-version: 17
cache: maven

- name: Scan tracked files for customer data
run: bash .githooks/pii-guard.sh --all

- name: Resolve version and validate tag
id: ver
shell: bash
Expand Down Expand Up @@ -53,13 +56,16 @@ jobs:
build:
needs: validate
strategy:
# One OS failing must not cancel the other installers mid-build.
fail-fast: false
matrix:
include:
- os: windows-latest
label: Windows
- os: macos-latest
label: macOS-AppleSilicon
- os: macos-13
# macos-13 was retired from the hosted fleet; macos-15-intel is the x64 label
- os: macos-15-intel
label: macOS-Intel
- os: ubuntu-latest
label: Linux
Expand All @@ -82,6 +88,8 @@ jobs:
node-version: 20

- name: Build installer
# bash on every OS: Windows' default pwsh splits -Dowasp.skip=true at the dot
shell: bash
run: mvn -Pgui-installers -DskipTests -Dowasp.skip=true clean verify --batch-mode

# Signing seam: fail loudly if secrets exist before signing is implemented,
Expand Down Expand Up @@ -123,9 +131,50 @@ jobs:
path: target/installers/*
if-no-files-found: error

build-jars:
needs: validate
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4

- uses: actions/setup-java@v4
with:
distribution: temurin
java-version: 11
cache: maven

# Platform jars differ only by which eyes-universal-core binary is bundled,
# so a single Linux build produces all seven (frontend-maven-plugin installs
# its own Node for the GUI bundle).
- name: Build CLI jars (universal + six platform jars)
run: mvn -DskipTests package --batch-mode

- name: Verify expected jars exist
shell: bash
run: |
cd jars
# Jars are named with the pom version; a suffixed tag (v1.2.3-rc1) still builds 1.2.3 jars.
BASE="${{ needs.validate.outputs.version }}"
BASE="${BASE%%-*}"
for suffix in "" "_Windows" "_Mac" "_MacArm" "_Linux" "_Alpine" "_Arm"; do
f="ImageTester_${BASE}${suffix}.jar"
if [ ! -f "$f" ]; then
echo "Missing expected jar: $f" >&2
ls >&2
exit 1
fi
done
ls -la ImageTester_*.jar

- uses: actions/upload-artifact@v4
with:
name: cli-jars
path: jars/ImageTester_*.jar
if-no-files-found: error

release:
if: startsWith(github.ref, 'refs/tags/')
needs: [validate, build]
needs: [validate, build, build-jars]
runs-on: ubuntu-latest
permissions:
contents: write
Expand Down
2 changes: 2 additions & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,8 @@ out/
target/
*.iml
*.jar
# libs/ is a file-based Maven repo for deps not published anywhere public
!libs/**
Artifacts/
debug/
src/test/java/Private/
Expand Down
32 changes: 32 additions & 0 deletions CONTRIBUTING.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,32 @@
# Contributing

## One-time setup: PII guard

This is a public repository that regularly handles customer files during
support work. A guard blocks customer data from being committed. Enable the
local hook once per clone:

```
git config core.hooksPath .githooks
```

CI runs the same scan (`.githooks/pii-guard.sh --all`) on every PR, push, and
release, so the guard holds even without the local hook — the hook just fails
faster.

## Test fixture policy

- **Never commit customer files.** Files from support tickets (PDFs, xlsx,
screenshots) stay outside the repo or in gitignored directories
(`TestData/` is gitignored; tracked fixtures there were explicitly
force-added).
- Every binary fixture must be listed in `.github/fixture-allowlist.txt`.
Adding one is a deliberate, reviewed act: open the file first — **including
every embedded image**, since spreadsheets and documents rasterize emails
and names into media parts that text scans cannot see.
- If a test needs a structure only a customer file has, sanitize a copy:
replace embedded media with generated images and scrub metadata (see
`TestData/xlsx-header-watermark.xlsx`, a scrubbed clone of a customer file,
and the note in `XlsxWatermarkStamperTest`).
- The email-domain allowlist lives in `.githooks/pii-guard.sh`
(`example.com`, `applitools.com`, …). Extend it only in a reviewed change.
Loading
Loading