<?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: shunta hayashi</title>
    <description>The latest articles on DEV Community by shunta hayashi (@taiman724).</description>
    <link>https://dev.to/taiman724</link>
    <image>
      <url>https://media2.dev.to/dynamic/image/width=90,height=90,fit=cover,gravity=auto,format=auto/https:%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Fuser%2Fprofile_image%2F3927433%2F924dc657-7cbf-4a78-abc1-a006da65ac6f.png</url>
      <title>DEV Community: shunta hayashi</title>
      <link>https://dev.to/taiman724</link>
    </image>
    <atom:link rel="self" type="application/rss+xml" href="https://dev.to/feed/taiman724"/>
    <language>en</language>
    <item>
      <title>What I learned from my first AI-assisted bug bounty submissions</title>
      <dc:creator>shunta hayashi</dc:creator>
      <pubDate>Fri, 29 May 2026 04:08:57 +0000</pubDate>
      <link>https://dev.to/taiman724/what-i-learned-from-my-first-ai-assisted-bug-bounty-submissions-4fh</link>
      <guid>https://dev.to/taiman724/what-i-learned-from-my-first-ai-assisted-bug-bounty-submissions-4fh</guid>
      <description>&lt;blockquote&gt;
&lt;p&gt;Third post in my "AI-assisted OSS contribution" series. The first two were about &lt;a href="https://dev.to/taiman724/pre-fork-due-diligence-for-oss-contributors-3e9o"&gt;pre-fork due diligence&lt;/a&gt; and &lt;a href="https://dev.to/taiman724/self-dogfooding-using-my-own-ai-pr-scanner-to-ship-a-fix-to-onnx-16lh"&gt;shipping a fix to ONNX with my own PR scanner&lt;/a&gt;. This one is about a harder game: security research and coordinated disclosure.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;For a while my AI-assisted open-source work was about &lt;em&gt;contributions&lt;/em&gt; — typo fixes, docs, small bug fixes, the occasional feature. Pull requests have a forgiving feedback loop: if a PR is wrong, a maintainer comments and you iterate. Bug bounty work is different. The feedback loop is slower, the bar for "novel and correct" is much higher, and a lot of the difficulty has nothing to do with the vulnerability itself.&lt;/p&gt;

&lt;p&gt;I ran a small experiment: use Claude (Opus) to help me find, verify, and write up vulnerabilities in &lt;strong&gt;public, in-scope open-source bug bounty programs&lt;/strong&gt; — the kind that publish a scope and a safe-harbor policy and explicitly invite testing. Here's what actually mattered, mostly the things I didn't expect.&lt;/p&gt;

&lt;h2&gt;
  
  
  1. The duplicate problem is the real boss fight
&lt;/h2&gt;

&lt;p&gt;The single biggest risk to a bounty submission is not "is it a real bug" — it's "did someone already report it." And you usually &lt;em&gt;cannot see&lt;/em&gt; the answer.&lt;/p&gt;

&lt;p&gt;I built a small novelty-checking toolchain around the assistant: query published advisories (GHSA via the GitHub API), aggregate cross-ecosystem advisory data (OSV), search the target repository's own issues and PRs, and pull recent security-research feeds. It catches a lot. But it has a fundamental blind spot: &lt;strong&gt;privately submitted reports are invisible until they're disclosed.&lt;/strong&gt; One of my submissions was closed as a duplicate of a report filed months earlier that I had no way of seeing. The finding was correct. It just wasn't &lt;em&gt;first&lt;/em&gt;.&lt;/p&gt;

&lt;p&gt;The lesson isn't "check harder." Public OSINT can only ever reduce duplicate risk, never eliminate it. The realistic takeaways:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Treat novelty as a probability, not a yes/no.&lt;/li&gt;
&lt;li&gt;Favor surfaces that are &lt;em&gt;less&lt;/em&gt; trodden — newer code paths, recently changed files, parsers for formats nobody enjoys reading.&lt;/li&gt;
&lt;li&gt;Accept that some fraction of correct work will be duplicated, and size your effort accordingly.&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  2. PoC-first, and verify against the real runtime
&lt;/h2&gt;

&lt;p&gt;It is very easy to read code, build a clean mental model of a bug, write a confident report — and be wrong, because the runtime doesn't behave the way the reference manual says it does. I got burned by exactly this kind of gap between "what the spec says" and "what the implementation does."&lt;/p&gt;

&lt;p&gt;The discipline that fixed it: &lt;strong&gt;no claim without a runnable proof of concept, executed against the actual runtime.&lt;/strong&gt; Not pseudocode. Not "this should work." A minimal, contained reproduction on my own machine — localhost only, no third-party or production systems touched — that either fires or it doesn't. An AI assistant is genuinely good at the first 80% of building that PoC fast; the last 20% (does it &lt;em&gt;actually&lt;/em&gt; reproduce?) is non-negotiable and is where most false positives die.&lt;/p&gt;

&lt;h2&gt;
  
  
  3. The signal economy is a real constraint
&lt;/h2&gt;

&lt;p&gt;Modern bounty platforms ration your ability to submit. New researchers get a limited number of "trial" reports, and a reputation/signal score that drops when you file invalid or duplicate reports — low enough, and you get blocked from submitting at all.&lt;/p&gt;

