Skip to content

fix: run the binary that was verified, not whatever PATH resolves - #21

Open
bradAGI wants to merge 2 commits into
trustabl:mainfrom
bradAGI:fix/verify-extracted-binary
Open

fix: run the binary that was verified, not whatever PATH resolves#21
bradAGI wants to merge 2 commits into
trustabl:mainfrom
bradAGI:fix/verify-extracted-binary

Conversation

@bradAGI

@bradAGI bradAGI commented Aug 24, 2026

Copy link
Copy Markdown
Contributor

Depends on #1 (the test suite) — it is the first commit here so CI can run. Review the second commit.

The gap

tar -xzf "$DEST/$ASSET" -C "$DEST"
export PATH="$DEST:$PATH"
...
trustabl "${BASE_ARGS[@]}" --format sarif > "$SARIF_FILE"

Untar, prepend to PATH, invoke the bare name. Nothing checks that the archive actually contained trustabl.

That makes the checksum verification conditional on something it does not verify. The script confirms the archive hashes correctly, then runs a binary it located by name. If the archive is missing the binary — a renamed asset, a truncated upload, a substituted tarball — the name resolves against the rest of PATH instead:

  • In a CodeBuild image with no trustabl installed: command not found, and the scan proceeds to report whatever the fallback produces.
  • In an image that carries one: it silently runs a different binary.

Either way the checksum attests to a file nobody executed.

Demonstrated with an archive that extracts to trustabl-linux instead of trustabl:

NOT OK 16 — an archive without the binary aborts
  not ok — exit code: expected '2', got '0'

Exit 0, a full readiness report, and trustabl.env published — from a binary that was never in the verified archive.

The fix

Two halves, and the second is the one that matters:

  1. Assert the extracted binary exists and is executable, before anything runs.
  2. Invoke it by absolute path, not by name, so PATH resolution cannot substitute another.

Exit 2 — no scan happened, so per docs/EVALUATION.md the output should not be trusted.

The absolute-path invocation also decouples this from where $DEST sits on PATH, which matters if that prepend is ever narrowed — as #8 proposes, for good reasons.

Verification

all 16 test(s) passed, and the normal path still works end to end against the real engine binary — real tarball, real sha256 check, real extraction:

Trustabl version: v9.9.9
checksum verified: trustabl_9.9.9_darwin_arm64.tar.gz
| Findings         | 3                                     |

Relationship to #7

#7 makes checksum verification mandatory. This closes the other half: verification is only worth having if the verified file is the one that runs. They are independent and can merge in either order.

The scanner is the whole product here — it resolves a release, verifies it,
runs the engine, scales the score, and decides whether the build fails — and
until now nothing checked any of it. A regression in the gate logic or in the
jq that reads the engine's ScanResult would ship silently.

The suite drives the real scan/trustabl-scan.sh. Each test builds an actual
gzipped release tarball containing a stub engine and a real checksums.txt over
it, then puts a stub curl on PATH that serves that directory by URL basename.
The download, sha256 verification, extraction and invocation therefore all run
unmodified; only the network and the engine binary are substituted.

The fixtures are unmodified output from a real `trustabl scan` (engine v0.1.7),
so the assertions pin the scanner against the ScanResult shape the engine
actually emits rather than a hand-written approximation of it.
Nothing checked that the extracted archive actually contained `trustabl`. The
script untarred, prepended the directory to PATH, and invoked the bare name.

That makes the checksum verification conditional on something it does not
check. If the archive is missing the binary — a renamed asset, a truncated
upload, a substituted tarball — the name resolves against the rest of PATH
instead. In a CodeBuild image with no trustabl installed that is a "command not
found" and a scan reporting whatever the fallback path produces; in an image
that carries one, it silently runs a different binary. Either way the checksum
attests to a file nobody executed.

The extracted binary is now asserted present and executable before anything
runs, and both scans invoke it by absolute path rather than by name, so PATH
resolution cannot substitute another. Exit 2: no scan happened.

This also makes the invocation independent of where $DEST sits on PATH, which
matters if the PATH prepend is ever narrowed.
@sairenchristianbuerano

Copy link
Copy Markdown
Collaborator

Thanks @bradAGI. Approving, and this is complementary to #7 rather than overlapping it.

Verifying the archive only means something if the binary that runs is the one that came out of it. Without this check, an archive missing trustabl leaves the name to resolve against the rest of PATH, and the checksum ends up attesting to a file nobody executed. That is a subtle failure and I am glad you caught it.

One note on sequencing: this and #7 touch adjacent lines in the same region, so whichever lands second needs a rebase. Not a problem with the change, just the shape of this queue.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants