fix(slack): replace deprecated @slack/events-api with native crypto validation#4313
Draft
angelcaamal wants to merge 2 commits into
Draft
fix(slack): replace deprecated @slack/events-api with native crypto validation#4313angelcaamal wants to merge 2 commits into
angelcaamal wants to merge 2 commits into
Conversation
Contributor
There was a problem hiding this comment.
Code Review
This pull request replaces the @slack/events-api dependency with a manual implementation of Slack webhook signature verification using the Node.js crypto module. The changes include a new verifyWebhook function and updated integration and unit tests to support the manual signing process. Feedback was provided to enhance security by implementing replay attack protection via timestamp verification, adding explicit checks for the signing secret, and optimizing the HMAC calculation process.
Comment on lines
+96
to
+125
| const signingSecret = process.env.SLACK_SECRET; | ||
| const requestSignature = req.headers['x-slack-signature']; | ||
| const requestTimestamp = req.headers['x-slack-request-timestamp']; | ||
| const requestBody = req.rawBody; | ||
|
|
||
| if (!requestSignature || !requestTimestamp) { | ||
| const err = new Error('Missing Slack validation headers.'); | ||
| err.code = 400; | ||
| throw err; | ||
| } | ||
|
|
||
| const baseString = `v0:${requestTimestamp}:${requestBody}`; | ||
| const expectedSignature = | ||
| 'v0=' + | ||
| crypto | ||
| .createHmac('sha256', signingSecret) | ||
| .update(baseString, 'utf8') | ||
| .digest('hex'); | ||
|
|
||
| const sigBuffer = Buffer.from(requestSignature, 'utf8'); | ||
| const expBuffer = Buffer.from(expectedSignature, 'utf8'); | ||
|
|
||
| // This method throws an exception if an incoming request is invalid. | ||
| verifyRequestSignature(signature); | ||
| if ( | ||
| sigBuffer.length !== expBuffer.length || | ||
| !crypto.timingSafeEqual(sigBuffer, expBuffer) | ||
| ) { | ||
| const err = new Error('Invalid Slack signature.'); | ||
| err.code = 401; | ||
| throw err; | ||
| } |
Contributor
There was a problem hiding this comment.
To improve security and robustness, several enhancements should be made to the webhook verification logic:
- Replay Attack Protection: Verify that the request timestamp is recent (e.g., within 5 minutes) as recommended by Slack documentation. This is a critical security step when manually validating signatures.
- Configuration Check: Explicitly check for the presence of
SLACK_SECRETto provide a clearer error message if the environment variable is missing. - Efficient HMAC Calculation: Use
hmac.update()multiple times instead of string concatenation. This is more efficient for large payloads and avoids potential encoding issues by handling the raw body Buffer directly.
const signingSecret = process.env.SLACK_SECRET;
const requestSignature = req.headers['x-slack-signature'];
const requestTimestamp = req.headers['x-slack-request-timestamp'];
const requestBody = req.rawBody;
if (!signingSecret) {
const err = new Error('Slack signing secret not found.');
err.code = 500;
throw err;
}
if (!requestSignature || !requestTimestamp) {
const err = new Error('Missing Slack validation headers.');
err.code = 400;
throw err;
}
// Prevent replay attacks by verifying the timestamp is recent
const now = Math.floor(Date.now() / 1000);
if (Math.abs(now - Number(requestTimestamp)) > 60 * 5) {
const err = new Error('Slack request timestamp is too old.');
err.code = 401;
throw err;
}
const hmac = crypto.createHmac('sha256', signingSecret);
hmac.update('v0:' + requestTimestamp + ':', 'utf8');
hmac.update(requestBody || '');
const expectedSignature = 'v0=' + hmac.digest('hex');
const sigBuffer = Buffer.from(requestSignature, 'utf8');
const expBuffer = Buffer.from(expectedSignature, 'utf8');
if (
sigBuffer.length !== expBuffer.length ||
!crypto.timingSafeEqual(sigBuffer, expBuffer)
) {
const err = new Error('Invalid Slack signature.');
err.code = 401;
throw err;
}
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Description
Fixes Internal: b/414440396
Note: Before submitting a pull request, please open an issue for discussion if you are not associated with Google.
Checklist
npm test(see Testing)npm run lint(see Style)GoogleCloudPlatform/nodejs-docs-samples. Not a fork.