&lt;p&gt;This completely changes the optimal strategy. When submissions are cheap, volume wins. When each submission costs scarce signal, &lt;strong&gt;quality dominates volume&lt;/strong&gt;, and a single duplicate or "informative" close is genuinely expensive. With an AI assistant that can generate plausible-looking reports quickly, this is the most important guardrail: the bottleneck must be &lt;em&gt;verification&lt;/em&gt;, not generation.&lt;/p&gt;

&lt;h2&gt;
  
  
  4. The 2026 market got harder while I was learning
&lt;/h2&gt;

&lt;p&gt;Some honest context, because it shaped my results. The open-source bounty landscape contracted noticeably in early 2026:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;The Internet Bug Bounty paused new submissions and cut its payout tiers steeply.&lt;/li&gt;
&lt;li&gt;At least one major runtime's bounty program was paused for lack of funding.&lt;/li&gt;
&lt;li&gt;Platforms tightened minimum signal requirements.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;A widely-cited reason: AI-assisted discovery started producing vulnerability reports faster than open-source maintainers could triage and remediate them. The irony isn't lost on me — the same tooling that makes an individual researcher more productive, in aggregate, helped congest the system that pays them. If you're starting now, plan for fewer open programs and lower-but-real payouts than the headline numbers from a year ago.&lt;/p&gt;

&lt;h2&gt;
  
  
  5. Disclose the AI, every time
&lt;/h2&gt;

&lt;p&gt;I disclose AI assistance in every submission. Not as a disclaimer-shaped apology — as a fact, the same way you'd note any tool in your methodology. Two practical reasons beyond honesty:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Triagers are increasingly wary of low-effort AI spam. Being upfront &lt;em&gt;and&lt;/em&gt; attaching a clean, reproducible PoC is how you signal you're not that.&lt;/li&gt;
&lt;li&gt;If a program's policy requires disclosure and you skip it, you can lose the report (and trust) on a technicality, regardless of finding quality.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;The model does the heavy lifting on code review, hypothesis generation, and drafting. I own scope selection, the decision to submit, the ethics, and the final verification. That division of labor is the whole point.&lt;/p&gt;

&lt;h2&gt;
  
  
  What I'd tell myself at the start
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Verification is the product, not the report.&lt;/strong&gt; Generation is cheap now; correctness isn't.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Duplicate risk is structural.&lt;/strong&gt; Reduce it, price it in, don't pretend you can eliminate it.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Respect the signal economy.&lt;/strong&gt; One careful submission beats five hopeful ones.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Stay in scope, stay contained, disclose the AI.&lt;/strong&gt; The boring compliance stuff is what makes the interesting work sustainable.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;I'm still early — a couple of submissions in, one under triage as I write this, plenty unproven. But the meta-lessons above transferred cleanly from the PR work in my earlier posts: the assistant compresses the &lt;em&gt;mechanical&lt;/em&gt; effort, and that just relocates all the value to judgment — what to look at, whether it's really true, and whether you should hit submit.&lt;/p&gt;

&lt;p&gt;&lt;em&gt;Developed with AI assistance (Claude Opus); all findings were reviewed, reproduced locally, and verified by me before submission. No unpatched or undisclosed vulnerability details are included in this post.&lt;/em&gt;&lt;/p&gt;

</description>
      <category>security</category>
      <category>opensource</category>
      <category>bugbounty</category>
      <category>ai</category>
    </item>
    <item>
      <title>Self-dogfooding: using my own AI-PR scanner to ship a fix to ONNX</title>
      <dc:creator>shunta hayashi</dc:creator>
      <pubDate>Wed, 13 May 2026 10:30:39 +0000</pubDate>
      <link>https://dev.to/taiman724/self-dogfooding-using-my-own-ai-pr-scanner-to-ship-a-fix-to-onnx-16lh</link>
      <guid>https://dev.to/taiman724/self-dogfooding-using-my-own-ai-pr-scanner-to-ship-a-fix-to-onnx-16lh</guid>
      <description>&lt;blockquote&gt;
&lt;p&gt;&lt;em&gt;Note: This article documents work performed with AI assistance (Claude Sonnet 4.6 via Claude Code), including the original bug analysis, the pre-submission review that prompted the path change, and the PR that was ultimately submitted. All technical claims are verified against the ONNX source tree and the public PR.&lt;/em&gt;&lt;/p&gt;
&lt;/blockquote&gt;




&lt;h2&gt;
  
  
  The hook: a real bug that was never going to ship as an advisory
&lt;/h2&gt;

