PATTERN Cited by 1 source
Refactor-for-seccomp-filter¶
Shape¶
When a program needs dynamic access to a sensitive syscall
(openat, connect, execve) during the processing of
untrusted input, a seccomp filter cannot selectively block
those calls — seccomp can only filter on
top-level argument values, not dereference pointers, so it
cannot distinguish "open /safe/path.svg" from "open
/etc/shadow". The resulting trade-off is binary: allow the
syscall (losing the guard) or deny it (breaking the program).
Pattern: rewrite the program so all dangerous syscalls happen in a preamble phase, before the user-input-processing phase; install a restrictive seccomp filter between the two phases; the processing phase then runs under a filter that denies the now-unneeded syscalls entirely.
┌────────────────────────────────┐
│ Preamble │ ← allowed to openat(), connect(), ...
│ open(input_file) │
│ open(output_file) │
│ open(shared_libs) │
│ load fonts, icons, assets │
└───────────────┬────────────────┘
▼
┌────────────────────────────────┐
│ prctl(PR_SET_SECCOMP, ...) │ ← install strict filter here
└───────────────┬────────────────┘
▼
┌────────────────────────────────┐
│ Processing phase │ ← denied: openat(), connect(), ...
│ parse user input │ allowed: write to open fds,
│ render image │ mmap, exit, clock_gettime
│ write output via open fd │
└────────────────────────────────┘
(Source: sources/2026-04-21-figma-server-side-sandboxing-containers-and-seccomp)
Canonical example: Figma RenderServer¶
RenderServer needed to process
user-supplied Figma files. The risky step is parsing the file,
which hands untrusted input to C++ libraries. Figma wanted a
seccomp filter denying openat for that step — but RenderServer
also wrote its own output via openat, so naive blocking
broke the program:
"Here is a basic example to illustrate the problem: Suppose we want to use RenderServer to help users export their Figma file as an SVG. In this scenario, the risky step that must be sandboxed is the processing of the Figma file, which might contain malicious input planted by an attacker who wants to hack Figma. [...] RenderServer will first finish generating the preview image before writing it to an output file. In case RenderServer becomes compromised by the image processing step, we want to apply a seccomp filter before image processing to prevent the compromised RenderServer process from doing malicious things such as opening and reading other sensitive files on the system. However, if we do that — by configuring our seccomp filter to prevent syscalls like
openat— RenderServer would stop working correctly because seccomp would also terminate it upon opening other files such as its designated output file."
The refactor:
"We refactored RenderServer to reorder all file opens so that they occur before any image processing happens on potentially dangerous user input — which was a lot of work. Ultimately, the refactor allowed us to add a restrictive seccomp filter via libseccomp to prevent RenderServer from doing anything else other than read from and write to only resources that it's allowed to access."
Consequences — observed at Figma¶
Benefits:
- ✅ Simpler sandbox: seccomp-only, no nsjail / namespaces / cgroups orchestration.
- ✅ Lower overhead: no per-request sandbox setup cost; runs "significantly faster".
- ✅ Easier to test and debug than the layered sandbox.
Constraints imposed by the pattern:
- ❌ Single-threaded: threads sharing the process all
come under the same filter at the same time; no runtime
can spawn workers that need
openatafter lockdown. - ❌ No dynamic resource loading: "we can't dynamically load fonts or images later in the runtime." All assets have to be in the preamble.
- ❌ High upfront engineering cost: "which was a lot of work" — every codepath that touches a denied syscall needs to move.
- ❌ Brittle to future features: adding a feature that
needs
openatlater in the pipeline means re-plumbing through the preamble or accepting a weaker filter.
When to use¶
- Program is source-modifiable (you own the code or its fork).
- Program's dangerous-syscall use is enumerable — finite set of paths / endpoints known at preamble time.
- Performance + simplicity are worth the single-threaded constraint.
- Sandbox is the only barrier against the exploited process — nsjail composition would be the alternative if extra isolation axes (network, filesystem view) are also needed.
When not to use¶
- Commodity programs you don't own the source of.
- Interactive / server programs whose dangerous-syscall use is user-driven (each new request wants to open a different file).
- Multi-threaded workloads where forcing single-threaded is unacceptable.
Related patterns¶
- patterns/seccomp-bpf-container-composition — the alternative when you cannot or do not want to restructure the program. nsjail / firejail stack namespaces + cgroups
- seccomp and let the filter be coarser because other layers also restrict reach.
- The filter + refactor is a narrow-but-sharp knife; the layered-composition is a broader-but-duller one.
Seen in¶
- sources/2026-04-21-figma-server-side-sandboxing-containers-and-seccomp — canonical example. Figma's RenderServer refactor is the worked instance; the post frames it as a necessary consequence of seccomp's pointer-dereference limit.