From b7fa160c9ce3a9ce732d5271dedb9b912aa01383 Mon Sep 17 00:00:00 2001 From: elfrost <5491654+elfrost@users.noreply.github.com> Date: Tue, 22 Sep 2026 09:20:47 -0400 Subject: [PATCH] =?UTF-8?q?docs:=20scan=20#109=20=E2=80=94=20overwirehq/cl?= =?UTF-8?q?aude-code-telegram=20(well-documented=20execution=20boundary,?= =?UTF-8?q?=20dependency-currency=20finding,=20post-only)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Post-only clean-scan write-up. 54 findings at medium+, 0 first-party defects after curation. The one actionable item is dependency currency in transitive poetry.lock pins, split by reachability: 17 opt-in-only (webhook server, token auth, MCP all default-off), 13 unconditionally installed. The write-up's angle is the project's unusually precise security documentation: the can_use_tool boundary is scoped in the docs to exactly six file tools, its gaps (Grep/Glob/LS left to the OS sandbox) are named and tracked (#219 fixed, #221 open), and the pull_request_target workflow carries a full threat-model header. First-party scanner hits were all FP/by-design: sqlalchemy identifier FPs, CI SHA-pin hardening, placeholder + redaction-test secrets. Coverage partial and stated: pip-audit resolved no deps from a dynamic-deps pyproject; Trivy's poetry.lock read carried the run (same blind spot as #108). Co-Authored-By: Claude Opus 4.8 --- docs/index.md | 7 +- docs/scan-log.md | 4 +- docs/scans/overwirehq-claude-code-telegram.md | 203 ++++++++++++++++++ 3 files changed, 210 insertions(+), 4 deletions(-) create mode 100644 docs/scans/overwirehq-claude-code-telegram.md diff --git a/docs/index.md b/docs/index.md index d0301e3..f8c4480 100644 --- a/docs/index.md +++ b/docs/index.md @@ -1,7 +1,7 @@ --- layout: default title: AI PatchLab Scans -description: "108 curated security scans of open-source AI agents, MCP servers and LLM apps - 24 confirmed fixes, run local-first with Semgrep, Gitleaks, Trivy and pip-audit." +description: "109 curated security scans of open-source AI agents, MCP servers and LLM apps - 24 confirmed fixes, run local-first with Semgrep, Gitleaks, Trivy and pip-audit." --- # AI PatchLab Scans @@ -20,7 +20,7 @@ remediation and confidence rules to normalize the findings. > **Want this run privately against your own codebase?** I do independent > security review of AI agents, MCP servers, and LLM apps — -> [**work with me →**]({{ '/work-with-me' | relative_url }}). 108 scans, 24 confirmed fixes, methodology in the open. +> [**work with me →**]({{ '/work-with-me' | relative_url }}). 109 scans, 24 confirmed fixes, methodology in the open. > **OpenAI just launched [Daybreak](https://openai.com/index/daybreak-securing-the-world/) and Patch the Planet.** > Same remediation loop, opposite trade-off: their path is a cloud frontier model; @@ -103,7 +103,7 @@ login and static assets. Fifty-two flagged, none reported. ## All scans -108 scans, newest first. **Findings** is the raw count the tools produced; +109 scans, newest first. **Findings** is the raw count the tools produced; **Real** is what survived curation. The gap between those two columns is the entire job. @@ -118,6 +118,7 @@ filed, which is the usual outcome of a clean scan. | Date | Repository | Findings | Real | Outcome | | --- | --- | ---: | --- | --- | +| 2026-09-22 | [overwirehq/claude-code-telegram](scans/overwirehq-claude-code-telegram.html) | 54 | 0 first-party — dependency | — | | 2026-09-21 | [HarnessRouter/harnessrouter](scans/harnessrouter-harnessrouter.html) | 117 | 1 real — dependency | — | | 2026-09-20 | [TencentCloud/Octop](scans/tencentcloud-octop.html) | 300 | 1 real — withheld | private | | 2026-09-19 | [hydropix/TranslateBooksWithLLMs](scans/hydropix-translatebookswithllms.html) | 55 | 1 real — withheld | private | diff --git a/docs/scan-log.md b/docs/scan-log.md index cd43cb1..df378bf 100644 --- a/docs/scan-log.md +++ b/docs/scan-log.md @@ -6,7 +6,9 @@ description: "The complete AI PatchLab scan log: every public repository scanned # Full scan log -Every scan in the series, newest first, with the summary written on the day of the scan. 108 scans. For the compact index, see the [scan log home]({{ '/' | relative_url }}). +Every scan in the series, newest first, with the summary written on the day of the scan. 109 scans. For the compact index, see the [scan log home]({{ '/' | relative_url }}). + +- **2026-09-22** — [overwirehq/claude-code-telegram](scans/overwirehq-claude-code-telegram.html) — 54 findings at `medium+` (1 critical, 21 high, 30 medium), **0 first-party defects — dependency currency, post-only** — a **Telegram bot that gives authorised users remote access to Claude Code**, i.e. it runs file and Bash tools on a host machine by design, so the whole security surface *is* the boundary around that execution (2k★, MIT, org-backed; active — 15 merged PRs from 7 authors and 9 closed issues in 60 days; strict-norm: real `SECURITY.md`, PVR enabled). Picked with the manual queue at zero. **The story is what precise security documentation looks like.** The real boundary is the `can_use_tool` callback that stops Claude — when steered off-course by content it reads mid-task — from acting outside the approved directory, and the maintainers know and *state* exactly what it covers: path validation is scoped in the docs to precisely six tools (`Read`, `Write`, `Edit`, `MultiEdit`, `NotebookEdit`, `NotebookRead`), while `Grep`/`Glob`/`LS` are deliberately left to the OS sandbox. Reading that as "Read is guarded but Grep is not, so the boundary is incomplete" is the plausible-but-wrong finding this site exists to resist — the boundary is [advertised, not accidental](scans/tracecathq-tracecat.html), and `ROADMAP-v2.md` even records the SDK's own `CanUseToolShadowedWarning` naming every tool the callback will never see, filed as tracking item #221. Two more choices earn credit: guarded tools are deliberately *stripped from* the SDK `allowed_tools` list (a pre-approved tool never produces a `can_use_tool` request, so leaving them in would render the checks silently inert — [issue #219](https://github.com/overwirehq/claude-code-telegram/issues/219)), and `autoAllowBashIfSandboxed` is disabled whenever the boundary checks must run, closing a second bypass. That is a maintainer who traced how the framework resolves permissions rather than trusting that a config allow-list is an enforcement boundary — it isn't, and the code says so. **When the machine is built and documented this carefully, the finding moves to the dependency manifest.** The 30 Trivy advisories in `poetry.lock` split cleanly by reachability: **17 are opt-in-only and not reachable on a default install** — `starlette` (6) and `python-multipart` (3) need the FastAPI webhook server (`ENABLE_API_SERVER=false` default); `pyjwt` (5) needs token auth (`ENABLE_TOKEN_AUTH=false` default, and the SECURITY.md documents token auth as non-functional, [#58](https://github.com/overwirehq/claude-code-telegram/issues/58)); the MCP SDK highs (3) need MCP enabled — while **13 are unconditionally installed** (`anyio` incl. the critical IDNA/TLS advisory, the `cryptography`/OpenSSL cluster, `urllib3`, `idna`, `requests`, `pydantic-settings`, `python-dotenv`) and are the genuine currency gap. The direct deps are current; the drift is transitive, which the repo's existing `.github/dependabot.yml` (configured for *version* updates, not *security* updates) will not raise on its own. **No advisory filed** — there is no first-party vulnerability. Of the other 24: 16 `github-actions-mutable-action-tag` (SHA-pin hardening); one `pull_request_target` checkout wrapped in a 40-line threat-model header (read-only tool allowlist, secrets scrubbed, "worst case is a prompt-injected review comment" — [the trigger decides severity](scans/lightseekorg-tokenspeed.html)); two `sqlalchemy-execute-raw-query` [identifier FPs](scans/aurelio-labs-semantic-router.html) (only a generated run of `?` placeholders is interpolated, values bound via `execute(query, params)`); and three gitleaks hits that are a `your-api-secret` placeholder plus two fake tokens in a test asserting the project's own `_redact_secrets()` helper scrubs them ([credited defence](scans/realiti4-claude-swap.html)). Coverage `partial` and honestly so: pip-audit resolved **no dependencies** from a `pyproject.toml` declaring `dynamic = ["dependencies"]` — reading identically to "clean" — and Trivy's `poetry.lock` read carried the run, the [same dependency blind spot](scans/harnessrouter-harnessrouter.html) as yesterday reached by a different manifest shape. - **2026-09-21** — [HarnessRouter/harnessrouter](scans/harnessrouter-harnessrouter.html) — 117 findings (2 critical, 42 high, 70 medium), **1 real — dependency currency, post-only** — a **self-hosted control plane that turns agent CLIs (Codex, Claude Code, Hermes, and a dozen more) into a single OpenAI-compatible API** (1.7k★, Apache-2.0, org-backed with a hosted "Cloud" edition; very active — 91 merged PRs from 5 authors in 60 days). Picked with the manual queue at zero: strict-norm (real `SECURITY.md`, PVR enabled, commercial backing), which is a fair target when the backlog is clear. **The story here is a well-built authorization layer that survived the sweep the series usually breaks projects on, so the one finding moved to the dependency manifest.** The route inventory — 113 gateway routes — comes back with exactly five answering without a credential once the project's own identity idioms (`_owned_session`, `_pub_org_member`, `_principal`) are resolved: `/v1/uhp`, `/healthz`, `/readyz`, `/version`, and `/share/{token}` where the unguessable token *is* the credential. That is the [name-matched sweep](scans/mai-with-u-maibot.html) returning empty for the right reason. Two design choices earn explicit credit: the self-hosted BFF stamps its internal trust key onto a gateway call **only for a request carrying a valid session cookie** — an earlier build stamped it unconditionally, and the code comments document catching and fixing exactly that [conditional-verification](scans/sentelabsai-openexecutive.html) class before I arrived — and the gateway **binds loopback with only the UI port published**, so the [DNS-rebinding shape](scans/liaohch3-claude-tap.html) that has caught several desktop apps here does not apply (the published surface is the session-gated Next.js app with `X-Frame-Options: DENY`). **The actionable finding is dependency currency, and it only surfaced because the two dependency tools disagreed about whether there was anything to scan.** pip-audit reported **no manifest** — it scans the repo root, and the requirements live in `gateway/requirements.txt` and `runner/requirements.txt` one level down — which on a monorepo reads identically to "clean". Trivy's whole-tree walk read both and found the pinned `next` **15.5.23** is one patch behind **15.5.24**, which fixes two criticals: [CVE-2026-75604](https://github.com/advisories) (Windows-hosted RCE — **dropped, the image is Linux**) and [GHSA-2xp9-vwfh-vxw4](https://github.com/advisories) (AVIF image-optimizer RCE — the optimizer runs at its default-enabled setting and the middleware matcher **excludes `_next/image`**, so the surface is reachable unauthenticated; default-empty `remotePatterns` constrains full exploitation, and with no Docker Linux engine available I could not run the primitive, so I claim the reachable surface and the currency gap, not a demonstrated RCE). `gateway/requirements.txt` also carries `PyJWT` 2.10.1 (CVE-2026-48526, auth-bypass) and `cryptography`/`aiohttp`/`python-multipart` CVEs — low reachability self-hosted (gateway loopback, `HR_IDENTITY_MODE=off`) but the hosted build shares the code. **No advisory filed**: the actionable item is a published upstream CVE with a one-line fix (`next >=15.5.24` + a `dependabot.yml` covering npm *and* pip, of which there is none today), not a first-party defect, and the "run the exploit primitive before filing" rule forbids an RCE-shaped advisory I can't demonstrate. Of the other 116: 29 `github-actions-mutable-action-tag` (SHA-pin hardening); 11 workflow shell-injection all on `workflow_dispatch`/`push:tags` triggers ([trigger decides severity](scans/lightseekorg-tokenspeed.html) — write access already required); 13 subprocess-audit hits in `runner/`, which runs agent CLIs as a per-session uid ([running code is the product](scans/realiti4-claude-swap.html)); a `ws://` `detect-insecure-websocket` false positive (matched a `.replace()` scheme-transform string); a test-fixture key in `gateway/tests/`. Coverage `partial` and honestly so — Semgrep's errors are non-Python config/data files, no first-party module skipped. The backlog item is real and is the day's [tooling note](scans/whiteguo233-openbiliclaw.html): `scan_dependency` is root-only, so on a monorepo it silently disagrees with Trivy — it should descend into subdirectory manifests or emit a louder meta-finding. - **2026-09-20** — [TencentCloud/Octop](scans/tencentcloud-octop.html) — 300 findings (300 above the medium floor, 3 of them scan-coverage meta findings), **1 real — withheld** — a **self-hosted, multi-user, multi-agent AI assistant** (4.3k★, MIT, created July 2026 by TencentCloud; very active — 26 merged PRs from 6+ authors in 60 days). Picked on responsiveness and on shape: a multi-user control plane with a real permission system is exactly where an authorization gap hides. The class, stated without a recipe: **one sensitive control-plane surface is gated by authentication alone, not by any permission key — and no key for it exists in the catalogue at all.** Octop ships a proper default-deny permission model — 27 module keys, a `require_permission(key)` factory, invited users created with the `USER` role and an *empty* permission set — and enforces it consistently across the API except for this one router, which sits behind bare "are you logged in?". A zero-permission invited user (the invite-onboarding default) reaches it, and the confinement that should contain the blast radius is a per-user policy left **unset (unrestricted) by default**; the shipped container image broadens that default scope well beyond any single user's own workspace. This is the [inert-security-flag](scans/mnemosyne-oss-mnemosyne.html) family inverted (the pattern is applied everywhere *but* one place) and the [scan-the-seam](scans/mljar-mercury.html) shape (the project's own permission catalogue is the contract; the finding is the route-group it forgot to include). Post-auth, not pre-auth — detail (route set, reachability chain, container specifics) withheld pending a fix. What made the write-up honest was discarding two coherent-but-wrong findings first: the `tarfile.extractall` hits both pass `filter=tarfile.data_filter` (credited FP), and the setup-takeover chain collapsed on [running the remedy against the real deployment](scans/roflcoopter-viseron.html) — the Docker entrypoint pre-creates the admin before the server listens, and every setup route re-checks `user_count == 0` server-side, so no wizard-race state is reachable. The setup wizard is genuinely hardened (CLI one-time password required by default, `127.0.0.1` bind default, a lockdown middleware that 503s until an admin exists). Of the 297 tool findings none was an exploitable vuln: the lone Critical is a valid `anyio` 4.14.1→4.14.2 bump (CVE-2026-63374, IDNA-only TLS spoofing on an already-redirected connection); 61+31 SQL hits are the [identifier false positive](scans/mnemosyne-oss-mnemosyne.html) at record volume (`_scope_filter` builds `"col = ?"` fragments, values bound as `?`); three `run-shell-injection` are `${{ github.ref_name }}` on a push-tags trigger (needs push access — the [trigger decides severity](scans/lightseekorg-tokenspeed.html)); the SSH/API-key hits are a doc placeholder, a `_SIGN_SECRET` commented as *public* third-party material, and a test fixture; six `logger-credential-leak` log only `.kind`/exceptions. Reported privately by email (repo PVR is disabled); GitHub private vulnerability reporting off, SECURITY.md forbids public issues. Scan coverage incomplete: pip-audit timed out at 300s, but **Trivy read `uv.lock`** so the dependency surface was still examined; Semgrep's 50 errors (0 timeouts) are all non-Python config/data/test files — no first-party Python module skipped. diff --git a/docs/scans/overwirehq-claude-code-telegram.md b/docs/scans/overwirehq-claude-code-telegram.md new file mode 100644 index 0000000..34316ac --- /dev/null +++ b/docs/scans/overwirehq-claude-code-telegram.md @@ -0,0 +1,203 @@ +--- +layout: default +title: "overwirehq/claude-code-telegram: security scan" +date: 2026-09-22 +--- + +# overwirehq/claude-code-telegram - security scan + +**Repository:** [overwirehq/claude-code-telegram](https://github.com/overwirehq/claude-code-telegram) +**Commit scanned:** `5016aee` (v1.8.0) +**Scan date:** 2026-09-22 +**Disclosure status:** post-only — nothing first-party survived curation. The one actionable item is dependency currency in a transitive-dependency lockfile, not a code defect. This repository has a real SECURITY.md and private vulnerability reporting enabled; no advisory was filed because there is no first-party vulnerability to file. + +## Summary + +| Severity | Count | +| --- | ---: | +| Critical | 1 | +| High | 21 | +| Medium | 30 | +| Low | 0 | +| Info | 2 | + +**Total findings:** 54 at `--min-severity medium` (0 first-party defects after curation; one dependency-currency finding composed of the reachable transitive CVEs) + +This is a bot that, by design, runs Claude Code with file and Bash tools on a +host machine on behalf of authorised Telegram users. The whole security surface +is therefore *the boundary around that execution* — the approved-directory +sandbox, the user allowlist, the tool-permission callback. That is exactly where +I spent the scan, and it is the part the maintainers have built and **documented** +with more care than almost anything else in this series. + +## Scan coverage + +| Tool | Status | Detail | +| --- | --- | --- | +| `semgrep` | `ran` | Ran without reporting a coverage problem. | +| `gitleaks` | `ran` | Ran without reporting a coverage problem. | +| `trivy` | `ran` | Ran without reporting a coverage problem. | +| `dependency-scan` | `partial` | A shipped lockfile was not covered by the dependency scan | +| `ai-security-review` | `not_run` | disabled by default (ADR-010) | + +**Coverage complete:** no — and it is the [same dependency blind spot](harnessrouter-harnessrouter.html) +as yesterday, reached by a different route. `dependency-scan` (pip-audit) found +the root `pyproject.toml`, but this project declares `dynamic = ["dependencies"]` +with the real list under `[tool.poetry.dependencies]`, so pip-audit resolved +**no dependencies** and returned an empty report — which reads identically to +"clean". Trivy reads `poetry.lock` directly, so it saw the pinned graph and +produced every dependency finding below. The lesson stands from the HarnessRouter +scan: when two dependency tools disagree about whether there is anything to scan, +the one that found nothing is usually the one that was pointed at the wrong file. +Everything of dependency interest here came from Trivy. + +## Top findings + +There is no first-party finding to lead with — the honest headline is that the +execution boundary held up. The one actionable item is dependency currency, and +it is only legible once you split it by reachability. + +### 1. Transitive lockfile currency — split cleanly by what the default install actually runs + +- **File:** `poetry.lock` +- **Tool:** trivy +- **Confidence:** high that the CVEs are real; the reachability split is the finding +- **Why it matters:** 30 advisories land in `poetry.lock`, and they divide into two + groups that deserve very different treatment: + - **Opt-in only (17), not reachable on a default install.** `starlette` (6) and + `python-multipart` (3) only execute if the FastAPI webhook server is turned on + — `ENABLE_API_SERVER` is `False` by default. `pyjwt`/`python-pyjwt` (5) matter + only on the token-auth path — `ENABLE_TOKEN_AUTH` is `False` by default, **and** + the project's own SECURITY.md documents token auth as incomplete and unusable + end to end (backed by `InMemoryTokenStorage`, tracked in + [#58](https://github.com/overwirehq/claude-code-telegram/issues/58)). The MCP + Python SDK highs (3) need MCP enabled. None of these is on the default + (long-polling, `ALLOWED_USERS`-gated) deployment. + - **Unconditionally installed (13), a genuine currency gap.** `anyio` (incl. the + critical IDNA/TLS host-encoding advisory), the `cryptography` + bundled OpenSSL + cluster, `urllib3`, `idna`, `requests`, `pydantic-settings`, and `python-dotenv` + ship on every install. These are the ones to refresh. +- **Recommendation:** Refresh `poetry.lock`. The direct dependencies are current + (`python-telegram-bot ^22.6`); the drift is entirely in transitive pins, which + is the specific thing the existing `.github/dependabot.yml` will **not** fix on + its own (see *Notes on the tool*). + +## Why the rest were dismissed + +Of 54 findings at `medium+`, zero were first-party defects. The distribution: + +| Reason | Findings | +| --- | ---: | +| `not-reachable` | 17 | +| `by-design` | 17 | +| `confirmed-real` (dependency currency) | 13 | +| `sql-identifier-fp` | 2 | +| `test-or-fixture-path` | 2 | +| `placeholder-secret` | 1 | + +The largest first-party family is `by-design` (17), and it is worth one sentence +because it is *well* by-design: 16 are `github-actions-mutable-action-tag` (SHA-pin +hardening on CI action refs, a best-practice nudge), and the 1 remaining is a +`pull_request_target` checkout that the maintainers have wrapped in a 40-line +threat-model header — the workflow runs with repository secrets against untrusted +PR code precisely *because* `pull_request_target` sources the workflow from the +default branch (a PR cannot edit it to reach the secrets), and the tool allowlist +handed to the review action is read-only plus `gh pr comment`, no Write/Edit/Bash, +with cloud and Actions secrets scrubbed from subprocess environments. They name +the residual themselves: "worst case is a prompt-injected review comment." That is +[the trigger deciding the severity](lightseekorg-tokenspeed.html), reasoned out in +the file. The two `sqlalchemy-execute-raw-query` highs are the +[recurring identifier false positive](aurelio-labs-semantic-router.html): only a +generated run of `?` placeholders is interpolated into the query string +(`",".join("?" for _ in slugs)`), and every value is bound through +`conn.execute(query, params)`. The three gitleaks hits are a literal +`your-api-secret` placeholder in `docs/setup.md` and two fake tokens in a test +that asserts the project's `_redact_secrets()` helper scrubs them — a +[credited defence](realiti4-claude-swap.html), not a leak. + +## Patterns observed + +The story here is not a bug; it is what precise security documentation looks like, +and it is rare enough to be worth describing. + +This project's real security boundary is the `can_use_tool` callback in +`src/claude/sdk_integration.py` — the thing that stops Claude, when steered +off-course by content it reads mid-task, from writing or reading outside the +approved directory. The interesting part is not that the callback exists; it is +that the maintainers **know and state exactly what it does and does not cover.** +The docs scope path validation to precisely six tools — `Read`, `Write`, `Edit`, +`MultiEdit`, `NotebookEdit`, `NotebookRead` — and no others. Tools like `Grep`, +`Glob`, and `LS` are in the default allow-list and are *not* path-validated by the +callback; agentic mode relies on the OS sandbox for those instead. A scanner-style +reading of that gap ("Read is guarded but Grep is not, so the boundary is +incomplete") is the kind of plausible-but-wrong finding this whole site exists to +resist — because the boundary is [advertised, not +accidental](tracecathq-tracecat.html). The project's `ROADMAP-v2.md` goes further +and records that the SDK itself emits a `CanUseToolShadowedWarning` at connect time +naming every tool the callback will never see, and files it as a tracking item +(#221). When the code and the docs agree on the boundary *and* the docs name the +gap, the gap is a design decision, not a vulnerability. + +Two more things earn credit. The `can_use_tool` wiring carries a comment trail +documenting [issue #219](https://github.com/overwirehq/claude-code-telegram/issues/219): +guarded tools are deliberately *stripped from* the SDK's `allowed_tools` list, +because a tool pre-approved by the CLI's permission engine never produces a +`can_use_tool` control request — so leaving them in the allow-list would render +the checks silently inert. They also disable `autoAllowBashIfSandboxed` whenever +the boundary checks are meant to run, because auto-approved sandboxed Bash is a +second bypass of the same control request. That is a maintainer who has actually +traced how the framework resolves permissions, rather than trusting that a +config-file allow-list is an enforcement boundary. It is not — and the code says +so, out loud. + +When a project is built and documented this carefully, the finding moves to the +dependency manifest. That is where it moved. + +## Notes on the tool + +Two backlog items, both already known and both reconfirmed here: + +1. **`dependency-scan` reads `pyproject.toml` but not the Poetry lock when + dependencies are `dynamic`.** pip-audit resolved zero dependencies from a + `[project]` table declaring `dynamic = ["dependencies"]`, and returned an empty + report that would render as a clean Python surface. Trivy's `poetry.lock` read + is what carried the run. `scan_dependency` should treat a `dynamic`-dependency + `pyproject.toml` with a sibling `poetry.lock` as a lockfile scan, or at minimum + emit a louder meta-finding when it resolves nothing but Trivy reports pip + advisories. This is the same underlying gap as the + [HarnessRouter monorepo case](harnessrouter-harnessrouter.html), reached by a + different manifest shape. + +2. **The reachability split is manual.** The 17 opt-in-only CVEs are dismissible + only because I read the defaults (`ENABLE_API_SERVER`, `ENABLE_TOKEN_AUTH`, + `enable_mcp` all `False`) and the SECURITY.md note that token auth is + non-functional. The scanner reports all 30 at face value. A "feature-gated + dependency" signal — CVEs whose reachable surface is behind a default-off flag — + would be a genuinely useful enrichment, but it needs per-project config + knowledge the current rules do not have. + +The dependency finding itself also has a coverage nuance worth stating plainly: +this repository **does** run Dependabot (`.github/dependabot.yml`), but it is +configured for *version* updates — three named direct dependencies weekly, the +rest grouped minor/patch monthly. Transitive-only CVEs like these are surfaced by +Dependabot *security* updates, a separate repository-level toggle, not by version +updates. So the lockfile can carry published transitive CVEs despite a Dependabot +config being present. That is not a criticism of the config; it is the reason the +finding exists at all. + +## Disclosure timeline + +- 2026-09-22 — scan run; curated to zero first-party defects and one + dependency-currency finding +- 2026-09-22 — public post (this page). No advisory filed: there is no first-party + vulnerability, and dependency currency with a clean reachability split is not an + advisory-shaped item. A short courtesy note via the repository's enabled private + vulnerability reporting would be reasonable if the maintainer wants the transitive + refresh on their radar, since Dependabot version-updates will not raise it. + +## Reproduce + +```bash +git clone https://github.com/overwirehq/claude-code-telegram /tmp/scan-target +python scanner/run_scan.py --repo /tmp/scan-target --reports-dir ./reports/overwirehq-claude-code-telegram --min-severity medium +```