Skip to content

Commit 9c4d1bf

Browse files
KTibowdskvrclaudedependabot[bot]
authored
use default import for dompurify instead of namespace import (#908)
* use default import for dompurify instead of namespace import * Add RelayMon trusted relay assertion publishing (#923) * Expose RelayMon trust observations as Nostr assertions RelayMon can now opt into kind 30385 Trusted Relay Assertion publication using the trustedrelays draft NIP shape. The worker records local observation history, derives reliability, quality, accessibility, score, confidence, and metadata tags from existing check results, and only republishes when material state changes or the refresh interval elapses. The change also adds config defaults, docs and docker-stack examples, database persistence for assertion state, unit coverage for the kind 30385 builder/material-change logic, and small validation repairs for current nostr-tools key handling, the kind1066 operational-status tag, and a date-sensitive SSL fixture. Constraint: TRA draft kind 30385 comes from Letdown2491/trustedrelays and uses replaceable d/status/score/component tags Constraint: RelayMon's current test and compile contract uses Deno --no-check tasks Rejected: Publish kind 30385 on every relay check | would spam replaceable assertions without material-change gating Rejected: Add a runtime dependency on the trustedrelays implementation | no new dependencies requested and RelayMon already has the observation data needed for local scoring Confidence: high Scope-risk: moderate Directive: Keep TRA publishing opt-in and preserve material-change gating before changing defaults Tested: deno task test:kind30385; deno task test:unit; deno task test; deno task compile; docker build -f apps/relaymon/.docker/Dockerfile -t nostrwatch/relaymon:tra-test . Not-tested: Strict deno type checking; repo has existing strict-check debt and RelayMon tasks use --no-check * Keep docs lint focused on checked-in docs Docs Lint requires package README anchor headings, so the Docker stacks README now exposes the required Installation section before Quick Start. Link Check was also failing on package README links into .planning, which is not part of the CI checkout or shipped docs surface, so lychee now ignores those local planning artifact URIs. Constraint: Docs Lint enforces package README heading anchors and lychee checks package/docs links on pull_request Rejected: Add .planning/codebase/CONCERNS.md for CI | planning artifacts are not part of the checked-in docs surface Confidence: high Scope-risk: narrow Tested: ./node_modules/.bin/markdownlint-cli2 'libraries/*/README.md' 'apps/*/README.md' 'internal/*/README.md'; docker run --rm -v "/tmp/nostr-watch-tra":/input -w /input lycheeverse/lychee:latest --cache --max-cache-age 1d 'libraries/*/README.md' 'apps/*/README.md' 'internal/*/README.md' 'docs/**/*.md' Not-tested: Full GitHub Actions matrix before push * Keep RelayMon TRA assertions monitor-local RelayMon should publish measured Trusted Relay Assertions without becoming a social trust aggregator. This keeps kind 30385 on the existing monitor identity, records detailed TRA history only when TRA publishing is enabled, and scores assertions from local observations plus current RelayMon probe data. Constraint: kind 30385 must use the same RelayMon monitor key as NIP-66 events Constraint: monitors that do not publish TRA must not write detailed TRA observation history Rejected: Add a providerKey for TRA signing | user clarified the monitor key is the provider identity Rejected: Ingest kind 1985 or manage kind 10385 in RelayMon | social trust aggregation and provider discovery belong outside RelayMon Confidence: high Scope-risk: moderate Directive: Keep RelayMon TRA scoring limited to this monitor's observations unless RelayMon explicitly becomes a social trust aggregator Tested: deno task test:config-types Tested: deno task test:database Tested: deno task test:kind30385 Tested: deno task test:unit Tested: deno task compile Tested: git diff --check Tested: deno lint with existing noisy rules excluded on changed TypeScript files Not-tested: full deno check remains blocked by existing project-wide strict type errors --------- Co-authored-by: sandwich <dskvr@users.noreply.github.com> * Allow RelayMon TRA publication during warmup (#925) Fisherman can spend a long time in warmup while it drains unchecked relays. The worker previously suppressed trusted relay assertions in warmup, so enabled TRA publishing did not record observation history or enqueue kind 30385 events until warmup ended. Keep normal NIP-66 check and delta event suppression intact, but let the opt-in TRA path run during warmup. Start health reporting before warmup so external monitors can see the process while the warmup queue is active. Constraint: Fisherman warmup is long enough to keep status stale and prevent any live kind 30385 evidence. Rejected: Wait for warmup to finish | leaves TRA publishing absent for the first long-running pass and does not explain current missing events. Confidence: high Scope-risk: narrow Directive: Do not re-add warmup suppression around publishTrustedRelayAssertion without a separate warmup TRA publisher or observation recorder. Tested: deno test --no-check --unstable-sloppy-imports --allow-net --allow-env --allow-read --allow-write --allow-run tests/unit/worker-tra.test.ts tests/unit/kind30385.test.ts tests/unit/config-types.test.ts Tested: deno fmt --check tests/unit/worker-tra.test.ts src/core/worker.ts Tested: git diff --check -- apps/relaymon/src/core/daemon.ts apps/relaymon/src/core/worker.ts apps/relaymon/tests/unit/worker-tra.test.ts Not-tested: live Fisherman deployment and live relay kind 30385 publication after image rollout Co-authored-by: sandwich <dskvr@users.noreply.github.com> * fix(relaymon): move TRA per-check DB work off the check hot path (#926) * Let remediation deletions publish after warmup Remediation queue draining used the same deletion helper as ordinary NIP-09 publishes, but daemon startup drained that queue while deletion publishing was still suppressed for warmup. That made valid queued deletions return false before any kind:5 event was sent, then remediation logged the skipped local publish as NOT ACK'd and left the row queued. Move the remediation drain until immediately after warmup finishes, where runWarmup's finally block has re-enabled deletion publishing. The queue still retries rows that relays reject or fail to ACK, but valid entries are no longer blocked by local startup suppression. Constraint: Deletion publishing is intentionally suppressed during warmup for normal check processing. Rejected: Bypass suppression inside deleteRelayCheckEvent | that would weaken the warmup guard for every deletion caller. Confidence: high Scope-risk: narrow Directive: Keep remediation queue drain after runWarmup unless deletion.ts gains caller-specific suppression semantics. Tested: deno test --no-check --unstable-sloppy-imports --allow-net --allow-env --allow-read --allow-write --allow-run tests/unit/deletion.test.ts tests/unit/nato-purge.test.ts tests/unit/nostrings-sweep.test.ts Tested: git diff --check -- apps/relaymon/src/core/daemon.ts Not-tested: live Fisherman deployment and relay ACK drain * fix(relaymon): move TRA per-check DB work off the check hot path Trusted Relay Assertion (kind 30385) processing ran inline on every relay check: recordTrustedRelayObservation + buildTrustedRelayAssertion do synchronous SQLite work, including a read-back of up to max_observations_per_relay history rows plus per-sample scoring. On Deno's single-threaded event loop (and a CPU-constrained host) those bursts starved in-flight WebSocket I/O and busted the tight check budgets (read/open timeouts), so healthy relays were falsely reported offline. This is what took Fisherman down once TRA was enabled there. Decouple TRA from the check hot path: - processRelay now calls enqueueTrustedRelayObservation(), a cheap guard + array push (no DB work). The queue is capped (drops oldest, warns) so it can't grow unbounded during warmup. - A lazily-started background processor drains the queue one relay at a time and yields the event loop (delay) between relays, so the synchronous per-relay work never sustains a block long enough to starve checks. Start is deferred via setTimeout so the first item never runs inside the check's call stack. - Throttle is configurable via trustedRelayAssertions.processing_throttle_ms (default 25ms). publishTrustedRelayAssertion (the per-item worker) is unchanged, so its record/build/gate/publish behavior and existing coverage are preserved. Tests: add coverage that enqueue defers work (nothing published synchronously; queue drains via processTrustedRelayQueueOnce), that it is a no-op when TRA is disabled, and that the queue caps memory by dropping oldest. Full unit suite: 369 passed. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com> --------- Co-authored-by: sandwich <dskvr@users.noreply.github.com> Co-authored-by: Claude Opus 4.8 (1M context) <noreply@anthropic.com> * ci: unified Docker image CD (dev + tagged, Docker Hub + GHCR) (#928) Replaces the two ad-hoc, manual-only docker-relaymon.yml / docker-trawler.yml workflows (whose `<pkg>-v*` tag triggers never matched the release's actual `<pkg>@v<version>` tags, so they only ever ran via manual dispatch) with a single matrix-driven workflow covering every package that produces a Docker image: rstate, trawler, relaymon (nocapd excluded — deprecated). Behaviour: - push to `next` (integration branch): rolling `:dev` + `:dev-<sha>` images for ALL matrix packages. - release tag `<pkg>@v<version>`: `:<version>` for that package, plus `:latest` when it is the highest non-prerelease version (computed from git tags). - both paths runnable via workflow_dispatch (mode dev|tagged, package, version, update_latest auto|true|false). - every image pushed to BOTH Docker Hub (nostrwatch/<app>) and GHCR (ghcr.io/sandwichfarm/<app>). The build plan (which images, which tags, latest-or-not) lives in scripts/ci/plan-docker-images.mjs — dependency-free and locally runnable, so the workflow logic is verifiable outside CI. Build contexts match each app's Dockerfile (rstate builds from apps/rstate; trawler/relaymon from repo root). Requires existing secrets DOCKERHUB_USERNAME / DOCKERHUB_TOKEN; GHCR uses the built-in GITHUB_TOKEN (packages: write). Co-authored-by: sandwich <dskvr@users.noreply.github.com> Co-authored-by: Claude Opus 4.8 (1M context) <noreply@anthropic.com> * rstate: publish trusted relay assertions from computed state (#922) * Prevent stale rstate relay assertion cache hits The relay MCP tools had inline cache keys that missed output-affecting inputs as the surface grew into simple, detailed, and full variants. Centralizing cache config keeps registration and tests aligned with the current slash-based tool names, and the software grouping tool now honors the family filter used by REST parity checks. This also fixes the rstate type-check failures that surfaced while validating the PR: scorer code now calls the current observation-store API, stale unused private state was removed, and relay pool connection state is exposed through stats instead of being written-only. Constraint: PR branch had to be rebuilt onto current origin/next because the old trusted-relay-assertions branch was 133 commits behind. Rejected: Push the stale local feature/trusted-relay-assertions branch directly | it would open a noisy outdated PR against next. Confidence: high Scope-risk: narrow Directive: Keep cache config keys aligned with actual MCP tool names, not internal aliases. Tested: cd apps/rstate && ./node_modules/.bin/tsc --noEmit Tested: cd apps/rstate && ./node_modules/.bin/vitest --run test/cache-key-coverage.test.ts test/rest_tool_parity.test.ts test/parity.test.ts (140 passing). Tested: cd apps/rstate && ./node_modules/.bin/tsup after building workspace prerequisites logger, utils, publisher, announce. Not-tested: full workspace recursive lint/build/test. * Publish trusted relay assertions from computed rstate output rstate now owns kind 30385 generation from its aggregate relay state, with a separate trust query surface and no new 1066 ingestion path. The implementation keeps TRA history gated behind publishing enablement and suppresses unchanged publishes unless material state moves or a refresh boundary is reached. Constraint: must not ingest kind 1066 for this slice Rejected: fold trust data into default relay responses | would bloat the existing API surface and complicate cache/tool parity Confidence: high Scope-risk: moderate Directive: keep TRA history writes gated on publishing enablement and do not add social trust ingestion here Tested: npm run test -- --run, npm run type-check, npm run build, git diff --check Not-tested: live relay publish round-trip against a production signer/relay pair --------- Co-authored-by: sandwich <dskvr@users.noreply.github.com> * fix(relaymon): publish each relay deletion once, not on every boot (#932) On startup relaymon re-published a NIP-09 (kind:5) deletion for every ignored relay (~17k), because the "already deleted" guard was an in-memory Set that resets each process. The deletion only needs to be sent once per ignore episode. Persist the guard: - relay_status gains a `deletion_published_at` column (idempotent migration). - deleteRelayCheckEvent skips when the durable marker is set (or the in-memory fast-path is) and stamps it on successful publish — so the once-only guarantee survives restarts. - IgnoreListSync.publishDeletions now only iterates ignored relays that are actually pending (deletion_published_at = 0) instead of all of them. - The marker is cleared whenever a relay becomes unignored (markRelayUnignored, persistResult, dedup re-evaluation), so a later re-ignore republishes exactly once (a prior deletion only covered the earlier 30166). Tests: deletion-publish-once.test.ts covers pending selection, mark/round-trip, cross-boot idempotency, and unignore/persist reset. Full relaymon unit suite: 374 passed. Co-authored-by: sandwich <dskvr@users.noreply.github.com> Co-authored-by: Claude Opus 4.8 (1M context) <noreply@anthropic.com> * chore(trawler): remove committed merge-conflict markers from CHANGELOG (#931) A prior merge committed unresolved conflict markers into apps/trawler/CHANGELOG.md on next. The HEAD side was empty and the other side held the 0.6.0/0.6.1 entries; dropped the markers and kept the entries. Content only — no version/release change. Co-authored-by: sandwich <dskvr@users.noreply.github.com> Co-authored-by: Claude Opus 4.8 (1M context) <noreply@anthropic.com> * fix build failures (#937) * test(quick/260503-kc3): add failing regression tests for remaining XSS vectors - operators.formatters.test.ts: name/about/reference/filterFormatters - monitors.formatters.test.ts: nip05/pubkey/checks/filterFormatters - relays.formatters.test.ts: software/geocode/minPowDifficulty/supportedNips/networks/ipv4/retentionPolicy - DataTableFallback.test.ts: structural regression for {@html row[column.key]} * fix(gui/security): sanitize remaining XSS sinks in dataTable formatters - operators.ts: hex-pubkey guard, escapeHtml on name/about, nostr-id guard on reference, alt="" on imgs, escaped filterFormatters - monitors.ts: hex-pubkey guard, safeImageUrl on photos, escaped name/pubkey/checks/nip05, fixed pastelPair?.dark and class typo - relays.ts: networks slug allowlist, IPv4 strict guard, retentionPolicy try/catch + escape, escaped geocode/supportedNips/minPowDifficulty/software - softwares.ts/isps.ts/geocodes.ts: escaped filterFormatters; geocodes.ts geocode formatter also escaped (additional sink found during audit) - test fixes: tighten 'x"' assertion to 'alert(1)' (avoided false positive on class="flex"); replace fileURLToPath with cwd-walking path resolver * fix(gui/security): bind cell + filter fallbacks as text, not HTML Replace the {@html} fallbacks in 4 components with split-conditional text bindings. Registered formatters keep their {@html ...} branch (output is sanitized as of the previous commit). Unregistered columns now render as plain text — closes the silent regression vector where any column without a formatter would re-introduce XSS. - data-view/table/DataTable.svelte: fallback row[column.key] now text-bound - lists/table/DataTable.svelte: same - lists/table/Filters.svelte: filter button fallback now text-bound - data-view/filters/DataViewFilters.svelte: same * fix build failures --------- Co-authored-by: sandwich <dskvr@users.noreply.github.com> * chore(deps-dev): bump http-proxy-middleware from 3.0.5 to 3.0.7 (#933) Bumps [http-proxy-middleware](https://github.com/chimurai/http-proxy-middleware) from 3.0.5 to 3.0.7. - [Release notes](https://github.com/chimurai/http-proxy-middleware/releases) - [Changelog](https://github.com/chimurai/http-proxy-middleware/blob/v3.0.7/CHANGELOG.md) - [Commits](https://github.com/chimurai/http-proxy-middleware/compare/v3.0.5...v3.0.7) --- updated-dependencies: - dependency-name: http-proxy-middleware dependency-version: 3.0.7 dependency-type: direct:development ... Signed-off-by: dependabot[bot] <support@github.com> Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> * chore(deps): bump dompurify from 3.4.2 to 3.4.11 (#934) Bumps [dompurify](https://github.com/cure53/DOMPurify) from 3.4.2 to 3.4.11. - [Release notes](https://github.com/cure53/DOMPurify/releases) - [Commits](https://github.com/cure53/DOMPurify/compare/3.4.2...3.4.11) --- updated-dependencies: - dependency-name: dompurify dependency-version: 3.4.11 dependency-type: direct:production ... Signed-off-by: dependabot[bot] <support@github.com> Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> Co-authored-by: sandwich <dskvr@users.noreply.github.com> * chore(deps): bump js-yaml from 4.1.1 to 4.2.0 (#935) Bumps [js-yaml](https://github.com/nodeca/js-yaml) from 4.1.1 to 4.2.0. - [Changelog](https://github.com/nodeca/js-yaml/blob/master/CHANGELOG.md) - [Commits](https://github.com/nodeca/js-yaml/compare/4.1.1...4.2.0) --- updated-dependencies: - dependency-name: js-yaml dependency-version: 4.2.0 dependency-type: direct:production ... Signed-off-by: dependabot[bot] <support@github.com> Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> * fix(gui/security): close remaining XSS vectors (#938) * test(quick/260503-kc3): add failing regression tests for remaining XSS vectors - operators.formatters.test.ts: name/about/reference/filterFormatters - monitors.formatters.test.ts: nip05/pubkey/checks/filterFormatters - relays.formatters.test.ts: software/geocode/minPowDifficulty/supportedNips/networks/ipv4/retentionPolicy - DataTableFallback.test.ts: structural regression for {@html row[column.key]} * fix(gui/security): sanitize remaining XSS sinks in dataTable formatters - operators.ts: hex-pubkey guard, escapeHtml on name/about, nostr-id guard on reference, alt="" on imgs, escaped filterFormatters - monitors.ts: hex-pubkey guard, safeImageUrl on photos, escaped name/pubkey/checks/nip05, fixed pastelPair?.dark and class typo - relays.ts: networks slug allowlist, IPv4 strict guard, retentionPolicy try/catch + escape, escaped geocode/supportedNips/minPowDifficulty/software - softwares.ts/isps.ts/geocodes.ts: escaped filterFormatters; geocodes.ts geocode formatter also escaped (additional sink found during audit) - test fixes: tighten 'x"' assertion to 'alert(1)' (avoided false positive on class="flex"); replace fileURLToPath with cwd-walking path resolver * fix(gui/security): bind cell + filter fallbacks as text, not HTML Replace the {@html} fallbacks in 4 components with split-conditional text bindings. Registered formatters keep their {@html ...} branch (output is sanitized as of the previous commit). Unregistered columns now render as plain text — closes the silent regression vector where any column without a formatter would re-introduce XSS. - data-view/table/DataTable.svelte: fallback row[column.key] now text-bound - lists/table/DataTable.svelte: same - lists/table/Filters.svelte: filter button fallback now text-bound - data-view/filters/DataViewFilters.svelte: same --------- Co-authored-by: sandwich <dskvr@users.noreply.github.com> * chore(deps-dev): bump vitest from 3.1.1 to 3.2.6 in /libraries/auditor (#920) * chore(deps-dev): bump vitest from 3.1.1 to 3.2.6 in /libraries/auditor Bumps [vitest](https://github.com/vitest-dev/vitest/tree/HEAD/packages/vitest) from 3.1.1 to 3.2.6. - [Release notes](https://github.com/vitest-dev/vitest/releases) - [Changelog](https://github.com/vitest-dev/vitest/blob/main/docs/releases.md) - [Commits](https://github.com/vitest-dev/vitest/commits/v3.2.6/packages/vitest) --- updated-dependencies: - dependency-name: vitest dependency-version: 3.2.6 dependency-type: direct:development ... Signed-off-by: dependabot[bot] <support@github.com> * Add RelayMon trusted relay assertion publishing (#923) * Expose RelayMon trust observations as Nostr assertions RelayMon can now opt into kind 30385 Trusted Relay Assertion publication using the trustedrelays draft NIP shape. The worker records local observation history, derives reliability, quality, accessibility, score, confidence, and metadata tags from existing check results, and only republishes when material state changes or the refresh interval elapses. The change also adds config defaults, docs and docker-stack examples, database persistence for assertion state, unit coverage for the kind 30385 builder/material-change logic, and small validation repairs for current nostr-tools key handling, the kind1066 operational-status tag, and a date-sensitive SSL fixture. Constraint: TRA draft kind 30385 comes from Letdown2491/trustedrelays and uses replaceable d/status/score/component tags Constraint: RelayMon's current test and compile contract uses Deno --no-check tasks Rejected: Publish kind 30385 on every relay check | would spam replaceable assertions without material-change gating Rejected: Add a runtime dependency on the trustedrelays implementation | no new dependencies requested and RelayMon already has the observation data needed for local scoring Confidence: high Scope-risk: moderate Directive: Keep TRA publishing opt-in and preserve material-change gating before changing defaults Tested: deno task test:kind30385; deno task test:unit; deno task test; deno task compile; docker build -f apps/relaymon/.docker/Dockerfile -t nostrwatch/relaymon:tra-test . Not-tested: Strict deno type checking; repo has existing strict-check debt and RelayMon tasks use --no-check * Keep docs lint focused on checked-in docs Docs Lint requires package README anchor headings, so the Docker stacks README now exposes the required Installation section before Quick Start. Link Check was also failing on package README links into .planning, which is not part of the CI checkout or shipped docs surface, so lychee now ignores those local planning artifact URIs. Constraint: Docs Lint enforces package README heading anchors and lychee checks package/docs links on pull_request Rejected: Add .planning/codebase/CONCERNS.md for CI | planning artifacts are not part of the checked-in docs surface Confidence: high Scope-risk: narrow Tested: ./node_modules/.bin/markdownlint-cli2 'libraries/*/README.md' 'apps/*/README.md' 'internal/*/README.md'; docker run --rm -v "/tmp/nostr-watch-tra":/input -w /input lycheeverse/lychee:latest --cache --max-cache-age 1d 'libraries/*/README.md' 'apps/*/README.md' 'internal/*/README.md' 'docs/**/*.md' Not-tested: Full GitHub Actions matrix before push * Keep RelayMon TRA assertions monitor-local RelayMon should publish measured Trusted Relay Assertions without becoming a social trust aggregator. This keeps kind 30385 on the existing monitor identity, records detailed TRA history only when TRA publishing is enabled, and scores assertions from local observations plus current RelayMon probe data. Constraint: kind 30385 must use the same RelayMon monitor key as NIP-66 events Constraint: monitors that do not publish TRA must not write detailed TRA observation history Rejected: Add a providerKey for TRA signing | user clarified the monitor key is the provider identity Rejected: Ingest kind 1985 or manage kind 10385 in RelayMon | social trust aggregation and provider discovery belong outside RelayMon Confidence: high Scope-risk: moderate Directive: Keep RelayMon TRA scoring limited to this monitor's observations unless RelayMon explicitly becomes a social trust aggregator Tested: deno task test:config-types Tested: deno task test:database Tested: deno task test:kind30385 Tested: deno task test:unit Tested: deno task compile Tested: git diff --check Tested: deno lint with existing noisy rules excluded on changed TypeScript files Not-tested: full deno check remains blocked by existing project-wide strict type errors --------- Co-authored-by: sandwich <dskvr@users.noreply.github.com> * Allow RelayMon TRA publication during warmup (#925) Fisherman can spend a long time in warmup while it drains unchecked relays. The worker previously suppressed trusted relay assertions in warmup, so enabled TRA publishing did not record observation history or enqueue kind 30385 events until warmup ended. Keep normal NIP-66 check and delta event suppression intact, but let the opt-in TRA path run during warmup. Start health reporting before warmup so external monitors can see the process while the warmup queue is active. Constraint: Fisherman warmup is long enough to keep status stale and prevent any live kind 30385 evidence. Rejected: Wait for warmup to finish | leaves TRA publishing absent for the first long-running pass and does not explain current missing events. Confidence: high Scope-risk: narrow Directive: Do not re-add warmup suppression around publishTrustedRelayAssertion without a separate warmup TRA publisher or observation recorder. Tested: deno test --no-check --unstable-sloppy-imports --allow-net --allow-env --allow-read --allow-write --allow-run tests/unit/worker-tra.test.ts tests/unit/kind30385.test.ts tests/unit/config-types.test.ts Tested: deno fmt --check tests/unit/worker-tra.test.ts src/core/worker.ts Tested: git diff --check -- apps/relaymon/src/core/daemon.ts apps/relaymon/src/core/worker.ts apps/relaymon/tests/unit/worker-tra.test.ts Not-tested: live Fisherman deployment and live relay kind 30385 publication after image rollout Co-authored-by: sandwich <dskvr@users.noreply.github.com> * fix(relaymon): move TRA per-check DB work off the check hot path (#926) * Let remediation deletions publish after warmup Remediation queue draining used the same deletion helper as ordinary NIP-09 publishes, but daemon startup drained that queue while deletion publishing was still suppressed for warmup. That made valid queued deletions return false before any kind:5 event was sent, then remediation logged the skipped local publish as NOT ACK'd and left the row queued. Move the remediation drain until immediately after warmup finishes, where runWarmup's finally block has re-enabled deletion publishing. The queue still retries rows that relays reject or fail to ACK, but valid entries are no longer blocked by local startup suppression. Constraint: Deletion publishing is intentionally suppressed during warmup for normal check processing. Rejected: Bypass suppression inside deleteRelayCheckEvent | that would weaken the warmup guard for every deletion caller. Confidence: high Scope-risk: narrow Directive: Keep remediation queue drain after runWarmup unless deletion.ts gains caller-specific suppression semantics. Tested: deno test --no-check --unstable-sloppy-imports --allow-net --allow-env --allow-read --allow-write --allow-run tests/unit/deletion.test.ts tests/unit/nato-purge.test.ts tests/unit/nostrings-sweep.test.ts Tested: git diff --check -- apps/relaymon/src/core/daemon.ts Not-tested: live Fisherman deployment and relay ACK drain * fix(relaymon): move TRA per-check DB work off the check hot path Trusted Relay Assertion (kind 30385) processing ran inline on every relay check: recordTrustedRelayObservation + buildTrustedRelayAssertion do synchronous SQLite work, including a read-back of up to max_observations_per_relay history rows plus per-sample scoring. On Deno's single-threaded event loop (and a CPU-constrained host) those bursts starved in-flight WebSocket I/O and busted the tight check budgets (read/open timeouts), so healthy relays were falsely reported offline. This is what took Fisherman down once TRA was enabled there. Decouple TRA from the check hot path: - processRelay now calls enqueueTrustedRelayObservation(), a cheap guard + array push (no DB work). The queue is capped (drops oldest, warns) so it can't grow unbounded during warmup. - A lazily-started background processor drains the queue one relay at a time and yields the event loop (delay) between relays, so the synchronous per-relay work never sustains a block long enough to starve checks. Start is deferred via setTimeout so the first item never runs inside the check's call stack. - Throttle is configurable via trustedRelayAssertions.processing_throttle_ms (default 25ms). publishTrustedRelayAssertion (the per-item worker) is unchanged, so its record/build/gate/publish behavior and existing coverage are preserved. Tests: add coverage that enqueue defers work (nothing published synchronously; queue drains via processTrustedRelayQueueOnce), that it is a no-op when TRA is disabled, and that the queue caps memory by dropping oldest. Full unit suite: 369 passed. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com> --------- Co-authored-by: sandwich <dskvr@users.noreply.github.com> Co-authored-by: Claude Opus 4.8 (1M context) <noreply@anthropic.com> * ci: unified Docker image CD (dev + tagged, Docker Hub + GHCR) (#928) Replaces the two ad-hoc, manual-only docker-relaymon.yml / docker-trawler.yml workflows (whose `<pkg>-v*` tag triggers never matched the release's actual `<pkg>@v<version>` tags, so they only ever ran via manual dispatch) with a single matrix-driven workflow covering every package that produces a Docker image: rstate, trawler, relaymon (nocapd excluded — deprecated). Behaviour: - push to `next` (integration branch): rolling `:dev` + `:dev-<sha>` images for ALL matrix packages. - release tag `<pkg>@v<version>`: `:<version>` for that package, plus `:latest` when it is the highest non-prerelease version (computed from git tags). - both paths runnable via workflow_dispatch (mode dev|tagged, package, version, update_latest auto|true|false). - every image pushed to BOTH Docker Hub (nostrwatch/<app>) and GHCR (ghcr.io/sandwichfarm/<app>). The build plan (which images, which tags, latest-or-not) lives in scripts/ci/plan-docker-images.mjs — dependency-free and locally runnable, so the workflow logic is verifiable outside CI. Build contexts match each app's Dockerfile (rstate builds from apps/rstate; trawler/relaymon from repo root). Requires existing secrets DOCKERHUB_USERNAME / DOCKERHUB_TOKEN; GHCR uses the built-in GITHUB_TOKEN (packages: write). Co-authored-by: sandwich <dskvr@users.noreply.github.com> Co-authored-by: Claude Opus 4.8 (1M context) <noreply@anthropic.com> * rstate: publish trusted relay assertions from computed state (#922) * Prevent stale rstate relay assertion cache hits The relay MCP tools had inline cache keys that missed output-affecting inputs as the surface grew into simple, detailed, and full variants. Centralizing cache config keeps registration and tests aligned with the current slash-based tool names, and the software grouping tool now honors the family filter used by REST parity checks. This also fixes the rstate type-check failures that surfaced while validating the PR: scorer code now calls the current observation-store API, stale unused private state was removed, and relay pool connection state is exposed through stats instead of being written-only. Constraint: PR branch had to be rebuilt onto current origin/next because the old trusted-relay-assertions branch was 133 commits behind. Rejected: Push the stale local feature/trusted-relay-assertions branch directly | it would open a noisy outdated PR against next. Confidence: high Scope-risk: narrow Directive: Keep cache config keys aligned with actual MCP tool names, not internal aliases. Tested: cd apps/rstate && ./node_modules/.bin/tsc --noEmit Tested: cd apps/rstate && ./node_modules/.bin/vitest --run test/cache-key-coverage.test.ts test/rest_tool_parity.test.ts test/parity.test.ts (140 passing). Tested: cd apps/rstate && ./node_modules/.bin/tsup after building workspace prerequisites logger, utils, publisher, announce. Not-tested: full workspace recursive lint/build/test. * Publish trusted relay assertions from computed rstate output rstate now owns kind 30385 generation from its aggregate relay state, with a separate trust query surface and no new 1066 ingestion path. The implementation keeps TRA history gated behind publishing enablement and suppresses unchanged publishes unless material state moves or a refresh boundary is reached. Constraint: must not ingest kind 1066 for this slice Rejected: fold trust data into default relay responses | would bloat the existing API surface and complicate cache/tool parity Confidence: high Scope-risk: moderate Directive: keep TRA history writes gated on publishing enablement and do not add social trust ingestion here Tested: npm run test -- --run, npm run type-check, npm run build, git diff --check Not-tested: live relay publish round-trip against a production signer/relay pair --------- Co-authored-by: sandwich <dskvr@users.noreply.github.com> * fix(relaymon): publish each relay deletion once, not on every boot (#932) On startup relaymon re-published a NIP-09 (kind:5) deletion for every ignored relay (~17k), because the "already deleted" guard was an in-memory Set that resets each process. The deletion only needs to be sent once per ignore episode. Persist the guard: - relay_status gains a `deletion_published_at` column (idempotent migration). - deleteRelayCheckEvent skips when the durable marker is set (or the in-memory fast-path is) and stamps it on successful publish — so the once-only guarantee survives restarts. - IgnoreListSync.publishDeletions now only iterates ignored relays that are actually pending (deletion_published_at = 0) instead of all of them. - The marker is cleared whenever a relay becomes unignored (markRelayUnignored, persistResult, dedup re-evaluation), so a later re-ignore republishes exactly once (a prior deletion only covered the earlier 30166). Tests: deletion-publish-once.test.ts covers pending selection, mark/round-trip, cross-boot idempotency, and unignore/persist reset. Full relaymon unit suite: 374 passed. Co-authored-by: sandwich <dskvr@users.noreply.github.com> Co-authored-by: Claude Opus 4.8 (1M context) <noreply@anthropic.com> * chore(trawler): remove committed merge-conflict markers from CHANGELOG (#931) A prior merge committed unresolved conflict markers into apps/trawler/CHANGELOG.md on next. The HEAD side was empty and the other side held the 0.6.0/0.6.1 entries; dropped the markers and kept the entries. Content only — no version/release change. Co-authored-by: sandwich <dskvr@users.noreply.github.com> Co-authored-by: Claude Opus 4.8 (1M context) <noreply@anthropic.com> * fix build failures (#937) * test(quick/260503-kc3): add failing regression tests for remaining XSS vectors - operators.formatters.test.ts: name/about/reference/filterFormatters - monitors.formatters.test.ts: nip05/pubkey/checks/filterFormatters - relays.formatters.test.ts: software/geocode/minPowDifficulty/supportedNips/networks/ipv4/retentionPolicy - DataTableFallback.test.ts: structural regression for {@html row[column.key]} * fix(gui/security): sanitize remaining XSS sinks in dataTable formatters - operators.ts: hex-pubkey guard, escapeHtml on name/about, nostr-id guard on reference, alt="" on imgs, escaped filterFormatters - monitors.ts: hex-pubkey guard, safeImageUrl on photos, escaped name/pubkey/checks/nip05, fixed pastelPair?.dark and class typo - relays.ts: networks slug allowlist, IPv4 strict guard, retentionPolicy try/catch + escape, escaped geocode/supportedNips/minPowDifficulty/software - softwares.ts/isps.ts/geocodes.ts: escaped filterFormatters; geocodes.ts geocode formatter also escaped (additional sink found during audit) - test fixes: tighten 'x"' assertion to 'alert(1)' (avoided false positive on class="flex"); replace fileURLToPath with cwd-walking path resolver * fix(gui/security): bind cell + filter fallbacks as text, not HTML Replace the {@html} fallbacks in 4 components with split-conditional text bindings. Registered formatters keep their {@html ...} branch (output is sanitized as of the previous commit). Unregistered columns now render as plain text — closes the silent regression vector where any column without a formatter would re-introduce XSS. - data-view/table/DataTable.svelte: fallback row[column.key] now text-bound - lists/table/DataTable.svelte: same - lists/table/Filters.svelte: filter button fallback now text-bound - data-view/filters/DataViewFilters.svelte: same * fix build failures --------- Co-authored-by: sandwich <dskvr@users.noreply.github.com> * chore(deps-dev): bump http-proxy-middleware from 3.0.5 to 3.0.7 (#933) Bumps [http-proxy-middleware](https://github.com/chimurai/http-proxy-middleware) from 3.0.5 to 3.0.7. - [Release notes](https://github.com/chimurai/http-proxy-middleware/releases) - [Changelog](https://github.com/chimurai/http-proxy-middleware/blob/v3.0.7/CHANGELOG.md) - [Commits](https://github.com/chimurai/http-proxy-middleware/compare/v3.0.5...v3.0.7) --- updated-dependencies: - dependency-name: http-proxy-middleware dependency-version: 3.0.7 dependency-type: direct:development ... Signed-off-by: dependabot[bot] <support@github.com> Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> * chore(deps): bump dompurify from 3.4.2 to 3.4.11 (#934) Bumps [dompurify](https://github.com/cure53/DOMPurify) from 3.4.2 to 3.4.11. - [Release notes](https://github.com/cure53/DOMPurify/releases) - [Commits](https://github.com/cure53/DOMPurify/compare/3.4.2...3.4.11) --- updated-dependencies: - dependency-name: dompurify dependency-version: 3.4.11 dependency-type: direct:production ... Signed-off-by: dependabot[bot] <support@github.com> Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> Co-authored-by: sandwich <dskvr@users.noreply.github.com> * chore(deps): bump js-yaml from 4.1.1 to 4.2.0 (#935) Bumps [js-yaml](https://github.com/nodeca/js-yaml) from 4.1.1 to 4.2.0. - [Changelog](https://github.com/nodeca/js-yaml/blob/master/CHANGELOG.md) - [Commits](https://github.com/nodeca/js-yaml/compare/4.1.1...4.2.0) --- updated-dependencies: - dependency-name: js-yaml dependency-version: 4.2.0 dependency-type: direct:production ... Signed-off-by: dependabot[bot] <support@github.com> Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> --------- Signed-off-by: dependabot[bot] <support@github.com> Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> Co-authored-by: sandwich <299465+dskvr@users.noreply.github.com> Co-authored-by: sandwich <dskvr@users.noreply.github.com> Co-authored-by: Claude Opus 4.8 (1M context) <noreply@anthropic.com> * chore(deps-dev): bump vitest from 3.1.2 to 3.2.6 in /libraries/schemata-js-ajv (#921) * chore(deps-dev): bump vitest in /libraries/schemata-js-ajv Bumps [vitest](https://github.com/vitest-dev/vitest/tree/HEAD/packages/vitest) from 3.1.2 to 3.2.6. - [Release notes](https://github.com/vitest-dev/vitest/releases) - [Changelog](https://github.com/vitest-dev/vitest/blob/main/docs/releases.md) - [Commits](https://github.com/vitest-dev/vitest/commits/v3.2.6/packages/vitest) --- updated-dependencies: - dependency-name: vitest dependency-version: 3.2.6 dependency-type: direct:development ... Signed-off-by: dependabot[bot] <support@github.com> * Add RelayMon trusted relay assertion publishing (#923) * Expose RelayMon trust observations as Nostr assertions RelayMon can now opt into kind 30385 Trusted Relay Assertion publication using the trustedrelays draft NIP shape. The worker records local observation history, derives reliability, quality, accessibility, score, confidence, and metadata tags from existing check results, and only republishes when material state changes or the refresh interval elapses. The change also adds config defaults, docs and docker-stack examples, database persistence for assertion state, unit coverage for the kind 30385 builder/material-change logic, and small validation repairs for current nostr-tools key handling, the kind1066 operational-status tag, and a date-sensitive SSL fixture. Constraint: TRA draft kind 30385 comes from Letdown2491/trustedrelays and uses replaceable d/status/score/component tags Constraint: RelayMon's current test and compile contract uses Deno --no-check tasks Rejected: Publish kind 30385 on every relay check | would spam replaceable assertions without material-change gating Rejected: Add a runtime dependency on the trustedrelays implementation | no new dependencies requested and RelayMon already has the observation data needed for local scoring Confidence: high Scope-risk: moderate Directive: Keep TRA publishing opt-in and preserve material-change gating before changing defaults Tested: deno task test:kind30385; deno task test:unit; deno task test; deno task compile; docker build -f apps/relaymon/.docker/Dockerfile -t nostrwatch/relaymon:tra-test . Not-tested: Strict deno type checking; repo has existing strict-check debt and RelayMon tasks use --no-check * Keep docs lint focused on checked-in docs Docs Lint requires package README anchor headings, so the Docker stacks README now exposes the required Installation section before Quick Start. Link Check was also failing on package README links into .planning, which is not part of the CI checkout or shipped docs surface, so lychee now ignores those local planning artifact URIs. Constraint: Docs Lint enforces package README heading anchors and lychee checks package/docs links on pull_request Rejected: Add .planning/codebase/CONCERNS.md for CI | planning artifacts are not part of the checked-in docs surface Confidence: high Scope-risk: narrow Tested: ./node_modules/.bin/markdownlint-cli2 'libraries/*/README.md' 'apps/*/README.md' 'internal/*/README.md'; docker run --rm -v "/tmp/nostr-watch-tra":/input -w /input lycheeverse/lychee:latest --cache --max-cache-age 1d 'libraries/*/README.md' 'apps/*/README.md' 'internal/*/README.md' 'docs/**/*.md' Not-tested: Full GitHub Actions matrix before push * Keep RelayMon TRA assertions monitor-local RelayMon should publish measured Trusted Relay Assertions without becoming a social trust aggregator. This keeps kind 30385 on the existing monitor identity, records detailed TRA history only when TRA publishing is enabled, and scores assertions from local observations plus current RelayMon probe data. Constraint: kind 30385 must use the same RelayMon monitor key as NIP-66 events Constraint: monitors that do not publish TRA must not write detailed TRA observation history Rejected: Add a providerKey for TRA signing | user clarified the monitor key is the provider identity Rejected: Ingest kind 1985 or manage kind 10385 in RelayMon | social trust aggregation and provider discovery belong outside RelayMon Confidence: high Scope-risk: moderate Directive: Keep RelayMon TRA scoring limited to this monitor's observations unless RelayMon explicitly becomes a social trust aggregator Tested: deno task test:config-types Tested: deno task test:database Tested: deno task test:kind30385 Tested: deno task test:unit Tested: deno task compile Tested: git diff --check Tested: deno lint with existing noisy rules excluded on changed TypeScript files Not-tested: full deno check remains blocked by existing project-wide strict type errors --------- Co-authored-by: sandwich <dskvr@users.noreply.github.com> * Allow RelayMon TRA publication during warmup (#925) Fisherman can spend a long time in warmup while it drains unchecked relays. The worker previously suppressed trusted relay assertions in warmup, so enabled TRA publishing did not record observation history or enqueue kind 30385 events until warmup ended. Keep normal NIP-66 check and delta event suppression intact, but let the opt-in TRA path run during warmup. Start health reporting before warmup so external monitors can see the process while the warmup queue is active. Constraint: Fisherman warmup is long enough to keep status stale and prevent any live kind 30385 evidence. Rejected: Wait for warmup to finish | leaves TRA publishing absent for the first long-running pass and does not explain current missing events. Confidence: high Scope-risk: narrow Directive: Do not re-add warmup suppression around publishTrustedRelayAssertion without a separate warmup TRA publisher or observation recorder. Tested: deno test --no-check --unstable-sloppy-imports --allow-net --allow-env --allow-read --allow-write --allow-run tests/unit/worker-tra.test.ts tests/unit/kind30385.test.ts tests/unit/config-types.test.ts Tested: deno fmt --check tests/unit/worker-tra.test.ts src/core/worker.ts Tested: git diff --check -- apps/relaymon/src/core/daemon.ts apps/relaymon/src/core/worker.ts apps/relaymon/tests/unit/worker-tra.test.ts Not-tested: live Fisherman deployment and live relay kind 30385 publication after image rollout Co-authored-by: sandwich <dskvr@users.noreply.github.com> * fix(relaymon): move TRA per-check DB work off the check hot path (#926) * Let remediation deletions publish after warmup Remediation queue draining used the same deletion helper as ordinary NIP-09 publishes, but daemon startup drained that queue while deletion publishing was still suppressed for warmup. That made valid queued deletions return false before any kind:5 event was sent, then remediation logged the skipped local publish as NOT ACK'd and left the row queued. Move the remediation drain until immediately after warmup finishes, where runWarmup's finally block has re-enabled deletion publishing. The queue still retries rows that relays reject or fail to ACK, but valid entries are no longer blocked by local startup suppression. Constraint: Deletion publishing is intentionally suppressed during warmup for normal check processing. Rejected: Bypass suppression inside deleteRelayCheckEvent | that would weaken the warmup guard for every deletion caller. Confidence: high Scope-risk: narrow Directive: Keep remediation queue drain after runWarmup unless deletion.ts gains caller-specific suppression semantics. Tested: deno test --no-check --unstable-sloppy-imports --allow-net --allow-env --allow-read --allow-write --allow-run tests/unit/deletion.test.ts tests/unit/nato-purge.test.ts tests/unit/nostrings-sweep.test.ts Tested: git diff --check -- apps/relaymon/src/core/daemon.ts Not-tested: live Fisherman deployment and relay ACK drain * fix(relaymon): move TRA per-check DB work off the check hot path Trusted Relay Assertion (kind 30385) processing ran inline on every relay check: recordTrustedRelayObservation + buildTrustedRelayAssertion do synchronous SQLite work, including a read-back of up to max_observations_per_relay history rows plus per-sample scoring. On Deno's single-threaded event loop (and a CPU-constrained host) those bursts starved in-flight WebSocket I/O and busted the tight check budgets (read/open timeouts), so healthy relays were falsely reported offline. This is what took Fisherman down once TRA was enabled there. Decouple TRA from the check hot path: - processRelay now calls enqueueTrustedRelayObservation(), a cheap guard + array push (no DB work). The queue is capped (drops oldest, warns) so it can't grow unbounded during warmup. - A lazily-started background processor drains the queue one relay at a time and yields the event loop (delay) between relays, so the synchronous per-relay work never sustains a block long enough to starve checks. Start is deferred via setTimeout so the first item never runs inside the check's call stack. - Throttle is configurable via trustedRelayAssertions.processing_throttle_ms (default 25ms). publishTrustedRelayAssertion (the per-item worker) is unchanged, so its record/build/gate/publish behavior and existing coverage are preserved. Tests: add coverage that enqueue defers work (nothing published synchronously; queue drains via processTrustedRelayQueueOnce), that it is a no-op when TRA is disabled, and that the queue caps memory by dropping oldest. Full unit suite: 369 passed. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com> --------- Co-authored-by: sandwich <dskvr@users.noreply.github.com> Co-authored-by: Claude Opus 4.8 (1M context) <noreply@anthropic.com> * ci: unified Docker image CD (dev + tagged, Docker Hub + GHCR) (#928) Replaces the two ad-hoc, manual-only docker-relaymon.yml / docker-trawler.yml workflows (whose `<pkg>-v*` tag triggers never matched the release's actual `<pkg>@v<version>` tags, so they only ever ran via manual dispatch) with a single matrix-driven workflow covering every package that produces a Docker image: rstate, trawler, relaymon (nocapd excluded — deprecated). Behaviour: - push to `next` (integration branch): rolling `:dev` + `:dev-<sha>` images for ALL matrix packages. - release tag `<pkg>@v<version>`: `:<version>` for that package, plus `:latest` when it is the highest non-prerelease version (computed from git tags). - both paths runnable via workflow_dispatch (mode dev|tagged, package, version, update_latest auto|true|false). - every image pushed to BOTH Docker Hub (nostrwatch/<app>) and GHCR (ghcr.io/sandwichfarm/<app>). The build plan (which images, which tags, latest-or-not) lives in scripts/ci/plan-docker-images.mjs — dependency-free and locally runnable, so the workflow logic is verifiable outside CI. Build contexts match each app's Dockerfile (rstate builds from apps/rstate; trawler/relaymon from repo root). Requires existing secrets DOCKERHUB_USERNAME / DOCKERHUB_TOKEN; GHCR uses the built-in GITHUB_TOKEN (packages: write). Co-authored-by: sandwich <dskvr@users.noreply.github.com> Co-authored-by: Claude Opus 4.8 (1M context) <noreply@anthropic.com> * rstate: publish trusted relay assertions from computed state (#922) * Prevent stale rstate relay assertion cache hits The relay MCP tools had inline cache keys that missed output-affecting inputs as the surface grew into simple, detailed, and full variants. Centralizing cache config keeps registration and tests aligned with the current slash-based tool names, and the software grouping tool now honors the family filter used by REST parity checks. This also fixes the rstate type-check failures that surfaced while validating the PR: scorer code now calls the current observation-store API, stale unused private state was removed, and relay pool connection state is exposed through stats instead of being written-only. Constraint: PR branch had to be rebuilt onto current origin/next because the old trusted-relay-assertions branch was 133 commits behind. Rejected: Push the stale local feature/trusted-relay-assertions branch directly | it would open a noisy outdated PR against next. Confidence: high Scope-risk: narrow Directive: Keep cache config keys aligned with actual MCP tool names, not internal aliases. Tested: cd apps/rstate && ./node_modules/.bin/tsc --noEmit Tested: cd apps/rstate && ./node_modules/.bin/vitest --run test/cache-key-coverage.test.ts test/rest_tool_parity.test.ts test/parity.test.ts (140 passing). Tested: cd apps/rstate && ./node_modules/.bin/tsup after building workspace prerequisites logger, utils, publisher, announce. Not-tested: full workspace recursive lint/build/test. * Publish trusted relay assertions from computed rstate output rstate now owns kind 30385 generation from its aggregate relay state, with a separate trust query surface and no new 1066 ingestion path. The implementation keeps TRA history gated behind publishing enablement and suppresses unchanged publishes unless material state moves or a refresh boundary is reached. Constraint: must not ingest kind 1066 for this slice Rejected: fold trust data into default relay responses | would bloat the existing API surface and complicate cache/tool parity Confidence: high Scope-risk: moderate Directive: keep TRA history writes gated on publishing enablement and do not add social trust ingestion here Tested: npm run test -- --run, npm run type-check, npm run build, git diff --check Not-tested: live relay publish round-trip against a production signer/relay pair --------- Co-authored-by: sandwich <dskvr@users.noreply.github.com> * fix(relaymon): publish each relay deletion once, not on every boot (#932) On startup relaymon re-published a NIP-09 (kind:5) deletion for every ignored relay (~17k), because the "already deleted" guard was an in-memory Set that resets each process. The deletion only needs to be sent once per ignore episode. Persist the guard: - relay_status gains a `deletion_published_at` column (idempotent migration). - deleteRelayCheckEvent skips when the durable marker is set (or the in-memory fast-path is) and stamps it on successful publish — so the once-only guarantee survives restarts. - IgnoreListSync.publishDeletions now only iterates ignored relays that are actually pending (deletion_published_at = 0) instead of all of them. - The marker is cleared whenever a relay becomes unignored (markRelayUnignored, persistResult, dedup re-evaluation), so a later re-ignore republishes exactly once (a prior deletion only covered the earlier 30166). Tests: deletion-publish-once.test.ts covers pending selection, mark/round-trip, cross-boot idempotency, and unignore/persist reset. Full relaymon unit suite: 374 passed. Co-authored-by: sandwich <dskvr@users.noreply.github.com> Co-authored-by: Claude Opus 4.8 (1M context) <noreply@anthropic.com> * chore(trawler): remove committed merge-conflict markers from CHANGELOG (#931) A prior merge committed unresolved conflict markers into apps/trawler/CHANGELOG.md on next. The HEAD side was empty and the other side held the 0.6.0/0.6.1 entries; dropped the markers and kept the entries. Content only — no version/release change. Co-authored-by: sandwich <dskvr@users.noreply.github.com> Co-authored-by: Claude Opus 4.8 (1M context) <noreply@anthropic.com> * fix build failures (#937) * test(quick/260503-kc3): add failing regression tests for remaining XSS vectors - operators.formatters.test.ts: name/about/reference/filterFormatters - monitors.formatters.test.ts: nip05/pubkey/checks/filterFormatters - relays.formatters.test.ts: software/geocode/minPowDifficulty/supportedNips/networks/ipv4/retentionPolicy - DataTableFallback.test.ts: structural regression for {@html row[column.key]} * fix(gui/security): sanitize remaining XSS sinks in dataTable formatters - operators.ts: hex-pubkey guard, escapeHtml on name/about, nostr-id guard on reference, alt="" on imgs, escaped filterFormatters - monitors.ts: hex-pubkey guard, safeImageUrl on photos, escaped name/pubkey/checks/nip05, fixed pastelPair?.dark and class typo - relays.ts: networks slug allowlist, IPv4 strict guard, retentionPolicy try/catch + escape, escaped geocode/supportedNips/minPowDifficulty/software - softwares.ts/isps.ts/geocodes.ts: escaped filterFormatters; geocodes.ts geocode formatter also escaped (additional sink found during audit) - test fixes: tighten 'x"' assertion to 'alert(1)' (avoided false positive on class="flex"); replace fileURLToPath with cwd-walking path resolver * fix(gui/security): bind cell + filter fallbacks as text, not HTML Replace the {@html} fallbacks in 4 components with split-conditional text bindings. Registered formatters keep their {@html ...} branch (output is sanitized as of the previous commit). Unregistered columns now render as plain text — closes the silent regression vector where any column without a formatter would re-introduce XSS. - data-view/table/DataTable.svelte: fallback row[column.key] now text-bound - lists/table/DataTable.svelte: same - lists/table/Filters.svelte: filter button fallback now text-bound - data-view/filters/DataViewFilters.svelte: same * fix build failures --------- Co-authored-by: sandwich <dskvr@users.noreply.github.com> * chore(deps-dev): bump http-proxy-middleware from 3.0.5 to 3.0.7 (#933) Bumps [http-proxy-middleware](https://github.com/chimurai/http-proxy-middleware) from 3.0.5 to 3.0.7. - [Release notes](https://github.com/chimurai/http-proxy-middleware/releases) - [Changelog](https://github.com/chimurai/http-proxy-middleware/blob/v3.0.7/CHANGELOG.md) - [Commits](https://github.com/chimurai/http-proxy-middleware/compare/v3.0.5...v3.0.7) --- updated-dependencies: - dependency-name: http-proxy-middleware dependency-version: 3.0.7 dependency-type: direct:development ... Signed-off-by: dependabot[bot] <support@github.com> Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> * chore(deps): bump dompurify from 3.4.2 to 3.4.11 (#934) Bumps [dompurify](https://github.com/cure53/DOMPurify) from 3.4.2 to 3.4.11. - [Release notes](https://github.com/cure53/DOMPurify/releases) - [Commits](https://github.com/cure53/DOMPurify/compare/3.4.2...3.4.11) --- updated-dependencies: - dependency-name: dompurify dependency-version: 3.4.11 dependency-type: direct:production ... Signed-off-by: dependabot[bot] <support@github.com> Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> Co-authored-by: sandwich <dskvr@users.noreply.github.com> * chore(deps): bump js-yaml from 4.1.1 to 4.2.0 (#935) Bumps [js-yaml](https://github.com/nodeca/js-yaml) from 4.1.1 to 4.2.0. - [Changelog](https://github.com/nodeca/js-yaml/blob/master/CHANGELOG.md) - [Commits](https://github.com/nodeca/js-yaml/compare/4.1.1...4.2.0) --- updated-dependencies: - dependency-name: js-yaml dependency-version: 4.2.0 dependency-type: direct:production ... Signed-off-by: dependabot[bot] <support@github.com> Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> * fix(gui/security): close remaining XSS vectors (#938) * test(quick/260503-kc3): add failing regression tests for remaining XSS vectors - operators.formatters.test.ts: name/about/reference/filterFormatters - monitors.formatters.test.ts: nip05/pubkey/checks/filterFormatters - relays.formatters.test.ts: software/geocode/minPowDifficulty/supportedNips/networks/ipv4/retentionPolicy - DataTableFallback.test.ts: structural regression for {@html row[column.key]} * fix(gui/security): sanitize remaining XSS sinks in dataTable formatters - operators.ts: hex-pubkey guard, escapeHtml on name/about, nostr-id guard on reference, alt="" on imgs, escaped filterFormatters - monitors.ts: hex-pubkey guard, safeImageUrl on photos, escaped name/pubkey/checks/nip05, fixed pastelPair?.dark and class typo - relays.ts: networks slug allowlist, IPv4 strict guard, retentionPolicy try/catch + escape, escaped geocode/supportedNips/minPowDifficulty/software - softwares.ts/isps.ts/geocodes.ts: escaped filterFormatters; geocodes.ts geocode formatter also escaped (additional sink found during audit) - test fixes: tighten 'x"' assertion to 'alert(1)' (avoided false positive on class="flex"); replace fileURLToPath with cwd-walking path resolver * fix(gui/security): bind cell + filter fallbacks as text, not HTML Replace the {@html} fallbacks in 4 components with split-conditional text bindings. Registered formatters keep their {@html ...} branch (output is sanitized as of the previous commit). Unregistered columns now render as plain text — closes the silent regression vector where any column without a formatter would re-introduce XSS. - data-view/table/DataTable.svelte: fallback row[column.key] now text-bound - lists/table/DataTable.svelte: same - lists/table/Filters.svelte: filter button fallback now text-bound - data-view/filters/DataViewFilters.svelte: same --------- Co-authored-by: sandwich <dskvr@users.noreply.github.com> * chore(deps-dev): bump vitest from 3.1.1 to 3.2.6 in /libraries/auditor (#920) * chore(deps-dev): bump vitest from 3.1.1 to 3.2.6 in /libraries/auditor Bumps [vitest](https://github.com/vitest-dev/vitest/tree/HEAD/packages/vitest) from 3.1.1 to 3.2.6. - [Release notes](https://github.com/vitest-dev/vitest/releases) - [Changelog](https://github.com/vitest-dev/vitest/blob/main/docs/releases.md) - [Commits](https://github.com/vitest-dev/vitest/commits/v3.2.6/packages/vitest) --- updated-dependencies: - dependency-name: vitest dependency-version: 3.2.6 dependency-type: direct:development ... Signed-off-by: dependabot[bot] <support@github.com> * Add RelayMon trusted relay assertion publishing (#923) * Expose RelayMon trust observations as Nostr assertions RelayMon can now opt into kind 30385 Trusted Relay Assertion publication using the trustedrelays draft NIP shape. The worker records local observation history, derives reliability, quality, accessibility, score, confidence, and metadata tags from existing check results, and only republishes when material state changes or the refresh interval elapses. The change also adds config defaults, docs and docker-stack examples, database persistence for assertion state, unit coverage for the kind 30385 builder/material-change logic, and small validation repairs for current nostr-tools key handling, the kind1066 operational-status tag, and a date-sensitive SSL fixture. Constraint: TRA draft kind 30385 comes from Letdown2491/trustedrelays and uses replaceable d/status/score/component tags Constraint: RelayMon's current test and compile contract uses Deno --no-check tasks Rejected: Publish kind 30385 on every relay check | would spam replaceable assertions without material-change gating Rejected: Add a runtime dependency on the trustedrelays implementation | no new dependencies requested and RelayMon already has the observation data needed for local scoring Confidence: high Scope-risk: moderate Directive: Keep TRA publishing opt-in and preserve material-change gating before changing defaults Tested: deno task test:kind30385; deno task test:unit; deno task test; deno task compile; docker build -f apps/relaymon/.docker/Dockerfile -t nostrwatch/relaymon:tra-test . Not-tested: Strict deno type checking; repo has existing strict-check debt and RelayMon tasks use --no-check * Keep docs lint focused on checked-in docs Docs Lint requires package README anchor headings, so the Docker stacks README now exposes the required Installation section before Quick Start. Link Check was also failing on package README links into .planning, which is not part of the CI checkout or shipped docs surface, so lychee now ignores those local planning artifact URIs. Constraint: Docs Lint enforces package README heading anchors and lychee checks package/docs links on pull_request Rejected: Add .planning/codebase/CONCERNS.md for CI | planning artifacts are not part of the checked-in docs surface Confidence: high Scope-risk: narrow Tested: ./node_modules/.bin/markdownlint-cli2 'libraries/*/README.md' 'apps/*/README.md' 'internal/*/README.md'; docker run --rm -v "/tmp/nostr-watch-tra":/input -w /input lycheeverse/lychee:latest --cache --max-cache-age 1d 'libraries/*/README.md' 'apps/*/README.md' 'internal/*/README.md' 'docs/**/*.md' Not-tested: Full GitHub Actions matrix before push * Keep RelayMon TRA assertions monitor-local RelayMon should publish measured Trusted Relay Assertions without becoming a social trust aggregator. This keeps kind 30385 on the existing monitor identity, records detailed TRA history only when TRA publishing is enabled, and scores assertions from local observations plus current RelayMon probe data. Constraint: kind 30385 must use the same RelayMon monitor key as NIP-66 events Constraint: monitors that do not publish TRA must not write detailed TRA observation history Rejected: Add a providerKey for TRA signing | user clarified the monitor key is the provider identity Rejected: Ingest kind 1985 or manage kind 10385 in RelayMon | social trust aggregation and provider discovery belong outside RelayMon Confidence: high Scope-risk: moderate Directive: Keep RelayMon TRA scoring limited to this monitor's observations unless RelayMon explicitly becomes a social trust aggregator Tested: deno task test:config-types Tested: deno task test:database Tested: deno task test:kind30385 Tested: deno task test:unit Tested: deno task compile Tested: git diff --check Tested: deno lint with existing noisy rules excluded on changed TypeScript files Not-tested: full deno check remains blocked by existing project-wide strict type errors --------- Co-authored-by: sandwich <dskvr@users.noreply.github.com> …
1 parent f42e7d6 commit 9c4d1bf

2 files changed

Lines changed: 4 additions & 11 deletions

File tree

apps/gui/src/lib/components/layout/PageHeader.svelte

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -3,7 +3,7 @@
33
import { clickToCopy } from "$utils/ux";
44
import { safeImageUrl } from "$utils/sanitize";
55
import { bannerStyleString } from "$utils/style-helpers";
6-
import * as DOMPurify from "dompurify";
6+
import DOMPurify from "dompurify";
77
88
export let title: string;
99
export let icon: string | undefined = undefined;
@@ -29,7 +29,7 @@
2929
// sanitize via DOMPurify rather than dropping {@html} entirely. DOMPurify
3030
// is already a dependency of apps/gui (used in $utils/notes.ts).
3131
// sanitize() defaults strip script tags and event-handler attributes.
32-
$: safeSubtitle = subtitle ? (DOMPurify as any).sanitize(subtitle) : '';
32+
$: safeSubtitle = subtitle ? DOMPurify.sanitize(subtitle) : '';
3333
3434
</script>
3535
<header

apps/gui/src/lib/utils/notes.ts

Lines changed: 2 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
import { marked, type MarkedOptions } from 'marked';
2-
import * as DOMPurify from 'dompurify';
2+
import DOMPurify from 'dompurify';
33
import { writable, get, type Writable } from 'svelte/store';
44
import { nip19 } from 'nostr-tools';
55
import type { UserService } from '$lib/services/UserService';
@@ -292,14 +292,7 @@ async function applyMarkdown(text: string, options: MarkedOptions): Promise<stri
292292
* replace the default allowlist); ADD_TAGS / ADD_ATTR extend it.
293293
*/
294294
function applySanitize(text: string): string {
295-
// DOMPurify's CommonJS shape: in some bundler setups DOMPurify is the
296-
// default export; in others it's the module namespace. The current
297-
// codebase uses `import * as DOMPurify` — the .sanitize is on the
298-
// namespace OR on .default depending on bundler. Defensive lookup.
299-
const purify =
300-
// eslint-disable-next-line @typescript-eslint/no-explicit-any
301-
(DOMPurify as any).sanitize ?? (DOMPurify as any).default?.sanitize;
302-
return purify(text, {
295+
return DOMPurify.sanitize(text, {
303296
ADD_TAGS: ['iframe'],
304297
ADD_ATTR: ['allow', 'allowfullscreen', 'frameborder', 'scrolling', 'src'],
305298
});

0 commit comments

Comments
 (0)