Skip to main content

Command Palette

Search for a command to run...

Dispatches from Below the Shell

Updated
•9 min read•View as Markdown
Dispatches from Below the Shell

What happens when you stop running commands and start reading the filesystem like a system detective.


Finding 01 — DNS is a three-layer negotiation, not a single config file

Networking | /etc/resolv.conf → /etc/nsswitch.conf → /etc/hosts

Most people think DNS resolution means one config file. In Linux, it's actually an ordered pipeline controlled by /etc/nsswitch.conf, a name service switch file that nobody talks about. The line hosts: files dns inside it means: check /etc/hosts first, then hit the nameserver in /etc/resolv.conf. This file decides the order of authority, not just the mechanism.

This container points at 8.8.8.8 (Google's DNS) and its /etc/hosts maps 127.0.0.1 to both localhost and runsc, a container-injected hostname that reveals we're running inside Google's gVisor runtime.

Why it matters: Any attacker who edits /etc/hosts can silently redirect traffic before DNS is ever consulted. The order in nsswitch.conf is the security boundary most admins forget exists.


Finding 02 — The routing table lives in a file, and it's written in hex

Networking | /proc/net/route

The kernel's routing table is exposed as a plain text file. The catch: every IP address and flag is encoded in little-endian hexadecimal. The entry E5000415 decoded as a gateway resolves to 21.4.0.229, the actual default gateway this machine uses to reach the internet.

Flag 0003 means two bits are set: "route is up" and "gateway required." Flag 0001 alone means "route is up, no gateway", a direct network route. You can read the entire network topology of a machine without running a single networking tool, just a file read and some Python to decode the hex.

Why it matters: On a compromised system where networking tools have been removed or altered, /proc/net/route remains. It's a read-only window into kernel state that can't be faked by userspace malware.


Finding 03 — /proc/self — the filesystem that knows it's you

Kernel | /proc/self/fd · /proc/self/maps · /proc/self/limits

/proc/self is one of Linux's most elegant design choices: a symlink that automatically points to the current process's own /proc/PID directory. Reading /proc/self/fd revealed that this shell's stdin, stdout, and stderr are all pipes, not a terminal, confirming we're running in a non-interactive subprocess context.

/proc/self/maps exposed the full virtual memory layout: where cat was loaded, where libc lives, the heap address range, the stack. Every segment is labeled with its permissions (r-xp for executable, rw-p for data). This is what debuggers and exploit tools read to understand a process's address space.

/proc/self/limits showed the process can open at most 20,000 files, a hard cap set by the container orchestrator, not the kernel defaults.

Why it matters: Everything a debugger does, attaching, reading memory, checking file descriptors, is essentially reading /proc/PID. The "filesystem" abstraction isn't just for storage; it's Linux's universal API for introspection.


Finding 04 — PID 1 is not init, and that tells us everything about the container

Container | /proc/1/cmdline · /proc/1/ns

On a normal Linux machine, PID 1 is systemd or init, the mother of all processes. Here, PID 1 is /process_api, a custom binary listening on 0.0.0.0:2024 with explicit flags like --oom-polling-period-ms 100 and --memory-limit-bytes 4294967296 (exactly 4GB).

This is the Anthropic sandbox orchestrator that spawned this shell. It's manually implementing OOM (out of memory) handling that the kernel normally does automatically, polling memory every 100ms and enforcing a 4GB ceiling itself, rather than relying on the kernel's OOM killer.

The namespace symlinks in /proc/1/ns/ show six isolated namespaces: ipc, mnt, net, pid, user, and uts. These are the six pillars of Linux container isolation. Each symlink pointing to a different inode number confirms the container has its own independent instance of each namespace.

Why it matters: Containers aren't magic, they're just processes with namespaces. The entire Docker/Kubernetes ecosystem is built on these six kernel features, and you can see them all from a single directory listing.


Finding 05 — /dev/stdin is lying to you, it's actually in /proc

Kernel | /dev/stdin → /proc/self/fd/0

The file /dev/stdin looks like a device. It isn't. It's a symlink to /proc/self/fd/0, meaning "file descriptor 0 of whatever process is asking." Same for stdout and stderr. These "device files" dynamically refer to the calling process's own file descriptors. Read /dev/stdin from two different programs simultaneously and you get two different streams.

Meanwhile, /dev/null, /dev/zero, and /dev/urandom are real character devices (note the c in their permissions: crw-rw-rw-). They're not files at all, they're kernel drivers with major:minor device numbers. /dev/null is major 1, minor 3. /dev/zero is 1:5. Reading them calls a kernel function; writing to them discards data into the kernel.

Why it matters: Linux's "everything is a file" philosophy runs deeper than most realize. Devices, processes, network connections, and kernel state all present as files, but some are kernel drivers, some are symlinks, and some are virtual filesystems. The abstraction is consistent; the implementation is radically different.


Finding 06 — The shadow file separates authentication from identity, on purpose

Security | /etc/passwd · /etc/shadow

/etc/passwd is world-readable. Every user can see every user account. But the passwords aren't there — they were moved to /etc/shadow decades ago, which is readable only by root and the shadow group. The x in the second field of each passwd entry means "go look in shadow."

What's striking about the shadow entries here: root:*:20553:0:99999:7:::, the * means root cannot log in with a password at all. The account exists but is locked for password auth. The number 20553 is the last password change date in days since the Unix epoch (Jan 1 1970), which puts it in early 2026.

The SUID binaries reveal the trust model: passwd, su, mount, newgrp. These are the small set of programs that need to briefly run as root on behalf of regular users. The SUID bit is essentially a controlled privilege escalation mechanism baked into the filesystem itself.

Why it matters: Every SUID binary is a potential privilege escalation vector. Historically, bugs in su, passwd, or even mount have led to full root compromise. The shorter this list, the smaller the attack surface.


Finding 07 — cgroups turn kernel resource limits into a filesystem you can write to

Container | /sys/fs/cgroup/memory · /proc/self/cgroup

Reading /proc/self/cgroup reveals the full container identity: container_01R1k4CsJU7isY8idrNdSQRr--wiggle--c51dd5. The name "wiggle" is presumably an internal Anthropic codename for this sandbox tier. The process is nested under multiple cgroup hierarchies: cpu, cpuacct, cpuset, devices, memory, pids, each enforcing different resource limits.

The memory cgroup exposes its limit at /sys/fs/cgroup/memory/memory.limit_in_bytes. The value there is 9223372036854771807, which is essentially int64_max, meaning the top-level cgroup has no memory limit. But /proc/1/cmdline showed the process_api is enforcing 4GB itself in userspace. The orchestrator is doing its own enforcement above the kernel's cgroup layer.

Why it matters: cgroups expose kernel resource accounting as writable files, you can set CPU shares, memory limits, and process counts by literally writing a number to a file. Kubernetes, Docker, and systemd all ultimately reduce to writing these files.


Finding 08 — The dynamic linker has its own config file, and it determines what code runs

Security | /etc/ld.so.conf · /etc/ld.so.conf.d/

When any dynamically linked program runs, before its first line of code executes, the Linux dynamic linker (ld-linux-x86-64.so.2) reads /etc/ld.so.conf to find shared libraries. This file just says include /etc/ld.so.conf.d/*.conf, a drop-in directory pattern that lets packages add their own library paths on install.

The /proc/self/maps output confirmed this in action: the memory map showed libc.so.6 loaded at address 0x7ed1fb000000, with three segments, read-only metadata, executable code (r-xp), and read-write data. The linker mapped shared library sections independently with different permissions. Executable memory is never writable, that's a hardware-enforced security boundary visible right here in the maps file.

Why it matters: If an attacker can write to /etc/ld.so.conf.d/, they can inject a malicious library that gets loaded into every dynamically linked program on the system. It's one of the most powerful and least-discussed persistence mechanisms on Linux.


Finding 09 — The root filesystem isn't a real disk, it's a 9P network protocol mount

Container | /proc/mounts · /proc/self/mountinfo

The most surprising finding: the root filesystem (/) is mounted via 9p, a network filesystem protocol originally developed for Plan 9. The mount options show trans=fd,rfdno=4,wfdno=4, meaning the filesystem communicates over file descriptors 4 (read) and 4 (write), a bidirectional pipe to the host's gVisor kernel.

This is not a normal disk. Every file read goes through a userspace kernel (gVisor/runsc), which intercepts syscalls and translates them. The cache=remote_revalidating option means file metadata is cached but always revalidated against the remote, like NFS. The system is running in a sandbox-within-a-sandbox: Linux namespaces inside gVisor inside a VM.

Skills directories (/mnt/skills/public) and user data directories (/mnt/user-data) are separate 9P mounts, each with different permissions, uploads are read-only, outputs are read-write.

Why it matters: The mount table is a complete map of what a system trusts and how. Read-only mounts, network mounts, tmpfs, each tells a security story. A system with its /tmp mounted noexec is meaningfully harder to exploit than one without it.


Finding 10 — 302 certificates and the invisible trust infrastructure

Security | /etc/ssl/certs/ · /proc/environ

There are 302 CA certificate files in /etc/ssl/certs/, each one a root of trust that your system will believe unconditionally when it comes to TLS. These are the organizations whose word the OS accepts that a website is who it claims to be. They span multiple continents; include government CAs, corporate CAs, and independent ones.

But the environment tells a more targeted story: three environment variables, REQUESTS_CA_BUNDLE, SSL_CERT_FILE, and NODE_EXTRA_CA_CERTS, all point to the same consolidated bundle at /etc/ssl/certs/ca-certificates.crt. This means Python's requests library, OpenSSL, and Node.js all use the same trust store, forced by environment configuration in /etc/profile.d/ and baked into the container image.

Why it matters: Corporate environments often inject their own CA into this directory to intercept TLS traffic (SSL inspection). If you're on a managed device, one of those 302 certificates might be your employer's, and they can read every "encrypted" connection your machine makes.


Closing Note

The Linux filesystem is not storage. It's an operating philosophy: expose everything as a file, make the kernel interrogable, let processes see themselves. A system investigator who can read /proc fluently doesn't need most userspace tools, the ground truth is always one file read away.