<?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: Varun Poojari</title>
    <description>The latest articles on DEV Community by Varun Poojari (@varun_poojari).</description>
    <link>https://dev.to/varun_poojari</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%2F4159640%2F962b1dd5-dc1d-42e0-b297-1b72a9225c39.jpg</url>
      <title>DEV Community: Varun Poojari</title>
      <link>https://dev.to/varun_poojari</link>
    </image>
    <atom:link rel="self" type="application/rss+xml" href="https://dev.to/feed/varun_poojari"/>
    <language>en</language>
    <item>
      <title>When you point an AI coding agent at a hostile repo, whose settings does it obey?</title>
      <dc:creator>Varun Poojari</dc:creator>
      <pubDate>Tue, 20 Oct 2026 14:47:55 +0000</pubDate>
      <link>https://dev.to/varun_poojari/when-you-point-an-ai-coding-agent-at-a-hostile-repo-whose-settings-does-it-obey-19gg</link>
      <guid>https://dev.to/varun_poojari/when-you-point-an-ai-coding-agent-at-a-hostile-repo-whose-settings-does-it-obey-19gg</guid>
      <description>&lt;p&gt;AI coding agents make good security reviewers. They can read a repo the way a human reviewer would. But a security review means running an agent on code you do not trust, and that raises a question I had not thought about until I tested it: whose configuration does the agent obey, yours or the repo's?&lt;/p&gt;

&lt;p&gt;I work on &lt;a href="https://github.com/secfoo-com/secfoo" rel="noopener noreferrer"&gt;secfoo&lt;/a&gt;, an open-source (MIT) CLI that hands an agent such as Claude Code a security brief and a target. While verifying its Claude adapter, I tested exactly that.&lt;/p&gt;

&lt;h2&gt;
  
  
  The test
&lt;/h2&gt;

&lt;p&gt;I built a small hostile repo with three things in it:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;a &lt;code&gt;.claude/settings.json&lt;/code&gt; with a hook that runs a command&lt;/li&gt;
&lt;li&gt;a &lt;code&gt;.mcp.json&lt;/code&gt; that defines an MCP server&lt;/li&gt;
&lt;li&gt;settings that enable project MCP servers and try to allow &lt;code&gt;Write&lt;/code&gt;, &lt;code&gt;Edit&lt;/code&gt; and &lt;code&gt;Bash&lt;/code&gt;
&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Then I ran the adapter's original command against it.&lt;/p&gt;

&lt;h2&gt;
  
  
  What happened with the old command
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;The repo's hook ran.&lt;/li&gt;
&lt;li&gt;The repo's own MCP server started.&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;Write&lt;/code&gt; stayed denied, so the read-only grant held.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;So the scanned code could make the scanner execute something, even though it could not write files. For a tool whose whole job is to open untrusted code, that is the wrong way round.&lt;/p&gt;

&lt;h2&gt;
  
  
  The fix
&lt;/h2&gt;

&lt;p&gt;Two flags on the agent command:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;code&gt;--setting-sources user&lt;/code&gt; loads only the user's own settings, so the target repo's settings are ignored&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;--strict-mcp-config&lt;/code&gt; uses only the MCP servers the tool passes in explicitly&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;While I was in there I also made three smaller changes: the prompt goes over stdin instead of the command line (command-line prompts can be truncated at newlines on Windows), &lt;code&gt;--no-session-persistence&lt;/code&gt; stops a session file being left behind for every scan, and a failed run now records why it failed, since Claude writes errors to its JSON output rather than stderr.&lt;/p&gt;

&lt;p&gt;The adapter command is now:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;claude &lt;span class="nt"&gt;-p&lt;/span&gt; &lt;span class="nt"&gt;--output-format&lt;/span&gt; json &lt;span class="nt"&gt;--allowedTools&lt;/span&gt; &amp;lt;tools&amp;gt; &lt;span class="se"&gt;\&lt;/span&gt;
  &lt;span class="nt"&gt;--permission-mode&lt;/span&gt; default &lt;span class="nt"&gt;--setting-sources&lt;/span&gt; user &lt;span class="se"&gt;\&lt;/span&gt;
  &lt;span class="nt"&gt;--strict-mcp-config&lt;/span&gt; &lt;span class="nt"&gt;--no-session-persistence&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;with the prompt read from stdin.&lt;/p&gt;

