The wordlist already knew your home directory
F5 Labs logged 807 attacks and 32,000 events in one month against exposed Vite dev servers, using a query-string trick that walks straight past Vite's own deny-list. The scanners are not looking for your app. They ask for /home/ubuntu/.aws/credentials by absolute path, and for /proc/self/environ, and for terraform.tfstate. In a Bromure Agentic Coding workspace that port faces a private switch, and the file the wordlist wants does not hold a key.
This attacker ships you no package and no archive. Your own dev server is listening, and a stranger asks it for files by name. Which machine answers decides what they get.
You tell the agent to get the frontend running so you can look at it. It types
npm run dev, binds the port, loads the page, and you go back to reviewing the
diff. Somewhere in that loop, in a docker-compose.yml or in a --host flag you
added weeks ago to make the preview reachable from your phone, the server bound
every interface rather than loopback. Twelve minutes later something calling
itself Googlebot asks your dev server for /@fs/../.env?raw?? and gets an
HTTP 200.
F5 Labs published the numbers on September 11. Across a single month of August 2026 sensor data, their honeypots recorded 807 session-grouped attacks and roughly 32,000 raw events aimed at exposed Vite development servers, against a baseline of 1,732 events over the prior three months. That is eighteen times the traffic, month over month, for one bug. BleepingComputer covered it on September 14.
A query string that walks past the deny-list
Vite serves files from the host filesystem through an internal route called
/@fs/, which is how the dev server hands your editor a module that lives
outside the project root. Because that route can reach anywhere, Vite ships a
deny-list, server.fs.deny, to block the obvious targets: .env files,
certificates, private source.
CVE-2026-39364, published April 7, lets an attacker skip that deny-list by decorating the request with a query string. F5 describes the mechanism:
The server processes the request, normalizes the path, and strips or misinterprets the query string during access validation, failing to trigger the server.fs.deny check.
The server fails to enforce deny-list filtering and serves the target file with an HTTP 200 response.
The bug affects Vite 7.1.0 through 7.3.2, and 8.x before 8.0.5. F5 caught these
request shapes in the wild, each short enough to read in one line:
GET /@fs/.env?raw??, GET /@fs/../.env?raw??,
GET /@fs/..%252f..%252f..%252f..%252froot/.env?raw??, and
GET /@fs/..%252f..%252f..%252f..%252fproc/self/environ?raw??. Other variants in
the same tooling use ?import&raw, ?import&url&inline, ?inline&import and
?raw?import.
Those parameter tricks carry their own CVE numbers: CVE-2025-30208,
CVE-2025-31125 and CVE-2024-45811, three earlier @fs bypasses still loaded in
the same scanners. Whoever runs the scanning fleet did not retool for a new bug.
They appended one more line to a file they already had.
The wordlist is a map of a developer's machine
Read what the scanners ask for, and notice how little of it concerns your application.
They ask for .env, .env.local, .env.production, .env.development and
.env.staging. They ask for terraform.tfstate, terraform.tfvars,
.terraform/terraform.tfstate, serverless.yml and
.serverless/serverless-state.json. They ask for .azure/credentials and
.azure/accessTokens.json. They ask for /etc/passwd, /proc/1/environ,
/proc/self/cwd/.env and /proc/self/environ. That last path holds the
environment block of the dev server process itself, where an API_KEY your shell
exported ends up.
And then they ask for AWS credentials by walking the list of home directories a developer's process might be running as:
/root/.aws/credentials, /home/ec2-user/.aws/credentials,
/home/ubuntu/.aws/credentials, /home/node/.aws/credentials,
/home/www-data/.aws/credentials, /home/admin/.aws/credentials,
/home/debian/.aws/credentials, /var/www/.aws/credentials,
/usr/src/app/.aws/credentials, /app/.aws/credentials. Then .aws/config,
.aws/credentials.backup, .aws/credentials.bak, .aws/sso/cache/,
rootkey.csv, aws-exports.js and amplifyconfiguration.json.
Those paths inventory a developer machine, enumerated by username, and Vite is only the door. The bug is incidental; three older bugs in the same route ride along in the same requests. The operators are betting that a process listening on a routable address runs as a user whose home directory holds real keys.
The traffic is dressed to survive a casual look at the logs. F5 recorded forged
User-Agent headers rotating through Googlebot/2.1, ClaudeBot/1.0,
GPTBot/1.4, PerplexityBot/1.0, OAI-SearchBot/1.3 and Amazonbot/0.1, plus
forged X-Forwarded-For and X-Real-IP values to get past IP allow-lists. The
sources sit in Google Cloud's 34.x and 35.x ranges across several regions, led by
the United States at 17,297 events, then Belgium at 4,407 and the Netherlands at
4,011. This month, a line in your access log claiming to be an AI crawler is weak
evidence that a crawler sent it.
Two of F5's recommendations are architecture questions
F5 closes with five pieces of advice. Three are ordinary and correct: patch to
7.3.2 or 8.0.5, filter /@fs/ at the edge, verify crawlers by reverse DNS
instead of trusting the header. The other two describe a posture you have to hold
rather than a task you finish.
Ensure development servers do not bind to external interfaces. Audit Docker compose configurations, Kubernetes ingress rules, and cloud security groups.
Rotate exposed secrets: If an unpatched Vite development server was reachable from external networks during August 2026, treat local .env variables, AWS credentials, Azure access tokens, and Terraform state files as potentially compromised.
The first asks you to keep a promise about each port, in each compose file,
across each branch, for as long as the project lives, at a moment when whatever
types npm run dev is often an agent rather than you. The second asks what you
do afterward, and answers: rotate what the machine could see.
Both of those get easier if the port opens somewhere else, and if the files the wordlist names hold nothing worth rotating.
Where a Bromure workspace puts the port
Bromure Agentic Coding runs coding agents inside a hardware-virtualized Linux VM on your Mac, with the security controls on the host side of that boundary. Two of them answer this campaign.
The dev server binds inside a private switch. In the default NAT mode, every
workspace VM attaches to one process-wide software L2 switch multiplexed onto a
single vmnet interface, on a private subnet. That subnet is 192.168.64.0/24
unless your own LAN already uses it, Bromure runs its own DHCP server on it, and
each workspace keeps a deterministic MAC and a stable lease. The manual states
the consequence: your Mac can reach the VMs, your physical LAN cannot see them,
and inbound connections from elsewhere are impossible unless you publish a
service yourself. An agent that binds 0.0.0.0 inside that VM has bound each
interface it owns, and each of them faces a switch that starts and ends on your
laptop. You have no security group to audit, because you have no route in to
secure.
You can still see what is listening, and it is a list, not an audit. The
workspace dashboard carries a Listening Ports card showing every externally
reachable socket in the guest as a <VM-IP>:<port> endpoint with one-click copy.
That turns F5's recommendation #2 into a card you glance at, refreshed from the
guest every second and a half, instead of an audit you schedule. When you do want
the world to see a preview, a likely-HTTP service gets a globe button that
publishes that one service through a Cloudflare quick tunnel, behind a one-time
consent dialog. You expose a service by clicking it, per service, on the host,
rather than by leaving a flag alive in a compose file.
You keep the reason you reached for --host in the first place. The disposable
Chromium sidecar shares the same L2 segment as the workspace VM, so the embedded
browser loads the agent's dev server at the VM's address. Not at localhost,
since the browser runs on a separate machine.
If other machines do need to reach the VM, Bridged mode joins it to your physical LAN. You set that per workspace in the workspace editor, on the host, and it falls back to NAT if the interface is unavailable at launch. You make that choice in a panel, not in a config file the agent can edit.
And if you publish the service on purpose
Sometimes you do want the preview on the internet, for a client or a teammate. So click the globe, open the tunnel, and let the scanners find it. Follow the wordlist down, entry by entry, and look at what comes back.
.env and /proc/self/environ return the workspace's environment, and each
credential in it is a fake. Bromure derives each placeholder from the real value
plus a per-install 32-byte salt through HKDF-SHA256, keeping the shape a
client-side validator expects: an Anthropic key reads sk-ant-api03-brm-…, a
GitHub token is ghp_ plus 36 characters, GitLab is glpat- plus 20. Bromure
writes those fakes into the environment variables and into ~/.git-credentials,
~/.docker/config.json, ~/.kube/config and the MCP configs. The real values
never enter the VM; they stay encrypted on the Mac, and a host-side proxy swaps
each one onto the wire at the last moment, scoped to the destination host it
belongs to.
/home/ubuntu/.aws/credentials is the sharpest entry, because /home/ubuntu is
the home directory of a Bromure workspace VM. The scanner guesses right about
which user runs the server. The file is still not there. Bromure's AWS setup
writes no credentials file; it writes ~/.aws/config with a credential_process
line pointing at a helper that vends, over a host socket, your real access key ID
paired with a forty-character fake secret key, and omits the session token.
The AWS SDKs, the aws CLI, terraform and boto3 all pick that up on their own. A
scanner that reads the config file gets the path of a helper it cannot call.
Take the fake secret anyway and you have taken something inert. The guest signs
its requests with that fake, and the host strips the signature and re-signs with
the real key on the way out. A request that reaches AWS by any other route fails
with InvalidSignatureException. F5's last recommendation is to treat each local
secret as compromised and rotate it. The matching line in Bromure's own
documentation reads the other way: because only the fake leaked, the real
credential never needs rotation.
One entry in the wordlist names a file worth real money: terraform.tfstate.
State files live wherever the repository lives, so your shared-folder list decides
their exposure. Host folders attach to a workspace as virtiofs mounts at
/home/ubuntu/<basename>, capped at eight per workspace, and they are the only
part of your Mac's filesystem the VM can reach. Share the repository the agent is
working on and nothing else, and the traversal finds one directory instead of a
disk.
Most developer-security stories this year describe something arriving: a package, an archive, a documentation file. Nothing arrives here. Instead, 32,000 requests a month ask a process you started to read files aloud to a stranger, and the outcome turns on which machine runs that process and what sits in its home directory.
Put the agent's dev server on a switch that only your Mac is on, and fill its home directory with placeholders. Install Bromure Agentic Coding, then let the scanners ask.