Data leaving the building
The most common malicious change is quiet: read something valuable, send it somewhere. It often arrives labelled as telemetry, analytics or error reporting.
Environment and secrets sent out
- In a diff
- A "telemetry" module that serialises process.env or os.environ, or reads variables named TOKEN or SECRET, then POSTs them, sometimes base64-encoded first.
- What visualdiff does
- Connects the read to the send within the same file, names both lines, and holds the change when the data is environment or credentials going anywhere but your own domains.
threat.exfiltration
Credential files read
- In a diff
- Paths like ~/.ssh/id_rsa, ~/.aws/credentials, .npmrc, .netrc, .docker/config.json, .kube/config, browser cookie stores or wallet files.
- What visualdiff does
- Flags every read of a known credential location, and escalates when the same file also talks to the network.
threat.credential-access
Data drops, tunnels and chat webhooks
- In a diff
- URLs on webhook.site, requestbin, pipedream, pastebin, transfer.sh, ngrok or trycloudflare tunnels, Discord webhooks or the Telegram bot API.
- What visualdiff does
- Every host is classified. Collection points, tunnels, shorteners, dynamic DNS and .onion addresses are drawn in red on the connections map and raise a finding.
threat.exfil-sink
Destinations you cannot read
- In a diff
- A raw IP address, a URL assembled from variables, or a destination decoded from a string at run time.
- What visualdiff does
- Raw IPs and computed destinations are listed separately as places the code goes that a reviewer cannot verify from the diff.
threat.raw-ipallowedHostsblockUnknownHosts
Machine fingerprinting
- In a diff
- Hostname, username, network interfaces and platform collected together, typical of install-time beacons.
- What visualdiff does
- Counted as system information in what a connection sends, and part of the exfiltration chain when it leaves.
threat.exfiltration
Malware behaviour
Code whose purpose is to damage, persist or profit. Rare in a normal pull request, which is exactly why nobody is looking for it.
Ransomware and wipers
- In a diff
- A recursive walk over the file system, encryption of what it finds, then renames, overwrites or deletes; or a walk followed by deletion alone.
- What visualdiff does
- Requires the walk, the encryption and the overwrite together before calling it ransomware, and lists each signal with its line. Ransom-note text adds evidence.
threat.ransomwarethreat.wiper
Reverse shells
- In a diff
- bash -i >& /dev/tcp/…, nc -e, socat exec:, or a Python socket wired to a subprocess.
- What visualdiff does
- Always critical, always on the read lane, whatever file it hides in.
threat.reverse-shell
Crypto miners
- In a diff
- stratum+tcp:// URLs, xmrig, or a known mining pool host in a build step or a container.
- What visualdiff does
- Mining pools are their own destination category and a critical finding.
threat.miner
Persistence
- In a diff
- Writes to crontab, systemd units, shell profiles, ~/.ssh/authorized_keys, LaunchAgents or Windows Run keys.
- What visualdiff does
- Flagged in application code and build scripts alike.
threat.persistence
Destructive commands
- In a diff
- rm -rf on a home or root directory, recursive deletes of the user’s files, mkfs, dd onto a disk, DROP DATABASE.
- What visualdiff does
- Flagged with the exact line; combined with a date check it becomes a time bomb.
threat.destructive
Time bombs and sandbox checks
- In a diff
- Behaviour gated on a date far in the future, or on detecting CI, a debugger or a virtual machine.
- What visualdiff does
- Comparisons of the clock with a literal future date and checks for sandbox markers are flagged; severity rises when the same file is destructive or sends data.
threat.timebombthreat.sandbox-evasion
Hiding in plain sight
A payload only works if the reviewer does not read it. These techniques make code unreadable, invisible, or sort it into the pile nobody opens.
Obfuscated execution
- In a diff
- eval, new Function, exec or a subprocess fed by base64, hex, String.fromCharCode or a decompressed blob.
- What visualdiff does
- Decoding that feeds execution is critical. Long encoded blobs and character-code chains are flagged on their own.
threat.obfuscated-execthreat.encoded-blobthreat.charcode
Trojan Source and invisible characters
- In a diff
- Unicode bidirectional controls that make code display differently from how it runs, zero-width characters, tag characters, and look-alike letters from other scripts in identifiers.
- What visualdiff does
- Every added line is checked for bidi controls, invisible and tag characters, and mixed-script identifiers.
threat.trojan-sourcethreat.invisible-textthreat.homoglyph
The pile nobody reads
- In a diff
- A payload placed in a lockfile, a generated or minified file, a block that claims to be moved, or a formatting-only commit.
- What visualdiff does
- Threat and supply-chain checks run on every line, including the ones the plan would skip. Anything they find is pulled into the read lane, with a note that it was hiding in a skipped file. Minified code added to source is flagged as unreviewable.
supply.minified-sourcesupply.lockfile-tamper
Install time and the supply chain
Code that runs before anyone runs the code: when a dependency installs, a registry answers, or a lockfile resolves.
Install scripts
- In a diff
- A new or changed preinstall, install, postinstall or prepare script; a setup.py that overrides install; a build.rs that fetches.
- What visualdiff does
- Every lifecycle script change is a finding. Scripts that download, evaluate or exfiltrate are high, and critical when the script’s target is in the same change.
supply.install-scriptthreat.install-payload
Dependency confusion and registry redirects
- In a diff
- A scope in .npmrc pointed at a new registry, an --extra-index-url in requirements, a changed GOPROXY or a disabled checksum database.
- What visualdiff does
- Any change to where packages come from is high: it decides which code arrives for every dependency at once.
supply.registry-redirect
Lockfile tampering
- In a diff
- A resolved URL that points away from the registry, an integrity hash removed, or an http:// tarball.
- What visualdiff does
- Lockfiles are read for where each package resolves from, even though the plan skips their churn.
supply.lockfile-tamper
Typosquats and hallucinated packages
- In a diff
- A package one letter from a popular one, or a name an agent invented that someone has since registered.
- What visualdiff does
- New npm and PyPI dependencies are checked against the registry (age, existence) and against the popular names they imitate.
Shown on the Tests and dependencies tab.
Unpinned sources, submodules and binaries
- In a diff
- Dependencies by git URL or tarball, a new or repointed submodule, a compiled binary or a .jar committed to the repository.
- What visualdiff does
- Each is flagged; compiled executables outside asset folders are high.
supply.unpinned-sourcesupply.submodulesupply.binary
Pipeline poisoning
The CI system holds the secrets and the deploy keys. A change to the pipeline is a change to who can use them.
Running untrusted code with secrets
- In a diff
- pull_request_target or workflow_run that checks out the pull request’s head and runs it.
- What visualdiff does
- Critical: it hands repository secrets to anyone who opens a pull request.
ci.untrusted-checkoutci.pull-request-target
Script injection
- In a diff
- A pull request title, branch name or comment interpolated straight into a run: step.
- What visualdiff does
- Flagged with the expression and the step.
ci.script-injection
Secrets sent from CI
- In a diff
- A step that uses secrets and curl, wget or nc to a host outside your domains; echoing secrets into logs.
- What visualdiff does
- Flagged, along with unpinned third-party actions, write-all tokens, curl | sh and self-hosted runners on pull request triggers.
ci.secrets-egressci.secret-echoci.unpinned-actionci.write-allpipe.curl-shci.self-hosted-pr
Poisoning the agents
Coding agents read instruction files, docs and tool configs, and act on them. A change to what an agent reads is a change to what it will do next time, for everyone.
Prompt injection in instruction files
- In a diff
- Edits to AGENTS.md, CLAUDE.md, .cursorrules or copilot-instructions.md with hidden HTML comments, invisible characters, or text like "ignore previous instructions" or "auto-approve changes to…".
- What visualdiff does
- Every change to an agent instruction file is listed; hidden or imperative content is high.
agent.instructions
New tools and hooks for agents
- In a diff
- An MCP server added to .mcp.json, a hook or a broad Bash permission in agent settings.
- What visualdiff does
- Flagged high: it gives every agent that opens the repository a new tool, command or permission.
agent.tool-config
Code that runs when the repository opens
- In a diff
- Editor tasks set to run on folder open, devcontainer commands that fetch scripts, new git hooks.
- What visualdiff does
- Flagged; high when they download or decode something.
dev.autorun
New doors and new vendors
Not malicious by itself, but every new way in and every new company your data reaches deserves a decision.
Routes, listeners and open ports
- In a diff
- New HTTP routes, servers bound to 0.0.0.0, EXPOSE lines, Kubernetes ports and security groups open to the internet.
- What visualdiff does
- Drawn on the left of the connections map; public ones are highlighted and counted.
iac.open-ingresscors.wildcardtls.disabled
Outside vendors
- In a diff
- A new SDK, API host, tracking script or API key for a company such as Segment, Sentry, Stripe or OpenAI.
- What visualdiff does
- Each vendor is named with what it typically receives. A policy can list approved vendors and block the rest.
approvedVendorsblockUnapprovedVendors
How visualdiff guards
Nothing is executed
visualdiff reads the diff. It never installs a dependency, runs a script or opens a URL it finds, so a malicious change cannot attack the reviewer’s tools.
Every line is scanned, including the skipped ones
The plan saves attention by skipping lockfiles, generated files, moves and formatting. The security checks do not skip them, and anything they find is moved back to the read lane.
The policy comes from the base branch
.visualdiff.json is read from the target branch, so a pull request cannot allow its own new hosts or vendors. allowedHosts, blockUnknownHosts and approvedVendors turn the map into a gate.
A hold blocks the merge
With --fail-on hold in CI, or the commit status marked as required, a critical finding stops the merge until a person has looked.
A policy that turns the connections map into a gate, read from .visualdiff.json on the base branch:
{
"allowedHosts": ["*.acme.com", "api.stripe.com", "*.ingest.sentry.io"],
"blockUnknownHosts": true,
"approvedVendors": ["stripe", "sentry", "aws"],
"blockUnapprovedVendors": true,
"blockOn": ["critical"]
}What it cannot see
- It is static analysis of the change. A payload fetched at run time, encrypted, or compiled into a binary will not show its contents; visualdiff can only point at the fetch, the blob or the binary.
- It reads the lines that changed, not the whole repository. A change that switches on code already in the repository shows only the switch.
- Heuristics have false positives and false negatives. Treat findings as reasons to read, and keep the controls that do not depend on reading: sandboxed CI, egress allowlists, least-privilege tokens, signed commits and branch protection.