You signed in with another tab or window. Reload to refresh your session.You signed out in another tab or window. Reload to refresh your session.You switched accounts on another tab or window. Reload to refresh your session.Dismiss alert
This issue tracks the medium and low severity findings from the full-codebase audit of MCP. Critical and high findings are filed as individual issues. Each item below is independently fixable; check off as addressed.
Medium
[security/server] Live SonarQube token committed in public README badge URLs
- C:/Users/bryan/Nextcloud/workspace/FerrFlow-Org/MCP/README.md
- README.md lines 4-5 embed a SonarQube badge token directly in the URL: ...&token=sqb_df3c6923234b4dc9cc88fdd1b251c8a54c3b7903. This is a checked-in credential in a public, npm-published (MPL-2.0) repo — git grep confirms it is tracked, added by commit 8d5644f ('docs: swap Codecov badge for SonarQube quality gate'). Although sqb_ project-badge tokens are scoped to reading a single metric, they …
- Fix: Rotate the token in SonarQube immediately, then either use an unauthenticated/public badge endpoint or move the token to a server-side proxy. Scrub it from git history (it remains in the commit log even after editing the file).
[ux/server] Destructive/irreversible admin, billing, and secret tools expose no confirmation semantics
- C:/Users/bryan/Nextcloud/workspace/FerrFlow-Org/MCP/packages/mcp-vault/src/tools/secret-mutations.ts
- A large set of irreversible tools are exposed flat with no destructive/confirmation hint and no dry-run: delete_secret ('Permanently delete... Irreversible at the API level', secrets.ts/secret-mutations.ts), remove_org_member, delete_team, update_org slug change, cancel_subscription (with immediate=true), revoke_token, revoke_my_session (org-admin.ts, subscriptions.ts, tokens.ts, me.ts), archive_s…
- Fix: Set annotations: { destructiveHint: true, idempotentHint: false } on every mutating tool (and readOnlyHint: true on the read tools) so clients can gate confirmations. Consider a required confirm: true arg on the truly irreversible ones (delete_secret, remove_org_member, cancel_subscription immed
[bug/server] serverInfo version is hardcoded and lags the real package version on every release
- C:/Users/bryan/Nextcloud/workspace/FerrFlow-Org/MCP/packages/mcp/src/index.ts
- Every package.json is at version 6.5.0, but the version string passed to new McpServer(...) is a stale literal: packages/mcp/src/index.ts and mcp-vault/src/index.ts say '5.2.5'; mcp-fleet/mcp-growth/mcp-track say '6.0.0'. The .ferrflow config only bumps the package.json versionedFiles, so the index.ts literal is never updated and drifts every release. MCP clients therefore receive a wrong se…
- Fix: Read the version from package.json at runtime (createRequire(import.meta.url)('../package.json').version) instead of a literal, or add the index.ts files to FerrFlow's versionedFiles so the bump stays in sync.
[docs/server] README is badly out of date: missing tools, wrong version, undocumented HTTP mode and sub-MCPs
- C:/Users/bryan/Nextcloud/workspace/FerrFlow-Org/MCP/README.md
- README.md still describes a 4.0.0 single-package server with a 12-tool table (get_stats, health_check, fetch_docs, get_me, list_tokens, create_token, revoke_token, list_orgs, list_projects, list_subscriptions, list_vaults, list_issues). The actual @ferrlabs/mcp is v6.5.0 and registers org-admin (invite/remove member, role change, create/delete team), subscriptions (activate/update/cancel), me (upd…
- Fix: Regenerate the tool table from the registered tools, document all five MCP entrypoints and the HTTP mode/env vars, fix the version, and correct the 'Kubernetes/GitHub/releases' description in the org docs to match what actually ships.
[bug/server] OAuth metadata host advertised to users is wrong (auth.ferrlabs.com vs api.ferrlabs.com)
- C:/Users/bryan/Nextcloud/workspace/FerrFlow-Org/MCP/packages/mcp-core/src/transports/http.ts
- README.md (line 25, line 77, step 4 of 'How auth resolution works' line 91) tells users the OAuth flow opens auth.ferrlabs.com and that it serves the discovery metadata. But http.ts (HttpServerOptions.authorizationServer JSDoc, lines 50-58) explicitly states the metadata host must be api.ferrlabs.com because 'auth.ferrlabs.com is the user-facing login SPA but doesn't serve the metadata documents…
- Fix: Document the auth.ferrlabs.com (authorize/login) vs api.ferrlabs.com (token exchange + OAuth metadata) split explicitly in the README so the authorizationServer/FERRLABS_AUTH_URL configuration is unambiguous.
[security/server] Token persisted to disk with no OS-level encryption; clear text JSON
- C:/Users/bryan/Nextcloud/workspace/FerrFlow-Org/MCP/packages/mcp-core/src/auth/persistence.ts
- persistence.ts writes the long-lived FerrLabs session token to ~/.config/ferrlabs/mcp/token.json (or %APPDATA% on Windows) as clear-text JSON. On POSIX it best-effort chmod 0600, but on Windows the chmod is skipped entirely (if (platform() !== 'win32')), so on the platform this audit is running on the token file inherits default ACLs with no tightening. Any process running as the user (or a ba…
- Fix: On Windows, restrict the file ACL (e.g. icacls to the current user) or use DPAPI/the OS credential store; document that FERRLABS_MCP_NO_PERSIST=1 keeps it in memory. At minimum warn that the token path should not live inside a synced folder.
[security/server] No rate limiting or request-size cap on the HTTP /mcp endpoint
- C:/Users/bryan/Nextcloud/workspace/FerrFlow-Org/MCP/packages/mcp-core/src/transports/http.ts
- The HTTP transport (http.ts, used in the Docker image with FERRLABS_MCP_MODE=http on 0.0.0.0:3000) reads the full POST body into memory via readJsonBody with no size limit (Buffer.concat(chunks) over unbounded chunks) and applies no rate limiting or per-IP throttling before the bearer check. An unauthenticated attacker can POST arbitrarily large bodies (the body is read before auth in handleMcpR…
- Fix: Cap the request body size (reject > N bytes) and add basic per-IP rate limiting / connection limits in front of /mcp, or document that it must run behind a gateway (Traefik) that does so.
[security/server] get_secret reveal=true returns plaintext secret values straight into the LLM context
- C:/Users/bryan/Nextcloud/workspace/FerrFlow-Org/MCP/packages/mcp-vault/src/tools/secrets.ts
- secrets.ts get_secret with reveal=true returns the decrypted secret value verbatim in the tool result text (JSON.stringify(secret) where secret includes value). Because the result flows into the assistant's context (and from there potentially into client logs, transcripts, or the FerrFleet run transcript if an agent calls it), a single tool call exfiltrates a production secret into a place f…
- Fix: Mark the tool with a sensitive/destructive annotation, require an explicit confirm/acknowledge_reveal argument, and consider returning a one-time reference or masked value by default so assistants don't casually splatter live secrets into transcripts.
Low
[security/server] HTTP transport reflects arbitrary Origin with Access-Control-Allow-Credentials: true
- C:/Users/bryan/Nextcloud/workspace/FerrFlow-Org/MCP/packages/mcp-core/src/transports/http.ts
- In transports/http.ts applyCorsHeaders sets Access-Control-Allow-Origin to the request's own Origin header (req.headers.origin ?? '*') together with Access-Control-Allow-Credentials: true and a permissive Allow-Headers list. Reflecting any origin while allowing credentials defeats the same-origin policy: any malicious web page the user visits can issue credentialed cross-origin requests to…
- Fix: Replace origin reflection with an explicit allowlist (or drop Allow-Credentials, since auth is via Bearer header not cookies). Default host to 127.0.0.1, and pass enableDnsRebindingProtection: true + allowedHosts/allowedOrigins to StreamableHTTPServerTransport per the MCP security guidance.
[security/server] fetch_docs follows redirects on user-influenced paths, weakening the host allowlist
- C:/Users/bryan/Nextcloud/workspace/FerrFlow-Org/MCP/packages/mcp/src/tools/docs.ts
- docs.ts fetch_docs constrains the host to a fixed PRODUCT_HOSTS allowlist (good — prevents arbitrary SSRF), but appends the caller-supplied slug to the URL (${host}/${slug}) and calls fetch() with default redirect following. A page on a product site that 3xx-redirects off-domain (e.g. an open redirect at ferrgrowth.com/r?to=...) would cause the server to fetch and return attacker-chosen conten…
- Fix: Set redirect: 'manual' (or 'error') on the fetch and re-validate any redirect Location host against PRODUCT_HOSTS before following; reject slugs containing '://' or starting with '//'.
[missing-feature/server] List tools are capped but offer no cursor pagination, silently truncating large result sets
- C:/Users/bryan/Nextcloud/workspace/FerrFlow-Org/MCP/packages/mcp-growth/src/tools/forms.ts
- list_form_submissions (max 500), list_issues (max 200), list_runs (max 100), list_org_audit (max 200) accept only a limit and forward it as a single query param — there is no offset/cursor/page parameter. An assistant asking 'show all submissions' for a form with thousands of entries gets a silently truncated window with no next token and no indication more exist, so it can't page through. For…
- Fix: Add a cursor/offset parameter (or a before/after id) to the list tools and surface in the tool description that results are paginated, so assistants can fetch subsequent pages and know when the list is exhausted.
Found during a full-codebase audit of the FerrLabs workspace.
This issue tracks the medium and low severity findings from the full-codebase audit of
MCP. Critical and high findings are filed as individual issues. Each item below is independently fixable; check off as addressed.Medium
-
C:/Users/bryan/Nextcloud/workspace/FerrFlow-Org/MCP/README.md- README.md lines 4-5 embed a SonarQube badge token directly in the URL:
...&token=sqb_df3c6923234b4dc9cc88fdd1b251c8a54c3b7903. This is a checked-in credential in a public, npm-published (MPL-2.0) repo —git grepconfirms it is tracked, added by commit 8d5644f ('docs: swap Codecov badge for SonarQube quality gate'). Although sqb_ project-badge tokens are scoped to reading a single metric, they …- Fix: Rotate the token in SonarQube immediately, then either use an unauthenticated/public badge endpoint or move the token to a server-side proxy. Scrub it from git history (it remains in the commit log even after editing the file).
-
C:/Users/bryan/Nextcloud/workspace/FerrFlow-Org/MCP/packages/mcp-vault/src/tools/secret-mutations.ts- A large set of irreversible tools are exposed flat with no destructive/confirmation hint and no dry-run: delete_secret ('Permanently delete... Irreversible at the API level', secrets.ts/secret-mutations.ts), remove_org_member, delete_team, update_org slug change, cancel_subscription (with immediate=true), revoke_token, revoke_my_session (org-admin.ts, subscriptions.ts, tokens.ts, me.ts), archive_s…
- Fix: Set
annotations: { destructiveHint: true, idempotentHint: false }on every mutating tool (and readOnlyHint: true on the read tools) so clients can gate confirmations. Consider a requiredconfirm: truearg on the truly irreversible ones (delete_secret, remove_org_member, cancel_subscription immed-
C:/Users/bryan/Nextcloud/workspace/FerrFlow-Org/MCP/packages/mcp/src/index.ts- Every package.json is at version 6.5.0, but the version string passed to
new McpServer(...)is a stale literal: packages/mcp/src/index.ts and mcp-vault/src/index.ts say '5.2.5'; mcp-fleet/mcp-growth/mcp-track say '6.0.0'. The.ferrflowconfig only bumps the package.jsonversionedFiles, so the index.ts literal is never updated and drifts every release. MCP clients therefore receive a wrong se…- Fix: Read the version from package.json at runtime (
createRequire(import.meta.url)('../package.json').version) instead of a literal, or add the index.ts files to FerrFlow's versionedFiles so the bump stays in sync.-
C:/Users/bryan/Nextcloud/workspace/FerrFlow-Org/MCP/README.md- README.md still describes a 4.0.0 single-package server with a 12-tool table (get_stats, health_check, fetch_docs, get_me, list_tokens, create_token, revoke_token, list_orgs, list_projects, list_subscriptions, list_vaults, list_issues). The actual @ferrlabs/mcp is v6.5.0 and registers org-admin (invite/remove member, role change, create/delete team), subscriptions (activate/update/cancel), me (upd…
- Fix: Regenerate the tool table from the registered tools, document all five MCP entrypoints and the HTTP mode/env vars, fix the version, and correct the 'Kubernetes/GitHub/releases' description in the org docs to match what actually ships.
-
C:/Users/bryan/Nextcloud/workspace/FerrFlow-Org/MCP/packages/mcp-core/src/transports/http.ts- README.md (line 25, line 77, step 4 of 'How auth resolution works' line 91) tells users the OAuth flow opens
auth.ferrlabs.comand that it serves the discovery metadata. But http.ts (HttpServerOptions.authorizationServer JSDoc, lines 50-58) explicitly states the metadata host must be api.ferrlabs.com because 'auth.ferrlabs.com is the user-facing login SPA but doesn't serve the metadata documents…- Fix: Document the auth.ferrlabs.com (authorize/login) vs api.ferrlabs.com (token exchange + OAuth metadata) split explicitly in the README so the authorizationServer/FERRLABS_AUTH_URL configuration is unambiguous.
-
C:/Users/bryan/Nextcloud/workspace/FerrFlow-Org/MCP/packages/mcp-core/src/auth/persistence.ts- persistence.ts writes the long-lived FerrLabs session token to
~/.config/ferrlabs/mcp/token.json(or %APPDATA% on Windows) as clear-text JSON. On POSIX it best-effort chmod 0600, but on Windows the chmod is skipped entirely (if (platform() !== 'win32')), so on the platform this audit is running on the token file inherits default ACLs with no tightening. Any process running as the user (or a ba…- Fix: On Windows, restrict the file ACL (e.g. icacls to the current user) or use DPAPI/the OS credential store; document that FERRLABS_MCP_NO_PERSIST=1 keeps it in memory. At minimum warn that the token path should not live inside a synced folder.
-
C:/Users/bryan/Nextcloud/workspace/FerrFlow-Org/MCP/packages/mcp-core/src/transports/http.ts- The HTTP transport (http.ts, used in the Docker image with FERRLABS_MCP_MODE=http on 0.0.0.0:3000) reads the full POST body into memory via readJsonBody with no size limit (
Buffer.concat(chunks)over unbounded chunks) and applies no rate limiting or per-IP throttling before the bearer check. An unauthenticated attacker can POST arbitrarily large bodies (the body is read before auth in handleMcpR…- Fix: Cap the request body size (reject > N bytes) and add basic per-IP rate limiting / connection limits in front of /mcp, or document that it must run behind a gateway (Traefik) that does so.
-
C:/Users/bryan/Nextcloud/workspace/FerrFlow-Org/MCP/packages/mcp-vault/src/tools/secrets.ts- secrets.ts get_secret with
reveal=truereturns the decrypted secret value verbatim in the tool result text (JSON.stringify(secret)where secret includesvalue). Because the result flows into the assistant's context (and from there potentially into client logs, transcripts, or the FerrFleet run transcript if an agent calls it), a single tool call exfiltrates a production secret into a place f…- Fix: Mark the tool with a sensitive/destructive annotation, require an explicit
confirm/acknowledge_revealargument, and consider returning a one-time reference or masked value by default so assistants don't casually splatter live secrets into transcripts.Low
-
C:/Users/bryan/Nextcloud/workspace/FerrFlow-Org/MCP/packages/mcp-core/src/transports/http.ts- In transports/http.ts
applyCorsHeaderssetsAccess-Control-Allow-Originto the request's own Origin header (req.headers.origin ?? '*') together withAccess-Control-Allow-Credentials: trueand a permissive Allow-Headers list. Reflecting any origin while allowing credentials defeats the same-origin policy: any malicious web page the user visits can issue credentialed cross-origin requests to…- Fix: Replace origin reflection with an explicit allowlist (or drop Allow-Credentials, since auth is via Bearer header not cookies). Default
hostto 127.0.0.1, and passenableDnsRebindingProtection: true+allowedHosts/allowedOriginsto StreamableHTTPServerTransport per the MCP security guidance.-
C:/Users/bryan/Nextcloud/workspace/FerrFlow-Org/MCP/packages/mcp/src/tools/docs.ts- docs.ts fetch_docs constrains the host to a fixed PRODUCT_HOSTS allowlist (good — prevents arbitrary SSRF), but appends the caller-supplied
slugto the URL (${host}/${slug}) and calls fetch() with default redirect following. A page on a product site that 3xx-redirects off-domain (e.g. an open redirect at ferrgrowth.com/r?to=...) would cause the server to fetch and return attacker-chosen conten…- Fix: Set
redirect: 'manual'(or 'error') on the fetch and re-validate any redirect Location host against PRODUCT_HOSTS before following; reject slugs containing '://' or starting with '//'.-
C:/Users/bryan/Nextcloud/workspace/FerrFlow-Org/MCP/packages/mcp-growth/src/tools/forms.ts- list_form_submissions (max 500), list_issues (max 200), list_runs (max 100), list_org_audit (max 200) accept only a
limitand forward it as a single query param — there is no offset/cursor/page parameter. An assistant asking 'show all submissions' for a form with thousands of entries gets a silently truncated window with nonexttoken and no indication more exist, so it can't page through. For…- Fix: Add a cursor/offset parameter (or a
before/afterid) to the list tools and surface in the tool description that results are paginated, so assistants can fetch subsequent pages and know when the list is exhausted.Found during a full-codebase audit of the FerrLabs workspace.