Skip to content

feat: add config_webhook_endpoints table, store CRUD methods, and secret rotation with Standard Webhooks signing - #5247

Merged
Pratham-Mishra04 merged 1 commit into
devfrom
07-14-feat_webhook_endpoint_registry_in_configstore
Jul 17, 2026
Merged

feat: add config_webhook_endpoints table, store CRUD methods, and secret rotation with Standard Webhooks signing#5247
Pratham-Mishra04 merged 1 commit into
devfrom
07-14-feat_webhook_endpoint_registry_in_configstore

Conversation

@Pratham-Mishra04

Copy link
Copy Markdown
Collaborator

Summary

Introduces the config_webhook_endpoints table and all associated store-layer logic needed to persist, retrieve, update, and rotate secrets for registered webhook endpoints. This is the foundational persistence layer for outbound webhook delivery.

Changes

  • Added TableWebhookEndpoint model (framework/configstore/tables/webhooks.go) defining the schema for webhook endpoints, including:
    • Supported event types (async_job.completed, async_job.failed)
    • Per-endpoint delivery tuning knobs (retries, backoff, timeouts, concurrency)
    • Custom delivery headers stored encrypted at rest
    • Signing secrets generated in Standard Webhooks format (whsec_ + base64 of 32 random bytes), encrypted at rest and never included in JSON serialization
    • BeforeSave/AfterFind GORM hooks handling serialization of virtual fields (Events, Headers) and encryption/decryption of sensitive columns
    • Validate() enforcing name, URL (HTTPS required unless allow_private_network is set, no credentials or fragments, private/link-local ranges blocked), event list, and header name rules
  • Added migrationAddWebhookEndpointsTable database migration step
  • Added ConfigStore interface methods: GetWebhookEndpoints, GetWebhookEndpointByID, GetWebhookEndpointByName, CreateWebhookEndpoint, UpdateWebhookEndpoint, DeleteWebhookEndpoint, RotateWebhookEndpointSecret
  • Implemented all store methods on RDBConfigStore:
    • CreateWebhookEndpoint auto-generates an ID and signing secret when not supplied, enforces name uniqueness, and returns the plaintext secret on the struct for one-time display
    • UpdateWebhookEndpoint resets the consecutive-failure counter only on URL changes; re-enabling a disabled endpoint does not reset it
    • RotateWebhookEndpointSecret replaces the signing secret atomically; only the new secret is stored
  • Added stub implementations of all new interface methods to MockConfigStore in the HTTP transport test package

Type of change

  • Bug fix
  • Feature
  • Refactor
  • Documentation
  • Chore/CI

Affected areas

  • Core (Go)
  • Transports (HTTP)
  • Providers/Integrations
  • Plugins
  • UI (React)
  • Docs

How to test

go test ./framework/configstore/... ./framework/configstore/tables/... ./transports/bifrost-http/lib/...

Key test scenarios covered:

  • TestWebhookEndpointCreate — ID and whsec_-prefixed secret are generated server-side; distinct endpoints receive distinct secrets
  • TestWebhookEndpointCreateDuplicateName — returns ErrAlreadyExists
  • TestWebhookEndpointUpdate — non-URL changes preserve the failure counter; URL changes reset it; signing secret is never modified
  • TestWebhookEndpointUpdateReenablePreservesFailureCounter — re-enabling a disabled endpoint does not reset the failure counter
  • TestWebhookEndpointRotateSecret — new secret is stored immediately; old secret is gone
  • TestTableWebhookEndpoint_EncryptDecrypt — ciphertext is stored, plaintext is restored after read
  • TestTableWebhookEndpoint_SecretsExcludedFromJSON — secret material never appears in JSON output
  • TestTableWebhookEndpoint_HeadersEncryptDecrypt — custom headers are encrypted at rest; env references survive the round-trip as references

Breaking changes

  • Yes
  • No

