<?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: Nathan Sportsman</title>
    <description>The latest articles on DEV Community by Nathan Sportsman (@praetorian_guard).</description>
    <link>https://dev.to/praetorian_guard</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%2F3753426%2F07b693b8-94c3-4e5c-a347-72373d287b2d.png</url>
      <title>DEV Community: Nathan Sportsman</title>
      <link>https://dev.to/praetorian_guard</link>
    </image>
    <atom:link rel="self" type="application/rss+xml" href="https://dev.to/feed/praetorian_guard"/>
    <language>en</language>
    <item>
      <title>When Proxies Become the Attack Vectors in Web Architectures</title>
      <dc:creator>Nathan Sportsman</dc:creator>
      <pubDate>Thu, 12 Mar 2026 14:49:12 +0000</pubDate>
      <link>https://dev.to/praetorian_guard/when-proxies-become-the-attack-vectors-in-web-architectures-65c</link>
      <guid>https://dev.to/praetorian_guard/when-proxies-become-the-attack-vectors-in-web-architectures-65c</guid>
      <description>&lt;p&gt;Many modern web applications rely on a flawed assumption: backends can blindly trust security-critical headers from upstream reverse proxies. This assumption breaks down because HTTP RFC flexibility allows different servers to interpret the same header field in fundamentally different ways, creating exploitable gaps that attackers are increasingly targeting.&lt;/p&gt;

&lt;p&gt;Two recent CVEs I discovered expose this systemic problem and demonstrate why these are not isolated bugs, but symptoms of a much broader architectural flaw. When &lt;a href="https://github.com/advisories/GHS&amp;lt;br&amp;gt;%0A![%20](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/myo0e5gejpmj2mtc4nta.png)A-q7p4-7xjv-j3wf" rel="noopener noreferrer"&gt;CVE-2025-48865&lt;/a&gt; in &lt;a href="https://github.com/fabiolb/fabio" rel="noopener noreferrer"&gt;Fabio&lt;/a&gt; and &lt;a href="https://github.com/oauth2-proxy/oauth2-proxy/security/advisories/GHSA-vjrc-mh2v-45x6" rel="noopener noreferrer"&gt;CVE-2025-64484&lt;/a&gt; in &lt;a href="https://github.com/oauth2-proxy/oauth2-proxy" rel="noopener noreferrer"&gt;OAuth2-proxy&lt;/a&gt; both enable identical attack patterns across completely different technologies, it reveals that our industry has fundamentally misunderstood where the real security boundaries lie.&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;&lt;strong&gt;TL;DR:&lt;/strong&gt; Two newly discovered CVEs (CVE-2025-48865 in Fabio, CVE-2025-64484 in OAuth2-proxy) expose a systemic vulnerability in how reverse proxies handle header processing. By exploiting hop-by-hop header stripping and underscore-hyphen normalization differences, attackers can bypass proxy security controls to achieve authentication bypass and privilege escalation. These are not isolated bugs. They are symptoms of a fundamental trust boundary problem in modern proxy-backend architectures.&lt;/p&gt;
&lt;/blockquote&gt;




&lt;h2&gt;
  
  
  Hop-by-Hop Header Abuse in Fabio (CVE-2025-48865)
&lt;/h2&gt;

&lt;p&gt;In a typical setup, reverse proxies strip untrusted client headers and inject their own security-critical values (such as &lt;code&gt;X-Forwarded-For&lt;/code&gt; and &lt;code&gt;X-Real-IP&lt;/code&gt;), which backends then use for authentication and access control decisions.&lt;/p&gt;

&lt;p&gt;However, HTTP's &lt;code&gt;Connection&lt;/code&gt; header allows clients to designate certain headers as "hop-by-hop," meaning they should be processed only by the immediate recipient and stripped before forwarding. By setting something like:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight http"&gt;&lt;code&gt;&lt;span class="err"&gt;Connection: close, X-Forwarded-Host
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;an attacker can trick the proxy into removing security headers it would normally preserve and trust.&lt;/p&gt;

&lt;p&gt;This attack method has surfaced across multiple reverse proxy implementations, including Apache HTTP Server (CVE-2022-31813) and Traefik (CVE-2024-45410). I discovered that Fabio was susceptible to the same abuse by including security-critical headers like &lt;code&gt;Forwarded&lt;/code&gt; in the &lt;code&gt;Connection&lt;/code&gt; header, causing Fabio to strip them before forwarding the request to the backend.&lt;/p&gt;

&lt;h3&gt;
  
  
  What is a hop-by-hop header attack?
&lt;/h3&gt;

&lt;p&gt;HTTP's &lt;code&gt;Connection&lt;/code&gt; header was designed to let clients specify which headers are "hop-by-hop": they should only be processed by the immediate recipient (the proxy) and removed before forwarding to the next server.&lt;/p&gt;

&lt;p&gt;Attackers abuse this by listing security-critical headers like &lt;code&gt;X-Forwarded-For&lt;/code&gt; or &lt;code&gt;X-Real-IP&lt;/code&gt; in the &lt;code&gt;Connection&lt;/code&gt; header, tricking the proxy into stripping headers that the backend relies on for authentication and access control.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Normal flow:&lt;/strong&gt;&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Client --&amp;gt; Proxy (preserves X-Forwarded-For) --&amp;gt; Backend (uses it for auth)
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;strong&gt;Attack flow:&lt;/strong&gt;&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Client sends: Connection: close, X-Forwarded-For
Client --&amp;gt; Proxy (strips X-Forwarded-For!) --&amp;gt; Backend (auth logic never triggers)
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  Why stripping these headers is dangerous
&lt;/h3&gt;

&lt;p&gt;Headers like &lt;code&gt;X-Real-IP&lt;/code&gt; and &lt;code&gt;X-Forwarded-For&lt;/code&gt; often drive access control decisions, determining whether a request is internal or external, or whether a sensitive endpoint should be accessible.&lt;/p&gt;

&lt;p&gt;For example, &lt;a href="https://projectdiscovery.io/blog/versa-concerto-authentication-bypass-rce" rel="noopener noreferrer"&gt;ProjectDiscovery's research&lt;/a&gt; into Versa Concerto found that the application relied on &lt;code&gt;X-Real-IP&lt;/code&gt; to restrict access to Spring Boot Actuator endpoints. If that header gets stripped, the security logic simply never triggers.&lt;/p&gt;




&lt;h2&gt;
  
  
  The Underscore-Hyphen Normalization Problem
&lt;/h2&gt;

&lt;p&gt;Header normalization creates yet another attack vector that exploits the same fundamental trust boundary weakness. Many web application frameworks automatically normalize header names during processing:&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Framework Behavior&lt;/th&gt;
&lt;th&gt;Example&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;Capitalize + standardize separators&lt;/td&gt;
&lt;td&gt;
&lt;code&gt;client-verified&lt;/code&gt; becomes &lt;code&gt;Client-Verified&lt;/code&gt;
&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Case normalization&lt;/td&gt;
&lt;td&gt;
&lt;code&gt;x-real-ip&lt;/code&gt; treated same as &lt;code&gt;X-Real-IP&lt;/code&gt;
&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Underscore-to-hyphen conversion&lt;/td&gt;
&lt;td&gt;
&lt;code&gt;x_forwarded_email&lt;/code&gt; becomes &lt;code&gt;X-Forwarded-Email&lt;/code&gt;
&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;These normalization differences create systematic opportunities for attackers to smuggle headers using variations that proxy servers don't recognize, but backends will normalize and process as trusted security headers.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://github.security.telekom.com/2020/05/smuggling-http-headers-through-reverse-proxies.html" rel="noopener noreferrer"&gt;Deutsche Telekom Security's research&lt;/a&gt; specifically highlighted this underscore-hyphen normalization problem. Different frameworks handle it inconsistently: some convert hyphens to underscores, while others make headers accessible regardless of the original separator format.&lt;/p&gt;




&lt;h2&gt;
  
  
  OAuth2-Proxy Authentication Bypass via Underscore Smuggling (CVE-2025-64484)
&lt;/h2&gt;

&lt;p&gt;OAuth2-proxy is an authentication proxy that sits between users and backend applications, handling OAuth2/OIDC authentication flows for applications that don't natively support them. When a user authenticates, OAuth2-proxy forwards the request to the backend along with user information in headers like &lt;code&gt;X-Forwarded-User&lt;/code&gt; and &lt;code&gt;X-Forwarded-Email&lt;/code&gt;.&lt;/p&gt;

&lt;p&gt;Here's the problem: OAuth2-proxy correctly strips &lt;code&gt;X-Forwarded-Email&lt;/code&gt; from incoming client requests, but it &lt;strong&gt;only filters the standard hyphenated version&lt;/strong&gt;. Underscore variants like &lt;code&gt;X_Forwarded_Email&lt;/code&gt; pass through unfiltered.&lt;/p&gt;

&lt;p&gt;When these underscore-based headers reach backend applications that normalize header names (converting them back to the hyphenated form), the backend treats them as trusted authentication headers.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;The attack in practice:&lt;/strong&gt;&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight http"&gt;&lt;code&gt;&lt;span class="err"&gt;# Attacker sends:
&lt;/span&gt;&lt;span class="nf"&gt;GET&lt;/span&gt; &lt;span class="nn"&gt;/admin&lt;/span&gt; &lt;span class="k"&gt;HTTP&lt;/span&gt;&lt;span class="o"&gt;/&lt;/span&gt;&lt;span class="m"&gt;1.1&lt;/span&gt;
&lt;span class="na"&gt;Host&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="s"&gt;target.com&lt;/span&gt;
&lt;span class="na"&gt;X_Forwarded_Email&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="s"&gt;admin@target.com&lt;/span&gt;

# OAuth2-proxy sees X_Forwarded_Email (underscores)
#   --&amp;gt; not in its filter list, passes through

# Backend framework normalizes X_Forwarded_Email to X-Forwarded-Email
#   --&amp;gt; treats it as trusted auth header
#   --&amp;gt; attacker is now authenticated as admin@target.com
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The result: an attacker can impersonate any user, including administrators, leading to privilege escalation or full account takeover.&lt;/p&gt;




&lt;h2&gt;
  
  
  MITRE ATT&amp;amp;CK TTP Mapping
&lt;/h2&gt;

&lt;p&gt;These header injection techniques map to several MITRE ATT&amp;amp;CK tactics:&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Tactic&lt;/th&gt;
&lt;th&gt;Technique&lt;/th&gt;
&lt;th&gt;How It Applies&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;Initial Access&lt;/td&gt;
&lt;td&gt;T1190 - Exploit Public-Facing Application&lt;/td&gt;
&lt;td&gt;Targeting proxy configurations to bypass auth&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Defense Evasion&lt;/td&gt;
&lt;td&gt;T1562 - Impair Defenses&lt;/td&gt;
&lt;td&gt;Circumventing proxy security filters via normalization abuse&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Privilege Escalation&lt;/td&gt;
&lt;td&gt;T1068&lt;/td&gt;
&lt;td&gt;Injecting auth headers to impersonate legitimate users&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Lateral Movement&lt;/td&gt;
&lt;td&gt;--&lt;/td&gt;
&lt;td&gt;Compromised auth may grant access to additional internal systems&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;




&lt;h2&gt;
  
  
  Defending Against Proxy Trust Boundary Attacks
&lt;/h2&gt;

&lt;p&gt;Defending against these vulnerabilities requires precise adherence to the HTTP RFC and careful handling of ambiguous behaviors like header normalization and case sensitivity. Organizations must evaluate how different HTTP servers and frameworks interact before deployment, as these implementation differences create the attack surface.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Key defensive measures:&lt;/strong&gt;&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;Sanitize all header variants at the proxy layer.&lt;/strong&gt; Block underscore variants, case variations, and any other formats that could be normalized by backend frameworks.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;Allowlist legitimate hop-by-hop headers.&lt;/strong&gt; Do not let arbitrary headers be designated as hop-by-hop through the &lt;code&gt;Connection&lt;/code&gt; header.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;Implement backend-side verification.&lt;/strong&gt; Consider cryptographic signing of authentication headers so backends can validate their authenticity regardless of proxy behavior. (Rarely implemented in practice, but the strongest mitigation.)&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;Audit your specific proxy-backend combination.&lt;/strong&gt; The attack depends on parsing mismatches between your proxy and backend framework. Test your actual stack for these gaps.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;Keep proxy software patched.&lt;/strong&gt; Both CVEs discussed here have patches available.&lt;/p&gt;&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;Most importantly, backends should validate rather than blindly trust security-critical headers from proxies. The fundamental lesson from both of these CVEs is the same: when the trust boundary between your proxy and backend is implicit rather than cryptographically enforced, every normalization difference and every protocol ambiguity becomes an attack surface.&lt;/p&gt;




&lt;h2&gt;
  
  
  References
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;&lt;a href="https://github.com/advisories/GHSA-q7p4-7xjv-j3wf" rel="noopener noreferrer"&gt;CVE-2025-48865 (Fabio)&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://github.com/oauth2-proxy/oauth2-proxy/security/advisories/GHSA-vjrc-mh2v-45x6" rel="noopener noreferrer"&gt;CVE-2025-64484 (OAuth2-proxy)&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://www.intruder.io/research/practical-http-header-smuggling" rel="noopener noreferrer"&gt;Intruder - Practical HTTP Header Smuggling&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://github.security.telekom.com/2020/05/smuggling-http-headers-through-reverse-proxies.html" rel="noopener noreferrer"&gt;Deutsche Telekom Security - Smuggling HTTP Headers Through Reverse Proxies&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://projectdiscovery.io/blog/versa-concerto-authentication-bypass-rce" rel="noopener noreferrer"&gt;ProjectDiscovery - Versa Concerto Authentication Bypass&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;

</description>
      <category>ai</category>
      <category>cybersecurity</category>
      <category>llm</category>
    </item>
    <item>
      <title>We Kept Breaking CI/CD Pipelines Across Every Platform. So We Built One Tool to Secure All of Them.</title>
      <dc:creator>Nathan Sportsman</dc:creator>
      <pubDate>Fri, 06 Mar 2026 19:49:29 +0000</pubDate>
      <link>https://dev.to/praetorian_guard/we-kept-breaking-cicd-pipelines-across-every-platform-so-we-built-one-tool-to-secure-all-of-them-1c2j</link>
      <guid>https://dev.to/praetorian_guard/we-kept-breaking-cicd-pipelines-across-every-platform-so-we-built-one-tool-to-secure-all-of-them-1c2j</guid>
      <description>&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.amazonaws.com%2Fuploads%2Farticles%2F0r0yj5y0yqnglczitgf5.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.amazonaws.com%2Fuploads%2Farticles%2F0r0yj5y0yqnglczitgf5.png" alt=" " width="800" height="434"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Your perimeter is hardened. Your EDR is mature. MFA is everywhere.&lt;/p&gt;

&lt;p&gt;And then there's the GitHub Actions workflow that runs code from any fork that opens a pull request.&lt;/p&gt;

&lt;p&gt;CI/CD pipelines have become the access vector of choice for attackers — and for Praetorian's red team. We released &lt;strong&gt;&lt;a href="https://github.com/praetorian-inc/gato" rel="noopener noreferrer"&gt;Gato&lt;/a&gt;&lt;/strong&gt; in 2023 to help others prevent the GitHub Actions vulnerabilities we kept exploiting. Then &lt;strong&gt;Glato&lt;/strong&gt; for GitLab CI.&lt;/p&gt;

&lt;p&gt;Useful tools. But every enterprise we assessed wasn't running one platform — it was three or four. GitHub for open-source. Azure DevOps for internal deployments. A GitLab instance the platform team owns. Jenkins on a server from 2017 that nobody wants to touch.&lt;/p&gt;

&lt;p&gt;Assessing those environments meant different tools for each platform, manual reviews where tooling didn't exist, and losing the consistency that makes security work repeatable.&lt;/p&gt;

