Skip to content

Add HMAC-SHA256 auth mode to WebhookTrigger#1178

Merged
chubes4 merged 1 commit intomainfrom
webhook-hmac-auth
Apr 24, 2026
Merged

Add HMAC-SHA256 auth mode to WebhookTrigger#1178
chubes4 merged 1 commit intomainfrom
webhook-hmac-auth

Conversation

@chubes4
Copy link
Copy Markdown
Member

@chubes4 chubes4 commented Apr 24, 2026

Closes #1177.

Summary

Adds an optional HMAC-SHA256 auth mode to inc/Api/WebhookTrigger.php alongside the existing Bearer-token flow. HMAC is the industry-standard webhook auth protocol used by GitHub, Stripe, Shopify, Slack, Linear, Mailgun, Twilio, Plaid, SendGrid, and PayPal. Wiring it in natively means DM can accept these webhooks directly — no mu-plugin bridge, no relay service.

  • Bearer stays the default. Missing / unknown webhook_auth_mode is treated as bearer, so every shipped flow is byte-for-byte unchanged.
  • HMAC is purely additive — no changes to the engine, job queue, datamachine_run_flow_now, rate limiting, or the downstream initial_data.webhook_trigger payload shape.

What's in the box

New

  • inc/Api/WebhookSignatureVerifier.php — static helper with timing-safe verify_hmac_sha256() supporting three formats: sha256=hex (GitHub), hex (Linear), base64 (Shopify).
  • datamachine/webhook-trigger-set-secret ability + wp datamachine flows webhook set-secret CLI subcommand.
  • docs/api/endpoints/webhook-triggers.md — full GitHub walkthrough, Shopify/Linear examples, security notes.

Changed

  • WebhookTrigger::handle_trigger() branches on scheduling_config.webhook_auth_mode:
    • bearer → existing code path (unchanged).
    • hmac_sha256 → read raw body via $request->get_body(), enforce webhook_max_body_bytes cap (default 1 MB → 413), verify signature via WebhookSignatureVerifier.
  • WebhookTriggerAbility:
    • executeEnable accepts auth_mode, signature_header, signature_format, generate_secret, secret.
    • executeSetSecret generates or rotates the HMAC secret and flips the flow into hmac_sha256 mode.
    • executeStatus returns auth_mode and HMAC header/format — never the secret.
    • executeDisable clears all HMAC-related fields.
    • executeRegenerate now refuses to run on HMAC flows (directs the caller to set-secret).
  • WebhookCommand CLI:
    • enable gains --auth-mode, --signature-header, --signature-format, --generate-secret, --secret flags.
    • New set-secret subcommand (--secret=<value> or --generate).
    • status and list display the auth mode.
  • Added common provider signature headers to the safe-headers logging list.

Acceptance criteria

  • Flows can be enabled with --auth-mode=hmac_sha256 via CLI
  • Signature verification accepts valid HMAC and rejects invalid HMAC
  • Missing signature header returns 401 when auth_mode = hmac_sha256
  • Existing Bearer flows are unaffected (regression test test_bearer_flow_still_works + test_missing_auth_mode_defaults_to_bearer)
  • status shows auth mode + HMAC header/format, never the secret
  • max_body_bytes cap (default 1 MB) returns 413 on overflow for HMAC flows
  • Docs include a copy-pasteable GitHub webhook setup
  • All tests green, lint clean for new/modified files

Tests

  • tests/Unit/Api/WebhookSignatureVerifierTest.php — 15 pure unit tests: valid / invalid per format, empty secret, empty body, empty signature, wrong secret, tampered body, missing sha256= prefix, format mismatch, uppercase hex, unknown format, hash_equals usage.
  • tests/Unit/Api/WebhookTriggerTest.php — 17 WP_UnitTestCase tests: Bearer regression (3 cases), HMAC valid/invalid/missing/oversized/tampered (5 cases), ability-level behavior (enable modes, set-secret, regenerate refusal, disable clearing), and missing_auth_mode_defaults_to_bearer.

All 32 new tests green:

homeboy test data-machine -- --filter='Webhook'
OK (40 tests, 82 assertions)

Full-suite run is clean relative to baseline — only the 37 pre-existing failures from NetworkSettingsTest, ImportExportStepConfigTest, etc. remain. No new regressions introduced.

Lint of modified / new files is clean; the only phpstan finding attributable to WebhookTriggerAbility comes from the shared FlowHelpers trait and pre-dates this PR (it affects every class that uses the trait).

