GSoC 2026 Proposal Draft – Idea 6: Strengthen the security of the plugin ecosystem – Ehtesham-Zahid

GSoC 2026 Proposal Draft – Idea 6: Strengthen the security of the plugin ecosystem
– Ehtesham-Zahid

Links

Joplin Contributions:

Chromium Contributions:


1. Introduction

My name is Ehtesham Zahid, a 4th semester Computer Science student at the University of Lahore, Pakistan. I have been working with TypeScript, React, Node.js, and the MERN stack for the past two years, building full-stack web applications both for clients and personal projects.

My open source experience includes:

  • Chromium (4 Merged PRs): Contributing to the Chromium project gave me hands-on experience navigating massive, complex codebases and adhering to strict, enterprise-level code review and security cultures.
  • Joplin PR #14689 (Merged): Fixed a race condition in handleAppFailure that caused a TypeError: Object has been destroyed during renderer crash recovery on Windows.
  • Joplin PR #14780 (Merged): Improved note list header icons by replacing inconsistent Unicode symbols with FontAwesome icons and adding accessibility labels for screen readers.
  • Joplin PR #14773 (Open): Implementing a global settings search feature for the desktop app (currently under review with feedback from laurent).

For this proposal specifically, I spent significant time reading through the plugin-repo-cli source code, the joplin/plugins workflow file, and RFC #9582 to understand the problem at a code level before proposing a solution. I have also been actively engaging with mentors on the forum to refine the approach.


2. Project Summary

What problem it solves:
Joplin's current plugin pipeline has no security review process. A developer writes a plugin, publishes it to npm, and it gets automatically fetched by plugin-repo-cli and made available to all users within 30 minutes — without any human involvement. The .jpl file is built on the developer's own machine with no guarantee it matches the source code, and some plugins don't even have a public repository, making independent verification impossible. A malicious plugin could silently access a user's notes, steal credentials, or send data to external servers, and nothing in the current pipeline would catch it.

Why it matters to users:
Joplin users trust the plugin ecosystem. Many plugins have access to sensitive data like notes, attachments, and sync credentials. A single malicious plugin reaching users could compromise that trust permanently. As the plugin ecosystem grows, the attack surface grows with it. Currently the only signal of trustworthiness is the "recommended" tag, which is limited to a small set of plugins from known developers. The vast majority of plugins, many of which are genuinely useful and widely used — have no verification signal at all. A structured review process changes this: it gives every plugin a clear, verifiable status that users can rely on, not just a small recommended subset.

What will be implemented:
At a high level, the proposed system replaces the fully automated npm-based pipeline with a review-gated workflow. Today, any developer can publish a plugin to npm and it is made available to all Joplin users within 30 minutes with zero human involvement. The system I propose changes this fundamentally. No plugin reaches users without explicit human approval and a verified source build.

The new workflow works as follows:

  1. Developer pushes source code to a public git repository and opens a review request PR in joplin/plugins with the repository URL and commit hash
  2. Developer comments “Review Plugin“ under PR and automation scans starts. Semgrep scans the source code for dangerous patterns (eval(), dynamic require(), external network calls) and socket.dev scans dependencies for supply chain risks and known vulnerabilities. The PR is blocked until both scans pass
  3. After Both Scans, an AI model via GitHub Models will review the code and give code report in a PR comment to make the review process fast for reviewer
  4. A Joplin reviewer examines the AI report, source code and approves the specific commit hash. If changes are requested, the developer pushes fixes and again runs the automated scans
  5. Only after human approval does Joplin's trusted GitHub Actions runner clone the source at the exact reviewed commit, build the .jpl from scratch, and add it to the repository
  6. Plugins are clearly labelled as reviewed, unreviewed, or suspended in both the repository and the plugin browser, giving users a clear trust signal for every plugin they install

Expected outcome:
Every new plugin reaching users will have been built from reviewed source code on a trusted server. Existing plugins remain available but clearly labelled as unreviewed, and authors can request a review at any time to upgrade their plugin's status. The result is a plugin ecosystem that stays open and developer-friendly while giving users meaningful, verifiable trust signals for every plugin they install.


3. Technical Approach

3.1 Architecture or Components involved:

Current Architecture:

Problems with the current approach:

1. Unverifiable binary: The .jpl made available to users is built on the developer's own machine and downloaded directly from npm. There is no way to verify it was compiled from the source code visible on GitHub. A developer could show clean code on GitHub while publishing a malicious binary to npm and nothing in the current pipeline would catch it.

2. Fully automated, zero human involvement: searchPlugins.ts queries npm every 30 minutes for any package tagged joplin-plugin. Any malicious actor can publish a package with this keyword and it becomes available to all Joplin users within 30 minutes with no human ever seeing it.

3. No review of updates: When a plugin updates, the new version is automatically picked up from npm and made available to users. Nobody reviews what changed between versions. A plugin could be clean on first submission and malicious 6 months later after an update.

4. Write access during plugin code execution: The entire workflow runs with contents: write permission. This means even the step that executes npm install pluginName and builds plugin code has full write access to joplin/plugins. A malicious plugin could include code in webpack.config.js that runs during the build step and pushes malicious changes directly to the repository, affecting all Joplin users.

5. Unpinned CLI dependency: The workflow installs @joplin/plugin-repo-cli from npm on every run with no pinned version. If the npm package is ever compromised through a supply chain attack or account takeover, the malicious version would be installed and executed automatically within 30 minutes with full write access to the repository and access to the GitHub token.

Proposed Architecture:

The proposed system replaces the fully automated npm-based pipeline with a review-gated workflow built around three core components:

1. PR-based submission: Plugin authors submit for review by opening a PR in joplin/plugins with their public git repository URL and the exact commit hash to be reviewed. The review system only requires a public git repository URL and a commit hash, not a GitHub-specific URL. Developers on GitLab, Codeberg, or any other forge submit the same way. The only GitHub-specific part is the review PR itself, which lives in joplin/plugins — Joplin's existing infrastructure.

A PR template in joplin/plugins guides authors through the exact fields needed, repository URL and commit hash. For updates, authors simply open a new PR with the same repository URL and the new commit hash. The template makes submission straightforward even for first time contributors, keeping the process as streamlined as possible.

2. Automated scanning pipeline: When the plugin author comments "Review Plugin" on their PR, GitHub Actions triggers automatically. This comment trigger prevents wasted CI runs on in-progress commits. CI only runs when the author is ready. The workflow triggers plugin-repo-cli scan which clones the source and runs two scanners:

  • Semgrep: scans source code for dangerous patterns including eval(), dynamic require(), external network calls, and sensitive filesystem access

  • socket.dev: scans package.json dependencies for supply chain risks, typosquatting, and known vulnerabilities

Both scans run directly on the cloned source, no npm publish required. If either scan fails, the PR is blocked until the developer resolves the issues. Once scans pass, the AI analysis runs:

An AI model via GitHub Models (free for public repositories) analyzes the cloned source after scans pass and posts a structured report as a PR comment, summarizing what the plugin does, detected capabilities (network access, filesystem, eval usage), and for updates a plain English summary of what changed. The reviewer verifies the report rather than reading raw code from scratch, significantly reducing cognitive load. AI assists but never replaces, final approval always requires a human reviewer.

3. Trusted build pipeline: Only after a human reviewer approves the PR does the trusted GitHub Actions runner clone the source at the exact reviewed commit hash, build the .jpl from scratch using npm install and npm run dist, and add it to the repository. The build is split into two separate jobs with different permission scopes:

  • Build job (contents: read only): runs plugin-repo-cli build-from-source which clones the repo at the reviewed commit, runs npm install --ignore-scripts and npm run dist, validates the .jpl, and outputs it as a GitHub Actions artifact. Plugin code runs here but cannot write to the repository.

  • Commit job (contents: write): downloads the artifact and runs plugin-repo-cli commit-plugin which updates manifests.json, updates the README, and commits and pushes. This job never runs any plugin code.

This separation means a malicious webpack config executing during the build step has no write access to the repository. Additionally, @joplin/plugin-repo-cli is pinned to an exact version in the workflow file. Version updates are made deliberately via a reviewed PR, eliminating the risk of a compromised version being automatically installed.

4. Update flow: For updates, after automated scans pass, the AI model detects that the plugin already exists in manifests.json with a previously reviewed commit and generates a diff report, a plain English summary of exactly what changed since the last reviewed commit. For new plugins, the AI generates a full capability report instead. This way the reviewer always gets the right level of detail: focused diff for updates, full analysis for new submissions. Update reviews are significantly faster than first-time submissions because the reviewer only needs to verify what changed, not re-review the entire codebase.

