- Enhanced Notifications component tests to include support for Discord, Gotify, and Webhook provider types. - Updated test cases to validate the correct handling of provider type options and ensure proper payload structure during creation, preview, and testing. - Introduced new tests for Gotify token handling and ensured sensitive information is not exposed in the UI. - Refactored existing tests for clarity and maintainability, including improved assertions and error handling. - Added comprehensive coverage for payload validation scenarios, including malformed requests and security checks against SSRF and oversized payloads.
18 KiB
post_title, categories, tags, summary, post_date
| post_title | categories | tags | summary | post_date | ||||||||||
|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|
| Current Spec: Notify HTTP Wrapper Rollout for Gotify and Custom Webhook |
|
|
Single authoritative plan for Notify HTTP wrapper rollout for Gotify and Custom Webhook, including token secrecy contract, SSRF hardening, transport safety, expanded test matrix, and safe PR slicing. | 2026-02-23 |
Active Plan: Notify Migration — HTTP Wrapper for Gotify and Custom Webhook
Date: 2026-02-23 Status: Ready for Supervisor Review Scope Type: Backend + Frontend + E2E + Coverage/CI alignment Authority: This is the only active authoritative plan in this file.
Introduction
This plan defines the Notify migration increment that enables HTTP-wrapper
routing for gotify and webhook providers while preserving current Discord
behavior.
Primary goals:
- Enable a unified wrapper path for outbound provider dispatch.
- Make Gotify token handling write-only and non-leaking by contract.
- Add explicit SSRF/redirect/rebinding protections.
- Add strict error leakage controls for preview/test paths.
- Add wrapper transport guardrails and expanded validation tests.
Research Findings
Current architecture and constraints
- Notification provider CRUD/Test/Preview routes already exist:
GET/POST/PUT/DELETE /api/v1/notifications/providersPOST /api/v1/notifications/providers/testPOST /api/v1/notifications/providers/preview
- Current provider handling is Discord-centric in handler/service/frontend.
- Security-event dispatch path exists and is stable.
- Existing notification E2E coverage is mostly Discord-focused.
Gaps to close
- Wrapper enablement for Gotify/Webhook is incomplete end-to-end.
- Token secrecy contract is not explicit enough across write/read/test flows.
- SSRF policy needs explicit protocol, redirect, and DNS rebinding rules.
- Error details need strict sanitization and request correlation.
- Retry/body/header transport limits need explicit hard requirements.
Requirements (EARS)
- WHEN provider type is
gotifyorwebhook, THE SYSTEM SHALL dispatch outbound notifications through a shared HTTP wrapper path. - WHEN provider type is
discord, THE SYSTEM SHALL preserve current behavior with no regression in create/update/test/preview flows. - WHEN a Gotify token is provided, THE SYSTEM SHALL accept it only on create and update write paths.
- WHEN a Gotify token is accepted, THE SYSTEM SHALL store it securely server-side.
- WHEN provider data is returned on read/test/preview responses, THE SYSTEM SHALL NOT return token values or secret derivatives.
- WHEN validation errors or logs are emitted, THE SYSTEM SHALL NOT echo token, auth header, or secret material.
- WHEN wrapper dispatch is used, THE SYSTEM SHALL enforce HTTPS-only targets by default.
- WHEN development override is required for HTTP targets, THE SYSTEM SHALL allow it only via explicit controlled dev flag, disabled by default.
- WHEN redirects are encountered, THE SYSTEM SHALL deny redirects by default; if redirects are enabled, THE SYSTEM SHALL re-validate each hop.
- WHEN resolving destination addresses, THE SYSTEM SHALL block loopback, link-local, private, multicast, and IPv6 ULA ranges.
- WHEN DNS resolution changes during request lifecycle, THE SYSTEM SHALL perform re-resolution checks and reject rebinding to blocked ranges.
- WHEN wrapper mode dispatches Gotify/Webhook, THE SYSTEM SHALL use
POSTonly. - WHEN preview/test/send errors are returned, THE SYSTEM SHALL return only
sanitized categories and include
request_id. - WHEN preview/test/send errors are returned, THE SYSTEM SHALL NOT include raw payloads, token values, or raw query-string data.
- WHEN wrapper transport executes, THE SYSTEM SHALL enforce max request and response body sizes, strict header allowlist, and bounded retry budget with exponential backoff and jitter.
- WHEN retries are evaluated, THE SYSTEM SHALL retry only on network errors,
429, and5xx; it SHALL NOT retry other4xxresponses.
Technical Specifications
Backend contract
- New module:
backend/internal/notifications/http_wrapper.go - Core types:
HTTPWrapperRequest,RetryPolicy,HTTPWrapperResult,HTTPWrapper - Core functions:
NewNotifyHTTPWrapper,Send,isRetryableStatus,sanitizeOutboundHeaders
Gotify secret contract
- Token accepted only in write path:
POST /api/v1/notifications/providersPUT /api/v1/notifications/providers/:id
- Token stored securely server-side.
- Token never returned in:
- provider reads/lists
- test responses
- preview responses
- Token never shown in:
- validation details
- logs
- debug payload echoes
- Token transport uses header
X-Gotify-Keyonly. - Query token usage is rejected.
SSRF hardening requirements
- HTTPS-only by default.
- Controlled dev override for HTTP (explicit flag, default-off).
- Redirect policy:
- deny redirects by default, or
- if enabled, re-validate each redirect hop before follow.
- Address range blocking includes:
- loopback
- link-local
- private RFC1918
- multicast
- IPv6 ULA
- other internal/non-routable ranges used by current SSRF guard.
- DNS rebinding mitigation:
- resolve before request
- re-resolve before connect/use
- reject when resolved destination shifts into blocked space.
- Wrapper dispatch method for Gotify/Webhook remains
POSTonly.
Error leakage controls
- Preview/Test/Send errors return:
errorcodecategory(sanitized)request_id
- Forbidden in error payloads/logs:
- raw request payloads
- tokens/auth headers
- full query strings containing secrets
- raw upstream response dumps that can leak sensitive fields.
Wrapper transport safety
- Request body max: 256 KiB.
- Response body max: 1 MiB.
- Strict outbound header allowlist:
Content-TypeUser-AgentX-Request-IDX-Gotify-Key- explicitly allowlisted custom headers only.
- Retry budget:
- max attempts: 3
- exponential backoff + jitter
- retry on network error,
429,5xx - no retry on other
4xx.
API Behavior by Mode
gotify
- Required:
type,url, valid payload withmessage. - Token accepted only on create/update writes.
- Outbound auth via
X-Gotify-Keyheader. - Query-token requests are rejected.
webhook
- Required:
type,url, valid renderable template. - Outbound dispatch through wrapper (
POSTJSON) with strict header controls.
discord
- Existing behavior remains unchanged for this migration.
Frontend Design
frontend/src/api/notifications.ts- supports
discord,gotify,webhook - submits token only on create/update writes
- never expects token in read/test/preview payloads
- supports
frontend/src/pages/Notifications.tsx- conditional provider fields
- masked Gotify token input
- no token re-display in readback views
frontend/src/pages/__tests__/Notifications.test.tsx- update discord-only assumptions
- add redaction checks
Test Matrix Expansion
Playwright E2E
- Update:
tests/settings/notifications.spec.ts - Add:
tests/settings/notifications-payload.spec.ts
Required scenarios:
- Redirect-to-internal SSRF attempt is blocked.
- DNS rebinding simulation is blocked (unit/integration + E2E observable path).
- Retry policy verification:
- retry on
429and5xx - no retry on non-
4294xx.
- retry on
- Token redaction checks across API/log/UI surfaces.
- Query-token rejection.
- Oversized payload rejection.
- Discord regression coverage.
Backend Unit/Integration
- Update/add:
backend/internal/services/notification_service_json_test.gobackend/internal/services/notification_service_test.gobackend/internal/services/enhanced_security_notification_service_test.gobackend/internal/api/handlers/notification_provider_handler_test.gobackend/internal/api/handlers/notification_provider_handler_validation_test.go
- Add integration file:
backend/integration/notification_http_wrapper_integration_test.go
Mandatory assertions:
- redirect-hop SSRF blocking
- DNS rebinding mitigation
- retry/non-retry classification
- token redaction in API/log/UI
- query-token rejection
- oversized payload rejection
Implementation Plan
Phase 1 — Backend safety foundation
- implement wrapper contract
- implement secret contract + SSRF/error/transport controls
- keep frontend unchanged
Exit criteria:
- backend tests green
- no Discord regression in backend paths
Phase 2 — Frontend enablement
- enable Gotify/Webhook UI/client paths
- enforce token write-only UX semantics
Exit criteria:
- frontend tests green
- accessibility and form behavior validated
Phase 3 — E2E and coverage hardening
- add expanded matrix scenarios
- enforce DoD sequence and patch-report artifacts
Exit criteria:
- E2E matrix passing
test-results/local-patch-report.mdgeneratedtest-results/local-patch-report.jsongenerated
PR Slicing Strategy
Decision: Multiple PRs for security and rollback safety.
Schema migration decision
- Decision: no schema migration in
PR-1. - Contingency: if schema changes become necessary, create separate
PR-0for migration-only changes beforePR-1.
PR-1 — Backend wrapper + safety controls
Scope:
- wrapper module + service/handler integration
- secret contract + SSRF + leakage + transport controls
- unit/integration tests
Mandatory rollout safety:
- feature flags for Gotify/Webhook dispatch are default
OFFin PR-1.
Validation gates:
- backend tests pass
- no token leakage in API/log/error flows
- no Discord regression
PR-2 — Frontend provider UX
Scope:
- API client and Notifications page updates
- frontend tests for mode handling and redaction
Dependencies: PR-1 merged.
Validation gates:
- frontend tests pass
- accessibility checks pass
PR-3 — Playwright matrix and coverage hardening
Scope:
- notifications E2E matrix expansion
- fixture updates as required
Dependencies: PR-1 and PR-2 merged.
Validation gates:
- security matrix scenarios pass
- patch-report artifacts generated
Risks and Mitigations
- Risk: secret leakage via error/log paths.
- Mitigation: mandatory redaction and sanitized-category responses.
- Risk: SSRF bypass via redirects/rebinding.
- Mitigation: default redirect deny + per-hop re-validation + re-resolution.
- Risk: retry storms or payload abuse.
- Mitigation: capped retries, exponential backoff+jitter, size caps.
- Risk: Discord regression.
- Mitigation: preserved behavior, regression tests, default-off new flags.
Acceptance Criteria (Definition of Done)
docs/plans/current_spec.mdcontains one active Notify migration plan only.- Gotify token contract is explicit: write-path only, secure storage, zero read/test/preview return.
- SSRF hardening includes HTTPS default, redirect controls, blocked ranges, rebinding checks, and POST-only wrapper method.
- Preview/test error details are sanitized with
request_idand no raw payload/token/query leakage. - Transport safety includes body size limits, strict header allowlist, and bounded retry/backoff+jitter policy.
- Test matrix includes redirect-to-internal SSRF, rebinding simulation, retry split, redaction checks, query-token rejection, oversized-payload rejection.
- PR slicing includes PR-1 default-off flags and explicit schema decision.
- No conflicting language remains.
- Status remains: Ready for Supervisor Review.
Supervisor Handoff
Ready for Supervisor review.
GAS Warning Remediation Plan — Missing Code Scanning Configurations (2026-02-24)
Status: Planned (ready for implementation PR) Issue: GitHub Advanced Security warning on PRs:
Code scanning cannot determine alerts introduced by this PR because 3 configurations present on refs/heads/development were not found:
trivy-nightly (nightly-build.yml),.github/workflows/docker-build.yml:build-and-push,.github/workflows/docker-publish.yml:build-and-push.
1) Root Cause Summary
Research outcome from current workflow state and history:
.github/workflows/docker-publish.ymlwas deleted in commitf640524baaf9770aa49f6bd01c5bde04cd50526c(2025-12-21), but historical code-scanning configuration identity from that workflow (.github/workflows/docker-publish.yml:build-and-push) still exists in baseline comparisons.- Both legacy
docker-publish.ymland currentdocker-build.ymlused job idbuild-and-pushand uploaded Trivy SARIF only for non-PR events (push/scheduled paths), so PR branches often do not produce configuration parity. .github/workflows/nightly-build.ymluploads SARIF with explicit categorytrivy-nightly, but this workflow is schedule/manual only, so PR branches do not emittrivy-nightly.- Current PR scanning in
docker-build.ymlusesscan-pr-imagewith categorydocker-pr-image, which does not satisfy parity for legacy/base configuration identities. - Result: GitHub cannot compute “introduced by this PR” for those 3 baseline configurations because matching configurations are absent in PR analysis runs.
2) Minimal-Risk Remediation Strategy (Future-PR Safe)
Decision: keep existing security scans and add compatibility SARIF uploads in PR context, without changing branch/release behavior.
Why this is minimal risk:
- No changes to image build semantics, release tags, or nightly promotion flow.
- Reuses already-generated SARIF files (no new scanner runtime dependency).
- Limited to additive upload steps and explicit categories.
- Provides immediate parity for PRs while allowing controlled cleanup of legacy configuration.
3) Exact Workflow Edits to Apply
A. .github/workflows/docker-build.yml
In job scan-pr-image, after existing Upload Trivy scan results step:
- Add compatibility upload step reusing
trivy-pr-results.sarifwith category:
.github/workflows/docker-build.yml:build-and-push
- Add compatibility alias upload step reusing
trivy-pr-results.sarifwith category:
trivy-nightly
- Add temporary legacy compatibility upload step reusing
trivy-pr-results.sarifwith category:
.github/workflows/docker-publish.yml:build-and-push
Implementation notes:
- Keep existing
docker-pr-imagecategory upload unchanged. - Add SARIF file existence guards before each compatibility upload (for example, conditional check that
trivy-pr-results.sarifexists) to avoid spurious step failures. - Keep compatibility upload steps non-blocking with
continue-on-error: true; useif: always()plus existence guard so upload attempts are resilient but quiet when SARIF is absent. - Add TODO/date marker in step name/description indicating temporary status for
docker-publishalias and planned removal checkpoint.
B. Mandatory category hardening (same PR)
In docker-build.yml non-PR Trivy upload, explicitly set category to .github/workflows/docker-build.yml:build-and-push.
- Requirement level: mandatory (not optional).
- Purpose: make identity explicit and stable even if future upload defaults change.
- Safe because it aligns with currently reported baseline identity.
4) Migration/Cleanup for Legacy docker-publish Configuration
Planned two-stage cleanup:
- Stabilization window (concrete trigger):
- Keep compatibility upload for
.github/workflows/docker-publish.yml:build-and-pushenabled. - Keep temporary alias active through 2026-03-24 and until at least 8 merged PRs with successful
scan-pr-imageruns are observed (both conditions required). - Verify warning is gone across representative PRs.
- Retirement window:
- Remove compatibility step for
docker-publishcategory fromdocker-build.yml. - In GitHub UI/API, close/dismiss remaining alerts tied only to legacy configuration if they persist and are no longer actionable.
- Confirm new PRs still show introduced-alert computation without warnings.
5) Validation Steps (Expected Workflow Observations)
For at least two PRs (one normal feature PR and one workflow-only PR), verify:
docker-build.ymlrunsscan-pr-imageand uploads SARIF under:
docker-pr-image.github/workflows/docker-build.yml:build-and-pushtrivy-nightly.github/workflows/docker-publish.yml:build-and-push(temporary)
- PR Security tab no longer shows:
- “Code scanning cannot determine alerts introduced by this PR because ... configurations ... were not found”.
- No regression:
- Existing Trivy PR blocking behavior remains intact.
- Main/development/nightly push flows continue unchanged.
6) Rollback Notes
If compatibility uploads create noise, duplicate alert confusion, or unstable checks:
- Revert only the newly added compatibility upload steps (keep original uploads).
- Re-run workflows on a test PR and confirm baseline behavior restored.
- If warning reappears, switch to fallback strategy:
- Keep only
.github/workflows/docker-build.yml:build-and-pushcompatibility upload. - Remove
trivy-nightlyalias and handle nightly parity via separate dedicated PR-safe workflow.
7) PR Slicing Strategy for This Fix
- PR-1 (recommended single PR, low-risk additive): add compatibility SARIF uploads in
docker-build.yml(scan-pr-image) with SARIF existence guards,continue-on-erroron compatibility uploads, and mandatory non-PR category hardening, plus brief inline rationale comments. - PR-2 (cleanup PR, delayed): remove
.github/workflows/docker-publish.yml:build-and-pushcompatibility upload after stabilization window and verify no warning recurrence.