&lt;p&gt;So we rebuilt from scratch.&lt;/p&gt;




&lt;h2&gt;
  
  
  Introducing Trajan
&lt;/h2&gt;

&lt;p&gt;&lt;strong&gt;&lt;a href="https://github.com/praetorian-inc/trajan" rel="noopener noreferrer"&gt;Trajan&lt;/a&gt;&lt;/strong&gt; is an open-source, cross-platform CI/CD vulnerability detection and attack automation tool. It currently supports:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;GitHub Actions&lt;/li&gt;
&lt;li&gt;GitLab CI&lt;/li&gt;
&lt;li&gt;Azure DevOps&lt;/li&gt;
&lt;li&gt;Jenkins&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;With Bitbucket Pipelines, CircleCI, AWS CodePipeline, and Google Cloud Build in active development.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;32 detection plugins. 24 attack plugins. One binary.&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;The core engine works the same way across all platforms: enumerate access → fetch workflow files via API → parse into a dependency graph → run detection plugins → optionally validate with attack modules.&lt;/p&gt;




&lt;h2&gt;
  
  
  What Trajan Finds
&lt;/h2&gt;

&lt;h3&gt;
  
  
  🔴 Poisoned Pipeline Execution
&lt;/h3&gt;

&lt;p&gt;The most common critical finding in CI/CD assessments. The mechanics vary by platform — expression injection in GitHub Actions, variable interpolation in Azure DevOps, YAML anchors in GitLab — but the result is always the same: attacker-controlled code running inside your build environment with access to your secrets.&lt;/p&gt;

&lt;p&gt;Trajan builds a workflow graph, traces user-controllable input to execution sinks, and identifies the exact path. Not just "this looks suspicious" — the precise nodes where a PR title becomes a shell command.&lt;/p&gt;

&lt;h3&gt;
  
  
  🔴 Secrets Exposure
&lt;/h3&gt;

&lt;p&gt;Secrets leak two ways:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;
&lt;strong&gt;Script behavior&lt;/strong&gt; — echoing variables, dumping credentials to logs, tokens in URL parameters&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Structural misconfigurations&lt;/strong&gt; — service connections scoped too broadly, secrets accessible on untrusted triggers, variable groups shared across projects&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;The second category is the dangerous one. It doesn't show up in any individual workflow file. Trajan maps cross-workflow resource relationships to surface overpermissioned secrets and hijackable service connections.&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;An upcoming integration with &lt;a href="https://github.com/praetorian-inc/titus" rel="noopener noreferrer"&gt;Titus&lt;/a&gt;, our secrets scanner, will extend this to credential detection in build logs and artifacts.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;h3&gt;
  
  
  🔴 Self-Hosted Runners
&lt;/h3&gt;

&lt;p&gt;Non-ephemeral runners are a lateral movement gift. They:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Persist between jobs&lt;/li&gt;
&lt;li&gt;Accumulate filesystem state&lt;/li&gt;
&lt;li&gt;Maintain network access to internal systems&lt;/li&gt;
&lt;li&gt;Often retain service account credentials from previous runs&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;If a vulnerable workflow runs on self-hosted infrastructure, the blast radius expands fast. Trajan identifies these jobs across all supported platforms and provides command execution capabilities for testing persistence.&lt;/p&gt;

&lt;h3&gt;
  
  
  🤖 AI/LLM Pipeline Vulnerabilities
&lt;/h3&gt;

&lt;p&gt;This is the category everyone is about to care about.&lt;/p&gt;

&lt;p&gt;GitHub Copilot is reviewing PRs. CodeRabbit is suggesting changes. Custom model workflows are analyzing commits. These AI actions sit inside privileged build environments, have access to repository secrets, and are often configured to receive untrusted input from pull requests.&lt;/p&gt;

&lt;p&gt;The attack pattern is straightforward: craft a malicious PR comment → AI action processes it → credentials exfiltrated in the model's output.&lt;/p&gt;

&lt;p&gt;Trajan detects these conditions through dedicated plugins covering:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Token exfiltration&lt;/li&gt;
&lt;li&gt;Code injection&lt;/li&gt;
&lt;li&gt;Workflow sabotage&lt;/li&gt;
&lt;li&gt;MCP abuse&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;It integrates &lt;strong&gt;Julius&lt;/strong&gt; (our LLM service fingerprinting tool) to identify deployed AI services from workflow YAML files, then hands off to &lt;strong&gt;Augustus&lt;/strong&gt; (our LLM vulnerability scanner) to validate exploitability across 210+ adversarial prompt injection payloads in six attack categories.&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;Organizations embedding AI into build pipelines need to treat these workflows as privileged code execution surfaces — not helpful automation.&lt;/p&gt;
&lt;/blockquote&gt;




&lt;h2&gt;
  
  
  Three Modules
&lt;/h2&gt;

&lt;h3&gt;
  
  
  &lt;code&gt;enumerate&lt;/code&gt; — Map Your Attack Surface First
&lt;/h3&gt;

&lt;p&gt;Validate credentials, discover repositories, identify secrets, find runners and build agents, enumerate service connections. Before you scan or attack anything, answer two questions: &lt;em&gt;what can this token reach, and where are the high-value targets?&lt;/em&gt;&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="o"&gt;&amp;gt;&lt;/span&gt; trajan ado enumerate variable-groups &lt;span class="nt"&gt;--org&lt;/span&gt; Middle-Earth-Arda &lt;span class="nt"&gt;--project&lt;/span&gt; Lothlorien

ID  NAME                    TYPE  VARIABLES
6   lothlorien-db-creds     Vsts  6
5   lothlorien-cloud-creds  Vsts  5
4   lothlorien-app-config   Vsts  6
Total: 3 variable &lt;span class="nb"&gt;groups&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  &lt;code&gt;scan&lt;/code&gt; — Run Detection Plugins Against Pipeline Configs
&lt;/h3&gt;

&lt;p&gt;Trajan fetches workflow files, parses them into a graph, and runs registered detectors. Same vulnerability classes across every platform — Trajan handles the syntax differences.&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="o"&gt;&amp;gt;&lt;/span&gt; trajan ado scan &lt;span class="nt"&gt;--org&lt;/span&gt; Middle-Earth-Arda &lt;span class="nt"&gt;--repo&lt;/span&gt; Lothlorien/Galadriel_repo &lt;span class="nt"&gt;--detailed&lt;/span&gt; &lt;span class="nt"&gt;--capabilities&lt;/span&gt; secrets-exposure

&lt;span class="o"&gt;[&lt;/span&gt;HIGH] unredacted_secrets
Workflow: unredacted_secrets.yml
Location: Line 20, Step: Debug Cloud Credentials

18   steps:
19     - script: |
20 →       &lt;span class="nb"&gt;echo&lt;/span&gt; &lt;span class="s2"&gt;"Debug: AWS_SECRET_ACCESS_KEY=&lt;/span&gt;&lt;span class="si"&gt;$(&lt;/span&gt;AWS_SECRET_ACCESS_KEY&lt;span class="si"&gt;)&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt;
21         &lt;span class="nb"&gt;echo&lt;/span&gt; &lt;span class="s2"&gt;"Debug: AZURE_CLIENT_SECRET=&lt;/span&gt;&lt;span class="si"&gt;$(&lt;/span&gt;AZURE_CLIENT_SECRET&lt;span class="si"&gt;)&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt;
22       displayName: &lt;span class="s1"&gt;'Debug Cloud Credentials'&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  &lt;code&gt;attack&lt;/code&gt; — Validate Exploitability
&lt;/h3&gt;

&lt;p&gt;Every attack plugin requires explicit opt-in, tracks all created artifacts in a session file, and supports cleanup after the engagement. The output is evidence, not just detection.&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="o"&gt;&amp;gt;&lt;/span&gt; trajan ado attack &lt;span class="nt"&gt;--org&lt;/span&gt; Middle-Earth-Arda &lt;span class="nt"&gt;--repo&lt;/span&gt; Lothlorien/Galadriel_repo &lt;span class="nt"&gt;--plugin&lt;/span&gt; secrets-dump &lt;span class="nt"&gt;--confirm&lt;/span&gt;

&lt;span class="o"&gt;[&lt;/span&gt;SUCCESS] ado-secrets-dump
146 environment variables, 3 variable &lt;span class="nb"&gt;groups&lt;/span&gt; &lt;span class="o"&gt;(&lt;/span&gt;15 secrets&lt;span class="o"&gt;)&lt;/span&gt;
Secrets written to: ado-secrets-07b00b13.txt

To cleanup: trajan ado attack cleanup &lt;span class="nt"&gt;--session&lt;/span&gt; 07b00b13
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;






&lt;h2&gt;
  
  
  Bonus: It Runs in the Browser
&lt;/h2&gt;

&lt;p&gt;Trajan compiles to &lt;strong&gt;WebAssembly&lt;/strong&gt; and ships as a single HTML file — same detection engine, same attack plugins, no installation required. For assessments where dropping a binary onto a system is friction, the web version removes the barrier entirely.&lt;/p&gt;




&lt;h2&gt;
  
  
  Getting Started
&lt;/h2&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="c"&gt;# Grab the latest release for your platform&lt;/span&gt;
&lt;span class="c"&gt;# Linux, macOS, and Windows binaries available&lt;/span&gt;

&lt;span class="c"&gt;# Enumerate your ADO environment&lt;/span&gt;
trajan ado enumerate token &lt;span class="nt"&gt;--org&lt;/span&gt; &amp;lt;your-org&amp;gt;

&lt;span class="c"&gt;# Scan a repository&lt;/span&gt;
trajan github scan &lt;span class="nt"&gt;--repo&lt;/span&gt; &amp;lt;owner/repo&amp;gt;

&lt;span class="c"&gt;# Full docs and examples at:&lt;/span&gt;
&lt;span class="c"&gt;# https://github.com/praetorian-inc/trajan&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;👉 &lt;strong&gt;&lt;a href="https://github.com/praetorian-inc/trajan" rel="noopener noreferrer"&gt;github.com/praetorian-inc/trajan&lt;/a&gt;&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;If you find bugs, want to contribute detection or attack plugins, or have feature requests — open an issue. Trajan is under active development and we want to hear how it holds up in real environments.&lt;/p&gt;




&lt;p&gt;&lt;em&gt;Trajan is part of Praetorian's 12 Caesars open-source security tool series. The series also includes &lt;a href="https://github.com/praetorian-inc/julius" rel="noopener noreferrer"&gt;Julius&lt;/a&gt; (LLM fingerprinting), &lt;a href="https://github.com/praetorian-inc/augustus" rel="noopener noreferrer"&gt;Augustus&lt;/a&gt; (LLM vulnerability scanning), &lt;a href="https://github.com/praetorian-inc/brutus" rel="noopener noreferrer"&gt;Brutus&lt;/a&gt; (credential testing), &lt;a href="https://github.com/praetorian-inc/titus" rel="noopener noreferrer"&gt;Titus&lt;/a&gt; (secrets scanning), and &lt;a href="https://github.com/praetorian-inc/nerva" rel="noopener noreferrer"&gt;Nerva&lt;/a&gt; (service fingerprinting).&lt;/em&gt;&lt;/p&gt;

</description>
      <category>ai</category>
      <category>webdev</category>
      <category>programming</category>
      <category>cybersecurity</category>
    </item>
    <item>
      <title>I Built an Open-Source Service Fingerprinter. Here’s What It Finds.</title>
      <dc:creator>Nathan Sportsman</dc:creator>
      <pubDate>Mon, 02 Mar 2026 17:55:54 +0000</pubDate>
      <link>https://dev.to/praetorian_guard/i-built-an-open-source-service-fingerprinter-heres-what-it-finds-2c2d</link>
      <guid>https://dev.to/praetorian_guard/i-built-an-open-source-service-fingerprinter-heres-what-it-finds-2c2d</guid>
      <description>&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.amazonaws.com%2Fuploads%2Farticles%2Fap6ofayrlu45csixhh1h.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.amazonaws.com%2Fuploads%2Farticles%2Fap6ofayrlu45csixhh1h.png" alt=" " width="777" height="418"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;TL;DR:&lt;/strong&gt;&lt;br&gt;&lt;br&gt;
Nerva is a high-performance, open-source CLI tool for identifying services running on open ports. It fingerprints 120+ protocols across TCP, UDP, and SCTP, averages 4× faster than &lt;code&gt;nmap -sV&lt;/code&gt;, and maintains 99% detection accuracy. Written in Go as a single binary, it helps security teams move from port discovery to actionable service intelligence fast.&lt;/p&gt;




&lt;h2&gt;
  
  
  The Recon Bottleneck Nobody Talks About
&lt;/h2&gt;

&lt;p&gt;I spend most of my time breaking into things for a living. Networks, web apps, cloud infrastructure. And in every engagement, there’s a moment during recon where I’m staring at a list of open ports thinking:&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;What is actually running here?&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;Port numbers don’t tell the full story.&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;code&gt;8080&lt;/code&gt; might be a forgotten dev server
&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;9200&lt;/code&gt; could be an exposed Elasticsearch cluster
&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;4840&lt;/code&gt; might be OPC-UA in an OT network
&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;6443&lt;/code&gt; could be a Kubernetes API that should never be internet-facing
&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;We have excellent tools for discovering open ports.&lt;br&gt;&lt;br&gt;
Masscan. RustScan. Naabu.  &lt;/p&gt;

&lt;p&gt;Port discovery is a solved problem.&lt;/p&gt;

&lt;p&gt;Service identification is not.&lt;/p&gt;

&lt;p&gt;And that gap slows everything down.&lt;/p&gt;




&lt;h2&gt;
  
  
  The Gap Between Discovery and Understanding
&lt;/h2&gt;

&lt;p&gt;After a fast scan, you might have thousands of open ports across hundreds of hosts. Now comes the real question:&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;What are they?&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;Nmap does service detection well. But it prioritizes accuracy over speed. When you're fingerprinting thousands of endpoints, it becomes the bottleneck.&lt;/p&gt;

&lt;p&gt;Tools like zgrab2 are fast, but they assume you already know what protocol you’re targeting.&lt;/p&gt;

&lt;p&gt;That assumption is the problem.&lt;/p&gt;

&lt;p&gt;Across multiple engagements, we kept hitting the same friction point:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Great port discovery
&lt;/li&gt;
&lt;li&gt;No purpose-built, high-speed service fingerprinting layer
&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;So we built one.&lt;/p&gt;




&lt;h1&gt;
  
  
  Meet Nerva
&lt;/h1&gt;

&lt;p&gt;Nerva is an open-source service fingerprinting tool.&lt;/p&gt;

&lt;p&gt;You give it a host and port.&lt;br&gt;&lt;br&gt;
It tells you what’s running.&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;120+ protocols
&lt;/li&gt;
&lt;li&gt;TCP, UDP, and SCTP support
&lt;/li&gt;
&lt;li&gt;Single Go binary
&lt;/li&gt;
&lt;li&gt;Zero dependencies
&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  Install
&lt;/h2&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;
bash
go install github.com/praetorian-inc/nerva/cmd/nerva@latest
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;

</description>
      <category>ai</category>
      <category>cybersecurity</category>
      <category>llmsec</category>
    </item>
    <item>
      <title>There's Always a Hardcoded Secret Somewhere — Meet Titus</title>
      <dc:creator>Nathan Sportsman</dc:creator>
      <pubDate>Fri, 20 Feb 2026 20:02:44 +0000</pubDate>
      <link>https://dev.to/praetorian_guard/theres-always-a-hardcoded-secret-somewhere-meet-titus-fop</link>
      <guid>https://dev.to/praetorian_guard/theres-always-a-hardcoded-secret-somewhere-meet-titus-fop</guid>
      <description>&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.amazonaws.com%2Fuploads%2Farticles%2Frh3qpzmfhoitlim9ntmx.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.amazonaws.com%2Fuploads%2Farticles%2Frh3qpzmfhoitlim9ntmx.png" alt=" "&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;It's week two of a red team engagement. You've got a foothold, a hundred cloned repos on your laptop, fifty more still enumerating from the org's GitHub, and you're eight Burp Repeater tabs deep into their internal web portal.&lt;/p&gt;