3.1.1 Proof of Concept Validation

Before building the full system, I validated the core tools against real Joplin plugin code:

POC — Custom Semgrep Rules I created a custom Joplin-specific Semgrep ruleset and ran it against a test plugin containing intentionally dangerous code. Semgrep detected all 4 security issues automatically and marked them as blocking:

[POC — Custom Semgrep Rules File Link]

3.2 Design Decisions & Alternatives Considered:

When designing this pipeline, I spent a lot of time weighing security guarantees against developer experience. I didn't want to just add more scanning tools to the existing pipeline; I wanted to close the fundamental trust gaps without creating massive bottlenecks for maintainers. Here is a breakdown of why I made these specific architectural choices, and the alternatives I evaluated and ultimately rejected:

Architectural Decision Alternative Considered Why I Chose This Approach
Direct Git-to-CI Pipeline (Bypassing NPM entirely) Publishing plugin source code to NPM (via Trusted Publishing) to use NPM-centric scanning tools. A mentor suggested we could publish the original source to NPM to leverage tools like socket.dev. I researched this and realized it makes NPM an unnecessary middleman. The .jpl still has to be built on our trusted GitHub runner anyway. By integrating socket.dev directly into our GitHub Actions pipeline, we scan the cloned repo directly. This gives us all the security benefits while keeping GitHub as the single, clean source of truth and avoiding sync complexities.
Semgrep + socket.dev ESLint + npm audit I initially considered standard tools like ESLint and npm audit. However, ESLint is primarily a linter, whereas I wanted Semgrep for its deep, cross-file semantic security analysis (making it much harder to hide malicious logic). For dependencies, npm audit only catches known CVEs. I chose socket.dev because it proactively catches supply-chain attacks and malicious install scripts before we even build the plugin.
Separated CI Jobs (Read vs. Write) A single CI Build-and-Publish Workflow I structured the GitHub Actions workflow into two distinct jobs. Running npm install on untrusted third-party code is inherently dangerous. If a malicious webpack.config.js tries to steal the repository token, my separated build job (contents: read) physically cannot push changes to joplin/plugins. The commit job only picks up the safe, compiled artifact.
Immutable Commit Hash Target Git Release Tags I require authors to submit an exact commit hash rather than a release tag. I made this choice because tags can be moved or manipulated after the review is done. A commit hash is mathematically immutable, giving us absolute certainty that the exact code the reviewer approved is what gets compiled for users.
AI-Assisted Diff Summaries Purely Manual Code Reviews I knew that asking maintainers to manually read every line of code for updates across 277+ plugins would create a massive bottleneck. I added AI summaries to act as an accelerator, not an approver. By instantly summarizing changes (e.g., "this update added a network call to a new domain"), I can drastically reduce the cognitive load on human reviewers and keep update cycles fast.

3.3 Changes to the Joplin Codebase:

1. manifests.json

Two new fields will be added per plugin:

  • _reviewed with three possible values: "unreviewed" for existing 200+ plugins, "reviewed" for plugins that passed automated and human review, and "suspended" for previously approved plugins pulled due to a discovered security issue

  • _review_date for the date the plugin was last reviewed

All 277 existing plugins will be automatically migrated to "_reviewed": "unreviewed" via a migration script. They remain available in the plugin browser with a clear Unreviewed badge. Plugin authors can open a review request PR at any time to get their plugin upgraded to "reviewed". Plugins without a public repository remain "unreviewed" permanently.

POC — Migration Script

I ran the migration script against the real manifests.json from joplin/plugins containing 277 real plugins. All plugins were successfully migrated to "_reviewed": "unreviewed":

Screenshot 2026-03-19 213433

[POC — Migration Script File Link]

2. plugin-repo-cli

Currently extractPluginFilesFromPackage() runs npm install packageName and grabs the pre-built .jpl from node_modules/packageName/publish/, a binary built on the developer's machine with no verification.

The new pipeline replaces this with three focused commands:

scan: clones the plugin source at the submitted commit hash, runs Semgrep and socket.dev on the cloned source, runs AI analysis via GitHub Models, and outputs a structured report. Called when the author comments "Review Plugin" on their PR.

build-from-source: clones the plugin source at the exact reviewed commit hash, runs npm install --ignore-scripts and npm run dist, validates the .jpl, and outputs it as a GitHub Actions artifact. Works with any public git repository URL, GitHub, GitLab, Codeberg, or any other forge. Called when a reviewer merges the PR.

commit-plugin: downloads the artifact from the build job, updates manifests.json with _reviewed and _review_date, updates the README, and commits and pushes. Never runs any plugin code. Called in the commit job with contents: write only.

POC — buildPluginFromSource() Implementation

I implemented and tested the core buildPluginFromSource() function against a real Joplin plugin at a specific reviewed commit:

Screenshot 2026-03-19 213444

On top of that npm install revealed 71 vulnerabilities in this plugin's dependencies, demonstrating exactly why automated socket.dev scanning is needed in the review pipeline.

[POC — buildPluginFromSource() File Link]

3. searchPlugins.ts removed

Plugin discovery via npm is no longer needed. Plugins only enter the system through the review PR process. The 30 minute cron job is replaced entirely by the PR merge trigger.

4. update-repo.yml

The existing cron schedule is replaced with a single workflow file containing two separate trigger conditions:

  • Scan trigger fires when the plugin author comments "Review Plugin" on their PR. Runs Semgrep and socket.dev on the cloned source and posts a structured report as a PR comment. This job has contents: read only, no write access needed.

  • Build trigger fires when a reviewer merges the PR. Runs plugin-repo-cli build-from-source for the approved plugin only, split into two jobs. Build job with contents: read and commit job with contents: write. Plugin code only ever runs in the build job which has no write access to the repository.

Both triggers live in a single workflow file with if conditions controlling which jobs run for each event. This is simpler to maintain than two separate files and keeps the entire pipeline in one place.

POC — Proposed GitHub Actions Workflow

A complete working implementation of the proposed pipeline is available as a proof of concept. The workflow file demonstrates all core security improvements in a single file: the "Review Plugin" comment trigger with commenter authorization check, Semgrep and socket.dev scanning with automated PR report, job separation between build (contents: read) and commit (contents: write), pinned @joplin/plugin-repo-cli version, and failure notification on build errors.

[POC — GitHub Actions Workflow File Link]

5. validateUntrustedManifest.ts

Updated to handle the new _reviewed and _review_date fields and protect them from being overridden by plugin authors.

6. yo joplin generator

Since the new review system clones from a public git repository, plugins without one cannot enter the review pipeline. Making repository_url mandatory at creation time ensures all newly created plugins are review-ready from the start.

7. Plugin browser UI

The plugin browser is updated to display review status labels:

  • Reviewed plugin passed automated and human review, built from verified source

  • Unreviewed existing plugin, available but not yet reviewed

  • Suspended previously approved plugin pulled due to security issue, hidden from browser immediately

Plugins are suspended by a maintainer adding "_reviewed": "suspended" to manifests.json directly. Suspended plugins are hidden from the plugin browser immediately without requiring an app update. The plugin browser reads status from the repository on each sync.

3.4 Libraries or Technologies

Semgrep is the primary source code scanner, detecting dangerous patterns like eval(), dynamic require(), external network calls, and sensitive filesystem access. It is purpose-built for security scanning with deeper semantic analysis than ESLint, understanding code patterns across files and contexts making it significantly harder to evade.

socket.dev handles dependency scanning, detecting supply chain attacks, typosquatting, and known vulnerabilities directly on the cloned source without requiring an npm publish. This is significantly more powerful than npm audit which only checks against known CVEs.

GitHub Actions serves as the trusted build pipeline, running plugin-repo-cli build-from-source in an isolated environment with the build job and commit job separated by permission scope.

GitHub Models provides free AI-assisted code review for public repositories, generating structured reports as PR comments after automated scans pass.

3.5 Potential Challenges

1. Build environment consistency: Node.js version must be standardised across the trusted build pipeline. Different plugins may require different Node.js versions. This will be handled by pinning the Node.js version in the GitHub Actions workflow and documenting the supported version range for plugin authors.

2. Review bandwidth: 200+ existing plugins create a large initial queue. Semgrep and socket.dev automation significantly reduce load on human reviewers. Priority labels for security fixes and expedited review for recommended plugins further reduce bottlenecks. The goal is to keep review turnaround under 24 hours for flagged security updates.

3. Plugins without public repositories: Some plugins don't have a public git repository. These will remain unreviewed permanently unless the author provides one. Looking at the current manifests.json, this affects a very small minority of plugins.

