<?xml version="1.0" encoding="UTF-8"?>
<rss version="2.0" xmlns:atom="http://www.w3.org/2005/Atom" xmlns:dc="http://purl.org/dc/elements/1.1/">
  <channel>
    <title>DEV Community: Toru Ikeda</title>
    <description>The latest articles on DEV Community by Toru Ikeda (@torudev).</description>
    <link>https://dev.to/torudev</link>
    <image>
      <url>https://media2.dev.to/dynamic/image/width=90,height=90,fit=cover,gravity=auto,format=auto/https:%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Fuser%2Fprofile_image%2F4084967%2Ffe402a96-5e2a-4ed9-992c-ea6ff246d28d.jpg</url>
      <title>DEV Community: Toru Ikeda</title>
      <link>https://dev.to/torudev</link>
    </image>
    <atom:link rel="self" type="application/rss+xml" href="https://dev.to/feed/torudev"/>
    <language>en</language>
    <item>
      <title>Can You Say What Your AI-Written Tasks Can Access Before You Run Them?</title>
      <dc:creator>Toru Ikeda</dc:creator>
      <pubDate>Tue, 03 Nov 2026 14:51:32 +0000</pubDate>
      <link>https://dev.to/torudev/can-you-say-what-your-ai-written-tasks-can-access-before-you-run-them-2lfj</link>
      <guid>https://dev.to/torudev/can-you-say-what-your-ai-written-tasks-can-access-before-you-run-them-2lfj</guid>
      <description>&lt;p&gt;Ask a coding agent to add a task, and the diff fills up with shell commands. Few people review them down to what &lt;code&gt;npm install&lt;/code&gt; runs underneath.&lt;/p&gt;

&lt;p&gt;What can those commands overwrite, and what can they talk to? I wanted a machine to answer that before anything runs.&lt;/p&gt;

&lt;p&gt;I build &lt;a href="https://github.com/lask-task-runner/lask" rel="noopener noreferrer"&gt;Lask&lt;/a&gt;, a task runner where you write tasks as typed functions in a small language, and every command runs in a container of the Docker image you declared for it.&lt;/p&gt;

&lt;p&gt;Lask 0.15.0 adds &lt;code&gt;within&lt;/code&gt;: you declare which files and networks commands may access, and the type checker verifies it statically.&lt;/p&gt;

&lt;p&gt;This post has two parts. The first part steps away from Lask and summarizes the recent discussion about guardrails for generative AI. I tried to keep it objective, and I mark where I give my own opinion. The second part shows how I implemented those ideas in Lask, so it is mostly my own take.&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;&lt;strong&gt;TL;DR&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Part 1 (general)&lt;/strong&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Reading code was never a strong defense against malicious code, and with AI producing more code, review is falling further behind.&lt;/li&gt;
&lt;li&gt;One response is to have AI prove its code correct. But proofs need a specification written by a human, which doesn't fit things like the install scripts &lt;code&gt;npm install&lt;/code&gt; runs.&lt;/li&gt;
&lt;li&gt;Another response, with growing research behind it, is to verify the boundary around the model, which holds whatever the model outputs.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;strong&gt;Part 2 (Lask)&lt;/strong&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Lask's &lt;code&gt;within&lt;/code&gt; implements that second idea as type checking in a task runner. Commands that might exceed the declared scope fail &lt;code&gt;lask check&lt;/code&gt; before anything runs.&lt;/li&gt;
&lt;li&gt;I suggest a split: a person checks the access scope with &lt;code&gt;lask envs access&lt;/code&gt; before running, and &lt;code&gt;lask check&lt;/code&gt; verifies that every command stays within it. This is static checking, not a machine-checked proof, and there are things it doesn't cover. They're listed near the end.&lt;/li&gt;
&lt;/ul&gt;
&lt;/blockquote&gt;

&lt;h2&gt;
  
  
  Part 1: The debate over AI guardrails
&lt;/h2&gt;

&lt;h3&gt;
  
  
  Reading code alone never stopped it
&lt;/h3&gt;

&lt;p&gt;Even when people wrote all the code, finding malicious code by reading it was hard.&lt;/p&gt;