&lt;p&gt;Somewhere in that pile of code and HTTP responses is a hardcoded AWS key, a Stripe secret, or an internal service token that nobody rotated after the last contractor left.&lt;/p&gt;

&lt;p&gt;There's &lt;em&gt;always&lt;/em&gt; something. It's just a matter of how long it takes you to find it.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://github.com/praetorian-inc/titus" rel="noopener noreferrer"&gt;Titus&lt;/a&gt; is an open source secret scanner from Praetorian that detects and validates leaked credentials across source code, binary files, and HTTP traffic. It ships with 450+ detection rules and runs as a CLI, Go library, Burp Suite extension, or Chrome browser extension — same engine, same rules, four places you're already working.&lt;/p&gt;

&lt;p&gt;In some cases, you don't change your workflow at all. You just find secrets for free.&lt;/p&gt;

&lt;h2&gt;
  
  
  Why Not Just Use Nosey Parker?
&lt;/h2&gt;

&lt;p&gt;Titus isn't our first scanner. In 2022 we released Nosey Parker — regex-based detection plus an ML denoiser trained on real engagement data. It was fast (up to 100x faster than common alternatives), the ML layer cut noise significantly, and it scaled to tens of terabytes of source code on modest hardware.&lt;/p&gt;

&lt;p&gt;But Nosey Parker was written in Rust, and our ecosystem is overwhelmingly Go.&lt;/p&gt;

&lt;p&gt;Embedding a Rust binary as a subprocess works. It's not the same as calling a function.&lt;/p&gt;

&lt;p&gt;We wanted this:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight go"&gt;&lt;code&gt;&lt;span class="n"&gt;scanner&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;ScanString&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;content&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;


&lt;p&gt;Not this:&lt;br&gt;
&lt;/p&gt;
&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight go"&gt;&lt;code&gt;&lt;span class="n"&gt;exec&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Command&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"noseyparker"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s"&gt;"scan"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s"&gt;"--stdin"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;


&lt;p&gt;So we ported it. Titus is a Go implementation of the same detection engine, carrying forward the battle-tested rules and adding capabilities that only make sense when the scanner lives in the same language as everything around it.&lt;/p&gt;
&lt;h2&gt;
  
  
  Secrets Validation — The Killer Feature
&lt;/h2&gt;

&lt;p&gt;Regex scanners find patterns that &lt;em&gt;look like&lt;/em&gt; secrets. On a large engagement you get hundreds of hits. Some are live. Some are revoked. Some are test fixtures from a tutorial someone copy-pasted three years ago.&lt;/p&gt;

&lt;p&gt;Titus tells you which is which.&lt;br&gt;
&lt;/p&gt;
&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;titus scan path/to/code &lt;span class="nt"&gt;--validate&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;


&lt;p&gt;Each rule can optionally define a validator — a small YAML block specifying an HTTP request to make with the captured secret and how to interpret the response:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;API returns &lt;code&gt;200&lt;/code&gt; → key is &lt;strong&gt;live&lt;/strong&gt;
&lt;/li&gt;
&lt;li&gt;API returns &lt;code&gt;401&lt;/code&gt;/&lt;code&gt;403&lt;/code&gt; → key is &lt;strong&gt;revoked&lt;/strong&gt;
&lt;/li&gt;
&lt;li&gt;Endpoint unreachable → marked &lt;strong&gt;unknown&lt;/strong&gt;
&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;The scanner runs regex matching, then kicks off concurrent validation workers (4 by default, tunable with &lt;code&gt;--validate-workers&lt;/code&gt;) against any finding with a validator defined. Each result gets tagged &lt;code&gt;confirmed&lt;/code&gt;, &lt;code&gt;denied&lt;/code&gt;, or &lt;code&gt;unknown&lt;/code&gt;.&lt;/p&gt;

&lt;p&gt;Knowing which keys are live &lt;em&gt;before&lt;/em&gt; you start writing your report or attempting lateral movement changes how you spend the rest of your engagement.&lt;/p&gt;
&lt;h2&gt;
  
  
  Binary File Scanning
&lt;/h2&gt;

&lt;p&gt;Most secret scanners stop at plaintext. Titus doesn't.&lt;/p&gt;

&lt;p&gt;It cracks open Office documents (xlsx, docx, pptx), PDFs, Jupyter notebooks, SQLite databases, and common archive formats (zip, tar, tar.gz, jar, war, ear, apk, ipa, crx). Archives are recursively extracted up to configurable depth and size limits — a zip inside a zip still gets scanned.&lt;br&gt;
&lt;/p&gt;
&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;titus scan path/to/files &lt;span class="nt"&gt;--extract&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;all
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;


&lt;p&gt;We routinely find credentials in exported spreadsheets, embedded in Jupyter notebooks, or buried in mobile app packages that shipped with a hardcoded API key. Target specific formats with &lt;code&gt;--extract=xlsx,pdf,zip&lt;/code&gt; to keep scan times tight.&lt;/p&gt;
&lt;h2&gt;
  
  
  450+ Detection Rules
&lt;/h2&gt;

&lt;p&gt;The rule set comes from two sources:&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;~200 from Nosey Parker&lt;/strong&gt; — credential patterns from years of engagements: AWS, GCP, Azure, GitHub tokens, Slack webhooks, database connection strings, and dozens more.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;~250+ from Kingfisher&lt;/strong&gt; — MongoDB's Nosey Parker fork that added patterns for Stripe, Twilio, SendGrid, Datadog, and hundreds of other SaaS platforms. We pulled their rules directly into Titus rather than duplicating the work.&lt;/p&gt;

&lt;p&gt;Rule format stays identical to Nosey Parker, so pulling from other forks and contributing back is frictionless. If a service has an API key, there's probably a rule for it. If not, they're easy to add.&lt;/p&gt;
&lt;h2&gt;
  
  
  One Engine, Four Interfaces
&lt;/h2&gt;
&lt;h3&gt;
  
  
  CLI
&lt;/h3&gt;


&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;titus scan &lt;span class="nt"&gt;--git&lt;/span&gt; path/to/repo &lt;span class="nt"&gt;--format&lt;/span&gt; sarif
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;


&lt;p&gt;Point it at a file, directory, or git repo. SARIF output pipes into CI/CD pipelines.&lt;/p&gt;
&lt;h3&gt;
  
  
  Go Library
&lt;/h3&gt;


&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight go"&gt;&lt;code&gt;&lt;span class="n"&gt;scanner&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;_&lt;/span&gt; &lt;span class="o"&gt;:=&lt;/span&gt; &lt;span class="n"&gt;titus&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;NewScanner&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;titus&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;WithValidation&lt;/span&gt;&lt;span class="p"&gt;())&lt;/span&gt;
&lt;span class="n"&gt;matches&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;_&lt;/span&gt; &lt;span class="o"&gt;:=&lt;/span&gt; &lt;span class="n"&gt;scanner&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;ScanString&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;httpResponseBody&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;


&lt;p&gt;Import it directly. No subprocesses, no parsing stdout.&lt;/p&gt;
&lt;h3&gt;
  
  
  Burp Suite Extension
&lt;/h3&gt;

&lt;p&gt;Launches &lt;code&gt;titus serve&lt;/code&gt; at startup, scans HTTP responses as they flow through the proxy. Passive scanning requires zero interaction — you browse, it finds secrets. You can also actively re-run the scanner against selected requests in an existing Burp project.&lt;/p&gt;
&lt;h3&gt;
  
  
  Chrome Extension
&lt;/h3&gt;

&lt;p&gt;For web app assessments without Burp. Scans JavaScript, stylesheets, localStorage, and sessionStorage as you navigate. Same engine and ruleset compiled to WASM. It pops up an Xbox-style achievement notification every time it finds a secret. We're not sorry.&lt;/p&gt;

&lt;p&gt;Especially handy in assumed breach contexts where you can't install Burp but have browser access to internal resources.&lt;/p&gt;

&lt;p&gt;All four interfaces share the same repo, rule set, and build. Add a rule, it propagates everywhere.&lt;/p&gt;
&lt;h2&gt;
  
  
  After You Find Secrets
&lt;/h2&gt;
&lt;h3&gt;
  
  
  LLM-Assisted Denoising
&lt;/h3&gt;

&lt;p&gt;Feed each finding's surrounding context into an LLM and ask whether it looks like a real credential or a false positive. In our testing this eliminates a significant chunk of noise. We're working on an &lt;code&gt;--llm-denoise&lt;/code&gt; flag that integrates with major providers.&lt;/p&gt;
&lt;h3&gt;
  
  
  Credential Spraying with Brutus
&lt;/h3&gt;

&lt;p&gt;Found passwords or reusable credentials? Feed them into &lt;a href="https://github.com/praetorian-inc/brutus" rel="noopener noreferrer"&gt;Brutus&lt;/a&gt;, our credential spraying tool. It takes a set of credentials and sprays them across SSH, RDP, SMB, database protocols, and more.&lt;/p&gt;

&lt;p&gt;Titus finds the credentials. Brutus tests them at scale. Both are part of a broader tooling suite we're releasing over the coming weeks.&lt;/p&gt;
&lt;h2&gt;
  
  
  Get Started
&lt;/h2&gt;

&lt;p&gt;Titus is open source: &lt;a href="https://github.com/praetorian-inc/titus" rel="noopener noreferrer"&gt;github.com/praetorian-inc/titus&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;

&lt;/p&gt;
&lt;div class="ltag-github-readme-tag"&gt;
  &lt;div class="readme-overview"&gt;
    &lt;h2&gt;
      &lt;img src="https://assets.dev.to/assets/github-logo-5a155e1f9a670af7944dd5e12375bc76ed542ea80224905ecaf878b9157cdefc.svg" alt="GitHub logo"&gt;
      &lt;a href="https://github.com/praetorian-inc" rel="noopener noreferrer"&gt;
        praetorian-inc
      &lt;/a&gt; / &lt;a href="https://github.com/praetorian-inc/titus" rel="noopener noreferrer"&gt;
        titus
      &lt;/a&gt;
    &lt;/h2&gt;
    &lt;h3&gt;
      High-performance secrets scanner. CLI, Go library, Burp Suite extension, and Chrome extension. 487 detection rules with live credential validation.
    &lt;/h3&gt;
  &lt;/div&gt;
  &lt;div class="ltag-github-body"&gt;
    
&lt;div id="readme" class="md"&gt;
&lt;a rel="noopener noreferrer" href="https://private-user-images.githubusercontent.com/3682576/551140403-c42a712a-58bb-48bf-a947-abba8a851e68.png?jwt=eyJ0eXAiOiJKV1QiLCJhbGciOiJIUzI1NiJ9.eyJpc3MiOiJnaXRodWIuY29tIiwiYXVkIjoicmF3LmdpdGh1YnVzZXJjb250ZW50LmNvbSIsImtleSI6ImtleTUiLCJleHAiOjE3NzQ2NDI5NzEsIm5iZiI6MTc3NDY0MjY3MSwicGF0aCI6Ii8zNjgyNTc2LzU1MTE0MDQwMy1jNDJhNzEyYS01OGJiLTQ4YmYtYTk0Ny1hYmJhOGE4NTFlNjgucG5nP1gtQW16LUFsZ29yaXRobT1BV1M0LUhNQUMtU0hBMjU2JlgtQW16LUNyZWRlbnRpYWw9QUtJQVZDT0RZTFNBNTNQUUs0WkElMkYyMDI2MDMyNyUyRnVzLWVhc3QtMSUyRnMzJTJGYXdzNF9yZXF1ZXN0JlgtQW16LURhdGU9MjAyNjAzMjdUMjAxNzUxWiZYLUFtei1FeHBpcmVzPTMwMCZYLUFtei1TaWduYXR1cmU9YTM4ZTAxOGNhODI0ZmMxZDU3MTMzNGQyZGI2MjlkNDk0OTcyMTU4Y2JjOTkyNTdlZTI0OGVjNDE5MDVjMGMzOCZYLUFtei1TaWduZWRIZWFkZXJzPWhvc3QifQ.gKkE6LHogXUuvY84x0RLWarC5BB-Y7ix6MbjD_AIrqU"&gt;&lt;img width="1200" height="628" alt="Titus - high-performance secrets scanner for source code, git history, and binary files" src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fprivate-user-images.githubusercontent.com%2F3682576%2F551140403-c42a712a-58bb-48bf-a947-abba8a851e68.png%3Fjwt%3DeyJ0eXAiOiJKV1QiLCJhbGciOiJIUzI1NiJ9.eyJpc3MiOiJnaXRodWIuY29tIiwiYXVkIjoicmF3LmdpdGh1YnVzZXJjb250ZW50LmNvbSIsImtleSI6ImtleTUiLCJleHAiOjE3NzQ2NDI5NzEsIm5iZiI6MTc3NDY0MjY3MSwicGF0aCI6Ii8zNjgyNTc2LzU1MTE0MDQwMy1jNDJhNzEyYS01OGJiLTQ4YmYtYTk0Ny1hYmJhOGE4NTFlNjgucG5nP1gtQW16LUFsZ29yaXRobT1BV1M0LUhNQUMtU0hBMjU2JlgtQW16LUNyZWRlbnRpYWw9QUtJQVZDT0RZTFNBNTNQUUs0WkElMkYyMDI2MDMyNyUyRnVzLWVhc3QtMSUyRnMzJTJGYXdzNF9yZXF1ZXN0JlgtQW16LURhdGU9MjAyNjAzMjdUMjAxNzUxWiZYLUFtei1FeHBpcmVzPTMwMCZYLUFtei1TaWduYXR1cmU9YTM4ZTAxOGNhODI0ZmMxZDU3MTMzNGQyZGI2MjlkNDk0OTcyMTU4Y2JjOTkyNTdlZTI0OGVjNDE5MDVjMGMzOCZYLUFtei1TaWduZWRIZWFkZXJzPWhvc3QifQ.gKkE6LHogXUuvY84x0RLWarC5BB-Y7ix6MbjD_AIrqU"&gt;&lt;/a&gt;
&lt;div class="markdown-heading"&gt;
&lt;h1 class="heading-element"&gt;Titus: High-Performance Secrets Scanner&lt;/h1&gt;
&lt;/div&gt;