&lt;p&gt;The bug was straightforward once I saw it. In &lt;code&gt;onnx/utils.py&lt;/code&gt;, a helper function called &lt;code&gt;_tar_members_filter&lt;/code&gt; uses a plain &lt;code&gt;str.startswith()&lt;/code&gt; call to validate that a tar archive member lives inside the intended extraction directory:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight python"&gt;&lt;code&gt;&lt;span class="c1"&gt;# onnx/utils.py  (simplified)
&lt;/span&gt;&lt;span class="n"&gt;abs_base&lt;/span&gt;   &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;os&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;path&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;abspath&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;base&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="n"&gt;abs_member&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;os&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;path&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;abspath&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;member_path&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="ow"&gt;not&lt;/span&gt; &lt;span class="n"&gt;abs_member&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;startswith&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;abs_base&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;   &lt;span class="c1"&gt;# &amp;lt;-- no os.sep guard
&lt;/span&gt;    &lt;span class="k"&gt;raise&lt;/span&gt; &lt;span class="nc"&gt;RuntimeError&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;traversal detected&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The problem is that &lt;code&gt;startswith&lt;/code&gt; is a string operation, not a path operation. Given a base directory of &lt;code&gt;/home/user/.onnx/models&lt;/code&gt;, a crafted archive member resolving to &lt;code&gt;/home/user/.onnx/models_evil/pwned.txt&lt;/code&gt; passes the check: the string &lt;code&gt;"models_evil"&lt;/code&gt; begins with the string &lt;code&gt;"models"&lt;/code&gt;. A separator guard — &lt;code&gt;startswith(abs_base + os.sep)&lt;/code&gt; — closes the gap. Without it, files can be written outside the extraction directory on Python 3.10 and 3.11, the versions where the fallback filter is active.&lt;/p&gt;

&lt;p&gt;I found this via static analysis. The fix was one line. I had a working proof-of-concept. My first instinct was to head straight to a bug bounty platform and file an advisory.&lt;/p&gt;

&lt;p&gt;I paused instead — and that pause changed what happened next.&lt;/p&gt;




&lt;h2&gt;
  
  
  The reviewer that changed my mind
&lt;/h2&gt;

&lt;p&gt;Before submitting anything, I ran a pre-submission review pass using an LLM agent configured for deep, adversarial analysis. I think of this as a second opinion before publication: give it the same evidence I have, tell it to find holes, and treat the output seriously.&lt;/p&gt;

&lt;p&gt;The technical verdict came back positive. The root cause — &lt;code&gt;startswith&lt;/code&gt; missing &lt;code&gt;+ os.sep&lt;/code&gt; — was confirmed correct. The fix was confirmed correct. The proof-of-concept logic was independently traced and verified.&lt;/p&gt;

&lt;p&gt;Then the report pivoted:&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;&lt;em&gt;Two essentially identical reports already exist on the bounty platform (both self-closed by the reporters), and a third was marked Duplicate.&lt;/em&gt;&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;The reviewer had checked the platform's listing for this repository. Three prior reports — same function, same root cause, same string-comparison bug — filed in March 2026. Two were self-closed by the reporters, almost certainly after maintainer feedback. One was marked Duplicate.&lt;/p&gt;

&lt;p&gt;The reviewer's framing was precise:&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;&lt;em&gt;Submitting a third (fourth) variant of the same finding carries serious duplicate-judgement and reputation risk that outweighs the (real but low-reach) underlying flaw.&lt;/em&gt;&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;It also identified weaknesses I had not fully interrogated: the vulnerable code path is only reachable through the backend test runner (not any production inference path), the affected Python versions (3.10 and 3.11) represent a shrinking window, and the impact framing in my draft was more aggressive than the actual constraint arithmetic supported.&lt;/p&gt;

&lt;p&gt;This is the part of AI-assisted research that I find most useful and most underrated: not the initial discovery, but the disciplined second-pass that asks "is this the right thing to do with the finding?"&lt;/p&gt;

&lt;p&gt;The answer was clearly no. Filing a fourth variant of a finding that three other reporters had already abandoned, on a platform that was already under scrutiny for low-quality reports, was not a good use of anyone's time.&lt;/p&gt;

&lt;p&gt;The real lesson: &lt;strong&gt;finding a bug does not mean you should file an advisory about it.&lt;/strong&gt; The path from "this is technically wrong" to "I should submit this through a bounty platform" has several gates, and the duplicate-risk gate is one of the most expensive to fail.&lt;/p&gt;




&lt;h2&gt;
  
  
  The pivot: direct PR instead of advisory
&lt;/h2&gt;

&lt;p&gt;Once the bounty-submission path was off the table, the decision was simple. The fix itself was uncontroversially correct. Changing &lt;code&gt;startswith(abs_base)&lt;/code&gt; to &lt;code&gt;startswith(abs_base + os.sep) and abs_member != abs_base&lt;/code&gt; is the canonical pattern. Python's own PEP 706 was written to address this class of tarball extraction vulnerability. The code needed the fix regardless of how many people had noticed the gap.&lt;/p&gt;

&lt;p&gt;A direct pull request solves the actual problem without any of the duplicate risk. The PR gets reviewed on the merits of the code change. Credit is attached to the commit. The maintainer relationship is positive rather than transactional.&lt;/p&gt;

&lt;p&gt;The principles I reached for in this decision:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;
&lt;strong&gt;If the fix is uncontroversial, the PR is the highest-EV path.&lt;/strong&gt; Advisory platforms add value when a finding needs coordinated disclosure, embargo, or cross-maintainer coordination. A one-line correctness fix to a fallback filter does not meet that bar.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Duplicate risk on advisory platforms is a reputation cost, not just a process cost.&lt;/strong&gt; Prior closed reports signal that maintainers have already processed this class of finding. Submitting again without materially new evidence is noise.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;The goal is fixed software, not credited findings.&lt;/strong&gt; If the code gets patched, the outcome is correct whether or not a bounty number is attached.&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;With that settled, I moved to the contribution workflow.&lt;/p&gt;




&lt;h2&gt;
  
  
  Pre-fork due diligence with my own tool
&lt;/h2&gt;

&lt;p&gt;This is the part I find satisfying to document: I used the tool I wrote about in &lt;a href="https://dev.to/taiman724/pre-fork-due-diligence-for-oss-contributors-3e9o"&gt;the first article in this series&lt;/a&gt; to scan the ONNX repository before I forked it.&lt;/p&gt;

&lt;p&gt;&lt;code&gt;gh-pr-trust-scan&lt;/code&gt; is a Python CLI that checks a repository for automated trust-gate workflows, explicit AI-ban policies in contribution documentation, and rejection-signal labels. The question it answers is: "Will this project reject an AI-assisted PR on policy grounds before anyone reviews the code?"&lt;/p&gt;

&lt;p&gt;Running it against &lt;code&gt;onnx/onnx&lt;/code&gt; takes a few seconds:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;gh-pr-trust-scan onnx/onnx
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;





&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Scanning onnx/onnx ...

Repo:    onnx/onnx
Verdict: SAFE

Findings:
  [LOW   ] No explicit AI ban label found

Stats:
  Last commit: 1 day ago
  Open PRs: 34
  Closed-no-merge PRs (last 30): 7
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Verdict: &lt;code&gt;SAFE&lt;/code&gt;. One &lt;code&gt;LOW&lt;/code&gt; finding — absence of any rejection-signal label, which is informational rather than a warning. No automated trust-gate workflows, no AI-ban policy language in CONTRIBUTING.md or the PR template, no labels associated with automated rejection.&lt;/p&gt;

&lt;p&gt;This is exactly the confirmation I needed before investing time in the implementation. ONNX is a well-maintained project with active CI, a clear DCO sign-off requirement, and no explicit policy against AI-assisted contributions. The scan took less time than reading CONTRIBUTING.md manually.&lt;/p&gt;

&lt;p&gt;This is what "dogfooding" looks like in practice. I built the tool to avoid wasted contribution effort, and using it before my own fork means I'm running the same workflow I recommend to others. The SAFE verdict also gave me a calibration data point: the tool correctly identified ONNX's DCO sign-off requirement without misclassifying it as a rejection signal.&lt;/p&gt;




&lt;h2&gt;
  
  
  The PR
&lt;/h2&gt;

&lt;p&gt;Branch: &lt;code&gt;fix/tar-traversal-separator-guard&lt;/code&gt;. I named it around the mechanical fix rather than the vulnerability class — advisory-flavored branch names tend to set an adversarial frame before anyone reads the code.&lt;/p&gt;

&lt;p&gt;The change itself is minimal:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight python"&gt;&lt;code&gt;&lt;span class="c1"&gt;# onnx/utils.py — before
&lt;/span&gt;&lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="ow"&gt;not&lt;/span&gt; &lt;span class="n"&gt;abs_member&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;startswith&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;abs_base&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;

&lt;span class="c1"&gt;# onnx/utils.py — after
&lt;/span&gt;&lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="ow"&gt;not&lt;/span&gt; &lt;span class="n"&gt;abs_member&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;startswith&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;abs_base&lt;/span&gt; &lt;span class="o"&gt;+&lt;/span&gt; &lt;span class="n"&gt;os&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;sep&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="ow"&gt;and&lt;/span&gt; &lt;span class="n"&gt;abs_member&lt;/span&gt; &lt;span class="o"&gt;!=&lt;/span&gt; &lt;span class="n"&gt;abs_base&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The &lt;code&gt;and abs_member != abs_base&lt;/code&gt; clause handles the edge case where the member path resolves to exactly the base directory itself, which should be allowed.&lt;/p&gt;

&lt;p&gt;I also moved &lt;code&gt;abs_base = os.path.abspath(base)&lt;/code&gt; outside the loop. The original code recomputed the same value on every iteration — a minor performance fix that also makes the intent clearer.&lt;/p&gt;

&lt;p&gt;Three regression tests cover the cases that matter:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight python"&gt;&lt;code&gt;&lt;span class="c1"&gt;# onnx/test/test_tar_members_filter.py (representative cases)
&lt;/span&gt;&lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;test_normal_member_allowed&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;self&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;
    &lt;span class="c1"&gt;# tar member inside base → passes filter
&lt;/span&gt;
&lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;test_sibling_prefix_rejected&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;self&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;
    &lt;span class="c1"&gt;# "../models_evil/pwned.txt" style bypass → raises RuntimeError
&lt;/span&gt;
&lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;test_exact_base_dir_allowed&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;self&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;
    &lt;span class="c1"&gt;# member resolving exactly to abs_base → allowed
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The PR description includes an explicit AI disclosure ("researched and drafted with AI assistance via Claude Code"), zero use of advisory-escalating language (no "CVE," "exploit," "RCE," or "vulnerability" in the PR title or summary), and a DCO sign-off on every commit as ONNX's CONTRIBUTING.md requires. The fix is framed as what it is: a correctness improvement to the path-containment check in a fallback branch.&lt;/p&gt;

&lt;p&gt;The PR is at: &lt;strong&gt;&lt;a href="https://github.com/onnx/onnx/pull/7948" rel="noopener noreferrer"&gt;https://github.com/onnx/onnx/pull/7948&lt;/a&gt;&lt;/strong&gt;&lt;/p&gt;




