---
type: toolset
title: Bezetacil Toolset — Electron Runtime Introspection
description: Pipeline ASAR, deobfuscation, harness runtime, debugger spy, CDP extraction
tags: [claude-desktop, electron, asar, reverse-engineering, harness, toolset]
timestamp: 2026-06-24
---

# Bezetacil Toolset — Electron Runtime Introspection

Toolset for reverse engineering, patching, and runtime introspection of Electron apps (developed for Claude Desktop).

## Overview

```
┌─────────────────────────────────────────────────────────┐
│ 1. ASAR Pipeline     │ Extract → Patch → Repack → Sign  │
│ 2. Deobfuscation     │ webcrack → deloom → analysis     │
│ 3. Bezetacil Harness │ Runtime injection + hot reload   │
│ 4. Debugger Spy      │ Network capture + WebSocket      │
│ 5. CDP Tools         │ Python PEP 723 scripts           │
│ 6. Bridge Server     │ Bidirectional TCP Python ↔ Node  │
└─────────────────────────────────────────────────────────┘
```

---

## 1. ASAR Pipeline

### Tools

```bash
# Install
npm install -g @electron/asar deloom webcrack

# Binary locations
~/.hermes/node/bin/asar
~/.hermes/node/bin/deloom
~/.hermes/node/bin/webcrack
```

### Complete Pipeline

```bash
# 1. Backup
cp /Applications/Claude.app/Contents/Resources/app.asar /tmp/app.asar.backup

# 2. Extract (with symlink to unpacked)
ln -sf /Applications/Claude.app/Contents/Resources/app.asar.unpacked /tmp/app.asar.backup.unpacked
asar extract /tmp/app.asar.backup /tmp/extracted/

# 3. Patch (Python)
python3 -c "
with open('/tmp/extracted/.vite/build/index.js') as f: content = f.read()
content = content.replace('old', 'new')
with open('/tmp/extracted/.vite/build/index.js', 'w') as f: f.write(content)
"

# 4. Repack (Node.js API — preserves native binaries)
cat > /tmp/build.mjs << 'EOF'
import { createPackageWithOptions, getRawHeader } from '@electron/asar/lib/asar.js';
import { createHash } from 'crypto';
const src = '/tmp/extracted', dest = '/tmp/app.asar.patched';
await createPackageWithOptions(src, dest, { unpack: '{*.{node,dylib},spawn-helper}' });
const hash = createHash('SHA256').update(getRawHeader(dest).headerString).digest('hex');
console.log('Hash:', hash);
EOF
node /tmp/build.mjs

# 5. Update Info.plist (9 files!) with header hash
python3 -c "
import plistlib, os
HASH = '...'
for p in [list of 9 plists]:
    with open(p,'rb') as f: pl = plistlib.load(f)
    pl['ElectronAsarIntegrity'] = {'Resources/app.asar': {'algorithm': 'SHA256', 'hash': HASH}}
    with open(p,'wb') as f: plistlib.dump(pl, f)
"

# 6. Deploy + Sign

cp /tmp/app.asar.patched /Applications/Claude.app/Contents/Resources/app.asar
cp -r /tmp/app.asar.patched.unpacked/* /Applications/Claude.app/Contents/Resources/app.asar.unpacked/
sudo codesign --remove-signature /Applications/Claude.app
sudo codesign -s - /Applications/Claude.app --deep --force --entitlements /tmp/entitlements.plist
```

### Lessons Learned

- **`--unpack-dir` CLI only accepts 1 value** → use glob `{...}` or Node.js API
- **9 copies of `ElectronAsarIntegrity`** spread across Info.plist files
- **Hash is SHA256 of `headerString`** (JSON), not the entire file
- **`--unpack` CLI is buggy for `.node`** → use API `createPackageWithOptions({unpack})`
- **Native binaries** (`.node`, `.dylib`, `spawn-helper`) must stay in `unpacked/`
- **Code signing**: remove original signature, re-sign ad-hoc with entitlements
- **Minimum entitlements**: `com.apple.security.virtualization`, `com.apple.security.device.audio-input`, `com.apple.security.cs.allow-jit`

### Applied Patches (V17c final)