Out of scope (per #1177)

  • Ed25519 (Discord) — different primitive, separate follow-up.
  • Slack / Stripe timestamp-prefixed signatures (v0=..., t=...,v1=...) — can extend webhook_signature_format later.
  • Replay protection / timestamp windows.
  • Moving secrets out of scheduling_config into a dedicated credentials table.

Verification

cd /var/lib/datamachine/workspace/data-machine@webhook-trigger-step
homeboy lint data-machine --changed-only --summary   # clean for our files
homeboy test data-machine -- --filter='Webhook'      # 40/40 pass
homeboy test data-machine                            # no new failures vs. baseline

Adds an optional HMAC-SHA256 auth mode to inc/Api/WebhookTrigger.php
alongside the existing Bearer flow, unlocking direct integration with
GitHub, Stripe, Shopify, Slack, Linear, and other providers that sign
the raw request body.

What's new
- New WebhookSignatureVerifier helper with timing-safe HMAC-SHA256
  verification in three formats: sha256=hex, hex, base64.
- WebhookTrigger::handle_trigger now branches on scheduling_config
  .webhook_auth_mode. Missing / unknown mode defaults to 'bearer'
  for backward compatibility with every shipped flow.
- HMAC path honors a configurable signature header (default
  X-Hub-Signature-256) and a max_body_bytes cap (default 1 MB, 413
  on overflow) so unauthenticated clients cannot force arbitrarily
  large HMAC computations.
- WebhookTriggerAbility: executeEnable accepts auth_mode /
  signature_header / signature_format / generate_secret / secret;
  new executeSetSecret ability; executeStatus now exposes auth_mode
  and HMAC header/format but never the secret; executeDisable
  clears all HMAC fields; executeRegenerate refuses non-bearer flows.
- CLI: wp datamachine flows webhook enable gains --auth-mode,
  --signature-header, --signature-format, --generate-secret,
  --secret flags; new set-secret subcommand; status + list show
  auth mode.

What's unchanged
- Every existing Bearer flow continues to work byte-for-byte.
- No engine, job-queue, or run_flow behavior changes — this is
  purely pre-ability-dispatch auth.

Tests
- 15 unit tests for WebhookSignatureVerifier (valid/invalid per
  format, empty secret/body/signature, tampered body, unknown
  format, hash_equals usage).
- 17 WP_UnitTestCase tests for WebhookTrigger + WebhookTriggerAbility
  covering Bearer regression, HMAC valid/invalid/missing/oversized,
  status/disable/regenerate/set-secret behavior, and the
  missing-mode-defaults-to-bearer path.

Docs
- New docs/api/endpoints/webhook-triggers.md with end-to-end GitHub
  walkthrough and Shopify / Linear examples.
- Updated docs/core-system/wp-cli.md and abilities-api.md.

Refs: #1177
@homeboy-ci
Copy link
Copy Markdown
Contributor

homeboy-ci Bot commented Apr 24, 2026

Homeboy Results — data-machine

Audit

⚡ Scope: changed files only

audit (changed files only)

  • Alignment score: 0.803
  • Outliers in current run: 45
  • Drift increased: no
  • Severity counts: info: 16, unknown: 45, warning: 161
  • Top actionable findings:
    1. inc/Api/WebhookSignatureVerifier.php — missing_method — Missing method: register_routes
    2. inc/Api/WebhookSignatureVerifier.php — missing_method — Missing method: register
    3. inc/Api/WebhookSignatureVerifier.php — missing_method — Missing method: check_permission
    4. inc/Api/WebhookSignatureVerifier.php — missing_registration — Missing registration: rest_api_init
    5. inc/Api/WebhookTrigger.php — missing_method — Missing method: check_permission
    6. inc/Engine/AI/Directives/DirectiveOutputValidator.php — missing_method — Missing method: get_outputs
    7. inc/Abilities/Flow/FlowHelpers.php — naming_mismatch — Helper-like name does not match convention suffix 'Ability': FlowHelpers
    8. inc/Abilities/Flow/WebhookTriggerAbility.php — missing_method — Missing method: execute
    9. inc/Abilities/Flow/WebhookTriggerAbility.php — missing_method — Missing method: registerAbility
    10. inc/Abilities/FlowStep/FlowStepHelpers.php — naming_mismatch — Helper-like name does not match convention suffix 'Ability': FlowStepHelpers
Audit findings (10 shown)
1. **inc/Api/WebhookSignatureVerifier.php** — missing_method — Missing method: register_routes
2. **inc/Api/WebhookSignatureVerifier.php** — missing_method — Missing method: register
3. **inc/Api/WebhookSignatureVerifier.php** — missing_method — Missing method: check_permission
4. **inc/Api/WebhookSignatureVerifier.php** — missing_registration — Missing registration: rest_api_init
5. **inc/Api/WebhookTrigger.php** — missing_method — Missing method: check_permission
6. **inc/Engine/AI/Directives/DirectiveOutputValidator.php** — missing_method — Missing method: get_outputs
7. **inc/Abilities/Flow/FlowHelpers.php** — naming_mismatch — Helper-like name does not match convention suffix 'Ability': FlowHelpers
8. **inc/Abilities/Flow/WebhookTriggerAbility.php** — missing_method — Missing method: execute
9. **inc/Abilities/Flow/WebhookTriggerAbility.php** — missing_method — Missing method: registerAbility
10. **inc/Abilities/FlowStep/FlowStepHelpers.php** — naming_mismatch — Helper-like name does not match convention suffix 'Ability': FlowStepHelpers
Tooling versions
  • Homeboy CLI: homeboy 0.89.1+50e70ddc
  • Extension: wordpress from https://github.com/Extra-Chill/homeboy-extensions
  • Extension revision: unknown
  • Action: Extra-Chill/homeboy-action@v2

Homeboy Action v1

@chubes4 chubes4 merged commit bbd396f into main Apr 24, 2026
1 check passed
@chubes4 chubes4 deleted the webhook-hmac-auth branch April 24, 2026 12:30
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

WebhookTrigger: add optional HMAC-SHA256 auth mode for provider-agnostic inbound webhooks

1 participant