Skip to content

Commit 42dae31

Browse files
committed
fixes
1 parent 6342342 commit 42dae31

File tree

8 files changed

+75
-5
lines changed

8 files changed

+75
-5
lines changed

lib/hooks/core/config_validator.rb

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -35,6 +35,11 @@ class ValidationError < StandardError; end
3535
optional(:secret_env_key).filled(:string)
3636
optional(:header).filled(:string)
3737
optional(:algorithm).filled(:string)
38+
optional(:timestamp_header).filled(:string)
39+
optional(:timestamp_tolerance).filled(:integer, gt?: 0)
40+
optional(:format).filled(:string)
41+
optional(:version_prefix).filled(:string)
42+
optional(:payload_template).filled(:string)
3843
end
3944

4045
optional(:opts).hash

spec/acceptance/acceptance_tests.rb

Lines changed: 25 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,7 @@
11
# frozen_string_literal: true
22

3-
require_relative "../spec_helper"
3+
FAKE_HMAC_SECRET = "octoawesome-secret"
4+
FAKE_ALT_HMAC_SECRET = "octoawesome-2-secret"
45

56
require "rspec"
67
require "net/http"
@@ -90,6 +91,17 @@
9091
expect(response.body).to include("request validation failed")
9192
end
9293

94+
it "receives a POST request but it uses the wrong algo" do
95+
payload = { action: "push", repository: { name: "test-repo" } }
96+
headers = {
97+
"Content-Type" => "application/json",
98+
"X-Hub-Signature-256" => "sha512=" + OpenSSL::HMAC.hexdigest(OpenSSL::Digest.new("sha512"), FAKE_HMAC_SECRET, payload.to_json)
99+
}
100+
response = http.post("/webhooks/github", payload.to_json, headers)
101+
expect(response).to be_a(Net::HTTPUnauthorized)
102+
expect(response.body).to include("request validation failed")
103+
end
104+
93105
it "successfully processes a valid POST request with HMAC signature" do
94106
payload = { action: "push", repository: { name: "test-repo" } }
95107
headers = {
@@ -102,5 +114,17 @@
102114
expect(body["status"]).to eq("success")
103115
end
104116
end
117+
118+
describe "slack" do
119+
it "receives a POST request but contains an invalid HMAC signature" do
120+
payload = { text: "Hello, Slack!" }
121+
digest = OpenSSL::HMAC.hexdigest(OpenSSL::Digest.new("sha256"), FAKE_ALT_HMAC_SECRET, payload.to_json)
122+
headers = { "Content-Type" => "application/json", "Signature-256" => "sha256=#{digest}" }
123+
response = http.post("/webhooks/slack", payload.to_json, headers)
124+
125+
expect(response).to be_a(Net::HTTPUnauthorized)
126+
expect(response.body).to include("request validation failed")
127+
end
128+
end
105129
end
106130
end
Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,13 @@
1+
path: /slack
2+
handler: SlackHandler
3+
4+
request_validator:
5+
type: hmac
6+
secret_env_key: ALT_WEBHOOK_SECRET
7+
header: Signature-256
8+
algorithm: sha256
9+
format: "version=signature" # produces "v0=abc123..."
10+
timestamp_header: "X-Timestamp"
11+
version_prefix: "v0"
12+
payload_template: "v0:{timestamp}:{body}"
13+
timestamp_tolerance: 300

spec/acceptance/docker-compose.yml

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -9,6 +9,7 @@ services:
99
environment:
1010
LOG_LEVEL: DEBUG
1111
GITHUB_WEBHOOK_SECRET: "octoawesome-secret"
12+
ALT_WEBHOOK_SECRET: "octoawesome-too-secret"
1213
command: ["script/server"]
1314
healthcheck:
1415
test: ["CMD", "curl", "-f", "http://0.0.0.0:8080/health"]
Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,9 @@
1+
# frozen_string_literal: true
2+
3+
class SlackHandler < Hooks::Handlers::Base
4+
def call(payload:, headers:, config:)
5+
return {
6+
status: "success"
7+
}
8+
end
9+
end

spec/acceptance/handlers/team1_handler.rb

Lines changed: 0 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -9,9 +9,6 @@ class Team1Handler < Hooks::Handlers::Base
99
# @param config [Hash] Endpoint configuration
1010
# @return [Hash] Response data
1111
def call(payload:, headers:, config:)
12-
# Log the webhook receipt
13-
puts "Team1Handler: Received webhook for #{config.dig(:opts, :teams)&.join(', ')}"
14-
1512
# Process the payload based on type
1613
if payload.is_a?(Hash)
1714
event_type = payload[:event_type] || "unknown"

spec/lib/hooks/plugins/request_validator/hmac_spec.rb

Lines changed: 20 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -313,6 +313,25 @@ def valid_with(args = {})
313313
expect(valid_with(payload: base_payload, headers: attacker_headers, config: server_config)).to be false
314314
end
315315

316+
it "fails when server expects algorithm-prefixed but receives version-prefixed" do
317+
# Server configured for algorithm-prefixed format
318+
server_config = {
319+
request_validator: {
320+
header: "X-Signature",
321+
algorithm: "sha256",
322+
format: "algorithm=signature"
323+
}
324+
}
325+
# Attacker sends version-prefixed format
326+
timestamp = Time.now.to_i.to_s
327+
versioned_sig = "v0=" + OpenSSL::HMAC.hexdigest(OpenSSL::Digest.new("sha256"), secret, "v0:#{timestamp}:#{base_payload}")
328+
attacker_headers = {
329+
"X-Signature" => versioned_sig,
330+
"X-Timestamp" => timestamp
331+
}
332+
expect(valid_with(payload: base_payload, headers: attacker_headers, config: server_config)).to be false
333+
end
334+
316335
it "fails when algorithm in config differs from signature prefix" do
317336
# Server configured for sha512
318337
server_config = {
@@ -429,7 +448,7 @@ def valid_with(args = {})
429448
expect(valid_with(headers:, config: base_config)).to be false
430449
end
431450

432-
it "returns false when timestamp header name case differs" do
451+
it "returns true when timestamp header name case differs due to normalization" do
433452
timestamp = Time.now.to_i.to_s
434453
signing_payload = "v0:#{timestamp}:#{payload}"
435454
signature = "v0=" + OpenSSL::HMAC.hexdigest(OpenSSL::Digest.new("sha256"), secret, signing_payload)

spec/spec_helper.rb

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -7,8 +7,10 @@
77

88
TIME_MOCK = "2025-01-01T00:00:00Z"
99
FAKE_HMAC_SECRET = "octoawesome-secret"
10+
FAKE_ALT_HMAC_SECRET = "octoawesome-2-secret"
1011

1112
ENV["GITHUB_WEBHOOK_SECRET"] = FAKE_HMAC_SECRET
13+
ENV["ALT_WEBHOOK_SECRET"] = FAKE_ALT_HMAC_SECRET
1214

1315
COV_DIR = File.expand_path("../coverage", File.dirname(__FILE__))
1416

0 commit comments

Comments
 (0)