Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
71 commits
Select commit Hold shift + click to select a range
0a266bb
test(security): reproduce outbound webhook SSRF
seonghobae Aug 23, 2026
c1353bd
fix(security): pin outbound webhook destinations
seonghobae Aug 23, 2026
ab6dd51
test(server): preserve facade static-path contract
seonghobae Aug 23, 2026
4b03927
test(server): reproduce unrelated fetch body consumption
seonghobae Aug 23, 2026
1a0389f
fix(server): preserve unrelated fetch request bodies
seonghobae Aug 23, 2026
3ddb73b
fix(webhook): preserve unrelated fetch call semantics
seonghobae Aug 23, 2026
e476627
fix(webhook): classify signed fetches without disturbing requests
seonghobae Aug 23, 2026
e3801b4
docs(security): trace outbound webhook SSRF boundary
seonghobae Aug 23, 2026
2a1c12f
test(webhook): preserve dev loopback delivery
seonghobae Aug 23, 2026
dd1893e
test(webhook): run dev loopback regression
seonghobae Aug 23, 2026
b0c4d45
fix(webhook): deliver bounded dev loopback hooks
seonghobae Aug 23, 2026
ed44d32
fix(webhook): share registration and delivery policy
seonghobae Aug 23, 2026
553d5ed
docs(security): trace webhook destination boundary
seonghobae Aug 23, 2026
b65fa41
test(security): reproduce legacy HTTP webhook failure
seonghobae Aug 23, 2026
0e28697
test(security): run legacy webhook migration regression
seonghobae Aug 23, 2026
710319b
fix(security): add legacy webhook migration boundary
seonghobae Aug 23, 2026
a8005a5
fix(security): disable legacy insecure webhooks at startup
seonghobae Aug 23, 2026
00cc8ae
test(security): cover webhook migration policy and rollback
seonghobae Aug 23, 2026
85b9c72
test(security): enforce legacy migration coverage
seonghobae Aug 23, 2026
1154088
docs(security): trace legacy webhook remediation
seonghobae Aug 23, 2026
95e7bd7
docs(security): correct SSRF reference link
seonghobae Aug 23, 2026
c7f299a
docs(security): record webhook destination hardening
seonghobae Aug 23, 2026
da062b9
test(security): cover legacy private webhook migration
seonghobae Aug 23, 2026
7dbc5a8
fix(security): migrate all legacy webhook policy violations
seonghobae Aug 23, 2026
584d7b0
test(security): verify startup migration for private HTTPS webhooks
seonghobae Aug 23, 2026
fc9160b
docs(security): align legacy webhook migration trace
seonghobae Aug 23, 2026
d7d6278
docs(security): describe legacy webhook policy migration
seonghobae Aug 23, 2026
988b74b
test(webhook): authenticate before reading registration body
seonghobae Aug 23, 2026
59d32e2
fix(security): authenticate before webhook body buffering
seonghobae Aug 23, 2026
2501a38
fix(security): defer webhook body reads until authorization
seonghobae Aug 23, 2026
e45cd67
test(security): require webhook transport to avoid global fetch mutation
seonghobae Aug 23, 2026
cb32767
fix(security): bind webhook delivery to SSRF-safe transport
seonghobae Aug 23, 2026
04c9b4a
fix(security): stop mutating global fetch on app import
seonghobae Aug 23, 2026
b24255d
test(security): prove core webhook registration enforces SSRF policy
seonghobae Aug 23, 2026
4ae6391
fix: enforce webhook destination policy in core route
seonghobae Aug 23, 2026
ec730ab
fix: preserve webhook migration failure outside finally
seonghobae Aug 23, 2026
92d03f4
test: bind toast asset to facade delegation
seonghobae Aug 23, 2026
91ffa41
test: reject stale webhook registration error contract
seonghobae Aug 23, 2026
fb61902
fix: remove stale webhook error translation
seonghobae Aug 23, 2026
81be5db
test: bound authorized webhook registration bodies
seonghobae Aug 23, 2026
5a01014
fix: bound authorized webhook registration bodies
seonghobae Aug 23, 2026
b67700f
test: model Request body prefetch in webhook limit regression
seonghobae Aug 23, 2026
208e1b9
test(billing): reproduce unsigned Stripe entitlement escalation
seonghobae Aug 23, 2026
5d9b23a
test(billing): execute Stripe trust-boundary regression
seonghobae Aug 23, 2026
4935661
fix(billing): add bounded raw-body Stripe verifier
seonghobae Aug 23, 2026
5e8441d
test(billing): cover Stripe raw-body verifier
seonghobae Aug 23, 2026
799eda0
fix(billing): remove unsigned Stripe route from public graph
seonghobae Aug 23, 2026
2a177e6
test(billing): include Stripe verifier in owned coverage
seonghobae Aug 23, 2026
8087838
test(billing): verify signed Stripe delivery stays non-authoritative
seonghobae Aug 23, 2026
9a56e66
test(a11y): match the shipped core route replay contract
seonghobae Aug 23, 2026
237b9cf
test(webhooks): require transport-owned timeout
seonghobae Aug 23, 2026
a67c89f
fix(webhooks): bound stalled transport requests
seonghobae Aug 23, 2026
4dcd8d8
test(security): require early webhook body cancellation
seonghobae Aug 23, 2026
5af895e
fix(security): release rejected webhook request bodies
seonghobae Aug 23, 2026
c321333
test(security): align webhook 413 access evidence
seonghobae Aug 23, 2026
a7ec6e8
fix(observability): align webhook 413 access evidence
seonghobae Aug 23, 2026
2781be8
fix(observability): translate bounded webhook status in route chain
seonghobae Aug 23, 2026
10c98b0
fix(webhooks): align oversized access status
seonghobae Aug 23, 2026
3a3977e
fix(webhooks): wrap registration with global middleware
seonghobae Aug 23, 2026
4db0cf5
fix(webhooks): expose facade status to access logger
seonghobae Aug 23, 2026
ad84b56
test(webhooks): distinguish missing access evidence
seonghobae Aug 24, 2026
3e6fc6c
fix(webhooks): preserve global middleware envelope
seonghobae Aug 24, 2026
33b5aa6
test(billing): require verified Stripe entitlement reconciliation
seonghobae Aug 24, 2026
2bbcd92
fix(billing): reconcile verified Stripe checkout entitlement
seonghobae Aug 24, 2026
e1db2fb
test(billing): reject unpaid Stripe checkout entitlement
seonghobae Aug 24, 2026
5eecf10
fix(billing): require paid Stripe checkout before entitlement
seonghobae Aug 24, 2026
4131b81
test(billing): cover delayed Stripe payment success
seonghobae Aug 24, 2026
5c42fbe
fix(billing): reconcile delayed Stripe payment success
seonghobae Aug 24, 2026
6d18ecc
fix(security): keep SSRF lane scoped to outbound webhooks
seonghobae Aug 24, 2026
bdfb48d
test(webhooks): reproduce no-op migration write-lock contention
seonghobae Aug 27, 2026
bc60476
fix(webhooks): avoid no-op startup write reservation
seonghobae Aug 27, 2026
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
10 changes: 9 additions & 1 deletion CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,14 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0

