Policy and process. The dependency-level audit (what the SDK logs, what the redactor catches) lives in SECURITY-AUDIT.md.
- Do not open a public GitHub issue for security reports.
- Use GitHub Security Advisories for this repository: https://github.com/bitrix24/templates-mcp/security/advisories/new. The form is private to the reporter and the maintainers, lets us iterate on a fix in a private fork, and pins a CVE on publication. Include reproduction steps, affected version, and the impact you observed.
- Acknowledgement within ~5 business days (best-effort, pre-release; no formal SLA until GA). Fix timeline depends on severity.
While the project is pre-release, only the latest tag receives fixes. Once a v0.x line stabilises, this section will list the supported range.
- Webhook URL secret leak. The webhook URL contains a per-user secret. Logger redaction is the primary control — see
server/utils/logger-redactor.tsand the audit pass inSECURITY-AUDIT.md. Any dependency bump that touches the SDK or its logger surface MUST re-run the audit. Redaction is URL-shaped only: if a Bitrix24 REST endpoint ever returns a credential as a JSON value (e.g.{ token: "…" }) and that body lands ingetLogger().info('post/response', …), the redactor will not catch it. No known REST method does this today; tracked as a known limitation inSECURITY-AUDIT.md. - Bearer token leak (HTTP modes).
NUXT_MCP_AUTH_TOKENis the only thing between a public/mcpand tool execution against your portal. It's compared withcrypto.timingSafeEqual. Rotation procedure below. - Prompt injection via tool input. Defensive hardening for LLM-controlled keys lives in
server/utils/v3-filter.tsandwire-coerce.ts; commit history references it as "defensive hardening for toV3Filter / pick against LLM-controlled keys" (PR #41). Re-audit if a new tool builds Bitrix24 REST filters from agent input. - Tool delete operations. Every delete tool gates on
confirmDelete: true(Ground Rule #9 inskills/manage-bx24-template-mcp/SKILL.md). Cascade-destructive deletes layer a second confirm (Rule #10). - DXT bundle. Webhook lives in OS keychain via Claude Desktop's
user_config(sensitive: true). Unpacked bundle lives on disk as plain files — protect with full-disk encryption if the threat model includes physical access.
- Multi-tenant deployment. The Bearer model is single-tenant; a multi-tenant variant needs per-tenant scoping and is not on the roadmap.
- DoS mitigation beyond Docker resource limits.
- Audit log of tool invocations. Planned (pre-GA): retention policy / log shipping when this lands.
| Secret | Where it lives | Rotation procedure |
|---|---|---|
NUXT_BITRIX24_WEBHOOK_URL |
Host .env (production); .env on laptop (local HTTP); OS keychain (DXT) |
Revoke webhook in Bitrix24 portal → create new → update store → docker compose up -d (production) or restart client (DXT). The old URL fails closed (401/403). |
NUXT_MCP_AUTH_TOKEN |
Host .env (production); .env on laptop (local HTTP); not used for DXT |
Generate new (openssl rand -hex 32), update .env, docker compose up -d, update every connected client header. No revocation list — old token is dead the instant the new one is loaded. |
| GitHub feedback PAT 1 | Host .env / laptop .env / DXT user_config |
Revoke PAT on GitHub → create new → update store → restart service. |
- Renovate is configured (
renovate.json); PRs open on the configured schedule (weekday 02:00–07:00 Europe/Minsk), withvulnerabilityAlertsraised out-of-schedule. - Image updates are split by file, not duplicated: Dockerfile base images → Dependabot (
.github/dependabot.yml); docker-compose infra images (digest-pinnednginx-proxy/acme-companion/watchtower) → Renovate'sdocker-composemanager. The full who-watches-what matrix and the step-by-step CVE response live in Patching upstream CVEs in pinned images. - Merge cadence: Renovate auto-merges
patch/pin/digestupdates;minor,major, and all docker-compose infra-image bumps are held for review. PRs open in the weekday 02:00–07:00 (Europe/Minsk) window. - Major bumps to
@bitrix24/b24jssdk,@modelcontextprotocol/sdk,zod, or@nuxtjs/mcp-toolkitMUST trigger:- Re-run of the SDK-logger audit in
SECURITY-AUDIT.md. - Manual smoke of all three transports (Remote HTTP, Local HTTP, DXT) — see
MANUAL-TEST-PHRASES.md.
- Re-run of the SDK-logger audit in
- The DXT bundle pins zod via a workaround (
mcp-stdio/nuxt-shims.tsforces init). zod major bumps require revalidating that workaround.
The reverse-proxy stack pins infra images by SHA digest — nginx-proxy and acme-companion in docker-compose.server.yml, watchtower in docker-compose.watchtower.yml. A digest pin is immutable on purpose: docker compose pull, make redeploy, and Watchtower will not pull an upstream security fix on their own — the digest has to be bumped in git. So for every pinned image we own the patch cadence; it is not automatic.
| Surface | Owner | Behaviour |
|---|---|---|
| npm dependencies | Renovate (renovate.json, vulnerabilityAlerts: true) |
PRs on schedule (weekday 02:00–07:00 Europe/Minsk); security alerts out-of-schedule |
| GitHub Actions | Renovate (helpers:pinGitHubActionDigests) |
SHA-pinned + # vX.Y.Z comment |
| Dockerfile base images | Dependabot (.github/dependabot.yml) |
mutable-tag bumps, weekly |
| docker-compose infra images | Renovate docker-compose manager |
tag and @sha256 bumped together, all docker-compose*.yml |
Dependabot's docker ecosystem only sees the canonical
docker-compose.yml, not split names likedocker-compose.server.yml(dependabot-core#12134) — that is why the Compose infra images are routed to Renovate. Don't re-enable Dependabot for compose; the two would race on the same image.Two
renovate.jsonpackageRules keep this clean: the app's own image (docker-compose.yml) is excluded from the docker-compose manager (it ships via CIv*tags), and infra-image bumps are never auto-merged — every nginx-proxy / acme-companion / watchtower change requires review.
- Locate every copy. Grep the repo for the image. The same nginx engine ships inside our
nginx-proxyreverse proxy — this is plain upstream nginx, not thebx-nginxpackage from the Bitrix virtual machine (VMBitrix). A "bx-nginx" advisory does not apply to this project directly (we don't run VMBitrix; we talk to Bitrix24 over REST). The underlying nginx CVE, however, does apply tonginx-proxy. Keep these two layers separate when triaging. - Check the real version, not the wrapper's. Wrapper images (
nginx-proxy,acme-companion) carry their own version number; find the bundled nginx —docker exec nginx-proxy nginx -v, or the wrapper's release notes — and compare that against the CVE's fixed version. - Bump to a patched release and re-pin the digest. Update the tag and the
@sha256:digest — never leave the old digest behind (that would re-introduce the vulnerable image). Get the multi-arch manifest digest withdocker buildx imagetools inspect <ref>(ordocker pull <ref>→ theDigest:line). Never hand-type or guess a digest. - Roll it out. The digest change in git is what makes
make redeploy/docker compose pullactually fetch the fix on the host. - Verify the patched version is live. e.g.
docker exec nginx-proxy nginx -von the host must report the fixed version.
If Renovate has already opened the bump PR, prefer merging that — it computes the new digest for you. The manual steps above are the break-glass path when you can't wait for the next scheduled run.
commitlintruns on every commit message (conventional commits).- ESLint enforces no direct
actions.*calls; onlycallV2/callV3/batchV2/batchV3fromserver/utils/sdk-helpers.ts. - Planned (pre-GA): a secret-scanning hook (e.g.
gitleaks) in pre-commit and CI. Today the only line of defence is reviewer eyes.
Footnotes
-
All transports use
NUXT_GITHUB_FEEDBACK_TOKEN— HTTP modes read it via Nuxt runtime-config, and the DXT manifest injects that same name.mcp-stdio/nuxt-shims.tsresolvesNUXT_GITHUB_FEEDBACK_TOKEN ?? GITHUB_FEEDBACK_TOKEN, keeping the un-prefixedGITHUB_FEEDBACK_TOKENonly as a back-compat fallback for older bundles. ↩