&lt;h2&gt;
  
  
  What I saw after
&lt;/h2&gt;

&lt;p&gt;Against the same hostile repo:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;the hook did not run&lt;/li&gt;
&lt;li&gt;the target's MCP server did not start&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;Write&lt;/code&gt; was still denied&lt;/li&gt;
&lt;li&gt;no session file was left behind&lt;/li&gt;
&lt;li&gt;secfoo's own MCP server, passed in explicitly, still started&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;I also ran a real SAST scan with the Claude agent on that repo. It finished successfully, found the one SQL injection, recorded tokens and cost, and left the target unchanged. The test suite went from 736 to 751 passing, with 15 new tests for this.&lt;/p&gt;

&lt;h2&gt;
  
  
  What I did not find
&lt;/h2&gt;

&lt;p&gt;I checked two other ideas and dropped them because I could not reproduce them: that an error result could be saved as a successful report (Claude exits with an error code, so it is not), and that a target's &lt;code&gt;permissions.allow&lt;/code&gt; could widen the tool grant (&lt;code&gt;Write&lt;/code&gt; stayed denied).&lt;/p&gt;

&lt;h2&gt;
  
  
  Limits
&lt;/h2&gt;

&lt;p&gt;I tested on Linux with Claude Code 2.1.288. I have not tested Windows or macOS, or older Claude Code versions (a CLI that does not know a new flag exits with an "unknown option" error). I also have not checked whether other agent adapters have the same problem.&lt;/p&gt;

&lt;h2&gt;
  
  
  What to take from this
&lt;/h2&gt;

&lt;p&gt;If you run any AI agent against code you did not write, check which config files it reads from the target. Project-level settings, hooks and tool servers are all things a repo can ship. Passing your own configuration explicitly, and ignoring the target's, is a small change, and it is easy to miss because nothing breaks when you leave it out.&lt;/p&gt;