Any type implementing the ConfigStore interface must add the seven new webhook endpoint methods. The MockConfigStore in the HTTP transport package has been updated; other mocks in the codebase will need the same stub additions.

Security considerations

  • Signing secrets are generated using crypto/rand and formatted as whsec_ + base64(32 bytes), following the Standard Webhooks specification.
  • Secrets and custom delivery headers are encrypted at rest using the existing encrypt package when encryption is enabled.
  • Secrets are excluded from JSON serialization (json:"-") and are surfaced in plaintext only once — at creation or rotation time — via the in-memory struct field.
  • Webhook URLs are validated against bifrost.ValidateExternalURL, which rejects link-local and metadata address ranges regardless of the allow_private_network flag. HTTP is only permitted when allow_private_network is explicitly set.
  • Credentials and fragments are rejected from webhook URLs.
  • Standard Webhooks signing headers and protocol-level headers are protected and cannot be overridden by caller-supplied custom headers.

Checklist

  • I read docs/contributing/README.md and followed the guidelines
  • I added/updated tests where appropriate
  • I updated documentation where needed
  • I verified builds succeed (Go and UI)
  • I verified the CI pipeline passes locally if applicable

@CLAassistant

Copy link
Copy Markdown

CLA assistant check
Thank you for your submission! We really appreciate it. Like many open source projects, we ask that you sign our Contributor License Agreement before we can accept your contribution.
You have signed the CLA already but the status is still pending? Let us recheck it.

Pratham-Mishra04 commented Jul 15, 2026

Copy link
Copy Markdown
Collaborator Author

This stack of pull requests is managed by Graphite. Learn more about stacking.

@coderabbitai

coderabbitai Bot commented Jul 15, 2026

Copy link
Copy Markdown
Contributor

Review Change Stack

Caution

Review failed

The pull request is closed.

ℹ️ Recent review info
⚙️ Run configuration

Configuration used: Path: .coderabbit.yaml

Review profile: CHILL

Plan: Pro Plus

Run ID: bf8f7ed2-5829-4c06-a912-1c9d0333ca6d

📥 Commits

Reviewing files that changed from the base of the PR and between b098b0b and 4b735cb.

📒 Files selected for processing (9)
  • framework/configstore/migrations.go
  • framework/configstore/rdb.go
  • framework/configstore/rdb_test.go
  • framework/configstore/store.go
  • framework/configstore/tables/clientconfig_test.go
  • framework/configstore/tables/encryption_test.go
  • framework/configstore/tables/webhooks.go
  • framework/configstore/tables/webhooks_test.go
  • transports/bifrost-http/lib/config_test.go

📝 Walkthrough

Summary by CodeRabbit

  • New Features

    • Added webhook endpoint management, including creation, updates, deletion, lookup, and listing.
    • Added support for subscribing to asynchronous job events.
    • Added secure webhook signing secret generation and rotation.
    • Added configurable delivery retries, timeouts, concurrency, and custom headers.
    • Added validation for endpoint URLs, events, and headers.
    • Added encryption for webhook secrets and sensitive header values.
  • Bug Fixes

    • Improved handling of webhook delivery failure counters when endpoint URLs change.
  • Tests

    • Added comprehensive coverage for validation, persistence, encryption, and endpoint lifecycle operations.

Walkthrough

Adds webhook endpoint models with validation and encrypted fields, a configstore migration, ConfigStore and RDB persistence methods, secret rotation, and comprehensive model and repository tests.

Changes

Webhook endpoint configuration

Layer / File(s) Summary
Endpoint model, validation, and encryption
framework/configstore/tables/webhooks.go, framework/configstore/tables/webhooks_test.go
Defines supported events, endpoint fields, URL and header validation, JSON persistence, encryption hooks, and round-trip tests for secrets and headers.
Migration and ConfigStore persistence
framework/configstore/migrations.go, framework/configstore/store.go, framework/configstore/rdb.go, framework/configstore/rdb_test.go, framework/configstore/tables/encryption_test.go, transports/bifrost-http/lib/config_test.go
Adds the webhook endpoint table migration, ConfigStore methods, RDB CRUD and secret rotation, repository coverage, SQLite setup, and mock compatibility methods.
Existing test compatibility
framework/configstore/tables/clientconfig_test.go
Repositions existing AfterFind assertions without changing their behavior.

Estimated code review effort: 4 (Complex) | ~45 minutes

Sequence Diagram(s)

sequenceDiagram
  participant Caller
  participant RDBConfigStore
  participant TableWebhookEndpoint
  participant ConfigstoreDatabase
  Caller->>RDBConfigStore: CreateWebhookEndpoint
  RDBConfigStore->>TableWebhookEndpoint: Validate endpoint
  RDBConfigStore->>ConfigstoreDatabase: Save encrypted endpoint
  ConfigstoreDatabase-->>RDBConfigStore: Commit result
  RDBConfigStore-->>Caller: Return creation result
Loading

Possibly related PRs

Suggested reviewers: akshaydeo, danpiths

🚥 Pre-merge checks | ✅ 4 | ❌ 1

❌ Failed checks (1 warning)

Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 38.46% which is insufficient. The required threshold is 80.00%. Write docstrings for the functions missing them to satisfy the coverage threshold.
✅ Passed checks (4 passed)
Check name Status Explanation
Linked Issues check ✅ Passed Check skipped because no linked issues were found for this pull request.
Out of Scope Changes check ✅ Passed Check skipped because no linked issues were found for this pull request.
Title check ✅ Passed The title is concise and accurately summarizes the main change: adding webhook endpoint persistence, CRUD methods, and secret rotation.
Description check ✅ Passed The description follows the template well and covers summary, changes, testing, breaking changes, security, and checklist items.
✨ Finishing Touches
📝 Generate docstrings
  • Create stacked PR
  • Commit on current branch
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Commit unit tests in branch 07-14-feat_webhook_endpoint_registry_in_configstore

Warning

There were issues while running some tools. Please review the errors and either fix the tool's configuration or disable the tool if it's a critical failure.

🔧 golangci-lint (2.12.2)

level=error msg="[linters_context] typechecking error: pattern ./...: directory prefix . does not contain main module or its selected dependencies"


Comment @coderabbitai help to get the list of available commands.

@greptile-apps

greptile-apps Bot commented Jul 15, 2026

Copy link
Copy Markdown
Contributor

Confidence Score: 5/5

This looks safe to merge.

  • Unresolved secret references now fail before persistence.
  • Create operations use a detached persistence value, so encryption hooks do not mutate caller-owned state.
  • No blocking issues remain in the updated code.

Important Files Changed

Filename Overview
framework/configstore/rdb.go Adds webhook endpoint CRUD, secret generation, rotation, and safe handling of unresolved references and caller-owned values.
framework/configstore/tables/webhooks.go Defines the webhook endpoint model, validation rules, serialization, and encryption hooks.
framework/configstore/migrations.go Adds the reversible migration for the webhook endpoint table.
framework/configstore/rdb_test.go Adds coverage for CRUD, failed-create retries, unresolved secrets, updates, and secret rotation.

Reviews (4): Last reviewed commit: "feat: webhook endpoint registry in confi..." | Re-trigger Greptile

Comment thread framework/configstore/rdb.go
Comment thread framework/configstore/rdb.go Outdated

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 3

🧹 Nitpick comments (1)
framework/configstore/tables/webhooks.go (1)

29-38: 📐 Maintainability & Code Quality | 🔵 Trivial | ⚡ Quick win

Do not expose a mutable global event registry.

Any importing package can modify WebhookEvents, changing validation globally or racing with IsValid. Keep the slice private and return a clone from an accessor.

Proposed refactor
-// WebhookEvents lists every supported webhook event.
-var WebhookEvents = []WebhookEvent{
+var webhookEvents = []WebhookEvent{
 	WebhookEventAsyncJobCompleted,
 	WebhookEventAsyncJobFailed,
 }
 
+func SupportedWebhookEvents() []WebhookEvent {
+	return slices.Clone(webhookEvents)
+}
+
 func (e WebhookEvent) IsValid() bool {
-	return slices.Contains(WebhookEvents, e)
+	return slices.Contains(webhookEvents, e)
 }

As per coding guidelines, shared state must have clear ownership and remain race-safe.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@framework/configstore/tables/webhooks.go` around lines 29 - 38, Make the
webhook event registry private instead of exposing the mutable WebhookEvents
slice. Update IsValid to use the private registry, and add an accessor that
returns a clone of the registry so callers cannot mutate shared state or
introduce races.

Source: Coding guidelines

🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Inline comments:
In `@framework/configstore/rdb.go`:
- Around line 7444-7459: Update the create flow around the plaintextSecret
capture and transaction in the webhook endpoint method to defer restoring
endpoint.Secret before entering the transaction. Ensure the deferred restoration
runs on both successful and failed creates, including transaction commit errors,
while preserving the existing transaction and explicit success-path behavior
without restoring it redundantly.

In `@framework/configstore/tables/webhooks.go`:
- Around line 153-172: Update the webhook validation method containing the
numeric-field and header checks to enforce explicit upper bounds for retries,
backoff durations, timeouts, payload size, and concurrent deliveries, rejecting
values above the defined maxima. Also limit both the number of custom headers
and their aggregate serialized size, including header names and values, while
preserving the existing nonnegative and protected-header validation.
- Around line 176-199: Update validateWebhookEndpointURL to accept separate
allowHTTP and allowPrivateNetwork policies: permit HTTP only when allowHTTP is
enabled, while keeping private and loopback address access controlled
exclusively by allowPrivateNetwork. Ensure loopback IPv4 and IPv6 URLs are
rejected by default even when using HTTPS, and update webhook configuration
defaults and validation tests to cover HTTPS-only behavior plus both loopback
forms.

---

Nitpick comments:
In `@framework/configstore/tables/webhooks.go`:
- Around line 29-38: Make the webhook event registry private instead of exposing
the mutable WebhookEvents slice. Update IsValid to use the private registry, and
add an accessor that returns a clone of the registry so callers cannot mutate
shared state or introduce races.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: Path: .coderabbit.yaml

Review profile: CHILL

Plan: Pro Plus

Run ID: 7fe28403-922c-4ea9-a95e-dde1892f7d20

📥 Commits

Reviewing files that changed from the base of the PR and between 968d6bf and b098b0b.

📒 Files selected for processing (9)
  • framework/configstore/migrations.go
  • framework/configstore/rdb.go
  • framework/configstore/rdb_test.go
  • framework/configstore/store.go
  • framework/configstore/tables/clientconfig_test.go
  • framework/configstore/tables/encryption_test.go
  • framework/configstore/tables/webhooks.go
  • framework/configstore/tables/webhooks_test.go
  • transports/bifrost-http/lib/config_test.go
💤 Files with no reviewable changes (1)
  • framework/configstore/tables/clientconfig_test.go

Comment thread framework/configstore/rdb.go Outdated
Comment thread framework/configstore/tables/webhooks.go
Comment thread framework/configstore/tables/webhooks.go
@Pratham-Mishra04
Pratham-Mishra04 force-pushed the 07-14-feat_webhook_endpoint_registry_in_configstore branch from b098b0b to 5354465 Compare July 15, 2026 20:12
coderabbitai[bot]
coderabbitai Bot previously approved these changes Jul 15, 2026
Comment thread framework/configstore/rdb.go Outdated
@Pratham-Mishra04
Pratham-Mishra04 force-pushed the 07-14-feat_webhook_endpoint_registry_in_configstore branch from 5354465 to fbcfb94 Compare July 16, 2026 09:41

Pratham-Mishra04 commented Jul 17, 2026

Copy link
Copy Markdown
Collaborator Author

Merge activity

  • Jul 17, 8:36 AM UTC: A user started a stack merge that includes this pull request via Graphite.
  • Jul 17, 8:39 AM UTC: Graphite rebased this pull request as part of a merge.
  • Jul 17, 8:40 AM UTC: @Pratham-Mishra04 merged this pull request with Graphite.

@Pratham-Mishra04
Pratham-Mishra04 changed the base branch from 07-14-refactor_share_ssrf-safe_outbound_dialer_via_core_network to graphite-base/5247 July 17, 2026 08:37
@Pratham-Mishra04
Pratham-Mishra04 changed the base branch from graphite-base/5247 to dev July 17, 2026 08:37
@Pratham-Mishra04
Pratham-Mishra04 dismissed coderabbitai[bot]’s stale review July 17, 2026 08:37

The base branch was changed.

@Pratham-Mishra04
Pratham-Mishra04 force-pushed the 07-14-feat_webhook_endpoint_registry_in_configstore branch from fbcfb94 to 4b735cb Compare July 17, 2026 08:38
@Pratham-Mishra04
Pratham-Mishra04 merged commit ba0c313 into dev Jul 17, 2026
13 of 14 checks passed
@Pratham-Mishra04
Pratham-Mishra04 deleted the 07-14-feat_webhook_endpoint_registry_in_configstore branch July 17, 2026 08:40
akshaydeo pushed a commit that referenced this pull request Jul 17, 2026
…ecret rotation with Standard Webhooks signing (#5247)

Introduces the `config_webhook_endpoints` table and all associated store-layer logic needed to persist, retrieve, update, and rotate secrets for registered webhook endpoints. This is the foundational persistence layer for outbound webhook delivery.

- Added `TableWebhookEndpoint` model (`framework/configstore/tables/webhooks.go`) defining the schema for webhook endpoints, including:
  - Supported event types (`async_job.completed`, `async_job.failed`)
  - Per-endpoint delivery tuning knobs (retries, backoff, timeouts, concurrency)
  - Custom delivery headers stored encrypted at rest
  - Signing secrets generated in Standard Webhooks format (`whsec_` + base64 of 32 random bytes), encrypted at rest and never included in JSON serialization
  - `BeforeSave`/`AfterFind` GORM hooks handling serialization of virtual fields (`Events`, `Headers`) and encryption/decryption of sensitive columns
  - `Validate()` enforcing name, URL (HTTPS required unless `allow_private_network` is set, no credentials or fragments, private/link-local ranges blocked), event list, and header name rules
- Added `migrationAddWebhookEndpointsTable` database migration step
- Added `ConfigStore` interface methods: `GetWebhookEndpoints`, `GetWebhookEndpointByID`, `GetWebhookEndpointByName`, `CreateWebhookEndpoint`, `UpdateWebhookEndpoint`, `DeleteWebhookEndpoint`, `RotateWebhookEndpointSecret`
- Implemented all store methods on `RDBConfigStore`:
  - `CreateWebhookEndpoint` auto-generates an ID and signing secret when not supplied, enforces name uniqueness, and returns the plaintext secret on the struct for one-time display
  - `UpdateWebhookEndpoint` resets the consecutive-failure counter only on URL changes; re-enabling a disabled endpoint does not reset it
  - `RotateWebhookEndpointSecret` replaces the signing secret atomically; only the new secret is stored
- Added stub implementations of all new interface methods to `MockConfigStore` in the HTTP transport test package

- [ ] Bug fix
- [x] Feature
- [ ] Refactor
- [ ] Documentation
- [ ] Chore/CI

- [x] Core (Go)
- [x] Transports (HTTP)
- [ ] Providers/Integrations
- [ ] Plugins
- [ ] UI (React)
- [ ] Docs

```sh
go test ./framework/configstore/... ./framework/configstore/tables/... ./transports/bifrost-http/lib/...
```

Key test scenarios covered:
- `TestWebhookEndpointCreate` — ID and `whsec_`-prefixed secret are generated server-side; distinct endpoints receive distinct secrets
- `TestWebhookEndpointCreateDuplicateName` — returns `ErrAlreadyExists`
- `TestWebhookEndpointUpdate` — non-URL changes preserve the failure counter; URL changes reset it; signing secret is never modified
- `TestWebhookEndpointUpdateReenablePreservesFailureCounter` — re-enabling a disabled endpoint does not reset the failure counter
- `TestWebhookEndpointRotateSecret` — new secret is stored immediately; old secret is gone
- `TestTableWebhookEndpoint_EncryptDecrypt` — ciphertext is stored, plaintext is restored after read
- `TestTableWebhookEndpoint_SecretsExcludedFromJSON` — secret material never appears in JSON output
- `TestTableWebhookEndpoint_HeadersEncryptDecrypt` — custom headers are encrypted at rest; env references survive the round-trip as references

- [x] Yes
- [ ] No

Any type implementing the `ConfigStore` interface must add the seven new webhook endpoint methods. The `MockConfigStore` in the HTTP transport package has been updated; other mocks in the codebase will need the same stub additions.

- Signing secrets are generated using `crypto/rand` and formatted as `whsec_` + base64(32 bytes), following the Standard Webhooks specification.
- Secrets and custom delivery headers are encrypted at rest using the existing `encrypt` package when encryption is enabled.
- Secrets are excluded from JSON serialization (`json:"-"`) and are surfaced in plaintext only once — at creation or rotation time — via the in-memory struct field.
- Webhook URLs are validated against `bifrost.ValidateExternalURL`, which rejects link-local and metadata address ranges regardless of the `allow_private_network` flag. HTTP is only permitted when `allow_private_network` is explicitly set.
- Credentials and fragments are rejected from webhook URLs.
- Standard Webhooks signing headers and protocol-level headers are protected and cannot be overridden by caller-supplied custom headers.

- [x] I read `docs/contributing/README.md` and followed the guidelines
- [x] I added/updated tests where appropriate
- [ ] I updated documentation where needed
- [x] I verified builds succeed (Go and UI)
- [x] I verified the CI pipeline passes locally if applicable
akshaydeo pushed a commit that referenced this pull request Jul 18, 2026
…ecret rotation with Standard Webhooks signing (#5247)

Introduces the `config_webhook_endpoints` table and all associated store-layer logic needed to persist, retrieve, update, and rotate secrets for registered webhook endpoints. This is the foundational persistence layer for outbound webhook delivery.

- Added `TableWebhookEndpoint` model (`framework/configstore/tables/webhooks.go`) defining the schema for webhook endpoints, including:
  - Supported event types (`async_job.completed`, `async_job.failed`)
  - Per-endpoint delivery tuning knobs (retries, backoff, timeouts, concurrency)
  - Custom delivery headers stored encrypted at rest
  - Signing secrets generated in Standard Webhooks format (`whsec_` + base64 of 32 random bytes), encrypted at rest and never included in JSON serialization
  - `BeforeSave`/`AfterFind` GORM hooks handling serialization of virtual fields (`Events`, `Headers`) and encryption/decryption of sensitive columns
  - `Validate()` enforcing name, URL (HTTPS required unless `allow_private_network` is set, no credentials or fragments, private/link-local ranges blocked), event list, and header name rules
- Added `migrationAddWebhookEndpointsTable` database migration step
- Added `ConfigStore` interface methods: `GetWebhookEndpoints`, `GetWebhookEndpointByID`, `GetWebhookEndpointByName`, `CreateWebhookEndpoint`, `UpdateWebhookEndpoint`, `DeleteWebhookEndpoint`, `RotateWebhookEndpointSecret`
- Implemented all store methods on `RDBConfigStore`:
  - `CreateWebhookEndpoint` auto-generates an ID and signing secret when not supplied, enforces name uniqueness, and returns the plaintext secret on the struct for one-time display
  - `UpdateWebhookEndpoint` resets the consecutive-failure counter only on URL changes; re-enabling a disabled endpoint does not reset it
  - `RotateWebhookEndpointSecret` replaces the signing secret atomically; only the new secret is stored
- Added stub implementations of all new interface methods to `MockConfigStore` in the HTTP transport test package

- [ ] Bug fix
- [x] Feature
- [ ] Refactor
- [ ] Documentation
- [ ] Chore/CI

- [x] Core (Go)
- [x] Transports (HTTP)
- [ ] Providers/Integrations
- [ ] Plugins
- [ ] UI (React)
- [ ] Docs

```sh
go test ./framework/configstore/... ./framework/configstore/tables/... ./transports/bifrost-http/lib/...
```

Key test scenarios covered:
- `TestWebhookEndpointCreate` — ID and `whsec_`-prefixed secret are generated server-side; distinct endpoints receive distinct secrets
- `TestWebhookEndpointCreateDuplicateName` — returns `ErrAlreadyExists`
- `TestWebhookEndpointUpdate` — non-URL changes preserve the failure counter; URL changes reset it; signing secret is never modified
- `TestWebhookEndpointUpdateReenablePreservesFailureCounter` — re-enabling a disabled endpoint does not reset the failure counter
- `TestWebhookEndpointRotateSecret` — new secret is stored immediately; old secret is gone
- `TestTableWebhookEndpoint_EncryptDecrypt` — ciphertext is stored, plaintext is restored after read
- `TestTableWebhookEndpoint_SecretsExcludedFromJSON` — secret material never appears in JSON output
- `TestTableWebhookEndpoint_HeadersEncryptDecrypt` — custom headers are encrypted at rest; env references survive the round-trip as references

- [x] Yes
- [ ] No

Any type implementing the `ConfigStore` interface must add the seven new webhook endpoint methods. The `MockConfigStore` in the HTTP transport package has been updated; other mocks in the codebase will need the same stub additions.

- Signing secrets are generated using `crypto/rand` and formatted as `whsec_` + base64(32 bytes), following the Standard Webhooks specification.
- Secrets and custom delivery headers are encrypted at rest using the existing `encrypt` package when encryption is enabled.
- Secrets are excluded from JSON serialization (`json:"-"`) and are surfaced in plaintext only once — at creation or rotation time — via the in-memory struct field.
- Webhook URLs are validated against `bifrost.ValidateExternalURL`, which rejects link-local and metadata address ranges regardless of the `allow_private_network` flag. HTTP is only permitted when `allow_private_network` is explicitly set.
- Credentials and fragments are rejected from webhook URLs.
- Standard Webhooks signing headers and protocol-level headers are protected and cannot be overridden by caller-supplied custom headers.

- [x] I read `docs/contributing/README.md` and followed the guidelines
- [x] I added/updated tests where appropriate
- [ ] I updated documentation where needed
- [x] I verified builds succeed (Go and UI)
- [x] I verified the CI pipeline passes locally if applicable
akhsaul pushed a commit to akhsaul/bifrost that referenced this pull request Aug 27, 2026
…ecret rotation with Standard Webhooks signing (maximhq#5247)

Introduces the `config_webhook_endpoints` table and all associated store-layer logic needed to persist, retrieve, update, and rotate secrets for registered webhook endpoints. This is the foundational persistence layer for outbound webhook delivery.

- Added `TableWebhookEndpoint` model (`framework/configstore/tables/webhooks.go`) defining the schema for webhook endpoints, including:
  - Supported event types (`async_job.completed`, `async_job.failed`)
  - Per-endpoint delivery tuning knobs (retries, backoff, timeouts, concurrency)
  - Custom delivery headers stored encrypted at rest
  - Signing secrets generated in Standard Webhooks format (`whsec_` + base64 of 32 random bytes), encrypted at rest and never included in JSON serialization
  - `BeforeSave`/`AfterFind` GORM hooks handling serialization of virtual fields (`Events`, `Headers`) and encryption/decryption of sensitive columns
  - `Validate()` enforcing name, URL (HTTPS required unless `allow_private_network` is set, no credentials or fragments, private/link-local ranges blocked), event list, and header name rules
- Added `migrationAddWebhookEndpointsTable` database migration step
- Added `ConfigStore` interface methods: `GetWebhookEndpoints`, `GetWebhookEndpointByID`, `GetWebhookEndpointByName`, `CreateWebhookEndpoint`, `UpdateWebhookEndpoint`, `DeleteWebhookEndpoint`, `RotateWebhookEndpointSecret`
- Implemented all store methods on `RDBConfigStore`:
  - `CreateWebhookEndpoint` auto-generates an ID and signing secret when not supplied, enforces name uniqueness, and returns the plaintext secret on the struct for one-time display
  - `UpdateWebhookEndpoint` resets the consecutive-failure counter only on URL changes; re-enabling a disabled endpoint does not reset it
  - `RotateWebhookEndpointSecret` replaces the signing secret atomically; only the new secret is stored
- Added stub implementations of all new interface methods to `MockConfigStore` in the HTTP transport test package

- [ ] Bug fix
- [x] Feature
- [ ] Refactor
- [ ] Documentation
- [ ] Chore/CI

- [x] Core (Go)
- [x] Transports (HTTP)
- [ ] Providers/Integrations
- [ ] Plugins
- [ ] UI (React)
- [ ] Docs

```sh
go test ./framework/configstore/... ./framework/configstore/tables/... ./transports/bifrost-http/lib/...
```

Key test scenarios covered:
- `TestWebhookEndpointCreate` — ID and `whsec_`-prefixed secret are generated server-side; distinct endpoints receive distinct secrets
- `TestWebhookEndpointCreateDuplicateName` — returns `ErrAlreadyExists`
- `TestWebhookEndpointUpdate` — non-URL changes preserve the failure counter; URL changes reset it; signing secret is never modified
- `TestWebhookEndpointUpdateReenablePreservesFailureCounter` — re-enabling a disabled endpoint does not reset the failure counter
- `TestWebhookEndpointRotateSecret` — new secret is stored immediately; old secret is gone
- `TestTableWebhookEndpoint_EncryptDecrypt` — ciphertext is stored, plaintext is restored after read
- `TestTableWebhookEndpoint_SecretsExcludedFromJSON` — secret material never appears in JSON output
- `TestTableWebhookEndpoint_HeadersEncryptDecrypt` — custom headers are encrypted at rest; env references survive the round-trip as references

- [x] Yes
- [ ] No

Any type implementing the `ConfigStore` interface must add the seven new webhook endpoint methods. The `MockConfigStore` in the HTTP transport package has been updated; other mocks in the codebase will need the same stub additions.

- Signing secrets are generated using `crypto/rand` and formatted as `whsec_` + base64(32 bytes), following the Standard Webhooks specification.
- Secrets and custom delivery headers are encrypted at rest using the existing `encrypt` package when encryption is enabled.
- Secrets are excluded from JSON serialization (`json:"-"`) and are surfaced in plaintext only once — at creation or rotation time — via the in-memory struct field.
- Webhook URLs are validated against `bifrost.ValidateExternalURL`, which rejects link-local and metadata address ranges regardless of the `allow_private_network` flag. HTTP is only permitted when `allow_private_network` is explicitly set.
- Credentials and fragments are rejected from webhook URLs.
- Standard Webhooks signing headers and protocol-level headers are protected and cannot be overridden by caller-supplied custom headers.

- [x] I read `docs/contributing/README.md` and followed the guidelines
- [x] I added/updated tests where appropriate
- [ ] I updated documentation where needed
- [x] I verified builds succeed (Go and UI)
- [x] I verified the CI pipeline passes locally if applicable
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.

2 participants