Patch local Claude.app so Preview can follow HTTPS auth redirects (e.g. WorkOS), not only localhost.
Output: /Applications/Claude (Patched).app copied from /Applications/Claude.app.
Quit stock Claude before launching the patched copy — both use com.anthropic.claudefordesktop.
/Users/rebers/Dev/sandbox/patch-claude-app.shRequires: node, npm, ditto, PlistBuddy, codesign, shasum.
File inside asar:
.vite/build/index.js
Method: isAllowedUrl — localhost check uses minified R9() (was V6i + port in older builds).
Original (68 bytes):
return R9(t.hostname)&&(t.protocol==="http:"||t.protocol==="https:")Patched (68 bytes):
return t.protocol==="https:"||R9(t.hostname)&&(t.protocol==="http:")Effect:
- Any
https://URL is allowed (external IdP / WorkOS redirects). http://remains limited to loopback viaR9(localhost,127.0.0.1,::1, etc.).
Confirm exactly one occurrence of the original string in raw app.asar before patching (minification changes between releases).
// Original
return V6i(t.hostname)&&t.port===`${this.port}`
// Patched
return t.protocol==="https:"||V6i(t.hostname) rm -rf '/Applications/Claude (Patched).app'
ditto '/Applications/Claude.app' '/Applications/Claude (Patched).app'Do not use plain asar extract + asar pack without Electron’s unpack rules. That packs .node binaries into the archive (~26 MB → ~68 MB) and the app crashes on launch.
Do use @electron/asar with unpack + integrity:
import crypto from 'node:crypto';
import { createPackageWithOptions, extractAll, getRawHeader } from '@electron/asar';
const ORIG = 'return R9(t.hostname)&&(t.protocol==="http:"||t.protocol==="https:")';
const PATCH = 'return t.protocol==="https:"||R9(t.hostname)&&(t.protocol==="http:")';
await extractAll('app.asar', 'extracted/');
// patch extracted/.vite/build/index.js (replace ORIG → PATCH once)
await createPackageWithOptions('extracted/', 'app.asar', {
unpack: '{**/*.node,**/*.dll,**/*.so,**/*.dylib}',
unpackDir: 'app.asar.unpacked',
integrity: true,
compression: 'maximum',
});
const { headerString } = getRawHeader('app.asar');
const hash = crypto.createHash('sha256').update(headerString).digest('hex');
// use hash in Info.plist (next step)Keep native modules in Contents/Resources/app.asar.unpacked/ (copied via ditto from stock). Patched app.asar should stay ~25–26 MB, not ~68 MB.
The 68-byte string can be found and replaced in the raw app.asar without changing file size, but per-file integrity blocks in the asar header still describe the old index.js. Electron validates those at runtime. Always repack with integrity: true.
Path:
/Applications/Claude (Patched).app/Contents/Info.plist
The hash is SHA256 of the asar JSON header (getRawHeader().headerString), not SHA256 of the whole app.asar file.
# WRONG — causes immediate crash
shasum -a 256 app.asar
# RIGHT — compute in Node (see getRawHeader above), then:
/usr/libexec/PlistBuddy -c "Set :ElectronAsarIntegrity:Resources/app.asar:hash <HEADER_HASH>" \
'/Applications/Claude (Patched).app/Contents/Info.plist'Stock app: plist hash matches getRawHeader on unmodified app.asar.
Remove team-bound entitlements; keep Electron runtime entitlements (JIT, devices, etc.):
- remove
com.apple.application-identifier - remove
com.apple.developer.team-identifier - keep
com.apple.security.cs.allow-jitand other device entitlements
Sign inside-out: nested .app / .framework helpers → Contents/MacOS/Claude → bundle root.
xattr -cr '/Applications/Claude (Patched).app'
xattr -dr com.apple.quarantine '/Applications/Claude (Patched).app'
codesign --verify --deep --strict '/Applications/Claude (Patched).app'# Size sanity (patched ≈ stock, not ~68M)
stat -f%z '/Applications/Claude.app/Contents/Resources/app.asar'
stat -f%z '/Applications/Claude (Patched).app/Contents/Resources/app.asar'
# Launch — should see main + Helper (GPU/Renderer/Network), no instant exit
pgrep -fl 'Claude \(Patched\)'Expected JSON checks (script prints this):
{
"patchedAsarBytes": "~25884252",
"headerHashMatchesPlist": true,
"hasOriginalPreviewGate": false,
"hasPatchedPreviewGate": true
}Symptoms:
- Crash ~200 ms after launch
EXC_BREAKPOINT/SIGTRAPon main thread- Stack:
node::builtins::BuiltinLoader::CompileAndCall→ElectronMain procPath:Claude (Patched).app
Common causes:
| Mistake | Result |
|---|---|
shasum whole app.asar for plist |
Wrong hash → crash |
Plain asar pack without unpack |
~68 MB asar → crash |
| In-place patch without integrity regen | Block hash mismatch → crash |
| Wrong/minified gate string | JS error or wrong behavior |
- Gate symbol changes between Claude releases (
V6i+ port →R9+ protocol). Always searchisAllowedUrl/hostnamein freshindex.js. - Byte-length-matched replacements avoid shifting asar offsets, but you still must regenerate integrity metadata.
ElectronAsarIntegrity= header hash, from@electron/asargetRawHeader, not file hash.- Repack must preserve
app.asar.unpackedvia theunpackglob; compression alone does not fix the 68 MB blow-up. - Ad-hoc sign after edits; official team entitlements cannot be reused on a modified bundle.
| File | Purpose |
|---|---|
patch-claude-app.sh |
Full automate: copy → repack → plist → sign → verify |
patch-claude-app.mjs |
Node repack + patch logic |
claude-patch.md |
This document |
After each Claude.app update from Anthropic, re-run patch-claude-app.sh and re-check the gate strings if the app fails verification or crashes at startup.