<?xml version="1.0" encoding="UTF-8"?>
<rss version="2.0" xmlns:atom="http://www.w3.org/2005/Atom" xmlns:dc="http://purl.org/dc/elements/1.1/">
  <channel>
    <title>DEV Community: Omobolaji Adeyan</title>
    <description>The latest articles on DEV Community by Omobolaji Adeyan (@doidun2).</description>
    <link>https://dev.to/doidun2</link>
    <image>
      <url>https://media2.dev.to/dynamic/image/width=90,height=90,fit=cover,gravity=auto,format=auto/https:%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Fuser%2Fprofile_image%2F3976519%2F273a2c45-4b87-406f-a703-ae44fbdb05c5.jpg</url>
      <title>DEV Community: Omobolaji Adeyan</title>
      <link>https://dev.to/doidun2</link>
    </image>
    <atom:link rel="self" type="application/rss+xml" href="https://dev.to/feed/doidun2"/>
    <language>en</language>
    <item>
      <title>SPF, DKIM, and DMARC in Phishing Detection: Useful Signals, Not Magic Answers</title>
      <dc:creator>Omobolaji Adeyan</dc:creator>
      <pubDate>Tue, 23 Jun 2026 01:11:05 +0000</pubDate>
      <link>https://dev.to/doidun2/spf-dkim-and-dmarc-in-phishing-detection-useful-signals-not-magic-answers-4g91</link>
      <guid>https://dev.to/doidun2/spf-dkim-and-dmarc-in-phishing-detection-useful-signals-not-magic-answers-4g91</guid>
      <description>&lt;p&gt;Email authentication is valuable evidence, but it is not a verdict.&lt;/p&gt;

&lt;p&gt;SPF, DKIM, and DMARC can help a receiving system decide whether a message came&lt;br&gt;
through authorized infrastructure and whether authenticated identities align&lt;br&gt;
with the visible sender. But in phishing detection, treating those checks as&lt;br&gt;
magic answers can create the wrong kind of confidence.&lt;/p&gt;

&lt;p&gt;That distinction shaped the email-authentication support I added to&lt;br&gt;
&lt;a href="https://github.com/omobolajiadeyan/phishguard-ai" rel="noopener noreferrer"&gt;PhishGuard AI&lt;/a&gt;, my&lt;br&gt;
open-source Python phishing-detection project.&lt;/p&gt;

&lt;p&gt;PhishGuard parses a trusted receiver's &lt;code&gt;Authentication-Results&lt;/code&gt; header and&lt;br&gt;
treats authentication failures as supporting evidence. It does not independently&lt;br&gt;
query DNS, evaluate SPF policy, verify DKIM signatures, or perform DMARC&lt;br&gt;
alignment. That means the caller must provide authentication results produced by&lt;br&gt;
a receiver they trust.&lt;/p&gt;

&lt;p&gt;This is deliberate. The goal is explainable phishing detection, not hidden&lt;br&gt;
security theater.&lt;/p&gt;
&lt;h2&gt;
  
  
  What SPF, DKIM, and DMARC Prove
&lt;/h2&gt;

&lt;p&gt;SPF can show whether a sending IP was authorized by the domain's SPF policy.&lt;/p&gt;

&lt;p&gt;DKIM can show whether a message has a valid cryptographic signature from a&lt;br&gt;
domain and whether signed content remained intact.&lt;/p&gt;

&lt;p&gt;DMARC can show whether SPF or DKIM passed in a way that aligns with the visible&lt;br&gt;
sender domain, according to the domain owner's policy.&lt;/p&gt;

&lt;p&gt;Together, these checks are powerful. They make spoofing harder, support domain&lt;br&gt;
protection, and give defenders useful evidence when investigating suspicious&lt;br&gt;
email.&lt;/p&gt;

&lt;p&gt;But they do not answer every question.&lt;/p&gt;
&lt;h2&gt;
  
  
  What They Do Not Prove
&lt;/h2&gt;

&lt;p&gt;An SPF, DKIM, or DMARC pass does not prove that a message is safe.&lt;/p&gt;

&lt;p&gt;Attackers can send malicious content from infrastructure they control. A&lt;br&gt;
compromised mailbox, abused email service, or maliciously registered domain may&lt;br&gt;
still pass authentication checks.&lt;/p&gt;