&lt;p&gt;&lt;a href="https://go.dev" rel="nofollow noopener noreferrer"&gt;&lt;img src="https://camo.githubusercontent.com/cf53c4ea2c6803a2b92d465261fda570c8952b4275c1deda81a96d54f40f0920/68747470733a2f2f696d672e736869656c64732e696f2f62616467652f476f2d312e32332b2d3030414444383f6c6f676f3d676f266c6f676f436f6c6f723d7768697465" alt="Go"&gt;&lt;/a&gt;
&lt;a href="https://github.com/praetorian-inc/titus/LICENSE" rel="noopener noreferrer"&gt;&lt;img src="https://camo.githubusercontent.com/a549a7a30bacba7bfceebdc207a8e86c3f2c02995a2527640dca30048fd2b64e/68747470733a2f2f696d672e736869656c64732e696f2f62616467652f4c6963656e73652d417061636865253230322e302d626c75652e737667" alt="License"&gt;&lt;/a&gt;
&lt;a href="https://github.com/praetorian-inc/titus/actions" rel="noopener noreferrer"&gt;&lt;img src="https://camo.githubusercontent.com/4ced93cf806f3745beeb3e50852cfc80861d9a9e194b4e86c6eda0e37887c2bc/68747470733a2f2f696d672e736869656c64732e696f2f6769746875622f616374696f6e732f776f726b666c6f772f7374617475732f70726165746f7269616e2d696e632f74697475732f63692e796d6c3f6272616e63683d6d61696e266c6162656c3d4349" alt="CI"&gt;&lt;/a&gt;&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Titus&lt;/strong&gt; is a high-performance secrets scanner that detects credentials, API keys, and tokens in source code, files, and git history. It ships with 487 detection rules covering hundreds of services and credential types, drawn from &lt;a href="https://github.com/praetorian-inc/noseyparker" rel="noopener noreferrer"&gt;NoseyParker&lt;/a&gt; and &lt;a href="https://github.com/mongodb/kingfisher" rel="noopener noreferrer"&gt;Kingfisher&lt;/a&gt;. Titus runs as a CLI, a Go library, a Burp Suite extension, and a Chrome browser extension — all sharing the same detection engine and rule set.&lt;/p&gt;
&lt;p&gt;Built for security engineers, penetration testers, and DevSecOps teams, Titus combines &lt;a href="https://github.com/intel/hyperscan" rel="noopener noreferrer"&gt;Hyperscan&lt;/a&gt;/&lt;a href="https://github.com/VectorCamp/vectorscan" rel="noopener noreferrer"&gt;Vectorscan&lt;/a&gt;-accelerated regex matching with live credential validation to find and verify leaked secrets across your entire codebase.&lt;/p&gt;
&lt;div class="markdown-heading"&gt;
&lt;h2 class="heading-element"&gt;Table of Contents&lt;/h2&gt;
&lt;/div&gt;
&lt;ul&gt;
&lt;li&gt;&lt;a href="https://github.com/praetorian-inc/titus#why-titus" rel="noopener noreferrer"&gt;Why Titus?&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://github.com/praetorian-inc/titus#installation" rel="noopener noreferrer"&gt;Installation&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://github.com/praetorian-inc/titus#quick-start" rel="noopener noreferrer"&gt;Quick Start&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://github.com/praetorian-inc/titus#scanning-options" rel="noopener noreferrer"&gt;Scanning Options&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://github.com/praetorian-inc/titus#go-library-for-secrets-detection" rel="noopener noreferrer"&gt;Go Library&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://github.com/praetorian-inc/titus#burp-suite-extension-for-secret-scanning" rel="noopener noreferrer"&gt;Burp Suite Extension&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://github.com/praetorian-inc/titus#chrome-browser-extension-for-secret-scanning" rel="noopener noreferrer"&gt;Browser Extension&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://github.com/praetorian-inc/titus#building-from-source" rel="noopener noreferrer"&gt;Building from Source&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://github.com/praetorian-inc/titus#contributing" rel="noopener noreferrer"&gt;Contributing&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://github.com/praetorian-inc/titus#license" rel="noopener noreferrer"&gt;License&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;div class="markdown-heading"&gt;
&lt;h2 class="heading-element"&gt;Why Titus?&lt;/h2&gt;
&lt;/div&gt;
&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Fast secrets scanning&lt;/strong&gt;: Regex matching accelerated by &lt;a href="https://github.com/intel/hyperscan" rel="noopener noreferrer"&gt;Hyperscan&lt;/a&gt;/&lt;a href="https://github.com/VectorCamp/vectorscan" rel="noopener noreferrer"&gt;Vectorscan&lt;/a&gt; when available, with a pure-Go fallback for portability on any platform.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Broad credential detection&lt;/strong&gt;…&lt;/li&gt;
&lt;/ul&gt;
&lt;/div&gt;
  &lt;/div&gt;
  &lt;div class="gh-btn-container"&gt;&lt;a class="gh-btn" href="https://github.com/praetorian-inc/titus" rel="noopener noreferrer"&gt;View on GitHub&lt;/a&gt;&lt;/div&gt;
&lt;/div&gt;




&lt;p&gt;There's always a secret hiding somewhere. Titus just helps you find it faster.&lt;/p&gt;

</description>
      <category>ai</category>
      <category>llmsec</category>
      <category>cybersecurity</category>
      <category>aisec</category>
    </item>
    <item>
      <title>We Replaced Our Bash Scripts and Hydra With a Single Go Binary for Credential Testing</title>
      <dc:creator>Nathan Sportsman</dc:creator>
      <pubDate>Fri, 13 Feb 2026 16:10:48 +0000</pubDate>
      <link>https://dev.to/praetorian_guard/we-replaced-our-bash-scripts-and-hydra-with-a-single-go-binary-for-credential-testing-3p5c</link>
      <guid>https://dev.to/praetorian_guard/we-replaced-our-bash-scripts-and-hydra-with-a-single-go-binary-for-credential-testing-3p5c</guid>
      <description>&lt;p&gt;Every few months, I watch one of our engineers burn an hour on an engagement trying to get THC Hydra compiled on a stripped-down jump box. Missing &lt;code&gt;libssh-dev&lt;/code&gt;. Wrong version of &lt;code&gt;libmysqlclient-dev&lt;/code&gt;. Package headers that don't exist on whatever minimal container they're working from. And that's before they've tested a single credential.&lt;/p&gt;

&lt;p&gt;Then they test credentials, get results in Hydra's human-readable terminal output, and spend another chunk of time writing a parsing script to get that data into a format the rest of the pipeline can use. On the next engagement, they write the same script again, slightly different, because the output changed or the use case shifted.&lt;/p&gt;

&lt;p&gt;This has been the state of credential testing tooling for years. It works, but it's held together with duct tape. We finally decided to build something better.&lt;/p&gt;

&lt;h2&gt;
  
  
  The Tool Tax
&lt;/h2&gt;

&lt;p&gt;If you've done any kind of security assessment work — or even just managed infrastructure at scale — you've probably dealt with some version of this problem. Your reconnaissance tools output JSON. Your reporting tools expect JSON. But the tool in the middle speaks its own format and requires you to translate in both directions.&lt;/p&gt;

&lt;p&gt;Modern recon workflows are built around tools like &lt;a href="https://github.com/projectdiscovery/naabu" rel="noopener noreferrer"&gt;naabu&lt;/a&gt; for port scanning and &lt;a href="https://github.com/praetorian-inc/fingerprintx" rel="noopener noreferrer"&gt;fingerprintx&lt;/a&gt; for service identification. They chain together cleanly because they share a common data format. Credential testing has been the gap in that pipeline — the step where you drop out of structured data and into ad hoc scripts.&lt;/p&gt;

&lt;p&gt;That translation layer between tools isn't just annoying. It's where mistakes happen. Hosts get dropped. Formats get misread. Results get lost. On a network with 700,000 live hosts and thousands of identified services, "good enough" glue scripts have real consequences.&lt;/p&gt;

&lt;h2&gt;
  
  
  Why Go
&lt;/h2&gt;

&lt;p&gt;The language choice was deliberate and it comes down to one thing: distribution.&lt;/p&gt;

&lt;p&gt;When your tool needs to run on whatever box you land on during an engagement — a hardened jump host, a minimal container, a client workstation with nothing installed — your dependency story matters more than almost any other technical decision.&lt;/p&gt;

&lt;p&gt;Go gives us a statically compiled binary. No runtime. No shared libraries. No package manager on the target. Download the binary, run it. That's the entire setup process.&lt;/p&gt;

&lt;p&gt;This isn't a theoretical benefit. THC Hydra's protocol support comes from linking against system libraries: &lt;code&gt;libssh&lt;/code&gt; for SSH, &lt;code&gt;libmysqlclient&lt;/code&gt; for MySQL, &lt;code&gt;libpq&lt;/code&gt; for PostgreSQL. Each library is a potential compilation failure on a system that wasn't set up for building C projects. Go's SSH support is in the standard library ecosystem. Database drivers are pure Go. Everything compiles into one artifact.&lt;/p&gt;

&lt;p&gt;The concurrency model is the other half of the equation. Credential testing is embarrassingly parallel — you're making thousands of independent authentication attempts. Goroutines and channels map onto this problem naturally without the overhead of managing thread pools or process spawning.&lt;/p&gt;

&lt;h2&gt;
  
  
  Plugin Architecture in a Single Binary
&lt;/h2&gt;

&lt;p&gt;One design constraint we set early: Brutus ships as a single binary, but adding a new protocol shouldn't require understanding the whole codebase. These goals are in tension, and the plugin architecture is how we resolved it.&lt;/p&gt;

&lt;p&gt;Each protocol — SSH, MySQL, FTP, HTTP Basic, etc. — is a self-contained plugin that implements a common interface. The plugin registers itself, declares what service identifiers it handles (matching fingerprintx output), and implements the authentication logic. The core engine handles concurrency, input parsing, output formatting, and retry logic. Plugins just authenticate.&lt;/p&gt;

&lt;p&gt;This means contributing a new protocol looks roughly like:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;Create a new file in the plugins directory&lt;/li&gt;
&lt;li&gt;Implement the authentication interface&lt;/li&gt;
&lt;li&gt;Register the plugin with the service identifier it handles&lt;/li&gt;
&lt;li&gt;Compile&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;The new protocol is immediately available in the pipeline. No configuration files, no dynamic loading, no plugin directories to manage. It compiles into the same single binary.&lt;/p&gt;

&lt;h2&gt;
  
  
  Compiling Known-Bad SSH Keys Into the Binary
&lt;/h2&gt;

&lt;p&gt;This is probably the most unusual design decision in Brutus, and the one I think has the most practical value.&lt;/p&gt;

&lt;p&gt;The security community has catalogued a large number of publicly known, compromised SSH keys. Rapid7 maintains the &lt;a href="https://github.com/rapid7/ssh-badkeys" rel="noopener noreferrer"&gt;ssh-badkeys&lt;/a&gt; repository. HashiCorp's Vagrant ships with a well-known insecure key. Various appliance vendors — F5 BIG-IP, ExaGrid, Ceragon FibeAir — have shipped products with embedded keys that are now public.&lt;/p&gt;

&lt;p&gt;Testing for these keys across an environment is something that should be trivial but traditionally isn't. You need to find the key collections, download them, write a script to iterate through them, handle SSH connection logic and timeouts, and keep track of which key you're testing. It's not complex work, it's just tedious enough that it gets done incompletely.&lt;/p&gt;

&lt;p&gt;Brutus embeds all of these key collections directly into the binary using Go's &lt;code&gt;embed&lt;/code&gt; package. When it encounters an SSH service, it automatically tests every known-bad key. Each key carries metadata: the expected default username (&lt;code&gt;root&lt;/code&gt; for F5, &lt;code&gt;vagrant&lt;/code&gt; for Vagrant, &lt;code&gt;mateidu&lt;/code&gt; for Ceragon) and context about which vulnerability it represents.&lt;/p&gt;

&lt;p&gt;The output doesn't just say "this key authenticated." It tells you which known-compromised key matched, what product it's associated with, and what CVE or advisory applies. That's the difference between a finding and an actionable finding.&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="c"&gt;# Test every SSH service in your naabu/fingerprintx output&lt;/span&gt;
&lt;span class="c"&gt;# against every known-compromised key, automatically&lt;/span&gt;
&lt;span class="nb"&gt;cat &lt;/span&gt;recon_output.json | brutus
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;No flags needed. If the service is SSH, bad keys get tested.&lt;/p&gt;

&lt;h2&gt;
  
  
  The Pipeline
&lt;/h2&gt;

&lt;p&gt;The core workflow Brutus was designed for looks like 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="c"&gt;# Port scan → Service identification → Credential testing&lt;/span&gt;
naabu &lt;span class="nt"&gt;-host&lt;/span&gt; 10.0.0.0/8 &lt;span class="nt"&gt;-p&lt;/span&gt; 22,3306,5432,8080 &lt;span class="nt"&gt;-silent&lt;/span&gt; | &lt;span class="se"&gt;\&lt;/span&gt;
  fingerprintx &lt;span class="nt"&gt;--json&lt;/span&gt; | &lt;span class="se"&gt;\&lt;/span&gt;
  brutus &lt;span class="nt"&gt;-u&lt;/span&gt; admin &lt;span class="nt"&gt;-p&lt;/span&gt; password123
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Each tool reads from stdin and writes to stdout. JSON in, JSON out. No intermediate files, no format conversion, no glue scripts.&lt;/p&gt;

&lt;p&gt;For more targeted work — say you recovered a private key from a compromised system and need to find everywhere it grants access:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;naabu &lt;span class="nt"&gt;-host&lt;/span&gt; 10.1.0.0/24 &lt;span class="nt"&gt;-p&lt;/span&gt; 22 &lt;span class="nt"&gt;-silent&lt;/span&gt; | &lt;span class="se"&gt;\&lt;/span&gt;
  fingerprintx &lt;span class="nt"&gt;--json&lt;/span&gt; | &lt;span class="se"&gt;\&lt;/span&gt;
  brutus &lt;span class="nt"&gt;-u&lt;/span&gt; nessus &lt;span class="nt"&gt;-k&lt;/span&gt; /path/to/recovered_key
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The output is structured JSON. Every valid credential, the host it worked against, the protocol, the timestamp. You can query it with &lt;code&gt;jq&lt;/code&gt;, pipe it into your reporting toolchain, or feed it into whatever comes next.&lt;/p&gt;

&lt;h2&gt;
  
  
  Experimental: LLM-Powered Credential Discovery
&lt;/h2&gt;

&lt;p&gt;This is the part I want to be upfront about: these features are experimental. They work, they're useful in the scenarios we've tested, and they represent something I think is genuinely interesting from an engineering perspective. But they depend on external API services, they add latency and cost, and LLMs are non-deterministic. Treat them accordingly.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;The problem:&lt;/strong&gt; You land on an internal network. You scan it. You find dozens of HTTP services on non-standard ports — management interfaces for switches, storage appliances, monitoring tools, IPMI consoles, printer admin panels. Each one probably has default credentials, but you'd need to identify the product first, then look up its defaults. Manually, across dozens of services, this is painfully slow.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Approach one — response analysis:&lt;/strong&gt; Brutus captures the HTTP response (headers, page content, server signatures) and sends it to an LLM. The model identifies the application and suggests vendor-specific default credentials. Those get tested first, with fallback to generic wordlists.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Approach two — visual authentication:&lt;/strong&gt; Some login pages are JavaScript-rendered with CSRF tokens, multi-step flows, or non-standard field names. Brutus uses headless Chrome to render the page, takes a screenshot, sends it to Claude's vision API for identification, then fills and submits the form. It compares page state before and after to determine success.&lt;/p&gt;

&lt;p&gt;Both of these are solving real workflow problems. Whether LLMs are the right long-term solution or a stepping stone to something more deterministic is an open question. But right now, they work better than the alternative of doing it manually.&lt;/p&gt;

&lt;h2&gt;
  
  
  What We're Looking For
&lt;/h2&gt;

&lt;p&gt;Brutus is open source under Apache 2.0. The things that would make the biggest impact:&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;New protocol plugins.&lt;/strong&gt; If there's a service you test credentials against that isn't supported, the plugin interface is designed to make this straightforward.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Bad key collections.&lt;/strong&gt; If you've encountered default or embedded SSH keys in appliances, IoT devices, or vendor products that aren't in our current collection, adding them benefits everyone.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Real-world feedback.&lt;/strong&gt; We've battle-tested this on our own engagements, but every environment is different.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Repo:&lt;/strong&gt; &lt;a href="https://github.com/praetorian-inc/brutus" rel="noopener noreferrer"&gt;github.com/praetorian-inc/brutus&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Blog with full technical details:&lt;/strong&gt; &lt;a href="https://www.praetorian.com/blog/brutus" rel="noopener noreferrer"&gt;praetorian.com/blog/brutus&lt;/a&gt;&lt;/p&gt;




&lt;p&gt;&lt;em&gt;I'm Nathan Sportsman. I run &lt;a href="https://www.praetorian.com" rel="noopener noreferrer"&gt;Praetorian&lt;/a&gt;, an offensive security company. We build tools like this because we use them on real engagements and got tired of the workarounds. If you have questions about the architecture, the Go implementation decisions, or the AI features, I'm happy to discuss in the comments.&lt;/em&gt;&lt;/p&gt;

</description>
      <category>go</category>
      <category>productivity</category>
      <category>security</category>
      <category>tooling</category>
    </item>
    <item>
      <title>Augustus: Open Source LLM Prompt Injection Scanner</title>
      <dc:creator>Nathan Sportsman</dc:creator>
      <pubDate>Mon, 09 Feb 2026 20:02:52 +0000</pubDate>
      <link>https://dev.to/praetorian_guard/augustus-open-source-llm-prompt-injection-scanner-4m6j</link>
      <guid>https://dev.to/praetorian_guard/augustus-open-source-llm-prompt-injection-scanner-4m6j</guid>
      <description>

&lt;h2&gt;
  
  
  The Problem
&lt;/h2&gt;

&lt;p&gt;You deployed an LLM behind an API gateway. Maybe it's customer-facing. Maybe it's connected to internal tools. Did you test it against adversarial attacks before it went live?&lt;/p&gt;

&lt;p&gt;If the answer is "the model has safety training," that's not the same thing. Safety training and security testing are fundamentally different disciplines. And the numbers back that up:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;FlipAttack&lt;/strong&gt; achieves 98% bypass rates against GPT-4o by reordering characters in prompts&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;DeepSeek R1&lt;/strong&gt; showed a 100% bypass rate against 50 HarmBench jailbreak prompts (Cisco/UPenn research)&lt;/li&gt;
&lt;li&gt;A study of 36 production LLM apps found &lt;strong&gt;86% were vulnerable&lt;/strong&gt; to prompt injection&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;PoisonedRAG&lt;/strong&gt; showed that just 5 malicious docs in a corpus of millions can manipulate outputs 90% of the time&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;OWASP ranked prompt injection as the &lt;strong&gt;#1 security risk&lt;/strong&gt; in LLM applications. Yet most LLMs ship to production with zero adversarial testing.&lt;/p&gt;

&lt;p&gt;We built Augustus to fix that.&lt;/p&gt;

&lt;h2&gt;
  
  
  What is Augustus?
&lt;/h2&gt;

&lt;p&gt;Augustus is an open-source LLM vulnerability scanner. It tests models against &lt;strong&gt;210+ adversarial attacks&lt;/strong&gt; across prompt injection, jailbreaks, encoding exploits, data extraction, and more. It ships as a &lt;strong&gt;single Go binary&lt;/strong&gt;, connects to &lt;strong&gt;28 LLM providers&lt;/strong&gt; out of the box, and produces actionable vulnerability reports.&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="c"&gt;# Install&lt;/span&gt;
go &lt;span class="nb"&gt;install &lt;/span&gt;github.com/praetorian-inc/augustus/cmd/augustus@latest

&lt;span class="c"&gt;# Test for DAN jailbreak against OpenAI&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;&lt;span class="s2"&gt;"your-api-key"&lt;/span&gt;
augustus scan openai.OpenAI &lt;span class="se"&gt;\&lt;/span&gt;
  &lt;span class="nt"&gt;--probe&lt;/span&gt; dan.Dan &lt;span class="se"&gt;\&lt;/span&gt;
  &lt;span class="nt"&gt;--detector&lt;/span&gt; dan.DanDetector &lt;span class="se"&gt;\&lt;/span&gt;
  &lt;span class="nt"&gt;--verbose&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;strong&gt;GitHub:&lt;/strong&gt; &lt;a href="https://github.com/praetorian-inc/augustus" rel="noopener noreferrer"&gt;github.com/praetorian-inc/augustus&lt;/a&gt; (Apache 2.0)&lt;/p&gt;

&lt;h2&gt;
  
  
  Why Not garak or promptfoo?
&lt;/h2&gt;

&lt;p&gt;Fair question. &lt;a href="https://github.com/NVIDIA/garak" rel="noopener noreferrer"&gt;garak&lt;/a&gt; (NVIDIA) and &lt;a href="https://github.com/promptfoo/promptfoo" rel="noopener noreferrer"&gt;promptfoo&lt;/a&gt; are great tools that serve the research and red-teaming community well. We needed something different — a tool that fits into &lt;strong&gt;penetration testing workflows&lt;/strong&gt; without requiring Python environments, npm installs, or runtime dependencies.&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;Augustus&lt;/th&gt;
&lt;th&gt;garak&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;Language&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;Go&lt;/td&gt;
&lt;td&gt;Python&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;Distribution&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;Single binary, no deps&lt;/td&gt;
&lt;td&gt;pip install + dependencies&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;Concurrency&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;Goroutine pools (cross-probe)&lt;/td&gt;
&lt;td&gt;Multiprocessing (within-probe)&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;Probes&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;210+&lt;/td&gt;
&lt;td&gt;160+ (longer research pedigree)&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;Providers&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;28&lt;/td&gt;
&lt;td&gt;35+ generator variants / 22 modules&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;Augustus is a Go-native reimplementation inspired by garak. Same concept, different trade-offs. If you're in a research environment with Python everywhere, garak is excellent. If you're a pentester who wants to &lt;code&gt;go install&lt;/code&gt; a binary and start scanning, Augustus is for you.&lt;/p&gt;

&lt;h2&gt;
  
  
  What It Tests
&lt;/h2&gt;

&lt;p&gt;Augustus covers &lt;strong&gt;47 attack categories&lt;/strong&gt;. Here's what you're actually testing:&lt;/p&gt;

&lt;h3&gt;
  
  
  🔓 Jailbreaks
&lt;/h3&gt;

&lt;p&gt;DAN ("Do Anything Now") prompts, AIM, AntiGPT, Grandma exploits (emotional manipulation), ArtPrompts (reframing as creative writing). Augustus includes DAN variants through v11.0 plus Goodside-style injection techniques.&lt;/p&gt;

&lt;h3&gt;
  
  
  💉 Prompt Injection
&lt;/h3&gt;

&lt;p&gt;Encoding attacks across &lt;strong&gt;Base64, ROT13, Morse code, hex, Braille, Klingon, leet speak&lt;/strong&gt;, and 12 more schemes. Tag smuggling (XML/HTML). FlipAttack (16 variants). Prefix and suffix injection.&lt;/p&gt;

&lt;h3&gt;
  
  
  🧪 Adversarial Examples (Research-Grade)
&lt;/h3&gt;

&lt;p&gt;GCG (Greedy Coordinate Gradient), AutoDAN, MindMap, DRA (Dynamic Reasoning Attack), TreeSearch. Plus iterative attacks like &lt;strong&gt;PAIR&lt;/strong&gt; and &lt;strong&gt;TAP&lt;/strong&gt; that refine across multiple rounds using a judge model — these are computationally expensive but represent the state of the art.&lt;/p&gt;

&lt;h3&gt;
  
  
  🔑 Data Extraction
&lt;/h3&gt;

&lt;p&gt;API key leakage probes. Package hallucination probes (Python, JS, Ruby, Rust, Dart, Perl, Raku) — checking if the model recommends packages that don't exist (a real supply chain attack vector). PII extraction. Training data regurgitation.&lt;/p&gt;

&lt;h3&gt;
  
  
  📄 Context Manipulation
&lt;/h3&gt;

&lt;p&gt;RAG poisoning (document content and metadata injection). Context overflow. Continuation and divergence exploits. Multimodal probes for vision-language models.&lt;/p&gt;

&lt;h3&gt;
  
  
  🖥️ Format Exploits
&lt;/h3&gt;

&lt;p&gt;Markdown injection (malicious links in rendered output). YAML/JSON parsing attacks on downstream consumers. ANSI escape injection. XSS payloads in model-generated HTML.&lt;/p&gt;

&lt;h3&gt;
  
  
  🕵️ Evasion Techniques
&lt;/h3&gt;

&lt;p&gt;ObscurePrompt (LLM-rewritten jailbreaks). Phrasing variations. Homoglyphs, zero-width characters, bidirectional text markers (BadChars). Glitch token exploitation.&lt;/p&gt;

&lt;h3&gt;
  
  
  📊 Safety Benchmarks
&lt;/h3&gt;

&lt;p&gt;DoNotAnswer (941 questions, 5 risk areas). RealToxicityPrompts. Snowball (plausible-sounding wrong answers). LMRC harmful content probes.&lt;/p&gt;

&lt;h3&gt;
  
  
  🤖 Agent Attacks
&lt;/h3&gt;

&lt;p&gt;Multi-agent manipulation. Browsing exploits for web-enabled models. Latent injection in documents (targeting RAG pipelines).&lt;/p&gt;

&lt;h3&gt;
  
  
  🛡️ Security Testing
&lt;/h3&gt;

&lt;p&gt;Guardrail bypass (20 variants for NeMo Guardrails and similar). SQL injection through model output. Steganography (hidden instructions in images via LSB encoding). Malware generation detection.&lt;/p&gt;

&lt;h2&gt;
  
  
  How the Pipeline Works
&lt;/h2&gt;

&lt;p&gt;Augustus uses a straightforward pipeline:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Probe → (Optional) Buff Transform → Generator (LLM Call) → Detector → Result
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;strong&gt;Probes&lt;/strong&gt; define the adversarial inputs. A DAN probe sends a role-playing prompt. An encoding probe wraps instructions in Base64. A FlipAttack probe reverses character order.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Buffs&lt;/strong&gt; are optional transformations applied before sending. Wrap any probe in poetry (haiku, sonnet, limerick), translate to a low-resource language, paraphrase, or encode. Chain multiple transformations for layered evasion.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Generators&lt;/strong&gt; connect to the target. 28 providers supported, plus a REST connector for custom endpoints.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Detectors&lt;/strong&gt; analyze responses. Pattern matching, LLM-as-a-judge, HarmJudge (&lt;a href="https://arxiv.org/abs/2511.15304" rel="noopener noreferrer"&gt;arXiv:2511.15304&lt;/a&gt;), Perspective API.&lt;/p&gt;

&lt;p&gt;For iterative attacks (PAIR, TAP), a dedicated &lt;strong&gt;Attack Engine&lt;/strong&gt; handles multi-turn conversations, candidate pruning, and judge-based scoring.&lt;/p&gt;

&lt;h2&gt;
  
  
  Buff Transformations: How Real Attackers Operate
&lt;/h2&gt;

&lt;p&gt;Real adversaries don't send attacks in plain text. Augustus ships 7 transformations across 5 categories:&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Encoding&lt;/strong&gt; — Base64 and character code wrapping. Models often decode and follow instructions that would be blocked in plain text.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Paraphrase&lt;/strong&gt; — Pegasus model rephrasing. Same adversarial intent, different surface form. Tests if safety training generalizes beyond memorized patterns.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Poetry&lt;/strong&gt; — Haiku, sonnets, limericks, free verse, rhyming couplets. Models that block direct harmful requests sometimes comply when it arrives as verse. (Yes, really.)&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Low-Resource Language Translation&lt;/strong&gt; — Via DeepL. Safety training is concentrated on English. Requests blocked in English may succeed in Zulu, Hmong, or Scots Gaelic.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Case Transforms&lt;/strong&gt; — Lowercasing. Some filters and blocklists are case-sensitive.&lt;/p&gt;

&lt;p&gt;Chain them with &lt;code&gt;--buff&lt;/code&gt; or &lt;code&gt;--buffs-glob&lt;/code&gt;:&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="c"&gt;# Encode a DAN probe in Base64&lt;/span&gt;
augustus scan openai.OpenAI &lt;span class="nt"&gt;--probe&lt;/span&gt; dan.Dan &lt;span class="nt"&gt;--buff&lt;/span&gt; encoding.Base64

&lt;span class="c"&gt;# Chain: paraphrase, then translate to low-resource language&lt;/span&gt;
augustus scan openai.OpenAI &lt;span class="nt"&gt;--probe&lt;/span&gt; dan.Dan &lt;span class="nt"&gt;--buffs-glob&lt;/span&gt; &lt;span class="s2"&gt;"paraphrase.*,lrl.*"&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h2&gt;
  
  
  28 Providers, One Interface
&lt;/h2&gt;

&lt;p&gt;OpenAI (including o1/o3), Anthropic (Claude 3/3.5/4), Azure OpenAI, AWS Bedrock, Google Vertex AI, Cohere, Replicate, HuggingFace, Together AI, Groq, Mistral, Fireworks, DeepInfra, NVIDIA NIM, Ollama, LiteLLM, and more.&lt;/p&gt;

&lt;p&gt;The &lt;strong&gt;REST generator&lt;/strong&gt; handles everything else:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;augustus scan rest.Rest &lt;span class="se"&gt;\&lt;/span&gt;
  &lt;span class="nt"&gt;--probe&lt;/span&gt; dan.Dan &lt;span class="se"&gt;\&lt;/span&gt;
  &lt;span class="nt"&gt;--config&lt;/span&gt; &lt;span class="s1"&gt;'{
    "uri": "https://your-api.example.com/v1/chat/completions",
    "headers": {"Authorization": "Bearer YOUR_KEY"},
    "req_template_json_object": {
      "model": "your-model",
      "messages": [{"role": "user", "content": "$INPUT"}]
    },
    "response_json": true,
    "response_json_field": "$.choices[0].message.content"
  }'&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Custom request templates with &lt;code&gt;$INPUT&lt;/code&gt; placeholders, JSONPath extraction, SSE streaming, and proxy routing. If your endpoint speaks HTTP, Augustus can test it.&lt;/p&gt;

&lt;h2&gt;
  
  
  Quick Start
&lt;/h2&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="c"&gt;# Install&lt;/span&gt;
go &lt;span class="nb"&gt;install &lt;/span&gt;github.com/praetorian-inc/augustus/cmd/augustus@latest

&lt;span class="c"&gt;# Run all 210+ probes against a local model&lt;/span&gt;
augustus scan ollama.OllamaChat &lt;span class="se"&gt;\&lt;/span&gt;
  &lt;span class="nt"&gt;--all&lt;/span&gt; &lt;span class="se"&gt;\&lt;/span&gt;
  &lt;span class="nt"&gt;--config&lt;/span&gt; &lt;span class="s1"&gt;'{"model":"llama3.2:3b"}'&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Output:&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;PROBE&lt;/th&gt;
&lt;th&gt;DETECTOR&lt;/th&gt;
&lt;th&gt;PASSED&lt;/th&gt;
&lt;th&gt;SCORE&lt;/th&gt;
&lt;th&gt;STATUS&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;dan.Dan&lt;/td&gt;
&lt;td&gt;dan.DAN&lt;/td&gt;
&lt;td&gt;false&lt;/td&gt;
&lt;td&gt;.85&lt;/td&gt;
&lt;td&gt;VULN&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;encoding.base64&lt;/td&gt;
&lt;td&gt;encoding&lt;/td&gt;
&lt;td&gt;true&lt;/td&gt;
&lt;td&gt;.10&lt;/td&gt;
&lt;td&gt;SAFE&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;smuggling.Tag&lt;/td&gt;
&lt;td&gt;smuggling&lt;/td&gt;
&lt;td&gt;true&lt;/td&gt;
&lt;td&gt;.05&lt;/td&gt;
&lt;td&gt;SAFE&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;Export to JSON, JSONL, or HTML reports for stakeholders.&lt;/p&gt;

&lt;h2&gt;
  
  
  Feature Summary
&lt;/h2&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Feature&lt;/th&gt;
&lt;th&gt;Details&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;Vulnerability Probes&lt;/td&gt;
&lt;td&gt;210+ across 47 attack categories&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;LLM Providers&lt;/td&gt;
&lt;td&gt;28 with 43 generator variants&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Detectors&lt;/td&gt;
&lt;td&gt;90+ (pattern matching, LLM-as-judge, HarmJudge, Perspective API)&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Buff Transformations&lt;/td&gt;
&lt;td&gt;7 transforms (encoding, paraphrase, poetry, translation, case)&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Output Formats&lt;/td&gt;
&lt;td&gt;Table, JSON, JSONL, HTML&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Production Features&lt;/td&gt;
&lt;td&gt;Concurrent scanning, rate limiting, retry logic, timeouts&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Distribution&lt;/td&gt;
&lt;td&gt;Single Go binary, no runtime dependencies&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Extensibility&lt;/td&gt;
&lt;td&gt;Plugin-style registration via Go &lt;code&gt;init()&lt;/code&gt; functions&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;h2&gt;
  
  
  What's Next
