<?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: Piyush Gupta</title>
    <description>The latest articles on DEV Community by Piyush Gupta (@piyush140104).</description>
    <link>https://dev.to/piyush140104</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%2F4089212%2Fb68d839f-7ef5-45a3-bc4f-3319f77b27a1.jpg</url>
      <title>DEV Community: Piyush Gupta</title>
      <link>https://dev.to/piyush140104</link>
    </image>
    <atom:link rel="self" type="application/rss+xml" href="https://dev.to/feed/piyush140104"/>
    <language>en</language>
    <item>
      <title>Building a Community Plugin Marketplace for OWASP OWTF (GSoC 2026)</title>
      <dc:creator>Piyush Gupta</dc:creator>
      <pubDate>Sat, 22 Aug 2026 06:24:06 +0000</pubDate>
      <link>https://dev.to/piyush140104/from-sandbox-to-review-queue-my-gsoc-2026-project-with-owasp-owtf-50dd</link>
      <guid>https://dev.to/piyush140104/from-sandbox-to-review-queue-my-gsoc-2026-project-with-owasp-owtf-50dd</guid>
      <description>&lt;p&gt;This is a write-up of what I built for Google Summer of Code 2026. I worked with the OWASP Foundation on OWTF (&lt;a href="https://github.com/owtf/owtf" rel="noopener noreferrer"&gt;Offensive Web Testing Framework&lt;/a&gt;) over the summer. The rest of this post is basically: what the project was, why it needed to exist, how I built it, what went wrong along the way, and what I took away.&lt;/p&gt;

&lt;h2&gt;
  
  
  What I built
&lt;/h2&gt;

&lt;p&gt;A Community Plugin Marketplace on top of OWTF. Any logged-in OWTF user could upload a Python plugin through the web UI. The plugin got validated on the server the moment it was uploaded, landed in a &lt;code&gt;pending&lt;/code&gt; queue, and waited for an admin to look at the source. Once the admin approved it, the plugin got copied into OWTF's normal plugin table. After that, the runner, the worklist, and the report generator all treated it exactly like a built-in plugin.&lt;/p&gt;

&lt;p&gt;Full delivery was six pull requests against &lt;code&gt;owtf/owtf&lt;/code&gt;, around 6,000 lines of Python and TypeScript, and 153 backend unit tests. All of them are &lt;a href="https://github.com/owtf/owtf/pulls/piyush140104" rel="noopener noreferrer"&gt;here&lt;/a&gt; if you want to look.&lt;/p&gt;

&lt;h2&gt;
  
  
  Why I built it
&lt;/h2&gt;

&lt;p&gt;Before this summer, OWTF plugins had to live inside the OWTF repo. Nothing else. Which meant if you wrote a detection for some new attack, you had exactly two options: send a pull request to the main framework (slow, kind of intimidating), or just keep the plugin on your own laptop.&lt;/p&gt;

&lt;p&gt;Both were bad. Contributing directly to a security framework is a real hurdle for most people, and in practice most useful community plugins were never going to make it upstream. A marketplace was the fix. Contributors upload, admins review, approved plugins run for everyone, and the framework grows without every good idea needing to go through the core maintainers.&lt;/p&gt;

&lt;h2&gt;
  
  
  How I built it
&lt;/h2&gt;

&lt;p&gt;I split the work into six pull requests, one job each, so each PR could be reviewed and merged on its own timeline.&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;PR&lt;/th&gt;
&lt;th&gt;What it did&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;&lt;a href="https://github.com/owtf/owtf/pull/1456" rel="noopener noreferrer"&gt;#1456&lt;/a&gt;&lt;/td&gt;
&lt;td&gt;Data model plus an idempotent DB upgrader that ran on server start (no Alembic dependency)&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;a href="https://github.com/owtf/owtf/pull/1457" rel="noopener noreferrer"&gt;#1457&lt;/a&gt;&lt;/td&gt;
&lt;td&gt;AST validator for uploaded plugin source, with alias tracking&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;a href="https://github.com/owtf/owtf/pull/1458" rel="noopener noreferrer"&gt;#1458&lt;/a&gt;&lt;/td&gt;
&lt;td&gt;Admin role, JWT and &lt;code&gt;@admin_required&lt;/code&gt; decorators, and an &lt;code&gt;owtf-admin&lt;/code&gt; CLI&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;a href="https://github.com/owtf/owtf/pull/1459" rel="noopener noreferrer"&gt;#1459&lt;/a&gt;&lt;/td&gt;
&lt;td&gt;Manager plus REST endpoints (upload, list, mine, review, approve, reject)&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;a href="https://github.com/owtf/owtf/pull/1460" rel="noopener noreferrer"&gt;#1460&lt;/a&gt;&lt;/td&gt;
&lt;td&gt;Runner integration so approved plugins ran through OWTF's standard &lt;code&gt;PluginRunner&lt;/code&gt;
&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;a href="https://github.com/owtf/owtf/pull/1461" rel="noopener noreferrer"&gt;#1461&lt;/a&gt;&lt;/td&gt;
&lt;td&gt;React and TypeScript marketplace UI plus the trust model doc&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;h3&gt;
  
  
  The validator