&lt;p&gt;An authentication failure also does not automatically prove phishing.&lt;/p&gt;

&lt;p&gt;Forwarding, mailing lists, and message transformations can break SPF or DKIM in&lt;br&gt;
ways that are not malicious. If a detector treats every authentication failure&lt;br&gt;
as a phishing verdict, it can punish legitimate mail and create avoidable false&lt;br&gt;
positives.&lt;/p&gt;

&lt;p&gt;This is why PhishGuard treats authentication results as evidence, not proof.&lt;/p&gt;
&lt;h2&gt;
  
  
  The Scoring Boundary
&lt;/h2&gt;

&lt;p&gt;The implementation is intentionally conservative:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Authentication failures can increase risk when combined with suspicious
content.&lt;/li&gt;
&lt;li&gt;Authentication passes do not reduce risk, because authenticated
infrastructure can still send malicious messages.&lt;/li&gt;
&lt;li&gt;Missing, malformed, and unsupported authentication values remain &lt;code&gt;unknown&lt;/code&gt;.&lt;/li&gt;
&lt;li&gt;A single SPF failure is not treated as proof of phishing.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;That last point matters. Security tools should preserve uncertainty when the&lt;br&gt;
input signal is incomplete.&lt;/p&gt;
&lt;h2&gt;
  
  
  Regression Examples
&lt;/h2&gt;

&lt;p&gt;Two regression cases demonstrate the boundary.&lt;/p&gt;

&lt;p&gt;A legitimate forwarded message with SPF failure remained &lt;code&gt;SAFE&lt;/code&gt;, moving from:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;0.3149 -&amp;gt; 0.3595
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;A synthetic credential lure with SPF, DKIM, and DMARC failures moved from:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;0.6525 SUSPICIOUS -&amp;gt; 0.8220 PHISHING
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The difference is context. Authentication failure is more meaningful when other&lt;br&gt;
features also look suspicious.&lt;/p&gt;
&lt;h2&gt;
  
  
  Explainable JSON Output
&lt;/h2&gt;

&lt;p&gt;PhishGuard can export the feature set as JSON so the decision is inspectable:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight json"&gt;&lt;code&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"verdict"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"PHISHING"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"probability"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mf"&gt;0.8134&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"features"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="nl"&gt;"spf_result"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"fail"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="nl"&gt;"dkim_result"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"fail"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="nl"&gt;"dmarc_result"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"fail"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="nl"&gt;"spf_auth_risk"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mf"&gt;1.0&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="nl"&gt;"dkim_auth_risk"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mf"&gt;1.0&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="nl"&gt;"dmarc_auth_risk"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mf"&gt;1.0&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The same analysis can be exported as SARIF 2.1.0 for GitHub Code Scanning and&lt;br&gt;
security automation workflows.&lt;/p&gt;

&lt;p&gt;That is important because security findings should be reviewable by people and&lt;br&gt;
usable by machines.&lt;/p&gt;

&lt;h2&gt;
  
  
  Why SARIF Matters
&lt;/h2&gt;

&lt;p&gt;SARIF gives security tools a standard way to describe findings, rules,&lt;br&gt;
locations, severity, fingerprints, and properties.&lt;/p&gt;

&lt;p&gt;For PhishGuard, SARIF output means a phishing finding can carry the same&lt;br&gt;
explainable feature evidence into CI and code-scanning workflows. A reviewer can&lt;br&gt;
see not only that something was flagged, but why it was flagged.&lt;/p&gt;

&lt;p&gt;This fits the larger design goal: make detection understandable enough that a&lt;br&gt;
human can challenge it.&lt;/p&gt;

&lt;h2&gt;
  
  
  Tests and Safety Checks
&lt;/h2&gt;

&lt;p&gt;The email-authentication work is covered by:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Case-insensitive parser tests&lt;/li&gt;
&lt;li&gt;Forwarding false-positive regression coverage&lt;/li&gt;
&lt;li&gt;CLI support&lt;/li&gt;
&lt;li&gt;JSON and SARIF output examples&lt;/li&gt;
&lt;li&gt;Repository policy checks&lt;/li&gt;
&lt;li&gt;CodeQL&lt;/li&gt;
&lt;li&gt;Packaging verification&lt;/li&gt;
&lt;li&gt;Tests across Python 3.10 through 3.13&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;The project also documents what the current metrics do and do not establish.&lt;br&gt;
Small fixtures and synthetic examples are useful regression evidence, but they&lt;br&gt;
are not the same thing as population-level accuracy or production adoption.&lt;/p&gt;

