You want one script that does everything. No digging around in settings. No manually editing JSON. No clicking Developer, Edit Config. Just run it once and Claude Desktop can execute bash commands through an MCP server.
This guide gives you exactly that.
1. Why You Would Want This
Out of the box, Claude Desktop is a chat window. It can write code, explain things, and draft documents, but it cannot actually do anything on your machine. It cannot run a command. It cannot check a log file. It cannot restart a service. You are the middleman, copying and pasting between Claude and your terminal.
MCP (Model Context Protocol) changes that. It lets Claude Desktop call local tools directly. Once you wire up a bash MCP server, Claude stops being a suggestion engine and becomes something closer to a capable assistant that can act on your behalf.
Here are real situations where this matters.
1.1 Debugging Without the Copy Paste Loop
You are troubleshooting a failing deployment. Normally the conversation goes like this: you describe the error, Claude suggests a command, you copy it into terminal, you copy the output back into Claude, Claude suggests the next command, and you repeat this loop fifteen times.
With bash MCP enabled, you say:
Check the last 50 lines of /var/log/app/error.log and tell me what is going wrong
Claude runs the command, reads the output, and gives you a diagnosis. If it needs more context it runs the next command itself. A fifteen step copy paste loop becomes one prompt.
1.2 System Health Checks on Demand
You want to know if your machine is in good shape. Instead of remembering the right incantations for disk usage, memory pressure, and process counts, you ask Claude:
Give me a quick health check on this machine. Disk, memory, CPU, and any processes using more than 1GB of RAM
Claude runs df -h, free -m, uptime, and ps aux --sort=-%mem in sequence, then summarises everything into a single readable report. No tab switching. No forgetting flags.
1.3 File Operations at Scale
You have 200 log files from last month and you need to find which ones contain a specific error code, then extract the timestamps of each occurrence into a CSV. Describing this to Claude without bash access means Claude writes you a script, you save it, chmod it, run it, fix the one thing that did not work, and run it again.
With bash MCP, you say:
Search all .log files in /var/log/myapp/ from February for error code E4012, extract the timestamps, and save them to ~/Desktop/e4012-timestamps.csv
Claude writes the pipeline, executes it, checks the output, and tells you it is done. If something fails it adjusts and retries.
1.4 Git Operations and Code Exploration
You are picking up an unfamiliar codebase. Instead of manually navigating directories, you ask Claude:
Show me the directory structure of this repo, find all Python files that import redis, and tell me how many lines of code are in each one
Claude runs find, grep, and wc itself, then gives you an annotated summary. You can follow up with questions like “show me the largest one” and Claude will cat the file and walk you through it.
1.5 Environment Setup and Configuration
You are setting up a new development environment and need to install dependencies, configure services, and verify everything works. Instead of following a README step by step, you point Claude at it:
Read the SETUP.md in this repo and execute the setup steps for a macOS development environment. Stop and ask me before doing anything destructive.
Claude reads the file, runs each installation command, checks for errors, and reports back. You stay in control of anything risky, but you are not manually typing brew install forty times.
2. What the Script Does
The installation script below handles the full setup in one shot:
Creates a local MCP launcher script at ~/mcp/run-bash-mcp.sh that runs a bash MCP server via npx bash-mcp
Locates your Claude Desktop config file automatically (macOS and Windows paths)
Creates a timestamped backup of the existing config
Safely merges the required mcpServers entry using jq without overwriting your other MCP servers
Sets correct file permissions
Validates the JSON and restores the backup if anything goes wrong
After a restart, Claude Desktop will have a tool called myLocalBashServer available in every conversation.
3. One Command Installation
I dislike wasting time following step by step guides. So just copy this entire block into your Terminal and run it. Done!
cat << 'EOF' > ~/claude-enable-bash-mcp.sh
#!/usr/bin/env bash
set -euo pipefail
SERVER_NAME="myLocalBashServer"
MCP_PACKAGE="bash-mcp"
die() { echo "ERROR: $*" >&2; exit 1; }
have() { command -v "$1" >/dev/null 2>&1; }
timestamp() { date +"%Y%m%d-%H%M%S"; }
echo "Creating MCP launcher..."
mkdir -p "$HOME/mcp"
LAUNCHER="$HOME/mcp/run-bash-mcp.sh"
cat > "$LAUNCHER" <<LAUNCH_EOF
#!/usr/bin/env bash
set -euo pipefail
export PATH="/opt/homebrew/bin:/usr/local/bin:/usr/bin:/bin:/usr/sbin:/sbin:\$PATH"
if ! command -v node >/dev/null 2>&1; then
echo "node is not installed or not on PATH" >&2
exit 1
fi
exec npx ${MCP_PACKAGE}
LAUNCH_EOF
chmod +x "$LAUNCHER"
echo "Locating Claude Desktop config..."
OS="$(uname -s || true)"
CONFIG=""
if [[ "$OS" == "Darwin" ]]; then
C1="$HOME/Library/Application Support/Claude/claude_desktop_config.json"
C2="$HOME/Library/Application Support/Anthropic/Claude/claude_desktop_config.json"
C3="$HOME/Library/Application Support/claude/claude_desktop_config.json"
if [[ -f "$C1" || -d "$(dirname "$C1")" ]]; then CONFIG="$C1"; fi
if [[ -z "$CONFIG" && ( -f "$C2" || -d "$(dirname "$C2")" ) ]]; then CONFIG="$C2"; fi
if [[ -z "$CONFIG" && ( -f "$C3" || -d "$(dirname "$C3")" ) ]]; then CONFIG="$C3"; fi
fi
if [[ -z "$CONFIG" && -n "${APPDATA:-}" ]]; then
W1="${APPDATA}/Claude/claude_desktop_config.json"
if [[ -f "$W1" || -d "$(dirname "$W1")" ]]; then CONFIG="$W1"; fi
fi
[[ -n "$CONFIG" ]] || die "Could not determine Claude Desktop config path. Open Claude Desktop → Settings → Developer → Edit Config once, then rerun this script."
mkdir -p "$(dirname "$CONFIG")"
if ! have jq; then
if [[ "$OS" == "Darwin" ]] && have brew; then
echo "Installing jq..."
brew install jq
else
die "jq is required. Install it and rerun."
fi
fi
if [[ ! -f "$CONFIG" ]]; then
echo '{}' > "$CONFIG"
fi
BACKUP="${CONFIG}.bak.$(timestamp)"
cp -f "$CONFIG" "$BACKUP"
echo "Updating Claude config..."
if ! jq . "$CONFIG" >/dev/null 2>&1; then
cp -f "$BACKUP" "$CONFIG"
die "Config was invalid JSON. Restored backup."
fi
TMP="$(mktemp)"
jq --arg name "$SERVER_NAME" --arg cmd "$LAUNCHER" '
.mcpServers = (.mcpServers // {}) |
.mcpServers[$name] = (
(.mcpServers[$name] // {}) |
.command = $cmd
)
' "$CONFIG" > "$TMP"
mv "$TMP" "$CONFIG"
echo ""
echo "DONE."
echo ""
echo "Launcher created at:"
echo " $LAUNCHER"
echo ""
echo "Claude config updated at:"
echo " $CONFIG"
echo ""
echo "Backup saved at:"
echo " $BACKUP"
echo ""
echo "IMPORTANT: Completely quit Claude Desktop and relaunch it."
echo "Claude only loads MCP servers on startup."
echo ""
echo "Then try:"
echo " Use the MCP tool ${SERVER_NAME} to run: pwd"
echo ""
EOF
chmod +x ~/claude-enable-bash-mcp.sh
~/claude-enable-bash-mcp.sh
4. What Happens Under the Hood
Claude Desktop runs local tools using MCP. The config file contains a key called mcpServers. Each entry defines a command Claude launches when it starts.
The script creates ~/mcp/run-bash-mcp.sh which uses npx bash-mcp to expose shell execution as a tool. The launcher explicitly sets PATH to include common binary locations like /opt/homebrew/bin because GUI launched apps on macOS do not inherit your shell profile. Without this, Node would not be found even if it is installed.
The config update uses jq to merge the new server entry into your existing config rather than replacing the whole file. If you already have other MCP servers configured they will not be touched. If the existing config is invalid JSON, the script restores the backup and exits rather than making things worse.
5. Test It
After restarting Claude Desktop, open a new chat and type:
Use your MCP myLocalBashServer to run: ls -la
If everything worked, Claude will call the MCP tool and return your directory listing. From there you can ask it to do anything your shell can do.
Some good first tests:
Use your MCP to show me disk usage on this machine
Use your MCP to determine what version of Python and Node do I have installed?
Use your MCP to find all files larger than 100MB in my home directory
6. Security Warning
You are giving Claude the ability to execute shell commands with your user permissions. That means file access, deletion, modification, everything your account can do.
Only enable this on a machine you control. Consider creating a dedicated limited permission user if you want stronger isolation. Claude will ask for confirmation before running destructive commands in most cases, but the capability is there.
That is it. One script. Full setup. No clicking through menus.
On a quiet Friday evening in late March 2024, a Microsoft engineer named Andres Freund was running some routine benchmarks on his Debian development box when he noticed something strange. SSH logins were taking about 500 milliseconds longer than they should have. Failed login attempts from automated bots were chewing through an unusual amount of CPU. Most engineers would have shrugged it off. Freund did not. He pulled on the thread, and what he found on the other end was a meticulously planned, state sponsored backdoor that had been three years in the making, hidden inside a tiny compression library that almost nobody had ever heard of, but that sat underneath virtually everything on the internet.
If he had not noticed that half second delay, you might be reading about the worst cybersecurity breach in human history instead of this article.
This is the story of XZ Utils, CVE-2024-3094, and the terrifying fragility hiding in plain sight beneath the digital world.
1. Everything You Do Online Runs on Linux. Everything.
Before we get to the attack, you need to understand something that most people never think about. Almost the entire internet runs on Linux. Not Windows. Not macOS. Linux.
Over 96% of the top one million web servers on Earth run Linux. 92% of all virtual machines across AWS, Google Cloud, and Microsoft Azure run Linux. 100% of the world’s 500 most powerful supercomputers run Linux, and that has been the case since 2017. Android, which powers 85% of the world’s smartphones, is built on the Linux kernel. Every time you send a WhatsApp message, stream Netflix, make a bank transfer, check your email, order food, hail a ride, or scroll through social media, your request is almost certainly being processed by a Linux machine sitting in a data centre somewhere.
Linux is not a product. It is not a company. It started in 1991 when a Finnish university student named Linus Torvalds decided to write his own operating system kernel because he could not afford a UNIX license. The entire philosophy traces back even further, to the 1980s, when Richard Stallman got so frustrated that he could not modify proprietary printer software at MIT to fix a paper jam notification that he launched the Free Software movement and the GNU project. Torvalds wrote the kernel. The GNU project supplied the tools. Together they created a free, open operating system that anyone could inspect, modify, and redistribute.
That openness is why Linux won. It is also why what happened with XZ was possible.
2. The Most Important Software You Have Never Heard Of
XZ Utils is a compression library. It squeezes data to make files smaller. It has no website worth visiting, no marketing team, no venture capital, no logo designed by an agency. It does one thing, quietly and reliably, inside Linux systems across the planet.
You have almost certainly never typed “xz” into anything. But xz has been working for you every single day. It compresses software packages before they are downloaded to your devices. It compresses kernel images. It compresses the backups that keep your data safe. It sits in the dependency chains of tools that handle everything from web traffic to secure shell (SSH) connections, the protocol that system administrators use to remotely manage servers. If SSH is the front door to every Linux server on the internet, xz was sitting in the lock mechanism.
For years, XZ Utils was maintained by essentially one person: a Finnish developer named Lasse Collin. He worked on it in his spare time. There was no salary, no team, no corporate sponsor, no security audit budget. Just one person and an issue queue. This arrangement is completely normal in open source. It is also completely terrifying.
3. The Long Con: A Three Year Espionage Operation
In October 2021, a new GitHub account appeared under the name “Jia Tan.” The account began submitting patches to XZ Utils. Small things. Helpful things. An editor configuration file here, a minor code improvement there. The contributions were competent, consistent, and completely legitimate. Over the next year, Jia Tan built a genuine track record of useful work.
Then, starting in April 2022, something else began happening. A handful of previously unknown accounts began appearing on the XZ Utils mailing list, publicly pressuring Lasse Collin. An account calling itself “Jigar Kumar” complained that patches were languishing and declared that progress would not happen until there was a new maintainer. Another account, “Dennis Ens,” piled on. The messages were not overtly hostile but carried a steady drumbeat of criticism and implied urgency. When Collin responded by mentioning his own long term mental health struggles and limited bandwidth, one of the accounts replied with something that reads, in hindsight, like a calculated psychological strike: expressing sympathy about the mental health issues while suggesting that Collin should recognise his own limits and pass maintainership to someone else.
These were almost certainly sock puppet accounts. Security journalist Brian Krebs later noted that none of the email addresses associated with these accounts appeared anywhere else on the internet, including in massive data breach dumps. That kind of total digital absence across multiple identities is extraordinarily rare and strongly suggests a coordinated operation that was planned from the beginning.
Waiting in the wings with a spotless contribution history was Jia Tan.
Collin eventually relented. By 2023, Jia Tan had been elevated to co-maintainer with direct commit access and release signing privileges. In an email to the mailing list, Collin even noted that Jia Tan had been helping a lot off-list and was “practically a co-maintainer already.”
The trap had been set with extraordinary patience.
4. The Backdoor: Hiding a Master Key in Plain Sight
In February and March 2024, Jia Tan made the move. Two carefully crafted binary test files were committed to the XZ Utils repository. Binary files are not human readable, and storing them in a tests directory is completely normal for a compression library. Nobody would look twice at them. But those files were not test data. They were encrypted payloads.
Jia Tan then modified the build process, specifically a script called build-to-host.m4, so that during compilation the malicious payloads would be silently extracted and injected into the liblzma library. The modified build script was only present in the distributed tar archive, not in the Git source code that most reviewers would examine. This was deliberate misdirection, exploiting the gap between what developers see when they browse a repository and what actually gets compiled on a target system.
The injected code targeted OpenSSH’s authentication mechanism. Through a chain of library dependencies involving systemd and glibc, the backdoor hijacked a cryptographic function called RSA_public_decrypt, replacing it with malicious code. The effect was devastating in its elegance: anyone possessing a specific Ed448 private key could bypass SSH authentication entirely and execute arbitrary code on any affected machine.
In other words, the attacker would have had a master key to every compromised Linux server on Earth.
The vulnerability was assigned CVE-2024-3094 with a CVSS score of 10.0, the maximum possible severity rating. Computer scientist Alex Stamos called it what it was: potentially the most widespread and effective backdoor ever planted in any software product. Akamai’s security researchers noted it would have dwarfed the SolarWinds compromise. The attackers were within weeks of gaining immediate, silent access to hundreds of millions of machines running Fedora, Debian, Ubuntu, and other major distributions.
5. Saved by Half a Second
On 28 March 2024, Andres Freund, a Microsoft principal engineer who also happens to be a PostgreSQL developer and committer, was doing performance testing on a Debian Sid (unstable) installation. He noticed that SSH logins were consuming far more CPU than they should, and that even failing logins from automated bots were taking half a second longer than expected. Half a second – that is the margin by which the internet was saved from what would have been the most catastrophic supply chain attack in computing history.
Freund did not dismiss the anomaly. He investigated. He traced the CPU spike and the latency increase to the updated xz library. He dug into the build artefacts. He found the obfuscated injection code. And on 29 March 2024, he published his findings to the oss-security mailing list.
The response was immediate and global. Red Hat issued an urgent security alert. CISA published an advisory. GitHub suspended Jia Tan’s account and disabled the XZ Utils repository. Every major Linux distribution began emergency rollbacks. Canonical delayed the Ubuntu 24.04 LTS beta release by a full week and performed a complete binary rebuild of every package in the distribution as a precaution.
The tower shook, but it did not fall. And it did not fall because one engineer thought half a second of unexplained latency was worth investigating on a Friday evening.
6. The Uncomfortable Architecture of the Internet
There is a famous XKCD comic, number 2347, that shows the entire modern digital infrastructure as a towering stack of blocks, with one tiny block near the bottom labelled “a project some random person in Nebraska has been thanklessly maintaining since 2003.” It was a joke. Then XZ happened and it stopped being funny.
Here is what the actual dependency stack looks like in simplified form:
Each layer assumes the one below it is solid. The higher you build, the less anyone thinks about the foundations. Trillion dollar companies, national defence systems, hospital networks, stock exchanges, telecommunications grids, and critical infrastructure all sit on top of libraries maintained by volunteers who do the work because they care, not because anyone is paying them.
The XZ incident made this fragility impossible to ignore. A compression utility that most people have never heard of turned out to be sitting in the authentication pathway for remote access to Linux systems deployed globally. A single exhausted maintainer was socially engineered into handing the keys to an adversary. And the whole thing nearly went undetected.
7. The Ghost in the Machine
We still do not know who Jia Tan actually is. Analysis of commit timestamps suggests the attacker worked office hours in a UTC+2 or UTC+3 timezone. They worked through Lunar New Year but took off Eastern European holidays including Christmas and New Year. The name “Jia Tan” suggests East Asian origin, possibly Chinese or Hokkien, but the work pattern does not align with that geography. The operational security was exceptional. Every associated email address was created specifically for this campaign and has never appeared in any data breach. Every IP address was routed through proxies.
The consensus among security researchers, including teams at Kaspersky, SentinelOne, Akamai, and CrowdStrike, is that this was almost certainly a state sponsored operation. The patience (three years), the sophistication (the build system injection, the encrypted payloads hidden in test binaries, the deliberate gap between the Git source and the release tarball), and the multi-identity social engineering campaign all point to a resourced intelligence operation, not a lone actor.
SentinelOne’s analysis found evidence that further backdoors were being prepared. Jia Tan had also submitted a commit that quietly disabled Landlock, a Linux kernel sandboxing feature that restricts process privileges. That change was committed under Lasse Collin’s name, suggesting the commit metadata may have been forged. The XZ backdoor, in other words, was likely just the first move in a longer campaign.
8. The Billion Dollar Assumption
Here is the maths that should keep every CIO awake at night. Linux powers an estimated 90% of cloud infrastructure. The global cloud market generates hundreds of billions of dollars in annual revenue. Financial services, healthcare, telecommunications, logistics, defence, and government services all depend on it. SAP reports that 78.5% of its enterprise clients deploy on Linux. The Linux kernel itself contains over 34 million lines of code contributed by more than 11,000 developers across 1,780 organisations.
And yet, deep in the foundations of this ecosystem, critical libraries are maintained by individuals working in their spare time, with no security budget, no formal audit process, no staffing, and no funding proportional to the economic value being extracted from their work.
The companies building on top of this stack generate trillions in aggregate revenue. The people maintaining the foundations often receive nothing. The gap between the value extracted and the investment returned is not a rounding error. It is a structural vulnerability, and the XZ incident proved that adversaries know exactly how to exploit it.
9. Why This Will Happen Again
The uncomfortable truth is that the open source model that made the modern internet possible also created a systemic single point of failure that cannot be patched with a software update.
Social engineering attacks are getting more sophisticated. Large language models can now generate convincing commit histories, craft personalised pressure campaigns adapted to a maintainer’s psychological profile, and manage multiple fake identities simultaneously at a scale that would have been impossible even two years ago. What took the XZ attackers three years of patient reputation building could potentially be compressed into months using AI driven automation.
Meanwhile, the number of single maintainer critical projects has not decreased. The funding landscape has improved marginally through initiatives like the Open Source Security Foundation and GitHub Sponsors, but the investment remains a fraction of what the problem demands. The fundamental dynamic, companies worth billions depending on code maintained by individuals worth nothing to those companies, has not changed.
The XZ backdoor was caught because one curious engineer refused to ignore half a second of unexplained latency. That is not a security strategy. That is luck.
10. What Needs to Change
The Jenga tower still stands, but the XZ incident demonstrated exactly how fragile it is. The blocks at the bottom, the invisible libraries, the thankless utilities, the compression tools nobody has heard of, are the ones holding everything up. And they are precisely the ones receiving the least attention.
The solution is not to abandon open source. The solution is to treat it like the critical infrastructure it actually is. That means sustained corporate investment in the projects companies depend on, not charitable donations but genuine funded maintenance and security audit commitments. It means governance models that can detect and resist social engineering campaigns targeting burnt out solo maintainers. It means recognising that the person maintaining a compression library in their spare time is not a hobbyist. They are, whether they intended it or not, a load bearing wall in the architecture of the global economy.
Richard Stallman started this whole thing because he could not fix a printer. Half a century later, the philosophy of openness he championed underpins nearly every digital interaction on Earth. That is an extraordinary achievement. But the scale has outgrown the model, and the adversaries have noticed.
The next Andres Freund might not be running benchmarks on a Friday evening. The next half second might not get noticed.
11. References
Title / Description
Type
Link
he Internet Was Weeks Away From Disaster and No One Knew
Enterprise operating systems for servers, are not chosen because they are liked. They are chosen because they survive stress. At scale, an operating system stops being a piece of software and becomes an amplifier of either discipline or entropy. Every abstraction, compatibility promise, and hidden convenience eventually expresses itself under load, during failure, or in a security review that nobody budgeted for.
This is not a desktop comparison. This is about the ugly work at the backend of enterprise applications and systems – where uptime is contractual, reputational, security incidents are existential, and operational drag quietly compounds until the organisation slows without understanding why.
1. Philosophy: Who the Operating System Is Actually Built For
Windows was designed around people. Linux was designed around workloads.
That single distinction explains almost everything that follows. Windows prioritises interaction, compatibility, and continuity across decades of application assumptions. Linux prioritises explicit control, even when that control is sharp edged and unforgiving.
In an enterprise environment, friendliness is rarely free. Every convenience hides a decision that an operator did not explicitly make. Linux assumes competence and demands intent. Windows assumes ambiguity and tries to smooth it over. At scale, smoothing becomes interference.
2. Kernel Architecture: Determinism, Path Length, and Control
Linux uses a monolithic kernel with loadable modules, not because it is ideologically pure, but because it is fast, inspectable, and predictable. Critical subsystems such as scheduling, memory management, networking, and block IO live in kernel space and communicate with minimal indirection. When a packet arrives or a syscall executes, the path it takes through the system is short and largely knowable.
This matters because enterprise failures rarely come from obvious bottlenecks. They come from variance. When latency spikes, when throughput collapses, when jitter appears under sustained load, operators need to reason about cause and effect. Linux makes this possible because the kernel exposes its internals aggressively. Schedulers are tunable. Queues are visible. Locks are measurable. The system does very little “on your behalf” without telling you.
Windows uses a hybrid kernel architecture that blends monolithic and microkernel ideas. This enables flexibility, portability, and decades of backward compatibility. It also introduces more abstraction layers between hardware, kernel services, and user space. Under moderate load this works well. Under sustained load, it introduces variance that is hard to model and harder to eliminate.
The result is not lower average performance, but wider tail latency. In enterprise systems, tail latency is what breaks SLAs, overloads downstream systems, and triggers cascading failures. Linux kernels are routinely tuned for single purpose workloads precisely to collapse that variance. Windows kernels are generalised by design.
3. Memory Management: Explicit Scarcity Versus Deferred Reality
Linux treats memory as a scarce, contested resource that must be actively governed. Operators decide whether overcommit is allowed, how aggressively the page cache behaves, which workloads are protected, and which ones are expendable. NUMA placement, HugePages, and cgroup limits exist because memory pressure is expected, not exceptional.
When Linux runs out of memory, it makes a decision. That decision may be brutal, but it is explicit.
Windows abstracts memory pressure for as long as possible. Paging, trimming, and background heuristics attempt to preserve system responsiveness without surfacing the underlying scarcity. When pressure becomes unavoidable, intervention is often global rather than targeted. In dense enterprise environments this leads to cascading degradation rather than isolated failure.
Linux enables intentional oversubscription as an engineering strategy. Windows often experiences accidental oversubscription as an operational surprise.
4. Restart Time and the Physics of Recovery
Linux assumes restarts are normal. As a result, they are fast. Kernel updates, configuration changes, and service restarts are treated as routine events. Reboots measured in seconds are common. Live patching reduces the need for them even further.
Windows treats restarts as significant milestones. Updates are bundled, sequenced, narrated, and frequently require multiple reboots. Maintenance windows expand not because the change is risky, but because the platform is slow to settle.
Mean time to recovery is a hard physical constraint. When a system takes ten minutes to come back instead of ten seconds, failure domains grow even if the original fault was small.
5. Bloat as Operational Debt, Not Disk Consumption
A Windows server often ships with a GUI, a browser, legacy subsystems, and optional features enabled by default. Each of these components must be patched, monitored, and defended whether they are used or not.
Linux distributions assume absence. You install what you need and nothing else. BusyBox demonstrates the extreme: one binary, dozens of capabilities, minimal surface area. This is not aesthetic minimalism. It is operational discipline.
Every unused component is latent liability. Linux is designed to minimise the number of things that exist.
6. Licensing Costs as a Systems Design Constraint
Linux licensing is deliberately dull. Costs scale predictably. Capacity planning is an engineering exercise, not a legal one.
Windows licensing scales with cores, editions, features, and access models. At small scale this is manageable. At large scale it starts influencing topology. Architects begin shaping systems around licensing thresholds rather than fault domains.
When licensing dictates architecture, reliability becomes secondary to compliance.
7. Networking, XDP, and eBPF: Policy at Line Rate
Linux treats the kernel as a programmable execution environment. With XDP and eBPF, packets can be inspected, redirected, or dropped before they meaningfully enter the networking stack. This allows DDoS mitigation, traffic shaping, observability, and enforcement at line rate.
This is not a performance optimisation. It is a relocation of control. Policy moves into the kernel. Infrastructure becomes introspective and reactive.
Windows networking is capable, but it does not expose equivalent in kernel programmability. As enterprises move toward zero trust, service meshes, and real time enforcement, Linux aligns naturally with those needs.
8. Containers as a Native Primitive, Not a Feature
Linux containers are not lightweight virtual machines. They are namespaces and control groups enforced by the kernel itself. This makes them predictable, cheap, and dense.
Windows containers exist, but they are heavier and less uniform. They rely on more layers and assumptions, which reduces density and increases operational variance.
Kubernetes did not emerge accidentally on Linux. It emerged because the primitives already existed.
9. Security Reality: Patch Gravity and Structural Exposure
Windows security is not weak because of negligence. It is fragile because of accumulated complexity.
A modern Windows enterprise stack requires constant patching across the operating system, the .NET runtime, PowerShell, IIS, legacy components kept alive for compatibility, and a long tail of bundled services that cannot easily be removed. Each layer brings its own CVEs, its own patch cadence, and its own regression risk. Patch cycles become continuous rather than episodic.
The .NET runtime is a prime example. It is powerful, expansive, and deeply embedded. It also requires frequent security updates that ripple through application stacks. Patching .NET is not a simple upgrade. It is a dependency exercise that demands testing across frameworks, libraries, and deployment pipelines.
Windows’ security model reflects its history as a general purpose platform. Backward compatibility is sacred. Legacy APIs persist. Optional components remain present even when unused. Security tooling becomes additive: agents layered on top of agents to compensate for surface area that cannot be removed.
Linux takes a subtractive approach. If a runtime is not installed, it cannot be exploited. Mandatory access controls such as SELinux and AppArmor constrain blast radius at the kernel level. Fewer components exist by default, which reduces the number of things that need constant attention.
Windows security is a campaign. Linux security is structural.
10. Stability as the Absence of Surprise
Linux systems often run for years not because they are neglected, but because updates rarely force disruption. Drivers, filesystems, and subsystems evolve quietly.
Windows stability has improved significantly, but its operational model still assumes periodic interruption. Reboots are expected. Downtime is normalised.
Enterprise stability is not about never failing. It is about failing in ways that are predictable, bounded, and quickly reversible.
Final Thought: Invisibility Is the Goal
Windows integrates. Linux disappears.
Windows participates in the system. Linux becomes the substrate beneath it. In enterprise environments, invisibility is not a weakness. It is the highest compliment.
If your operating system demands attention in production, it is already costing you more than you think. Linux is designed to avoid being noticed. Windows is designed to be experienced.
At scale, that philosophical difference becomes destiny.
The in memory data store landscape fractured in March 2024 when Redis Inc abandoned its BSD 3-clause licence in favour of the dual RSALv2/SSPLv1 model. The community response was swift and surgical: Valkey emerged as a Linux Foundation backed fork, supported by AWS, Google Cloud, Oracle, Alibaba, Tencent, and Ericsson. Eighteen months later, both projects have diverged significantly, and the choice between them involves far more than licensing philosophy.
1. The Fork That Changed Everything
When Redis Inc made its licensing move, the stated rationale was protecting against cloud providers offering Redis as a managed service without contribution. The irony was immediate. AWS and Google Cloud responded by backing Valkey with their best Redis engineers. Tencent’s Binbin Zhu alone had contributed nearly a quarter of all Redis open source commits. The technical leadership committee now has over 26 years of combined Redis experience and more than 1,000 commits to the codebase.
Redis Inc CEO Rowan Trollope dismissed the fork at the time, asserting that innovation would differentiate Redis. What he perhaps underestimated was that the innovators had just walked out the door.
By May 2025, Redis pivoted again, adding AGPLv3 as a licensing option for Redis 8. The company brought back Salvatore Sanfilippo (antirez), Redis’s original creator. The messaging was careful: “Redis as open source again.” But the damage to community trust was done. As Kyle Davis, a Valkey maintainer, stated after the September 2024 Valkey 8.0 release: “From this point forward, Redis and Valkey are two different pieces of software.”
2. Architecture: Same Foundation, Diverging Paths
Both Redis and Valkey maintain the single threaded command execution model that ensures atomicity without locks or context switches. This architectural principle remains sacred. What differs is how each project leverages additional threads for I/O operations.
2.1 Threading Models
Redis introduced multi threaded I/O in version 6.0, offloading socket reads and writes to worker threads while keeping data manipulation single threaded. Redis 8.0 enhanced this with a new I/O threading model claiming 112% throughput improvement when setting io-threads to 8 on multi core Intel CPUs.
Valkey took a more aggressive approach. The async I/O threading implementation in Valkey 8.0, contributed primarily by AWS engineers, allows the main thread and I/O threads to operate concurrently. The I/O threads handle reading, parsing, writing responses, polling for I/O events, and even memory deallocation. The main thread orchestrates jobs to I/O threads while executing commands, and the number of active I/O threads adjusts dynamically based on load.
The results speak clearly. On AWS Graviton r7g instances, Valkey 8.0 achieves 1.2 million queries per second compared to 380K QPS in Valkey 7.2 (a 230% improvement). Independent benchmarks from Momento on c8g.2xlarge instances (8 vCPU) showed Valkey 8.1.1 reaching 999.8K RPS on SET operations with 0.8ms p99 latency, while Redis 8.0 achieved 729.4K RPS with 0.99ms p99 latency. That is 37% higher throughput on writes and 60%+ faster p99 latencies on reads.
2.2 Memory Efficiency
Valkey 8.0 introduced a redesigned hash table implementation that reduces memory overhead per key. The 8.1 release pushed further, observing roughly 20 byte reduction per key-value pair for keys without TTL, and up to 30 bytes for keys with TTL. For a dataset with 50 million keys, that translates to roughly 1GB of saved memory.
Redis 8.0 counters with its own optimisations, but the Valkey improvements came from engineers intimately familiar with the original Redis codebase. Google Cloud’s benchmarks show Memorystore for Valkey 8.0 achieving 2x QPS at microsecond latency compared to Memorystore for Redis Cluster.
3. Feature Comparison
3.1 Core Data Types
Both support the standard Redis data types: strings, hashes, lists, sets, sorted sets, streams, HyperLogLog, bitmaps, and geospatial indexes. Both support Lua scripting and Pub/Sub messaging.
3.2 JSON Support
Redis 8.0 bundles RedisJSON (previously a separate module) directly into core, available under the AGPLv3 licence. This provides native JSON document storage with partial updates and JSONPath queries.
Valkey responded with valkey-json, an official module compatible with Valkey 8.0 and above. As of Valkey 8.1, JSON support is production-ready through the valkey bundle container that packages valkey-json 1.0, valkey-bloom 1.0, valkey-search 1.0, and valkey-ldap 1.0 together.
3.3 Vector Search
This is where the AI workload story becomes interesting.
Redis 8.0 introduced vector sets as a new native data type, designed by Sanfilippo himself. It provides high dimensional similarity search directly in core, positioning Redis for semantic search, RAG pipelines, and recommendation systems.
Valkey’s approach is modular. valkey-search provides KNN and HNSW approximate nearest neighbour algorithms, capable of searching billions of vectors with millisecond latencies and over 99% recall. Google Cloud contributed their vector search module to the project, and it is now the official search module for Valkey OSS. Memorystore for Valkey can perform vector search at single digit millisecond latency on over a billion vectors.
The architectural difference matters. Redis embeds vector capability in core; Valkey keeps it modular. For organisations wanting a smaller attack surface or not needing vector search, Valkey’s approach offers more control.
3.4 Probabilistic Data Structures
Both now offer Bloom filters. Redis bundles RedisBloom in core; Valkey provides valkey-bloom as a module. Bloom filters use roughly 98% less memory than traditional sets for membership testing with an acceptable false positive rate.
3.5 Time Series
Redis 8.0 bundles RedisTimeSeries in core. Valkey does not yet have a native time series module, though the roadmap indicates interest.
3.6 Search and Query
Redis 8.0 includes the Redis Query Engine (formerly RediSearch), providing secondary indexing, full-text search, and aggregation capabilities.
Valkey search currently focuses on vector search but explicitly states its goal is to extend Valkey into a full search engine supporting full-text search. This is roadmap, not shipped product, as of late 2025.
4. Licensing: The Uncomfortable Conversation
4.1 Redis Licensing (as of Redis 8.0)
Redis now offers a tri licence model: RSALv2, SSPLv1, and AGPLv3. Users choose one.
AGPLv3 is OSI approved open source, but organisations often avoid it due to its copyleft requirements. If you modify Redis and offer it as a network service, you must release your modifications. Many enterprise legal teams treat AGPL as functionally similar to proprietary for internal use policies.
RSALv2 and SSPLv1 are source available but not open source by OSI definition. Both restrict offering Redis as a managed service without licensing arrangements.
The practical implication: most enterprises consuming Redis 8.0 will either use it unmodified (which sidesteps AGPL concerns) or license Redis commercially.
4.2 Valkey Licensing
Valkey remains BSD 3-clause. Full stop. You can fork it, modify it, commercialise it, and offer it as a managed service without restriction. This is why AWS, Google Cloud, Oracle, and dozens of others are building their managed offerings on Valkey.
For financial services institutions subject to regulatory scrutiny around software licensing, Valkey’s licence clarity is a non-trivial advantage.
AWS has made its position clear through pricing. The discounts for Valkey versus Redis OSS are substantial and consistent across services.
5.1 Annual Cost Comparison by Cluster Size
The following table shows annual costs for typical ElastiCache node-based deployments in us-east-1 using r7g Graviton3 instances. All configurations assume high availability with one replica per shard. Pricing reflects on-demand rates; reserved instances reduce costs further but maintain the same 20% Valkey discount.
For serverless deployments, the 33% discount on both storage and compute makes the differential even more pronounced at scale.
Workload
Storage
Requests/sec
Redis OSS/year
Valkey/year
Savings
Small
5GB
10,000
$5,475
$3,650
$1,825
Medium
25GB
50,000
$16,350
$10,900
$5,450
Large
100GB
200,000
$54,400
$36,250
$18,150
XL
500GB
1,000,000
$233,600
$155,700
$77,900
Note: Serverless calculations assume simple GET/SET operations (1 ECPU per request) with sub-1KB payloads. Complex operations on sorted sets or hashes consume proportionally more ECPUs.
5.2 Memory Efficiency Multiplier
The above comparisons assume identical node sizing, but Valkey 8.1’s memory efficiency improvements often allow downsizing. AWS documented a real customer case where upgrading from ElastiCache for Redis OSS to Valkey 8.1 reduced memory usage by 36%, enabling a downgrade from r7g.xlarge to r7g.large nodes. Combined with the 20% engine discount, total savings reached 50%.
For the Large Cluster example above, if memory efficiency allows downsizing from r7g.2xlarge to r7g.xlarge:
Scenario
Configuration
Annual Cost
vs Redis OSS Baseline
Redis OSS (baseline)
12× r7g.2xlarge
$91,764
—
Valkey (same nodes)
12× r7g.2xlarge
$73,380
-20%
Valkey (downsized)
12× r7g.xlarge
$36,792
-60%
This 60% saving reflects real-world outcomes when combining engine pricing with memory efficiency gains.
5.3 ElastiCache Serverless
ElastiCache Serverless charges for data storage (GB-hours) and compute (ElastiCache Processing Units or ECPUs). One ECPU covers 1KB of data transferred for simple GET/SET operations. More complex commands like HMGET consume ECPUs proportional to vCPU time or data transferred, whichever is higher.
In us-east-1, ElastiCache Serverless for Valkey prices at $0.0837/GB-hour for storage and $0.00227/million ECPUs. ElastiCache Serverless for Redis OSS prices at $0.125/GB-hour for storage and $0.0034/million ECPUs. That is 33% lower on storage and 33% lower on compute for Valkey.
The minimum metered storage is 100MB for Valkey versus 1GB for Redis OSS. This enables Valkey caches starting at $6/month compared to roughly $91/month for Redis OSS.
For a reference workload of 10GB average storage and 50,000 requests/second (simple GET/SET, sub-1KB payloads), the monthly cost breaks down as follows. Storage runs 10 GB × $0.0837/GB-hour × 730 hours = $611/month for Valkey versus $912.50/month for Redis OSS. Compute runs 180 million ECPUs/hour × 730 hours × $0.00227/million = $298/month for Valkey versus $446/month for Redis OSS. Total monthly cost is roughly $909 for Valkey versus $1,358 for Redis OSS, a 33% saving.
5.4 ElastiCache Node Base
For self managed clusters where you choose instance types, Valkey is priced 20% lower than Redis OSS across all node types.
A cache.r7g.xlarge node (4 vCPU, 26.32 GiB memory) in us-east-1 costs $0.449/hour for Valkey versus $0.561/hour for Redis OSS. Over a month, that is $328 versus $410 per node. For a cluster with 6 nodes (3 shards, 1 replica each), annual savings reach $5,904.
Reserved nodes offer additional discounts (up to 55% for 3 year all upfront) on top of the Valkey pricing advantage. Critically, if you hold Redis OSS reserved node contracts and migrate to Valkey, your reservations continue to apply. You simply receive 20% more value from them.
5.5 MemoryDB
Amazon MemoryDB, the durable in-memory database with multi AZ persistence, follows the same pattern. MemoryDB for Valkey is 30% lower on instance hours than MemoryDB for Redis OSS.
A db.r6g.xlarge node in us-west-2 costs $0.432/hour for Valkey versus approximately $0.617/hour for Redis OSS. For a typical HA deployment (1 shard, 1 primary, 1 replica), monthly costs run $631 for Valkey versus $901 for Redis OSS.
MemoryDB for Valkey also eliminates data written charges up to 10TB/month. Above that threshold, pricing is $0.04/GB, which is 80% lower than MemoryDB for Redis OSS.
5.6 Data Tiering Economics
For workloads with cold data that must remain accessible, ElastiCache and MemoryDB both support data tiering on r6gd node types. This moves infrequently accessed data from memory to SSD automatically.
A db.r6gd.4xlarge with data tiering can store 840GB total (approximately 105GB in memory, 735GB on SSD) at significantly lower cost than pure in-memory equivalents. For compliance workloads requiring 12 months of data retention, this can reduce costs by 52.5% compared to fully in memory configurations while maintaining low millisecond latencies for hot data.
5.7 Scaling Economics
ElastiCache Serverless for Valkey 8.0 scales dramatically faster than 7.2. In AWS benchmarks, scaling from 0 to 5 million RPS takes under 13 minutes on Valkey 8.0 versus 50 minutes on Valkey 7.2. The system doubles supported RPS every 2 minutes versus every 10 minutes.
For burst workloads, this faster scaling means lower peak latencies. The p99 latency during aggressive scaling stays under 8ms for Valkey 8.0 versus potentially spiking during the longer scaling windows of earlier versions.
5.8 Migration Economics
AWS provides zero-downtime, in place upgrades from ElastiCache for Redis OSS to ElastiCache for Valkey. The process is a few clicks in the console or a single CLI command:
Your reserved node pricing carries over, and you immediately begin receiving the 20% discount on node based clusters or 33% discount on serverless. There is no migration cost beyond the time to validate application compatibility.
5.9 Total Cost of Ownership
For an enterprise running 100GB across 10 ElastiCache clusters with typical caching workloads, the annual savings from Redis OSS to Valkey are substantial:
Serverless scenario (10 clusters, 10GB each, 100K RPS average per cluster): roughly $109,000/year on Valkey versus $163,000/year on Redis OSS, saving $54,000 annually.
Node-based scenario (10 clusters, cache.r7g.2xlarge, 3 shards + 1 replica each): roughly $315,000/year on Valkey versus $394,000/year on Redis OSS, saving $79,000 annually.
These numbers exclude operational savings from faster scaling, lower latencies reducing retry logic, and memory efficiency improvements allowing smaller node selections.
6. Google Cloud and Azure Considerations
Google Cloud Memorystore for Valkey is generally available with a 99.99% SLA. Committed use discounts offer 20% off for one-year terms and 40% off for three-year terms, fungible across Memorystore for Valkey, Redis Cluster, Redis, and Memcached. Google was first to market with Valkey 8.0 as a managed service.
Azure offers Azure Cache for Redis as a managed service, based on licensed Redis rather than Valkey. Microsoft’s agreement with Redis Inc means Azure customers do not currently have a Valkey option through native Azure services. For Azure-primary organisations wanting Valkey, options include self-managed deployments on AKS or multi-cloud architectures leveraging AWS or GCP for caching.
Redis Cloud (Redis Inc’s managed offering) operates across AWS, GCP, and Azure with consistent pricing. Commercial quotes are required for production workloads, making direct comparison difficult, but the pricing does not include the aggressive discounting that cloud providers apply to Valkey.
7. Third Party Options
Upstash offers a true pay-per-request serverless Redis-compatible service at $0.20 per 100K requests plus $0.25/GB storage. For low-traffic applications (under 1 million requests/month with 1GB storage), Upstash costs roughly $2.25/month versus $91+/month for ElastiCache Serverless Redis OSS. Upstash also provides a REST API for environments where TCP is restricted, such as Cloudflare Workers.
Dragonfly, KeyDB, and other Redis-compatible alternatives exist but lack the cloud provider backing and scale validation that Valkey has demonstrated.
Enable with io-threads N in configuration. Start with core count minus 2 for the I/O thread count. The system dynamically adjusts active threads based on load, so slight overprovisioning is safe.
For TLS workloads, Valkey 8.1 offloads TLS negotiation to I/O threads, improving new connection rates by roughly 300%.
9.2 Memory Defragmentation
Valkey 8.1 reduced active defrag cycle time to 500 microseconds with anti-starvation protection. This eliminates the historical issue of 1ms+ latency spikes during defragmentation.
9.3 Cluster Scaling
Valkey 8.0 introduced automatic failover for empty shards and replicated migration states. During slot movement, cluster consistency is maintained even through node failures. This was contributed by Google and addresses real production pain from earlier Redis cluster implementations.
10. The Verdict
The Redis fork has produced genuine competition for the first time in the in-memory data store space. Valkey is not merely a “keep the lights on” maintenance fork. It is evolving faster than Redis in core performance characteristics, backed by engineers who wrote much of the original Redis codebase, and supported by the largest cloud providers.
For enterprise architects, the calculus is increasingly straightforward. Unless you have specific dependencies on Redis 8’s bundled modules (particularly time series or full-text search), Valkey offers superior performance, clearer licensing, and lower costs on managed platforms.
The commercial signals are unambiguous. AWS prices Valkey 20-33% below Redis OSS on ElastiCache and 30% below on MemoryDB. Reserved node contracts transfer seamlessly. Migration is zero-downtime. The incentive structure points one direction.
The Redis licence changes in 2024 were intended to monetise cloud provider usage. Instead, they unified the cloud providers behind an alternative that is now outperforming the original. The return to AGPLv3 in 2025 acknowledges the strategic error, but the community momentum has shifted.
Redis is open source again. But the community that made it great is building Valkey.
CVE-2024-3094 represents one of the most sophisticated supply chain attacks in recent history. Discovered in March 2024, this vulnerability embedded a backdoor into XZ Utils versions 5.6.0 and 5.6.1, allowing attackers to compromise SSH authentication on Linux systems. With a CVSS score of 10.0 (Critical), this attack demonstrates the extreme risks inherent in open source supply chains and the sophistication of modern cyber threats.
This article provides a technical deep dive into how the backdoor works, why it’s extraordinarily dangerous, and practical methods for detecting compromised systems remotely.
Table of Contents
What Makes This Vulnerability Exceptionally Dangerous
The Anatomy of the Attack
Technical Implementation of the Backdoor
Detection Methodology
Remote Scanning Tools and Techniques
Remediation Steps
Lessons for the Security Community
What Makes This Vulnerability Exceptionally Dangerous
Supply Chain Compromise at Scale
Unlike traditional vulnerabilities discovered through code audits or penetration testing, CVE-2024-3094 was intentionally inserted through a sophisticated social engineering campaign. The attacker, operating under the pseudonym “Jia Tan,” spent over two years building credibility in the XZ Utils open source community before introducing the malicious code.
This attack vector is particularly insidious for several reasons:
Trust Exploitation: Open source projects rely on volunteer maintainers who operate under enormous time pressure. By becoming a trusted contributor over years, the attacker bypassed the natural skepticism that would greet code from unknown sources.
Delayed Detection: The malicious code was introduced gradually through multiple commits, making it difficult to identify the exact point of compromise. The backdoor was cleverly hidden in test files and binary blobs that would escape cursory code review.
Widespread Distribution: XZ Utils is a fundamental compression utility used across virtually all Linux distributions. The compromised versions were integrated into Debian, Ubuntu, Fedora, and Arch Linux testing and unstable repositories, affecting potentially millions of systems.
The Perfect Backdoor
What makes this backdoor particularly dangerous is its technical sophistication:
Pre-authentication Execution: The backdoor activates before SSH authentication completes, meaning attackers can gain access without valid credentials.
Remote Code Execution: Once triggered, the backdoor allows arbitrary command execution with the privileges of the SSH daemon, typically running as root.
Stealth Operation: The backdoor modifies the SSH authentication process in memory, leaving minimal forensic evidence. Traditional log analysis would show normal SSH connections, even when the backdoor was being exploited.
Selective Targeting: The backdoor contains logic to respond only to specially crafted SSH certificates, making it difficult for researchers to trigger and analyze the malicious behavior.
Timeline and Near Miss
The timeline of this attack demonstrates how close the security community came to widespread compromise:
Late 2021: “Jia Tan” begins contributing to XZ Utils project
2022-2023: Builds trust through legitimate contributions and pressures maintainer Lasse Collin
February 2024: Backdoored versions 5.6.0 and 5.6.1 released
March 29, 2024: Andres Freund, a PostgreSQL developer, notices unusual SSH behavior during performance testing and discovers the backdoor
March 30, 2024: Public disclosure and emergency response
Had Freund not noticed the 500ms SSH delay during unrelated performance testing, this backdoor could have reached production systems across the internet. The discovery was, by the discoverer’s own admission, largely fortuitous.
The Anatomy of the Attack
Multi-Stage Social Engineering
The attack began long before any malicious code was written. The attacker needed to:
Establish Identity: Create a credible online persona with consistent activity patterns
Build Reputation: Make legitimate contributions to build trust
Apply Pressure: Create artificial urgency around maintainer succession
Gain Commit Access: Become a co-maintainer with direct repository access
This process took approximately two years, demonstrating extraordinary patience and planning. The attacker created multiple personas to add social pressure on the sole maintainer, suggesting burnout and need for help.
Code Insertion Strategy
The malicious code was inserted through several mechanisms:
Obfuscated Build Scripts: The backdoor was triggered through the build system rather than in the main source code. Modified build scripts would inject malicious code during compilation.
Binary Test Files: Large binary test files were added to the repository, containing encoded malicious payloads. These files appeared to be legitimate test data but actually contained the backdoor implementation.
Multi-Commit Obfuscation: The backdoor was introduced across multiple commits over several weeks, making it difficult to identify a single “smoking gun” commit.
Ifunc Abuse: The backdoor used GNU indirect function (ifunc) resolvers to hook into the SSH authentication process at runtime, modifying program behavior without changing the obvious code paths.
Technical Implementation of the Backdoor
How XZ Utils Connects to SSH
To understand the backdoor, we must first understand an unexpected dependency chain:
On many modern Linux distributions, the SSH daemon links against libsystemd for process notification and logging. The systemd library, in turn, links against liblzma for compression functionality. This creates an indirect but critical dependency: SSH loads XZ Utils’ compression library into its address space.
The attackers exploited this dependency chain to inject their backdoor into the SSH authentication process.
Stage 1: Build Time Injection
The attack begins during the XZ Utils build process:
# Simplified representation of the malicious build script
if test -f "$srcdir/tests/files/good-large_compressed.lzma"; then
# Extract and execute embedded script from "test file"
eval $(xz -dc "$srcdir/tests/files/good-large_compressed.lzma" | head -c 1024)
fi
The build script would:
Detect specific binary test files in the source tree
Decompress these files to extract shell commands
Execute the extracted commands during the build process
Modify the liblzma source code to include backdoor hooks
These binary test files appeared legitimate because they were valid LZMA compressed data. However, they contained carefully crafted payloads that would only activate under specific conditions.
Stage 2: Runtime Hooking
Once compiled with the malicious modifications, liblzma contains an ifunc resolver that executes early during library loading:
// Conceptual representation of the backdoor logic
int backdoor_rsa_verify(unsigned char *signature, RSA *key) {
// Check if signature contains magic bytes
if (signature_contains_trigger(signature)) {
// Extract and execute payload from certificate
char *command = decode_payload(signature);
// Execute with sshd privileges (typically root)
system(command);
// Return success to bypass authentication
return 1;
}
// Otherwise, perform normal verification
return original_rsa_verify(signature, key);
}
The backdoor:
Intercepts all SSH authentication attempts
Examines the RSA signature for special markers
If triggered, extracts commands from the certificate
Executes commands with root privileges
Returns success to complete the “authentication”
From the SSH server’s perspective, this appears as a normal successful authentication. The logs would show a legitimate connection from an authorized user, even though no valid credentials were presented.
Why Traditional Detection Fails
The backdoor was designed to evade common security measures:
No Network Signatures: The malicious traffic looks identical to normal SSH, using standard protocols and ports.
No File System Artifacts: The backdoor exists only in memory after library loading. No malicious files are written to disk during exploitation.
Clean Source Code: The primary liblzma source code remains clean. The modifications occur during build time and aren’t present in the repository’s main files.
Log Evasion: Successful backdoor authentication appears in logs as a normal SSH connection, complete with username and source IP.
Selective Activation: The backdoor only responds to specially crafted certificates, making it difficult to trigger during security research or scanning.
Detection Methodology
Since the backdoor operates at runtime and leaves minimal artifacts, detection focuses on behavioral analysis rather than signature matching.
Timing Based Detection
The most reliable detection method exploits an unintended side effect: the backdoor’s cryptographic operations introduce measurable timing delays.
Normal SSH server (10 samples):
- Mean: 180ms
- Std Dev: 25ms
- Variance: 625ms²
Backdoored SSH server (10 samples):
- Mean: 850ms
- Std Dev: 180ms
- Variance: 32,400ms²
The backdoored server shows both higher average timing and greater variance, as the backdoor’s overhead varies depending on system state and what initialization code paths execute.
Banner Analysis
While not definitive, certain configurations increase vulnerability likelihood:
# SSH banner typically reveals:
SSH-2.0-OpenSSH_9.6p1 Debian-5ubuntu1
# Breaking down the information:
# OpenSSH_9.6p1 - Version commonly affected
# Debian-5ubuntu1 - Distribution and package version
Debian and Ubuntu were the primary targets because:
They quickly incorporated the backdoored versions into testing repositories
They use systemd, creating the sshd → libsystemd → liblzma dependency chain
They enable systemd notification in sshd by default
Library Linkage Analysis
On accessible systems, verifying SSH’s library dependencies provides definitive evidence:
For integration with existing security scanning workflows, an Nmap NSE script provides standardized vulnerability reporting. Nmap Scripting Engine (NSE) scripts are written in Lua and leverage Nmap’s network scanning capabilities. Understanding NSE Script Structure NMAP NSE scripts follow a specific structure that integrates with Nmap’s scanning engine. Create the React2Shell detection script with:
# Isolate the system from network
# Save current state for forensics first
netstat -tupan > /tmp/netstat_snapshot.txt
ps auxf > /tmp/process_snapshot.txt
# Then block incoming SSH
iptables -I INPUT -p tcp --dport 22 -j DROP
# Or shutdown SSH entirely
systemctl stop ssh
Step 4: Remediation
For systems with the vulnerable version but no evidence of compromise:
# Debian/Ubuntu systems
apt-get update
apt-get install --only-upgrade xz-utils
# Verify the new version
xz --version
# Should show 5.4.x or 5.5.x
# Alternative: Explicit downgrade
apt-get install xz-utils=5.4.5-0.3
# Restart SSH to unload old library
systemctl restart ssh
# Verify library version
readlink -f /lib/x86_64-linux-gnu/liblzma.so.5
# Should NOT be 5.6.0 or 5.6.1
# Confirm SSH no longer shows timing anomalies
# Run scanner again from remote system
./ssh_backdoor_scanner.py remediated-server.com
# Monitor for a period
tail -f /var/log/auth.log
System Hardening Post Remediation
After removing the backdoor, implement additional protections:
This attack highlights critical vulnerabilities in the open source ecosystem:
Maintainer Burnout: Many critical projects rely on volunteer maintainers working in isolation. The XZ Utils maintainer was a single individual managing a foundational library with limited resources and support.
Trust But Verify: The security community must develop better mechanisms for verifying not just code contributions, but also the contributors themselves. Multi-year social engineering campaigns can bypass traditional code review.
Automated Analysis: Build systems and binary artifacts must receive the same scrutiny as source code. The XZ backdoor succeeded partly because attention focused on C source files while malicious build scripts and test files went unexamined.
Dependency Awareness: Understanding indirect dependency chains is critical. Few would have identified XZ Utils as SSH-related, yet this unexpected connection enabled the attack.
Detection Strategy Evolution
The fortuitous discovery of this backdoor through performance testing suggests the security community needs new approaches:
Behavioral Baselining: Systems should establish performance baselines for critical services. Deviations, even subtle ones, warrant investigation.
Timing Analysis: Side-channel attacks aren’t just theoretical concerns. Timing differences can reveal malicious code even when traditional signatures fail.
Continuous Monitoring: Point-in-time security assessments miss time-based attacks. Continuous behavioral monitoring can detect anomalies as they emerge.
Cross-Discipline Collaboration: The backdoor was discovered by a database developer doing performance testing, not a security researcher. Encouraging collaboration across disciplines improves security outcomes.
Infrastructure Recommendations
Organizations should implement:
Binary Verification: Don’t just verify source code. Ensure build processes are deterministic and reproducible. Compare binaries across different build environments.
Runtime Monitoring: Deploy tools that can detect unexpected library loading, function hooking, and behavioral anomalies in production systems.
Network Segmentation: Limit the blast radius of compromised systems through proper network segmentation and access controls.
Incident Response Preparedness: Have procedures ready for supply chain compromises, including rapid version rollback and system isolation capabilities.
The Role of Timing in Security
This attack demonstrates the importance of performance analysis in security:
Performance as Security Signal: Unexplained performance degradation should trigger security investigation, not just performance optimization.
Side Channel Awareness: Developers should understand that any observable behavior, including timing, can reveal system state and potential compromise.
Benchmark Everything: Establish performance baselines for critical systems and alert on deviations.
Conclusion
CVE-2024-3094 represents a watershed moment in supply chain security. The sophistication of the attack, spanning years of social engineering and technical preparation, demonstrates that determined adversaries can compromise even well-maintained open source projects.
The backdoor’s discovery was largely fortuitous, happening during unrelated performance testing just before the compromised versions would have reached production systems worldwide. This near-miss should serve as a wake-up call for the entire security community.
The detection tools and methodologies presented in this article provide practical means for identifying compromised systems. However, the broader lesson is that security requires constant vigilance, comprehensive monitoring, and a willingness to investigate subtle anomalies that might otherwise be dismissed as performance issues.
As systems become more complex and supply chains more intricate, the attack surface expands beyond traditional code vulnerabilities to include the entire software development and distribution process. Defending against such attacks requires not just better tools, but fundamental changes in how we approach trust, verification, and monitoring in software systems.
The React2Shell backdoor was detected and neutralized before widespread exploitation. The next supply chain attack may not be discovered so quickly, or so fortunately. The time to prepare is now.
Additional Resources
Technical References
National Vulnerability Database: https://nvd.nist.gov/vuln/detail/CVE-2024-3094
Technical Analysis by Sam James: https://gist.github.com/thesamesam/223949d5a074ebc3dce9ee78baad9e27
Detection Tools
The scanner tools discussed in this article are available for download and can be deployed in production environments for ongoing monitoring. They require no authentication to the target systems and work by analyzing observable timing behavior in the SSH handshake and authentication process.
These tools should be integrated into regular security scanning procedures alongside traditional vulnerability scanners and intrusion detection systems.
Indicators of Compromise
XZ Utils version 5.6.0 or 5.6.1 installed
SSH daemon (sshd) linking to liblzma library
Unusual SSH authentication timing (>800ms for auth probe)
High variance in SSH connection establishment times
Recent XZ Utils updates from February or March 2024
Debian or Ubuntu systems with systemd enabled SSH
OpenSSH versions 9.6 or 9.7 on Debian-based distributions
Recommended Actions
Scan all SSH-accessible systems for timing anomalies
Verify XZ Utils versions across your infrastructure
Review SSH authentication logs for suspicious patterns
Implement continuous monitoring for behavioral anomalies
Establish performance baselines for critical services
Develop incident response procedures for supply chain compromises
Consider additional SSH hardening measures
Review and audit all open source dependencies in your environment
When you deploy applications behind a Network Load Balancer (NLB) in AWS, you usually expect perfect traffic distribution, fast, fair, and stateless. But what if your backend holds stateful sessions, like in-memory login sessions, caching, or WebSocket connections and you need a given client to keep hitting the same target every time?
That’s where NLB sticky sessions (also called connection stickiness or source IP affinity) come in. They’re powerful but also misunderstood and misconfiguring them can lead to uneven load, dropped connections, or mysterious client “resets.”
Let’s break down exactly how they work, how to set them up, what to watch for, and how to troubleshoot the tricky edge cases that appear in production.
1. What Are Sticky Sessions on an NLB?
At a high level, sticky sessions ensure that traffic from the same client consistently lands on the same target (EC2 instance, IP, or container) behind your NLB.
Unlike the Application Load Balancer (ALB) — which uses HTTP cookies for stickiness, the NLB operates at Layer 4 (TCP/UDP). That means it doesn’t look inside your packets. Instead, it bases stickiness on network-level parameters like:
Source IP address
Destination IP and port
Source port (sometimes included in the hash)
Protocol (TCP, UDP, or TLS passthrough)
AWS refers to this as “source IP affinity.” When enabled, the NLB creates a flow-hash mapping that ties the client to a backend target. As long as the hash remains the same, the same client gets routed to the same target — even across multiple connections.
2. Enabling Sticky Sessions on an AWS NLB
Stickiness is configured per target group, not at the NLB level.
Step-by-Step via AWS Console
Go to EC2 → Load Balancers → Target Groups Find the target group your NLB listener uses.
Select the Target Group → Attributes tab
Under Attributes, set:
Stickiness.enabled = true
Stickiness.type = source_ip
Save changes and confirm the attributes are updated.
The hash is used to choose a target. When stickiness is enabled, NLB remembers this mapping for some time (typically a few minutes to hours, depending on flow expiration).
Key Behavior Points:
If the same client connects again using the same IP and port, the hash matches == same backend target.
If any part of that tuple changes (e.g. client source port changes), the hash may change == client might hit a different target.
NLBs maintain this mapping in memory; if the NLB node restarts or fails over, the mapping is lost.
Sticky mappings can also be lost when cross-zone load balancing or target health status changes.
Not Cookie Based
Because NLBs don’t inspect HTTP traffic, there’s no cookie involved. This means:
You can’t set session duration or expiry time like in ALB stickiness.
Stickiness only works as long as the same network path and source IP persist.
4. Known Limitations & Edge Cases
Sticky sessions on NLBs are helpful but brittle. Here’s what can go wrong:
Issue
Cause
Effect
Client source IP changes
NAT, VPN, mobile switching networks
Hash changes → new target
Different source port
Client opens multiple sockets or reconnects
Each connection may map differently
TLS termination at NLB
NLB terminates TLS
Stickiness not supported (only for TCP listeners)
Unhealthy target
Health check fails
Mapping breaks; NLB reroutes
Cross-zone load balancing toggled
Distribution rules change
May break existing sticky mappings
DNS round-robin at client
NLB has multiple IPs per AZ
Client DNS resolver may change NLB node
UDP behavior
Stateless packets; different flow hash
Stickiness unreliable for UDP
Scaling up/down
New targets added
Hash table rebalanced; some clients remapped
Tip: If you rely on stickiness, keep your clients stable (same IP) and avoid frequent target registration changes.
5. Troubleshooting Sticky Session Problems
When things go wrong, these are the most common patterns you’ll see:
1. “Stickiness not working”
Check target group attributes: aws elbv2 describe-target-group-attributes --target-group-arn <arn> Ensure stickiness.enabled is true.
Make sure your listener protocol is TCP, not TLS.
Confirm that client IPs aren’t being rewritten by NAT or proxy.
Check CloudWatch metrics. If one target gets all the traffic, stickiness might be too “sticky” due to limited source IP variety.
2. “Some clients lose session state randomly”
Verify client network stability. Mobile clients or corporate proxies can rotate IPs.
Confirm health checks aren’t flapping targets.
Review your application session design, if session data lives in memory, consider an external session store (Redis, DynamoDB, etc.).
3. “Load imbalance: one instance overloaded”
This can happens when many users share one public IP (common in offices or ISPs). All those clients hash to the same backend.
Mitigate by:
Disabling stickiness if not strictly required.
Using ALB with cookie based stickiness (more granular).
Scaling target capacity.
4. “Connections drop after some time”
NLB may remove stale flow mappings.
Check TCP keepalive settings on clients and targets. Ensure keepalive_time < NLB idle timeout (350 seconds) to prevent connection resets. Linux commands below:
# Check keepalive time (seconds before sending first keepalive probe)
sysctl net.ipv4.tcp_keepalive_time
# Check keepalive interval (seconds between probes)
sysctl net.ipv4.tcp_keepalive_intvl
# Check keepalive probes (number of probes before giving up)
sysctl net.ipv4.tcp_keepalive_probes
# View all at once
sysctl -a | grep tcp_keepalive
Verify idle timeout on backend apps (e.g., web servers closing connections too early).
6. Observability & Testing
You can validate sticky behavior with:
CloudWatch metrics: ActiveFlowCount, NewFlowCount, and per target request metrics.
VPC Flow Logs: confirm that repeated requests from the same client IP go to the same backend ENI.
Packet captures: Use tcpdump or ss on your backend instances to see if the same source IP consistently connects.
for i in {1..100}; do
echo "=== Request $i at $(date) ===" | tee -a curl_test.log
curl https://<nlb-dns-name>/ -v 2>&1 | tee -a curl_test.log
sleep 0.5
done
Run it from the same host and check which backend responds (log hostname on each instance). Then try from another IP or VPN; you’ll likely see a different target.
7. Best Practices
Only enable stickiness if necessary. Stateless applications scale better without it.
If using TLS: terminate TLS at the backend or use ALB if you need session affinity.
Use shared session stores. Tools like ElastiCache (Redis) or DynamoDB make scaling simpler and safer.
Avoid toggling cross-zone load balancing during traffic, it resets the sticky map.
Set up proper health checks. Unhealthy targets break affinity immediately.
Monitor uneven load. Large NAT’d user groups can overload a single instance.
For UDP consider designing idempotent stateless processing; sticky sessions may not behave reliably.
8. Example Architecture Pattern
Scenario: A multiplayer game server behind an NLB. Each player connects via TCP to the game backend that stores their in-memory state.
✅ Recommended setup:
Enable stickiness.enabled = true and stickiness.type = source_ip
Disable TLS termination at NLB
Keep targets in the same AZ with cross-zone load balancing disabled to maintain stable mapping
Maintain external health and scaling logic to avoid frequent re-registrations
This setup ensures that the same player IP always lands on the same backend server, as long as their network path is stable.
9. Summary Table
Attribute
Supported Value
Notes
stickiness.enabled
true / false
Enables sticky sessions
stickiness.type
source_ip
Only option for NLB
Supported Protocols
TCP, UDP (limited)
Not supported for TLS listeners
Persistence Duration
Until flow reset
Not configurable
Cookie-based Stickiness
❌ No
Use ALB for cookie-based
Best for
Stateful TCP apps
e.g. games, custom protocols
10. When to Use ALB Instead
If you’re dealing with HTTP/HTTPS applications that manage user sessions via cookies or tokens, you’ll be much happier using an Application Load Balancer. It offers:
Configurable cookie duration
Per application stickiness
Layer 7 routing and metrics
The NLB should be reserved for high performance, low latency, or non HTTP workloads that need raw TCP/UDP handling.
11. Closing Thoughts
AWS NLB sticky sessions are a great feature, but they’re not magic glue. They work well when your network topology and client IPs are predictable, and your app genuinely needs flow affinity. However, if your environment involves NATs, mobile networks, or frequent scale-ups, expect surprises.
When in doubt: 1. Keep your app stateless, 2. Let the load balancer do its job, and 3. Use stickiness only as a last resort for legacy or session bound systems.
If you have multiple connections on your device (and maybe you have a zero trust client installed); how do you find out which network interface on your device will be used to route the traffic?
Below is a route get request for googles DNS service:
If you have multiple interfaces enabled, then the first item in the Service Order will be used. If you want to see the default interface for your device:
I frequently forget this command shortcut, so this post is simply because I am lazy. To clear your history in iTerm press Command + K. Control + L only clears the screen, so as soon as you run the next command you will see the scroll back again.
If you want to view your command history (for terminal) type:
$ host -a andrewbaker.ninja
Trying "andrewbaker.ninja"
;; ->>HEADER<<- opcode: QUERY, status: NOERROR, id: 45489
;; flags: qr rd ra; QUERY: 1, ANSWER: 10, AUTHORITY: 0, ADDITIONAL: 0
;; QUESTION SECTION:
;andrewbaker.ninja. IN ANY
;; ANSWER SECTION:
andrewbaker.ninja. 300 IN A 13.244.140.33
andrewbaker.ninja. 21600 IN NS ns-1254.awsdns-28.org.
andrewbaker.ninja. 21600 IN NS ns-1514.awsdns-61.org.
andrewbaker.ninja. 21600 IN NS ns-1728.awsdns-24.co.uk.
andrewbaker.ninja. 21600 IN NS ns-1875.awsdns-42.co.uk.
andrewbaker.ninja. 21600 IN NS ns-491.awsdns-61.com.
andrewbaker.ninja. 21600 IN NS ns-496.awsdns-62.com.
andrewbaker.ninja. 21600 IN NS ns-533.awsdns-02.net.
andrewbaker.ninja. 21600 IN NS ns-931.awsdns-52.net.
andrewbaker.ninja. 900 IN SOA ns-1363.awsdns-42.org. awsdns-hostmaster.amazon.com. 1 7200 900 1209600 86400
Received 396 bytes from 100.64.0.1#53 in 262 ms
The -a option is used to find all Domain records and Zone information. You can also notice the local DNS server address utilised for the lookup.
$ host 13.244.140.33
33.140.244.13.in-addr.arpa domain name pointer ec2-13-244-140-33.af-south-1.compute.amazonaws.com.
3. To find Domain Name servers
Use the -t option to get the domain name servers. It’s used to specify the query type. Below we pass the -t argument to find nameservers of a specific domain. NS record specifies the authoritative nameservers.
$ host -t ns andrewbaker.ninja
andrewbaker.ninja name server ns-1254.awsdns-28.org.
andrewbaker.ninja name server ns-1514.awsdns-61.org.
andrewbaker.ninja name server ns-1728.awsdns-24.co.uk.
andrewbaker.ninja name server ns-1875.awsdns-42.co.uk.
andrewbaker.ninja name server ns-491.awsdns-61.com.
andrewbaker.ninja name server ns-496.awsdns-62.com.
andrewbaker.ninja name server ns-533.awsdns-02.net.
andrewbaker.ninja name server ns-931.awsdns-52.net.
4. To query certain nameserver for a specific domain
To query details about a specific authoritative domain name server, use the below command.
$ host google.com olga.ns.cloudflare.com
Using domain server:
Name: olga.ns.cloudflare.com
Address: 173.245.58.137#53
Aliases:
google.com has address 172.217.170.14
google.com has IPv6 address 2c0f:fb50:4002:804::200e
google.com mail is handled by 10 smtp.google.com.
5. To find domain MX records
To get a list of a domain’s MX ( Mail Exchanger ) records.
$ host -t txt google.com
google.com descriptive text "docusign=1b0a6754-49b1-4db5-8540-d2c12664b289"
google.com descriptive text "v=spf1 include:_spf.google.com ~all"
google.com descriptive text "google-site-verification=TV9-DBe4R80X4v0M4U_bd_J9cpOJM0nikft0jAgjmsQ"
google.com descriptive text "facebook-domain-verification=22rm551cu4k0ab0bxsw536tlds4h95"
google.com descriptive text "atlassian-domain-verification=5YjTmWmjI92ewqkx2oXmBaD60Td9zWon9r6eakvHX6B77zzkFQto8PQ9QsKnbf4I"
google.com descriptive text "onetrust-domain-verification=de01ed21f2fa4d8781cbc3ffb89cf4ef"
google.com descriptive text "globalsign-smime-dv=CDYX+XFHUw2wml6/Gb8+59BsH31KzUr6c1l2BPvqKX8="
google.com descriptive text "docusign=05958488-4752-4ef2-95eb-aa7ba8a3bd0e"
google.com descriptive text "apple-domain-verification=30afIBcvSuDV2PLX"
google.com descriptive text "google-site-verification=wD8N7i1JTNTkezJ49swvWW48f8_9xveREV4oB-0Hf5o"
google.com descriptive text "webexdomainverification.8YX6G=6e6922db-e3e6-4a36-904e-a805c28087fa"
google.com descriptive text "MS=E4A68B9AB2BB9670BCE15412F62916164C0B20BB"
7. To find domain SOA record
To get a list of a domain’s Start of Authority record
$ host -C google.com
Nameserver 216.239.36.10:
google.com has SOA record ns1.google.com. dns-admin.google.com. 505465897 900 900 1800 60
Nameserver 216.239.38.10:
google.com has SOA record ns1.google.com. dns-admin.google.com. 505465897 900 900 1800 60
Nameserver 216.239.32.10:
google.com has SOA record ns1.google.com. dns-admin.google.com. 505465897 900 900 1800 60
Nameserver 216.239.34.10:
google.com has SOA record ns1.google.com. dns-admin.google.com. 505465897 900 900 1800 60
8. To find domain CNAME records
CNAME stands for canonical name record. This DNS record is responsible for redirecting one domain to another, which means it maps the original domain name to an alias.
To find out the domain CNAME DNS records, use the below command.
$ dig www.yahoo.com
]
; <<>> DiG 9.10.6 <<>> www.yahoo.com
;; global options: +cmd
;; Got answer:
;; ->>HEADER<<- opcode: QUERY, status: NOERROR, id: 45503
;; flags: qr rd ra; QUERY: 1, ANSWER: 3, AUTHORITY: 0, ADDITIONAL: 1
;; OPT PSEUDOSECTION:
; EDNS: version: 0, flags:; udp: 512
;; QUESTION SECTION:
;www.yahoo.com. IN A
;; ANSWER SECTION:
www.yahoo.com. 12 IN CNAME new-fp-shed.wg1.b.yahoo.com.
new-fp-shed.wg1.b.yahoo.com. 38 IN A 87.248.100.215
new-fp-shed.wg1.b.yahoo.com. 38 IN A 87.248.100.216
;; Query time: 128 msec
;; SERVER: 8.8.8.8#53(8.8.8.8)
;; WHEN: Mon Jan 30 17:07:55 SAST 2023
;; MSG SIZE rcvd: 106
In the above shown example CNAME entry, if you want to reach “www.yahoo.com”, your computer’s DNS resolver will first fire an address lookup for “www.yahoo.com“. Your resolver then sees that it was returned a CNAME record of “new-fp-shed.wg1.b.yahoo.com“, and in response it will now fire another lookup for “new-fp-shed.wg1.b.yahoo.com“. It will then be returned the A record. So its important to note here is that there are two separate and independent DNS lookups performed by the resolver in order to convert a CNAME into a usable A record.
9. To find domain TTL information
TTL Stands for Time to live. It is a part of the Domain Name Server. It is automatically set by an authoritative nameserver for each DNS record.
In simple words, TTL refers to how long a DNS server caches a record before refreshing the data. Use the below command to see the TTL information of a domain name (in the example below its 300 seconds/5 minutes).
When you open terminal you will see that it defaults the information that you see on the prompt, which can use up quite a bit of the screen real estate.
Last login: Sat Jan 14 11:13:00 on ttys000
cp363412~$
Customize the zsh Prompt in Terminal
Typically, the default zsh prompt carries information like the username, machine name, and location starting in the user’s home directory. These details are stored in the zsh shell’s system file at the /etc/zshrc location.
\h The hostname, up to the first . (e.g. andrew)
\H The hostname. (e.g. andrew.ninja.com)
\j The number of jobs currently managed by the shell.
\l The basename of the shell's terminal device name.
\s The name of the shell, the basename of $0 (the portion following
the final slash).
\w The current working directory.
\W The basename of $PWD.
\! The history number of this command.
\# The command number of this command
To change this, open Terminal, type the following command, and hit Return: