Cybersecurity for AI Systems from First PrinciplesPost 2 of 19 · 19 of 19 written
- How to use this guide
- 01What security actually is
- 02How computers fail
- 03Networks
- 04Cryptography
- 05Identity and access
- 06Web and application security
- 07Cloud, infrastructure, supply chain, detection
- 08The bridge: why AI systems are different
- 09Adversarial machine learning
- 10LLM and generative AI security
- 11Agentic systems
- 12The ML supply chain and MLOps
- 13Privacy, governance and regulation
- 14Red teaming, evaluation, and AI for defense
- 15Worked example: threat-modeling an AI agent
- 16The question toolkit
- 17Learning path, practice, reading list
- 18Glossary
- 19Sources
2. How computers fail
To a computer, instructions and data are both just bytes; the difference exists only by convention. Most low-level attacks happen when an attacker makes the system cross that convention - data gets executed, or reads and writes go where they shouldn’t.
The root cause: code and data share one medium
In the von Neumann design, programs and data live in the same memory. Above the hardware, the same pattern repeats: software is full of interpreters. A SQL engine, a shell, an HTML renderer, a template engine, a YAML loader, Python’s pickle, and an LLM are all interpreters. Each one treats some of its input as commands.
Injection is the general name for attacker-controlled data reaching an interpreter in a position where it is treated as code. The general cure is structural: keep code and data in separate channels. Parameterized SQL queries do this - the query shape is sent separately, and user values can never become SQL syntax. Escaping strings is a weaker, error-prone substitute.
Hold on to this idea. Part 10 shows that LLMs have no separate channel for instructions and data, which is why prompt injection is so hard.
Memory safety
A running program keeps its memory in regions. The stack holds function calls, local variables, and return addresses (where to continue after a function ends). The heap holds data allocated while the program runs.
In C and C++, the language does not check that a write stays inside the buffer it targets. A buffer overflow writes past the end of a buffer and overwrites whatever sits next to it. If that neighbour is a return address or a function pointer, an attacker can redirect the program’s execution. Related bug classes:
- Out-of-bounds read - reading past a buffer. Heartbleed (2014) let anyone read chunks of server memory, including private keys.
- Use-after-free - memory is freed, then used again after the attacker has placed their own data there.
- Integer overflow - a size calculation wraps around, so a buffer is allocated too small.
Operating systems add mitigations: ASLR randomizes where things sit in memory, non-executable memory stops data regions from running as code, and stack canaries detect overwritten return addresses. These raise attacker cost; they do not remove the bug. The real fix is memory-safe languages (Rust, Go, Java, Python). Microsoft and the Chromium project have both reported that roughly 70% of their serious security bugs were memory-safety bugs (approximate figures, from public statements around 2019–2020).
AI link. Your Python is memory safe; the ML stack under it is not. PyTorch internals, CUDA kernels, tokenizers, image and video decoders, and C++ inference servers all parse input in native code. When an untrusted image, audio file or model file enters your pipeline, it enters C and C++ parsers.
Processes, privilege and isolation
The operating system kernel controls hardware and memory. Programs run in user mode and must ask the kernel for anything privileged through system calls; that interface is a major trust boundary. Each process gets its own virtual memory, so one process cannot normally read another’s. Each runs as a user with permissions; root (or Administrator) can do anything.
Privilege escalation means gaining more rights than granted. Vertical escalation moves to a higher privilege level (user to root). Horizontal escalation moves sideways to another account at the same level (your account to someone else’s).
Isolation comes in strengths. Pick the strength that matches how untrusted the code is:
| Boundary | What separates workloads | Main weakness |
|---|---|---|
| Process | Separate virtual memory, user permissions | Same kernel, same file system, often same network |
| Container | Kernel namespaces and cgroups | Shares the host kernel; a kernel bug can escape |
| Sandboxed runtime (e.g. gVisor) | An extra layer intercepting system calls | Smaller kernel attack surface, some overhead |
| Virtual machine / microVM (e.g. Firecracker) | Hardware virtualization, separate kernel | Hypervisor bugs are rare but possible |
| Separate machine | Physical separation | Cost; network paths between them still matter |
AI link. If an LLM can run code (a “code interpreter” tool), that code is untrusted, no matter how it was written. Run it in a microVM or gVisor-class sandbox, with no network by default, CPU, memory and time limits, and no access to secrets. Never run it in the same container that holds your API keys.
Parsing: the attack surface nobody draws
Every file-format parser is a small interpreter for attacker-controlled input. Classic failures:
- XML external entities (XXE) - an XML file instructs the parser to read local files or make network requests.
- Path traversal / Zip Slip - an archive entry named
../../etc/...writes outside the target folder. - Decompression bombs - a small file expands to gigabytes and exhausts memory.
- Unsafe deserialization - formats that can describe arbitrary objects (YAML’s unsafe loaders, Java serialization, Python pickle) can run code while loading. For pickle this is by design, not a bug.
The discipline known as LangSec treats input as a formal language: define precisely what valid input looks like, parse it completely and strictly at the boundary, and only then act on it (“parse, don’t validate”). Two practical rules follow. Prefer allowlists (accept known-good) over blocklists (reject known-bad), because attackers find what you forgot to block. And canonicalize before checking: decode URLs, resolve paths and normalize Unicode first, or encoded tricks slip past your check.
AI link. RAG ingestion parses PDFs, HTML and Office files from untrusted sources. Model loading parses weight files. Data pipelines parse scraped content. Each is a parser exposed to attackers.
Time and races
TOCTOU (time-of-check to time-of-use): the system checks something, then uses it later, and the attacker changes it in between. A file is checked as safe, then replaced before it is opened. In AI systems: an agent approves a URL or a tool definition, and its content changes before or between uses (Part 11 calls this a “rug pull”).
Questions to ask
- Where does untrusted data reach an interpreter: SQL, shell, template, deserializer, or model?
- Are code and data separated structurally, or only by escaping strings?
- Which native-code parsers touch untrusted input in this pipeline?
- What privileges does this process run with, and what could an attacker reach from it?
- What isolation boundary separates untrusted code from secrets and the network?
- Is anything checked at one time and used at another?