&lt;h2&gt;
  
  
  Engineering Evidence
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;Project:
&lt;a href="https://github.com/omobolajiadeyan/phishguard-ai" rel="noopener noreferrer"&gt;github.com/omobolajiadeyan/phishguard-ai&lt;/a&gt;
&lt;/li&gt;
&lt;li&gt;GitHub Marketplace Action:
&lt;a href="https://github.com/marketplace/actions/phishguard-ai-phishing-detector" rel="noopener noreferrer"&gt;PhishGuard AI Phishing Detector&lt;/a&gt;
&lt;/li&gt;
&lt;li&gt;Authentication implementation:
&lt;a href="https://github.com/omobolajiadeyan/phishguard-ai/pull/21" rel="noopener noreferrer"&gt;PR #21&lt;/a&gt;
&lt;/li&gt;
&lt;li&gt;JSON and SARIF examples:
&lt;a href="https://github.com/omobolajiadeyan/phishguard-ai/blob/main/docs/EMAIL_OUTPUT_EXAMPLES.md" rel="noopener noreferrer"&gt;EMAIL_OUTPUT_EXAMPLES.md&lt;/a&gt;
&lt;/li&gt;
&lt;li&gt;Current release:
&lt;a href="https://github.com/omobolajiadeyan/phishguard-ai/releases/tag/v0.5.1" rel="noopener noreferrer"&gt;v0.5.1&lt;/a&gt;
&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  The Lesson
&lt;/h2&gt;

&lt;p&gt;Explainable security software should preserve uncertainty.&lt;/p&gt;

&lt;p&gt;SPF, DKIM, and DMARC are useful signals. They can strengthen a phishing&lt;br&gt;
assessment, especially when combined with suspicious content, URL structure, and&lt;br&gt;
other behavioral features.&lt;/p&gt;

&lt;p&gt;But they should not become shortcuts for thinking.&lt;/p&gt;

&lt;p&gt;The more important engineering principle is this: a security tool should show&lt;br&gt;
its work. It should make the signal, trust boundary, limitation, and scoring&lt;br&gt;
impact visible enough for another person to test, question, and improve.&lt;/p&gt;

&lt;p&gt;That is the direction I am building with PhishGuard AI.&lt;/p&gt;

&lt;h2&gt;
  
  
  Feedback I Would Value
&lt;/h2&gt;

&lt;p&gt;If you work with email security, SOC workflows, GitHub Actions, or phishing&lt;br&gt;
analysis, I would welcome technical feedback on three questions:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;Should authentication failures influence risk differently for forwarded
messages, mailing lists, and direct sender-to-recipient mail?&lt;/li&gt;
&lt;li&gt;What additional safe regression examples would make this kind of scoring
easier to trust?&lt;/li&gt;
&lt;li&gt;Would SARIF output from a phishing detector be useful in your CI or security
review workflow, or would another format be more practical?&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;Comments, issues, and small test cases are welcome. The most useful feedback is&lt;br&gt;
specific: a false-positive case, a scoring concern, a documentation gap, or a&lt;br&gt;
workflow where this output would or would not help.&lt;/p&gt;

</description>
      <category>cybersecurity</category>
      <category>emailsecurity</category>
      <category>python</category>
      <category>opensource</category>
    </item>
    <item>
      <title>From Single Files to Scenario Suites: Batch Validation in the OWASP Agent Security Regression Harness</title>
      <dc:creator>Omobolaji Adeyan</dc:creator>
      <pubDate>Thu, 18 Jun 2026 17:33:45 +0000</pubDate>
      <link>https://dev.to/doidun2/from-single-files-to-scenario-suites-batch-validation-in-the-owasp-agent-security-regression-4hn7</link>
      <guid>https://dev.to/doidun2/from-single-files-to-scenario-suites-batch-validation-in-the-owasp-agent-security-regression-4hn7</guid>
      <description>&lt;p&gt;Security regression testing is most valuable when it can cover your entire suite of scenarios in one command - not just a single file at a time. I recently contributed a change to the &lt;a href="https://github.com/OWASP/Agent-Security-Regression-Harness" rel="noopener noreferrer"&gt;OWASP Agent Security Regression Harness&lt;/a&gt; that extends the &lt;code&gt;validate&lt;/code&gt; command to accept files, directories, and glob patterns. The PR was merged with green CI. Here is what the problem was, what changed, and what it teaches about scoped open-source contributions.&lt;/p&gt;

&lt;h2&gt;
  
  
  What the Harness Does
&lt;/h2&gt;

&lt;p&gt;The OWASP Agent Security Regression Harness is a framework for writing and running executable security regression tests for agentic applications and MCP-integrated systems. You define scenarios as YAML files describing security-relevant goals, expected behaviors, and assertions. The harness runs those scenarios against your agent and reports pass or fail results.&lt;/p&gt;

&lt;p&gt;The &lt;code&gt;validate&lt;/code&gt; command checks that a scenario file is structurally correct before you try to run it. Catching a malformed scenario early - before it silently passes CI - is exactly the kind of hygiene that prevents false confidence in a security regression suite.&lt;/p&gt;

&lt;h2&gt;
  
  
  The Problem: One File at a Time
&lt;/h2&gt;

&lt;p&gt;Before this change, &lt;code&gt;validate&lt;/code&gt; accepted a single file path:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;agent-harness validate scenarios/goal_hijack/basic.yaml
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;That works fine with one scenario. Real suites grow. A project might have dozens of scenario files across multiple subdirectories, covering prompt injection, goal hijacking, secret disclosure, and other attack patterns. Validating each file individually does not scale - and more importantly, a CI job that only validates one file gives you incomplete coverage.&lt;/p&gt;

&lt;p&gt;The practical risk: an invalid scenario sitting in a directory that was never validated can produce misleading results. A scenario that fails to parse might be skipped silently, reducing the effective coverage of your regression suite without any visible signal.&lt;/p&gt;

&lt;h2&gt;
  
  
  What Changed
&lt;/h2&gt;

&lt;p&gt;&lt;a href="https://github.com/OWASP/Agent-Security-Regression-Harness/pull/150" rel="noopener noreferrer"&gt;PR #150&lt;/a&gt; extended &lt;code&gt;validate&lt;/code&gt; to accept one or more files, directories, or glob patterns:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="c"&gt;# Validate one file&lt;/span&gt;
agent-harness validate scenarios/goal_hijack/basic.yaml

&lt;span class="c"&gt;# Validate every scenario in a directory (recursive)&lt;/span&gt;
agent-harness validate scenarios/