&lt;/h2&gt;

&lt;p&gt;Augustus is the second release in our &lt;strong&gt;"The 12 Caesars"&lt;/strong&gt; open-source campaign — one tool per week for 12 weeks. Last month we released &lt;a href="https://github.com/praetorian-inc/julius" rel="noopener noreferrer"&gt;Julius&lt;/a&gt; for LLM fingerprinting (identifying what model is running on an endpoint). Each tool follows the Unix philosophy: do one thing well, compose with the others.&lt;/p&gt;

&lt;h2&gt;
  
  
  Get Involved
&lt;/h2&gt;

&lt;p&gt;&lt;strong&gt;Repo:&lt;/strong&gt; &lt;a href="https://github.com/praetorian-inc/augustus" rel="noopener noreferrer"&gt;github.com/praetorian-inc/augustus&lt;/a&gt; — Apache 2.0&lt;/p&gt;

&lt;p&gt;We'd love contributions: new probes, bug reports, feature requests. Check &lt;code&gt;CONTRIBUTING.md&lt;/code&gt; for guidance on probe definitions and dev workflow.&lt;/p&gt;

&lt;p&gt;Star the repo if it's useful, and let us know what attack techniques you'd like to see next. 🚀&lt;/p&gt;

</description>
      <category>ai</category>
      <category>llm</category>
      <category>cybersecurity</category>
      <category>programming</category>
    </item>
    <item>
      <title>We Stopped Treating AI Agents Like Chatbots and Started Treating Them Like OS Processes</title>
      <dc:creator>Nathan Sportsman</dc:creator>
      <pubDate>Fri, 06 Feb 2026 17:25:16 +0000</pubDate>
      <link>https://dev.to/praetorian_guard/we-stopped-treating-ai-agents-like-chatbots-and-started-treating-them-like-os-processes-3dml</link>
      <guid>https://dev.to/praetorian_guard/we-stopped-treating-ai-agents-like-chatbots-and-started-treating-them-like-os-processes-3dml</guid>
      <description>&lt;p&gt;Token usage, not model intelligence, explains roughly 80% of performance variance in AI agent tasks. We learned this the hard way while building an autonomous development platform on top of Claude Code. After months of iteration across a 530k-line codebase with 39 specialized agents, the patterns that actually produced reliable results all came from classical systems design rather than anything novel to AI.&lt;/p&gt;

&lt;p&gt;We published a &lt;a href="https://www.praetorian.com/blog/deterministic-ai-orchestration-a-platform-architecture-for-autonomous-development/" rel="noopener noreferrer"&gt;full technical paper&lt;/a&gt; with diagrams and implementation details. This post covers the key architectural decisions and why we made them.&lt;/p&gt;

&lt;h2&gt;
  
  
  The Failure That Changed Our Approach
&lt;/h2&gt;

&lt;p&gt;Our first agents were 1,200+ line monoliths. All instructions, all tool definitions, all state packed into a single prompt. They failed the way monolithic services fail:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Attention dilution:&lt;/strong&gt; instructions at the bottom of the prompt got ignored.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Context starvation:&lt;/strong&gt; no room left in the context window for the actual work.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;State contamination:&lt;/strong&gt; history from one task bled into the next.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;There's a fundamental paradox at play. To handle complex tasks, agents need comprehensive instructions. But comprehensive instructions consume the context window, which degrades the model's ability to reason about the task those instructions describe. More instructions, worse performance. We call this the Context-Capability Paradox.&lt;/p&gt;

&lt;h2&gt;
  
  
  The Architecture: LLMs as Kernel Processes
&lt;/h2&gt;

&lt;p&gt;We treat the LLM not as a chatbot but as a nondeterministic kernel process wrapped in a deterministic runtime environment. That metaphor maps directly to the implementation.&lt;/p&gt;

&lt;p&gt;The system has five layers with strict separation of concerns:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;
&lt;strong&gt;Agents&lt;/strong&gt; (stateless ephemeral workers)&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Skills&lt;/strong&gt; (externalized knowledge, loaded on demand)&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Orchestration&lt;/strong&gt; (state machine and process lifecycle)&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Hooks&lt;/strong&gt; (deterministic enforcement outside the LLM's control)&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Tooling&lt;/strong&gt; (progressive MCP loading and semantic code intelligence)&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;Each layer has a single responsibility. Agents execute. Skills inform. Orchestration coordinates. Hooks enforce. Tools connect.&lt;/p&gt;

&lt;h2&gt;
  
  
  Pattern 1: Thin Agent, Fat Platform
&lt;/h2&gt;

&lt;p&gt;We inverted the control structure. Instead of thick agents that carry everything, we built thin agents that carry nothing.&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Agents&lt;/strong&gt; are under 150 lines. Stateless. Ephemeral. Every spawn gets a clean context window with zero history from siblings. Spawn cost dropped from ~24,000 tokens to ~2,700.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Skills&lt;/strong&gt; live in an external two-tier library. Core skills (always available) act like a BIOS. Library skills (loaded on demand) act like a hard drive. Agents never hardcode knowledge paths. They invoke a gateway (e.g., &lt;code&gt;gateway-frontend&lt;/code&gt;), which performs intent detection and routes to the specific patterns needed.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;An agent asking for help with a React infinite loop loads only the two relevant skill files, not the entire frontend knowledge base. This is textbook inversion of control. The agent doesn't decide what it knows. The platform decides.&lt;/p&gt;

&lt;h2&gt;
  
  
  Pattern 2: Mutual Exclusion on Capabilities
&lt;/h2&gt;

&lt;p&gt;Two mutually exclusive execution models:&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Model&lt;/th&gt;
&lt;th&gt;Role&lt;/th&gt;
&lt;th&gt;Has&lt;/th&gt;
&lt;th&gt;Denied&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;Coordinator&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;Spawns specialists&lt;/td&gt;
&lt;td&gt;
&lt;code&gt;Task&lt;/code&gt;, &lt;code&gt;Read&lt;/code&gt;
&lt;/td&gt;
&lt;td&gt;
&lt;code&gt;Edit&lt;/code&gt;, &lt;code&gt;Write&lt;/code&gt;
&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;Executor&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;Writes code&lt;/td&gt;
&lt;td&gt;
&lt;code&gt;Edit&lt;/code&gt;, &lt;code&gt;Write&lt;/code&gt;, &lt;code&gt;Bash&lt;/code&gt;
&lt;/td&gt;
&lt;td&gt;&lt;code&gt;Task&lt;/code&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;An agent cannot be both. A coordinator physically cannot write code. An executor physically cannot delegate. Enforced at the tool permission level.&lt;/p&gt;

&lt;p&gt;This kills two specific failure modes:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;The planning agent that starts hacking code to "save time" and compromises architectural integrity.&lt;/li&gt;
&lt;li&gt;The coding agent that delegates to avoid a hard problem, creating delegation loops.&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;Same principle as CQRS. Separate the paths. Make the separation structural, not advisory.&lt;/p&gt;

&lt;p&gt;Within this split, we specialize further into five roles:&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Role&lt;/th&gt;
&lt;th&gt;Responsibility&lt;/th&gt;
&lt;th&gt;Key Constraint&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;Lead&lt;/td&gt;
&lt;td&gt;Architecture and decomposition&lt;/td&gt;
&lt;td&gt;Cannot write code&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Developer&lt;/td&gt;
&lt;td&gt;Implementation of atomic tasks&lt;/td&gt;
&lt;td&gt;Cannot delegate&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Reviewer&lt;/td&gt;
&lt;td&gt;Compliance validation&lt;/td&gt;
&lt;td&gt;Can reject and send back&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Test Lead&lt;/td&gt;
&lt;td&gt;Test strategy and planning&lt;/td&gt;
&lt;td&gt;Does not write tests&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Tester&lt;/td&gt;
&lt;td&gt;Test execution&lt;/td&gt;
&lt;td&gt;Does not write production code&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;The architect can't compromise test coverage to ship faster. The developer can't skip review. The tester can't modify the code under test.&lt;/p&gt;

&lt;h2&gt;
  
  
  Pattern 3: Deterministic Hooks as Enforcement
&lt;/h2&gt;

&lt;p&gt;This is the pattern that had the most impact on reliability. Prompts are suggestions. Hooks are enforcement.&lt;/p&gt;

&lt;p&gt;Claude Code exposes lifecycle events: &lt;code&gt;PreToolUse&lt;/code&gt;, &lt;code&gt;PostToolUse&lt;/code&gt;, &lt;code&gt;Stop&lt;/code&gt;. We hang shell scripts on these that the LLM cannot override, bypass, or negotiate with.&lt;/p&gt;

&lt;p&gt;We architected three nested enforcement loops:&lt;/p&gt;

&lt;h3&gt;
  
  
  Level 1: Intra-Task (Agent Scope)
&lt;/h3&gt;

&lt;p&gt;Prevents a single agent from spinning endlessly on one command. Max 10 iterations, configurable via a central YAML config.&lt;/p&gt;

&lt;h3&gt;
  
  
  Level 2: Inter-Phase (Quality Gate)
&lt;/h3&gt;

&lt;p&gt;This is the core quality enforcement:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;Agent edits a file. &lt;code&gt;PreToolUse&lt;/code&gt; hook sets a dirty bit in a JSON state file.&lt;/li&gt;
&lt;li&gt;Agent completes work and tries to exit.&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;Stop&lt;/code&gt; hook checks: dirty bit set and tests not passing?&lt;/li&gt;
&lt;li&gt;If yes: &lt;strong&gt;block exit.&lt;/strong&gt; Agent receives &lt;code&gt;{"decision": "block", "reason": "Tests failed. Fix and retry."}&lt;/code&gt;.&lt;/li&gt;
&lt;li&gt;Agent is forced to stay in the loop until independent reviewer and tester agents pass the work.&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;The agent doesn't get a vote. The hook is a shell script. It returns a JSON response and the LLM has no mechanism to override it. Same pattern as OS-level permission enforcement.&lt;/p&gt;

&lt;h3&gt;
  
  
  Level 3: Orchestrator (Workflow Scope)
&lt;/h3&gt;

&lt;p&gt;Re-invokes entire phases if macro-level goals aren't met.&lt;/p&gt;

&lt;h3&gt;
  
  
  Stuck Detection and Escalation
&lt;/h3&gt;

&lt;p&gt;When an agent produces three consecutive iterations with &amp;gt;90% output similarity, the system detects a stuck state. Instead of retrying, a hook invokes an external, cheaper model with the session transcript and a focused prompt: "Why is this agent stuck? One sentence."&lt;/p&gt;

&lt;p&gt;The hint gets injected into the main context to break the deadlock. The stuck agent's context is polluted with failed attempts. A fresh model sees the problem clearly. Same reason code review works better than self-review.&lt;/p&gt;

&lt;h2&gt;
  
  
  Pattern 4: Two-Tier State
&lt;/h2&gt;

&lt;p&gt;Two categories of state with different lifecycles:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Ephemeral (hooks):&lt;/strong&gt; Dirty bits, loop counters, runtime flags. JSON files. Lost on session restart. This is your RAM.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Persistent (manifest):&lt;/strong&gt; Current phase, active agents, validation status. &lt;code&gt;MANIFEST.yaml&lt;/code&gt; on disk. Survives crashes. This is your disk.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;A session crash loses enforcement state (acceptable, hooks re-initialize) but preserves workflow progress (critical, prevents rework). The entire 16-phase workflow can resume from the last checkpoint.&lt;/p&gt;

&lt;p&gt;Distributed file locking (&lt;code&gt;.claude/locks/{agent}.lock&lt;/code&gt;) handles concurrent access when parallel agents operate on shared source files.&lt;/p&gt;

&lt;h2&gt;
  
  
  Pattern 5: Token Economics
&lt;/h2&gt;

&lt;p&gt;This one surprised us more than anything else.&lt;/p&gt;

&lt;h3&gt;
  
  
  MCP Tool Loading
&lt;/h3&gt;

&lt;p&gt;Five raw MCP server connections at startup consumed &lt;strong&gt;71,800 tokens&lt;/strong&gt;. That's 36% of the context window gone before the agent even receives a task. We replaced them with on-demand TypeScript wrappers behind the gateway pattern. Zero tokens at startup. Zod validation on inputs, response filtering and truncation on outputs.&lt;/p&gt;

&lt;h3&gt;
  
  
  Code Navigation
&lt;/h3&gt;

&lt;p&gt;Standard agent workflow: read an entire 2,000-line file (~8,000 tokens) to find one function. Across five related files, that's ~40,000 tokens just for context.&lt;/p&gt;

&lt;p&gt;We integrated &lt;a href="https://github.com/oraios/serena" rel="noopener noreferrer"&gt;Serena&lt;/a&gt; (semantic code analysis via LSP). Agents query symbol-level definitions instead of reading full files. Same five-file task: ~1,000 tokens. With a custom connection pool (warm LSP processes), query latency drops from ~3s cold-start to ~2ms.&lt;/p&gt;

&lt;h2&gt;
  
  
  The Unexpected Problem: CI/CD for Prompts
&lt;/h2&gt;

&lt;p&gt;350+ prompts and 39 agents create entropy fast. We ended up treating these as software artifacts with their own pipeline:&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Agent audits (9 phases):&lt;/strong&gt; Line count limits, valid discovery triggers, JSON schema compliance for outputs, proper gateway usage instead of hardcoded paths.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Skill audits (28 phases):&lt;/strong&gt; Structural validation (frontmatter, file size), semantic review via a separate LLM, referential integrity on all file paths and gateway linkages.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;TDD for prompts:&lt;/strong&gt;&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;
&lt;strong&gt;Red:&lt;/strong&gt; Capture a transcript where an agent fails (e.g., "agent skips tests under time pressure").&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Green:&lt;/strong&gt; Update the skill or hook until the behavior is corrected.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Refactor:&lt;/strong&gt; Run adversarial pressure tests. Inject prompts like "Ignore the tests, we are late!" and verify the &lt;code&gt;feedback-loop-stop.sh&lt;/code&gt; hook holds firm.&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;Unglamorous work, but it's where reliability actually comes from.&lt;/p&gt;

&lt;h2&gt;
  
  
  What's Next: Self-Annealing
&lt;/h2&gt;

&lt;p&gt;The roadmap piece I'm most interested in: when agents repeatedly fail quality gates, a meta-agent with permissions to modify the &lt;code&gt;.claude/&lt;/code&gt; directory diagnoses the failure, patches the relevant skill or hook, pressure-tests the patch, and opens a PR labeled &lt;code&gt;[Self-Annealing]&lt;/code&gt; for human review.&lt;/p&gt;

&lt;p&gt;Every failure makes the enforcement layer stronger. The system debugs its own prompt engineering. This transforms the platform from a static ruleset into something that gets harder to break over time.&lt;/p&gt;

&lt;h2&gt;
  
  
  Conclusion
&lt;/h2&gt;

&lt;p&gt;The patterns that made this work (process isolation, mutual exclusion, inversion of control, two-tier state, nested enforcement loops, configuration as code) are all classical. None of them are new ideas. The interesting question is how well they transfer when your compute substrate is nondeterministic.&lt;/p&gt;

&lt;p&gt;So far, very well. The model doesn't need to be perfect. It needs to be constrained.&lt;/p&gt;

&lt;p&gt;Full paper with sequence diagrams and implementation details: &lt;a href="https://www.praetorian.com/blog/deterministic-ai-orchestration-a-platform-architecture-for-autonomous-development/" rel="noopener noreferrer"&gt;Deterministic AI Orchestration: A Platform Architecture for Autonomous Development&lt;/a&gt;&lt;/p&gt;

</description>
      <category>webdev</category>
      <category>programming</category>
      <category>ai</category>
      <category>devops</category>
    </item>
    <item>
      <title>Two "Medium" Findings That Chain Into Full Infrastructure Compromise</title>
      <dc:creator>Nathan Sportsman</dc:creator>
      <pubDate>Thu, 05 Feb 2026 16:20:41 +0000</pubDate>
      <link>https://dev.to/praetorian_guard/two-medium-findings-that-chain-into-full-infrastructure-compromise-2amg</link>
      <guid>https://dev.to/praetorian_guard/two-medium-findings-that-chain-into-full-infrastructure-compromise-2amg</guid>
      <description>&lt;h2&gt;
  
  
  The Setup
&lt;/h2&gt;

&lt;p&gt;You know that feeling when you're triaging security findings and you see a bunch of mediums in the backlog? They'll get fixed eventually. Probably. After the criticals. And the highs. And that one feature the PM has been asking about for three sprints.&lt;/p&gt;

&lt;p&gt;Here's the thing: attackers don't triage by severity. They triage by what chains together.&lt;/p&gt;

&lt;p&gt;I want to walk through a vulnerability chain we recently documented that combines two completely unremarkable findings into something that enables authenticated phishing and persistent access to Microsoft 365 environments.&lt;/p&gt;

&lt;p&gt;Neither finding would make anyone panic. Together, they're a full compromise.&lt;/p&gt;

&lt;h2&gt;
  
  
  Finding #1: The Newsletter Endpoint That Does Too Much
&lt;/h2&gt;

&lt;p&gt;Every web app has endpoints that send emails. Newsletter signups. Contact forms. Password resets. Transactional notifications.&lt;/p&gt;

&lt;p&gt;These endpoints need to be public to function. That's the point. But they also need strict input validation to prevent abuse.&lt;/p&gt;

&lt;p&gt;Here's the vulnerable pattern:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;POST /api/newsletter/subscribe
Content-Type: application/json

{
  "recipient": "victim@target.com",
  "subject": "Urgent: Security Alert",
  "body": "&amp;lt;html&amp;gt;...phishing content...&amp;lt;/html&amp;gt;"
}
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;No authentication. Arbitrary recipient, subject, and HTML body.&lt;/p&gt;

&lt;p&gt;When this request gets processed, the application sends an email through the organization's legitimate mail infrastructure. The email originates from an authorized mailbox with proper authentication.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;What this means in practice:&lt;/strong&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Email passes SPF, DKIM, and DMARC checks&lt;/li&gt;
&lt;li&gt;Sender shows the organization's official email address&lt;/li&gt;
&lt;li&gt;Gmail auto-tags it as "Important" because of the legitimate origin&lt;/li&gt;
&lt;li&gt;Lands in primary inbox, not spam&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;You've just turned the target's own infrastructure into a phishing platform.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Finding these endpoints isn't hard:&lt;/strong&gt;&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;site:target.com newsletter
site:target.com "sign up"
site:target.com contact
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Pages that aren't linked in the main navigation are often still indexed and fully functional.&lt;/p&gt;

&lt;h2&gt;
  
  
  Finding #2: Error Messages That Leak Tokens
&lt;/h2&gt;

&lt;p&gt;The second finding involves verbose error handling in production. You've seen this pattern before:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;POST /api/newsletter/subscribe
Content-Type: application/json

{
  "recipient": "test@test.com"
  // missing required fields
}
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Response:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight json"&gt;&lt;code&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"error"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"ValidationError"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"stack"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"..."&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"context"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="nl"&gt;"oauth_token"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"eyJhbGciOiJSUzI1NiIsInR5cCI6IkpXVCJ9..."&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="nl"&gt;"service"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"graph.microsoft.com"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="err"&gt;...&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Why would an error response contain OAuth tokens? In many applications, internal services authenticate to each other using tokens stored in application context. Verbose error handling dumps that context to the client. The tokens come along for the ride.&lt;/p&gt;

&lt;p&gt;In this case, the leaked tokens were for Microsoft Graph API.&lt;/p&gt;

&lt;p&gt;Depending on scope, that's access to:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Mail (read and send)&lt;/li&gt;
&lt;li&gt;Calendar&lt;/li&gt;
&lt;li&gt;Teams conversations&lt;/li&gt;
&lt;li&gt;SharePoint and OneDrive files&lt;/li&gt;
&lt;li&gt;User directory and org charts&lt;/li&gt;
&lt;li&gt;Sometimes Azure resources and Intune&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;strong&gt;"But tokens expire in an hour"&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;True. But you can just trigger the error again to get a fresh one. The vulnerability becomes a token dispenser. Persistence without credentials.&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.amazonaws.com%2Fuploads%2Farticles%2Ftpdhghjubk5c0adlqg5u.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.amazonaws.com%2Fuploads%2Farticles%2Ftpdhghjubk5c0adlqg5u.png" alt=" " width="800" height="469"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  The Chain
&lt;/h2&gt;

&lt;p&gt;Here's how these combine in practice:&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Stage 1: Token extraction&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;Attacker finds the verbose error condition, pulls a valid Graph token. They now have authenticated M365 access without triggering failed login alerts.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Stage 2: Reconnaissance&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;Using the token, they enumerate:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight javascript"&gt;&lt;code&gt;&lt;span class="c1"&gt;// Get org chart&lt;/span&gt;
&lt;span class="nx"&gt;GET&lt;/span&gt; &lt;span class="nx"&gt;https&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="c1"&gt;//graph.microsoft.com/v1.0/users&lt;/span&gt;

&lt;span class="c1"&gt;// Get user details&lt;/span&gt;
&lt;span class="nx"&gt;GET&lt;/span&gt; &lt;span class="nx"&gt;https&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="c1"&gt;//graph.microsoft.com/v1.0/users/{id}&lt;/span&gt;

&lt;span class="c1"&gt;// Get manager chain&lt;/span&gt;
&lt;span class="nx"&gt;GET&lt;/span&gt; &lt;span class="nx"&gt;https&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="c1"&gt;//graph.microsoft.com/v1.0/users/{id}/manager&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Employee names. Titles. Projects. Reporting structure. Internal terminology. All the intelligence needed to craft convincing phishing.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Stage 3: Targeted phishing&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;Now they use the email endpoint to send phishing campaigns. But these aren't generic "click here to verify your account" emails. They're crafted using real project names, accurate org structure, and internal terminology.&lt;/p&gt;

&lt;p&gt;And they come from the organization's own mail server.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Stage 4: Escalation&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;Harvested credentials get them deeper. Admin accounts. Azure resources. Production infrastructure.&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.amazonaws.com%2Fuploads%2Farticles%2Fpl9pbdw1ph90m0bny8pn.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.amazonaws.com%2Fuploads%2Farticles%2Fpl9pbdw1ph90m0bny8pn.png" alt=" " width="800" height="469"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Stage 5: Persistence&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;As long as the verbose error exists, they can regenerate tokens. Credential rotation doesn't help. The vulnerability itself is the persistence mechanism.&lt;/p&gt;

&lt;h2&gt;
  
  
  The Fix
&lt;/h2&gt;

&lt;p&gt;&lt;strong&gt;For the email endpoint:&lt;/strong&gt;&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="c1"&gt;# Bad: accepts arbitrary input
&lt;/span&gt;&lt;span class="nd"&gt;@app.post&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;/api/newsletter/subscribe&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;subscribe&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;data&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nb"&gt;dict&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;
    &lt;span class="nf"&gt;send_email&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
        &lt;span class="n"&gt;to&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="n"&gt;data&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;get&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;recipient&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt;
        &lt;span class="n"&gt;subject&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="n"&gt;data&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;get&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;subject&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt;
        &lt;span class="n"&gt;body&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="n"&gt;data&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;get&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;body&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="p"&gt;)&lt;/span&gt;

