<?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: roberta carraro</title>
    <description>The latest articles on DEV Community by roberta carraro (@itchymutt).</description>
    <link>https://dev.to/itchymutt</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.amazonaws.com%2Fuploads%2Fuser%2Fprofile_image%2F3896779%2Fa1b4daee-19e0-4201-88ae-c9b100276f6d.png</url>
      <title>DEV Community: roberta carraro</title>
      <link>https://dev.to/itchymutt</link>
    </image>
    <atom:link rel="self" type="application/rss+xml" href="https://dev.to/feed/itchymutt"/>
    <language>en</language>
    <item>
      <title>"os.system(f'pip install {library}')"</title>
      <dc:creator>roberta carraro</dc:creator>
      <pubDate>Fri, 24 Apr 2026 23:11:05 +0000</pubDate>
      <link>https://dev.to/itchymutt/ossystemfpip-install-library-4om3</link>
      <guid>https://dev.to/itchymutt/ossystemfpip-install-library-4om3</guid>
      <description>&lt;p&gt;&lt;strong&gt;Update:&lt;/strong&gt; This was found in an older version of CrewAI's code interpreter. CrewAI has since removed the tool entirely. The finding is real but historical. The point is the class of problem, not the specific instance.&lt;/p&gt;




&lt;p&gt;I've been building a static effect analyzer. You point it at a Python or TypeScript file and it tells you what each function does to the outside world: network, filesystem, database, subprocess, etc.&lt;/p&gt;

&lt;p&gt;I ran it on CrewAI's code interpreter and got this:&lt;br&gt;
&lt;/p&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;libgaze check code_interpreter.py

  run_code_unsafe:347  can Unsafe
    365 | os.system&lt;span class="o"&gt;(&lt;/span&gt;f&lt;span class="s2"&gt;"pip install {library}"&lt;/span&gt;&lt;span class="o"&gt;)&lt;/span&gt;
    370 | &lt;span class="nb"&gt;exec&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;code, &lt;span class="o"&gt;{}&lt;/span&gt;, exec_locals&lt;span class="o"&gt;)&lt;/span&gt;

2/13 functions are pure.
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Line 365. &lt;code&gt;library&lt;/code&gt; is a string from the LLM. No validation, no allowlist. The LLM decides what gets pip-installed on your machine via &lt;code&gt;os.system&lt;/code&gt; with an f-string.&lt;/p&gt;

&lt;p&gt;The function is honestly named &lt;code&gt;run_code_unsafe&lt;/code&gt;. But the function that calls it, &lt;code&gt;_run&lt;/code&gt;, is 150 lines long and picks between the "safe" and "unsafe" paths based on a config flag. The tool traces the call graph and shows that &lt;code&gt;_run&lt;/code&gt; inherits &lt;code&gt;Unsafe&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;  _run:194  can Fs, Net, Unsafe
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;If A calls B and B is &lt;code&gt;Unsafe&lt;/code&gt;, A is &lt;code&gt;Unsafe&lt;/code&gt;. You can't hide it behind indirection.&lt;/p&gt;

&lt;h2&gt;
  
  
  How it works
&lt;/h2&gt;

&lt;p&gt;There are ten effects. Fixed list, not extensible:&lt;/p&gt;

&lt;p&gt;&lt;code&gt;Net&lt;/code&gt; &lt;code&gt;Fs&lt;/code&gt; &lt;code&gt;Db&lt;/code&gt; &lt;code&gt;Console&lt;/code&gt; &lt;code&gt;Env&lt;/code&gt; &lt;code&gt;Time&lt;/code&gt; &lt;code&gt;Rand&lt;/code&gt; &lt;code&gt;Async&lt;/code&gt; &lt;code&gt;Unsafe&lt;/code&gt; &lt;code&gt;Fail&lt;/code&gt;&lt;/p&gt;

&lt;p&gt;The analyzer does two passes. First, walk the AST and check every call against a table of known effects (&lt;code&gt;os.system&lt;/code&gt; is &lt;code&gt;Unsafe&lt;/code&gt;, &lt;code&gt;requests.get&lt;/code&gt; is &lt;code&gt;Net&lt;/code&gt;, &lt;code&gt;open()&lt;/code&gt; is &lt;code&gt;Fs&lt;/code&gt;, etc.). Second, propagate through the call graph within the file. Iterate until stable.&lt;/p&gt;

&lt;p&gt;No type inference. No cross-file analysis. Just the AST and a vocabulary. It's enough to find the CrewAI thing and a lot of things like it.&lt;/p&gt;

&lt;p&gt;I scanned 15,293 functions across CrewAI, LangChain, AutoGPT, MCP Servers, Vercel AI SDK, and OpenAI Agents JS. The same ten effects worked for all of them. Didn't add or remove a single one between Python and TypeScript.&lt;/p&gt;

&lt;h2&gt;
  
  
  Try it
&lt;/h2&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;pip &lt;span class="nb"&gt;install &lt;/span&gt;libgaze
libgaze check your_file.py
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;





&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;npm &lt;span class="nb"&gt;install&lt;/span&gt; &lt;span class="nt"&gt;-g&lt;/span&gt; libgaze-ts
libgaze-ts check your_file.ts
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Zero dependencies on the Python side. 2.4MB on the TypeScript side (oxc-parser, not the full TypeScript compiler).&lt;/p&gt;

&lt;p&gt;Both support &lt;code&gt;--json&lt;/code&gt;, directory scanning, &lt;code&gt;--deny&lt;/code&gt; for CI gating, and &lt;code&gt;.gazepolicy&lt;/code&gt; files for per-function rules. There's a GitHub Action too.&lt;/p&gt;

&lt;p&gt;The repo is &lt;a href="https://github.com/itchymutt/gaze" rel="noopener noreferrer"&gt;github.com/itchymutt/gaze&lt;/a&gt;. MIT licensed.&lt;/p&gt;

&lt;p&gt;If you run it on something and find something interesting, let me know.&lt;/p&gt;

</description>
      <category>python</category>
      <category>typescript</category>
      <category>security</category>
      <category>ai</category>
    </item>
  </channel>
</rss>