### Security

- Constrained outbound webhook registration and delivery to public HTTPS with
per-attempt DNS/IP authorization, validated-address pinning, redirect
non-following, and replay-safe fallback; the explicit development exception
is limited to loopback HTTP. Active legacy webhook rows rejected by the
current synchronous destination policy, including HTTP and local/private
HTTPS literals, are transactionally disabled on startup with a tenant-visible
replacement action instead of silently retrying forever; DNS-backed names
remain re-authorized immediately before each delivery attempt.
- Made contextual-orchestrator briefing requests fail closed unless an authenticated endpoint is configured. Deterministic generated text is restricted to explicit `SCOPEWEAVE_DEV=1`, message/provider responses are bounded and validated, and non-loopback HTTP transport is rejected.
- Made `SCOPEWEAVE_JWT_SECRET` mandatory at startup and rejected weak or
unexpanded placeholder values so production deployments fail closed.
Expand Down Expand Up @@ -106,4 +114,4 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0

## [1.0.1] - 2026-06-25
### 성능 개선 (Performance)
- 드래그 앤 드롭 동작 중 `dragover` 이벤트에서 발생하는 O(N) 작업 리스트 검색 성능 병목 문제를, O(1) 해시맵(Map) 기반의 캐싱 조회 로직으로 개선하여 큰 크기의 WBS 리스트에서의 버벅임 현상을 해결했습니다.
- 드래그 앤 드롭 동작 중 `dragover` 이벤트에서 발생하는 O(N) 작업 리스트 검색 성능 병목 문제를, O(1) 해시맵(Map) 기반의 캐싱 조회 로직으로 개선하여 큰 크기의 WBS 리스트에서의 버벅임 현상을 해결했습니다.
53 changes: 53 additions & 0 deletions docs/doctoring/outbound-webhook-ssrf.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,53 @@
# Outbound webhook SSRF and DNS-rebinding boundary

