While working on T268627: PushToWatch extension does not show anything in page footer as well as fixing some unrelated issues in that unmaintained codebase, I realized that the form (which currently requires no authentication whatsoever, thus rendering the extension 100% unsuitable for a public, world-editable wiki) shown on the page footer doesn't have an edit token, rendering the extension vulnerable to cross-site request forgeries.
Quick patch (on top of all the patches I currently have open in gerrit against this extension):
diff --git a/src/PushToWatch.php b/src/PushToWatch.php index 9a7f9a2..dd3e6bf 100644 --- a/src/PushToWatch.php +++ b/src/PushToWatch.php @@ -85,6 +85,7 @@ class PushToWatch { $output .= Html::rawElement( 'form', [ 'method' => 'post' ], wfMessage( 'pushtowatch' )->escaped() . wfMessage( 'word-separator' )->parse() . + Html::hidden( 'wpPushToWatchToken', RequestContext::getMain()->getUser()->getEditToken() ) . Html::submitButton( '', [ 'style' => 'display:none' ] ) . // @todo FIXME: give this element class="mw-autocomplete-user" and add the // 'mediawiki.userSuggest' ResourceLoader module to output to enable @@ -107,14 +108,21 @@ class PushToWatch { */ public static function onSkinAddFooterLinks( Skin $sk, string $key, array &$footerLinks ) { $title = $sk->getRelevantTitle(); + $request = $sk->getRequest(); $output = '<hr />'; + $isTokenOK = $sk->getUser()->matchEditToken( $request->getVal( 'wpPushToWatchToken' ) ); try { - $user = $sk->getRequest()->getText( 'pushtowatch_user' ); - // FIXME: This destroys all usernames that contain other characters! - $user = preg_replace( "#[^a-z]#i", '', $user ); - if ( $user ) { - self::addToWatch( $title, $user ); + if ( $request->wasPosted() && $isTokenOK ) { + $user = $request->getText( 'pushtowatch_user' ); + // FIXME: This destroys all usernames that contain other characters! + $user = preg_replace( "#[^a-z]#i", '', $user ); + if ( $user ) { + self::addToWatch( $title, $user ); + } + } elseif ( $request->wasPosted() && !$isTokenOK ) { + // CSRF attempt or something... + $output .= Html::errorBox( $sk->msg( 'sessionfailure' )->parse() ); } } catch ( Exception $e ) { $output .= Html::errorBox( $skin->msg( 'pushtowatch-error', $user )->parse() );