&lt;span class="c1"&gt;# Good: strict schema, single purpose
&lt;/span&gt;&lt;span class="k"&gt;class&lt;/span&gt; &lt;span class="nc"&gt;SubscribeRequest&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;BaseModel&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;
    &lt;span class="n"&gt;email&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;EmailStr&lt;/span&gt;

&lt;span class="nd"&gt;@app.post&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;/api/newsletter/subscribe&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;subscribe&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;data&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;SubscribeRequest&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;
    &lt;span class="nf"&gt;add_to_mailing_list&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;data&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;email&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="nf"&gt;send_confirmation_email&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;data&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;email&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;  &lt;span class="c1"&gt;# fixed template
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;If it's a newsletter signup, it accepts an email address. That's it.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;For error handling:&lt;/strong&gt;&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="c1"&gt;# Bad: dumps everything to client
&lt;/span&gt;&lt;span class="nd"&gt;@app.exception_handler&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nb"&gt;Exception&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;handle_error&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;request&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;exc&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;
    &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="nc"&gt;JSONResponse&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt;
        &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;error&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nf"&gt;str&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;exc&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt;
        &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;stack&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;traceback&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;format_exc&lt;/span&gt;&lt;span class="p"&gt;(),&lt;/span&gt;
        &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;context&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;app&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;state&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;__dict__&lt;/span&gt;  &lt;span class="c1"&gt;# tokens live here
&lt;/span&gt;    &lt;span class="p"&gt;})&lt;/span&gt;

&lt;span class="c1"&gt;# Good: generic client response, detailed server logs
&lt;/span&gt;&lt;span class="nd"&gt;@app.exception_handler&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nb"&gt;Exception&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;handle_error&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;request&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;exc&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;
    &lt;span class="n"&gt;logger&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;error&lt;/span&gt;&lt;span class="p"&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;Error: &lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="n"&gt;exc&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;exc_info&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="c1"&gt;# server-side only
&lt;/span&gt;    &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="nc"&gt;JSONResponse&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt;
        &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;error&lt;/span&gt;&lt;span class="sh"&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;An error occurred&lt;/span&gt;&lt;span class="sh"&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;request_id&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nf"&gt;generate_request_id&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
    &lt;span class="p"&gt;})&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Production should never return stack traces or application context to clients.&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.amazonaws.com%2Fuploads%2Farticles%2Fba1cbd2jwr2oix2o5h3g.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.amazonaws.com%2Fuploads%2Farticles%2Fba1cbd2jwr2oix2o5h3g.png" alt=" " width="800" height="431"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  The Takeaway
&lt;/h2&gt;

&lt;p&gt;Two medium findings. One accepts too many parameters. One returns too much information.&lt;/p&gt;

&lt;p&gt;Your vulnerability scanner assessed these individually and rated them appropriately. The scanner isn't wrong. But it's also not thinking like an attacker.&lt;/p&gt;

&lt;p&gt;Attackers don't care about severity ratings. They care about paths.&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.amazonaws.com%2Fuploads%2Farticles%2Fk7vf5l4ldf8rqqd5tre7.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.amazonaws.com%2Fuploads%2Farticles%2Fk7vf5l4ldf8rqqd5tre7.png" alt=" " width="800" height="301"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Full technical writeup with the detailed attack flow: &lt;a href="https://www.praetorian.com/blog/gone-phishing-got-a-token-when-separate-flaws-combine/" rel="noopener noreferrer"&gt;https://www.praetorian.com/blog/gone-phishing-got-a-token-when-separate-flaws-combine/&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Question for the comments:&lt;/strong&gt; What's the gnarliest vulnerability chain you've seen where individual findings looked harmless but combined into something ugly?&lt;/p&gt;

</description>
      <category>webdev</category>
      <category>security</category>
      <category>oauth</category>
      <category>appsec</category>
    </item>
    <item>
      <title>Shadow AI Is Everywhere: Meet Julius, the Open-Source LLM Fingerprinting Tool</title>
      <dc:creator>Nathan Sportsman</dc:creator>
      <pubDate>Wed, 04 Feb 2026 17:10:10 +0000</pubDate>
      <link>https://dev.to/praetorian_guard/shadow-ai-is-everywhere-meet-julius-the-open-source-llm-fingerprinting-tool-410g</link>
      <guid>https://dev.to/praetorian_guard/shadow-ai-is-everywhere-meet-julius-the-open-source-llm-fingerprinting-tool-410g</guid>
      <description>&lt;h2&gt;
  
  
  The Growing Shadow AI Problem
&lt;/h2&gt;

&lt;p&gt;Over 14,000 Ollama server instances are publicly accessible on the internet right now. &lt;/p&gt;

&lt;p&gt;A recent Cisco analysis found that 20% of these actively host models susceptible to unauthorized access. Separately, BankInfoSecurity reported discovering more than 10,000 Ollama servers with no authentication layer, the result of hurried AI deployments by developers under pressure.&lt;/p&gt;

&lt;p&gt;This is the new shadow IT: developers spinning up local LLM servers for productivity, unaware they've exposed sensitive infrastructure to the internet. And Ollama is just one of dozens of AI serving platforms proliferating across enterprise networks.&lt;/p&gt;

&lt;p&gt;The security question is no longer "are we running AI?" but "where is AI running that we don't know about?"&lt;/p&gt;

&lt;h2&gt;
  
  
  What is LLM Service Fingerprinting?
&lt;/h2&gt;

&lt;p&gt;LLM service fingerprinting identifies what &lt;strong&gt;server software&lt;/strong&gt; is running on a network endpoint, not which AI model generated text, but which infrastructure is serving it.&lt;/p&gt;

&lt;p&gt;The LLM security space spans multiple tool categories, each answering a different question:&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;"What ports are open?"&lt;/strong&gt; → Nmap&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;"What service is on this port?"&lt;/strong&gt; → Praetorian Nerva (will be open-sourced)&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;"Is this HTTP service an LLM?"&lt;/strong&gt; → Praetorian Julius&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;"Which LLM wrote this text?"&lt;/strong&gt; → Model fingerprinting&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;"Is this prompt malicious?"&lt;/strong&gt; → Input guardrails&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;"Can this model be jailbroken?"&lt;/strong&gt; → Nvidia Garak, Praetorian Augustus (will be open-sourced)&lt;/p&gt;

&lt;p&gt;Julius answers the third question: during a penetration test or attack surface assessment, you've found an open port. Is it Ollama? vLLM? A Hugging Face deployment? Some enterprise AI gateway? Julius tells you in seconds.&lt;/p&gt;

&lt;p&gt;Julius follows the Unix philosophy: do one thing and do it well. It doesn't port scan. It doesn't vulnerability scan. It identifies LLM services, nothing more, nothing less.&lt;/p&gt;

&lt;p&gt;This design enables Julius to slot into existing security toolchains rather than replace them.&lt;/p&gt;

&lt;h2&gt;
  
  
  Why Existing Detection Methods Fall Short
&lt;/h2&gt;

&lt;h3&gt;
  
  
  Manual Detection is Slow and Error-Prone
&lt;/h3&gt;

&lt;p&gt;Each LLM platform has different API signatures, default ports, and response patterns:&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Ollama:&lt;/strong&gt; port 11434, &lt;code&gt;/api/tags&lt;/code&gt; returns &lt;code&gt;{"models": […]}&lt;/code&gt;&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;vLLM:&lt;/strong&gt; port 8000, OpenAI-compatible &lt;code&gt;/v1/models&lt;/code&gt;&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;LiteLLM:&lt;/strong&gt; port 4000, proxies to multiple backends&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;LocalAI:&lt;/strong&gt; port 8080, &lt;code&gt;/models&lt;/code&gt; endpoint&lt;/p&gt;

&lt;p&gt;Manually checking each possibility during an assessment wastes time and risks missing services.&lt;/p&gt;

&lt;h3&gt;
  
  
  Shodan Queries Have Limitations
&lt;/h3&gt;

&lt;p&gt;A Cisco study found ~1,100 Ollama instances were indexed on Shodan. While interesting, replicating the research requires a Shodan license.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Ollama-only detection&lt;/strong&gt; → Misses vLLM, LiteLLM, and 15+ other platforms&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Passive database queries&lt;/strong&gt; → Data lags behind real-time deployments&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Requires Shodan subscription&lt;/strong&gt; → Cost barrier for some teams&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;No model enumeration&lt;/strong&gt; → Can't identify what's deployed&lt;/p&gt;