4. Plugins with native dependencies: Some plugins may have native dependencies that require platform-specific build steps. This will be discussed with mentors during community bonding to determine the correct handling strategy.

3.6 Testing Strategy

Unit tests:

  • buildPluginFromSource() covers correct commit cloned, build succeeds, manifest fields stamped correctly, unapproved plugins refused

  • Migration script covers all 277 existing plugins correctly migrated to "_reviewed": "unreviewed"

  • validateUntrustedManifest.ts covers new fields handled correctly and protected from override

Integration tests:

  • End-to-end pipeline: PR opened, "Review Plugin" triggered, scans run, reviewer approves, .jpl built, added to repo

  • Forge-agnostic cloning: tested with GitHub, GitLab, and Codeberg URLs

  • Build job / commit job separation: confirm build job cannot write to repository

3.7 Documentation Plan

Developer guide covers how to submit a plugin for review under the new system, including the "Review Plugin" comment trigger and what to expect from automated scanning.

Reviewer guide covers how to review a plugin, approve a commit, handle updates, and use the automated scan report.

Security response guide covers how to handle a discovered vulnerability in an approved plugin, including the suspended status mechanism.

Updated README for joplin/plugins repo reflecting the new workflow.


4. Implementation Plan

Total duration: 350 hours / 12 weeks
Note on availability: I am available 8 hours per day, 6 days per week throughout the programme. My university exams fall in early June. Following mentor advice, I will take the second week of June (June 8-14) completely off to focus on exams without compromising either. I will compensate by using the extended timeline period (August 24 onwards) if needed. I am fully available in September and will continue contributing until the project is complete.

Community Bonding (May 1 - May 24)

  • Discuss technical design with mentors and finalize implementation approach.
  • Agree on Semgrep ruleset configuration, rules, severity levels, blocking findings.
  • Agree on socket.dev integration approach.
  • Identify edge cases, plugins without repository_url, native dependencies, Node.js version standardisation.
  • Review existing test coverage in plugin-repo-cli and plan additional tests.
  • Set up development environment.
  • Deliverable: Finalized technical design document approved by mentors, development environment ready.

Week 1-2 (May 25 - June 7): manifests.json + Migration

  • Add _reviewed and _review_date fields to manifests.json schema.
  • Write migration script to set all 277 existing plugins to "_reviewed": "unreviewed".
  • Update validateUntrustedManifest.ts to handle new fields.
  • Update yo joplin generator to make repository_url required.
  • Remove searchPlugins.ts.
  • Write unit tests for migration script.
  • Deliverable: Migration script tested and reviewed by mentors.

Week 3 (June 8 - June 14): EXAMS (1 week off)

  • No coding this week as advised by mentor.
  • Focusing entirely on university exams.

Week 4-5 (June 15 - June 28): buildPluginFromSource() + Trusted Build Pipeline

  • Implement buildPluginFromSource(), clones at reviewed commit, npm install --ignore-scripts, npm run dist, grabs .jpl, works with any public git URL.
  • Implement PR template in joplin/plugins guiding authors through repo URL and commit hash submission.
  • Replace cron schedule in update-repo.yml with two triggers: "Review Plugin" comment trigger and PR merge trigger.
  • Implement job separation: build job with contents: read, commit job with contents: write.
  • Implement plugin-repo-cli commands: build-from-source and commit-plugin.
  • Write unit tests for buildPluginFromSource().
  • Deliverable: Core build pipeline working with job separation.

Week 6-7 (June 29 - July 10): Semgrep + socket.dev Integration

  • Implement plugin-repo-cli scan command, clones source, runs Semgrep and socket.dev, outputs structured report.
  • Configure Semgrep rules: eval(), dynamic require(), external network calls, sensitive filesystem operations.
  • Configure socket.dev for supply chain attack detection.
  • Wire scan command into GitHub Actions scan job.
  • Test against real plugins, both clean and intentionally malicious.
  • Fine-tune rules to minimize false positives.
  • Deliverable: Semgrep and socket.dev scanning running on "Review Plugin" trigger.
  • Midterm Evaluation (July 6-10): PR-based submission, job-separated build pipeline, and automated scanning working end-to-end.

Week 8 (July 11 - July 18): AI Integration

  • Integrate AI-assisted review via GitHub Models into plugin-repo-cli scan command.
  • Full capability report for new plugins, diff report for updates.
  • Test AI report quality against real plugins.
  • Deliverable: Complete automated scanning pipeline with AI-assisted review.

Week 9-10 (July 19 - August 2): Plugin Browser UI Updates

  • Update plugin browser to display Reviewed, Unreviewed, and Suspended labels.
  • Implement suspended plugin mechanism, hidden from browser immediately on status change.
  • Write tests for UI changes.
  • End-to-end testing of complete pipeline.
  • Deliverable: Plugin browser showing review status for all plugins, full pipeline tested.

Week 11-12 (August 3 - August 17): Testing + Documentation + Buffer

  • Final end-to-end testing.
  • Write developer guide on how to submit a plugin for review.
  • Write reviewer guide on how to review a plugin and handle updates.
  • Write security response guide on how to handle discovered vulnerabilities.
  • Update README for joplin/plugins.
  • Fix remaining bugs and address any remaining feedback from mentors.
  • Final PR submitted and reviewed.
  • Deliverable: Complete, documented, tested system ready for merge.

Final Submission (August 17 - August 24)

  • Submit final work product.
  • Submit final mentor evaluation.

Extended Period (August 24 onwards, if needed)

  • Support plugin authors in migrating to the new review system and address any remaining feedback from mentors.
  • Note: I am fully available in September and will continue contributing until the project is complete.

5. Deliverables

By the end of GSoC 2026, the following will exist:

Code:

  • Migration script setting all 277 existing plugins to "_reviewed": "unreviewed"

  • _reviewed and _review_date fields added to manifests.json

  • searchPlugins.ts removed, npm discovery completely eliminated

  • buildPluginFromSource() replacing extractPluginFilesFromPackage(), builds from source at reviewed commit, works with any public git URL

  • Three new plugin-repo-cli commands: scan, build-from-source, and commit-plugin

  • update-repo.yml replaced with PR-based pipeline, "Review Plugin" comment trigger and PR merge trigger

  • Job separation: build job (contents: read) and commit job (contents: write), plugin code never runs with write access

  • @joplin/plugin-repo-cli pinned to exact version

  • PR template guiding plugin authors through submission

  • validateUntrustedManifest.ts updated for new fields

  • yo joplin generator updated, repository_url made required

  • Plugin browser UI showing Reviewed, Unreviewed, and Suspended status labels

Tests:

  • Unit tests for buildPluginFromSource(), migration script, validateUntrustedManifest.ts, and all three new plugin-repo-cli commands

  • Integration tests for end-to-end pipeline, forge-agnostic cloning, and job separation security guarantee

Documentation:

  • Developer guide for plugin submission

  • Reviewer guide for reviewing plugins and handling updates

  • Security response guide for handling discovered vulnerabilities

  • Updated README for joplin/plugins


6. Availability

Timezone: UTC+5 (Pakistan Standard Time). I am flexible with timing and can adjust my working hours to overlap with mentor timezones as needed.

Weekly availability: 8 hours per day, 6 days per week. GSoC will be my primary focus during this period with no other commitments. Following mentor advice, I will take the second week of June (June 8-14) completely off for university exams rather than trying to balance both. I will use the extended timeline period (August 24 onwards) to compensate if needed and am fully available in September.

Communication: I will post weekly updates on the Joplin forum and maintain a public log of progress on GitHub. I am comfortable working independently with remote mentors and will initiate weekly structured check-ins. I have worked in this style before on freelance projects with remote clients.

Other proposals: I am not applying to any other GSoC organisation. Joplin is my sole focus, the complexity of the codebase, the quality of the community, and the impact of this specific project make it the only place I want to spend my summer.

Contact:


AI Disclosure

AI was used to correct grammatical mistakes, improve clarity and wording. This is disclosed in accordance with Joplin's AI assistance policy.

Hello, to make it easier to review it and discuss it, please create it as described here:

1 Like

Hey Laurent, thanks for the heads up! I've updated the post with the full proposal inline. Would appreciate your feedback whenever you get a chance.

@CalebJohn @personalizedrefriger

1 Like

Thank you for the draft submission!

