Skip to main content
Read this before you build a project someone else wrote, and before you run DeckTalk as a service that renders other people’s decks.

What runs when you build a project

A build runs two kinds of code that come with the project. Every page section is a web page, and its script runs in the Chromium that records it. A render section names a program in render_command, and record starts it to draw the section’s picture. Both run as your user. On your own work that is what you want, and on someone else’s it means their code reaches what you can reach. Two machine keys decide how far each is trusted: record.page_policy for the pages and record.programs for the programs. A project file that names either is refused, so a project cannot trust itself, and both are set with decktalk config set KEY VALUE --scope machine.

A page someone else wrote

On your own machine a deck is your own work, so by default its page is trusted: it reaches the network the way it would in your browser, and a font or a library it loads from another origin is reported as PAGE_CDN_ASSET rather than stopped. A service that renders pages other people wrote cannot extend that trust, because a trusted page reaches anything the machine can reach, a cloud metadata endpoint included. record.page_policy says how far a page is trusted.
Under untrusted, three things hold at once.
  • The sandbox is on, or nothing runs. A Chromium that cannot start its sandbox is refused with a TOOL error. The run never falls back to a browser without one.
  • No request leaves the origin. The page is served at the project’s own origin, and a request for any other one is aborted before it reaches the network, whatever its scheme. The browser is also pointed at a proxy that answers nothing, and WebRTC may not send UDP past it, which closes the channels request routing never sees: a WebSocket, a DNS lookup and a WebRTC probe. The recording still names every host the page reached for, as PAGE_CDN_ASSET.
  • The browser environment is scrubbed. Chromium is handed only the variables that say where a file, a display, a locale or a proxy is, so a key the host holds never reaches the process that runs a page’s script. ffmpeg and ffprobe are handed the same set. This holds under trusted too.
The policy is read by every launch that opens a project’s page, which is record, check, storyboard, cue when a section’s recording no longer stands, and every picture assemble draws. A run that may spend never opens an untrusted page, because the API key is within its reach whether or not it finds anything to buy, so it is refused with APPROVAL before any browser starts. Voice in a run of its own with decktalk narrate --spend, then render in a run without --spend.

Programs a project names

A project can name programs as well as pages: a render section’s render_command draws its picture. A program runs with your user’s rights and can read every file you can, the project’s .env included. So cloning someone else’s deck and building it would run their code as you, and record.programs decides whether it does. untrusted refuses every program whatever programs says. A refusal comes before any program starts and, in a build, before the first stage. The build below passes --no-spend because a build with no terminal to ask is otherwise refused with APPROVAL for the narration it would buy.
DECKTALK_RECORD_PROGRAMS=run allows them for every command a job runs. Read the programs that PROGRAM_NAMED lists before you allow them. A program runs in an empty scratch folder rather than in the project, and a word of its command that names a project file by a relative path is handed over as that file’s absolute path. It gets the scrubbed environment with no proxy variable, because a proxy URL can carry a password. None of that keeps it from a file: DeckTalk hides no file from a program on any platform, .env included, so a machine that allows a project’s programs trusts that project’s author with every file the user can read.

The sandbox in a Linux container

Chromium’s sandbox needs user namespaces. In a Linux container that takes two things, and a container missing either one refuses an untrusted run.
  • A user other than root. Chromium refuses its sandbox to root, so run the render as an ordinary user.
  • A seccomp profile that allows user namespaces. Docker’s default profile blocks the calls the sandbox makes. A render running as an ordinary user under the default profile is still refused, and the same render under a profile that allows those calls launches.
The refusal names both:
The engine’s refusal is defence in depth. A service that renders strangers’ pages still runs each render in a network namespace with no egress of its own, because the machine is where the boundary belongs.