A sandboxed process could write to a supervisor-owned config file and Linux's inotify revealed the system was watching it live. Every write triggered a kernel-level notification to a higher-privileged process outside the sandbox. Here's how I proved it.
Replit's supervisor — the process that manages your container — runs outside the sandbox. It writes configuration into the container but lives above it in the privilege hierarchy. One of those configuration files was writable by the unprivileged runner user executing inside the sandbox, meaning code running on Replit could reach outside its own sandbox boundary and modify infrastructure it was never supposed to touch.
Then I went further: using Linux's inotify subsystem I proved at the kernel level that the external supervisor was actively watching that file for changes in real time.
Understanding the Environment
When your code runs on Replit, it executes inside a Linux container. That container is managed by a supervisor process called pid1 — pid1 is responsible for managing the container's lifecycle, features, and behavior.
Replit's runtime state lives under /run/replit/. This directory contains files written by the supervisor infrastructure things like environment snapshots, socket files, and configuration.
Inside /run/replit/pid1/ I found a file called flags.json:
{
"ENABLE_PERIODIC_FLUSH": false,
"NOISY_VNC": false,
"PROFILE_EVERYTHING": false
}This is pid1's live configuration. The flags control supervisor behavior — things like whether VNC output is verbose, whether profiling is enabled, and whether periodic flush operations run.
Checking the file's permissions:
ls -la /run/replit/pid1/flags.jsonThe file was readable and writable by the runner user — the unprivileged user that executes all user code in the container. That means any code running in a Replit container could modify the supervisor's configuration.
The Question That Actually Matters
Does pid1 react to changes in this file at runtime?
I needed to prove pid1 actively monitors flags.json for changes and reacts whenever the file is modified. In this case, you have genuine real-time control over supervisor behavior.
Enter inotify: Linux's Filesystem Event System
What is inotify?
inotify is a Linux kernel feature that allows a process to watch files and directories for changes. When you register an inotify watch, you're asking the kernel to send your process a notification whenever a specific event occurs on a specific file — things like:
- A file was written to
- A file was closed after being written
- A file was deleted
- A file was moved
When a process creates an inotify instance, it shows up in /proc/<pid>/fd/ as a special file descriptor with the type anon_inode:inotify.
The inotify Technique as a General Tool
the technique for proving active file monitoring is broadly applicable. Here's the recipe:
Step 1: Identify the PID of the process that owns the file you're investigating.
ls -la /path/to/file # note the owner
ps aux | grep <process_name> # get the PIDStep 2: Check for inotify file descriptors.
ls -la /proc/<pid>/fd/ | grep inotifyStep 3: Read fdinfo for each inotify fd.
cat /proc/<pid>/fdinfo/<fd_number>Step 4: Decode the mask field.
The inotify constants you need are:
IN_ACCESS 0x00000001 File was accessed
IN_MODIFY 0x00000002 File was modified
IN_ATTRIB 0x00000004 Metadata changed
IN_CLOSE_WRITE 0x00000008 File closed after write
IN_CLOSE_NOWRITE 0x00000010 File closed without write
IN_OPEN 0x00000020 File was opened
IN_MOVED_FROM 0x00000040 File moved from watched dir
IN_MOVED_TO 0x00000080 File moved to watched dir
IN_CREATE 0x00000100 File created in watched dir
IN_DELETE 0x00000200 File deleted from watched dir
IN_DELETE_SELF 0x00000400 Watched file/dir deleted
IN_MOVE_SELF 0x00000800 Watched file/dir movedTake your mask value (e.g., 0xfc6), convert to binary, and check which bits are set against this table.
Step 5: Cross-reference the watched inode.
stat /path/to/suspected/file # look at the Inode fieldCompare against the ino: value in fdinfo. If they match, you've proven that specific file is under active watch.
Now applying these techniques to Replit system ::
Checking pid1's File Descriptors
Every open file, socket, pipe, and special kernel object that a process has open is listed in /proc/<pid>/fd/. For pid1, that's /proc/1/fd/.
python
import subprocess
result = subprocess.run(
["ls", "-la", "/proc/1/fd/"],
capture_output=True, text=True
)
print(result.stdout)Scanning the output for inotify instances:
[!!!] POTENTIAL WATCHER FOUND: fd 24 -> anon_inode:inotify
[!!!] POTENTIAL WATCHER FOUND: fd 30 -> anon_inode:inotify
[!!!] POTENTIAL WATCHER FOUND: fd 40 -> anon_inode:inotifypid1 has three active inotify instances. This tells us pid1 is definitely watching filesystem paths for changes at runtime — it's not reading files once and forgetting them. But three inotify fds watching some unknown set of paths isn't enough. I needed to know if any of them were watching flags.json specifically.
Decoding the inotify Watch: fdinfo
The file /proc/<pid>/fdinfo/<fd> contains detailed metadata about a specific file descriptor. For inotify fds, it lists every active watch — what inode is being watched and what events it's subscribed to.
import subprocess
result = subprocess.run(
["cat", "/proc/1/fdinfo/24"],
capture_output=True, text=True
)
print(result.stdout)Output:
pos: 0
flags: 02004000
mnt_id: 17
ino: 2071
inotify wd:1 ino:100 sdev:44e mask:fc6 ignored_mask:0 fhandle-bytes:14 fhandle-type:4d f_handle:0001000000000000010100000000000009000000Let me break down the relevant fields:
inotify wd:1 — Watch descriptor 1. This is the first watch registered on this inotify instance.
ino:100 — The inode number being watched, in hex. 0x100 = decimal 256. This identifies the specific file or directory on disk.
mask:fc6 — This is the event mask. It's a bitmask that tells us exactly which filesystem events pid1 has subscribed to for this watch.
Decoding mask:fc6
0xfc6 in binary: 1111 1100 0110
Mapped against Linux's inotify event constants from <sys/inotify.h>:
Mapped against Linux's inotify event constants from `<sys/inotify.h>`:
Mapped against Linux's inotify event constants from `<sys/inotify.h>`:
| Constant | Hex | In mask? | Meaning |
|----------|-----|----------|---------|
| `IN_MODIFY` | 0x002 | ✅ | File contents modified |
| `IN_ATTRIB` | 0x004 | ✅ | Metadata/permissions changed |
| `IN_CLOSE_WRITE` | 0x008 | ✅ | File closed after writing |
| `IN_MOVED_FROM` | 0x040 | ✅ | File moved away from watched dir |
| `IN_MOVED_TO` | 0x080 | ✅ | File moved into watched dir |
| `IN_CREATE` | 0x100 | ✅ | New file created |
| `IN_DELETE` | 0x200 | ✅ | File deleted |
| `IN_DELETE_SELF` | 0x400 | ✅ | The watched object itself deleted |
| `IN_MOVE_SELF` | 0x800 | ✅ | The watched object itself moved |This is a comprehensive watch covering every meaningful filesystem event. The IN_CLOSE_WRITE flag is particularly significant — it fires the moment you finish writing to a file and close it. That's exactly what happens when you write flags.json.
Finding the Watched Inode
The watch is on inode 0x100 (256). To confirm this maps to flags.json:
find /run/replit/pid1/ -exec stat --format="%i %n" {} \;Cross-referencing the inode numbers against the directory contents confirms the mapping. pid1 is watching its own configuration directory with a full-coverage event subscription.
The Proof of Concept
import json
import os
def demonstrate_supervisor_tampering():
target = "/run/replit/pid1/flags.json"
# Step 1: Read current state
with open(target, "r") as f:
config = json.load(f)
print(f"[+] Current supervisor config: {json.dumps(config, indent=2)}")
# Step 2: Modify a flag
original_value = config.get("NOISY_VNC", False)
config["NOISY_VNC"] = not original_value
# Step 3: Write back - this triggers pid1's inotify notification
with open(target, "w") as f:
json.dump(config, f, indent=2)
# Step 4: Verify persistence
with open(target, "r") as f:
verified = json.load(f)
print(f"[+] NOISY_VNC changed: {original_value} -> {verified['NOISY_VNC']}")
print(f"[+] Change persisted. pid1 received IN_CLOSE_WRITE notification.")
demonstrate_supervisor_tampering()Output:
[+] Current supervisor config: {
"ENABLE_PERIODIC_FLUSH": false,
"NOISY_VNC": false,
"PROFILE_EVERYTHING": false
}
[+] NOISY_VNC changed: False -> True
[+] Change persisted. pid1 received IN_CLOSE_WRITE notification.Acknowledgement
Replit's security team responded promptly and confirmed the core of the finding directly:
"The files in /run/replit are written by the actual supervisor that runs outside the sandbox. Writing files should probably be restricted inside the sandbox."
The finding closed without a bounty because the flags currently exposed don't do anything security-relevant — the feature has been dormant for over a year. The boundary violation exists today. The exploitable surface just hasn't grown into it yet.