Engine binary download/verification model has no independent integrity check, and doesn't match its own tagged commit

Author: jojig-daoCreated Sep 12, 2026Updated Sep 14, 2026
Labelsneeds triage

Summary

The engine binary the launcher downloads and executes on every edit (via the PostToolUse/Stop hooks) has no independent integrity verification, no re-check after the first download, a hardcoded upstream-only download URL, and — as shipped at engine-v0.1.5 — doesn't even correspond to the source commit tagged skill-v4.3.1. This is a materially different, and materially weaker, trust model than the plain-JS skill releases before the Rust engine (skill-v4.0.4 and earlier), where the sha-pinned tag covered 100% of the code that actually ran.

This was found while reviewing a re-pin from skill-v4.0.4 to skill-v4.3.1 (commit cd12f8660e2dde57b9615c8a6b8ea674101f9cfc). Static source review only — no binaries were downloaded or executed; findings below are read directly from crates/, the shell launcher, and Cargo.lock at that commit.

Findings

1. The "same-origin sha256 sidecar" is not independent verification. plugin/skills/impeccable/scripts/impeccable (the shell launcher) downloads, on cache miss, https://github.com/pbakaus/impeccable/releases/download/engine-v<version>/impeccable-<os>-<arch> and then fetches <same-url>.sha256 — from the identical release, identical repo, identical publisher — and compares. Anyone who can push a malicious binary asset to that release (compromised maintainer account, compromised release CI) can trivially upload a matching .sha256 right next to it. IMPECCABLE_DOWNLOAD_BASE can redirect the whole fetch (binary + sidecar together) to an attacker-controlled host, with no separate anchor.

By contrast, this same codebase does have a real independent-trust-root mechanism — crates/skills/src/bundle_signature.rs — Ed25519 signature verification against public keys compiled into the binary (include_str! of scripts/bundle-signing-keys.json), used to gate impeccable install/update/link's download of the skill-content universal.zip. That mechanism is not applied to the native engine binary — only to markdown/text skill content. The signing infrastructure already exists; it's just not extended to the artifact that actually executes.

2. No re-verification after the first download. The launcher's exec order is: IMPECCABLE_BIN env override (no check) → sibling bin/<os>-<arch>/ copy (no check) → ~/.impeccable/bin/impeccable unversioned home binary (handshake-probed only, not hash-checked) → the version-pinned cache $IMPECCABLE_HOME/bin/<version>/impeccable, exec'd immediately if present, with zero re-verification → only then does it attempt a fresh download+sha256-verify. The cache key is the version string (e.g. "0.1.5"), not a content digest. Once cached, every subsequent hook invocation trusts that file forever with no integrity recheck — a later compromise of the cached file on disk, or of the release asset between one machine's first download and another's, is invisible to this mechanism.

3. Download source is hardcoded to this repo, bypassing any downstream fork's own pinning. Both the shell launcher's default and crates/skills/src/engine_binary.rs's DEFAULT_DOWNLOAD_BASE hardcode https://github.com/pbakaus/impeccable/releases/download as the binary source, regardless of which fork or commit a downstream project has actually reviewed and pinned. Anyone building a "reviewed, controlled provenance" process around a forked/pinned commit of this repo will find that process never actually covers the artifact that executes — the binary always comes from this repo's own release infrastructure.

4. The shipped binary doesn't match the tagged source commit it's supposed to represent. The engine-v0.1.5 tag (what the shipped VERSION file requests) peels to one commit ("Release engine 0.1.5, CLI 4.1.0 and skill 4.3.0"), which is earlier than the commit tagged skill-v4.3.1 ("Release skill 4.3.1"). Diffing them shows real crates/ changes landed after the engine release was cut (e.g. crates/context/src/hook_markers.rs, crates/skills/src/hook_manifest.rs, plus tests) that are not in the binary every machine will actually download and run, even though skill-v4.3.1 is the tag someone auditing the source would review. Reviewing the crates/ tree at a given skill tag does not fully describe the executing artifact.

5. Hooks run unsandboxed, and the blast radius is broader than one interactive session. plugin/hooks/hooks.json wires PostToolUse (Edit|Write) and Stop to exec the downloaded engine binary directly. Tracing crates/cli/src/main.rs's dispatch, the hook/hook-before-edit subcommands route only into local detector code in the reviewed commit (no network primitive found there) — but the same compiled binary already links ureq+rustls and reads OPENAI_API_KEY from the environment elsewhere in the same binary (generate_image.rs, context_cli.rs's update check). A compromised binary would need no new capability to exfiltrate — the network stack and env-reading code already ship in the artifact every hook invocation execs. plugin/skills/impeccable/reference/hooks.md documents this hook firing across multiple AI coding tools, including one whose cloud-agent mode reads a team-committed hook config file directly from a repo's default branch — meaning this isn't necessarily confined to one developer's local secrets; a repo that commits that hook file runs the binary inside a cloud-agent context too, with no sandboxing described or found anywhere in the launcher or engine source.

Prior related issue (#479) — and why it doesn't cover this

#479 ("Verify skill bundle integrity before extraction") raised the same class of concern — unverified downloaded content extracted and later executed — against the pre-engine JS architecture's downloadFile+extractZip path for the skill bundle. That issue predates the Rust engine binary (reviewed "at v3.5.0") and reads as the likely origin of the bundle_signature.rs Ed25519 mechanism that now protects the skill-content zip.

That fix, however, only covers the skill-content bundle (markdown/text universal.zip). It was never extended to the native engine binary introduced by the engine-v* release channel — the compiled artifact hooks actually exec on every edit, which is what findings 1–5 above are about. The two download paths (skill content vs. engine binary) now have genuinely different trust models: one has an independent signature anchor, the other has a same-origin checksum with none of the properties #479 asked for (no independent signing key, no re-verification, source pinned to one hardcoded host).

Suggested remediation

  • Extend the existing Ed25519 bundle-signing mechanism (or an equivalent, e.g. minisign/cosign) to cover the engine binary release artifacts, with the public key(s) compiled into the CLI/launcher rather than fetched alongside the binary.
  • Re-verify the cached binary's signature/hash on some cadence (or at minimum, allow/encourage a content-addressed cache key instead of a bare version string) rather than trusting an already-cached file forever.
  • Let IMPECCABLE_DOWNLOAD_BASE (or an equivalent override) be paired with the signing-key check so a downstream fork's pinned binary can be verified independently of which host served it.
  • Ensure the tagged release commit (skill-vX.Y.Z) and the binary release tag (engine-vA.B.C) it depends on stay in sync, or document explicitly which commit the shipped binary actually corresponds to at each skill release.

Happy to provide the specific commit SHAs referenced above if useful, or to open follow-up issues per finding if that's preferred over one combined report.