&lt;p&gt;The fix is in the open: &lt;a href="https://github.com/secfoo-com/secfoo" rel="noopener noreferrer"&gt;secfoo on GitHub&lt;/a&gt; (PR #26). If you find a case I missed, please open an issue.&lt;/p&gt;

</description>
      <category>security</category>
      <category>ai</category>
      <category>opensource</category>
      <category>devsecops</category>
    </item>
    <item>
      <title>I mapped secfoo's prompt review to the OWASP LLM Top 10</title>
      <dc:creator>Varun Poojari</dc:creator>
      <pubDate>Wed, 07 Oct 2026 13:02:25 +0000</pubDate>
      <link>https://dev.to/varun_poojari/i-mapped-secfoos-prompt-review-to-the-owasp-llm-top-10-2hgo</link>
      <guid>https://dev.to/varun_poojari/i-mapped-secfoos-prompt-review-to-the-owasp-llm-top-10-2hgo</guid>
      <description>&lt;p&gt;A lot of LLM app risk sits somewhere duller than the model. It sits in a system prompt glued to user input with string concatenation, or in a tool definition that hands the model a shell.&lt;/p&gt;

&lt;p&gt;Both are visible in the code, before anything ships. This post walks through the &lt;code&gt;prompt-review&lt;/code&gt; skill in &lt;a href="https://github.com/secfoo-com/secfoo" rel="noopener noreferrer"&gt;secfoo&lt;/a&gt;, an open-source security assessment CLI, and maps each of its seven checks to the &lt;a href="https://genai.owasp.org/llm-top-10/" rel="noopener noreferrer"&gt;OWASP Top 10 for LLM Applications (2025)&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;&lt;em&gt;Disclosure: I'm an AI security intern at RakFort and I contribute to secfoo. The mapping is my own reading, not an official one.&lt;/em&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  What prompt-review looks at
&lt;/h2&gt;

&lt;p&gt;secfoo has no scanner engine of its own. A skill is a structured brief written in Markdown. secfoo hands that brief and your target to a coding-agent CLI you already use (Claude Code, Cursor, Antigravity or Gemini CLI) and collects the report.&lt;/p&gt;

&lt;p&gt;The &lt;a href="https://github.com/secfoo-com/secfoo/blob/main/src/secfoo/skills/definitions/prompt-review.md" rel="noopener noreferrer"&gt;&lt;code&gt;prompt-review&lt;/code&gt; brief&lt;/a&gt; is 45 lines long and asks the agent to do seven things:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;
&lt;strong&gt;Locate every prompt surface.&lt;/strong&gt; System prompts, templates, few-shot examples, tool and function-calling schemas, allowed-tool lists and permission scopes.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Check injection resistance.&lt;/strong&gt; Find where untrusted input (user messages, retrieved documents, tool outputs, web content) is concatenated into instruction context with no delimiter or boundary.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Check tool permission scope.&lt;/strong&gt; Are callable tools scoped to the minimum, or does the model get shell execution, unrestricted file writes, unscoped database access or outbound network calls?&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Check for a human in the loop before side effects.&lt;/strong&gt; Are model-suggested commands, SQL, file writes, API calls or payments validated or confirmed before they run?&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Check for data leakage.&lt;/strong&gt; Are secrets, API keys or PII interpolated into prompts, logged in full, or sent to a third-party model provider without the user knowing?&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Check guardrail robustness.&lt;/strong&gt; Can a crafted message override the refusal instructions, and is there a code-level backstop for high-risk actions?&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Check rate and cost controls.&lt;/strong&gt; Can one caller trigger an unbounded number of expensive model calls?&lt;/li&gt;
&lt;/ol&gt;

&lt;h2&gt;
  
  
  How the checks map to the OWASP LLM Top 10
&lt;/h2&gt;

&lt;p&gt;Six of the ten OWASP categories are touched by at least one check.&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;#&lt;/th&gt;
&lt;th&gt;Check&lt;/th&gt;
&lt;th&gt;OWASP LLM Top 10 (2025)&lt;/th&gt;
&lt;th&gt;Why it maps&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;1&lt;/td&gt;
&lt;td&gt;Prompt surfaces&lt;/td&gt;
&lt;td&gt;LLM07 System Prompt Leakage&lt;/td&gt;
&lt;td&gt;You cannot judge what a system prompt exposes until you have found every one&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;2&lt;/td&gt;
&lt;td&gt;Injection resistance&lt;/td&gt;
&lt;td&gt;LLM01 Prompt Injection&lt;/td&gt;
&lt;td&gt;Covers direct input and indirect input from documents, tools and web content&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;3&lt;/td&gt;
&lt;td&gt;Tool permission scope&lt;/td&gt;
&lt;td&gt;LLM06 Excessive Agency&lt;/td&gt;
&lt;td&gt;Excessive functionality and excessive permissions&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;4&lt;/td&gt;
&lt;td&gt;Human in the loop&lt;/td&gt;
&lt;td&gt;LLM06 Excessive Agency, LLM05 Improper Output Handling&lt;/td&gt;
&lt;td&gt;Excessive autonomy, plus model output passed to a shell or database unvalidated&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;5&lt;/td&gt;
&lt;td&gt;Data leakage&lt;/td&gt;
&lt;td&gt;LLM02 Sensitive Information Disclosure, LLM07 System Prompt Leakage&lt;/td&gt;
&lt;td&gt;Secrets and PII in prompts and logs&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;6&lt;/td&gt;
&lt;td&gt;Guardrail robustness&lt;/td&gt;
&lt;td&gt;LLM01 Prompt Injection&lt;/td&gt;
&lt;td&gt;Jailbreaks are the direct form of injection&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;7&lt;/td&gt;
&lt;td&gt;Rate and cost controls&lt;/td&gt;
&lt;td&gt;LLM10 Unbounded Consumption&lt;/td&gt;
&lt;td&gt;Cost and availability risk from uncapped calls&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;The table shows why I like this brief as a first pass. Checks 2 to 4 are the chain an agent compromise needs: untrusted text gets in, the model has a powerful tool, and nothing stops the call.&lt;/p&gt;

&lt;h2&gt;
  
  
  Try it on your own repo
&lt;/h2&gt;

&lt;p&gt;You need Python 3.10+ and one supported agent CLI installed and signed in.&lt;br&gt;
&lt;/p&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;secfoo

&lt;span class="c"&gt;# see which agent CLIs secfoo can find on your PATH&lt;/span&gt;
secfoo agents

&lt;span class="c"&gt;# review the current directory&lt;/span&gt;
secfoo run &lt;span class="nt"&gt;--skill&lt;/span&gt; prompt-review &lt;span class="nt"&gt;--agent&lt;/span&gt; claude

&lt;span class="c"&gt;# or point it at a public repo and run the full checklist&lt;/span&gt;
secfoo run &lt;span class="nt"&gt;--skill&lt;/span&gt; prompt-review &lt;span class="nt"&gt;--depth&lt;/span&gt; standard &lt;span class="se"&gt;\&lt;/span&gt;
  &lt;span class="nt"&gt;--target&lt;/span&gt; https://github.com/org/repo &lt;span class="nt"&gt;--agent&lt;/span&gt; claude

&lt;span class="c"&gt;# browse the report in the local dashboard (127.0.0.1:8787)&lt;/span&gt;
secfoo serve
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;A few things worth knowing before the first run:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;code&gt;--depth&lt;/code&gt; defaults to &lt;code&gt;quick&lt;/code&gt;, a fast triage. &lt;code&gt;standard&lt;/code&gt; runs the full checklist.&lt;/li&gt;
&lt;li&gt;On a real terminal the first run asks for a project name and an optional application ID. Rescans with the same ID land in the same case file.&lt;/li&gt;
&lt;li&gt;Skills run concurrently, so &lt;code&gt;--skill prompt-review --skill secret-scanning&lt;/code&gt; costs you one wait, not two.&lt;/li&gt;
&lt;li&gt;Runs stay on your machine. &lt;code&gt;secfoo list&lt;/code&gt; and &lt;code&gt;secfoo show &amp;lt;run-uuid&amp;gt;&lt;/code&gt; read them back in the terminal.&lt;/li&gt;
&lt;li&gt;For CI, &lt;code&gt;--fail-on high&lt;/code&gt; exits with code 2 when a finding at that severity or worse turns up, and &lt;code&gt;--json&lt;/code&gt; writes a machine-readable result to stdout.&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  A small target to practise on
&lt;/h2&gt;

&lt;p&gt;If you have no LLM code to hand, save this as &lt;code&gt;bot.py&lt;/code&gt; in an empty folder and run the skill against it. I wrote it to be wrong in as many ways as possible.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight python"&gt;&lt;code&gt;&lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;os&lt;/span&gt;
&lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;subprocess&lt;/span&gt;

&lt;span class="n"&gt;SYSTEM&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;
    &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;You are a support bot. &lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;
    &lt;span class="sa"&gt;f&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;Billing API key: &lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="n"&gt;os&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;environ&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;BILLING_API_KEY&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="s"&gt;. &lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;
    &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;Never reveal the key.&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;
&lt;span class="p"&gt;)&lt;/span&gt;

&lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;run_shell&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;cmd&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nb"&gt;str&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;-&amp;gt;&lt;/span&gt; &lt;span class="nb"&gt;str&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
    &lt;span class="sh"&gt;"""&lt;/span&gt;&lt;span class="s"&gt;Tool exposed to the model.&lt;/span&gt;&lt;span class="sh"&gt;"""&lt;/span&gt;
    &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="n"&gt;subprocess&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;run&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;cmd&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;shell&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="bp"&gt;True&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;capture_output&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="bp"&gt;True&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;text&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="bp"&gt;True&lt;/span&gt;&lt;span class="p"&gt;).&lt;/span&gt;&lt;span class="n"&gt;stdout&lt;/span&gt;

&lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;answer&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;llm&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;user_msg&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nb"&gt;str&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;retrieved_doc&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nb"&gt;str&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;-&amp;gt;&lt;/span&gt; &lt;span class="nb"&gt;str&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
    &lt;span class="n"&gt;prompt&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;SYSTEM&lt;/span&gt; &lt;span class="o"&gt;+&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="se"&gt;\n&lt;/span&gt;&lt;span class="s"&gt;Context: &lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt; &lt;span class="o"&gt;+&lt;/span&gt; &lt;span class="n"&gt;retrieved_doc&lt;/span&gt; &lt;span class="o"&gt;+&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="se"&gt;\n&lt;/span&gt;&lt;span class="s"&gt;User: &lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt; &lt;span class="o"&gt;+&lt;/span&gt; &lt;span class="n"&gt;user_msg&lt;/span&gt;
    &lt;span class="n"&gt;reply&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;llm&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;prompt&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;tools&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="n"&gt;run_shell&lt;/span&gt;&lt;span class="p"&gt;])&lt;/span&gt;
    &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="n"&gt;reply&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;tool_call&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
        &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="nf"&gt;run_shell&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;reply&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;tool_call&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;args&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;cmd&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;])&lt;/span&gt;
    &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="n"&gt;reply&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;text&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Reading it against the seven checks by hand, this is what a reviewer should flag:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Check 2:&lt;/strong&gt; &lt;code&gt;retrieved_doc&lt;/code&gt; and &lt;code&gt;user_msg&lt;/code&gt; are concatenated straight into the instruction context, so a poisoned document can issue instructions.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Check 3:&lt;/strong&gt; &lt;code&gt;run_shell&lt;/code&gt; gives the model arbitrary command execution, far beyond what a support bot needs.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Check 4:&lt;/strong&gt; the tool call runs immediately, with no allow-list and no confirmation.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Check 5:&lt;/strong&gt; a live API key sits inside the system prompt.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Check 6:&lt;/strong&gt; the only thing protecting that key is the sentence "Never reveal the key".&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Check 7:&lt;/strong&gt; nothing limits how often &lt;code&gt;answer&lt;/code&gt; can be called.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Compare that list with what your agent reports. An LLM is doing the reading, so wording and coverage vary between agents and between runs.&lt;/p&gt;