&lt;h2&gt;
  
  
  Lessons
&lt;/h2&gt;

&lt;p&gt;The full loop in this session looked like this:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;static analysis → bug found
     ↓
proof-of-concept → bug confirmed
     ↓
DEEP_REVIEW → duplicate risk identified, advisory path closed
     ↓
pivot decision → direct PR
     ↓
gh-pr-trust-scan onnx/onnx → SAFE verdict, fork with confidence
     ↓
implementation + tests → PR submitted
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Start to PR submission happened inside a single working session. The only reason the loop closed cleanly was the pre-submission review step: without it, I would have filed an advisory that duplicated three prior reports and likely produced no useful outcome.&lt;/p&gt;

&lt;p&gt;A few things I am taking forward:&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Finding a bug and deciding what to do about it are different skills.&lt;/strong&gt; Static analysis and proof-of-concept work are pattern recognition problems. Deciding whether to file an advisory, open a PR, or do nothing requires understanding the platform dynamics, the prior report history, and the realistic impact of the finding. These are judgment calls that benefit from a structured second-opinion process.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Pre-submission review is cost-effective at any scope.&lt;/strong&gt; The review pass took less than the time I would have spent polishing the advisory before submission. Catching duplicate risk at the review stage costs essentially nothing; catching it after submission costs reputation.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Using a tool on your own work produces better feedback than testing it on examples.&lt;/strong&gt; Running &lt;code&gt;gh-pr-trust-scan&lt;/code&gt; against &lt;code&gt;onnx/onnx&lt;/code&gt; gave me concrete signal about edge cases — how the tool handles DCO requirements without flagging them as AI-hostile — than any synthetic test scenario. Running it on a real, active OSS project confirmed that severity calibration is sensible.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;What comes next:&lt;/strong&gt; monitor the PR for maintainer feedback. If the review surfaces a preference for the &lt;code&gt;pathlib.Path.relative_to()&lt;/code&gt; formulation over &lt;code&gt;startswith + os.sep&lt;/code&gt;, that goes into the code and serves as a useful style data point for future contributions.&lt;/p&gt;




&lt;p&gt;&lt;em&gt;This article was researched and drafted with AI assistance (Claude Sonnet 4.6 via Claude Code). Tool behavior described matches the gh-pr-trust-scan codebase as of May 2026 (commit d9b365a). The ONNX PR linked above is real and submitted under the same author identity (taiman724 on GitHub, with DCO sign-off).&lt;/em&gt;&lt;/p&gt;

</description>
      <category>opensource</category>
      <category>github</category>
      <category>contributing</category>
      <category>security</category>
    </item>
    <item>
      <title>Pre-fork due diligence for OSS contributors</title>
      <dc:creator>shunta hayashi</dc:creator>
      <pubDate>Tue, 12 May 2026 15:11:32 +0000</pubDate>
      <link>https://dev.to/taiman724/pre-fork-due-diligence-for-oss-contributors-3e9o</link>
      <guid>https://dev.to/taiman724/pre-fork-due-diligence-for-oss-contributors-3e9o</guid>
      <description>&lt;blockquote&gt;
&lt;p&gt;&lt;em&gt;Note: This article was researched and drafted with AI assistance (Claude Sonnet 4.6 via Claude Code). All claims about specific repository policies are illustrative; readers should verify current state before acting on them.&lt;/em&gt;&lt;/p&gt;
&lt;/blockquote&gt;




&lt;h2&gt;
  
  
  Why you should scan a repo before you fork it
&lt;/h2&gt;

&lt;p&gt;You found an issue. You know exactly how to fix it. You fork the repo, write the code, open a pull request — and it gets closed in minutes, not by a human, but by an automated workflow you never knew existed. No review. No feedback. Just a bot verdict and a "wasted" label.&lt;/p&gt;

&lt;p&gt;This scenario has become noticeably more common in 2025 and 2026. A growing number of open-source maintainers have responded to the flood of low-quality, AI-generated contributions by deploying automated trust-gate systems directly in their CI pipelines. These gates can reject a PR silently — or with a curt machine-generated comment — based on signals that have nothing to do with whether your code is correct. They evaluate &lt;em&gt;who&lt;/em&gt; contributed and &lt;em&gt;how&lt;/em&gt;, not just &lt;em&gt;what&lt;/em&gt; was contributed.&lt;/p&gt;

&lt;p&gt;The cost is asymmetric. A maintainer's automated rejection takes milliseconds. The contributor's lost time — understanding the codebase, writing tests, drafting a good PR description — might be hours or days. Pre-fork due diligence costs five minutes. Doing it consistently is one of the highest-leverage habits an AI-assisted contributor can develop in 2026.&lt;/p&gt;




&lt;h2&gt;
  
  
  Common rejection vectors
&lt;/h2&gt;

&lt;h3&gt;
  
  
  Automated trust-gate workflows
&lt;/h3&gt;

&lt;p&gt;The most aggressive rejection mechanism is a CI workflow that evaluates the contributor's account history before it evaluates the code. These tools look at signals like global merge ratio (how many of your past PRs across all of GitHub were merged versus closed), account age, and contribution velocity. If your profile doesn't meet the threshold, the workflow closes the PR automatically and may apply a label like &lt;code&gt;suspicious-author&lt;/code&gt; or &lt;code&gt;spam-likely&lt;/code&gt;.&lt;/p&gt;

