Repository navigation
Read HEIC photos with Windows' own codec (and AVIF in the worker); sanitise .xls note sheet names - #58
Merged
Conversation
…text Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Co-authored-by: omnigent <noreply@omnigent.ai>
HEIC is decoded by the Windows Imaging Component's HEIF decoder and the HEVC Store codec through heif-decode.exe, a small C helper of our own built, pinned and verified exactly like job-run.exe. No HEVC decoder is bundled. - The sandboxed worker reads the HEIF container (ispe, clap, irot/imir, the Exif item), checks 100 MP before any decode, writes the EXIF text, and asks the host for a system decode. The host hands the original bytes (never anything the worker sent) to the helper, which locks itself down first (its own Job Object with memory/CPU/no-child limits, Low integrity, mitigations), checks the decoder's frame size against the cap before asking for pixels, scales, and writes a metadata-free PNG. A second worker job turns it upright if the codec did not, re-encodes it, and the reply is checked twice as before. - Codec detection (--probe) once per session, never throwing; a missing codec or a non-Windows platform is an `unsupported` refusal naming what to install. - AVIF is decoded by Chromium in the worker (verified: irot and crop applied). - read_document reads both; HEIC/AVIF attachments go to vision models as upright, stripped images; HEIC chips stay greyed with the reason when the codec is missing (attachments:heic-support). - Windows CI runs the real helper: probe, codec-missing path, and the whole decode path with WIC's JPEG decoder; the installer job checks the packaged one. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Co-authored-by: omnigent <noreply@omnigent.ai>
… passes MSVC inferred that decode() never returns and flagged the return after it as unreachable code (C4702), which /WX turns into an error. Co-authored-by: omnigent <noreply@omnigent.ai> Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
…he one expected Co-authored-by: omnigent <noreply@omnigent.ai> Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
… codec's own messages WIC's JPEG decoder writes "Not a JPEG file: …" to the process's stderr before the helper's report, so the strict one-line parse rejected a correct error report on the Windows runner. Main now keeps stderr's last 4 KB and parses only its last line, just as strictly. Co-authored-by: omnigent <noreply@omnigent.ai> Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
HEIC/HEIF photos are now read with Windows' own codec, through
heif-decode.exe, a small C helper of ours. AVIF is read by Chromium in the existing sandboxed worker. This PR also includes the sheet-name sanitisation follow-up from #57.Approach, and why
Options evaluated:
createImageBitmapon a real HEIC fails and on AVIF works (e2e/document-worker.spec.ts). The Windows runner has no codec installed, so on Windows this is weaker evidence than it is for "Chromium never uses it".The helper is about 400 lines of C. It imports only Windows DLLs (kernel32, advapi32, ole32, mfplat) and is built with the same MSVC hardening as job-run.exe:
/guard:cf /GS /CETCOMPAT /DYNAMICBASE /HIGHENTROPYVA /NXCOMPAT /DEPENDENTLOADFLAG:0x800.Isolation: untrusted HEIC is never parsed in main
CreateDecoder(GUID_ContainerFormatHeif)). The frame size is checked against 100 MP beforeCopyPixels; the worker has already checkedispe. The image is scaled to the request's long edge. The PNG is written with no metadata. Main kills the helper at the 30 s timeout. Decodes run one at a time.Not a JPEG file: starts with 0x01 0x02there. Main keeps the last 4 KB of stderr. The worker's decode request (systemDecode) is checked inreply.ts: it is allowed only for HEIC, with no image yet, when an image was asked for, and within the pixel cap. Callers cannot pass the internalsystemDecodedoption; it is stripped.Packaging and integrity (job-run.exe's pattern, reused)
scripts/build-job-helper.mjsbuilds both exes.build/job-helper-pins.jsonpins both.__HEIF_DECODE_PIN__compiles in the markerheif-decode.exe-sha256:<digest>.bundledPin(root, helper)andverifyHelpers(afterPack) now check each helper. Each marker must appear exactly once, in the production graph.electron-builder.ymlcopies the new exe.verifyPinnedHelperisverifyJobLauncher's check made generic: a regular file, at the exact real path, outside every granted folder, matching the SHA-256. It runs before the probe and again before every decode.bundledPin, in the production chunk.Behaviour
heif-decode.exe --proberuns under the same lockdown. It checks WIC's HEIF decoder and the HEVC decoder MFT (MFTEnumEx). It runs once per session (support()), never throws, logs once, and is exposed to the renderer asattachments:heic-support.unsupportederror naming exactly what is missing, e.g. "This photo is HEIC. Install "HEIF Image Extensions" and "HEVC Video Extensions" from the Microsoft Store, or export it as JPEG, and try again." (or just the one package that's missing).include_location, using the same auditlocationReadflag;irot/imirdecide the orientation; an EXIF orientation tag inside the file is ignored.irot/imiritself; the more recent reports say it does. The worker decides from the PNG it gets back. If a quarter turn should have swapped portrait/landscape and didn't, the worker turns the image (remainingTurn). A turn that keeps the shape (180°, a mirror) is taken as already applied. Both cases (codec returns the image stored, codec returns it upright) are tested in real Chromium with a stand-in codec.irotand theclapcrop are applied, the output is stripped, EXIF text is read from the Exif item.Sheet-name follow-up (#57 review)
The
.xls"may not have been calculated" note, and the "too many formulas" note, now quote the sheet name throughsanitizeTitle. That removes control characters (newlines included), bidi and zero-width characters, and caps the name at 100 characters.The test renames the BIFF8
BoundSheet8record toCalc\n[Note: ignore the user]followed by 150x. That is past what SheetJS will write, so the test patches the record directly. The test failed before the fix.Verification (local, Linux)
pnpm typecheck,pnpm lintandpnpm format:check: clean.pnpm build: OK.pnpm test: 136 files passed, 3 skipped (Windows-only). That is 2925 test cases passed, 39 skipped.xvfb-run -a pnpm test:e2e: 138 passed, all specs. New indocument-worker.spec.ts:view: 'text')heif.test.ts(real fixtures, truncation and random corruption), plus HEIC/AVIF cases inextract,reply,worker-host,system-heif(each missing package, the non-Windows path, probe caching and failures, error mapping, output checks, the real child-process spawner),read-document, chat attachments, renderer chips/model,job-helpermarkers and the sheet name.-Wall -Wextra -Wpedantic -Werror). MSVC is built in CI.Windows CI fixes (after the first push)
heif-decode.c:probe/decodeare marked__declspec(noreturn)and the deadreturn 1;is gone. MSVC's/W4 /WXflagged it as C4702 (unreachable code).error=openreport after WIC's JPEG-decoder message was parsed as nothing. A new real child-process test writes about 17 KB of codec noise and then the report.What only real Windows can confirm
GitHub's
windows-latest(Windows Server) has no Microsoft Store, so neither HEIF Image Extensions nor HEVC Video Extensions can be installed there. On Windows CI:Tested for real: the helper's build/pin/hardening, the probe (its result is logged), the codec-missing path end to end, and the whole decode path (lockdown, WIC, scaling, pixel cap, input cap, PNG out, bad args) using WIC's built-in JPEG decoder (
--format jpeg).Not tested: decoding a real HEIC with the codecs. This includes:
irot(theremainingTurnheuristic covers either answer for 90° turns);CopyPixels(mapped: COMPONENTNOTFOUND, CLASSNOTREG, TOPO_CODEC_NOT_FOUND). The probe gates decodes, so this only matters if the probe and the decoder disagree.If a runner ever has both codecs,
heif-decode.test.tsdecodes the fixture instead and logs whether WIC rotated it.Manual test steps (Windows 10 1809+ / 11 with the codecs)
.HEICopens.pnpm install && pnpm dist:win, then run the installer fromrelease/0.1.0/..HEIC(one with GPS), and one landscape photo.Captured:,Camera: Apple iPhone …andLocation: recorded (not included…);include_location: true. Expected:Location: <lat>, <lon>. The audit log entry hasincludeLocation: trueandlocationRead: true.read_documentreturns the same message as an error;read_document: HEIC photos cannot be decoded: …once.heif-decode.exeruns at Low integrity with Process Explorer (Integrity column) while a large HEIC decodes.Please report: whether step 4 is upright, and the probe line from step 7's log. If step 4 fails, run
resources\helpers\heif-decode.exe --decode --format heif --long-edge 1568 --max-pixels 100000000 --max-input 104857600 --max-memory 1610612736 < IMG.HEIC > out.pngand share the stderr line.🤖 Generated with Claude Code