Force push incident on Plone Collective

Last Thursday, May 7th 2026, we discovered an attempt to add malicious code in packages in the Plone collective. This is similar to an earlier attack in January.

Someone who had write access to the collective got hacked, and his account was used for an attack. We have removed this user from the Collective.

This time about 166 packages were affected. In most cases extra code was added in directories .vscode and public. We don't know exactly what the code does. But the goal seems to be to steal crypto wallets when you open such a package in the Visual Studio Code editor.
End users would not be affected, only developers working on these repositories.

It seems to be the "PolinRider" attack, or a very similar one, described here:

An example is this commit to zestreleaser.towncrier. I made the original commit myself in December, but this week someone changed the commit, and added the bad files.
We have gotten rid of the bad code in all affected repositories. In a few cases we did our own force push to restore the original commit. In most cases we simply removed the .vscode and public directories and did a normal push. See for example this commit.

Two things may have gone wrong:

  1. Maybe the .vscode and public directories already existed and contained valid code. In that case, individual package maintainers can bring the good code back themselves from a previous commit.
  2. The force pushes also added two lines to .gitignore: temp_auto_push.bat and temp_interactive_push.bat. That part is not dangerous, but you can still remove them if you are a maintainer. It seemed less important to try to clean that up as well, than it was to remove the actually bad code.

In January we added organisation-wide branch protection rulesets on the plone GitHub org to prevent force pushes like this. Unfortunately rulesets are not available organisation-wide on the collective GitHub org. Last week we added protective rulesets on each of the affected repositories. Today we have added these for all existing repositories in the collective.

Note that we have setup the rulesets so that you cannot force push to the default branch (usually main or master) and to branches that match our usual maintenance pattern [0-9]*.x. So if you create a branch called feature, and make a PR (pull request) of this, you can still force push a change, and this is useful. Potential attackers could also do this though. If a force push happens in a PR, GitHub shows a note about this in the PR, so be careful when you see such a note, and inspect the changes.

When you add a new repository to the collective, you should add these rulesets yourself.

For the list of repositories, and the main script that we used to add the rulesets, see Script to add and check standard branch/tag rulesets on GitHub · GitHub

Maurits van Rees
Plone Security Team

Here is the list of affected repositories as well. (I tried to include the script here, but it did not show up nicely.)

Affected repos
collective-referencecontent
collective-searchblocks
collective.address
collective.ajaxify
collective.anotherdynamicgroupsplugin
collective.auditlog
collective.beaker
collective.behavior.talcondition
collective.big.bang
collective.catalogtrace
collective.checklist
collective.ckeditor
collective.ckeditortemplates
collective.classschedule
collective.click_to_zoom
collective.collectionepilog
collective.collectionfilter
collective.contact.core
collective.contact.plonegroup
collective.contactformprotection
collective.contentrules.setfield
collective.contentsections
collective.cron
collective.customizedstaticresources
collective.datatablesviews
collective.datepickerpattern
collective.deletepermission
collective.dms.basecontent
collective.dms.batchimport
collective.dms.mailcontent
collective.dms.scanbehavior
collective.documentgenerator
collective.eeafaceted.collectionwidget
collective.eeafaceted.dashboard
collective.eeafaceted.z3ctable
collective.excelexport
collective.exportimport.examples
collective.externalimageeditor
collective.externallinkfilter
collective.faq
collective.formsupport.counter
collective.ftw.tokenauth
collective.ftw.upgrade
collective.ftwslacker
collective.generic.devmode
collective.generic.skel
collective.generic.webbuilder
collective.geolocationbehavior
collective.gridlisting
collective.html2blocks
collective.iconifiedcategory
collective.imagemaps
collective.immediatecreate
collective.impersonate
collective.js.backbone
collective.js.chosen
collective.js.datatables
collective.js.tooltipster
collective.js.underscore
collective.jsconfiguration
collective.keycdn
collective.labels
collective.ldapsetup
collective.localstyles
collective.markdownplus
collective.mockmailhost
collective.pdbpp
collective.portlet.embed
collective.portlet.relateditems
collective.pwexpiry
collective.querynextprev
collective.quickupload
collective.recipe.omelette
collective.restapi.easyform
collective.revisionmanager
collective.richdescription
collective.sendinblue
collective.sidebar
collective.sitter
collective.solr
collective.splitsitemap
collective.tiles.advancedstatic
collective.tiles.carousel
collective.timestamp
collective.translators
collective.transmute
collective.vectorsearch
collective.venue
collective.volto.contactsblock
collective.volto.dropdownmenu
collective.volto.footer
collective.volto.formsupport
collective.volto.gdprcookie
collective.volto.otp
collective.volto.secondarymenu
collective.volto.sections
collective.volto.slimheader
collective.volto.subfooter
collective.webhook
collective.wfadaptations
collective.workspace
collective.xkey
collective.z3cform.captionedrelationfield
collective.z3cform.chosen
collective.z3cform.datagridfield
collective.z3cform.jsonwidget
collective.z3cform.select2
dexterity.localroles
dexterity.localrolesfield
experimental.gracefulblobmissing
fortytwo
i18ndude
iw.rejectanonymous
keycloak-and-plone
lineage.controlpanels
lineage.themeselection
mr.scripty
mrs-developer
pas.plugins.eea
pas.plugins.headers
pas.plugins.tfa
plone_clean_history
plone.app.locales
plone6-autologin-extension
ploneconf.site
plonex
Products.csvreplicata
Products.DateRecurringIndex
Products.LoginLockout
Products.PasswordStrength
Products.PDBDebugMode
Products.PrintingMailHost
Products.RefBiblioParser
pyf-gui
pyf.aggregator
pytest-jsonschema
robotsuite
rss-provider
seven-training-addon
tech-event
training_buildout
tui-forms
volto-blocks-widget
volto-contents-next
volto-csp
volto-dropdownmenu
volto-footer
volto-form-block
volto-form-counter
volto-gdpr-privacy
volto-google-analytics
volto-hideshow-blocks
volto-image-editor
volto-middleware-binary
volto-multilingual-widget
volto-rss-receiver
volto-secondarymenu
volto-slimheader
volto-social-settings
volto-speakerdeck
volto-subfooter
volto-venue
zestreleaser.towncrier
zpretty
15 Likes

Thanks a lot @mauritsvanrees!

And thanks a lot @mamico for figuring out the list of affected repositories and fixing some variants of this attack. And @dataflake for finding the PolinRider write-up and help search for further infected packages and his knowledge about protective rulesets.
Both are members of the Plone/Zope Security Team.

7 Likes

Big thanks to the entire security team for their efforts!

Thank you @mauritsvanrees, the security team, and everybody who is/was involved in working on this. You rock!