&lt;p&gt;The xz Utils backdoor, found in March 2024, wasn't caught by reading code (&lt;a href="https://en.wikipedia.org/wiki/XZ_Utils_backdoor" rel="noopener noreferrer"&gt;XZ Utils backdoor&lt;/a&gt;). Andres Freund, a PostgreSQL developer, traced it while investigating unusually high CPU usage during SSH logins.&lt;/p&gt;

&lt;p&gt;Dependency code often isn't read at all. Imtiaz and Williams studied updates to popular packages in four package registries (&lt;a href="https://arxiv.org/abs/2206.09422" rel="noopener noreferrer"&gt;arXiv:2206.09422&lt;/a&gt;). 52.5% of updates were only partially code-reviewed, and only 9.0% of packages had every update fully reviewed.&lt;/p&gt;

&lt;p&gt;The report from the S3C2 industry summit on supply chain security reaches the same view (&lt;a href="https://arxiv.org/abs/2505.10538" rel="noopener noreferrer"&gt;arXiv:2505.10538&lt;/a&gt;). Since there is no reliable automated way to detect malicious commits, teams rely on human review, but human review is error-prone and not always reliable.&lt;/p&gt;

&lt;h3&gt;
  
  
  AI is outpacing review
&lt;/h3&gt;

&lt;p&gt;With AI writing code, there's more for people to read.&lt;/p&gt;

&lt;p&gt;Chen and Stratton at Harvard analyzed development data from 655 firms (&lt;a href="https://fion.ac/jellyfish.pdf" rel="noopener noreferrer"&gt;Artificial Intelligence in the Firm&lt;/a&gt;). It's a working paper estimating how AI coding tools affect development, and it hasn't been peer-reviewed. After firms adopted AI agents, lines of code added rose 44.0% and pull requests opened rose 35.7%. Meanwhile, the average time from opening a pull request to merging it grew 56.1%. The authors conclude that code review became the bottleneck.&lt;/p&gt;

&lt;p&gt;He et al. followed 802 developers at a company that mandated doubling output with AI (&lt;a href="https://arxiv.org/abs/2607.01904" rel="noopener noreferrer"&gt;arXiv:2607.01904&lt;/a&gt;). Load per reviewer roughly doubled.&lt;/p&gt;

&lt;p&gt;Reading code was already a weak defense against malicious code, and AI leaves even less room to read. So I think we need checks that a machine can do without a person reading the code.&lt;/p&gt;

&lt;h3&gt;
  
  
  Proofs need a human-written spec
&lt;/h3&gt;

&lt;p&gt;In December 2025, Martin Kleppmann predicted that AI will make formal verification mainstream (&lt;a href="https://martin.kleppmann.com/2025/12/08/ai-formal-verification.html" rel="noopener noreferrer"&gt;AI will make formal verification go mainstream&lt;/a&gt;). Formal verification proves mathematically that a program meets its specification. His argument: AI makes writing proofs much cheaper, so instead of having people review AI-written code, have the AI prove it correct.&lt;/p&gt;

&lt;p&gt;With this approach, it doesn't matter if the AI writes a bad proof. The proof checker rejects it and makes the AI try again.&lt;/p&gt;

&lt;p&gt;But in the same post, Kleppmann notes that reading and writing formal specifications still takes expertise and careful thought. A proof only guarantees that the code meets the spec, and a person writes the spec.&lt;/p&gt;

&lt;h3&gt;
  
  
  Install scripts have no spec
&lt;/h3&gt;

&lt;p&gt;I think proof-based verification is hard to apply to tasks like builds and installs. &lt;code&gt;npm install&lt;/code&gt; runs install scripts written by other people, one for each dependency that has one. Nowhere is there a spec for what those scripts should do.&lt;/p&gt;

&lt;p&gt;This year, those install scripts were exactly what attackers used on npm. Palo Alto Networks' Unit 42 updated its overview of attacks on npm on July 15, 2026 (&lt;a href="https://unit42.paloaltonetworks.com/monitoring-npm-supply-chain-attacks/" rel="noopener noreferrer"&gt;The npm Threat Landscape&lt;/a&gt;). It says many compromises rely on preinstall or postinstall hooks to exfiltrate secrets.&lt;/p&gt;