&lt;/h3&gt;

&lt;p&gt;The most interesting piece for me was the AST validator. It walked the source of an uploaded plugin before the file even hit disk, and it rejected:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Imports:&lt;/strong&gt; &lt;code&gt;os&lt;/code&gt;, &lt;code&gt;sys&lt;/code&gt;, &lt;code&gt;subprocess&lt;/code&gt;, &lt;code&gt;socket&lt;/code&gt;, &lt;code&gt;ctypes&lt;/code&gt;, &lt;code&gt;signal&lt;/code&gt;, &lt;code&gt;resource&lt;/code&gt;, &lt;code&gt;pickle&lt;/code&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Functions:&lt;/strong&gt; &lt;code&gt;eval&lt;/code&gt;, &lt;code&gt;exec&lt;/code&gt;, &lt;code&gt;compile&lt;/code&gt;, &lt;code&gt;__import__&lt;/code&gt;, &lt;code&gt;input&lt;/code&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Dangerous calls:&lt;/strong&gt; &lt;code&gt;os.system&lt;/code&gt;, &lt;code&gt;os.popen&lt;/code&gt;, &lt;code&gt;subprocess(...)&lt;/code&gt; with &lt;code&gt;shell=True&lt;/code&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;File writes:&lt;/strong&gt; &lt;code&gt;open(...)&lt;/code&gt; in write or append modes&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;A naive "just block &lt;code&gt;subprocess&lt;/code&gt;" rule falls apart the second somebody aliases the import. So I made the validator track aliases too:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight python"&gt;&lt;code&gt;&lt;span class="kn"&gt;from&lt;/span&gt; &lt;span class="n"&gt;subprocess&lt;/span&gt; &lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;run&lt;/span&gt; &lt;span class="k"&gt;as&lt;/span&gt; &lt;span class="n"&gt;process&lt;/span&gt;
&lt;span class="nf"&gt;process&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;id&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;shell&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="bp"&gt;True&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;   &lt;span class="c1"&gt;# still rejected
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Any name that resolved back to a blocked module or function got caught, even through renames and re-imports. Without that, the whole check would have been theatre.&lt;/p&gt;

&lt;h3&gt;
  
  
  The trust model
&lt;/h3&gt;

&lt;p&gt;The AST validator was the safety gate at upload time, but the real security boundary of the marketplace was admin source code review. Approved plugins ran with the same permissions as built-in OWTF plugins. I wrote down exactly what that means, and what it does not mean, in &lt;a href="https://github.com/piyush140104/owtf/blob/feat/community-plugin-ui/docs/community_plugin_trust_model.md" rel="noopener noreferrer"&gt;&lt;code&gt;docs/community_plugin_trust_model.md&lt;/code&gt;&lt;/a&gt;. It covers roles, endpoint exposure, serializer discipline, and what was intentionally left out of scope.&lt;/p&gt;

&lt;h2&gt;
  
  
  Challenges I faced
&lt;/h2&gt;

&lt;p&gt;&lt;strong&gt;SQLite lied to me about foreign keys.&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;My first version of the runner integration synthesised community plugin keys at query time. All my local tests passed. All of them. And the moment the same code hit prod postgres, it crashed on a foreign key check. Turned out SQLite (my dev database) doesn't enforce FKs the same way PostgreSQL does, so the whole class of bug was invisible to my test suite.&lt;/p&gt;

&lt;p&gt;The fix was to stop faking it. I changed the approval flow so it wrote a real "mirror row" into the standard &lt;code&gt;plugins&lt;/code&gt; table with &lt;code&gt;source = "community"&lt;/code&gt;. Once that was in place, the FK to &lt;code&gt;test_groups.code&lt;/code&gt; was respected, and every existing query, worklist call, and report generator just worked.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;6,500 line pull requests are not pull requests.&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;I opened my first PR as one giant thing that covered everything: model, validator, admin role, API, runner, UI. Reviews were slow and shallow because nobody could hold that much context in their head at once, myself included. I split it into six focused PRs, and after that reviews got much faster, feedback got sharper, and each PR merged on its own schedule.&lt;/p&gt;

&lt;p&gt;Splitting felt like starting over. It wasn't. It was just the actual work of shipping code that other people could review.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Serializer discipline is easy to get wrong.&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;Community plugin rows had three legitimate audiences: the public API, the plugin's owner, and admin reviewers. Each one got a different serializer. &lt;code&gt;to_dict&lt;/code&gt; was public and only exposed metadata. &lt;code&gt;to_owner_dict&lt;/code&gt; added rejection reasons for the uploader. &lt;code&gt;to_admin_dict&lt;/code&gt; added reviewer identity and resource limits. &lt;code&gt;file_path&lt;/code&gt; never left the server on any of them. I added a pytest assertion (&lt;code&gt;TestSerializersNeverLeakFilePath&lt;/code&gt;) so any regression would fail loudly, because I knew otherwise I would forget.&lt;/p&gt;

