diff --git a/api.wordpress.org/public_html/dotorg/slack/announce.php b/api.wordpress.org/public_html/dotorg/slack/announce.php index 04a7c8918b..3f87ea45ea 100644 --- a/api.wordpress.org/public_html/dotorg/slack/announce.php +++ b/api.wordpress.org/public_html/dotorg/slack/announce.php @@ -1,13 +1,21 @@ 'wordpressdotorg', 'link_names' => 1, - 'text' => sprintf( '@%s: Use the `/committers` command.', $_POST['user_name'] ), + 'text' => sprintf( '@%s: Use the `/committers` command.', $user_name ), ) ); exit; diff --git a/api.wordpress.org/public_html/dotorg/slack/community-deputies-calendly-webhook.php b/api.wordpress.org/public_html/dotorg/slack/community-deputies-calendly-webhook.php index cb4a7b7e38..c02d71a57a 100644 --- a/api.wordpress.org/public_html/dotorg/slack/community-deputies-calendly-webhook.php +++ b/api.wordpress.org/public_html/dotorg/slack/community-deputies-calendly-webhook.php @@ -1,4 +1,15 @@ getMessage(), E_USER_WARNING ); } finally { @@ -59,6 +61,7 @@ * request, so this should still be 200 even if something goes wrong on our end. */ http_response_code( 200 ); + // phpcs:ignore WordPress.Security.EscapeOutput.OutputNotEscaped -- plain-text response body for Slack. die( $result ); } } diff --git a/api.wordpress.org/public_html/dotorg/slack/security-team.php b/api.wordpress.org/public_html/dotorg/slack/security-team.php index 65ce43cf57..e94096d080 100644 --- a/api.wordpress.org/public_html/dotorg/slack/security-team.php +++ b/api.wordpress.org/public_html/dotorg/slack/security-team.php @@ -1,14 +1,23 @@ get_col( "SELECT user_id FROM slack_users WHERE slack_id IN ($slack_user_ids_for_sql)" ); @@ -62,7 +71,7 @@ function api_call() { header( 'Content-type: text/plain' ); // Confirm it came from the Trac server. - if ( ! hash_equals( API_TOKEN, $_GET['token'] ?? '' ) ) { + if ( ! is_string( $_GET['token'] ?? null ) || ! hash_equals( API_TOKEN, $_GET['token'] ) ) { exit; } @@ -76,12 +85,12 @@ function api_call() { exit; } - echo implode( "\n", $team ) . "\n"; // Trailing newline critical. + // phpcs:ignore WordPress.Security.EscapeOutput.OutputNotEscaped -- plain-text list; trailing newline critical. + echo implode( "\n", $team ) . "\n"; exit; } +// phpcs:ignore WordPress.Security.ValidatedSanitizedInput.InputNotSanitized -- substring comparison only. if ( isset( $_SERVER['REQUEST_URI'] ) && false !== strpos( $_SERVER['REQUEST_URI'], '/security-team.php?token=' ) ) { api_call(); } - -} diff --git a/api.wordpress.org/public_html/dotorg/slack/subgroup.php b/api.wordpress.org/public_html/dotorg/slack/subgroup.php index 61ab079ae3..cc335732af 100644 --- a/api.wordpress.org/public_html/dotorg/slack/subgroup.php +++ b/api.wordpress.org/public_html/dotorg/slack/subgroup.php @@ -1,4 +1,16 @@ [ + 'regexp' => '/^\d+\z/', + 'default' => '', + ], + ] + ); + $signature = filter_var( + $_SERVER['HTTP_X_SLACK_SIGNATURE'] ?? '', + FILTER_VALIDATE_REGEXP, + [ + 'options' => [ + 'regexp' => '/^v0=[0-9a-f]{64}\z/', + 'default' => '', + ], + ] + ); if ( ! $timestamp || ! $signature ) { return false; } @@ -104,6 +135,7 @@ function verify_slack_signature( $body ) { // Dispatch: slash command vs. interactivity callback. if ( isset( $_POST['payload'] ) ) { + // phpcs:ignore WordPress.Security.ValidatedSanitizedInput.InputNotSanitized -- JSON document covered by the verified signature above; fields are validated where read. $payload = json_decode( $_POST['payload'], true ); handle_interaction( $payload ); exit; @@ -111,9 +143,28 @@ function verify_slack_signature( $body ) { handle_slash_command(); function handle_slash_command() { - $channel_id = $_POST['channel_id'] ?? ''; - $user_id = $_POST['user_id'] ?? ''; - $trigger_id = $_POST['trigger_id'] ?? ''; + // Slack channel and user IDs are uppercase alphanumeric, eg. `C0123ABCD` and `U0123ABCD`. + $id_options = [ + 'options' => [ + 'regexp' => '/^[A-Z0-9]+\z/', + 'default' => '', + ], + ]; + + $channel_id = (string) filter_var( $_POST['channel_id'] ?? '', FILTER_VALIDATE_REGEXP, $id_options ); + $user_id = (string) filter_var( $_POST['user_id'] ?? '', FILTER_VALIDATE_REGEXP, $id_options ); + + // A trigger ID, eg. `13345224609.738474920.8088930838d88f008e0`. + $trigger_id = (string) filter_var( + $_POST['trigger_id'] ?? '', + FILTER_VALIDATE_REGEXP, + [ + 'options' => [ + 'regexp' => '/^[A-Za-z0-9.]+\z/', + 'default' => '', + ], + ] + ); // trigger_id is only valid for 3s, and listing every subgroup's membership will take // longer than that. Open a loading view now, then views.update after we have the data. @@ -692,7 +743,7 @@ function finalize_create( $new_id, $name, $creator, $parent_id, $parent_name, $u // conversations.invite fails the entire batch on one malformed ID. Filter // defensively so a stray character in a config value can't sink everyone. $valid_invitees = array_values( array_filter( $invitees, function ( $id ) { - return preg_match( '/^[UWB][A-Z0-9]+$/', $id ); + return preg_match( '/^[UWB][A-Z0-9]+\z/', $id ); } ) ); $invitees = $valid_invitees; diff --git a/api.wordpress.org/public_html/dotorg/slack/trac-bot.php b/api.wordpress.org/public_html/dotorg/slack/trac-bot.php index 8fda47cef2..345b082c61 100644 --- a/api.wordpress.org/public_html/dotorg/slack/trac-bot.php +++ b/api.wordpress.org/public_html/dotorg/slack/trac-bot.php @@ -1,4 +1,14 @@ send( $parser->get_channel(), $parser->get_thread() ); - if ( $_POST['channel_name'] === 'test' ) { + if ( 'test' === $channel_name ) { // Don't post to Trac if we're coming from #test. continue; } @@ -111,7 +129,7 @@ $trac_xmlrpc = new \Trac( 'slackbot', SLACKBOT_WPORG_PASSWORD, "https://$trac.trac.wordpress.org/login/xmlrpc" ); - $comment = sprintf( $comment_template, $_POST['channel_name'], $_POST['user_name'], str_replace( '.', '', $_POST['timestamp'] ) ); + $trac_comment = sprintf( $comment_template, $channel_name, $user_name, str_replace( '.', '', $msg_timestamp ) ); foreach ( $results['ticket'] as $ticket ) { $ticket_id = is_array( $ticket ) ? $ticket['id'] : $ticket; @@ -135,7 +153,7 @@ $parser->set_redundancy( 'trac', $trac, 'ticket', $ticket_id ); - $trac_xmlrpc->ticket_update( $ticket_id, $comment ); + $trac_xmlrpc->ticket_update( $ticket_id, $trac_comment ); } } }