&lt;p&gt;According to the same article, in the May TanStack incident, code injected into CI harvested &lt;code&gt;NPM_TOKEN&lt;/code&gt; and &lt;code&gt;GITHUB_TOKEN&lt;/code&gt;. In April, a self-propagating worm was reported that used stolen npm tokens to publish versions carrying malicious code (&lt;a href="https://thehackernews.com/2026/04/self-propagating-supply-chain-worm.html" rel="noopener noreferrer"&gt;The Hacker News&lt;/a&gt;).&lt;/p&gt;

&lt;p&gt;The common advice is &lt;code&gt;ignore-scripts&lt;/code&gt;, which stops install scripts. But some packages need their scripts, so stopping all of them isn't always practical.&lt;/p&gt;

&lt;h3&gt;
  
  
  Verify the boundary outside the model
&lt;/h3&gt;

&lt;p&gt;Instead of checking that AI output is correct, a growing body of research checks the boundary that AI output can't cross, whatever it is.&lt;/p&gt;

&lt;p&gt;Moon and Varshney's Containment Verification (&lt;a href="https://arxiv.org/abs/2605.09045" rel="noopener noreferrer"&gt;arXiv:2605.09045&lt;/a&gt;) assumes nothing about how the AI behaves. It treats the AI as able to emit any value within the types the framework accepts. It then proves in Dafny that the agent framework enforces its boundary policy for every such value. The authors say the guarantee doesn't depend on alignment, that is, on whether the model has been tuned to behave as intended.&lt;/p&gt;

&lt;p&gt;Google DeepMind's CaMeL (&lt;a href="https://arxiv.org/abs/2503.18813" rel="noopener noreferrer"&gt;arXiv:2503.18813&lt;/a&gt;) also leaves the model untouched. It places an interpreter outside the model that records where each value came from and checks those records against a policy before any tool call, to defend against prompt injection.&lt;/p&gt;

&lt;p&gt;Anthropic's Agent SDK documentation draws the same line between permission checks and isolation (&lt;a href="https://code.claude.com/docs/en/agent-sdk/secure-deployment" rel="noopener noreferrer"&gt;Securely deploying AI agents&lt;/a&gt;). Of the mechanism that parses commands and matches them against permission rules, it says: "This is a permission gate, not a sandbox."&lt;/p&gt;

&lt;h3&gt;
  
  
  Three layers against risky operations
&lt;/h3&gt;

&lt;p&gt;Putting this together, the mechanisms that guard against an agent's risky operations fall into three layers. Each works at a different time and checks something different. Table 1 lists them in the order they act.&lt;/p&gt;

&lt;p&gt;Table 1. Three layers against risky operations&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Layer&lt;/th&gt;
&lt;th&gt;What it checks&lt;/th&gt;
&lt;th&gt;When&lt;/th&gt;
&lt;th&gt;If it gets it wrong&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;Static boundary check&lt;/td&gt;
&lt;td&gt;Whether the isolation mechanism and its settings keep to the decided scope&lt;/td&gt;
&lt;td&gt;Before running&lt;/td&gt;
&lt;td&gt;It runs with isolation that doesn't keep to the scope&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Permission check&lt;/td&gt;
&lt;td&gt;Whether this operation may run&lt;/td&gt;
&lt;td&gt;Right before running&lt;/td&gt;
&lt;td&gt;The operation runs&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Runtime isolation&lt;/td&gt;
&lt;td&gt;What a running operation can access&lt;/td&gt;
&lt;td&gt;While running&lt;/td&gt;
&lt;td&gt;It accesses more than intended&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;A static boundary check examines the isolation mechanism, such as a container or sandbox, and its settings before anything runs. Containment Verification, from the previous section, proves that the framework enforces its boundary policy whatever values the AI emits. It checks the isolation, not the AI's output, so the result doesn't change whatever the AI writes.&lt;/p&gt;