Status: **active pull request only** (`#588`). This document does not claim that the repair is shipped on protected `develop` or released. Protected `develop` remains the source of shipped truth until the reviewed exact contributor head is integrated through live repository and organization gates.

## Customer decision this control supports

A ScopeWeave organization administrator may configure a webhook destination that causes ScopeWeave to send signed event data from the server. Because the administrator controls the destination URL, the product must distinguish an ordinary public webhook endpoint from a destination that could reach the ScopeWeave host, cloud metadata, a private network, or another special-use address.

The active repair therefore makes the network destination an authorization boundary rather than trusting a syntactically valid URL. A customer can use public HTTPS webhook endpoints; production ScopeWeave will reject destinations that are local, private, special-use, ambiguous after DNS resolution, or otherwise outside the public-unicast authority admitted by the transport.

## Threat and control traceability

| Threat / requirement | Active-PR control | Regression evidence |
| --- | --- | --- |
| Direct loopback, private, link-local, reserved, documentation, multicast, or other special-use IP destination | `server/webhook_transport.mjs` parses the URL and rejects non-public destination authorities before network I/O. IPv4-mapped IPv6 forms are normalized into the same decision. | `tests/unit/webhook-transport.test.mjs` exercises representative denied IPv4/IPv6 and mapped forms. |
| Hostname resolves to one denied answer or a mixed public+denied answer set | Every A/AAAA result must pass the public-destination policy; a mixed answer set fails closed. | DNS policy cases in `tests/unit/webhook-transport.test.mjs`. |
| DNS validation and connection use different resolver answers (rebinding/TOCTOU) | Resolution is performed before connection; the HTTPS request receives a custom `lookup` result pinned to an address from the just-validated answer set while the original hostname remains the TLS authority/SNI identity. | Rebinding and pinned-lookup cases in `tests/unit/webhook-transport.test.mjs`. |
| Redirect moves a signed body/secret to a second authority | The bounded transport uses Node HTTPS directly and does not implement redirect following. A redirect response is an application response, not a new destination request. | Redirect/non-replay cases in `tests/unit/webhook-transport.test.mjs`. |
| Retry reuses stale DNS authority | The existing application retry calls the protected transport again, so the outer delivery retry performs a new resolution/validation/pinning decision. Pre-connect failure may try another address only from the same already-validated answer set; once TLS has connected, no candidate replay occurs for that attempt. | Pre-connect fallback, post-connect replay, and rebinding-across-attempts tests. |
| Credential or fragment-bearing registration URL | Production registration accepts canonical public `https:` destinations only and rejects credentials/fragments. | `tests/api/webhook-destination-policy.test.mjs`. |
| Development compatibility accidentally weakens production | HTTP is admitted only when `SCOPEWEAVE_DEV=1` and only for explicit loopback development destinations. | Development/production registration policy tests. |
| Transport or resolver details expose internal information | Customer-visible transport errors are stable and do not include resolver answers, credentials, or lower-layer exception text. | Sanitized-failure regressions in `tests/unit/webhook-transport.test.mjs`. |
| Security wrapper changes unrelated outbound integrations | The fetch facade classifies a signed ScopeWeave webhook from method and signature/event headers without constructing or consuming an unrelated `Request`; all unrelated calls retain their original native-fetch input/init semantics. | Existing `tests/api/orchestrator-attribution.test.mjs` plus `tests/api/webhook-fetch-contract.test.mjs`. |