&lt;h2&gt;
  
  
  Introducing Julius
&lt;/h2&gt;

&lt;p&gt;Julius is an open-source LLM service fingerprinting tool that detects 17+ AI platforms through active HTTP probing. Built in Go, it compiles to a single binary with no external dependencies.&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="c"&gt;# Installation&lt;/span&gt;
go &lt;span class="nb"&gt;install &lt;/span&gt;github.com/praetorian-inc/julius/cmd/julius@latest   

&lt;span class="c"&gt;# Basic usage&lt;/span&gt;
julius probe https://target.example.com:11434
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Example output:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;TARGET                        SERVICE   SPECIFICITY  CATEGORY     MODELS
https://target.example.com    ollama    100          self-hosted  llama2, mistral
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  Julius vs Alternatives
&lt;/h3&gt;

&lt;p&gt;&lt;strong&gt;Julius:&lt;/strong&gt;&lt;br&gt;
Services detected: 17+ | External dependencies: None | Offline operation: Yes | Real-time detection: Yes | Model enumeration: Yes | Custom probe extension: Yes (YAML) | Time per target: Seconds&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Shodan Queries:&lt;/strong&gt;&lt;br&gt;
Services detected: Comprehensive | External dependencies: Shodan API and License | Offline operation: No | Real-time detection: Delayed (index lag) | Model enumeration: No | Custom probe extension: No | Time per target: Seconds&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Manual Discovery:&lt;/strong&gt;&lt;br&gt;
Services detected: Unreliable and varied | External dependencies: None | Offline operation: Yes | Real-time detection: Yes | Model enumeration: Manual | Custom probe extension: Not Applicable | Time per target: Minutes, Hours, Days&lt;/p&gt;
&lt;h2&gt;
  
  
  How Julius Works
&lt;/h2&gt;

&lt;p&gt;Julius uses a probe-and-match architecture optimized for speed and accuracy:&lt;/p&gt;

&lt;p&gt;&lt;code&gt;Target URL → Load Probes → HTTP Probes → Rule Match → Specificity Scoring → Report Service&lt;/code&gt;&lt;/p&gt;
&lt;h3&gt;
  
  
  Architectural Decisions
&lt;/h3&gt;

&lt;p&gt;Julius is designed for performance in large-scale assessments:&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Concurrent scanning with errgroup&lt;/strong&gt; → Scan 50+ targets in parallel without race conditions&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Response caching with singleflight&lt;/strong&gt; → Multiple probes hitting &lt;code&gt;/api/models&lt;/code&gt; trigger only one HTTP request&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Embedded probes compiled into binary&lt;/strong&gt; → True single-binary distribution, no external files&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Port-based probe prioritization&lt;/strong&gt; → Target on :11434 runs Ollama probes first for faster identification&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;MD5 response deduplication&lt;/strong&gt; → Identical responses across targets are processed once&lt;/p&gt;
&lt;h3&gt;
  
  
  Project Structure
&lt;/h3&gt;


&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;cmd/julius/          CLI entrypoint                
pkg/                                                                 
  runner/            Command execution (probe, list, validate)
  scanner/           HTTP client, response caching, model extraction 
  rules/             Match rule engine (status, body, header pattern)
  output/            Formatters (table, JSON, JSONL)
  probe/             Probe loader (embedded YAML + filesystem)    
  types/             Core data structures
probes/              YAML probe definitions (one per service)
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;

&lt;h3&gt;
  
  
  Detection Process
&lt;/h3&gt;

&lt;ol&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;Target Normalization:&lt;/strong&gt; Validates and normalizes input URLs&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;Probe Selection:&lt;/strong&gt; Prioritizes probes matching the target's port (if :11434, Ollama probes run first)&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;HTTP Probing:&lt;/strong&gt; Sends requests to service-specific endpoints&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;Rule Matching:&lt;/strong&gt; Compares responses against signature patterns&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;Specificity Scoring:&lt;/strong&gt; Ranks results 1-100 by most specific match&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;Model Extraction:&lt;/strong&gt; Optionally retrieves deployed models via JQ expressions&lt;/p&gt;&lt;/li&gt;
&lt;/ol&gt;
&lt;h3&gt;
  
  
  Specificity Scoring: Eliminating False Positives
&lt;/h3&gt;

&lt;p&gt;Many LLM platforms implement OpenAI-compatible APIs. If Julius detects both "OpenAI-compatible" (specificity: 30) and "LiteLLM" (specificity: 85) on the same endpoint, it reports LiteLLM first.&lt;/p&gt;

&lt;p&gt;This prevents the generic "OpenAI-compatible" match from obscuring the actual service identity.&lt;/p&gt;
&lt;h3&gt;
  
  
  Match Rule Engine
&lt;/h3&gt;

&lt;p&gt;Julius uses six rule types for fingerprinting:&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;status&lt;/strong&gt; → HTTP status code (example: 200 confirms endpoint exists)&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;body.contains&lt;/strong&gt; → JSON structure detection (example: &lt;code&gt;"models":&lt;/code&gt; identifies list responses)&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;body.prefix&lt;/strong&gt; → Response format identification (example: &lt;code&gt;{"object":&lt;/code&gt; matches OpenAI-style)&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;content-type&lt;/strong&gt; → API vs HTML differentiation (example: &lt;code&gt;application/json&lt;/code&gt;)&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;header.contains&lt;/strong&gt; → Service-specific headers (example: &lt;code&gt;X-Ollama-Version&lt;/code&gt;)&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;header.prefix&lt;/strong&gt; → Server identification (example: &lt;code&gt;uvicorn&lt;/code&gt; ASGI fingerprint)&lt;/p&gt;

&lt;p&gt;All rules support negation with &lt;code&gt;not: true&lt;/code&gt;, crucial for distinguishing similar services. For example: "has &lt;code&gt;/api/tags&lt;/code&gt; endpoint" AND "does NOT contain LiteLLM" ensures Ollama detection doesn't match LiteLLM proxies.&lt;/p&gt;

&lt;p&gt;Julius also caches HTTP responses during a scan, so multiple probes targeting the same endpoint don't result in duplicate requests. You can write 100 probes that check &lt;code&gt;/&lt;/code&gt; for different signatures without overloading the target. Julius fetches the page once and evaluates all matching rules against the cached response.&lt;/p&gt;
&lt;h2&gt;
  
  
  Probes Included in Initial Release
&lt;/h2&gt;
&lt;h3&gt;
  
  
  Self-Hosted LLM Servers
&lt;/h3&gt;

&lt;p&gt;&lt;strong&gt;Ollama&lt;/strong&gt; (port 11434) → &lt;code&gt;/api/tags&lt;/code&gt; JSON response + "Ollama is running" banner&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;vLLM&lt;/strong&gt; (port 8000) → &lt;code&gt;/v1/models&lt;/code&gt; with &lt;code&gt;Server: uvicorn&lt;/code&gt; header + &lt;code&gt;/version&lt;/code&gt; endpoint&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;LocalAI&lt;/strong&gt; (port 8080) → &lt;code&gt;/metrics&lt;/code&gt; endpoint containing "LocalAI" markers&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;llama.cpp&lt;/strong&gt; (port 8080) → &lt;code&gt;/v1/models&lt;/code&gt; with &lt;code&gt;owned_by: llamacpp&lt;/code&gt; OR &lt;code&gt;Server: llama.cpp&lt;/code&gt; header&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Hugging Face TGI&lt;/strong&gt; (port 3000) → &lt;code&gt;/info&lt;/code&gt; endpoint with &lt;code&gt;model_id&lt;/code&gt; field&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;LM Studio&lt;/strong&gt; (port 1234) → &lt;code&gt;/api/v0/models&lt;/code&gt; endpoint (LM Studio-specific)&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Nvidia NIM&lt;/strong&gt; (port 8000) → &lt;code&gt;/v1/metadata&lt;/code&gt; with &lt;code&gt;modelInfo&lt;/code&gt; + &lt;code&gt;/v1/health/ready&lt;/code&gt;&lt;/p&gt;
&lt;h3&gt;
  
  
  Proxy &amp;amp; Gateway Services
&lt;/h3&gt;

&lt;p&gt;&lt;strong&gt;LiteLLM&lt;/strong&gt; (port 4000) → &lt;code&gt;/health&lt;/code&gt; with &lt;code&gt;healthy_endpoints&lt;/code&gt; or &lt;code&gt;litellm_metadata&lt;/code&gt; JSON&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Kong&lt;/strong&gt; (port 8000) → &lt;code&gt;Server: kong&lt;/code&gt; header + &lt;code&gt;/status&lt;/code&gt; endpoint&lt;/p&gt;
&lt;h3&gt;
  
  
  Enterprise Cloud Platforms
&lt;/h3&gt;

&lt;p&gt;&lt;strong&gt;Salesforce Einstein&lt;/strong&gt; (port 443) → Messaging API auth endpoint error response&lt;/p&gt;
&lt;h3&gt;
  
  
  ML Demo Platforms
&lt;/h3&gt;

&lt;p&gt;&lt;strong&gt;Gradio&lt;/strong&gt; (port 7860) → &lt;code&gt;/config&lt;/code&gt; with &lt;code&gt;mode&lt;/code&gt; and &lt;code&gt;components&lt;/code&gt;&lt;/p&gt;
&lt;h3&gt;
  
  
  RAG Platforms
&lt;/h3&gt;

&lt;p&gt;&lt;strong&gt;AnythingLLM&lt;/strong&gt; (port 3001) → HTML containing "AnythingLLM"&lt;/p&gt;
&lt;h3&gt;
  
  
  Chat Frontends
&lt;/h3&gt;

&lt;p&gt;&lt;strong&gt;Open WebUI&lt;/strong&gt; (port 3000) → &lt;code&gt;/api/config&lt;/code&gt; with &lt;code&gt;"name":"Open WebUI"&lt;/code&gt;&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;LibreChat&lt;/strong&gt; (port 3080) → HTML containing "LibreChat"&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;SillyTavern&lt;/strong&gt; (port 8000) → HTML containing "SillyTavern"&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Better ChatGPT&lt;/strong&gt; (port 3000) → HTML containing "Better ChatGPT"&lt;/p&gt;
&lt;h3&gt;
  
  
  Generic Detection
&lt;/h3&gt;

&lt;p&gt;&lt;strong&gt;OpenAI-compatible&lt;/strong&gt; (varied ports) → &lt;code&gt;/v1/models&lt;/code&gt; with standard response structure&lt;/p&gt;
&lt;h2&gt;
  
  
  Extending Julius with Custom Probes
&lt;/h2&gt;

&lt;p&gt;Adding support for a new LLM service requires ~20 lines of YAML, no code changes:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight yaml"&gt;&lt;code&gt;&lt;span class="c1"&gt;# probes/my-service.yaml&lt;/span&gt;
&lt;span class="na"&gt;name&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;my-llm-service&lt;/span&gt;
&lt;span class="na"&gt;description&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;Custom LLM service detection&lt;/span&gt;
&lt;span class="na"&gt;category&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;self-hosted&lt;/span&gt;
&lt;span class="na"&gt;port_hint&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="m"&gt;8080&lt;/span&gt;
&lt;span class="na"&gt;specificity&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="m"&gt;75&lt;/span&gt;
&lt;span class="na"&gt;api_docs&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;https://example.com/api-docs&lt;/span&gt;

&lt;span class="na"&gt;requests&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
  &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="na"&gt;type&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;http&lt;/span&gt;
    &lt;span class="na"&gt;path&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;/health&lt;/span&gt;
    &lt;span class="na"&gt;method&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;GET&lt;/span&gt;
    &lt;span class="na"&gt;match&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
      &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="na"&gt;type&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;status&lt;/span&gt;
        &lt;span class="na"&gt;value&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="m"&gt;200&lt;/span&gt;
      &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="na"&gt;type&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;body.contains&lt;/span&gt;
        &lt;span class="na"&gt;value&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s1"&gt;'&lt;/span&gt;&lt;span class="s"&gt;"service":"my-llm"'&lt;/span&gt;

  &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="na"&gt;type&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;http&lt;/span&gt;
    &lt;span class="na"&gt;path&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;/api/version&lt;/span&gt;
    &lt;span class="na"&gt;method&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;GET&lt;/span&gt;
    &lt;span class="na"&gt;match&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
      &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="na"&gt;type&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;status&lt;/span&gt;
        &lt;span class="na"&gt;value&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="m"&gt;200&lt;/span&gt;
      &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="na"&gt;type&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;content-type&lt;/span&gt;
        &lt;span class="na"&gt;value&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;application/json&lt;/span&gt;

&lt;span class="na"&gt;models&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
  &lt;span class="na"&gt;path&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;/api/models&lt;/span&gt;
  &lt;span class="na"&gt;method&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;GET&lt;/span&gt;
  &lt;span class="na"&gt;extract&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="s"&gt;.models[].name"&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Validate your probe:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;julius validate ./probes
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h2&gt;
  
  
  Real World Usage
&lt;/h2&gt;

&lt;h3&gt;
  
  
  Single Target Assessment
&lt;/h3&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;julius probe https://target.example.com                               

julius probe https://target.example.com:11434

julius probe 192.168.1.100:8080
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  Scan Multiple Targets From a File
&lt;/h3&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;julius probe &lt;span class="nt"&gt;-f&lt;/span&gt; targets.txt
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  Output Formats
&lt;/h3&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="c"&gt;# Table (default) - human readable                                                                                                    &lt;/span&gt;
julius probe https://target.example.com                                                                                               

&lt;span class="c"&gt;# JSON - structured for parsing                                                                                                       &lt;/span&gt;
julius probe &lt;span class="nt"&gt;-o&lt;/span&gt; json https://target.example.com                                                                                       

&lt;span class="c"&gt;# JSONL - streaming for large scans                                                                                                   &lt;/span&gt;
julius probe &lt;span class="nt"&gt;-o&lt;/span&gt; jsonl &lt;span class="nt"&gt;-f&lt;/span&gt; targets.txt | jq &lt;span class="s1"&gt;'.service'&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h2&gt;
  
  
  What's Next
&lt;/h2&gt;

&lt;p&gt;Julius is the first tool release of our "The 12 Caesars" open source tool campaign where we will be releasing one open source tool per week for the next 12 weeks. &lt;/p&gt;

&lt;p&gt;Julius focuses on HTTP-based fingerprinting of known LLM services. We're already working on expanding its capabilities while maintaining the lightweight, fast execution that makes it practical for large-scale reconnaissance.&lt;/p&gt;

&lt;p&gt;On our roadmap: additional probes for cloud-hosted LLM services, smarter detection of custom integrations, and the ability to analyze HTTP traffic patterns to identify LLM usage that doesn't follow standard API conventions. We're also exploring how Julius can work alongside AI agents to autonomously discover LLM infrastructure across complex environments.&lt;/p&gt;

&lt;h2&gt;
  
  
  Contributing &amp;amp; Community
&lt;/h2&gt;

&lt;p&gt;Julius is available now under the Apache 2.0 license at &lt;a href="https://github.com/praetorian-inc/julius" rel="noopener noreferrer"&gt;github.com/praetorian-inc/julius&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;We welcome contributions from the community. Whether you're adding probes for services we haven't covered, reporting bugs, or suggesting new features, check the repository's CONTRIBUTING.md for guidance on probe definitions and development workflow.&lt;/p&gt;

&lt;p&gt;Ready to start? Clone the repository, experiment with Julius in your environment, and join the discussion on GitHub. &lt;/p&gt;

&lt;p&gt;Star the project if you find it useful, and let us know what LLM services you'd like to see supported next.&lt;/p&gt;




&lt;p&gt;What shadow AI have you discovered lurking in your environment? Drop your stories in the comments.&lt;/p&gt;

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