| # | Patch | Purpose |
|---|-------|-----------|
| 1 | `function Uy(A){return false}` | 1p mode — claude.ai voice UI |
| 2 | `voice:{buildGate:()=>!0}` | Enable voice feature |
| 3 | `if(false)return{...entitlement_missing...}` | Bypass entitlements verification |
| 4 | Audio proxy handler in array `C` | Proxy `/v1/audio/*` → gateway |
| 5 | `Sqi="localhost:8443"` | Redirect CSP to Caddy (not effective) |
| 6 | `require("../../harness-bootstrap")` | Inject harness at boot |

---

## 2. Deobfuscation

### Pipeline

```bash
# Deminify + unpack webpack bundle
node ~/.hermes/node/bin/webcrack index.js -o /tmp/demified/

# Result: 19MB deobfuscated.js + extracted modules
ls /tmp/demified/
# 333.js  486.js  515.js  747.js  823.js  949.js  bundle.json  deobfuscated.js
```

### Key Code Discoveries

| Symbol | Meaning | Line |
|---------|-------------|-------|
| `Uy()` | `deploymentModeIs3p` — returns `true` in 3p | 150368 |
| `_cr` | 1p deployment mode class | 216220 |
| `nk` | `app://localhost` — URL in 3p | 149817 |
| `Cmn()` | Registers protocol handler `app://` | 482078 |
| `k3A()` | Blocked paths checker | 481540 |
| `qpn` | List of blocked paths in 3p | 481538 |
| `Emn()` | API stub handler (array `C`) | 481925 |
| `Sqi` | `a-api.anthropic.com` (CSP) | 144596 |
| `jr.quickAccess.dictation` | Swift bridge dictation | 318974 |
| `fKr()` | Initializes Swift bridge | 319000 |

---

## 3. Bezetacil Harness

### Location

```
~/.bezetacil/
└── scripts/
    └── 001-debugger-spy.js   ← auto-loaded at boot
```

### Harness Bootstrap (injected into `index.js`)

```javascript
require("../../harness-bootstrap");
```

Loads automatically when the app starts. The harness:
1. Reads `~/.bezetacil/scripts/*.js` in alphabetical order
2. Executes each script via `eval()` in the main process context
3. **Hot reload**: `fs.watch()` on directory — editing file = automatic reload
4. Exposes global API: `__HARNESS__.eval()`, `__HARNESS__.log()`, `__HARNESS__.reload()`

### Harness API

```javascript
// Execute code in main process
__HARNESS__.eval("require('electron').BrowserWindow.getAllWindows().length")

// Manual hot reload
__HARNESS__.reload()

// Status

__HARNESS__.status()
// → { log: '/tmp/claude-harness.log', scriptsDir: '...', pid: 89917 }
```

### Development Cycle

```
1. Edit ~/.bezetacil/scripts/001-my-script.js
2. Save
3. ~3s later: automatic hot reload
4. Check /tmp/claude-harness.log for logs
```

**Zero repacks!** Only need to rebuild if changing the bootstrap itself.

### Naming Convention and Hot Reload

**CRITICAL RULE:** Bezetacil scripts must NEVER be versioned with incremental numeric suffixes (e.g., `v001-script.js`, `v002-script.js`, `007-capture.js`).

**Why:** The harness loads ALL `*.js` from the directory in alphabetical order. Each hot reload **adds** new handlers via `eval()` without removing previous ones. Versioning with incremental names causes multiple versions of the same script to coexist in memory, each registering their own event listeners — causing handler duplication, race conditions, and files generated with incorrect names.

**Correct format:** Use fixed descriptive names.

```
✅ 001-debugger-spy.js        ← fixed name, rewritten when updated
✅ capture-tts-audio.js       ← fixed descriptive name
✅ supports1m-autodiscovery.js ← fixed descriptive name

❌ 001-script.js, 002-script.js, 003-script.js  ← NEVER do this
❌ v1-spy.js, v2-spy.js                          ← NEVER do this
❌ 007-capture-tts-audio.js                      ← NEVER do this
```

**Guard pattern:** Every script must start with a guard to prevent multiple instances after hot reload:

```javascript
(function() {
  // Guard: prevents multiple instances after hot reload
  if (globalThis.__MY_SCRIPT_LOADED__) return;
  globalThis.__MY_SCRIPT_LOADED__ = true;
  // ... script ...
})();
```

**Additional conventions:**
- `.js` — active script, loaded at boot
- `.js.disabled` — disabled script (ignored by harness)
- `.js.inject_first` — injected BEFORE normal scripts (hooks that need to be in place before initialization)
- `.js.inject_last` — injected AFTER normal scripts (observers, spy, loggers)