&lt;p&gt;These workflows are usually small GitHub Actions that run on &lt;code&gt;pull_request&lt;/code&gt; events. They're often invisible from the repo's front page — you have to look inside &lt;code&gt;.github/workflows/&lt;/code&gt; to find them. Common identifiers include step names or action references containing strings like &lt;code&gt;trust-score&lt;/code&gt;, &lt;code&gt;min-global-merge-ratio&lt;/code&gt;, or references to community-maintained anti-spam action collections. A new GitHub account used primarily for AI-assisted contribution is exactly the profile these tools are tuned to catch.&lt;/p&gt;

&lt;h3&gt;
  
  
  Anti-slop quality-gate workflows
&lt;/h3&gt;

&lt;p&gt;A second category focuses on content quality rather than account history. These workflows look for statistical signals associated with machine-generated text — unusual vocabulary distributions, patterns common in LLM output, or structural anti-patterns in commit messages and PR descriptions. The term "slop" has become shorthand for this class of low-effort generated content in OSS communities. Workflows in this family typically reference action names or step IDs containing &lt;code&gt;anti-slop&lt;/code&gt; or similar identifiers.&lt;/p&gt;

&lt;p&gt;It is worth noting that a well-crafted, human-reviewed AI-assisted contribution can pass these checks — but only if the contributor has actually read and understood the code before submitting. Blind "generate and submit" workflows are what these gates are designed to block.&lt;/p&gt;

&lt;h3&gt;
  
  
  Explicit AI bans in contribution documentation
&lt;/h3&gt;

&lt;p&gt;The third category is simpler to detect but easier to overlook: written policy. Many maintainers have added explicit clauses to &lt;code&gt;CONTRIBUTING.md&lt;/code&gt;, PR templates, or even the main &lt;code&gt;README.md&lt;/code&gt; stating that AI-generated or AI-assisted contributions are not accepted. Language varies:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;&lt;code&gt;"AI tools are not permitted"&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;"no AI"&lt;/code&gt; / &lt;code&gt;"ban AI"&lt;/code&gt; / &lt;code&gt;"prohibit AI"&lt;/code&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;"LLM not allowed"&lt;/code&gt; / &lt;code&gt;"Copilot is not allowed"&lt;/code&gt;
&lt;/li&gt;
&lt;li&gt;&lt;code&gt;"all submissions must be human-written"&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;&lt;code&gt;"human-authored contributions only"&lt;/code&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Some policies stop short of an outright ban but require disclosure: &lt;code&gt;"disclose AI"&lt;/code&gt; or &lt;code&gt;"AI disclosure required"&lt;/code&gt;. These MEDIUM-severity signals are worth reading carefully — a disclosure requirement is very different from a ban, but missing it can still get your PR closed.&lt;/p&gt;

&lt;h3&gt;
  
  
  Rejection-signal labels
&lt;/h3&gt;

&lt;p&gt;Finally, some repos attach labels that serve as a public ledger of past rejections. Labels like &lt;code&gt;no-ai&lt;/code&gt;, &lt;code&gt;ai-rejected&lt;/code&gt;, &lt;code&gt;human-only&lt;/code&gt;, &lt;code&gt;ai-ban&lt;/code&gt;, and &lt;code&gt;ai-generated-rejected&lt;/code&gt; are visible on closed PRs and on the label list itself. A repo with fifty closed PRs all tagged &lt;code&gt;ai-generated-rejected&lt;/code&gt; is telling you something important about maintainer tolerance, regardless of what the written policy says.&lt;/p&gt;




&lt;h2&gt;
  
  
  The manual scan workflow
&lt;/h2&gt;

&lt;p&gt;You can run a quick scan by hand using the GitHub CLI (&lt;code&gt;gh&lt;/code&gt;). The following three commands cover the main surface areas.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Step 1 — Check workflow files for trust-gate patterns:&lt;/strong&gt;&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="c"&gt;# List all workflow file names, then inspect suspicious ones&lt;/span&gt;
gh api repos/&amp;lt;owner&amp;gt;/&amp;lt;repo&amp;gt;/contents/.github/workflows &lt;span class="se"&gt;\&lt;/span&gt;
  &lt;span class="nt"&gt;--jq&lt;/span&gt; &lt;span class="s1"&gt;'.[].name'&lt;/span&gt;

&lt;span class="c"&gt;# Fetch the content of a specific workflow and grep for known patterns&lt;/span&gt;
gh api repos/&amp;lt;owner&amp;gt;/&amp;lt;repo&amp;gt;/contents/.github/workflows/pr-check.yml &lt;span class="se"&gt;\&lt;/span&gt;
  &lt;span class="nt"&gt;--jq&lt;/span&gt; &lt;span class="s1"&gt;'.content'&lt;/span&gt; | &lt;span class="nb"&gt;base64&lt;/span&gt; &lt;span class="nt"&gt;-d&lt;/span&gt; | &lt;span class="se"&gt;\&lt;/span&gt;
  &lt;span class="nb"&gt;grep&lt;/span&gt; &lt;span class="nt"&gt;-iE&lt;/span&gt; &lt;span class="s1"&gt;'trust-score|anti-slop|min-global-merge-ratio|fossier'&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;strong&gt;Step 2 — Scan CONTRIBUTING.md for policy language:&lt;/strong&gt;&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="c"&gt;# Fetch CONTRIBUTING.md and search for AI-related policy keywords&lt;/span&gt;
