Skip to content

Changed webhook trigger default request behavior#27219

Merged
kevinansfield merged 1 commit intomainfrom
webhook-request-external
Apr 8, 2026
Merged

Changed webhook trigger default request behavior#27219
kevinansfield merged 1 commit intomainfrom
webhook-request-external

Conversation

@kevinansfield
Copy link
Copy Markdown
Member

ref https://linear.app/ghost/issue/ONC-1597/

  • Webhook triggers now use request-external by default instead of @tryghost/request
  • Internal IP destinations can still be enabled with security.allowWebhookInternalIPs for self-hosted setups

To allow webhooks to reach internal IP destinations, add to your Ghost config:

{
  "security": {
    "allowWebhookInternalIPs": true
  }
}

ref https://linear.app/ghost/issue/ONC-1597/

- Webhook triggers now use `request-external` by default instead of `@tryghost/request`
- Internal IP destinations can still be enabled with `security.allowWebhookInternalIPs` for self-hosted setups

To allow webhooks to reach internal IP destinations, add to your Ghost config:

```
{
  "security": {
    "allowWebhookInternalIPs": true
  }
}
```
@kevinansfield kevinansfield enabled auto-merge (squash) April 8, 2026 07:01
@coderabbitai
Copy link
Copy Markdown
Contributor

coderabbitai bot commented Apr 8, 2026

Walkthrough

A new security configuration flag security.allowWebhookInternalIPs has been introduced with a default value of false. The WebhookTrigger constructor now conditionally selects between two HTTP request implementations based on this configuration: @tryghost/request when enabled, or an external request implementation when disabled. The outbound webhook request options explicitly include method: 'POST'. Configuration defaults, testing environments, and unit and integration tests have been updated to reflect this behavior.

🚥 Pre-merge checks | ✅ 2 | ❌ 1

❌ Failed checks (1 warning)

Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 0.00% which is insufficient. The required threshold is 80.00%. Write docstrings for the functions missing them to satisfy the coverage threshold.
✅ Passed checks (2 passed)
Check name Status Explanation
Title check ✅ Passed The title clearly summarizes the main change: webhook triggers now use a different default request behavior, specifically switching from @tryghost/request to request-external by default.
Description check ✅ Passed The description directly relates to the changeset, explaining the default behavior change and the new configuration option for enabling internal IP support in self-hosted setups.

✏️ Tip: You can configure your own custom pre-merge checks in the settings.

✨ 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 webhook-request-external

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

@sonarqubecloud
Copy link
Copy Markdown

sonarqubecloud bot commented Apr 8, 2026

Quality Gate Passed Quality Gate passed

Issues
1 New issue
0 Accepted issues

Measures
0 Security Hotspots
No data about Coverage
0.0% Duplication on New Code

See analysis details on SonarQube Cloud

@kevinansfield kevinansfield disabled auto-merge April 8, 2026 07:05
Copy link
Copy Markdown
Contributor

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

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

🧹 Nitpick comments (2)
ghost/core/core/shared/config/env/config.testing.json (1)

17-19: Consider keeping the shared test baseline on the new default.

Setting this to true in the global testing configs means any webhook spec that forgets to override it will exercise @tryghost/request instead of the new request-external path. If only a small set of suites need localhost delivery, I'd opt them in locally so default test coverage stays aligned with production. The same applies to ghost/core/core/shared/config/env/config.testing-mysql.json.

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@ghost/core/core/shared/config/env/config.testing.json` around lines 17 - 19,
The shared test config currently sets security.allowWebhookInternalIPs to true
which causes tests to exercise the old internal webhook path; change
security.allowWebhookInternalIPs back to the new default (false) in both
config.testing.json and config.testing-mysql.json so suites that need localhost
delivery must opt-in locally, i.e., update the security.allowWebhookInternalIPs
value in the JSON objects referenced in those two files.
ghost/core/test/integration/services/webhook-request.test.js (1)

65-96: These specs still don't exercise the new internal-IP contract.

Both branches post to a public host, and the false branch also switches env to development, so the tests can pass without ever proving that internal targets are rejected by default and allowed when the flag is enabled. A localhost/127.0.0.1 case in each branch would lock down the behavior this config is meant to control.

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@ghost/core/test/integration/services/webhook-request.test.js` around lines 65
- 96, Tests currently don't verify internal-IP behavior; both branches use a
public WEBHOOK_TARGET and the false branch sets env=development which bypasses
IP checks. Update the two test cases in webhook-request.test.js to include an
additional sub-case each that targets a localhost address (e.g.,
http://127.0.0.1:PORT or http://localhost:PORT) and uses nock to intercept that
host/path; for the branch where
configUtils.set('security:allowWebhookInternalIPs', false) assert that the POST
to localhost is rejected/not attempted (scope.isDone() should be false or
trigger should throw) and for the branch with allowWebhookInternalIPs true
assert the localhost POST is delivered successfully; keep setupWebhookModel(),
instantiation of new WebhookTrigger({models, payload, limitService}) and
trigger.trigger(WEBHOOK_EVENT, {}) the same so the tests exercise the
internal-IP contract.
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Nitpick comments:
In `@ghost/core/core/shared/config/env/config.testing.json`:
- Around line 17-19: The shared test config currently sets
security.allowWebhookInternalIPs to true which causes tests to exercise the old
internal webhook path; change security.allowWebhookInternalIPs back to the new
default (false) in both config.testing.json and config.testing-mysql.json so
suites that need localhost delivery must opt-in locally, i.e., update the
security.allowWebhookInternalIPs value in the JSON objects referenced in those
two files.

In `@ghost/core/test/integration/services/webhook-request.test.js`:
- Around line 65-96: Tests currently don't verify internal-IP behavior; both
branches use a public WEBHOOK_TARGET and the false branch sets env=development
which bypasses IP checks. Update the two test cases in webhook-request.test.js
to include an additional sub-case each that targets a localhost address (e.g.,
http://127.0.0.1:PORT or http://localhost:PORT) and uses nock to intercept that
host/path; for the branch where
configUtils.set('security:allowWebhookInternalIPs', false) assert that the POST
to localhost is rejected/not attempted (scope.isDone() should be false or
trigger should throw) and for the branch with allowWebhookInternalIPs true
assert the localhost POST is delivered successfully; keep setupWebhookModel(),
instantiation of new WebhookTrigger({models, payload, limitService}) and
trigger.trigger(WEBHOOK_EVENT, {}) the same so the tests exercise the
internal-IP contract.

ℹ️ Review info
⚙️ Run configuration

Configuration used: Path: .coderabbit.yaml

Review profile: CHILL

Plan: Pro

Run ID: b030875a-108a-478e-b14d-bb00e8549a41

📥 Commits

Reviewing files that changed from the base of the PR and between a597db6 and d7082f5.

⛔ Files ignored due to path filters (1)
  • ghost/core/test/e2e-api/admin/__snapshots__/config.test.js.snap is excluded by !**/*.snap
📒 Files selected for processing (6)
  • ghost/core/core/server/services/webhooks/webhook-trigger.js
  • ghost/core/core/shared/config/defaults.json
  • ghost/core/core/shared/config/env/config.testing-mysql.json
  • ghost/core/core/shared/config/env/config.testing.json
  • ghost/core/test/integration/services/webhook-request.test.js
  • ghost/core/test/unit/server/services/webhooks/trigger.test.js

@kevinansfield kevinansfield merged commit 815962d into main Apr 8, 2026
40 checks passed
@kevinansfield kevinansfield deleted the webhook-request-external branch April 8, 2026 08:08
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