Notes:

  • This proposal suggests switching from NPM to GitHub for plugin publishing. Could it make sense to instead 1) stop publishing JPL files to NPM and 2) publish the original plugin source?
    • This would allow us to use existing tools that support NPM package scanning (e.g. https://socket.dev/) as a part of the review process.
    • Drawback: This complicates using GitHub as a source review tool. There may be differences between GitHub and NPM sources (though enforcing NPM Trusted Publishing might help).
  • Be sure to follow the AI policy's "Proposals" section:

    Proposals

    • You must not use AI to write your GSoC proposal.
    • It is acceptable to use AI tools to improve clarity, grammar, or wording.
    • Any such use must still be disclosed in an AI Assistance Disclosure section.
1 Like

Hey @personalizedrefriger, this was an interesting angle to look at this from! I went into depth on this approach and here is what I found.

I looked into the full npm + Trusted Publishing approach you mentioned. The main concern I found is that even with Trusted Publishing, npm becomes an unnecessary middleman, the .jpl still needs to be built somewhere, and that has to be Joplin's trusted GitHub Actions runner anyway. So the source would go GitHub → npm → back to GitHub to build, without adding any security at the build step.

The only real benefit of including npm in the flow would be running dependency checks through socket.dev. But after some research I found out that we can directly run socket.dev on our source/package.json by integrating it into our CI/CD pipeline, no npm publish needed at all.

So all the flow I proposed initially will remain the same, we just replace the npm audit step with a socket.dev scan. Keeping GitHub as the single source of truth and running socket.dev directly on the cloned source gives us the full benefit of socket.dev scanning but without the complexity of integrating npm and Trusted Publishing into the flow.

Here is the updated flowchart with npm audit replaced by socket.dev scan. Does this approach make sense to you? Looking forward to hearing your thoughts!

Thanks!

P.S. AI was used to improve clarity, wording, and fix grammatical mistakes. I will add this disclosure to the proposal.

Quick update: I've decided to withdraw my C2SI application and focus entirely on this Joplin proposal. I'd rather put all my energy into one strong submission than split my attention. Looking forward to continuing the discussion and refining the proposal further before the deadline!

1 Like

Thanks for the draft, I just have some high level comments and some more specific comments below

  • This is quite a low level proposal. I would like to see a section at the top providing a high level description of the proposed solution.
  • I would like to see some thoughts on unreviewed plugins/updates from 2 perspectives
    1. Security: Previously if a plugin pushed a security update it would be available to users within 30 minutes. Inserting a review process means that a security update can be delayed indefinitely, how will we deal with that?
    2. User Experience: Delayed updates and potentially slower publishing cycles are bad for users, how will this system ensure timely delivery of plugins/update.

Keep in mind, this project is not purely technical, if there are human reviewers we need to consider how to improve the process for them to keep the entire pipeline moving.

I think this section needs an update, there isn't currently a "30 minutes" attack window. It's actually the opposite, this proposal slows down security update increasing vulnerability.

personalizedrefriger had some good comments about pushing the source to npm, which are worth looking at. If your don't proposed that route, I would like to see other forges supported besides GitHub.

Plugins are not automatically distributed. They become available, but must still be downloaded/installed by the user.

In my experience it's not good to plan on 8 hours/day during this period, even by shifting hours before and after. Exams are stressful enough. I have seen students attempt this in the past, and usually both their exams and progress on the project suffer. I highly recommend adjusting your plan to account for taking some time off during and around exams.

1 Like

Hey, thanks for a detailed review Caleb!

I have addressed all of your comments below in detail:

Updated the Section 2(Project Summary) part with more details. I hope this addresses your concern!

These are some really important points, here are my thoughts! As we understand that Human Involvement is essential for safety, right? We can't fully automate trust. But we can make the human review process fast and efficient so the delay is minimized.

Thoughts on Security Update Problem:

This is actually a nuanced problem. We can think about it from two angles:

The old system's 30 minute security fix is actually a false advantage. Here's why:

  • Yes, a plugin author could push a security fix and it reaches users in 30 minutes
  • But that same 30 minute window is also how a malicious update reaches users in 30 minutes​:sweat_smile:
  • The old system can't distinguish between a genuine security fix and a malicious update so the speed advantage cuts both ways

A security fix takes longer but users can trust it's actually a fix and not an attack disguised as one.

Thoughts on UX(making review process fast):

1. Automated scanning:

  • Semgrep + socket.dev run automatically
  • If scans pass cleanly, reviewer only needs to check the diff
  • Most legitimate security fixes are gonna be small and obvious to review quickly

2. Priority labels for security updates

  • Plugin authors can label their PR as "security fix"
  • Reviewers prioritize these above regular submissions

3. Expedited review for recommended plugins

  • recommended plugins are from trusted, well-known developers, their updates get priority in the review queue

4. AI assisted review

  • After Semgrep and socket.dev pass, an AI model analyzes the source and posts a structured report as a PR comment. Summarizing what the plugin does, detected capabilities (network access, filesystem, eval usage), and for updates, a plain English diff summary
  • Reviewer verifies the report rather than reading raw code from scratch, significantly reducing cognitive load
  • AI assists, never replaces — final approval always requires a human
  • A discussion about this can be found here: [Vetting Process]

You're right! the framing was misleading and I will update the proposal. It's a genuine trade-off, we lose speed but gain verification. I've addressed how we plan to minimize the delay right above this.

I spent some time trying to fit npm into the workflow but every approach I tried just added complexity without meaningful security gains. The .jpl still needs to be built somewhere trusted, and that has to be GitHub Actions anyway. So the source would go GitHub → npm → back to GitHub to build, which doesn't add anything at the build step. The only real benefit of npm would be native socket.dev integration, but we can run socket.dev directly on the cloned source without publishing to npm at all.

The only genuine concern I have with dropping npm is plugins that don't have a public git repository. But looking at the current plugin list, these are a very small minority. Plugins without a public repo would simply remain unreviewed permanently, which seems like a reasonable trade-off.

That said, I want to make sure I'm not missing something important here. Is there a specific security or workflow benefit of npm that you feel my current approach doesn't cover? I'm open to reconsidering if there's a strong reason I haven't thought of!

Regarding other forges — great point, I missed that completely! I will update the proposal to make the system forge-agnostic from the start. The review system only requires a public git repository URL and a commit hash, not a GitHub-specific URL. Developers on GitLab, Codeberg, or any other forge submit the same way. The automated scanning runs on the cloned source regardless of where it's hosted. The only GitHub-specific part is the review PR itself, which lives in joplin/plugins — Joplin's existing infrastructure.

You're right, poor word choice on my part​:sweat_smile:. I will update "distributed" to "made available" throughout the proposal. The meaning was that any malicious plugin becomes available to all Joplin users within 30 minutes with no human review, not that it gets pushed automatically.

That's really good advice, thank you! I'll be honest, I was trying to show maximum commitment but you're right that it's not realistic. I will update the timeline after thoroughly reviewing my semester timetable.

Looking forward to your thoughts and happy to iterate further on this!

Might be better to have this process less public. In general security disclosures are kept as private as possible to ensure the update is distributed before it is publicized.

We should consider removal of this system once reviews are in place. But that doesn’t need to be discussed in your proposal.

Looking forward to seeing the updates! Also just a reminder to follow the AI policy linked above gsoc/ai_policy.md at master · joplin/gsoc · GitHub use of AI must be disclosed in the proposal.

1 Like

Hey Caleb, thanks for the feedback!

You're absolutely right. Making it public before the fix is distributed increases the risk window. One approach I found is using GitHub Private Security Advisories which keeps the vulnerability and fix completely private until it's ready to ship. It does add some steps to the review flow and our automated scanning wouldn't trigger automatically for private forks, but I couldn't find a cleaner workaround. Happy to discuss this further during community bonding if you have a better approach in mind!

Noted​:saluting_face:

I've updated the proposal with everything we discussed, added more technical details and POCs. @CalebJohn @personalizedrefriger please take a look when you get a chance, thanks!!

GSoC 2026 Proposal Draft – Idea 6: Strengthen the security of the plugin ecosystem
– Ehtesham-Zahid

Links

Joplin Contributions:

Chromium Contributions:


1. Introduction

My name is Ehtesham Zahid, a 4th semester Computer Science student at the University of Lahore, Pakistan. I have been working with TypeScript, React, Node.js, and the MERN stack for the past two years, building full-stack web applications both for clients and personal projects.

My open source experience includes:

  • Chromium (4 Merged PRs): Contributing to the Chromium project gave me hands-on experience navigating massive, complex codebases and adhering to strict, enterprise-level code review and security cultures.

  • Joplin PR #14689 (Merged): Fixed a race condition in handleAppFailure that caused a TypeError: Object has been destroyed during renderer crash recovery on Windows.

  • Joplin PR #14780 (Merged): Improved note list header icons by replacing inconsistent Unicode symbols with FontAwesome icons and adding accessibility labels for screen readers.

  • Joplin PR #14773 (Open): Implementing a global settings search feature for the desktop app (currently under review).

For this proposal specifically, I spent significant time reading through the plugin-repo-cli source code, the joplin/plugins workflow file, and RFC #9582 to understand the problem at a code level before proposing a solution. This included discovering how _publish_commit stores source commits in branch:hash format, identifying plugins with missing repository_url, and running POC implementations against real plugin data, including finding 71 vulnerabilities in a single plugin's dependencies. I have also been actively engaging with mentors on the forum to refine the approach.


2. Project Summary

What problem it solves:
Joplin's current plugin pipeline has no security review process. A developer writes a plugin, publishes it to npm, and it gets automatically fetched by plugin-repo-cli and made available to all users within 30 minutes, without any human involvement. The .jpl file is built on the developer's own machine with no guarantee it matches the source code, and some plugins don't even have a public repository, making independent verification impossible. A malicious plugin could silently access a user's notes, steal credentials, or send data to external servers, and nothing in the current pipeline would catch it.

Why it matters to users:
Joplin users trust the plugin ecosystem. Many plugins have access to sensitive data like notes, attachments, and sync credentials. A single malicious plugin reaching users could compromise that trust permanently. As the plugin ecosystem grows, the attack surface grows with it. Currently the only signal of trustworthiness is the "recommended" tag, which is limited to a small set of plugins from known developers. The vast majority of plugins, many of which are genuinely useful and widely used, have no verification signal at all. A structured review process changes this: it gives every plugin a clear, verifiable status that users can rely on, not just a small recommended subset.

What will be implemented:
At a high level, the proposed system replaces the fully automated npm-based pipeline with a review-gated workflow. Today, any developer can publish a plugin to npm and it is made available to all Joplin users within 30 minutes with zero human involvement. The system I propose changes this fundamentally. No plugin reaches users without explicit human approval and a verified source build.

The new workflow works as follows:

  1. Developer pushes source code to a public git repository and opens a review request PR in joplin/plugins with the repository URL and commit hash

  2. PR Author comments “Review Plugin“ under PR and automation scans starts. Semgrep scans the source code for dangerous patterns (eval(), dynamic require(), external network calls) and socket.dev scans dependencies for supply chain risks and known vulnerabilities. The PR is blocked until both scans pass

  3. After Both Scans, an AI model via GitHub Models will review the code and give code report in a PR comment to make the review process fast for reviewer

  4. A Joplin reviewer examines the AI report, source code and approves the specific commit hash. If changes are requested, the developer pushes fixes and again runs the automated scans

  5. Only after human approval does Joplin's trusted GitHub Actions runner clone the source at the exact reviewed commit, build the .jpl from scratch, and add it to the repository

  6. Plugins are clearly labelled as reviewed, unreviewed, or suspended in both the repository and the plugin browser, giving users a clear trust signal for every plugin they install

Expected outcome:
Every new plugin reaching users will have been built from reviewed source code on a trusted server. Existing plugins remain available but clearly labelled as unreviewed, and authors can request a review at any time to upgrade their plugin's status. The result is a plugin ecosystem that stays open and developer-friendly while giving users meaningful, verifiable trust signals for every plugin they install.


3. Technical Approach

3.1 Architecture:

Current Architecture:

Problems with the current approach:

1. Unverifiable binary: While researching how plugins are delivered, I noticed we’re essentially taking the developer's word for it. They show us clean code in their public repository, but the actual .jpl file users download is built on the developer's own machine and uploaded to npm. I have no way to verify that the original source code matches the binary on npm, which creates a significant trust gap.

2. Fully automated, zero human involvement: Looking at searchPlugins.ts, I saw that it automatically pulls in any package tagged joplin-plugin every half hour. I feel this is a major risk because a malicious plugin could be live for all Joplin users in minutes without a single human ever seeing the code or even knowing the plugin was submitted.

3. No review of updates: I also realized that once a plugin is in the system, updates just flow through automatically. There’s no gatekeeper to check what changed between version 1.0 and 1.1. I’m concerned that a plugin could start out clean and then introduce malicious behavior later through an unreviewed update.

4. Write access during plugin code execution: One of the bigger security holes I found is that the entire workflow runs with contents: write permissions. Since we run npm install on third-party code during the build, I realized that a malicious script hidden in a plugin could potentially steal our GitHub token and push unauthorized changes directly to the Joplin repository.

5. Unpinned CLI dependency: Finally, I noticed that we install @joplin/plugin-repo-cli from npm without pinning it to a specific version. I think this is a supply chain risk; if that CLI tool’s npm account were ever compromised, our own pipeline would automatically start running the compromised version with full write access to our repo.

My Proposed Architecture:

To solve the trust and security gaps I identified, I am proposing a system that replaces the fully automated npm-based pipeline with a review-gated workflow. My plan is built around the following core components:

1. PR-based submission: Instead of Joplin’s bot searching npm, I want authors to submit for review by opening a PR in joplin/plugins. I’m designing this to require just two things: the public repository URL and the exact commit hash. I chose this approach specifically because it’s "forge-agnostic"; it doesn't matter if a developer uses GitHub, GitLab, or Codeberg. As long as the repository is public, my system can clone and verify the source. I’ll also provide a PR template to make this as easy as possible for first-time contributors.

2. Automated scanning pipeline: I noticed that running security scans on every single "Work in Progress" commit would be a waste of resources, so I’m implementing a "Review Plugin" comment trigger. When an author is ready, they just comment on the PR, and the automation kicks in. To prevent the DoS spam I mentioned earlier, I’ve included an authorization check so the workflow only triggers for the author or a maintainer. Once started, I’ll have the system run two specific scanners:

  • Semgrep: To find dangerous patterns like eval() or unauthorized network calls.

  • Socket.dev: To catch the supply chain risks and typosquatting I'm concerned about in dependencies.

After these pass, I’m using GitHub Models to generate an AI report. I want this to summarize what the plugin actually does so the human reviewer doesn't have to start from scratch.

3. Trusted build pipeline: To fix the "Write Access" hole I discovered, I’m splitting the build process into two separate jobs with different permission scopes:

  • The Build Job (contents: read only): This is where the untrusted plugin code is actually compiled. Even if a malicious script tries to steal the token, I’ve ensured it only has "Read" access, so it can't push changes to our repo.

  • The Commit Job (contents: write): This job only starts after the build is finished. It takes the safe, compiled artifact and adds it to the repository. Since this job never executes the plugin's code, the "Write" permission is finally safe to use. I’m also pinning @joplin/plugin-repo-cli to a specific version to eliminate that unpinned dependency risk I found.

4. Update flow: I realized that if we have 277+ plugins, maintainers will be overwhelmed if they have to re-review every line of code for every small update. I want to solve the "Maintainer Fatigue" problem by making the review process as frictionless as possible.

To do this, I am implementing a "One-Click Review Link" generator. My script will automatically construct a comparison URL by grabbing the last reviewed hash from manifests.json and the new hash from the PR. I’m designing this to be forge-agnostic. The utility will detect if the repository is on GitHub, GitLab, or a Gitea-based forge like Codeberg and generate the correct URL structure (handling the /-/ difference in GitLab, for example).

When a maintainer opens a PR for an update, they will see a direct link (e.g., .../compare/old_hash...new_hash) along with an AI-generated "Plain English Diff" that summarizes the changes. This means instead of hunting through external repositories, a reviewer can click one link, verify the specific changes in seconds, and move on. I believe this is the only way to keep the security gate sustainable as the ecosystem grows.

3.1.1 Proof of Concept Validation

Before building the full system, I wanted to ensure these tools could actually catch Joplin-specific risks:

POC — Custom Semgrep Rules I created a custom Semgrep ruleset and ran it against a test plugin I loaded with intentionally dangerous code (like hidden eval() calls and unauthorized network requests). I was pleased to see that Semgrep detected all 4 security issues automatically and marked them as blocking. This gave me confidence that the automation will catch the "obvious" red flags before a human even has to look at the code.

[POC — Custom Semgrep Rules File Link]

3.1.2 System Threat Model

I’ve engineered this pipeline to specifically target the most common supply-chain vectors. A security architecture is only as strong as the attacks it prevents, so I focused on these four risks:

1. The "Bait-and-Switch" (The NPM Middleman)

  • The Threat: Joplin’s current pipeline pulls .jpl files from npm without source verification. I noticed this creates a major blind spot: a developer can host clean code in a public repo but publish a malicious binary to npm. Without a verified link between the two, Joplin has no way to detect the discrepancy.

  • My Mitigation: I researched a mentor's suggestion of using NPM Trusted Publishing to secure this link, but I realized that still leaves npm as an unnecessary middleman. It would require routing code from a repository → npm → back to our GitHub runner just to build the binary. I am eliminating npm as a source for the build process. By building the .jpl directly from the verified git commit on our own trusted runner, I am eliminating the possibility of a "Bait-and-Switch." The code a reviewer sees is exactly the code that gets compiled for the user, making the source repository the single, verifiable source of truth.

2. The Malicious Update

  • The Threat: A developer could publish a clean v1.0.0 to gain user trust, then slip credential-stealing code into v1.1.0 six months later.

  • My Mitigation: Because my pipeline requires human approval for every update (which I’ve accelerated using AI diff summaries and one-click compare links), I effectively eliminate the automated publishing window that allows malicious updates to slip through unnoticed.

3. CI Environment Poisoning

  • The Threat: Running npm install on untrusted code is dangerous. A malicious post-install script or webpack config could execute during the build to steal the GITHUB_TOKEN and push unauthorized changes directly to the Joplin repository.

  • My Mitigation: I’ve enforced strict CI job separation. My build step runs in a completely isolated job with contents: read permissions only. This makes it physically impossible for untrusted code to "write" back to the repository, even if it manages to execute during the build.

4. Lockfile Poisoning & Typosquatting

  • The Threat: An attacker could manipulate the package-lock.json file to redirect a dependency download to a malicious server. Standard tools like npm audit often miss this because they only look at package names, not registry URLs.

  • My Mitigation: Instead of deleting the lockfile (which breaks deterministic builds), I chose to integrate socket.dev. It natively scans lockfiles for these exact unexpected registry URLs and supply chain attacks right on the cloned source before the build even begins.

3.2 Design Decisions & Alternatives Considered:

Architectural Decision Alternative Considered Why I Chose This Approach
Direct Git-to-CI Pipeline (Bypassing NPM) Publishing source code to NPM via Trusted Publishing. A mentor suggested using NPM Trusted Publishing, but I realized it makes npm an unnecessary middleman. By integrating socket.dev directly into our CI to scan the cloned repo, we get the same security benefits without the sync complexities. This keeps the source repository as the single source of truth for all forges (GitHub, GitLab, Codeberg).
Semgrep + socket.dev ESLint + npm audit I initially considered standard tools like ESLint, but it's primarily a linter. I chose Semgrep for its deep, cross-file semantic analysis. For dependencies, npm audit only catches known CVEs; I chose socket.dev because it proactively catches supply-chain attacks and malicious install scripts before the build even begins.
Separated CI Jobs (Read vs. Write) A single CI Build-and-Publish Workflow Running npm install on untrusted code is dangerous. If a malicious script tries to steal the repository token, my separated Build Job (contents: read) physically cannot push changes. The Commit Job only picks up the safe, compiled artifact, ensuring the "Master Key" is never exposed to untrusted code.
Immutable Commit Hash Target Git Release Tags I’m requiring authors to submit an exact commit hash rather than a release tag. I made this choice because tags are mutable, they can be moved or manipulated after a review is finished. A commit hash is mathematically immutable, giving us absolute certainty that we are building the exact code that was approved.
AI-Assisted Reviews & One-Click Links Purely Manual Code Reviews I knew that reviewing updates for 277+ plugins manually would create a massive bottleneck. I added AI summaries and automated 'Compare' links to act as an accelerator. By instantly showing exactly what changed since the last reviewed commit, I can drastically reduce the cognitive load on maintainers and keep update cycles fast.

3.3 Changes to the Joplin Codebase:

To implement this architecture, I have identified the specific components of the Joplin codebase that need to be modified or removed. My research into the existing scripts and my subsequent POC testing guided these decisions:

1. Modifications to manifests.json
I’m adding two new fields to each plugin entry to track security status: _reviewed (can be unreviewed, reviewed, or suspended) and _review_date. This ensures the Joplin app can distinguish between legacy plugins and those that have passed my new security gate.

POC VERIFICATION: Manifest Migration
Status: Successfully tested against the live joplin/plugins repository.

Result: All 277 existing plugins were migrated to unreviewed status without breaking the manifest structure, ensuring a smooth transition to the new system.

Screenshot 2026-03-19 213433

[POC — Migration Script File Link]

2. Deep Refactoring of plugin-repo-cli
I am replacing the current "binary-only" extraction logic with three focused, security-hardened commands. I’m moving away from trusting npm-built files and instead building directly from the verified source:

  • scan: Clones the source at the submitted hash and runs my Semgrep and socket.dev suite.

  • build-from-source: Creates a clean, isolated environment to compile the .jpl using npm install --ignore-scripts. This prevents malicious post-install scripts from executing during the build.

  • commit-plugin: A "write-only" command that updates the repo once the build is verified. It is strictly isolated so it never touches untrusted third-party code.

POC VERIFICATION: Source-to-Binary Build
Status: Tested against a real Joplin plugin at a specific reviewed commit.

Key Finding: During the test build, I discovered 71 vulnerabilities in the plugin’s dependencies. This confirmed that automated scanning is not just an "extra feature," but a mandatory requirement for user safety.

Screenshot 2026-03-19 213444

[POC — buildPluginFromSource() File Link]

3. Removing searchPlugins.ts
I’ve decided to remove the npm-discovery script entirely. Since plugins will now only enter the system through a reviewed PR process, we no longer need a cron job to "hunt" for packages. This effectively closes the "30-minute automated window" where malicious code could slip in unnoticed.

4. Redesigning update-repo.yml (The Security Orchestrator)
I’m rewriting the main workflow to enforce the job separation (contents: read vs contents: write) described in my threat model. This is the heart of the Security-First design:

  • Authorization Shield: I'm implementing a check that ensures the "Review Plugin" trigger only executes if the commenter is the PR author or a maintainer, preventing CI abuse.
  • Physical Isolation: The Build job runs with "Read-Only" permissions. Even if a plugin has a malicious webpack.config.js, it cannot touch the repository. The Commit job only starts after the build is safe.

POC VERIFICATION: Hardened Workflow Implementation
Status: Complete working implementation available.

Features: Demonstrates the comment-trigger logic, automated PR reports, job isolation, and pinned @joplin/plugin-repo-cli versions to prevent supply-chain attacks on our own tools.

[POC — GitHub Actions Workflow File Link]

5. Updates to validateUntrustedManifest.ts
I’ll update this utility to handle the new fields and, more importantly, protect them. I’m specifically designing this to ensure that plugin authors cannot "self-approve" their submissions by manually editing the _reviewed field in their own PRs.

6. Changes to the yo joplin Generator
To ensure all future plugins are "Review-Ready," I’m making the repository_url field mandatory during plugin creation. This sets the standard for the new workflow from the very first line of code a developer writes.

7. Enhancing the Plugin Browser UI
I want to make the security status transparent. I am proposing three clear labels in the Joplin Desktop app: Reviewed, Unreviewed, and Suspended. While the labels are Desktop-only for now, the Suspended status will ensure the plugin is hidden from the browser for all users on all platforms.

  • Instant Safety: If a maintainer marks a plugin as suspended in the repo, it will be immediately hidden from the global plugin browser for all users. We no longer have to wait for a full app update to protect users from a discovered threat.

3.4 Libraries or Technologies

To build a hardened and verifiable review system, I’ve selected a suite of tools that prioritize deep analysis and environmental isolation over basic linting:

  • Semgrep: My primary source code scanner. I chose it over ESLint because of its semantic analysis capabilities. It allows me to write custom rules that understand the intent of the code (like detecting hidden eval calls or unauthorized fs access) which is significantly harder to bypass than simple pattern matching.

  • Socket.dev: This handles all dependency and lockfile scanning. Unlike npm audit, which only reports known CVEs, socket.dev proactively catches supply chain attacks, typosquatting, and registry redirects before they can compromise our build environment.

  • GitHub Actions: The "Orchestrator." I am using it not just for automation, but as a trusted execution environment. Its native support for granular, permission-scoped CI (the contents: read/write split) is the technical foundation of my entire pipeline.

  • GitHub Models (AI): I’m integrating GitHub’s AI infrastructure to provide human-readable review summaries. It will analyze the automated scan results and the "Forge-Agnostic" diffs to generate a structured report for the maintainer, drastically reducing the cognitive load of a manual review.

  • TypeScript / Node.js: The core environment for all my custom scripts within plugin-repo-cli. I’ll be using these to build the "Migration Script," the "Link Generator," and the core build-from-source logic.

3.5 Potential Challenges

I don't expect this migration to be perfectly linear. Dealing with 277+ diverse plugins means running into "edge cases" that a clean local POC can't always predict. Here is how I’m planning to handle the friction points:

1. Build environment consistency: Different plugins may target different Node.js versions, and just pinning a single version in the workflow doesn't fully solve this. My plan is to implement logic to parse the engines.node field in package.json to select the runner's Node version dynamically.

2. The "Human Bottleneck" (Maintainer Fatigue): This is the biggest risk to the project's success. If the new review process is too slow, authors will get frustrated and the ecosystem will stall. I’m addressing this by making the automation do 90% of the heavy lifting. My goal is to turn a 30-minute deep-dive audit into a 5-minute "sanity check."

3. Handling "Repo-less" and Legacy Plugins: During my triage of the 277 existing plugins, I found a small minority that simply don't have a repository_url. My migration script marks them as unreviewed permanently. I’ve designed the system so that the "path to safety" is clear: if an author wants that Reviewed badge, they must provide a public repository.

4. Plugins with native dependencies: This is the "messiest" technical part. Not every plugin uses the same Node.js version, and some use native C++ dependencies that require specific build tools. My plan is to use the Community Bonding period to audit these specific plugins with the mentors. I’d rather build a rock-solid pipeline for the 95% of standard plugins first, then work with the team to decide if we need a "multi-platform build matrix" for the outliers.

3.6 Testing Strategy

Since this pipeline handles untrusted code, my testing strategy is split between verifying the core logic and trying to "exploit" the security boundaries I’ve set up.

1. Unit Testing: The Building Blocks
I’ll use Vitest (consistent with the Joplin codebase) to ensure my core utilities are bulletproof:

  • buildPluginFromSource(): I’ll verify that it rejects any attempt to build from a branch name instead of a specific commit hash. I’ll also test that it correctly stamps the _reviewed and _review_date fields only when the build is 100% successful.

  • The Migration Script: I’ve already run this against a copy of the 277-plugin manifest. My test suite will ensure that zero plugins are missed and that no existing fields (like description or version) are accidentally corrupted during the shift.

  • validateUntrustedManifest.ts: This is a critical test. I will write "Negative Tests" where a fake plugin author tries to manually set their own status to reviewed. My code must detect and strip these unauthorized fields immediately.

2. Integration Testing: The End-to-End Pipeline
I’ll set up a staging repository to simulate the entire PR lifecycle:

  • The Full Loop: I’ll open a PR, trigger the "Review Plugin" comment, and verify that the automated report (Semgrep + Socket.dev + AI) appears as a comment.

  • Forge-Agnostic Check: I’ll run test submissions using repositories hosted on GitHub, GitLab, and Codeberg to ensure my "Link Generator" and "Cloning Utility" handle the different URL structures perfectly.

3.7 Documentation & Onboarding Plan

A new security pipeline is only useful if developers and maintainers know how to interact with it. I will provide four specific sets of documentation:

1. Contributor Documentation (Plugin Authors)

This guide explains the new PR-based submission process so authors can get their plugins reviewed quickly. It will cover:

  • How to format the submission PR using the new template.

  • How to trigger scans using the Review Plugin comment.

  • Interpreting Results: A guide on reading Semgrep and Socket.dev reports so authors can fix vulnerabilities before requesting a human review.

2. Maintainer Workflow (Reviewer Guide)

This is a technical reference for the Joplin core team to help them manage the 277+ plugin backlog and new submissions. It covers:

  • Using the AI-generated diff summaries to focus on high-risk code changes.

  • Using the "One-Click Compare" links for GitHub, GitLab, and Codeberg.

  • The specific criteria for moving a plugin from unreviewed to reviewed.

3. Vulnerability Response Protocol

A clear set of instructions for handling security flaws discovered after a plugin has been approved:

  • The "Kill Switch": Steps to manually set a plugin to suspended in the manifest for immediate removal from the browser.

  • The Recovery Path: The process for an author to submit a fix and re-verify the plugin through the pipeline.

4. Repository Infrastructure (README & Templates)

I will update the joplin/plugins README to reflect the new workflow and implement GitHub PR Templates. These templates will force-prompt authors for the repository_url and commit_hash at the moment of creation, ensuring the automation has the data it needs to start.

3.8 Security Vulnerability Response Plan

Moving to a gated review process means I have to ensure security patches don't get stuck in a standard queue. I’ve designed the following workflow to handle critical issues without the overhead of a standard feature PR.

1. Instant De-listing via "Suspended" Status

If a live plugin is found to have a critical exploit or malicious code, the priority is to remove it from the store immediately.

  • My implementation: I’ve included the suspended status in the _reviewed field of the manifest.

  • The process: A maintainer can update this field in the repository in seconds. Since the Joplin app reads the manifest on every sync, the plugin is hidden from the browser for all users immediately. This removes the threat while the developer works on a fix.

2. Coordinated Disclosure for Critical Exploits

For severe vulnerabilities, a public PR is risky because it effectively discloses the exploit before a patch is live.

  • The challenge: Managing a "private" review process (via email or private repos) adds significant logistical overhead and extra steps for both the author and the maintainer.

  • My plan: I recognize that this is a sensitive area that needs to align with Joplin’s existing security culture. I intend to use the Community Bonding period to discuss the exact protocol with mentors. Whether we use private GitHub Security Advisories or a dedicated security contact, I want to ensure the final workflow is as frictionless as possible for the team.

3. Priority Handling for Routine Security Updates

Not every security update is a zero-day; often it’s just a dependency bump to patch a known CVE.

  • The shortcut: I’m adding a "Security Fix" label to the PR template. I will configure the workflow to prioritize these PRs at the top of the review list.

  • The speed: Using the AI-generated diff reports I’m building, a maintainer can verify a simple version bump almost instantly. This ensures routine security maintenance is shipped in hours rather than days.

3.9 Migration & Developer Experience (DX) Strategy

Transitioning 277+ existing plugins is the biggest hurdle. My core philosophy here is that security should not be a blocker. I’ve designed the migration so that the _reviewed: "unreviewed" status is purely informational; it won't hide or break a single plugin.

1. The Baseline Migration

My first action will be running the migration script I’ve already prototyped. It stamps all 277 existing plugins with _reviewed: "unreviewed" and _review_date: null.

  • Impact: Nothing breaks. Every plugin stays available in the browser, just now with an "Unreviewed" badge.

2. Automated Triage and Classification

After auditing the current manifests.json, I realized that not all plugins are in the same state. I’ve categorized them into four cases that my script will automatically identify:

  • Case 1 (Ready to Go): Plugins with both a repository_url and a _publish_commit. I can pull these into the review pipeline immediately with zero author effort.

  • Case 2 (Need Author Input): Plugins with a repo URL but no commit hash. I’ll open the PR, but I’ll need the author to confirm which commit they want to be "The Source of Truth."

  • Case 3 (No Repository): Plugins with a commit but no repo link. These stay unreviewed permanently unless the author provides a public URL.

  • Case 4 (Fully Blocked): Neither field exists. These are legacy cases that require manual outreach.

3. An Impact-First Priority Queue

I don’t want to waste maintainer time on abandoned plugins with 5 users. I’m taking a data-driven approach by querying the npm downloads API: https://api.npmjs.org/downloads/point/last-month/``<_npm_package_name>

By sorting the 277 plugins by monthly downloads, I can identify the Top 20–30 plugins that have the most real-world exposure.

  • My GSoC Goal: I will personally open review PRs for the Top 20–30 plugins that fall into Case 1. I’ll trigger the scans, verify the results, and work with the maintainers to get the most popular plugins genuinely "Reviewed" by the end of the summer.

  • For Case 2 Top Plugins: I’ll open the PR and tag the author, asking for the commit hash. Once they reply, I’ll handle the rest of the verification.

4. Protecting Maintainer Bandwidth

I am being realistic about the fact that maintainers are busy. My automation handles the heavy lifting so that the human review is just a final "sanity check."

  • If Semgrep and Socket.dev pass and the AI report is positive, a maintainer should be able to approve a plugin in minutes.

  • For the hundreds of other plugins, I am leaving the door open for an "organic" migration. Authors can opt-in whenever they want. I’m not forcing a massive manual audit on the core team; I'm building the engine that makes the audit easy.

3.10 Project Out of Scope

To keep the focus on the core security architecture and ensure the pipeline is production-ready within 12 weeks, I am defining the following as out of scope. These represent significant technical hurdles that would detract from the primary goal of establishing a verified build process.

1. Multi-Platform Build Matrix for Native Modules

Some plugins use native C++ components (via node-gyp). Automating these across Windows, macOS, and Linux runners is a massive rabbit hole of environment-specific debugging. I am focusing on the 95% of plugins that are pure JS/TS. Native modules will remain a manual review process for the initial rollout.

2. Security UI Implementation for Joplin Mobile

I am limiting the UI labels (Reviewed/Unreviewed/Suspended) to the Joplin Desktop App. Adding these to the React Native mobile codebase would require a separate cycle of cross-platform testing and UI work that would distract from the core security-first infrastructure.

3. Manual Audit of the Full Plugin Backlog

While this project establishes the infrastructure for a secure ecosystem, completing a full manual audit of the entire 277+ plugin backlog is outside the 12-week scope. I will, however, kickstart the process by personally guiding the top 20–30 most downloaded plugins through the new pipeline. The rest of the backlog will be reviewed organically as authors opt-in.


4. Implementation Plan

Project Duration: 350 hours (Large Project)
Estimated Commitment: 35–40 hours per week

Availability Note: I am fully committed to this project for the entire 12-week period. As suggested by mentor, I have scheduled a one-week buffer in early June to account for my university exams. I will compensate for this by extending my contributions into late August and September to ensure the project is fully polished.

Community Bonding (May 1 – May 24)

I will use this period to talk with mentors to agree on the final technical details so there are no surprises once coding starts.

  • Finalize the Semgrep ruleset and severity levels with mentors.
  • I’ll set up a private "practice" repository. This lets me test the automated triggers and the new security permissions safely without touching the real Joplin files.
  • Run a final triage of the 277 plugins to ensure the "Case 1–4" list is current.
  • Deliverable: A final project blueprint and a working practice repository for safe testing.

Week 1–2 (May 25 – June 7)

I will start by updating the core data structures.

  • Add _reviewed and _review_date fields to the manifests.json schema.
  • Execute the Baseline Migration (marking all 277 plugins as unreviewed).
  • Refactor validateUntrustedManifest.ts to protect these fields from unauthorized PR edits.
  • Update the yo joplin generator to make repository_url a mandatory field.
  • Deliverable: Successful schema migration and a baseline-secured manifest.

Week 3 (June 8 – June 14)

  • UNIVERSITY EXAMS: I will be taking this week off to focus on my exams. I have front-loaded the foundation work in Weeks 1–2 to ensure no momentum is lost.

Week 4–5 (June 15 – June 28)

I’ll focus on the "Source-to-Binary" engine. This is where I solve the trust gap between GitHub and npm.

  • Implement buildPluginFromSource() to clone at a specific hash and compile in an isolated environment (npm install --ignore-scripts).
  • Overhaul update-repo.yml to enforce job separation: a Read-Only build job and a Write-Access commit job.
  • Create the PR template for joplin/plugins that requires the repository URL and commit hash.
  • Deliverable: A hardened build pipeline with physical permission separation.

Week 6–7 (June 29 – July 12)

I will integrate the security scanners and the authorization logic.

  • Implement the scan command (Semgrep + Socket.dev) and wire it into the GitHub Actions "Scan Job."
  • Build the authenticated comment-trigger logic (restricting triggers to authors/maintainers).
  • Midterm Evaluation (July 6-10): I will demonstrate the full end-to-end flow from PR submission to automated security report.
  • Deliverable: Fully automated, authenticated security scanning on PRs.

Week 8–9 (July 13 – July 26)

I will focus on the "Human-in-the-Loop" accelerators and the Desktop UI labels.

  • Integrate GitHub Models to generate "Plain English" diff summaries and capability reports.
  • Implement the Reviewed, Unreviewed, and Suspended labels in the Joplin Desktop plugin browser.
  • Build the logic that hides suspended plugins from the UI immediately upon manifest sync.
  • Deliverable: AI-assisted review reports and a transparent security UI in the Desktop app.

Week 10–11 (July 27 – August 9)

I’ll put the infrastructure to work by securing the most popular plugins in the ecosystem.

  • I will manually guide the Top 20–30 plugins through the review pipeline.
  • I’ll contact the authors of popular "Case 2" plugins to confirm their commit hashes so I can pull them into the "Reviewed" state.
  • I will perform final end-to-end testing of the entire workflow, from PR submission and automated scanning to the final manifest update, using a staging repository.
  • Deliverable: Verified "Reviewed" status for the top 30 plugins and a production-ready, integrated pipeline.

Week 12 (August 10 – August 24)

FINAL SUBMISSION WEEK: I’ll focus on documentation and final handover.

  • Write the Author, Reviewer, and Security Response guides.
  • Final bug fixes and README updates for the joplin/plugins repository.
  • Final Work Product Submission (August 17-24): Submit code and the final project report.
  • Deliverable: Completed, documented system and final project report.

Extended Period (August 24 – September)

I have specifically opted into the extended timeline to compensate for my university exam week in June, ensuring that the project receives my full 350-hour commitment. Beyond this formal compensation period, I am completely free and available throughout September should the project require further polish, bug fixes, or extended support.

  • Directly assisting plugin authors with repository-linking and troubleshooting build-from-source errors.
  • Identifying and fixing technical edge cases as the volume of real-world submissions increases.
  • Ensuring the Joplin core team has a seamless, well-documented system for long-term maintenance.

5. Deliverables

By the end of GSoC 2026, I will have delivered the following production-ready components:

Code & Infrastructure

  • A Migration script to stamp all 277+ existing plugins as unreviewed and a classification tool to prioritize the "Top 20–30" highest-impact plugins

  • Hardened plugin-repo-cli with three new commands (scan, build-from-source, commit-plugin) and a Forge-agnostic link generator for one-click code comparisons (GitHub/GitLab/Codeberg).

  • Overhaul of update-repo.yml with physical job separation (contents: read for builds vs contents: write for commits) and authenticated comment triggers to prevent CI abuse.

  • Integration of GitHub Models (AI) to post automated "Plain English" diff summaries and capability reports directly on PRs

  • manifests.json schema update and UI status labels implemented for the Joplin Desktop plugin browser

  • Updated yo joplin generator with a mandatory repository_url field

Testing Suite

  • Unit Tests (Vitest) for the migration script, buildPluginFromSource(), and manifest validation logic

  • Integration Tests for end-to-end pipeline, forge-agnostic cloning, and job separation security guarantee

Documentation

  • Clear, step-by-step guides for plugin authors and the Joplin core team.

  • A technical incident response protocol for handling post-release vulnerabilities.

  • A fully updated README for the joplin/plugins repository.


6. Availability & Communication

I am based in the PKT timezone (UTC+5) and will shift my working hours to ensure a 3–4 hour daily overlap with my mentors for meetings and real-time collaboration. GSoC will be my primary focus this summer, and I am committing 35–40 hours per week to the project.

As noted in my timeline, I will be taking a break from June 8–14 to focus on my university exams. I have front-loaded the foundation work in the weeks prior to ensure no momentum is lost. I will explicitly compensate for this exam week by working through the Extended Timeline period starting August 24. Since I am fully free throughout September, I will remain available to handle any final polish, support plugin authors during the migration, and ensure a perfect handover to the Joplin team.

I believe proactive communication is the foundation of a successful remote project. I will provide:

  • Weekly structured progress reports on the Joplin forum and a detailed "Dev Log" in a public GitHub repository.

  • Consistent check-ins with my mentors to demonstrate new features and discuss any technical blockers.

  • Constant availability via Discord, GitHub, and Email.

Having worked on freelance projects with remote clients, I am highly comfortable managing my own schedule and hitting deadlines independently. Finally, I want to emphasize that I am not applying to any other GSoC organizations. Joplin is my sole focus for 2026 because I am fully invested in solving this specific security challenge for the plugin ecosystem.


AI Disclosure

AI was used to correct grammatical mistakes, improve clarity and wording. This is disclosed in accordance with Joplin's AI assistance policy.

Hi @CalebJohn and @personalizedrefriger, since the original post is locked, I’ve shared a major update to my proposal in the comment above.

Key changes in this version:

  • Physical read/write job separation to safely isolate untrusted code during builds.

  • A data-driven migration strategy to triage the 277+ existing plugins.

  • AI-generated diffs and forge-agnostic 'One-Click' comparison links to speed up audits.

  • A dedicated protocol for emergency de-listing and prioritized security patching.

I’d love to hear your thoughts on this more detailed approach!

Hi @CalebJohn @personalizedrefriger , just a quick update to let you know I’ve officially submitted my proposal.

I know it’s been a busy season with a high volume of proposals, and while I understand you couldn't reply to every individual update, your feedback on other Idea #6 proposals was incredibly helpful for refining my final approach, so thank you for that!!

I’m looking forward to staying active in the community and continuing to contribute while the proposals are under review. Cheers!