gh api repos/&amp;lt;owner&amp;gt;/&amp;lt;repo&amp;gt;/contents/CONTRIBUTING.md &lt;span class="se"&gt;\&lt;/span&gt;
  &lt;span class="nt"&gt;--jq&lt;/span&gt; &lt;span class="s1"&gt;'.content'&lt;/span&gt; | &lt;span class="nb"&gt;base64&lt;/span&gt; &lt;span class="nt"&gt;-d&lt;/span&gt; | &lt;span class="se"&gt;\&lt;/span&gt;
  &lt;span class="nb"&gt;grep&lt;/span&gt; &lt;span class="nt"&gt;-iE&lt;/span&gt; &lt;span class="s1"&gt;'no.?ai|ai.is.not.allowed|ai.tools|human.authored|human.written|llm.not.allowed|disclose.ai|ban.ai|prohibit.ai|reject.ai'&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;strong&gt;Step 3 — Inspect repository labels:&lt;/strong&gt;&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="c"&gt;# List all labels; look for rejection-signal names&lt;/span&gt;
gh label list &lt;span class="nt"&gt;--repo&lt;/span&gt; &amp;lt;owner&amp;gt;/&amp;lt;repo&amp;gt; | &lt;span class="se"&gt;\&lt;/span&gt;
  &lt;span class="nb"&gt;grep&lt;/span&gt; &lt;span class="nt"&gt;-iE&lt;/span&gt; &lt;span class="s1"&gt;'no-ai|ai-rejected|human-only|ai-ban|ai-generated'&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Running all three before you fork gives you a solid picture in under a minute. The limitation is that you have to remember to do it, and you need to know what patterns to look for. That's the gap the tool below is designed to close.&lt;/p&gt;




&lt;h2&gt;
  
  
  Automating with gh-pr-trust-scan
&lt;/h2&gt;

&lt;p&gt;&lt;code&gt;gh-pr-trust-scan&lt;/code&gt; is a small Python CLI that wraps the three manual steps above into a single command, applies a curated set of detection patterns, and produces a machine-readable verdict. It was built specifically to answer one question: &lt;em&gt;"Will this project reject my AI-assisted PR on policy grounds before anyone looks at the code?"&lt;/em&gt;&lt;/p&gt;

&lt;h3&gt;
  
  
  Installing the tool
&lt;/h3&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="c"&gt;# Recommended: isolated environment via pipx&lt;/span&gt;
pipx &lt;span class="nb"&gt;install &lt;/span&gt;gh-pr-trust-scan

&lt;span class="c"&gt;# Or with pip&lt;/span&gt;
pip &lt;span class="nb"&gt;install &lt;/span&gt;gh-pr-trust-scan
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;blockquote&gt;
&lt;p&gt;&lt;strong&gt;Note:&lt;/strong&gt; The package is not yet published to PyPI (coming soon). During the development period, install from source:&lt;/p&gt;


&lt;pre class="highlight shell"&gt;&lt;code&gt;git clone https://github.com/taiman724/gh-pr-trust-scan
&lt;span class="nb"&gt;cd &lt;/span&gt;gh-pr-trust-scan
pip &lt;span class="nb"&gt;install&lt;/span&gt; &lt;span class="nt"&gt;-e&lt;/span&gt; &lt;span class="s2"&gt;".[dev]"&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/blockquote&gt;

&lt;p&gt;Requirements: Python 3.10+ and the GitHub CLI (&lt;code&gt;gh&lt;/code&gt;) authenticated via &lt;code&gt;gh auth login&lt;/code&gt;.&lt;/p&gt;

&lt;h3&gt;
  
  
  Running a scan
&lt;/h3&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="c"&gt;# Basic scan — prints a human-readable verdict&lt;/span&gt;
gh-pr-trust-scan owner/repo

&lt;span class="c"&gt;# Full GitHub URL also works&lt;/span&gt;
gh-pr-trust-scan https://github.com/owner/repo

&lt;span class="c"&gt;# JSON output for scripting or CI integration&lt;/span&gt;
gh-pr-trust-scan owner/repo &lt;span class="nt"&gt;--json&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The tool produces one of three verdicts:&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Verdict&lt;/th&gt;
&lt;th&gt;When it fires&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;SAFE&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;No explicit AI contribution policy detected (all findings LOW or none)&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;WARN&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;Discouraging policy language or rejection labels found, but no automated gate&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;AVOID&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;At least one HIGH-severity finding — an automated rejection gate is present&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;A SAFE verdict on a repo with an actively maintained codebase and no policy signals is a reasonable green light. A WARN verdict calls for reading the actual CONTRIBUTING.md carefully before investing time. An AVOID verdict means a bot will likely close your PR before a human sees it.&lt;/p&gt;

&lt;p&gt;Here is what the output looks like for a repo with multiple signals:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Scanning example-org/example-repo ...

Repo:    example-org/example-repo
Verdict: AVOID  (trust-gate detected)