## Design boundary

`server/app_core.mjs` is the protected-develop application moved without behavioral editing for this slice. `server/app.mjs` is a bounded facade for webhook registration and signed webhook delivery. `server/webhook_transport.mjs` owns destination policy, resolution, address authorization, HTTPS connection pinning, and transport-level replay safety.

This structure is intentional: tenant/auth, billing, attachment, Clearfolio, project-planning, event filtering, webhook signing, attempt accounting, and the existing three-second per-attempt abort budget remain in their prior owning code. The security slice does not make those concerns subordinate to model judgment and does not alter central `.github` policy.

## Evidence state and merge boundary

The preserved RED history is followed by production implementation and two additional compatibility repairs. On contributor head `e4766272b3d5ae47e187431dd60cef7251d2086b`, the repository's existing unit/API/cloud suites are green, including the webhook transport and unrelated orchestrator attribution regressions. That hosted Server Tests run checked out GitHub's synthetic pull-request merge revision, however, so it is useful behavioral evidence but is not accepted here as immutable contributor-head merge authority.

Exact-head repository CI is being repaired independently in ScopeWeave PR `#523`; the centrally owned reusable SAST/Security exact-head defect is tracked through `ContextualWisdomLab/.github#1222`. Before `#588` can integrate, the unchanged final contributor head must receive authoritative exact-head owned coverage, required security/dependency/supply-chain evidence, zero valid unresolved findings, and qualifying independent current-head approval. Pending, synthetic-only, stale, predecessor, status-only, or model-only evidence is non-passing.

## Standards and primary technical basis

OWASP's SSRF guidance explicitly treats custom webhooks as an SSRF risk, recommends disabling redirect following, and for arbitrary external destinations recommends resolving A and AAAA records and applying the same public-address validation to every result. The implementation additionally binds that validation result to the actual socket lookup so the network destination cannot silently diverge from the authorization decision. Node's `https.request()` supports the HTTP request options needed for a custom `lookup` seam while retaining TLS hostname handling. RFC and IANA registries provide the authority for private, link-local, unique-local, and other special-purpose address classifications.

## References (APA 7)

Cheshire, S., Aboba, B., & Guttman, E. (2005). *Dynamic configuration of IPv4 link-local addresses* (RFC 3927). RFC Editor. https://doi.org/10.17487/RFC3927

Hinden, R., & Haberman, B. (2005). *Unique local IPv6 unicast addresses* (RFC 4193). RFC Editor. https://doi.org/10.17487/RFC4193

Internet Assigned Numbers Authority. (n.d.). *Number-related registries*. Retrieved August 23, 2026, from https://www.iana.org/numbers/registries

Node.js contributors. (n.d.). *HTTPS*. Node.js documentation. Retrieved August 23, 2026, from https://nodejs.org/api/https.html

OWASP Foundation. (n.d.). *Server-side request forgery prevention cheat sheet*. OWASP Cheat Sheet Series. Retrieved August 23, 2026, from https://cheatsheetseries.owasp.org/cheatsheets/Server_Side_Request_Forgery_Prevention_Cheat_Sheet.html

Rekhter, Y., Moskowitz, B., Karrenberg, D., de Groot, G. J., & Lear, E. (1996). *Address allocation for private internets* (RFC 1918). RFC Editor. https://doi.org/10.17487/RFC1918
Loading
Loading