&lt;h2&gt;
  
  
  What it does not do
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;It is a code review, not a red team.&lt;/strong&gt; Nothing is sent to your running app. Pair it with dynamic testing tools such as &lt;a href="https://github.com/NVIDIA/garak" rel="noopener noreferrer"&gt;garak&lt;/a&gt; or &lt;a href="https://github.com/Azure/PyRIT" rel="noopener noreferrer"&gt;PyRIT&lt;/a&gt;.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Four OWASP categories are out of scope for this skill:&lt;/strong&gt; LLM03 Supply Chain, LLM04 Data and Model Poisoning, LLM08 Vector and Embedding Weaknesses and LLM09 Misinformation. secfoo's &lt;code&gt;sca-reachability&lt;/code&gt; skill and AI-BOM upload cover part of the supply-chain side. I found nothing for the other three.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;The reviewer is an LLM.&lt;/strong&gt; It can miss things and it can over-report. Treat findings as leads to verify.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Your code goes wherever your agent CLI sends it.&lt;/strong&gt; secfoo stores runs locally, but the agent still talks to its model provider. Use &lt;code&gt;--exclude&lt;/code&gt; to keep paths out of a scan.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Agent runs cost tokens.&lt;/strong&gt; &lt;code&gt;--max-cost&lt;/code&gt; puts a dollar cap on a run for agents that report spend.&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  Where this could go next
&lt;/h2&gt;

&lt;p&gt;The gaps above are the interesting part. Adding a skill to secfoo means dropping one Markdown file into &lt;a href="https://github.com/secfoo-com/secfoo/tree/main/src/secfoo/skills/definitions" rel="noopener noreferrer"&gt;&lt;code&gt;src/secfoo/skills/definitions/&lt;/code&gt;&lt;/a&gt;. The CLI choices and dashboard pages are derived from that folder.&lt;/p&gt;

&lt;p&gt;So a brief for vector-store weaknesses or model supply chain is a small, self-contained pull request. If you test LLM apps and have opinions on what such a brief should ask, the repo is MIT licensed and &lt;a href="https://github.com/secfoo-com/secfoo/blob/main/CONTRIBUTING.md" rel="noopener noreferrer"&gt;CONTRIBUTING.md&lt;/a&gt; explains how to start.&lt;/p&gt;

&lt;p&gt;Repo: &lt;a href="https://github.com/secfoo-com/secfoo" rel="noopener noreferrer"&gt;github.com/secfoo-com/secfoo&lt;/a&gt;. If the tool is useful to you, a star helps other people find it.&lt;/p&gt;