&lt;span class="c"&gt;# Validate a glob pattern&lt;/span&gt;
agent-harness validate &lt;span class="s2"&gt;"scenarios/**/*.yaml"&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The command prints one line per scenario, a summary, and exits non-zero if any scenario is invalid - making it usable as a CI gate.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight yaml"&gt;&lt;code&gt;&lt;span class="na"&gt;valid&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;goal_hijack_basic&lt;/span&gt;
&lt;span class="na"&gt;valid&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;prompt_injection_system&lt;/span&gt;
&lt;span class="na"&gt;invalid&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;secret_disclosure_draft - field 'assertions' is required&lt;/span&gt;
&lt;span class="nn"&gt;---&lt;/span&gt;
&lt;span class="s"&gt;2 valid, 1 invalid&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The core of the implementation is a &lt;code&gt;_discover_scenario_files&lt;/code&gt; function that handles the three input cases cleanly:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight python"&gt;&lt;code&gt;&lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;_discover_scenario_files&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;patterns&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nb"&gt;list&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="nb"&gt;str&lt;/span&gt;&lt;span class="p"&gt;])&lt;/span&gt; &lt;span class="o"&gt;-&amp;gt;&lt;/span&gt; &lt;span class="nb"&gt;list&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="n"&gt;Path&lt;/span&gt;&lt;span class="p"&gt;]:&lt;/span&gt;
    &lt;span class="sh"&gt;"""&lt;/span&gt;&lt;span class="s"&gt;Return unique scenario files matched by files, directories, or globs.&lt;/span&gt;&lt;span class="sh"&gt;"""&lt;/span&gt;
    &lt;span class="n"&gt;scenario_files&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nb"&gt;list&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="n"&gt;Path&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;[]&lt;/span&gt;
    &lt;span class="n"&gt;seen&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nb"&gt;set&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="n"&gt;Path&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;set&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;

    &lt;span class="k"&gt;for&lt;/span&gt; &lt;span class="n"&gt;pattern&lt;/span&gt; &lt;span class="ow"&gt;in&lt;/span&gt; &lt;span class="n"&gt;patterns&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
        &lt;span class="n"&gt;path&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nc"&gt;Path&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;pattern&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
        &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="n"&gt;path&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;is_dir&lt;/span&gt;&lt;span class="p"&gt;():&lt;/span&gt;
            &lt;span class="n"&gt;matches&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;sorted&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
                &lt;span class="n"&gt;matched&lt;/span&gt;
                &lt;span class="k"&gt;for&lt;/span&gt; &lt;span class="n"&gt;suffix&lt;/span&gt; &lt;span class="ow"&gt;in&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;*.yaml&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;*.yml&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
                &lt;span class="k"&gt;for&lt;/span&gt; &lt;span class="n"&gt;matched&lt;/span&gt; &lt;span class="ow"&gt;in&lt;/span&gt; &lt;span class="n"&gt;path&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;rglob&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;suffix&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
                &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="n"&gt;matched&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;is_file&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
            &lt;span class="p"&gt;)&lt;/span&gt;
        &lt;span class="k"&gt;else&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
            &lt;span class="n"&gt;glob_matches&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;sorted&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
                &lt;span class="nc"&gt;Path&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;match&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="k"&gt;for&lt;/span&gt; &lt;span class="n"&gt;match&lt;/span&gt; &lt;span class="ow"&gt;in&lt;/span&gt; &lt;span class="n"&gt;glob&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;glob&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;pattern&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;recursive&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="bp"&gt;True&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
            &lt;span class="p"&gt;)&lt;/span&gt;
            &lt;span class="n"&gt;matches&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;glob_matches&lt;/span&gt; &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="n"&gt;glob_matches&lt;/span&gt; &lt;span class="k"&gt;else&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="n"&gt;path&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;

        &lt;span class="k"&gt;for&lt;/span&gt; &lt;span class="n"&gt;match&lt;/span&gt; &lt;span class="ow"&gt;in&lt;/span&gt; &lt;span class="n"&gt;matches&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
            &lt;span class="n"&gt;normalized&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;match&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;resolve&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
            &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="n"&gt;normalized&lt;/span&gt; &lt;span class="ow"&gt;in&lt;/span&gt; &lt;span class="n"&gt;seen&lt;/span&gt; &lt;span class="ow"&gt;or&lt;/span&gt; &lt;span class="ow"&gt;not&lt;/span&gt; &lt;span class="n"&gt;match&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;is_file&lt;/span&gt;&lt;span class="p"&gt;():&lt;/span&gt;
                &lt;span class="k"&gt;continue&lt;/span&gt;
            &lt;span class="n"&gt;seen&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;add&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;normalized&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
            &lt;span class="n"&gt;scenario_files&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;append&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;match&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;

    &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="n"&gt;scenario_files&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Deduplication via resolved paths prevents a scenario from being validated twice when a glob pattern and an explicit file path both match the same file.&lt;/p&gt;

&lt;p&gt;The &lt;code&gt;nargs="+"&lt;/code&gt; change to the argument parser is the smallest part of the diff, but it is what makes the whole thing composable in CI:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight yaml"&gt;&lt;code&gt;&lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="na"&gt;name&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;Validate all security scenarios&lt;/span&gt;
  &lt;span class="na"&gt;run&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;agent-harness validate "scenarios/**/*.yaml"&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h2&gt;
  
  
  The Review
&lt;/h2&gt;

&lt;p&gt;The maintainer accepted the change with one round of review. The scope was deliberate: no unrelated refactors, no new dependencies, tests for each input mode (single file, recursive directory, mixed-validity glob), and documentation in the GitHub Actions guide. Keeping a contribution scoped to what the issue actually asked for is what makes a PR reviewable quickly.&lt;/p&gt;

&lt;p&gt;CI ran lint (&lt;code&gt;ruff&lt;/code&gt;), type checking (&lt;code&gt;mypy&lt;/code&gt;), and the full test suite - 333 tests, 1 skipped, all scenario validation checks passing.&lt;/p&gt;