&lt;p&gt;A permission check decides, right before running, whether an operation runs. Permission rules and confirmation prompts belong here. If the check gets it wrong, the operation simply runs.&lt;/p&gt;

&lt;p&gt;Runtime isolation has no say in whether something runs. The operation runs, but it can only access what's inside the isolation. Even a risky operation that got past the permission check is contained. But if the isolation is configured too broadly by mistake, it can access more than intended. Without a static check, nobody usually verifies those settings until something runs.&lt;/p&gt;

&lt;p&gt;The three layers are meant to be stacked. The static check catches mistakes in the isolation settings before anything runs, and isolation contains risky operations that got past the permission check.&lt;/p&gt;

&lt;h2&gt;
  
  
  Part 2: Declaring access scope in Lask and checking it before running
&lt;/h2&gt;

&lt;p&gt;From here on, it's about Lask. I implemented the static boundary check, the first layer in Table 1, as type checking in a task runner.&lt;/p&gt;

&lt;h3&gt;
  
  
  Narrowing the mount with base_access
&lt;/h3&gt;

&lt;p&gt;Lask has always run every command in a container. The host's home directory and &lt;code&gt;~/.ssh&lt;/code&gt; aren't visible from inside.&lt;/p&gt;

&lt;p&gt;But it mounted the repository directory read-write at &lt;code&gt;/work&lt;/code&gt;. An install script could overwrite source files, read &lt;code&gt;.env&lt;/code&gt;, and talk to the outside world.&lt;/p&gt;

&lt;p&gt;With &lt;code&gt;base_access&lt;/code&gt;, you can narrow that mount path by path. The example below makes the whole repository read-only, lets only &lt;code&gt;dist&lt;/code&gt; be written, and makes &lt;code&gt;.env&lt;/code&gt; appear empty. &lt;code&gt;network: "none"&lt;/code&gt; also cuts off outside access.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;offline = #node:24.21.0-alpine3.24{base_access: { ".": "ro", "dist": "rw", ".env": "none" }, network: "none"}
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;In a container with these settings, I tried writing to the source, reading &lt;code&gt;.env&lt;/code&gt;, and reaching the npm registry.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;$ touch src/x.js
touch: src/x.js: Read-only file system

$ wc -c .env
0 .env

$ node -e "fetch('https://registry.npmjs.org')..."
TypeError: fetch failed
  [cause]: Error: getaddrinfo EAI_AGAIN registry.npmjs.org
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;code&gt;.env&lt;/code&gt; is 11 bytes on the host but 0 bytes inside the container. I checked this with Lask 0.15.0, Docker Engine 29.5.3, and macOS 26.6.2 on Apple Silicon.&lt;/p&gt;

&lt;p&gt;So far, you could do the same by assembling Docker's &lt;code&gt;--mount&lt;/code&gt; and &lt;code&gt;--network none&lt;/code&gt; yourself. The difference is the check in the next section.&lt;/p&gt;

&lt;h3&gt;
  
  
  Exceed the scope, fail before running
&lt;/h3&gt;

&lt;p&gt;&lt;code&gt;within&lt;/code&gt; declares what a module or function may access. Written at the top of a module, it covers every declaration in the module.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;// main.lask
within { files: { ".": "ro", "dist": "rw", ".env": "none" }, network: "none", volumes: false, publish: false }

offline = #node:24.21.0-alpine3.24{base_access: { ".": "ro", "dist": "rw", ".env": "none" }, network: "none"}

// Builds into dist without any network.
build(): String = do {
  $[offline] npm run build
  read_file("dist/manifest.json", offline)
}
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;It can restrict four things:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;code&gt;files&lt;/code&gt;: what can be read or written, path by path, inside the repository&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;volumes&lt;/code&gt;: whether host paths outside the repository can be mounted&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;network&lt;/code&gt;: whether the container can talk to the outside&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;publish&lt;/code&gt;: whether the outside can connect to the container&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;code&gt;lask check&lt;/code&gt; follows every place in the module that runs a command, and verifies that the settings of the container running it stay within the declaration.&lt;/p&gt;