**Lifecycle:**

```
1. Edit ~/.bezetacil/scripts/my-script.js    ← always the SAME name
2. Save
3. ~3s later: automatic hot reload
4. Check /tmp/claude-harness.log for logs
5. Restart app if needed to clear old handlers from memory
```

---

## 4. Debugger Spy

### Script: `001-debugger-spy.js`

Uses `webContents.debugger` + `Fetch.enable` + `Network.enable` to capture:

- **Fetch/XHR**: URL, method, headers, postData, responseBody
- **WebSocket**: frames sent and received (full payload)
- **Network**: all requests with URL and method

### Output

- `/tmp/voice-captures.json` — array of captures with timestamp
- `/tmp/claude-harness.log` — debug logs `[SPY]`

### Capture Example

```json
{
  "ts": "2026-06-23T13:56:35.268Z",
  "type": "ws",
  "method": "Network.webSocketFrameReceived",
  "payload": "{\"type\": \"TranscriptText\", \"data\": \"Houston, we may have a problem.\"}"
}
```

### WebContents covered

- `BrowserWindow.getAllWindows()` → main webContents
- `webContents.getAllWebContents()` → ALL webContents (including WebContentsView)
- `BrowserWindow.getBrowserViews()` → child views

---

## 5. CDP Tools

### Python Scripts (PEP 723 — `uv run`)

Location: `/tmp/cdp-*.py`

| Script | Function |
|--------|--------|
| `cdp-eval.py` | Execute JS in main process via CDP |
| `cdp-harness.py` | Use harness API via CDP |
| `cdp-fetch.py` | Load scripts from file server |
| `cdp-load-scripts.py` | Orchestrate injection |

### Usage Pattern

```bash
# Connect to CDP (must be active in UI)
curl -s http://localhost:9229/json | jq '.[0].webSocketDebuggerUrl'

# Execute via harness
uv run /tmp/cdp-eval.py
```

### File Server

```bash
cd /tmp/scripts && python3 -m http.server 8765
```

Allows loading scripts from within Electron without string escaping.

---

## 6. Bridge Server (TCP)

### Concept

Bidirectional TCP connection between Python and the Electron main process for real-time commands.

### Files

- `/tmp/scripts/bridge-server-v2.py` — async Python server (port 9876)
- `/tmp/scripts/harness-bridge.js` — TCP client in Electron

### How It Works

```
Python (bridge-server) ←→ TCP :9876 ←→ Electron (harness-bridge)
                            │
                            ├─ command: {"id":1, "code":"1+1"}
                            └─ response: {"id":1, "result":"2"}
```

---

## 7. Entitlements

File: `/tmp/entitlements.plist`

```xml
<key>com.apple.security.cs.allow-jit</key><true/>
<key>com.apple.security.virtualization</key><true/>
<key>com.apple.security.device.audio-input</key><true/>
<key>com.apple.security.device.camera</key><true/>
<key>com.apple.security.device.bluetooth</key><true/>
<key>com.apple.security.device.usb</key><true/>
<key>com.apple.security.device.print</key><true/>
<key>com.apple.security.personal-information.location</key><true/>
<key>com.apple.security.personal-information.photos-library</key><true/>
```

Extracted from the original DMG at `/Users/alefita/Downloads/Claude.dmg`.

---

## 8. ASAR Versions

| Version | Patches | Status |
|--------|---------|--------|
| V0 | Original (backup) | Clean base |
| V8 | Uy=false, voice gate, entitlement, audio proxy | 3p mode, no voice UI |
| V9 | V8 + Uy=false | **Stable** — 1p mode, voice works |
| V15 | V9 + harness v1 | Initial harness |
| V16 | V15 + Sqi localhost | Redirect attempt (not effective) |
| **V17c** | V16 + harness v3 (bezetacil) | **Current** — hot reload, debugger spy |

---

## 9. Scripts Quick Reference

```bash
# Full rebuild
cd /tmp/claude-v17 && node /tmp/deploy-v17c.mjs

# Hot reload — just edit
vim ~/.bezetacil/scripts/001-debugger-spy.js

# View logs
tail -f /tmp/claude-harness.log

# View captures
cat /tmp/voice-captures.json | python3 -m json.tool | head -50

# Re-sign (after rebuild)
sudo codesign --remove-signature /Applications/Claude.app
sudo codesign -s - /Applications/Claude.app --deep --force --entitlements /tmp/entitlements.plist
```