&lt;h2&gt;
  
  
  What I learned
&lt;/h2&gt;

&lt;p&gt;More than I expected. Things I'm taking into every future project:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Every hard bug is a design problem in disguise.&lt;/strong&gt; The SQLite vs postgres crash wasn't a debugging problem, it was a "you never modelled the FK properly" problem. Fixing symptoms wasted time.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;CI that runs against a different database than prod is not really CI.&lt;/strong&gt; If your dev DB and prod DB diverge, set up a real prod-DB test run for anything that touches transactions or foreign keys.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Small PRs get reviewed. Large PRs get ignored.&lt;/strong&gt; Splitting was the single highest-leverage thing I did all summer.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Write down the trust model.&lt;/strong&gt; For anything security-related, the doc that spells out what you do and do not protect against is worth more than the code. It stops the next contributor from making the same mistakes.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Ask for real code review, not vibe checks.&lt;/strong&gt; The reviews that actually changed my project were the ones where my mentor pushed back on design. Polite "looks good" reviews taught me nothing.&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  Gotchas for whoever picks this up next
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;Do not synthesise community plugin keys at query time. Persist the mirror row.&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;Plugin.code&lt;/code&gt; has a foreign key to &lt;code&gt;test_groups.code&lt;/code&gt;. Use outer joins to keep community plugins visible in existing queries.&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;file_path&lt;/code&gt; must never leave the server. The pytest assertion mentioned above will yell if it ever does.&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;SIGALRM&lt;/code&gt; timeout enforcement only works on the main thread.&lt;/li&gt;
&lt;li&gt;Import ordering matters: &lt;code&gt;owtf/models/plugin.py&lt;/code&gt; has to import &lt;code&gt;test_group&lt;/code&gt; before &lt;code&gt;metadata.create_all()&lt;/code&gt;, or the FK breaks on fresh installs.&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  Out of scope and future work
&lt;/h2&gt;

&lt;p&gt;A few things I deliberately left out of this summer's delivery:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Actual runtime isolation&lt;/strong&gt; (containerised execution, seccomp). The trust boundary was admin review this summer. A future contributor could layer a container-per-plugin runner under the existing &lt;code&gt;PluginRunner&lt;/code&gt; without breaking the API.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Plugin versioning.&lt;/strong&gt; Approval was per upload. Supporting &lt;code&gt;v1&lt;/code&gt; to &lt;code&gt;v2&lt;/code&gt; upgrades on the same plugin name is the natural next step.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Community moderation&lt;/strong&gt; (star, report, changelog). The data model has room for it, the UI just doesn't surface it yet.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Dependency review.&lt;/strong&gt; The validator was static and didn't inspect third party packages a plugin might install.&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  Acknowledgements
&lt;/h2&gt;

&lt;p&gt;Big thank you to my GSoC mentors for the reviews, the pushback, and the patience across the summer. Thanks also to the OWASP Foundation for accepting the project and to Google for running the program.&lt;/p&gt;

&lt;h2&gt;
  
  
  Links
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;All my PRs:&lt;/strong&gt; &lt;a href="https://github.com/owtf/owtf/pulls/piyush140104" rel="noopener noreferrer"&gt;https://github.com/owtf/owtf/pulls/piyush140104&lt;/a&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;OWTF repo:&lt;/strong&gt; &lt;a href="https://github.com/owtf/owtf" rel="noopener noreferrer"&gt;https://github.com/owtf/owtf&lt;/a&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Trust model doc:&lt;/strong&gt; &lt;a href="https://github.com/piyush140104/owtf/blob/feat/community-plugin-ui/docs/community_plugin_trust_model.md" rel="noopener noreferrer"&gt;&lt;code&gt;docs/community_plugin_trust_model.md&lt;/code&gt;&lt;/a&gt; (on my fork; will be on &lt;code&gt;owtf/owtf&lt;/code&gt; once PR #1461 merges)&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;GSoC project page:&lt;/strong&gt; &lt;a href="https://summerofcode.withgoogle.com/programs/2026/projects/KHC9YGfD" rel="noopener noreferrer"&gt;summerofcode.withgoogle.com/programs/2026/projects/KHC9YGfD&lt;/a&gt;
&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  Contact
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;GitHub:&lt;/strong&gt; &lt;a href="https://github.com/piyush140104" rel="noopener noreferrer"&gt;github.com/piyush140104&lt;/a&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;LinkedIn:&lt;/strong&gt; &lt;a href="https://www.linkedin.com/in/piyush-gupta-2ab942288/" rel="noopener noreferrer"&gt;linkedin.com/in/piyush-gupta-2ab942288&lt;/a&gt;
&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;If you're picking this project up after me, open an issue on &lt;code&gt;owtf/owtf&lt;/code&gt; and tag &lt;code&gt;@piyush140104&lt;/code&gt;. Happy to answer questions.&lt;/p&gt;

</description>
      <category>opensource</category>
      <category>gsoc</category>
      <category>python</category>
      <category>security</category>
    </item>
  </channel>
</rss>
