Repository navigation
Sightread Signal Reference
Every signal Sightread can raise: what makes it fire, its usual false alarms, how to review a finding and the incidents it is built on. Each signal has its own anchor, so Sightread-Signal-Reference#remote-fetch-exec opens one; reports, SARIF rules and explain link here. Verified against rhythm-sightread 1.0.0, whose docs/signals.md this page is generated from.
rhythm sightread signals lists them in the terminal and rhythm sightread explain <id> prints one. Severity is the default before the context rules raise or lower it; signals.severity in the config changes it for one project. Confidence is confirmed for an exact pattern and likely for a heuristic; --min-confidence confirmed keeps the likely ones out of the verdict and the gate.
A signal fires when a capability is in the new version and not in the installed one, so code a plugin already had stays quiet. Five signals can never be ignored, allowed or lowered: integrity-mismatch, version-republished, approved-version-changed, archive-path-escape and known-malicious.
| ID | Severity | Confidence | Ecosystems | Title |
|---|---|---|---|---|
integrity-mismatch |
critical | confirmed | all | Archive does not match the published hash |
version-republished |
critical | confirmed | npm, composer | Same version, different bytes than the lockfile recorded |
approved-version-changed |
critical | confirmed | all | Allowed version now has a different hash |
known-malicious |
critical | confirmed | npm, composer | Listed as malicious by OSV |
wporg-closed |
critical | confirmed | wp-plugin, wp-theme | Closed on WordPress.org |
archive-path-escape |
critical | confirmed | all | Archive entry points outside the package |
remote-fetch-exec |
critical | confirmed | all | Fetches remote code and runs it |
decode-exec |
critical | confirmed | all | Decodes hidden code and runs it |
admin-user-hidden |
critical | confirmed | wp-plugin, wp-theme | Hides users from the admin user list |
lockfile-source-change |
high | confirmed | npm, composer | Lockfile entry moved off the registry |
archive-link |
high | confirmed | all | Archive holds a link or special file |
archive-collision |
high | confirmed | all | Archive holds two entries with the same name |
unanalysed |
high | confirmed | all | Code that could not be analysed |
eval-dynamic |
high | confirmed | all | Runs code built at run time |
obfuscation |
high | likely | all | Adds a large encoded or scrambled blob |
input-unserialize |
high | likely | wp-plugin, wp-theme, composer | Unserializes request or remote data |
write-outside-package |
high | likely | all | Writes files outside its own folder |
admin-user-create |
high | confirmed | wp-plugin, wp-theme | Creates an administrator account |
capability-change |
high | likely | wp-plugin, wp-theme | Grants admin capabilities to another role |
update-suppression |
high | likely | wp-plugin, wp-theme | Blocks updates of other plugins or themes |
bot-cloaking |
high | likely | wp-plugin, wp-theme, npm | Shows search engines different content |
credential-access |
high | likely | all | Reads credentials or tokens |
data-exfil |
high | likely | all | Sends site data to a new host |
code-in-asset |
high | confirmed | all | Hides code in an image, font or text file |
npm-install-script-new |
high | confirmed | npm | Adds or changes an install script |
composer-plugin-new |
high | confirmed | composer | Runs code when Composer installs it |
owner-change |
high | confirmed | all | New owner or publisher |
provenance-dropped |
high | confirmed | npm | Release lost its build provenance |
typosquat |
high | likely | npm, composer | New dependency named like a popular package |
known-vulnerable |
high | confirmed | npm, composer | Has a known vulnerability |
dependency-confusion |
critical | confirmed | npm, composer | Private name also published publicly |
outbound-new-domain |
medium | confirmed | all | Contacts a new domain |
rest-open-route |
medium | likely | wp-plugin, wp-theme | Adds a REST route anyone can call |
changelog-mismatch |
medium | likely | all | Changelog does not mention what the code adds |
dependency-new |
medium | confirmed | npm, composer | Adds a runtime dependency |
composer-autoload-files |
medium | confirmed | composer | Adds a file that runs on every request |
fresh-release |
medium | likely | all | Released very recently |
dormant-release |
medium | likely | all | First release after a long silence |
git-tag-missing |
medium | likely | npm | No matching tag in the repository |
size-jump |
medium | likely | all | Code grew far more than usual |
unicode-hidden |
medium | confirmed | all | Hidden or bidirectional characters in code |
secret-added |
medium | confirmed | all | Adds a credential to the code |
cron-new-hook |
low | confirmed | wp-plugin, wp-theme | Schedules a new background task |
license-change |
low | confirmed | all | License changed |
abandoned |
low | confirmed | wp-plugin, wp-theme, composer | Marked abandoned or not updated in years |
local-drift |
info | confirmed | wp-plugin, wp-theme, composer | Installed copy differs from the official one |
php-requirement |
info | confirmed | wp-plugin, wp-theme, composer | PHP requirement changed |
files-removed |
info | confirmed | all | Removes security-relevant files |
The scorer adjusts a finding's severity by what else is in the same update, in this order:
-
Owner change raises. With
owner-changein the same update,eval-dynamic,obfuscation,input-unserialize,write-outside-package,admin-user-create,capability-change,outbound-new-domain,data-exfilanddormant-releasego up one level (at most critical). The finding saysraised by owner-change. -
Same function raises. Two different code signals in one function raise the lower one a level, with
raised by <other id>. -
Vendored paths lower. Findings under
tests/,test/,docs/,examples/, anode_modules/inside a package orvendor/inside a WordPress plugin go down one level, as doesobfuscationin*.min.jsor*.bundle.js. The finding is markedvendored path. Critical never goes below high and the five signals above are never lowered. -
Confidence gate. With
--min-confidence confirmed, likely findings stay in the JSON withbelow_confidence: truebut don't count. - Order. Findings sort by severity, then confidence, then file and line, so output never depends on timing.
Archive does not match the published hash
- Severity: critical (never ignored, allowed or lowered)
- Confidence: confirmed
- Ecosystems: all
- Advice: Someone between you and the source changed it, or the release was edited; don't install this copy.
Fires when
The bytes Sightread downloaded do not match the hash, checksums or signature the source publishes for this version, or the integrity your lockfile recorded. Something between you and the source changed them, or the release was edited after it was published. Nothing else in the report can be trusted for this copy.
False alarms
WordPress.org rebuilds readme.txt after a release; a difference in readme.txt only is noted, not flagged. A proxy that rewrites downloads causes this too and is worth fixing.
How to review
- Run the review again later and from another network to rule out a broken mirror or proxy.
- Compare the files listed in the finding with the copy on the registry's own site.
- Report the mismatch to the maintainers or the registry if it stays.
Incidents
None recorded yet.
Same version, different bytes than the lockfile recorded
- Severity: critical (never ignored, allowed or lowered)
- Confidence: confirmed
- Ecosystems: npm, composer
- Advice: Keep the copy you have. The same version should never change its bytes.
Fires when
Your lockfile recorded one integrity hash or commit for this exact version. The registry now serves different bytes under the same version number. A published version should never change, so this points to a moved tag, a rewritten Composer reference or a registry problem.
False alarms
Very rare. A private registry that rebuilds tarballs can cause it; fix the registry instead of accepting the new bytes.
How to review
- Compare the integrity or reference in the lockfile with what the registry reports now.
- For Composer, look at the repository's tag history for a tag that moved.
- Keep the bytes you installed until the maintainers explain the change.
Incidents
None recorded yet.
Allowed version now has a different hash
- Severity: critical (never ignored, allowed or lowered)
- Confidence: confirmed
- Ecosystems: all
- Raised by: the allow list check, not a detector
- Advice: Review it again. The allow entry covers other bytes than these.
Fires when
Your allow file records a reviewed archive for this version by its sha256. The archive the source serves now has a different hash. The review you recorded does not cover these bytes.
False alarms
WordPress.org rebuilds zips without changing the files. Allow entries for WordPress.org use the manifest hash, so a rebuild does not fire.
How to review
- Check who added the allow entry and when (git log on .sightread/allowed.yaml).
- Review the new archive as if it had never been allowed.
- Replace the entry only after that review.
Incidents
None recorded yet.
Listed as malicious by OSV
- Severity: critical (never ignored, allowed or lowered)
- Confidence: confirmed
- Ecosystems: npm, composer
- Advice: Don't install it. Rotate the credentials of any machine that already did.
Fires when
OSV lists a MAL- record for this version: a security team found code in it that was published to harm the people who install it. These records come from the OpenSSF malicious packages feed and from registry takedowns.
False alarms
A record that turns out wrong is withdrawn; the OSV page shows when that happened.
How to review
- Open the OSV record linked in the finding.
- Find every machine that already installed this version, CI runners included.
- Rotate the credentials those machines hold.
- Pin the last version before it.
Incidents
- Shai-Hulud: Hundreds of republished npm packages were listed as MAL- records within days.
Closed on WordPress.org
- Severity: critical
- Confidence: confirmed
- Ecosystems: wp-plugin, wp-theme
- Advice: Hold the update and find out why it was closed.
Fires when
WordPress.org closed this plugin or theme, so it gets no more updates from there. Closures follow security reports, guideline breaks or the author's own request.
False alarms
A closure at the author's request is high instead of critical: the author stopped, nothing was found.
How to review
- Read the closure reason in the finding and on the WordPress.org page.
- Look for a security advisory about the slug.
- Plan a replacement if it stays closed.
Incidents
- Essential Plugin 2025: WordPress.org closed the portfolio's plugins once the changed code was found and pushed a cleaned release.
Archive entry points outside the package
- Severity: critical (never ignored, allowed or lowered)
- Confidence: confirmed
- Ecosystems: all
- Advice: Don't extract or install this archive.
Fires when
An entry name in the archive points outside the package folder through "..", an absolute path, a drive letter or a backslash, or is a name Windows opens as a device or stream (CON, NUL, a colon). Sightread never extracts it. An unzip tool or package manager might write it anywhere on disk.
False alarms
None known: release tools never write such names.
How to review
- List the archive's entries with a tool that only lists, never extracts.
- Compare with the archive of the previous release.
- Report it to the registry.
Incidents
None recorded yet.
Fetches remote code and runs it
- Severity: critical
- Confidence: confirmed
- Ecosystems: all
- Advice: Hold the update. Nothing local can tell you what the downloaded code will do.
Fires when
New code downloads something and runs it in the same function: the response reaches eval, include, a child process or a code file that is then loaded. What runs can change at any time without a new release. Reviewing this version tells you nothing about what it will run tomorrow.
False alarms
Fetching JSON and decoding it with json_decode does not fire. The fetch and the run must be in one function body.
How to review
- Open the finding with explain and read the hunk.
- Find the URL the code fetches and who runs that host.
- Check whether the changelog mentions it.
- Ask the maintainers before installing.
Incidents
- intercom-php 2026: Version 5.0.2 downloaded Bun during composer install and ran an obfuscated script with it.
- Gravity Forms 2025: The altered build fetched payloads from gravityapi.org and ran them.
Decodes hidden code and runs it
- Severity: critical
- Confidence: confirmed
- Ecosystems: all
- Advice: Hold the update. Decode the string offline and read it first.
Fires when
New code decodes a hidden string (base64, gzinflate, rot13, hex and the like) and runs the result. Readable code has no reason to hide from the people who read it. Built names such as 'ev'.'al' and comments between tokens are folded before matching.
False alarms
Decoding into output, a download or json_decode does not fire.
How to review
- Read the hunk with explain.
- Decode the string offline with a tool that only prints the result.
- Never run the decoded text to see what it does.
Incidents
- event-stream 2018: flatmap-stream decrypted a payload at load time and ran it.
- intercom-php 2026: The script it ran was packed and decoded before it ran.
Hides users from the admin user list
- Severity: critical
- Confidence: confirmed
- Ecosystems: wp-plugin, wp-theme
- Advice: Hold the update. Check the users table itself for accounts the list hides.
Fires when
New code filters the admin user list (pre_user_query, users_list_table_query_args or views_users) to leave users out of it. An administrator nobody sees under Users keeps access after a cleanup.
False alarms
Filters that only add columns or sort the list do not fire.
How to review
- Read the filter callback in the hunk.
- Check which users it removes from the list.
- Compare the users table in the database with what the Users screen shows.
Incidents
None recorded yet.
Lockfile entry moved off the registry
- Severity: high
- Confidence: confirmed
- Ecosystems: npm, composer
- Advice: Find out who moved this entry off the registry before merging.
Fires when
The lockfile entry for this package used to come from the registry. Now it resolves to a git URL, a local file, a tarball or another host. Packages from those places skip the registry's integrity and publication records.
False alarms
An entry that was already off the registry is listed under Not checked (vcs) and does not fire.
How to review
- Read the resolved URL in the lockfile diff.
- Check whether your team made the change on purpose.
- If nobody did, find the commit that changed it.
Incidents
None recorded yet.
Archive holds a link or special file
- Severity: high
- Confidence: confirmed
- Ecosystems: all
- Advice: Check where the link points before installing.
Fires when
The archive holds a symbolic link, hard link, device or FIFO entry. Sightread records it and never follows it. A link whose target leaves the package can make an extractor read or write elsewhere; then it is critical.
False alarms
npm packages sometimes ship links that stay inside the package; those stay high.
How to review
- Read the link target in the finding.
- Check whether the previous release had the same link.
- Prefer an install method that refuses links.
Incidents
None recorded yet.
Archive holds two entries with the same name
- Severity: high
- Confidence: confirmed
- Ecosystems: all
- Advice: Don't trust either copy until the archive is rebuilt.
Fires when
The archive holds two entries with the same name, or names that differ only in case or Unicode normalisation. Different tools keep different copies. The file you review may not be the file that gets installed.
False alarms
None known: release tools do not write duplicate names.
How to review
- Find both entries named in the finding.
- Compare their contents.
- Ask the maintainers to rebuild the archive.
Incidents
None recorded yet.
Code that could not be analysed
- Severity: high
- Confidence: confirmed
- Ecosystems: all
- Advice: Read these files by hand. Sightread could not.
Fires when
Sightread could not read one or more code files: too large, a parse problem, a decode limit or the time budget. Hiding code behind such limits is a known trick. A gap is reported instead of a clean result.
False alarms
Binary and media files over the size cap are listed, not flagged, unless they hold code.
How to review
- Read the reason in the finding.
- Open the file and see what makes it unusual.
- Raise the limit with --max-size if it is a known large bundle.
Incidents
- Shai-Hulud 2025: The second wave hid its payload in a 9.7 MB obfuscated file.
Runs code built at run time
- Severity: high
- Confidence: confirmed
- Ecosystems: all
- Advice: Hold the update until you know where the evaluated text comes from.
Fires when
New code runs text built at run time: eval, assert with a string, create_function, preg_replace with /e, new Function, vm, or a function whose name is built from strings or taken from request data. Such code does whatever its input says.
False alarms
eval in a comment or a string, assert() with a boolean and the webpack and esbuild runtime helpers (webpack_require, new Function("return this")) do not fire. Minifying a file does not hide a new eval.
How to review
- Read the hunk with explain.
- Find where the evaluated text or the function name comes from.
- If it comes from a request, a remote response or a decoded string, treat it as remote control of the site.
Incidents
- Essential Plugin 2025: version_info_clean() called @$clean(...), a function name taken from fetched data.
Adds a large encoded or scrambled blob
- Severity: high
- Confidence: likely
- Ecosystems: all
- Advice: Hold the update. Nobody can review code that can't be read.
Fires when
The update adds a large encoded or scrambled blob (base64, hex, \x chains or char-code arrays with high entropy), or a code file far more random than the rest of the package. Code nobody can read can't be reviewed.
False alarms
Data URIs in CSS and SVG, base64 fonts, .pot and .mo files, source maps and recognised minified bundles do not fire.
How to review
- Read the file name and the blob's size in the finding.
- Decode it offline with a tool that only prints.
- Check whether the project publishes the readable source.
Incidents
- event-stream 2018: flatmap-stream carried its payload as an encrypted blob.
- intercom-php 2026: The stealer it ran was obfuscated.
Unserializes request or remote data
- Severity: high
- Confidence: likely
- Ecosystems: wp-plugin, wp-theme, composer
- Advice: Hold the update. Unserializing outside data can run code through classes already loaded.
Fires when
New code passes request data or a remote response to unserialize or maybe_unserialize without allowed_classes => false. Unserializing outside data lets whoever controls it build objects from classes the site already loads. That is a well known way to run code on a PHP site.
False alarms
maybe_unserialize(get_option(...)) of the plugin's own option does not fire.
How to review
- Find the source of the data in the hunk.
- Check for allowed_classes => false.
- Ask the maintainers why json_decode would not do.
Incidents
- Essential Plugin 2025: fetch_ver_info() passed a remote response to @unserialize().
Writes files outside its own folder
- Severity: high
- Confidence: likely
- Ecosystems: all
- Advice: Hold the update. Find out what it writes there and why.
Fires when
New code writes files outside its own folder: under ABSPATH, wp-content, mu-plugins, other plugins, the home folder or a parent path. Writing wp-config.php, .htaccess, mu-plugins, .npmrc, .ssh, .git or CI files makes it critical. A package rarely needs more than its own folder, uploads and the temp folder.
False alarms
Writes under wp_upload_dir(), the package's own folder and the OS temp folder do not fire.
How to review
- Read the target in the finding.
- Check what is written and when.
- Look for the change in the changelog.
Incidents
- Essential Plugin 2025: Eight months after the release the code wrote about 6 KB into wp-config.php.
Creates an administrator account
- Severity: high
- Confidence: confirmed
- Ecosystems: wp-plugin, wp-theme
- Advice: Hold the update. Check the site's users for accounts you don't know.
Fires when
New code creates an administrator account, sets a user's role to administrator, grants super admin or writes wp_capabilities directly. A plugin almost never needs to make administrators on its own.
False alarms
Creating a user with a role other than administrator does not fire.
How to review
- Read the user name, email and role in the hunk.
- Check when the code runs: activation, a request or a cron job.
- Look at the site's users for accounts created recently.
Incidents
- Social Warfare 2024: Injected code created the administrators Options and PluginAuth.
- Gravity Forms 2025: The altered build added an administrator account.
Grants admin capabilities to another role
- Severity: high
- Confidence: likely
- Ecosystems: wp-plugin, wp-theme
- Advice: Check which role gets admin rights and whether the changelog says why.
Fires when
New code grants admin-level capabilities (manage_options, edit_plugins, install_plugins, unfiltered_html, promote_users) to a role other than administrator. A new user_has_cap or map_meta_cap filter that grants them counts too. A subscriber with manage_options can take over the site.
False alarms
An install routine that adds the plugin's own custom capability to administrator does not fire.
How to review
- Read which role gets which capability.
- Check whether the plugin documents that role.
- Look at the roles on a staging copy after the update.
Incidents
None recorded yet.
Blocks updates of other plugins or themes
- Severity: high
- Confidence: likely
- Ecosystems: wp-plugin, wp-theme
- Advice: Hold the update. It stops other packages from updating.
Fires when
New code filters update checks (site_transient_update_plugins, auto_update_plugin and the like) so other plugins, themes or core stop getting updates. Blocked updates keep a site on versions with known problems. They also keep a changed copy from being replaced.
False alarms
A plugin that filters only its own update entry (its basename) does not fire.
How to review
- Read which packages the filter removes.
- Check whether it touches only its own entry.
- Compare the Updates screen on a staging copy.
Incidents
- Gravity Forms 2025: The altered build blocked updates.
Shows search engines different content
- Severity: high
- Confidence: likely
- Ecosystems: wp-plugin, wp-theme, npm
- Advice: Hold the update. It shows search engines what you can't see.
Fires when
New code checks for search engine crawlers or logged-in visitors and prints fetched or encoded content in that branch. Owners and visitors see a normal page while search engines index something else.
False alarms
SEO plugins that only add meta tags for bots do not fire: the branch must print fetched or encoded content.
How to review
- Read the branch in the hunk.
- Fetch a page of a staging copy with a crawler user agent and compare.
- Look in your search console for pages you don't recognise.
Incidents
- Display Widgets 2017: The new owner's release showed hidden content to visitors who were not logged in.
- Essential Plugin 2025: Spam was shown to Googlebot only.
Reads credentials or tokens
- Severity: high
- Confidence: likely
- Ecosystems: all
- Advice: Hold the update. Rotate tokens on any machine that already ran it.
Fires when
New code reads credential files (.npmrc, .ssh, .aws, auth.json, wp-config.php constants) or token variables (NPM_TOKEN, GITHUB_TOKEN, AWS_*, *_SECRET). A stolen token lets someone publish or deploy as you.
False alarms
Reading process.env.NODE_ENV and settings the package documents does not fire.
How to review
- Read what is read and where it goes next.
- Check whether the package documents needing it.
- If it runs at install time, treat CI tokens as exposed.
Incidents
- Shai-Hulud: Install scripts read npm and GitHub tokens and published the owners' other packages with them.
- phpass 2022: The hijacked release sent AWS keys from the environment.
Sends site data to a new host
- Severity: high
- Confidence: likely
- Ecosystems: all
- Advice: Hold the update until you know what is sent and to whom.
Fires when
New code sends site data (users, admin_email, options, environment variables or files) over the network from the function that reads it. The host is new in this version. Telemetry to a host the old version already used is medium.
False alarms
Telemetry to a host that is already in the old version is medium.
How to review
- Read what is collected and where it goes.
- Look for it in the privacy policy or the changelog.
- Block the host on a staging copy and see what changes.
Incidents
- Social Warfare 2024: The new administrator accounts were sent to 94.156.79.8.
- phpass 2022: AWS keys from the environment were sent to a remote host.
Hides code in an image, font or text file
- Severity: high
- Confidence: confirmed
- Ecosystems: all
- Advice: Hold the update. Code doesn't belong in an image or a font.
Fires when
PHP or JS code sits in a file that should hold none (an image, a font, a text file) or after __halt_compiler(). Code hidden there escapes reviews that only read .php and .js files. It is critical when another file includes it.
False alarms
Real images whose binary data contains the bytes <? do not fire: a PHP open tag must be followed by a statement that parses.
How to review
- Open the file with a hex viewer, not an image viewer.
- Search the package for includes of its name.
- Compare it with the same file in the old version.
Incidents
None recorded yet.
Adds or changes an install script
- Severity: high
- Confidence: confirmed
- Ecosystems: npm
- Advice: Install with --ignore-scripts until someone has read the script.
Fires when
The package adds or changes a preinstall, install, postinstall or prepare script, a binding.gyp that makes npm build on install, or a binding.gyp command node-gyp runs during that build. These scripts run on every machine that installs the package, CI included, before anyone reads the code. Sightread reads the script and the files it calls.
False alarms
An unchanged node-gyp install, a swap to another native build with plain flags only, a binding.gyp edit that runs no new command and a prepare that only runs husky or build tools the package already used do not fire. A build that downloads from a URL or builds from another directory does.
How to review
- Read the script in the finding and the files it runs.
- Install with --ignore-scripts until it is reviewed.
- Check that the repository has the same script.
Incidents
- axios 2026: The new dependency plain-crypto-js dropped a remote access tool from its postinstall script.
- Shai-Hulud: Every republished package carried a new install script.
Runs code when Composer installs it
- Severity: high
- Confidence: confirmed
- Ecosystems: composer
- Advice: Keep it out of allow-plugins until the plugin code is reviewed.
Fires when
The package becomes a composer-plugin or adds event scripts (post-install-cmd, post-update-cmd, post-autoload-dump, pre-*). Composer runs that code on install and update on every machine, CI included.
False alarms
Scripts for the package's own workflow (test, lint) and scripts-descriptions do not fire.
How to review
- Read the plugin class or the scripts in composer.json.
- Check allow-plugins in your root composer.json.
- Install with --no-plugins --no-scripts until it is reviewed.
Incidents
- intercom-php 2026: Version 5.0.2 became a Composer plugin on post-install-cmd and post-update-cmd.
New owner or publisher
- Severity: high
- Confidence: confirmed
- Ecosystems: all
- Advice: Read this release closely. The people behind it changed.
Fires when
The people who publish this package changed: no maintainer of the old version remains, or the release came from someone who never published it before. Bought or hijacked packages often ship their first harmful release right after the handover. A change of hands is often normal; the trust you gave the old team simply does not carry over.
False alarms
A team that only grew does not fire. When WordPress.org takes a plugin over, the note says so.
How to review
- Read the old and new maintainers in the evidence.
- Look for an announcement of the handover.
- Review this release more closely than usual.
Incidents
- Essential Plugin 2025: A buyer of 31 plugins shipped the changed code in the first release after the sale.
- event-stream 2018: The author handed the package to a volunteer who added flatmap-stream.
- Display Widgets 2017: Bought for $15,000; the next release added hidden content.
Release lost its build provenance
- Severity: high
- Confidence: confirmed
- Ecosystems: npm
- Advice: Hold the update until the maintainers confirm they published it.
Fires when
The installed version was published with provenance or trusted publishing, which ties it to a CI run in the public repository. This release has none. It may have been published from a laptop with a stolen token.
False alarms
Packages that never had provenance do not fire.
How to review
- Compare with the provenance of earlier releases on npmjs.com.
- Check the repository for a matching tag and CI run.
- Ask the maintainers whether the release process changed.
Incidents
- axios 2026: 1.14.1 was published outside the project's trusted publishing.
New dependency named like a popular package
- Severity: high
- Confidence: likely
- Ecosystems: npm, composer
- Advice: Check the dependency's name letter by letter.
Fires when
A new dependency is named like a popular package with a letter swapped, a separator changed, a scope confused, a look-alike character or a word like "plain" or "official" added. Such names count on nobody reading them closely.
False alarms
Names that are popular themselves (preact, react-dom, lodash.merge, colors next to color) do not fire, nor do names under the popular package's own npm scope or Composer vendor, which only its owner can publish to, nor packages that share a maintainer with the popular one.
How to review
- Compare the name with the popular one in the evidence.
- Check who publishes it and when it first appeared.
- Ask why the package needs it.
Incidents
- axios 2026: plain-crypto-js, named like crypto-js, was published hours before axios added it.
Has a known vulnerability
- Severity: high
- Confidence: confirmed
- Ecosystems: npm, composer
- Advice: Pick a version that fixes the advisory if there is one.
Fires when
OSV lists an advisory for this version that it does not fix. Updating to it keeps or brings in a known problem.
False alarms
An advisory this version fixes is shown as info, titled "fixes ".
How to review
- Read the advisory.
- Check whether a later version fixes it.
- Decide whether your site uses the affected code.
Incidents
None recorded yet.
Private name also published publicly
- Severity: critical
- Confidence: confirmed
- Ecosystems: npm, composer
- Advice: Pin this name to your private registry.
Fires when
A private package name of yours also exists on the public registry with a higher version. A misconfigured install can take the public one, which anyone could have published.
False alarms
Checked only with --check-confusion.
How to review
- Check that your registry settings pin the scope or vendor to your private registry.
- Look at who published the public package.
- Claim the name publicly if you can.
Incidents
None recorded yet.
Contacts a new domain
- Severity: medium
- Confidence: confirmed
- Ecosystems: all
- Advice: Find out who runs this host before updating.
Fires when
New code names a host the old version never did. Raw IP addresses, blockchain RPC endpoints, paste sites, URL shorteners, dynamic DNS and raw GitHub content are high. Sightread lists hosts and never contacts them.
False alarms
Hosts already in the old version, the package's own homepage and repository hosts (not shared code hosts such as github.com), your domains_ok and the shipped list of well-known hosts are info. So are URLs in readmes, comments and docs folders.
How to review
- Find out who runs the host.
- Look for it in the changelog.
- Read what the code sends there and what it does with the answer.
Incidents
- Essential Plugin 2025: The code found its server through an Ethereum contract over public RPC hosts.
- Social Warfare 2024: Data went to the raw IP address 94.156.79.8.
- Gravity Forms 2025: The altered build called gravityapi.org.
Adds a REST route anyone can call
- Severity: medium
- Confidence: likely
- Ecosystems: wp-plugin, wp-theme
- Advice: Check what this route does for someone who isn't logged in.
Fires when
The update adds a REST route whose permission_callback is __return_true or missing, so anyone on the internet can call it. It is high when the same file also gains code execution, unserialize, file writes or user changes.
False alarms
Read-only routes that return public data stay medium.
How to review
- Read the route's callback.
- Check what it returns or changes.
- Call it on a staging copy without logging in.
Incidents
- Essential Plugin 2025: Version 2.6.7 added a REST route with permission_callback __return_true.
Changelog does not mention what the code adds
- Severity: medium
- Confidence: likely
- Ecosystems: all
- Advice: Hold the update. Nothing in the changelog explains this.
Fires when
The changelog entry for this version is missing or generic ("compatibility", "tested up to", "bug fixes") while the code adds network, execution, file write, user or install-time behaviour. A release that adds such things without a word about them deserves a question.
False alarms
An entry that names the capability (the domain, telemetry, auto-update, license check) does not fire.
How to review
- Read the changelog entry next to the findings it does not explain.
- Look at the repository's commits for the release.
- Ask the maintainers.
Incidents
- Essential Plugin 2025: The changed release said only "Check compatibility with WordPress version 6.8.2".
Adds a runtime dependency
- Severity: medium
- Confidence: confirmed
- Ecosystems: npm, composer
- Advice: Look at the new dependency as closely as at the package itself.
Fires when
The update adds a runtime dependency, or a lockfile change adds a package. Its code and everything it pulls in now run in your project. It is high when the dependency is younger than 7 days or has install scripts.
False alarms
devDependencies are info, as are development entries a lockfile adds.
How to review
- Check who publishes the dependency and how old it is.
- Read its install scripts.
- Ask whether the change needs it.
Incidents
- event-stream 2018: A new maintainer added flatmap-stream.
- axios 2026: 1.14.1 added plain-crypto-js, published hours earlier.
Adds a file that runs on every request
- Severity: medium
- Confidence: confirmed
- Ecosystems: composer
- Advice: Read the file. It runs on every request.
Fires when
A new autoload.files entry runs on every request that loads vendor/autoload.php, whether anything uses the package or not. Its code also goes through the code signals.
False alarms
A helpers file with only function definitions is low.
How to review
- Read the file.
- Check that it only defines functions.
- Look for anything it does at load time.
Incidents
None recorded yet.
Released very recently
- Severity: medium
- Confidence: likely
- Ecosystems: all
- Advice: Wait a few days before installing it.
Fires when
The version was published less than min_age ago (72 hours by default). Most harmful releases are found and pulled within days, so waiting costs little.
False alarms
None: it is a quarantine hint.
How to review
- Wait until the release is older than min_age.
- Run the review again.
- Install sooner only when it fixes something you need now.
Incidents
- Shai-Hulud: The waves were caught within days.
- intercom-php 2026: Version 5.0.2 was live for 1 hour 44 minutes.
First release after a long silence
- Severity: medium
- Confidence: likely
- Ecosystems: all
- Advice: Check who is behind the first release in a year.
Fires when
The previous release came out more than a year before this one. A package that wakes up after a long silence often has a new owner or a hijacked account behind it.
False alarms
None: stable packages release rarely too. The signal only asks for a look.
How to review
- Check this update for owner and publisher signals.
- Look for an announcement.
- Read the whole diff.
Incidents
- phpass 2022: An abandoned package got a new release from a hijacked account.
No matching tag in the repository
- Severity: medium
- Confidence: likely
- Ecosystems: npm
- Advice: Find the source this version was built from.
Fires when
The GitHub repository has no tag for this version (v, and @ were tried). A release with no matching source was likely published by hand, outside the project's usual process. It is checked only when the update also has owner-change, npm-install-script-new or provenance-dropped.
False alarms
Monorepo tag forms are tried before this fires.
How to review
- Look at the repository's tags and releases.
- Compare the package contents with the nearest tag.
- Ask the maintainers.
Incidents
- axios 2026: 1.14.1 had no matching git tag.
Code grew far more than usual
- Severity: medium
- Confidence: likely
- Ecosystems: all
- Advice: Find out what the new bulk is.
Fires when
The package's code grew far more than its history explains, or a minified or binary file appeared in a package that had none. Large payloads hide in large files.
False alarms
New languages/, vendor/ or dist/ folders lower it to info.
How to review
- Look at the largest added files.
- Check whether they are vendored libraries or build output.
- Compare with the repository.
Incidents
- Display Widgets 2017: A release added a 38 MB GeoIP download.
- Shai-Hulud 2025: The second wave shipped a 9.7 MB obfuscated file.
Hidden or bidirectional characters in code
- Severity: medium
- Confidence: confirmed
- Ecosystems: all
- Advice: Read these lines with hidden characters shown.
Fires when
New code holds bidirectional control or zero-width characters, or identifiers that mix scripts. They make code read differently from how it runs (Trojan Source).
False alarms
Translation files and i18n strings are info.
How to review
- Open the file in an editor that shows hidden characters.
- Read the lines in the finding.
- Ask for them to be removed from code.
Incidents
None recorded yet.
Adds a credential to the code
- Severity: medium
- Confidence: confirmed
- Ecosystems: all
- Advice: Tell the maintainers. A published key must be rotated.
Fires when
An added line holds something shaped like a credential: an API key, a token or a private key. Shipping it exposes it to everyone who installs the package. Sightread shows it masked.
False alarms
Keys in tests and fixtures folders and documented examples (EXAMPLE, xxxx) are info.
How to review
- Check whether the key looks real.
- Tell the maintainers so they rotate it.
- Never use it yourself.
Incidents
None recorded yet.
Schedules a new background task
- Severity: low
- Confidence: confirmed
- Ecosystems: wp-plugin, wp-theme
- Advice: Check what the new background task does.
Fires when
The update schedules a new background task (wp_schedule_event, Action Scheduler or a new cron interval). Code in a scheduled task runs without anyone visiting a page. It is medium when the hook's file also gains network or code execution.
False alarms
A documented cleanup task stays low.
How to review
- Read what the scheduled callback does.
- Check how often it runs.
- Look for it in the changelog.
Incidents
None recorded yet.
License changed
- Severity: low
- Confidence: confirmed
- Ecosystems: all
- Advice: Check the new license before you ship it.
Fires when
The declared license changed. That can change what you may do with the code. A license change sometimes comes with a sale or a fork.
False alarms
None.
How to review
- Read the old and the new license.
- Check with whoever handles licensing for your projects.
Incidents
None recorded yet.
Marked abandoned or not updated in years
- Severity: low
- Confidence: confirmed
- Ecosystems: wp-plugin, wp-theme, composer
- Advice: Plan a replacement.
Fires when
Packagist marks the package abandoned, or WordPress.org shows no update in two years. Abandoned packages get no security fixes. Their names and accounts also attract takeovers.
False alarms
None.
How to review
- Look for the suggested replacement.
- Plan the move.
Incidents
- phpass 2022: An abandoned package was taken over through its maintainer's expired email domain.
Installed copy differs from the official one
- Severity: info
- Confidence: confirmed
- Ecosystems: wp-plugin, wp-theme, composer
- Advice: Updating will overwrite these files. Keep a copy of edits you want.
Fires when
The installed copy differs from the official copy of the installed version. Someone may have edited files on the server, or something else changed them. Updating will overwrite them.
False alarms
Changes to readme.txt, .po, .mo and .pot files and JSON translations under languages/ are not counted. PHP translation files (.l10n.php) are: PHP runs them.
How to review
- Compare the files in the finding with the official copy.
- Decide whether the changes are edits you made.
- Keep a copy of edits you want before updating.
Incidents
None recorded yet.
PHP requirement changed
- Severity: info
- Confidence: confirmed
- Ecosystems: wp-plugin, wp-theme, composer
- Advice: Check your server's PHP version first.
Fires when
The minimum PHP version changed. A site on an older PHP may stop working after the update.
False alarms
None.
How to review
- Check your server's PHP version.
- Test the update on a staging copy.
Incidents
None recorded yet.
Removes security-relevant files
- Severity: info
- Confidence: confirmed
- Ecosystems: all
- Advice: Check that the removed checks moved somewhere else.
Fires when
The update deletes files that held security checks: nonces, sanitisers, escaping, capability checks, .htaccess deny rules or index.php silence guards. Removing them can open paths that were closed.
False alarms
A refactor that moves the checks still lists the removed file; the info level reflects that.
How to review
- Check whether the checks moved to another file.
- Read the changelog.
- Test the affected screens.
Incidents
None recorded yet.
← Your first review · Home · Allow and baseline →
rhythm · Useful ideas. Working systems. · Check only sites you own or have permission to test. Keep staging sealed (responsible use) · This wiki is published from wiki/ in the rhythm repository; suggest changes there with a pull request.
Get started
rhythm in depth
Tuner
- Overview
- Your first scan
- Reading results
- Choosing checks
- Requests and politeness
- Output and reports
- Batch scans
- Local scans
- Integrity and malware
- Changes and notifications
Tuner in depth
- Known vulnerabilities
- PHP advisories
- CI and code scanning
- GitHub Action
- Config file
- Watch and serve
- Writing checks
- Check packs
- Finding reference
- CLI reference
- Coming from Wordfence CLI or WPScan
- FAQ and troubleshooting
Soundcheck
- Overview
- Your first check
- Reading results
- Review and accept
- Discovery
- Page health
- Visual changes
- Interactions
- Forms and flows
- Cache
- Speed
- Accessibility and SEO
- EAA mode
- Keyboard walkthrough
- Accessibility statement
Soundcheck in depth
- CI
- CI baselines and PR comments
- Configuration
- Error rules and exec
- WordPress updates
- Fleet and client reports
- History
- Import from BackstopJS and Lost Pixel
- Firefox and WebKit
- CLI reference
- FAQ and troubleshooting
- Benchmark
Echo
- Overview
- Your first run
- Connectors
- Sealing staging
- Echo Inbox
- Messages and checks
- Checks and rules
- Attachments
- Actions and compare
- With Soundcheck
- Replay
- Deliverability
Echo cookie consent
Echo in depth
- Docker and DDEV
- Agency hub and reports
- Schedule
- MCP server
- Conformance kit
- CI
- Configuration
- API
- CLI reference
- FAQ and troubleshooting
- Benchmark
Roadie
- Overview
- Getting started
- Copy and prepare
- Sources and targets
- Profiles
- Pseudonymisation
- Live keys
- Kill switch
- Verify and reports
Roadie in depth
- Laravel
- Generic SQL and Postgres
- Multisite
- CI and the GitHub Action
- Configuration
- Security and limits
- CLI reference
- Troubleshooting
- FAQ and honest limits
- Benchmark
Sightread
- Overview
- Your first review
- Signal reference
- Allow and baseline
- Sources and integrity
- With Soundcheck and Roadie
- Watch
Sightread in depth
- CI and the GitHub Action
- Configuration
- Output and reports
- Security model
- CLI reference
- FAQ and honest limits
- Benchmark
Project