&lt;p&gt;What would you add to the seven checks? Tell me in the comments.&lt;/p&gt;

</description>
      <category>security</category>
      <category>ai</category>
      <category>llm</category>
      <category>opensource</category>
    </item>
    <item>
      <title>What does an AI security review actually cost? I measured it per run</title>
      <dc:creator>Varun Poojari</dc:creator>
      <pubDate>Sat, 03 Oct 2026 11:37:16 +0000</pubDate>
      <link>https://dev.to/varun_poojari/what-does-an-ai-security-review-actually-cost-i-measured-it-per-run-4f2n</link>
      <guid>https://dev.to/varun_poojari/what-does-an-ai-security-review-actually-cost-i-measured-it-per-run-4f2n</guid>
      <description>&lt;p&gt;Everyone asks whether AI agents can find security bugs. Fewer people ask the question a team lead asks five minutes later: &lt;strong&gt;what does it cost each time we run it?&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;I'm an intern at RakFort in Dublin, and for the last few weeks my job has been testing the cost tracking in &lt;a href="https://github.com/secfoo-com/secfoo" rel="noopener noreferrer"&gt;secfoo&lt;/a&gt;, an open-source (MIT) CLI that gives AI coding agents a fixed security brief and a fixed report format. This post is what I found, including the parts that are not finished.&lt;/p&gt;

&lt;h2&gt;
  
  
  The problem with "just ask the agent"
&lt;/h2&gt;

&lt;p&gt;You can point Claude Code, Cursor or Codex at a repo and ask for a security review. You will get something useful. You will not get:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;the same structure twice, so you cannot compare runs&lt;/li&gt;
&lt;li&gt;a record of what the run cost&lt;/li&gt;
&lt;li&gt;a way to stop a CI job that is burning money&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;secfoo handles the first with what it calls skills (structured briefs such as &lt;code&gt;sast&lt;/code&gt;, &lt;code&gt;threat-modeling&lt;/code&gt; and &lt;code&gt;secret-scanning&lt;/code&gt;). The second and third are what I tested.&lt;/p&gt;

&lt;h2&gt;
  
  
  The setup
&lt;/h2&gt;

&lt;p&gt;I used a deliberately small target: a 30-line Flask app with one SQL injection, where &lt;code&gt;get_user()&lt;/code&gt; builds its query by joining strings. Small on purpose, so I could check the report by hand.&lt;br&gt;
&lt;/p&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; &lt;span class="s2"&gt;"secfoo[api]"&lt;/span&gt;
&lt;span class="nb"&gt;export &lt;/span&gt;&lt;span class="nv"&gt;OPENAI_API_KEY&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;...
secfoo run &lt;span class="nt"&gt;--skill&lt;/span&gt; sast &lt;span class="nt"&gt;--agent&lt;/span&gt; api &lt;span class="nt"&gt;--target&lt;/span&gt; &lt;span class="nb"&gt;.&lt;/span&gt; &lt;span class="nt"&gt;--project-name&lt;/span&gt; demo &lt;span class="nt"&gt;--app-id&lt;/span&gt; &lt;span class="s2"&gt;""&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The &lt;code&gt;api&lt;/code&gt; agent calls a model API directly, so you do not need an agent CLI installed. The last two flags skip the interactive prompts, which matters in scripts.&lt;/p&gt;

&lt;p&gt;Here is the real output:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;$ secfoo run --skill sast --agent api --target . --project-name demo --app-id ""
  success — SAST — Static Code Analysis
                                              Assessment results
┏━━━━━━━━━━━━━━━━━━━━━━━━━━━━━┳━━━━━━━━━┳━━━━━━━━━━┳━━━━━━━━━━━━━━━━━━━━┳━━━━━━━━┳━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━┓
┃ Skill                       ┃ Status  ┃ Duration ┃ Findings (C/H/M/L) ┃   Cost ┃ Run ID                               ┃
┡━━━━━━━━━━━━━━━━━━━━━━━━━━━━━╇━━━━━━━━━╇━━━━━━━━━━╇━━━━━━━━━━━━━━━━━━━━╇━━━━━━━━╇━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━┩
│ SAST — Static Code Analysis │ success │ 11.2s    │ 0/1/0/0            │ &amp;lt;$0.01 │ 888f222e-7228-4b2b-b306-78db7a17d499 │
└─────────────────────────────┴─────────┴──────────┴────────────────────┴────────┴──────────────────────────────────────┘
AI spend for this run: &amp;lt;$0.01
Run secfoo serve to view full reports in your browser.
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h2&gt;
  
  
  What one run cost
