squirrelscan
GitHub

Obfuscated inline script: spotting an injected payload

A large inline script with high entropy and eval or anti-tamper markers is rarely something a site ships on purpose. squirrel measures all three.

What an obfuscated inline script is

An obfuscated script is JavaScript deliberately rewritten so a reader cannot follow it: identifiers replaced with hex names such as _0x4a2b, strings encoded and rebuilt with String.fromCharCode or atob, and the whole body wrapped in an eval or a packer. Injected payloads use it to hide what they do from anyone who views source.

Minification is a different goal. It shortens code without hiding intent, and a minified bundle is usually readable again once it is formatted. Minifiers do not forbid eval or runtime string rebuilding, so those markers are a signal rather than proof. What is worth looking at is the combination: a large, high-entropy blob shipped inline rather than as an external, source-mapped bundle.

What squirrel checks

The rule runs on every crawled page over every inline <script>. An inline script here means one whose src attribute is absent or empty. A script is a hit only when all three conditions hold: its text is at least 4096 characters long, counted as UTF-16 code units rather than encoded bytes; its lowercased body contains at least one packer marker; and its Shannon entropy is at least 4.5 bits per character. When several scripts qualify, the longest is reported.

The packer markers are eval(, function("return this"), function('return this'), atob(, unescape(, fromcharcode, the code has been tampered and _0x.

It emits at most one check named obfuscated-script:

  • Info when this is the only integrity signal on the page. Message: Suspicious 152.4KB obfuscated inline script (single signal — review). When the page’s sign-in controls were cut by the scan work limits (details.controlsUnread, details.templatesUnread, details.hiddenCssTruncated), the message names what was left unread and details.scanTruncated is true, because a corroborating signal may have been missed, as in integrity/brand-impersonation.
  • Fail when at least two distinct integrity signals fire on the page. Message: Likely injected payload: 152.4KB obfuscated inline script (3 corroborating integrity signals).

The size in the message is that character count divided by 1024, so it tracks the text length rather than the transferred bytes. The reported value carries that size, the entropy rounded to two decimals, and the list of markers that matched. Scripts with a real src are out of scope for this signal.

How to fix it

<!-- serve heavy JavaScript as an external, readable bundle -->
<script src="/assets/app.4f2c1a.js" defer></script>

If you did not add the inline blob, find the file or plugin that emits it, remove it, then look for dropped files and a loader that re-injects it. Rotate credentials and read the access logs.

A tag manager or consent platform that ships a large inline loader using atob can match. Confirm the vendor and the exact bytes before treating it as an injection, and disable the rule for that page if it is expected.

Rule IDintegrity/obfuscated-script
CategorySite Integrity
ScopePage
Severitywarning
Weight8/10

Enable / disable

Disable this rule

[rules]
disable = ["integrity/obfuscated-script"]

Disable all Site Integrity rules

[rules]
disable = ["integrity/*"]

Enable only this rule

[rules]
enable = ["integrity/obfuscated-script"]

enable replaces the default ["*"], so this is the only rule selected. Leave disable out: a rule that matches any disable pattern is skipped even when enable names it. Only an enabled setting under a rule’s own [rule_options] table overrides both lists.

Site Integrity findings ship in every audit next to the SEO, performance and agent experience rules. See Website security scan with AI for how an agent works through a report.

Glossary: Hacked site signals.

References

Check your site

Run squirrel audit https://example.com and open the Site Integrity section of the report. Each script is listed by page with its size, entropy and matched markers. Signed-out local audits need no account and cost nothing. Signed-in audits spend credits.

Type to search…

↑↓ navigate↵ openesc close