Findings:
  [HIGH  ] Trust-score gate detected in workflow (.github/workflows/pr-review.yml)
  [MEDIUM] 'human-written' requirement found (line 18): All submissions must be human-written. (CONTRIBUTING.md)
  [MEDIUM] Label 'human-only' found

Stats:
  Last commit: 1 day ago
  Open PRs: 23
  Closed-no-merge PRs (last 30): 9
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;And the equivalent JSON, useful for scripting:&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;"repo"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"example-org/example-repo"&lt;/span&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;"AVOID"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"findings"&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="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
      &lt;/span&gt;&lt;span class="nl"&gt;"severity"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"HIGH"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
      &lt;/span&gt;&lt;span class="nl"&gt;"category"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"trust_gate"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
      &lt;/span&gt;&lt;span class="nl"&gt;"evidence"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"trust-score gate detected in workflow"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
      &lt;/span&gt;&lt;span class="nl"&gt;"file"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;".github/workflows/pr-review.yml"&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;span class="nl"&gt;"severity"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"MEDIUM"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
      &lt;/span&gt;&lt;span class="nl"&gt;"category"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"human_only_requirement"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
      &lt;/span&gt;&lt;span class="nl"&gt;"evidence"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"'human-written' requirement found (line 18)"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
      &lt;/span&gt;&lt;span class="nl"&gt;"file"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"CONTRIBUTING.md"&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;span class="nl"&gt;"severity"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"MEDIUM"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
      &lt;/span&gt;&lt;span class="nl"&gt;"category"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"label"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
      &lt;/span&gt;&lt;span class="nl"&gt;"evidence"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"Label 'human-only' found"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
      &lt;/span&gt;&lt;span class="nl"&gt;"file"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"labels"&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;span class="nl"&gt;"stats"&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;"last_commit"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"1 day ago"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="nl"&gt;"open_prs"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mi"&gt;23&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="nl"&gt;"closed_no_merge_last_30d"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mi"&gt;9&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="nl"&gt;"flagged_closed_prs"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mi"&gt;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;h3&gt;
  
  
  Adding custom patterns
&lt;/h3&gt;

&lt;p&gt;All detection keywords live in a single file: &lt;code&gt;src/gh_pr_trust_scan/patterns.py&lt;/code&gt;. Adding a new trust-gate or policy phrase requires no changes to the scanner logic — just append an entry to the appropriate list:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight python"&gt;&lt;code&gt;&lt;span class="c1"&gt;# patterns.py — adding a custom workflow pattern
&lt;/span&gt;&lt;span class="n"&gt;WORKFLOW_PATTERNS&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="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;pattern&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="sa"&gt;r&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;my-org/custom-trust-gate-action&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;severity&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;HIGH&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;category&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;trust_gate&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;description&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;Custom trust gate action detected&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
&lt;span class="p"&gt;})&lt;/span&gt;

&lt;span class="c1"&gt;# Adding a new text-file pattern (e.g. a new policy phrase)
&lt;/span&gt;&lt;span class="n"&gt;TEXT_PATTERNS_HIGH&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="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;pattern&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="sa"&gt;r&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;\bno\s+generated\s+code\b&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;severity&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;HIGH&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;category&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;ai_ban_explicit&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;description&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;no generated code&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt; policy found&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
&lt;span class="p"&gt;})&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The pattern values are Python regexes compiled case-insensitively, so you can handle variations with standard regex syntax. The community is especially interested in patterns for emerging tools and newly observed policy phrases — if you encounter a rejection mechanism that the tool misses, a PR adding the pattern is a concise and high-value contribution.&lt;/p&gt;




&lt;h2&gt;
  
  
  Closing thoughts
&lt;/h2&gt;

&lt;p&gt;&lt;code&gt;gh-pr-trust-scan&lt;/code&gt; is a static signal detector. It catches what is written down and what is visible in the repository's public API. It cannot tell you whether a maintainer will appreciate your change, whether the project is actively maintained, or whether your implementation approach aligns with the project's unstated conventions. Those questions still require reading the repo: scanning open issues, reviewing recent merged PRs, and — when in doubt — opening an issue to discuss before writing code.&lt;/p&gt;

&lt;p&gt;The broader advice stands regardless of what tools you use: invest a few minutes of research before you invest hours of implementation. OSS contribution policies are increasingly explicit and machine-enforced. Treating due diligence as part of your workflow, rather than an afterthought, is what separates PRs that get merged from PRs that get closed by bots.&lt;/p&gt;

&lt;p&gt;Contributions to &lt;code&gt;gh-pr-trust-scan&lt;/code&gt; are welcome. The highest-value PRs are new detection patterns for trust-gate tools or policy language not yet covered. If you encounter a rejection signal that the tool misses, please open an issue first — especially for patterns that touch specific third-party tools, where context matters.&lt;/p&gt;




&lt;p&gt;&lt;em&gt;This article was researched and drafted with AI assistance (Claude Sonnet 4.6 via Claude Code). Pattern data and tool behavior are based on the &lt;code&gt;gh-pr-trust-scan&lt;/code&gt; codebase as of May 2026. Repository policies change — always verify current CONTRIBUTING.md content before acting on a scan result.&lt;/em&gt;&lt;/p&gt;

</description>
      <category>opensource</category>
      <category>github</category>
      <category>contributing</category>
      <category>security</category>
    </item>
  </channel>
</rss>