&lt;/h2&gt;

&lt;p&gt;The scan finished in &lt;strong&gt;11.2 seconds&lt;/strong&gt; and reported &lt;strong&gt;one finding: the SQL injection in &lt;code&gt;get_user()&lt;/code&gt;&lt;/strong&gt;, which is the right answer for this app.&lt;/p&gt;

&lt;p&gt;It used &lt;strong&gt;2,183 input tokens and 981 output tokens&lt;/strong&gt;, 3,164 in total. secfoo printed the spend for the run as &lt;strong&gt;under one cent&lt;/strong&gt;. It shows &lt;code&gt;&amp;lt;$0.01&lt;/code&gt; rather than rounding down to zero, which I liked: a zero would suggest the run was free.&lt;/p&gt;

&lt;p&gt;In my earlier testing, a repeat scan of an unchanged repo used exactly the same input tokens. That is expected today: nothing is cached between runs yet, so the second scan costs the same as the first.&lt;/p&gt;

&lt;h2&gt;
  
  
  Same kind of target, different agent
&lt;/h2&gt;

&lt;p&gt;Earlier I ran a similar small app through Codex instead of the &lt;code&gt;api&lt;/code&gt; agent:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;secfoo run &lt;span class="nt"&gt;--skill&lt;/span&gt; sast &lt;span class="nt"&gt;--agent&lt;/span&gt; codex &lt;span class="nt"&gt;--target&lt;/span&gt; &lt;span class="nb"&gt;.&lt;/span&gt; &lt;span class="nt"&gt;--project-name&lt;/span&gt; demo &lt;span class="nt"&gt;--app-id&lt;/span&gt; &lt;span class="s2"&gt;""&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;It finished in 27.5 seconds and also found the injection. Two things were different:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Cost shows as &lt;code&gt;-&lt;/code&gt;, not &lt;code&gt;$0.00&lt;/code&gt;.&lt;/strong&gt; Codex runs on a subscription login, so there is no per-run price to record. A dash is the right answer. A zero would be a lie.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Tokens were much higher.&lt;/strong&gt; Codex's own log reported 15,201 tokens for the run, roughly five times the &lt;code&gt;api&lt;/code&gt; agent, because an agent CLI explores the repo with tools instead of receiving one prepared prompt.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;That second point is the most useful thing I learned. The agent you pick can change the token count by a multiple, on a small target, for the same finding.&lt;/p&gt;

&lt;h2&gt;
  
  
  Seeing spend across runs
&lt;/h2&gt;

&lt;p&gt;Per-run numbers are nice. The summary is what a team would actually use:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;secfoo cost
secfoo cost &lt;span class="nt"&gt;--by&lt;/span&gt; skill &lt;span class="nt"&gt;--since&lt;/span&gt; 2026-09-01
secfoo cost &lt;span class="nt"&gt;--by&lt;/span&gt; agent &lt;span class="nt"&gt;--project&lt;/span&gt; demo
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;You can group by agent, skill or project. The same numbers appear in the local dashboard (&lt;code&gt;secfoo serve&lt;/code&gt;), and in my tests the dashboard total matched the CLI total for the same runs.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;$ secfoo cost --by agent --project demo
                   AI spend by agent
┏━━━━━━━┳━━━━━━┳━━━━━━━━━━━━━━┳━━━━━━━━━━━━━━━┳━━━━━━━━┓
┃ Agent ┃ Runs ┃ Input tokens ┃ Output tokens ┃   Cost ┃
┡━━━━━━━╇━━━━━━╇━━━━━━━━━━━━━━╇━━━━━━━━━━━━━━━╇━━━━━━━━┩
│ api   │    2 │        2,183 │           981 │ &amp;lt;$0.01 │
├───────┼──────┼──────────────┼───────────────┼────────┤
│ Total │    2 │        2,183 │           981 │ &amp;lt;$0.01 │
└───────┴──────┴──────────────┴───────────────┴────────┘
1 run(s) have no cost recorded -- their agent doesn't report it (cursor, agy), or they ran before cost tracking existed.
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;That table shows two runs, not one. My first attempt failed because I pasted my API key with a character missing. secfoo still counted it as a run and told me that one run had no cost recorded. I did not plan that, but it is a fair picture of how it behaves when something goes wrong.&lt;/p&gt;