&lt;p&gt;Now suppose an agent adds a task that uploads &lt;code&gt;dist&lt;/code&gt; to a preview server. It needs the network, so the agent also adds &lt;code&gt;within { network: "default" }&lt;/code&gt; to the function.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;// main.lask (added)
// Uploads dist to the preview server.
upload() within { network: "default" } = do {
  online = #node:24.21.0-alpine3.24{base_access: { ".": "ro", "dist": "rw", ".env": "none" }}
  $[online] npx -y some-uploader dist
}
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Here's what &lt;code&gt;lask check&lt;/code&gt; says:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;$ lask check
main.lask:14:3-14:38: E-TYPE-WITHIN-EXCEEDED [static]: the network is "default", which within { files: { ".": "ro", ".env": "none", "dist": "rw" }, volumes: false, network: "none", publish: false } of the module does not allow (network)
  note: the constraint is at main.lask:1
  note: reached through upload, at main.lask:14
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The module's &lt;code&gt;within&lt;/code&gt; acts as a ceiling, so a function's &lt;code&gt;within&lt;/code&gt; can't widen it. A function's constraint is checked together with the module's, and both must hold.&lt;/p&gt;

&lt;p&gt;&lt;code&gt;lask run&lt;/code&gt; runs the same check first, so not a single task in this module runs.&lt;/p&gt;

&lt;h3&gt;
  
  
  Unprovable settings fail too
&lt;/h3&gt;

&lt;p&gt;If a network setting is read from an environment variable, its value isn't known until the program runs. Lask treats that as "can't be shown to hold" and reports an error.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;within { network: "none" }

