Skip to content

Drop swagger-ui, document telemetry, add opt-outs (#7524)#7757

Merged
JohnMcLear merged 15 commits into
developfrom
feature/7524-drop-swagger-ui-telemetry
May 16, 2026
Merged

Drop swagger-ui, document telemetry, add opt-outs (#7524)#7757
JohnMcLear merged 15 commits into
developfrom
feature/7524-drop-swagger-ui-telemetry

Conversation

@JohnMcLear
Copy link
Copy Markdown
Member

Summary

  • Drops swagger-ui-express (Scarf telemetry pixel — swagger-api/swagger-ui#10573)
  • /api-docs now served by vendored RapiDoc 9.3.4 (MIT, ~647KB), with load-fonts="false" + explicit system-font stacks so no Google Fonts request either
  • New privacy.updateCheck and privacy.pluginCatalog opt-outs (default true; behaviour unchanged for existing operators)
  • New PRIVACY.md documenting both outbound calls and how to disable each
  • bin/plugins/stalePlugins.ts reads settings.updateServer and honours the new flag

Closes #7524.

How the opt-outs behave

Flag Default Off behaviour
privacy.updateCheck true UpdateCheck.check() and getLatestVersion() early-return; one-time log line on first skip; admin "update available" panel simply omits the line
privacy.pluginCatalog true getAvailablePlugins() throws a tagged disabled error; admin socket handlers emit results:catalogDisabled; admin plugin page renders an info banner pointing to pnpm run plugins i ep_<name>; install/uninstall by name still work

Vendored asset audit

  • grep -E "scarf|google-analytics|googletagmanager|sentry|datadog|segment\.|mixpanel|amplitude|navigator\.sendBeacon" src/static/vendor/rapidoc/rapidoc-min.jsno matches
  • Outbound URLs in the bundle (grep -oE "https?://..." → sort -u):
    • fonts.gstatic.com/...woff2 → embedded in @font-face; disabled via load-fonts="false" + explicit regular-font/mono-font attributes + CSS override
    • github.com/..., marked.js.org → comment-only links
  • SHA-256 + source URL pinned in src/static/vendor/rapidoc/VERSION

Test plan

  • pnpm test:vitest → 603 / 603 (3 new specs: privacy/settings-defaults, privacy/updateCheck-optout, privacy/installer-optout)
  • pnpm exec tsc --noEmit clean in src/ and admin/
  • Manual: /api-docs renders RapiDoc; DevTools Network shows no third-party hosts (Google Fonts disabled at attribute level)
  • Manual: privacy.updateCheck: false logs "Update check disabled..." on boot; no info.json request
  • Manual: privacy.pluginCatalog: false → no plugins.json request on idle server
  • Reviewer manual check: with privacy.pluginCatalog: false, log into /admin/plugins and confirm the localised info banner renders (admin_plugins.catalog_disabled)
  • grep -rIn "swagger-ui-express" src admin bin → no results

Notes for reviewers

  • The privacy guard for the plugin catalog lives in src/static/js/pluginfw/pluginCatalogGuard.ts (small standalone helper). This is a deliberate split so the gate is unit-testable under vitest without dragging in installer.ts's CJS require('./plugins') chain.
  • src/node/types/SwaggerUIResource.ts is a TypeScript-only type used by openapi.ts; kept as-is. The unrelated comment at openapi.ts:810 mentioning swagger-node-express is historical and kept.
  • @scarf/scarf is already listed under ignoredBuiltDependencies in pnpm-workspace.yaml, so even if a future transitive dep pulls Scarf in, its install-time pixel is suppressed. PRIVACY.md notes this.

🤖 Generated with Claude Code

@qodo-code-review
Copy link
Copy Markdown

Qodo reviews are paused for this user.

Troubleshooting steps vary by plan Learn more →

On a Teams plan?
Reviews resume once this user has a paid seat and their Git account is linked in Qodo.
Link Git account →

Using GitHub Enterprise Server, GitLab Self-Managed, or Bitbucket Data Center?
These require an Enterprise plan - Contact us
Contact us →

@qodo-free-for-open-source-projects
Copy link
Copy Markdown

Review Summary by Qodo

Drop swagger-ui telemetry, add privacy opt-outs, document outbound calls

✨ Enhancement 🐞 Bug fix

Grey Divider

Walkthroughs

Description
• Drops swagger-ui-express dependency (injects Scarf telemetry pixel)
• Replaces /api-docs with vendored RapiDoc 9.3.4 (MIT, no outbound calls)
• Adds privacy.updateCheck and privacy.pluginCatalog opt-outs (default true)
• Publishes PRIVACY.md documenting both outbound calls and disabling each
• Updates admin UI to show banner when plugin catalog is disabled
Diagram
flowchart LR
  A["swagger-ui-express<br/>+ Scarf pixel"] -->|removed| B["RapiDoc 9.3.4<br/>vendored"]
  C["UpdateCheck.ts<br/>hourly info.json"] -->|gated by| D["privacy.updateCheck"]
  E["installer.ts<br/>plugins.json fetch"] -->|gated by| F["privacy.pluginCatalog"]
  D -->|default true| G["PRIVACY.md<br/>opt-out guide"]
  F -->|default true| G
  H["admin plugins page"] -->|disabled signal| I["info banner"]
Loading

Grey Divider

File Changes

1. src/node/utils/Settings.ts ⚙️ Configuration changes +12/-0

Add privacy settings block with updateCheck and pluginCatalog

src/node/utils/Settings.ts


2. src/node/utils/UpdateCheck.ts ✨ Enhancement +9/-0

Gate version check behind privacy.updateCheck setting

src/node/utils/UpdateCheck.ts


3. src/static/js/pluginfw/installer.ts ✨ Enhancement +2/-0

Gate plugin catalog fetch behind privacy.pluginCatalog setting

src/static/js/pluginfw/installer.ts


View more (22)
4. src/static/js/pluginfw/pluginCatalogGuard.ts ✨ Enhancement +11/-0

New guard function for plugin catalog disabled state

src/static/js/pluginfw/pluginCatalogGuard.ts


5. src/node/hooks/express/adminplugins.ts ✨ Enhancement +22/-5

Check privacy flag before catalog operations, emit disabled signal

src/node/hooks/express/adminplugins.ts


6. bin/plugins/stalePlugins.ts 🐞 Bug fix +8/-1

Read updateServer setting and respect privacy.pluginCatalog flag

bin/plugins/stalePlugins.ts


7. src/node/handler/RestAPI.ts 🐞 Bug fix +4/-7

Replace swagger-ui-express with static RapiDoc HTML shell

src/node/handler/RestAPI.ts


8. src/static/api-docs.html ✨ Enhancement +31/-0

New minimal HTML shell mounting RapiDoc web component

src/static/api-docs.html


9. src/static/vendor/rapidoc/rapidoc-min.js Dependencies +3895/-0

Vendored RapiDoc 9.3.4 minified bundle (MIT license)

src/static/vendor/rapidoc/rapidoc-min.js


10. src/static/vendor/rapidoc/VERSION 📝 Documentation +3/-0

Version and SHA256 checksum for vendored RapiDoc

src/static/vendor/rapidoc/VERSION


11. src/static/vendor/rapidoc/LICENSE 📝 Documentation +22/-0

MIT license copy for vendored RapiDoc

src/static/vendor/rapidoc/LICENSE


12. src/tests/backend-new/specs/privacy/settings-defaults.test.ts 🧪 Tests +12/-0

Test privacy settings default to true

src/tests/backend-new/specs/privacy/settings-defaults.test.ts


13. src/tests/backend-new/specs/privacy/updateCheck-optout.test.ts 🧪 Tests +31/-0

Test UpdateCheck respects privacy.updateCheck=false

src/tests/backend-new/specs/privacy/updateCheck-optout.test.ts


14. src/tests/backend-new/specs/privacy/installer-optout.test.ts 🧪 Tests +21/-0

Test plugin catalog guard throws when disabled

src/tests/backend-new/specs/privacy/installer-optout.test.ts


15. admin/src/pages/HomePage.tsx ✨ Enhancement +11/-0

Subscribe to catalogDisabled signal, render info banner

admin/src/pages/HomePage.tsx


16. admin/src/index.css ✨ Enhancement +10/-0

Add banner styling for disabled catalog notification

admin/src/index.css


17. src/locales/en.json 📝 Documentation +1/-0

Add localized string for disabled catalog banner

src/locales/en.json


18. settings.json.template ⚙️ Configuration changes +11/-0

Add privacy block with updateCheck and pluginCatalog options

settings.json.template


19. PRIVACY.md 📝 Documentation +68/-0

New privacy stance document listing outbound calls and opt-outs

PRIVACY.md


20. README.md 📝 Documentation +1/-1

Add reference to PRIVACY.md in governance statement

README.md


21. CHANGELOG.md 📝 Documentation +8/-0

Document privacy improvements and swagger-ui removal

CHANGELOG.md


22. docs/superpowers/specs/2026-05-15-issue-7524-swagger-ui-telemetry-design.md 📝 Documentation +246/-0

Design specification for swagger-ui removal and privacy opt-outs

docs/superpowers/specs/2026-05-15-issue-7524-swagger-ui-telemetry-design.md


23. docs/superpowers/plans/2026-05-15-issue-7524-swagger-ui-telemetry.md 📝 Documentation +1131/-0

Detailed implementation plan with 12 TDD-flavored tasks

docs/superpowers/plans/2026-05-15-issue-7524-swagger-ui-telemetry.md


24. pnpm-lock.yaml Dependencies +8/-67

Remove swagger-ui-express and related type dependencies

pnpm-lock.yaml


25. src/package.json Additional files +0/-2

...

src/package.json


Grey Divider

Qodo Logo

@qodo-free-for-open-source-projects
Copy link
Copy Markdown

qodo-free-for-open-source-projects Bot commented May 15, 2026

Code Review by Qodo

🐞 Bugs (1) 📘 Rule violations (0) 📎 Requirement gaps (0)

Grey Divider


Action required

1. Promise sent to admin ✓ Resolved 🐞 Bug ≡ Correctness
Description
In the admin plugin socket handler, checkUpdates emits the unresolved Promise returned by
checkPluginForUpdates() because it is not awaited, so the admin UI will throw when it calls
.includes() on data.updatable. Additionally, the error path emits {} for updatable, which
also violates the UI's string[] expectation and can throw on transient errors.
Code

src/node/hooks/express/adminplugins.ts[R74-82]

   const updatable = checkPluginForUpdates();
Evidence
checkPluginForUpdates() is async, but checkUpdates does not await it before emitting. The admin
UI handler calls .includes() on the received updatable, which will throw if it is a Promise (or
{} in the catch path).

src/node/hooks/express/adminplugins.ts[67-82]
admin/src/pages/HomePage.tsx[60-71]

Agent prompt
The issue below was found during a code review. Follow the provided context and guidance below and implement a solution

## Issue description
The `checkUpdates` socket event emits an unresolved Promise (missing `await`) and emits the wrong type (`{}`) on error. This breaks the admin plugins page because the client expects `updatable` to be a `string[]` and calls `.includes()`.
## Issue Context
- `checkPluginForUpdates` is `async`.
- Admin UI (`HomePage.tsx`) assumes `updatable: string[]`.
## Fix Focus Areas
- src/node/hooks/express/adminplugins.ts[74-82]
- admin/src/pages/HomePage.tsx[66-70]

ⓘ Copy this prompt and use it to remediate the issue with your preferred AI generation tools



Remediation recommended

2. console.info bypasses log4js 🐞 Bug ◔ Observability
Description
New opt-out messages use console.info(), but the Settings logger override only rebinds
console.log/warn/error/debug, so these messages can bypass log4js appenders/formatting/level
control. This affects the new "Update check disabled…" and "stalePlugins… exiting" notices, which
operators may rely on to confirm the opt-out is active.
Code

src/node/utils/UpdateCheck.ts[R60-65]

+  if (!settings.privacy.updateCheck) {
+    if (!loggedDisabled) {
+      console.info('Update check disabled by privacy.updateCheck=false (see PRIVACY.md)');
+      loggedDisabled = true;
+    }
+    return;
Evidence
Settings initializes log4js by rebinding several console methods but not console.info. The new
opt-out logs use console.info, so they will not go through the same logging pipeline as
console.log/console.warn/console.error.

src/node/utils/Settings.ts[65-75]
src/node/utils/UpdateCheck.ts[59-66]
bin/plugins/stalePlugins.ts[9-15]

Agent prompt
The issue below was found during a code review. Follow the provided context and guidance below and implement a solution

## Issue description
`console.info()` is not routed through Etherpad's log4js console overrides, so new privacy opt-out logs can be missing from configured log outputs.
## Issue Context
Settings.ts overrides console.log/warn/error/debug but leaves console.info untouched.
## Fix Focus Areas
- src/node/utils/Settings.ts[70-75]
- src/node/utils/UpdateCheck.ts[60-64]
- bin/plugins/stalePlugins.ts[10-14]

ⓘ Copy this prompt and use it to remediate the issue with your preferred AI generation tools


Grey Divider

Qodo Logo

Comment thread src/node/hooks/express/adminplugins.ts Outdated
JohnMcLear and others added 13 commits May 15, 2026 13:46
Three-deliverable plan: vendor RapiDoc to replace swagger-ui-express
(Scarf-injecting), add privacy.updateCheck and privacy.pluginCatalog
opt-outs for our two outbound calls, and ship PRIVACY.md as a public
stance doc.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Twelve TDD-flavoured tasks: privacy settings shape, UpdateCheck +
installer opt-outs (each with a failing-test-first cycle), admin
backend/UI plumbing, dependency drop, vendored RapiDoc, PRIVACY.md,
final verification matrix.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Adds privacy.updateCheck and privacy.pluginCatalog, both defaulting to
true so behavior is unchanged until operators opt out.

Refs #7524

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
check() and getLatestVersion() now early-return when the setting is
off. Logs once on first skip. The admin "update available" panel
already tolerates an undefined latestVersion.

Refs #7524

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Extracts the gate into pluginCatalogGuard.ts so it can be unit-tested
under vitest without dragging in the CJS require() chain from
installer.ts. getAvailablePlugins() now throws the tagged disabled
error before any fetch.

Refs #7524

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Short-circuits the four catalog-driven socket events. The install/
uninstall events are untouched so operators can still install by
plugin name even when the catalog is disabled.

Refs #7524

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Was hardcoding static.etherpad.org and ignoring opt-out. Now exits 0
cleanly when privacy.pluginCatalog=false.

Refs #7524

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Refs #7524

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Drops the swagger-ui-express dep (third-party Scarf telemetry pixel,
see swagger-api/swagger-ui#10573) and serves /api-docs with a static
HTML shell that mounts <rapi-doc>. /api-docs.json is unchanged.

The vendored RapiDoc asset is added in the next commit so the tree is
broken for one diff hunk — pair this with the rapidoc-min.js commit
during review.

Refs #7524

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Pinned bundle with checksum in VERSION. Replaces swagger-ui-dist which
shipped a Scarf telemetry pixel.

Disables RapiDoc's bundled Google Fonts request via load-fonts="false"
plus explicit regular-font/mono-font system stacks — RapiDoc's CSS
@font-face rules would otherwise fetch Open Sans from fonts.gstatic.com
at render time.

Also fixes the /api-docs route's res.sendFile to use an absolute path
resolved via settings.root (the previous {root: 'src/static'} was
resolved from CWD which is already src/, producing src/src/static).

Refs #7524

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Subscribes to results:catalogDisabled and renders a localized info
banner on the plugins page. install/uninstall still function via CLI.

Refs #7524

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Publishes Etherpad's stance on telemetry: two documented, opt-out
outbound calls; no third-party analytics; no install-time phone-homes
in our deps.

Refs #7524

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Qodo flagged that checkUpdates emitted the unresolved Promise (missing
await) and emitted {} for updatable on the error path, both breaking
the admin UI's expected string[] shape. Pre-existing bug surfaced when
the surrounding block was edited for the privacy.pluginCatalog gate.

Refs #7524
@JohnMcLear JohnMcLear force-pushed the feature/7524-drop-swagger-ui-telemetry branch from 3a3118a to 3539dcb Compare May 15, 2026 12:48
@JohnMcLear JohnMcLear requested a review from SamTV12345 May 15, 2026 13:15
Comment thread src/static/vendor/rapidoc/VERSION Outdated
@@ -0,0 +1,3 @@
rapidoc 9.3.4 - vendored 2026-05-15
Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

Why rapidoc? It is an unmaintained api doc. Yours is not even the latest one https://github.com/rapi-doc/RapiDoc/releases/tag/v9.3.4 . there is scalar: https://github.com/scalar/scalar

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

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

Good catch — switched to Scalar in 14a0350. RapiDoc is indeed effectively unmaintained.

Scalar 1.57.2 (MIT, actively developed) is now the vendored standalone bundle. Privacy posture is preserved by configuring the embed:

  • withDefaultFonts: false — no fonts.scalar.com woff2 fetch
  • agent.disabled: true — no api.scalar.com/vector/* calls (search/curated/embeddings)
  • mcp.disabled: true — no MCP integration UI
  • showDeveloperTools: 'never', hideClientButton: true, telemetry: false

Verified with headless Chromium against /api-docs: page mounts Scalar, renders the Etherpad OpenAPI document, and makes zero requests to any host outside localhost.

Per @SamTV12345's review on #7757: RapiDoc has been effectively
unmaintained for a while. Scalar (https://github.com/scalar/scalar)
is MIT-licensed, actively developed, and ships a self-contained
standalone bundle that works the same way for our purposes.

Privacy posture is preserved by configuring the embed:
  - withDefaultFonts: false   (no fonts.scalar.com woff2 fetch)
  - telemetry: false          (defensive)
  - agent.disabled: true      (no api.scalar.com/vector/* calls)
  - mcp.disabled:   true      (no MCP integration)
  - showDeveloperTools: 'never'
  - hideClientButton: true

Verified with headless Chromium: page loads /api-docs, mounts Scalar,
renders the Etherpad OpenAPI document, and makes zero requests to
any host other than localhost.

Vendor:
  - src/static/vendor/scalar/standalone.js     (@scalar/api-reference 1.57.2)
  - src/static/vendor/scalar/VERSION           (sha256 pinned)
  - src/static/vendor/scalar/LICENSE           (MIT)
Removed:
  - src/static/vendor/rapidoc/*

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Conflicts:
  - src/static/js/pluginfw/installer.ts: keep both sides (assertPluginCatalogEnabled from PR + checkEngineCompatibility/InstallerTaskQueue from develop)
  - pnpm-lock.yaml: regenerated via pnpm install --lockfile-only

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
@JohnMcLear JohnMcLear merged commit 278acb1 into develop May 16, 2026
27 of 31 checks passed
@JohnMcLear JohnMcLear deleted the feature/7524-drop-swagger-ui-telemetry branch May 16, 2026 17:34
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

Scarf Telemetry being injected by swagger-ui

2 participants