&lt;h2&gt;
  
  
  Making cost a CI gate
&lt;/h2&gt;

&lt;p&gt;This is the part I think matters most for real pipelines:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;secfoo run &lt;span class="nt"&gt;--skill&lt;/span&gt; sast &lt;span class="nt"&gt;--agent&lt;/span&gt; api &lt;span class="nt"&gt;--target&lt;/span&gt; &lt;span class="nb"&gt;.&lt;/span&gt; &lt;span class="se"&gt;\&lt;/span&gt;
  &lt;span class="nt"&gt;--fail-on&lt;/span&gt; high &lt;span class="nt"&gt;--max-cost&lt;/span&gt; 2.00 &lt;span class="nt"&gt;--json&lt;/span&gt; &lt;span class="o"&gt;&amp;gt;&lt;/span&gt; secfoo-result.json
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The exit code tells CI what happened: &lt;code&gt;0&lt;/code&gt; means the run succeeded and no gate tripped, &lt;code&gt;1&lt;/code&gt; means a run failed or timed out, and &lt;code&gt;2&lt;/code&gt; means the run succeeded but a finding or the cost crossed your threshold. A security scan that can fail a build for being too expensive is not something I had seen before.&lt;/p&gt;

&lt;h2&gt;
  
  
  What is not there yet
&lt;/h2&gt;

&lt;p&gt;I was testing, so I was looking for gaps. The honest list:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Cost coverage depends on the agent.&lt;/strong&gt; API-key runs give you tokens and cost. Subscription-based agent CLIs often give you neither, and you see a dash.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Codex tokens are not recorded yet.&lt;/strong&gt; Codex prints its total, but secfoo does not pick it up today. There is an open item for it.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Failed runs are counted as runs.&lt;/strong&gt; They show up in the totals with no cost recorded, as my mistyped key showed.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Severity can differ between the CLI and the dashboard.&lt;/strong&gt; In my run the CLI table counted the finding as High, and the dashboard labelled the same finding Critical. It is a known issue.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;&lt;code&gt;--since&lt;/code&gt; compares against UTC.&lt;/strong&gt; If you run scans late at night in another timezone, the cut-off may not be where you expect.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;No savings on repeat scans yet.&lt;/strong&gt; Reusing results for unchanged code is planned, not shipped.&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  What I would tell a team
&lt;/h2&gt;

&lt;ol&gt;
&lt;li&gt;Start with the &lt;code&gt;api&lt;/code&gt; agent on one repo, so you get real token and cost numbers from day one.&lt;/li&gt;
&lt;li&gt;Run the same repo through the agent your developers already use, and compare the reports side by side.&lt;/li&gt;
&lt;li&gt;Put &lt;code&gt;--max-cost&lt;/code&gt; in CI before you put the scan on every pull request.&lt;/li&gt;
&lt;/ol&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;secfoo
secfoo run &lt;span class="nt"&gt;--skill&lt;/span&gt; sast &lt;span class="nt"&gt;--agent&lt;/span&gt; claude
secfoo serve
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The repo is here: &lt;strong&gt;&lt;a href="https://github.com/secfoo-com/secfoo" rel="noopener noreferrer"&gt;https://github.com/secfoo-com/secfoo&lt;/a&gt;&lt;/strong&gt;. If it is useful to you, a star helps a small open-source project a lot, and issues telling us where the output is wrong help even more.&lt;/p&gt;

&lt;p&gt;We are also part of the team organising a secfoo hackathon in Dublin in mid-November. If you would like to take part, or to help as a moderator, leave a comment and I will get back to you.&lt;/p&gt;

</description>
      <category>security</category>
      <category>ai</category>
      <category>opensource</category>
      <category>devsecops</category>
    </item>
  </channel>
</rss>