probe(): String =
  $[runnable(#node:24.21.0-alpine3.24, network = get_env("NET"))] node --version
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;





&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;$ lask check
p.lask:4:3-4:81: E-TYPE-WITHIN-UNPROVEN [static]: 'network' is not known here without running the program, so within { network: "none" } of the module cannot be shown to hold
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;code&gt;E-TYPE-WITHIN-EXCEEDED&lt;/code&gt; means the scope is shown to be exceeded. &lt;code&gt;E-TYPE-WITHIN-UNPROVEN&lt;/code&gt; means it couldn't be shown not to be. To pass, settings that affect the scope must be literals or names bound to literals. That's less convenient, but once you declare a constraint, I decided only what is shown to stay within it should pass.&lt;/p&gt;

&lt;p&gt;When a function takes an image and its settings as an argument (an "environment" in Lask), the check happens at each call site. For a function you invoke directly with &lt;code&gt;lask run&lt;/code&gt;, it uses the arguments from the command line and checks before running.&lt;/p&gt;

&lt;p&gt;&lt;code&gt;#local&lt;/code&gt;, which runs commands directly on the host, can access the whole host, so it satisfies no constraint.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;l.lask:3:20-3:38: E-TYPE-WITHIN-EXCEEDED [static]: #local reaches the whole host, which within { network: "none" } of the module does not allow (network)
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  Separate tasks that need the network
&lt;/h3&gt;

&lt;p&gt;Installing dependencies needs the network. So I put installation in a separate module that allows network access but can only write &lt;code&gt;node_modules&lt;/code&gt;.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;// deps.lask
within { files: { ".": "ro", "node_modules": "rw", ".env": "none" }, volumes: false, publish: false }

deps = #node:24.21.0-alpine3.24{base_access: { ".": "ro", "node_modules": "rw", ".env": "none" }}

// Installs dependencies; install scripts can reach the network but write only node_modules.
export install() = $[deps] npm install --no-package-lock
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Install scripts can reach the outside, but they can't read &lt;code&gt;.env&lt;/code&gt; or overwrite source files. The build side can read &lt;code&gt;node_modules&lt;/code&gt; but can't reach the network.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2F9tv90csffdyyivwxha28.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2F9tv90csffdyyivwxha28.png" alt="Two containers side by side: deps.lask's install and main.lask's build. In both, the repository is read-only and .env appears empty. install can write node_modules and reach the internet. build can write dist and has no route out." width="800" height="520"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;&lt;em&gt;Figure 1. What each task's container can access&lt;/em&gt;&lt;/p&gt;

&lt;p&gt;&lt;code&gt;lask envs access&lt;/code&gt; lists what the commands in a module can access. It runs nothing and doesn't contact the container engine.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;$ lask envs access --module deps.lask
ENVIRONMENT                            FILES                             NETWORK    CHECK  REQUIRED BY
#node:24.21.0-alpine3.24{base_access}  . ro, .env none, node_modules rw  default !  ok     deps.lask: install

1 row (1 registry): 1 ok
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;code&gt;!&lt;/code&gt; marks anything that can access beyond the repository. Here, it shows that the container can reach the outside.&lt;/p&gt;

&lt;h3&gt;
  
  
  Check with envs access before running
&lt;/h3&gt;

&lt;p&gt;In Kleppmann's argument, people read the spec and a proof checker verifies that the code meets it. I think Lask can narrow what people read in the same way. Instead of reading every command an agent added, you check the access scope with &lt;code&gt;lask envs access&lt;/code&gt; before running.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;$ lask envs access build
within { files: { ".": "ro", ".env": "none", "dist": "rw" }, volumes: false, network: "none", publish: false }
ENVIRONMENT                                    FILES                     NETWORK  CHECK  REQUIRED BY
#node:24.21.0-alpine3.24{base_access,network}  . ro, .env none, dist rw  none     ok     main.lask: build

1 row (1 registry): 1 ok
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Given a function, the first line shows the constraint the function is checked against. Below it are the scopes of the commands the function runs.&lt;/p&gt;

&lt;p&gt;The list is built from the same code that runs. If an agent edits the &lt;code&gt;within&lt;/code&gt; line itself, the constraint on the first line changes, so you notice before running.&lt;/p&gt;

&lt;p&gt;Without a function, you get the list for the whole module. With the &lt;code&gt;upload()&lt;/code&gt; task from earlier, it looks like this:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;$ lask envs access
ENVIRONMENT                                    FILES                     NETWORK    CHECK     REQUIRED BY
#node:24.21.0-alpine3.24{base_access,network}  . ro, .env none, dist rw  none       ok        main.lask: build
#node:24.21.0-alpine3.24{base_access}          . ro, .env none, dist rw  default !  exceeded  main.lask: upload

2 rows (2 registry): 1 ok, 1 exceeded
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;An &lt;code&gt;exceeded&lt;/code&gt; row is a task with a command that goes beyond the declaration. Rows marked &lt;code&gt;!&lt;/code&gt; tell you which tasks talk to the outside. &lt;code&gt;envs access&lt;/code&gt; runs nothing, and it exits with 0 even when something exceeds the scope.&lt;/p&gt;

&lt;p&gt;A person reads this list, and &lt;code&gt;lask check&lt;/code&gt; verifies that every command stays within it. However many tasks an agent adds, this list is all a person needs to check.&lt;/p&gt;

&lt;p&gt;The split looks like this:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;An agent adds a task.&lt;/li&gt;
&lt;li&gt;A person checks the access scope with &lt;code&gt;lask envs access&lt;/code&gt;.&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;lask run&lt;/code&gt; checks the module before running anything.&lt;/li&gt;
&lt;li&gt;If everything is &lt;code&gt;ok&lt;/code&gt;, the commands run. If anything is &lt;code&gt;exceeded&lt;/code&gt; or &lt;code&gt;unproven&lt;/code&gt;, nothing runs.&lt;/li&gt;
&lt;/ol&gt;

&lt;h3&gt;
  
  
  What within doesn't protect
&lt;/h3&gt;

&lt;p&gt;&lt;code&gt;within&lt;/code&gt; is a static check done by Lask's type checker. Unlike Containment Verification in Part 1, the checker itself isn't machine-verified, for example in Dafny. If the checker has a bug, a command that exceeds the scope can still pass.&lt;/p&gt;

&lt;p&gt;What it protects is limited too:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;The network can only be &lt;code&gt;none&lt;/code&gt; or &lt;code&gt;default&lt;/code&gt;. The install scripts in &lt;code&gt;deps.lask&lt;/code&gt; can talk to anywhere on the internet. The next section covers limiting destinations.&lt;/li&gt;
&lt;li&gt;Environment variables aren't covered. A secret passed through &lt;code&gt;env&lt;/code&gt; can be read by that command. Pass a secret to a container with network access, and it could still be exfiltrated.&lt;/li&gt;
&lt;li&gt;Tasks run with &lt;code&gt;#local&lt;/code&gt; satisfy no constraint. Tasks that need &lt;code&gt;#local&lt;/code&gt; have to live in a module without &lt;code&gt;within&lt;/code&gt;.&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;lask sync&lt;/code&gt;, which builds and pulls images, isn't covered.&lt;/li&gt;
&lt;li&gt;The container engine, such as Docker, is what enforces the scope at runtime. If its isolation is broken, the declaration means nothing.&lt;/li&gt;
&lt;li&gt;Only commands run through Lask are checked. If an agent runs &lt;code&gt;npm install&lt;/code&gt; directly from a shell, Lask can't check it.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;The last point is a separate problem: how to restrict the agent's own permissions. That belongs to the agent's permission settings and sandbox.&lt;/p&gt;

&lt;h3&gt;
  
  
  Egress, under consideration
&lt;/h3&gt;

&lt;p&gt;Outbound traffic from containers (egress) is currently all or nothing. The &lt;code&gt;npm install&lt;/code&gt; in &lt;code&gt;deps.lask&lt;/code&gt; only needs to reach the npm registry, but with &lt;code&gt;network: "default"&lt;/code&gt;, install scripts can reach anywhere on the internet. As with the npm worms in Part 1, any secret they can read could be sent out.&lt;/p&gt;

&lt;p&gt;So, as the next step, I'm considering egress that lets you list the destinations a task may reach. The design I have in mind:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Connect the task's container to an internal Docker network with no route to the outside.&lt;/li&gt;
&lt;li&gt;Put a proxy container on the same network and route all outbound traffic through it. Pin the proxy image by digest, like any other image.&lt;/li&gt;
&lt;li&gt;The proxy only forwards traffic to destinations on the allowlist.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;The task's container has no way out except through the proxy, so an install script that tries to bypass the proxy can't reach the outside. This is still at the design stage, and the syntax isn't decided.&lt;/p&gt;

&lt;h2&gt;
  
  
  Wrapping up
&lt;/h2&gt;

&lt;p&gt;It's getting hard to read all the code AI writes. But you can narrow what people read to the list &lt;code&gt;lask envs access&lt;/code&gt; prints, and leave the rest to type checking. That's what I wanted to show in this post.&lt;/p&gt;

&lt;p&gt;&lt;code&gt;within&lt;/code&gt; just landed in 0.15.0, and both how constraints are written and what the check covers are still being worked out. I'd love to hear how it works for you, or what constraints you'd want.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://github.com/lask-task-runner/lask" rel="noopener noreferrer"&gt;https://github.com/lask-task-runner/lask&lt;/a&gt;&lt;/p&gt;

</description>
      <category>security</category>
      <category>ai</category>
      <category>supplychain</category>
      <category>docker</category>
    </item>
    <item>
      <title>Say Goodbye to "It Works on My Machine": Why I Built a New Task Runner 🚀</title>
      <dc:creator>Toru Ikeda</dc:creator>
      <pubDate>Wed, 19 Aug 2026 12:00:46 +0000</pubDate>
      <link>https://dev.to/torudev/say-goodbye-to-it-works-on-my-machine-why-i-built-a-new-task-runner-3mi9</link>
      <guid>https://dev.to/torudev/say-goodbye-to-it-works-on-my-machine-why-i-built-a-new-task-runner-3mi9</guid>
      <description>&lt;p&gt;Have you ever spent hours—or even days—debugging a failed build or deployment, only to realize the issue was a slight difference in local environments?&lt;/p&gt;

&lt;p&gt;Maybe your colleague had Go 1.21 installed instead of Go 1.22. Maybe they were on Windows while you were on a Mac. Or maybe their AWS CLI version was slightly outdated.&lt;/p&gt;

&lt;p&gt;We've all been there. It's the classic "It works on my machine" problem.&lt;/p&gt;

&lt;p&gt;To solve this, I built &lt;a href="https://github.com/lask-task-runner/lask" rel="noopener noreferrer"&gt;Lask&lt;/a&gt;, a task runner that eliminates environment discrepancies by embedding the execution environment directly into the syntax. Here is why I built it and how it works.&lt;/p&gt;

&lt;p&gt;🛑 &lt;strong&gt;The limitations of current solutions&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;When trying to standardize tasks, teams usually lean on two approaches:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;
&lt;strong&gt;Shell Scripts &amp;amp; Makefiles&lt;/strong&gt;: Writing scripts automates the steps, but it doesn't standardize the execution environment. You are still at the mercy of whatever is installed on the host OS. &lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;CI/CD Pipelines&lt;/strong&gt;: Running tasks in GitHub Actions or CI provides a perfectly clean, reproducible environment. However, it sacrifices the fast feedback loop of local development. Developers often struggle to test the exact same CI pipeline locally.&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;I wanted a tool that combines the reproducibility of CI/CD with the speed and convenience of local execution.&lt;/p&gt;

&lt;p&gt;✨ &lt;strong&gt;Enter: Lask&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;Lask is an open-source task runner designed around three core principles: Reproducibility, Portability, and Ease of Execution.&lt;/p&gt;

&lt;p&gt;Instead of relying on whatever tools are installed on your host machine, Lask lets you define the execution environment inline, right next to your command.&lt;/p&gt;

&lt;p&gt;Here is what a &lt;code&gt;publish&lt;/code&gt; task looks like in Lask:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;// main.lask
publish(--var: String = "1.0.0") = do {
  // Run inside a Go 1.22 container
  $[#golang:1.22] GOOS=linux GOARCH=arm64 go build -o bootstrap ./cmd/lambda

  // Run inside a Zip container
  $[#zip:latest] zip function.zip bootstrap

  // Run inside an AWS CLI container
  $[#aws-cli:2.36] aws lambda update-function-code \
    --function-name my-function \
    --zip-file fileb://function.zip
}
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;By simply typing lask run publish in your terminal, Lask uses containers under the hood to execute each step in its exact required environment.&lt;/p&gt;

&lt;p&gt;Why this changes the game:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;🔒 &lt;strong&gt;True Reproducibility&lt;/strong&gt;: Because the environment (#golang:1.22, #aws-cli:2.36) is hardcoded into the task, anyone running the script gets the exact same result. No more accidental mismatches.&lt;/li&gt;
&lt;li&gt;🌍 &lt;strong&gt;Portability&lt;/strong&gt;: Whether you are on Linux, macOS, Windows, or inside a CI runner, the behavior is identical. You effectively bring the CI environment down to your local machine.&lt;/li&gt;
&lt;li&gt;⚡ &lt;strong&gt;Zero Setup&lt;/strong&gt;: Lask is delivered as a single binary. It also features static typing for safety and a built-in REPL for int
eractive experimentation!
&lt;/li&gt;
&lt;/ul&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="nv"&gt;$ &lt;/span&gt;lask repl
lask&amp;gt; &lt;span class="nv"&gt;$[&lt;/span&gt;&lt;span class="c"&gt;#rancher/cowsay] cowsay "Hello, Lask!"&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;🤝 &lt;strong&gt;I'd love your feedback!&lt;/strong&gt;&lt;br&gt;
Lask is completely open-source and actively being developed. If you are tired of debugging local environment issues, I would be thrilled if you gave it a try or checked out the code.&lt;/p&gt;

&lt;p&gt;⭐ GitHub: &lt;a href="https://github.com/lask-task-runner/lask" rel="noopener noreferrer"&gt;lask-task-runner/lask&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;What tools do you currently use to keep dev environments in sync across your team? Let me know in the comments below! 👇&lt;/p&gt;

</description>
      <category>devops</category>
      <category>docker</category>
      <category>showdev</category>
      <category>opensource</category>
    </item>
  </channel>
</rss>