&lt;h2&gt;
  
  
  The Lesson
&lt;/h2&gt;

&lt;p&gt;A validation command that only accepts one file at a time is not a CI-ready tool. The change is small - under 100 lines including tests - but it makes the harness meaningfully more useful for teams running scenario suites in automated pipelines.&lt;/p&gt;

&lt;p&gt;If you work on security regression testing for AI systems or MCP-integrated applications, the OWASP Agent Security Regression Harness is worth looking at. The project has open issues for SARIF output, suite-level runners, and additional adapter support - all practical contributions with clear scope.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://github.com/OWASP/Agent-Security-Regression-Harness/pull/150" rel="noopener noreferrer"&gt;PR #150 on GitHub&lt;/a&gt;&lt;/p&gt;

</description>
      <category>security</category>
      <category>owasp</category>
      <category>python</category>
      <category>testing</category>
    </item>
    <item>
      <title>PhishGuard AI</title>
      <dc:creator>Omobolaji Adeyan</dc:creator>
      <pubDate>Tue, 09 Jun 2026 19:28:21 +0000</pubDate>
      <link>https://dev.to/doidun2/phishguard-ai-291k</link>
      <guid>https://dev.to/doidun2/phishguard-ai-291k</guid>
      <description>&lt;p&gt;I have added explainable SPF, DKIM, and DMARC signals to PhishGuard AI, my open-source Python phishing-detection project.&lt;/p&gt;

&lt;p&gt;The implementation parses a trusted receiver's &lt;code&gt;Authentication-Results&lt;/code&gt;header and treats authentication failures as supporting evidence rather thanproof of phishing.&lt;/p&gt;

&lt;p&gt;Measured regression examples:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;A legitimate forwarded message with SPF failure remained &lt;code&gt;SAFE&lt;/code&gt;, moving from &lt;code&gt;0.3149&lt;/code&gt; to &lt;code&gt;0.3595&lt;/code&gt;.&lt;/li&gt;
&lt;li&gt;A synthetic credential lure with SPF, DKIM, and DMARC failures moved from &lt;code&gt;0.6525 SUSPICIOUS&lt;/code&gt; to &lt;code&gt;0.8220 PHISHING&lt;/code&gt;.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Pass results do not reduce the risk score because authenticated infrastructure can still send malicious messages. Missing, malformed, and unsupported values remain &lt;code&gt;unknown&lt;/code&gt;.&lt;/p&gt;

&lt;p&gt;The change includes case-insensitive parsing, forwarding false-positive coverage, CLI support, documentation, packaging verification, repository policy checks, CodeQL, and tests across Python 3.10 through 3.13.&lt;/p&gt;

&lt;p&gt;Pull request and engineering evidence:&lt;br&gt;
&lt;a href="https://github.com/omobolajiadeyan/phishguard-ai/pull/21" rel="noopener noreferrer"&gt;https://github.com/omobolajiadeyan/phishguard-ai/pull/21&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;The project now also has a one-minute safe demo and a guide for first-time contributors:&lt;/p&gt;

&lt;p&gt;&lt;a href="https://github.com/omobolajiadeyan/phishguard-ai" rel="noopener noreferrer"&gt;https://github.com/omobolajiadeyan/phishguard-ai&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;I welcome technically grounded feedback, testing, documentation improvements, and focused open-source contributions.&lt;/p&gt;

&lt;h1&gt;
  
  
  Cybersecurity #Python #EmailSecurity #OpenSource #DevSecOps
&lt;/h1&gt;

&lt;h2&gt;
  
  
  Suggested Featured Link
&lt;/h2&gt;

&lt;p&gt;Title: Explainable SPF, DKIM and DMARC Signals in PhishGuard AI&lt;/p&gt;

&lt;p&gt;URL: &lt;a href="https://github.com/omobolajiadeyan/phishguard-ai/pull/21" rel="noopener noreferrer"&gt;https://github.com/omobolajiadeyan/phishguard-ai/pull/21&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Description: Conservative email-authentication scoring with false-positive regressions, documented trust boundaries, cross-version tests, CodeQL, and reproducible before-and-after results.&lt;/p&gt;

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