Mostly safe — a couple of notes worth reading.
Scanned 5/3/2026, 7:04:06 PM·Cached result·Fast Scan·45 rules·How we decide ↗
AIVSS Score
Low
Severity Breakdown
0
critical
4
high
35
medium
5
low
MCP Server Information
Findings
This package presents moderate security concerns with a B grade and 61/100 safety score, driven primarily by 35 medium-severity issues concentrated in server configuration and readiness gaps. The four high-severity findings include vulnerabilities in ANSI escape injection handling, resource exhaustion risks, and one insecure container image, along with a vulnerable dependency that should be addressed before deployment. While no critical issues were detected, the configuration weaknesses and injection vectors warrant remediation before using this in production environments.
No known CVEs found for this package or its dependencies.
Scan Details
Want deeper analysis?
Fast scan found 44 findings using rule-based analysis. Upgrade for LLM consensus across 5 judges, AI-generated remediation, and cross-file taint analysis.
Building your own MCP server?
Same rules, same LLM judges, same grade. Private scans stay isolated to your account and never appear in the public registry. Required for code your team hasn’t shipped yet.
Showing 1–30 of 44 findings
44 findings
TLS certificate verification is disabled on an outbound HTTP client. Any MITM in the network path can intercept and modify requests / responses — credentials, tokens, and tool output flow over a channel with no integrity guarantee. Python requests / httpx: drop `verify=False`. If the peer is using a private CA, set `verify="/path/to/ca-bundle.pem"` or configure the system trust store. Node TS axios / fetch: drop `rejectUnauthorized: false` from the agent / `httpsAgent` options. Same private-CA
Evidence
| 1638 | it("accepts TLS handshakes (wss)", async () => { |
| 1639 | await new Promise<void>((resolve, reject) => { |
| 1640 | const socket = tlsConnect( |
| 1641 | { port: wsPort, host: "127.0.0.1", rejectUnauthorized: false }, |
| 1642 | () => { |
| 1643 | expect(socket.authorized || !socket.authorized).toBe(true); // handshake completed |
| 1644 | socket.end(); |
Remediation
Drop the verify-disable flag. If the peer presents a private CA: - Python: pass `verify="/path/to/ca.pem"` or trust the system store - Node: `new https.Agent({ ca: fs.readFileSync("ca.pem") })` - Go: load the CA via `x509.NewCertPool().AppendCertsFromPEM(...)` and set `tls.Config.RootCAs` Self-signed certificates: import the cert into the OS trust chain rather than disabling verification per-call.
TLS certificate verification is disabled on an outbound HTTP client. Any MITM in the network path can intercept and modify requests / responses — credentials, tokens, and tool output flow over a channel with no integrity guarantee. Python requests / httpx: drop `verify=False`. If the peer is using a private CA, set `verify="/path/to/ca-bundle.pem"` or configure the system trust store. Node TS axios / fetch: drop `rejectUnauthorized: false` from the agent / `httpsAgent` options. Same private-CA
Evidence
| 2120 | In a separate terminal: |
| 2121 | |
| 2122 | ```sh |
| 2123 | curl -k https://localhost:3000/health |
| 2124 | ``` |
| 2125 | |
| 2126 | Expected: `{"status":"ok"}` |
Remediation
Drop the verify-disable flag. If the peer presents a private CA: - Python: pass `verify="/path/to/ca.pem"` or trust the system store - Node: `new https.Agent({ ca: fs.readFileSync("ca.pem") })` - Go: load the CA via `x509.NewCertPool().AppendCertsFromPEM(...)` and set `tls.Config.RootCAs` Self-signed certificates: import the cert into the OS trust chain rather than disabling verification per-call.
TLS certificate verification is disabled on an outbound HTTP client. Any MITM in the network path can intercept and modify requests / responses — credentials, tokens, and tool output flow over a channel with no integrity guarantee. Python requests / httpx: drop `verify=False`. If the peer is using a private CA, set `verify="/path/to/ca-bundle.pem"` or configure the system trust store. Node TS axios / fetch: drop `rejectUnauthorized: false` from the agent / `httpsAgent` options. Same private-CA
Evidence
| 251 | it("accepts TLS handshakes (wss)", async () => { |
| 252 | await new Promise<void>((resolve, reject) => { |
| 253 | const socket = tlsConnect( |
| 254 | { port: wsPort, host: "127.0.0.1", rejectUnauthorized: false }, |
| 255 | () => { |
| 256 | expect(socket.authorized || !socket.authorized).toBe(true); |
| 257 | socket.end(); |
Remediation
Drop the verify-disable flag. If the peer presents a private CA: - Python: pass `verify="/path/to/ca.pem"` or trust the system store - Node: `new https.Agent({ ca: fs.readFileSync("ca.pem") })` - Go: load the CA via `x509.NewCertPool().AppendCertsFromPEM(...)` and set `tls.Config.RootCAs` Self-signed certificates: import the cert into the OS trust chain rather than disabling verification per-call.
TLS certificate verification is disabled on an outbound HTTP client. Any MITM in the network path can intercept and modify requests / responses — credentials, tokens, and tool output flow over a channel with no integrity guarantee. Python requests / httpx: drop `verify=False`. If the peer is using a private CA, set `verify="/path/to/ca-bundle.pem"` or configure the system trust store. Node TS axios / fetch: drop `rejectUnauthorized: false` from the agent / `httpsAgent` options. Same private-CA
Evidence
| 330 | port, |
| 331 | path: "/health", |
| 332 | method: "GET", |
| 333 | rejectUnauthorized: false, |
| 334 | }, |
| 335 | (res) => { |
| 336 | expect(res.statusCode).toBe(200); |
Remediation
Drop the verify-disable flag. If the peer presents a private CA: - Python: pass `verify="/path/to/ca.pem"` or trust the system store - Node: `new https.Agent({ ca: fs.readFileSync("ca.pem") })` - Go: load the CA via `x509.NewCertPool().AppendCertsFromPEM(...)` and set `tls.Config.RootCAs` Self-signed certificates: import the cert into the OS trust chain rather than disabling verification per-call.
Service binds to 0.0.0.0 — all network interfaces. For MCP servers that only need to talk to a single parent process, bind to 127.0.0.1 (or a Unix domain socket) instead.
Evidence
| 686 | test("DRAWIO_MCP_HOST maps to --host", () => { |
| 687 | const result = envToArgs({ DRAWIO_MCP_HOST: "0.0.0.0" }); |
| 688 | expect(result).toEqual(["--host", "0.0.0.0"]); |
| 689 | }); |
| 690 | |
| 691 | test("missing DRAWIO_MCP_HOST produces no args", () => { |
Remediation
Bind to 127.0.0.1 for local-only access. If cross-host access is truly required, put the service behind an authenticated reverse proxy rather than exposing it on 0.0.0.0.
User-controlled value printed to terminal without ANSI escape sanitization. Malicious input can inject cursor-control sequences, rewrite earlier output, or hide shell commands from the operator.
Evidence
| 184 | result.then( |
| 185 | (resolved) => sendReply(true, resolved), |
| 186 | (error) => { |
| 187 | console.error( |
| 188 | `[plugin] Async tool ${toolName} failed for request ID ${request.__request_id}:`, |
| 189 | error, |
| 190 | ); |
| 191 | sendReply(false, error); |
| 192 | }, |
| 193 | ); |
Remediation
Strip C0/C1 control codes before printing user-controlled values. Python: re.sub(r"[\x00-\x08\x0b-\x1f\x7f]", "", s). Prefer a structured logger (json/logfmt) over raw print to stdout.
User-controlled value printed to terminal without ANSI escape sanitization. Malicious input can inject cursor-control sequences, rewrite earlier output, or hide shell commands from the operator.
Evidence
| 92 | }); |
| 93 | |
| 94 | socket.addEventListener("error", (event) => { |
| 95 | console.error("[websocketManager] WebSocket error:", event); |
| 96 | updateState("disconnected"); |
| 97 | }); |
| 98 | } catch (error) { |
Remediation
Strip C0/C1 control codes before printing user-controlled values. Python: re.sub(r"[\x00-\x08\x0b-\x1f\x7f]", "", s). Prefer a structured logger (json/logfmt) over raw print to stdout.
User-controlled value printed to terminal without ANSI escape sanitization. Malicious input can inject cursor-control sequences, rewrite earlier output, or hide shell commands from the operator.
Evidence
| 112 | }); |
| 113 | |
| 114 | socket.addEventListener("error", (event) => { |
| 115 | console.error("[background] WebSocket error:", event); |
| 116 | setExtensionIcon("disconnected"); |
| 117 | }); |
| 118 | } catch (error) { |
Remediation
Strip C0/C1 control codes before printing user-controlled values. Python: re.sub(r"[\x00-\x08\x0b-\x1f\x7f]", "", s). Prefer a structured logger (json/logfmt) over raw print to stdout.
User-controlled value printed to terminal without ANSI escape sanitization. Malicious input can inject cursor-control sequences, rewrite earlier output, or hide shell commands from the operator.
Evidence
| 195 | } |
| 196 | sendReply(true, result); |
| 197 | } catch (error) { |
| 198 | console.error( |
| 199 | `[plugin] Tool ${toolName} failed for request ID ${request.__request_id}:`, |
| 200 | error, |
| 201 | ); |
| 202 | sendReply(false, error); |
| 203 | } |
| 204 | }; |
Remediation
Strip C0/C1 control codes before printing user-controlled values. Python: re.sub(r"[\x00-\x08\x0b-\x1f\x7f]", "", s). Prefer a structured logger (json/logfmt) over raw print to stdout.
cachedir==2.4.0 last released 1014 days ago (>730d) — possible abandoned package
Remediation
Typosquat: verify you meant the popular package. If so, correct the spelling; if you truly intended the less-common name, suppress with an inline waiver. Stale release: check whether the package has a maintained fork or successor. If no patched release exists, vendor the code or migrate to an active alternative before the unmaintained code accrues unfixed CVEs.
Network / IO / subprocess call without an explicit timeout. A malicious or hung upstream (HTTP host, socket peer, child process) can pin threads, exhaust connection/process pools, and make the MCP server unresponsive. Always pass a bounded timeout. v2 extends v1 with subprocess coverage (R03 from the legacy readiness audit).
Evidence
| 52 | } |
| 53 | |
| 54 | try { |
| 55 | const response = await fetch("/api/config"); |
| 56 | if (response.ok) { |
| 57 | const serverConfig = await response.json(); |
| 58 | cachedConfig = { |
Remediation
Pass timeout= on every call: - HTTP: `requests.get(url, timeout=5)`, `httpx.get(url, timeout=5.0)` - Node fetch: `AbortSignal.timeout(5000)` - Subprocess: `subprocess.run(["cmd"], timeout=30, check=True)` Pick a value short enough to fail fast and retry.
Network / IO / subprocess call without an explicit timeout. A malicious or hung upstream (HTTP host, socket peer, child process) can pin threads, exhaust connection/process pools, and make the MCP server unresponsive. Always pass a bounded timeout. v2 extends v1 with subprocess coverage (R03 from the legacy readiness audit).
Evidence
| 39 | url: string, |
| 40 | destPath: string, |
| 41 | ): Promise<void> { |
| 42 | const response = await fetch(url); |
| 43 | if (!response.ok) { |
| 44 | throw new Error( |
| 45 | `Failed to download: ${response.status} ${response.statusText}`, |
Remediation
Pass timeout= on every call: - HTTP: `requests.get(url, timeout=5)`, `httpx.get(url, timeout=5.0)` - Node fetch: `AbortSignal.timeout(5000)` - Subprocess: `subprocess.run(["cmd"], timeout=30, check=True)` Pick a value short enough to fail fast and retry.
Network / IO / subprocess call without an explicit timeout. A malicious or hung upstream (HTTP host, socket peer, child process) can pin threads, exhaust connection/process pools, and make the MCP server unresponsive. Always pass a bounded timeout. v2 extends v1 with subprocess coverage (R03 from the legacy readiness audit).
Evidence
| 1801 | const originalReject = process.env.NODE_TLS_REJECT_UNAUTHORIZED; |
| 1802 | process.env.NODE_TLS_REJECT_UNAUTHORIZED = "0"; |
| 1803 | try { |
| 1804 | const res = await fetch(`https://localhost:${port}/health`); |
| 1805 | expect(res.status).toBe(200); |
| 1806 | const body = await res.json(); |
| 1807 | expect(body).toEqual({ status: "ok" }); |
Remediation
Pass timeout= on every call: - HTTP: `requests.get(url, timeout=5)`, `httpx.get(url, timeout=5.0)` - Node fetch: `AbortSignal.timeout(5000)` - Subprocess: `subprocess.run(["cmd"], timeout=30, check=True)` Pick a value short enough to fail fast and retry.
Network / IO / subprocess call without an explicit timeout. A malicious or hung upstream (HTTP host, socket peer, child process) can pin threads, exhaust connection/process pools, and make the MCP server unresponsive. Always pass a bounded timeout. v2 extends v1 with subprocess coverage (R03 from the legacy readiness audit).
Evidence
| 18 | "https://api.github.com/repos/jgraph/drawio/releases/latest"; |
| 19 | |
| 20 | export async function getLatestWarUrl(): Promise<string> { |
| 21 | const response = await fetch(DRAWIO_GITHUB_API); |
| 22 | if (!response.ok) { |
| 23 | throw new Error(`Failed to get draw.io release info: ${response.status}`); |
| 24 | } |
Remediation
Pass timeout= on every call: - HTTP: `requests.get(url, timeout=5)`, `httpx.get(url, timeout=5.0)` - Node fetch: `AbortSignal.timeout(5000)` - Subprocess: `subprocess.run(["cmd"], timeout=30, check=True)` Pick a value short enough to fail fast and retry.
Network / IO / subprocess call without an explicit timeout. A malicious or hung upstream (HTTP host, socket peer, child process) can pin threads, exhaust connection/process pools, and make the MCP server unresponsive. Always pass a bounded timeout. v2 extends v1 with subprocess coverage (R03 from the legacy readiness audit).
Evidence
| 348 | }); |
| 349 | |
| 350 | it("rejects plain HTTP", async () => { |
| 351 | await expect(fetch(`http://localhost:${port}/health`)).rejects.toThrow(); |
| 352 | }); |
| 353 | }); |
Remediation
Pass timeout= on every call: - HTTP: `requests.get(url, timeout=5)`, `httpx.get(url, timeout=5.0)` - Node fetch: `AbortSignal.timeout(5000)` - Subprocess: `subprocess.run(["cmd"], timeout=30, check=True)` Pick a value short enough to fail fast and retry.
Network / IO / subprocess call without an explicit timeout. A malicious or hung upstream (HTTP host, socket peer, child process) can pin threads, exhaust connection/process pools, and make the MCP server unresponsive. Always pass a bounded timeout. v2 extends v1 with subprocess coverage (R03 from the legacy readiness audit).
Evidence
| 1815 | }); |
| 1816 | |
| 1817 | it("rejects plain HTTP", async () => { |
| 1818 | await expect(fetch(`http://localhost:${port}/health`)).rejects.toThrow(); |
| 1819 | }); |
| 1820 | }); |
| 1821 | ``` |
Remediation
Pass timeout= on every call: - HTTP: `requests.get(url, timeout=5)`, `httpx.get(url, timeout=5.0)` - Node fetch: `AbortSignal.timeout(5000)` - Subprocess: `subprocess.run(["cmd"], timeout=30, check=True)` Pick a value short enough to fail fast and retry.
Package declares an install-time hook (npm postinstall/preinstall/prepare, setup.py cmdclass override, custom setuptools install class, or non-default pyproject build-backend). Anyone installing this package runs the hook. Confirm the hook is necessary and review its contents; prefer shipping a plain library without install-time execution.
Evidence
| 16 | "zip:plugin": "MODE=plugin wxt zip", |
| 17 | "compile": "tsc --noEmit", |
| 18 | "test:iframe-host": "tsx scripts/serve-iframe-host.ts", |
| 19 | "postinstall": "wxt prepare" |
| 20 | }, |
| 21 | "keywords": [ |
| 22 | "mcp", |
Remediation
Prefer libraries that do not require install-time code execution: - Drop `postinstall`/`preinstall`/`prepare` scripts if the work can happen at runtime or build-time instead. - Ship pre-built native binaries rather than compiling via a custom `cmdclass` or `build_ext` override. - For Dockerfiles: replace `RUN curl … | sh` with a pinned download + checksum verification + explicit `RUN` of a named script. - If the hook is unavoidable, document exactly what it does so downstream reviewers
Package declares an install-time hook (npm postinstall/preinstall/prepare, setup.py cmdclass override, custom setuptools install class, or non-default pyproject build-backend). Anyone installing this package runs the hook. Confirm the hook is necessary and review its contents; prefer shipping a plain library without install-time execution.
Evidence
| 13 | } |
| 14 | }, |
| 15 | "scripts": { |
| 16 | "postinstall": "node scripts/install-caddy.mjs", |
| 17 | "setup": "node scripts/install-caddy.mjs", |
| 18 | "build": "tsc", |
| 19 | "trust": "sudo ./bin/caddy trust", |
Remediation
Prefer libraries that do not require install-time code execution: - Drop `postinstall`/`preinstall`/`prepare` scripts if the work can happen at runtime or build-time instead. - Ship pre-built native binaries rather than compiling via a custom `cmdclass` or `build_ext` override. - For Dockerfiles: replace `RUN curl … | sh` with a pinned download + checksum verification + explicit `RUN` of a named script. - If the hook is unavoidable, document exactly what it does so downstream reviewers
Dockerfile never sets a non-root `USER` directive, so the CMD runs as root by default. Any RCE or library-level vulnerability exploited inside this container gets full privileges (MCP Top-10 R3). Add `USER <non-root>` before CMD / ENTRYPOINT in the final stage — e.g. `USER 1000`, `USER nobody`, or `USER nonroot` on distroless.
Evidence
| 1 | # ============================================================================= |
| 2 | # Draw.io MCP Server — Distroless Container |
| 3 | # ============================================================================= |
| 4 | # Multi-stage build: |
| 5 | # 1. deps — install production dependencies only |
| 6 | # 2. build — compile TypeScript |
| 7 | # 3. runtime — Google distroless (no shell, no package manager, minimal CVEs) |
| 8 | # ============================================================================= |
| 9 | |
| 10 | # ---------------------- |
Remediation
Create and switch to a non-root user before the CMD / ENTRYPOINT: RUN adduser --system --uid 1000 app USER 1000 Or reuse the base image's shipped non-root account (e.g. `USER nobody`, `USER nonroot` on distroless). Multi-stage builds only need the USER directive in the final stage.
GitHub Actions `uses:` reference is not pinned to a 40-character commit SHA. Tags (`@v4`) and branches (`@main`) are mutable — a compromised maintainer or a tag rewrite can substitute malicious code into your CI pipeline silently. Pin to a SHA: `uses: actions/checkout@8e5e7e5ab8b370d6c329ec480221332ada57f0ab`. For readability, include the version as a trailing comment: `# v4.1.1`. Tools like `pinact` / `ratchet` automate this. Allowed unpinned forms (excluded by the rule): - Local actions `.
Evidence
| 23 | steps: |
| 24 | - uses: actions/checkout@v4 |
| 25 | - name: Install pnpm |
| 26 | uses: pnpm/action-setup@v4 |
| 27 | |
| 28 | - name: Use Node.js ${{ matrix.node-version }} |
| 29 | uses: actions/setup-node@v4 |
Remediation
Pin every `uses:` to a 40-character commit SHA. Trailing comment with the version helps reviewers: `uses: actions/checkout@8e5e7e5ab8b370d6c329ec480221332ada57f0ab # v4.1.1` Automate the migration with `pinact` (https://github.com/suzuki-shunsuke/pinact) or `ratchet` (https://github.com/sethvargo/ratchet). Add a `pinact run --check` pre-commit hook so future PRs stay pinned. Re-pin when the action releases a new version — Dependabot can do this automatically with `version-update-strategy: inc
GitHub Actions `uses:` reference is not pinned to a 40-character commit SHA. Tags (`@v4`) and branches (`@main`) are mutable — a compromised maintainer or a tag rewrite can substitute malicious code into your CI pipeline silently. Pin to a SHA: `uses: actions/checkout@8e5e7e5ab8b370d6c329ec480221332ada57f0ab`. For readability, include the version as a trailing comment: `# v4.1.1`. Tools like `pinact` / `ratchet` automate this. Allowed unpinned forms (excluded by the rule): - Local actions `.
Evidence
| 16 | uses: pnpm/action-setup@v4 |
| 17 | |
| 18 | - name: Use Node.js ${{ matrix.node-version }} |
| 19 | uses: actions/setup-node@v4 |
| 20 | with: |
| 21 | node-version: ${{ matrix.node-version }} |
| 22 | cache: "pnpm" |
Remediation
Pin every `uses:` to a 40-character commit SHA. Trailing comment with the version helps reviewers: `uses: actions/checkout@8e5e7e5ab8b370d6c329ec480221332ada57f0ab # v4.1.1` Automate the migration with `pinact` (https://github.com/suzuki-shunsuke/pinact) or `ratchet` (https://github.com/sethvargo/ratchet). Add a `pinact run --check` pre-commit hook so future PRs stay pinned. Re-pin when the action releases a new version — Dependabot can do this automatically with `version-update-strategy: inc
GitHub Actions `uses:` reference is not pinned to a 40-character commit SHA. Tags (`@v4`) and branches (`@main`) are mutable — a compromised maintainer or a tag rewrite can substitute malicious code into your CI pipeline silently. Pin to a SHA: `uses: actions/checkout@8e5e7e5ab8b370d6c329ec480221332ada57f0ab`. For readability, include the version as a trailing comment: `# v4.1.1`. Tools like `pinact` / `ratchet` automate this. Allowed unpinned forms (excluded by the rule): - Local actions `.
Evidence
| 10 | steps: |
| 11 | - uses: actions/checkout@v4 |
| 12 | - name: Install pnpm |
| 13 | uses: pnpm/action-setup@v4 |
| 14 | |
| 15 | - name: Use Node.js |
| 16 | uses: actions/setup-node@v4 |
Remediation
Pin every `uses:` to a 40-character commit SHA. Trailing comment with the version helps reviewers: `uses: actions/checkout@8e5e7e5ab8b370d6c329ec480221332ada57f0ab # v4.1.1` Automate the migration with `pinact` (https://github.com/suzuki-shunsuke/pinact) or `ratchet` (https://github.com/sethvargo/ratchet). Add a `pinact run --check` pre-commit hook so future PRs stay pinned. Re-pin when the action releases a new version — Dependabot can do this automatically with `version-update-strategy: inc
GitHub Actions `uses:` reference is not pinned to a 40-character commit SHA. Tags (`@v4`) and branches (`@main`) are mutable — a compromised maintainer or a tag rewrite can substitute malicious code into your CI pipeline silently. Pin to a SHA: `uses: actions/checkout@8e5e7e5ab8b370d6c329ec480221332ada57f0ab`. For readability, include the version as a trailing comment: `# v4.1.1`. Tools like `pinact` / `ratchet` automate this. Allowed unpinned forms (excluded by the rule): - Local actions `.
Evidence
| 8 | publish: |
| 9 | runs-on: ubuntu-latest |
| 10 | steps: |
| 11 | - uses: actions/checkout@v4 |
| 12 | - name: Install pnpm |
| 13 | uses: pnpm/action-setup@v4 |
Remediation
Pin every `uses:` to a 40-character commit SHA. Trailing comment with the version helps reviewers: `uses: actions/checkout@8e5e7e5ab8b370d6c329ec480221332ada57f0ab # v4.1.1` Automate the migration with `pinact` (https://github.com/suzuki-shunsuke/pinact) or `ratchet` (https://github.com/sethvargo/ratchet). Add a `pinact run --check` pre-commit hook so future PRs stay pinned. Re-pin when the action releases a new version — Dependabot can do this automatically with `version-update-strategy: inc
GitHub Actions `uses:` reference is not pinned to a 40-character commit SHA. Tags (`@v4`) and branches (`@main`) are mutable — a compromised maintainer or a tag rewrite can substitute malicious code into your CI pipeline silently. Pin to a SHA: `uses: actions/checkout@8e5e7e5ab8b370d6c329ec480221332ada57f0ab`. For readability, include the version as a trailing comment: `# v4.1.1`. Tools like `pinact` / `ratchet` automate this. Allowed unpinned forms (excluded by the rule): - Local actions `.
Evidence
| 26 | uses: pnpm/action-setup@v4 |
| 27 | |
| 28 | - name: Use Node.js ${{ matrix.node-version }} |
| 29 | uses: actions/setup-node@v4 |
| 30 | with: |
| 31 | node-version: ${{ matrix.node-version }} |
| 32 | cache: "pnpm" |
Remediation
Pin every `uses:` to a 40-character commit SHA. Trailing comment with the version helps reviewers: `uses: actions/checkout@8e5e7e5ab8b370d6c329ec480221332ada57f0ab # v4.1.1` Automate the migration with `pinact` (https://github.com/suzuki-shunsuke/pinact) or `ratchet` (https://github.com/sethvargo/ratchet). Add a `pinact run --check` pre-commit hook so future PRs stay pinned. Re-pin when the action releases a new version — Dependabot can do this automatically with `version-update-strategy: inc
GitHub Actions `uses:` reference is not pinned to a 40-character commit SHA. Tags (`@v4`) and branches (`@main`) are mutable — a compromised maintainer or a tag rewrite can substitute malicious code into your CI pipeline silently. Pin to a SHA: `uses: actions/checkout@8e5e7e5ab8b370d6c329ec480221332ada57f0ab`. For readability, include the version as a trailing comment: `# v4.1.1`. Tools like `pinact` / `ratchet` automate this. Allowed unpinned forms (excluded by the rule): - Local actions `.
Evidence
| 11 | node-version: [23.x] |
| 12 | |
| 13 | steps: |
| 14 | - uses: actions/checkout@v4 |
| 15 | - name: Install pnpm |
| 16 | uses: pnpm/action-setup@v4 |
Remediation
Pin every `uses:` to a 40-character commit SHA. Trailing comment with the version helps reviewers: `uses: actions/checkout@8e5e7e5ab8b370d6c329ec480221332ada57f0ab # v4.1.1` Automate the migration with `pinact` (https://github.com/suzuki-shunsuke/pinact) or `ratchet` (https://github.com/sethvargo/ratchet). Add a `pinact run --check` pre-commit hook so future PRs stay pinned. Re-pin when the action releases a new version — Dependabot can do this automatically with `version-update-strategy: inc
GitHub Actions `uses:` reference is not pinned to a 40-character commit SHA. Tags (`@v4`) and branches (`@main`) are mutable — a compromised maintainer or a tag rewrite can substitute malicious code into your CI pipeline silently. Pin to a SHA: `uses: actions/checkout@8e5e7e5ab8b370d6c329ec480221332ada57f0ab`. For readability, include the version as a trailing comment: `# v4.1.1`. Tools like `pinact` / `ratchet` automate this. Allowed unpinned forms (excluded by the rule): - Local actions `.
Evidence
| 21 | node-version: [23.x] |
| 22 | |
| 23 | steps: |
| 24 | - uses: actions/checkout@v4 |
| 25 | - name: Install pnpm |
| 26 | uses: pnpm/action-setup@v4 |
Remediation
Pin every `uses:` to a 40-character commit SHA. Trailing comment with the version helps reviewers: `uses: actions/checkout@8e5e7e5ab8b370d6c329ec480221332ada57f0ab # v4.1.1` Automate the migration with `pinact` (https://github.com/suzuki-shunsuke/pinact) or `ratchet` (https://github.com/sethvargo/ratchet). Add a `pinact run --check` pre-commit hook so future PRs stay pinned. Re-pin when the action releases a new version — Dependabot can do this automatically with `version-update-strategy: inc
GitHub Actions `uses:` reference is not pinned to a 40-character commit SHA. Tags (`@v4`) and branches (`@main`) are mutable — a compromised maintainer or a tag rewrite can substitute malicious code into your CI pipeline silently. Pin to a SHA: `uses: actions/checkout@8e5e7e5ab8b370d6c329ec480221332ada57f0ab`. For readability, include the version as a trailing comment: `# v4.1.1`. Tools like `pinact` / `ratchet` automate this. Allowed unpinned forms (excluded by the rule): - Local actions `.
Evidence
| 9 | roam: |
| 10 | runs-on: ubuntu-latest |
| 11 | steps: |
| 12 | - uses: actions/checkout@v4 |
| 13 | with: |
| 14 | fetch-depth: 0 |
| 15 | - uses: actions/setup-python@v5 |
Remediation
Pin every `uses:` to a 40-character commit SHA. Trailing comment with the version helps reviewers: `uses: actions/checkout@8e5e7e5ab8b370d6c329ec480221332ada57f0ab # v4.1.1` Automate the migration with `pinact` (https://github.com/suzuki-shunsuke/pinact) or `ratchet` (https://github.com/sethvargo/ratchet). Add a `pinact run --check` pre-commit hook so future PRs stay pinned. Re-pin when the action releases a new version — Dependabot can do this automatically with `version-update-strategy: inc
GitHub Actions `uses:` reference is not pinned to a 40-character commit SHA. Tags (`@v4`) and branches (`@main`) are mutable — a compromised maintainer or a tag rewrite can substitute malicious code into your CI pipeline silently. Pin to a SHA: `uses: actions/checkout@8e5e7e5ab8b370d6c329ec480221332ada57f0ab`. For readability, include the version as a trailing comment: `# v4.1.1`. Tools like `pinact` / `ratchet` automate this. Allowed unpinned forms (excluded by the rule): - Local actions `.
Evidence
| 44 | working-directory: packages/drawio-mcp-extension |
| 45 | |
| 46 | - name: Upload |
| 47 | uses: actions/upload-artifact@v4 |
| 48 | with: |
| 49 | name: extension-zip |
| 50 | path: | |
Remediation
Pin every `uses:` to a 40-character commit SHA. Trailing comment with the version helps reviewers: `uses: actions/checkout@8e5e7e5ab8b370d6c329ec480221332ada57f0ab # v4.1.1` Automate the migration with `pinact` (https://github.com/suzuki-shunsuke/pinact) or `ratchet` (https://github.com/sethvargo/ratchet). Add a `pinact run --check` pre-commit hook so future PRs stay pinned. Re-pin when the action releases a new version — Dependabot can do this automatically with `version-update-strategy: inc
GitHub Actions `uses:` reference is not pinned to a 40-character commit SHA. Tags (`@v4`) and branches (`@main`) are mutable — a compromised maintainer or a tag rewrite can substitute malicious code into your CI pipeline silently. Pin to a SHA: `uses: actions/checkout@8e5e7e5ab8b370d6c329ec480221332ada57f0ab`. For readability, include the version as a trailing comment: `# v4.1.1`. Tools like `pinact` / `ratchet` automate this. Allowed unpinned forms (excluded by the rule): - Local actions `.
Evidence
| 13 | uses: pnpm/action-setup@v4 |
| 14 | |
| 15 | - name: Use Node.js |
| 16 | uses: actions/setup-node@v4 |
| 17 | with: |
| 18 | node-version: "23.x" |
| 19 | registry-url: "https://registry.npmjs.org" |
Remediation
Pin every `uses:` to a 40-character commit SHA. Trailing comment with the version helps reviewers: `uses: actions/checkout@8e5e7e5ab8b370d6c329ec480221332ada57f0ab # v4.1.1` Automate the migration with `pinact` (https://github.com/suzuki-shunsuke/pinact) or `ratchet` (https://github.com/sethvargo/ratchet). Add a `pinact run --check` pre-commit hook so future PRs stay pinned. Re-pin when the action releases a new version — Dependabot can do this automatically with `version-update-strategy: inc
GitHub Actions `uses:` reference is not pinned to a 40-character commit SHA. Tags (`@v4`) and branches (`@main`) are mutable — a compromised maintainer or a tag rewrite can substitute malicious code into your CI pipeline silently. Pin to a SHA: `uses: actions/checkout@8e5e7e5ab8b370d6c329ec480221332ada57f0ab`. For readability, include the version as a trailing comment: `# v4.1.1`. Tools like `pinact` / `ratchet` automate this. Allowed unpinned forms (excluded by the rule): - Local actions `.
Evidence
| 12 | - uses: actions/checkout@v4 |
| 13 | with: |
| 14 | fetch-depth: 0 |
| 15 | - uses: actions/setup-python@v5 |
| 16 | with: |
| 17 | python-version: "3.12" |
| 18 | - run: pip install roam-code |
Remediation
Pin every `uses:` to a 40-character commit SHA. Trailing comment with the version helps reviewers: `uses: actions/checkout@8e5e7e5ab8b370d6c329ec480221332ada57f0ab # v4.1.1` Automate the migration with `pinact` (https://github.com/suzuki-shunsuke/pinact) or `ratchet` (https://github.com/sethvargo/ratchet). Add a `pinact run --check` pre-commit hook so future PRs stay pinned. Re-pin when the action releases a new version — Dependabot can do this automatically with `version-update-strategy: inc
GitHub Actions `uses:` reference is not pinned to a 40-character commit SHA. Tags (`@v4`) and branches (`@main`) are mutable — a compromised maintainer or a tag rewrite can substitute malicious code into your CI pipeline silently. Pin to a SHA: `uses: actions/checkout@8e5e7e5ab8b370d6c329ec480221332ada57f0ab`. For readability, include the version as a trailing comment: `# v4.1.1`. Tools like `pinact` / `ratchet` automate this. Allowed unpinned forms (excluded by the rule): - Local actions `.
Evidence
| 25 | uses: pnpm/action-setup@v4 |
| 26 | |
| 27 | - name: Use Node.js ${{ matrix.node-version }} |
| 28 | uses: actions/setup-node@v4 |
| 29 | with: |
| 30 | node-version: ${{ matrix.node-version }} |
| 31 | cache: "pnpm" |
Remediation
Pin every `uses:` to a 40-character commit SHA. Trailing comment with the version helps reviewers: `uses: actions/checkout@8e5e7e5ab8b370d6c329ec480221332ada57f0ab # v4.1.1` Automate the migration with `pinact` (https://github.com/suzuki-shunsuke/pinact) or `ratchet` (https://github.com/sethvargo/ratchet). Add a `pinact run --check` pre-commit hook so future PRs stay pinned. Re-pin when the action releases a new version — Dependabot can do this automatically with `version-update-strategy: inc
GitHub Actions `uses:` reference is not pinned to a 40-character commit SHA. Tags (`@v4`) and branches (`@main`) are mutable — a compromised maintainer or a tag rewrite can substitute malicious code into your CI pipeline silently. Pin to a SHA: `uses: actions/checkout@8e5e7e5ab8b370d6c329ec480221332ada57f0ab`. For readability, include the version as a trailing comment: `# v4.1.1`. Tools like `pinact` / `ratchet` automate this. Allowed unpinned forms (excluded by the rule): - Local actions `.
Evidence
| 22 | steps: |
| 23 | - uses: actions/checkout@v4 |
| 24 | - name: Install pnpm |
| 25 | uses: pnpm/action-setup@v4 |
| 26 | |
| 27 | - name: Use Node.js ${{ matrix.node-version }} |
| 28 | uses: actions/setup-node@v4 |
Remediation
Pin every `uses:` to a 40-character commit SHA. Trailing comment with the version helps reviewers: `uses: actions/checkout@8e5e7e5ab8b370d6c329ec480221332ada57f0ab # v4.1.1` Automate the migration with `pinact` (https://github.com/suzuki-shunsuke/pinact) or `ratchet` (https://github.com/sethvargo/ratchet). Add a `pinact run --check` pre-commit hook so future PRs stay pinned. Re-pin when the action releases a new version — Dependabot can do this automatically with `version-update-strategy: inc
GitHub Actions `uses:` reference is not pinned to a 40-character commit SHA. Tags (`@v4`) and branches (`@main`) are mutable — a compromised maintainer or a tag rewrite can substitute malicious code into your CI pipeline silently. Pin to a SHA: `uses: actions/checkout@8e5e7e5ab8b370d6c329ec480221332ada57f0ab`. For readability, include the version as a trailing comment: `# v4.1.1`. Tools like `pinact` / `ratchet` automate this. Allowed unpinned forms (excluded by the rule): - Local actions `.
Evidence
| 45 | key: playwright-${{ runner.os }}-${{ hashFiles('pnpm-lock.yaml') }} |
| 46 | |
| 47 | - name: Cache draw.io assets |
| 48 | uses: actions/cache@v4 |
| 49 | with: |
| 50 | path: ~/.cache/drawio-mcp-server |
| 51 | key: drawio-assets-${{ runner.os }}-${{ hashFiles('pnpm-lock.yaml') }} |
Remediation
Pin every `uses:` to a 40-character commit SHA. Trailing comment with the version helps reviewers: `uses: actions/checkout@8e5e7e5ab8b370d6c329ec480221332ada57f0ab # v4.1.1` Automate the migration with `pinact` (https://github.com/suzuki-shunsuke/pinact) or `ratchet` (https://github.com/sethvargo/ratchet). Add a `pinact run --check` pre-commit hook so future PRs stay pinned. Re-pin when the action releases a new version — Dependabot can do this automatically with `version-update-strategy: inc
GitHub Actions `uses:` reference is not pinned to a 40-character commit SHA. Tags (`@v4`) and branches (`@main`) are mutable — a compromised maintainer or a tag rewrite can substitute malicious code into your CI pipeline silently. Pin to a SHA: `uses: actions/checkout@8e5e7e5ab8b370d6c329ec480221332ada57f0ab`. For readability, include the version as a trailing comment: `# v4.1.1`. Tools like `pinact` / `ratchet` automate this. Allowed unpinned forms (excluded by the rule): - Local actions `.
Evidence
| 20 | uses: pnpm/action-setup@v4 |
| 21 | |
| 22 | - name: Use Node.js |
| 23 | uses: actions/setup-node@v4 |
| 24 | with: |
| 25 | node-version: "23.x" |
| 26 | registry-url: "https://registry.npmjs.org" |
Remediation
Pin every `uses:` to a 40-character commit SHA. Trailing comment with the version helps reviewers: `uses: actions/checkout@8e5e7e5ab8b370d6c329ec480221332ada57f0ab # v4.1.1` Automate the migration with `pinact` (https://github.com/suzuki-shunsuke/pinact) or `ratchet` (https://github.com/sethvargo/ratchet). Add a `pinact run --check` pre-commit hook so future PRs stay pinned. Re-pin when the action releases a new version — Dependabot can do this automatically with `version-update-strategy: inc
GitHub Actions `uses:` reference is not pinned to a 40-character commit SHA. Tags (`@v4`) and branches (`@main`) are mutable — a compromised maintainer or a tag rewrite can substitute malicious code into your CI pipeline silently. Pin to a SHA: `uses: actions/checkout@8e5e7e5ab8b370d6c329ec480221332ada57f0ab`. For readability, include the version as a trailing comment: `# v4.1.1`. Tools like `pinact` / `ratchet` automate this. Allowed unpinned forms (excluded by the rule): - Local actions `.
Evidence
| 20 | node-version: [23.x] |
| 21 | |
| 22 | steps: |
| 23 | - uses: actions/checkout@v4 |
| 24 | - name: Install pnpm |
| 25 | uses: pnpm/action-setup@v4 |
Remediation
Pin every `uses:` to a 40-character commit SHA. Trailing comment with the version helps reviewers: `uses: actions/checkout@8e5e7e5ab8b370d6c329ec480221332ada57f0ab # v4.1.1` Automate the migration with `pinact` (https://github.com/suzuki-shunsuke/pinact) or `ratchet` (https://github.com/sethvargo/ratchet). Add a `pinact run --check` pre-commit hook so future PRs stay pinned. Re-pin when the action releases a new version — Dependabot can do this automatically with `version-update-strategy: inc
GitHub Actions `uses:` reference is not pinned to a 40-character commit SHA. Tags (`@v4`) and branches (`@main`) are mutable — a compromised maintainer or a tag rewrite can substitute malicious code into your CI pipeline silently. Pin to a SHA: `uses: actions/checkout@8e5e7e5ab8b370d6c329ec480221332ada57f0ab`. For readability, include the version as a trailing comment: `# v4.1.1`. Tools like `pinact` / `ratchet` automate this. Allowed unpinned forms (excluded by the rule): - Local actions `.
Evidence
| 39 | run: pnpm audit --audit-level=moderate |
| 40 | |
| 41 | - name: Cache Playwright browsers |
| 42 | uses: actions/cache@v4 |
| 43 | with: |
| 44 | path: ~/.cache/ms-playwright |
| 45 | key: playwright-${{ runner.os }}-${{ hashFiles('pnpm-lock.yaml') }} |
Remediation
Pin every `uses:` to a 40-character commit SHA. Trailing comment with the version helps reviewers: `uses: actions/checkout@8e5e7e5ab8b370d6c329ec480221332ada57f0ab # v4.1.1` Automate the migration with `pinact` (https://github.com/suzuki-shunsuke/pinact) or `ratchet` (https://github.com/sethvargo/ratchet). Add a `pinact run --check` pre-commit hook so future PRs stay pinned. Re-pin when the action releases a new version — Dependabot can do this automatically with `version-update-strategy: inc
GitHub Actions `uses:` reference is not pinned to a 40-character commit SHA. Tags (`@v4`) and branches (`@main`) are mutable — a compromised maintainer or a tag rewrite can substitute malicious code into your CI pipeline silently. Pin to a SHA: `uses: actions/checkout@8e5e7e5ab8b370d6c329ec480221332ada57f0ab`. For readability, include the version as a trailing comment: `# v4.1.1`. Tools like `pinact` / `ratchet` automate this. Allowed unpinned forms (excluded by the rule): - Local actions `.
Evidence
| 13 | steps: |
| 14 | - uses: actions/checkout@v4 |
| 15 | - name: Install pnpm |
| 16 | uses: pnpm/action-setup@v4 |
| 17 | |
| 18 | - name: Use Node.js ${{ matrix.node-version }} |
| 19 | uses: actions/setup-node@v4 |
Remediation
Pin every `uses:` to a 40-character commit SHA. Trailing comment with the version helps reviewers: `uses: actions/checkout@8e5e7e5ab8b370d6c329ec480221332ada57f0ab # v4.1.1` Automate the migration with `pinact` (https://github.com/suzuki-shunsuke/pinact) or `ratchet` (https://github.com/sethvargo/ratchet). Add a `pinact run --check` pre-commit hook so future PRs stay pinned. Re-pin when the action releases a new version — Dependabot can do this automatically with `version-update-strategy: inc
GitHub Actions `uses:` reference is not pinned to a 40-character commit SHA. Tags (`@v4`) and branches (`@main`) are mutable — a compromised maintainer or a tag rewrite can substitute malicious code into your CI pipeline silently. Pin to a SHA: `uses: actions/checkout@8e5e7e5ab8b370d6c329ec480221332ada57f0ab`. For readability, include the version as a trailing comment: `# v4.1.1`. Tools like `pinact` / `ratchet` automate this. Allowed unpinned forms (excluded by the rule): - Local actions `.
Evidence
| 17 | steps: |
| 18 | - uses: actions/checkout@v4 |
| 19 | - name: Install pnpm |
| 20 | uses: pnpm/action-setup@v4 |
| 21 | |
| 22 | - name: Use Node.js |
| 23 | uses: actions/setup-node@v4 |
Remediation
Pin every `uses:` to a 40-character commit SHA. Trailing comment with the version helps reviewers: `uses: actions/checkout@8e5e7e5ab8b370d6c329ec480221332ada57f0ab # v4.1.1` Automate the migration with `pinact` (https://github.com/suzuki-shunsuke/pinact) or `ratchet` (https://github.com/sethvargo/ratchet). Add a `pinact run --check` pre-commit hook so future PRs stay pinned. Re-pin when the action releases a new version — Dependabot can do this automatically with `version-update-strategy: inc
GitHub Actions `uses:` reference is not pinned to a 40-character commit SHA. Tags (`@v4`) and branches (`@main`) are mutable — a compromised maintainer or a tag rewrite can substitute malicious code into your CI pipeline silently. Pin to a SHA: `uses: actions/checkout@8e5e7e5ab8b370d6c329ec480221332ada57f0ab`. For readability, include the version as a trailing comment: `# v4.1.1`. Tools like `pinact` / `ratchet` automate this. Allowed unpinned forms (excluded by the rule): - Local actions `.
Evidence
| 15 | contents: read |
| 16 | id-token: write |
| 17 | steps: |
| 18 | - uses: actions/checkout@v4 |
| 19 | - name: Install pnpm |
| 20 | uses: pnpm/action-setup@v4 |
Remediation
Pin every `uses:` to a 40-character commit SHA. Trailing comment with the version helps reviewers: `uses: actions/checkout@8e5e7e5ab8b370d6c329ec480221332ada57f0ab # v4.1.1` Automate the migration with `pinact` (https://github.com/suzuki-shunsuke/pinact) or `ratchet` (https://github.com/sethvargo/ratchet). Add a `pinact run --check` pre-commit hook so future PRs stay pinned. Re-pin when the action releases a new version — Dependabot can do this automatically with `version-update-strategy: inc
Silent error swallowing detected. An except clause that does pass or ... discards the exception with no log, no metric, and no trace. This blinds incident response and hides real failures.
Evidence
| 75 | if (title) currentSection = title; |
| 76 | try { |
| 77 | onInit({ appendChild() {} }); |
| 78 | } catch {} |
| 79 | }, |
| 80 | |
| 81 | addPaletteFunctions( |
Remediation
Log the exception at minimum (`logger.exception(e)`), emit a metric, or re-raise if the error is not recoverable. If you genuinely want to ignore an exception, say so with a comment.
Silent error swallowing detected. An except clause that does pass or ... discards the exception with no log, no metric, and no trace. This blinds incident response and hides real failures.
Evidence
| 166 | try { |
| 167 | await download(archiveUrl, tmpArchive); |
| 168 | } catch (err) { |
| 169 | try { rmSync(tmpArchive, { force: true }); } catch {} |
| 170 | fail(`download failed: ${err.message}`); |
| 171 | } |
Remediation
Log the exception at minimum (`logger.exception(e)`), emit a metric, or re-raise if the error is not recoverable. If you genuinely want to ignore an exception, say so with a comment.
Silent error swallowing detected. An except clause that does pass or ... discards the exception with no log, no metric, and no trace. This blinds incident response and hides real failures.
Evidence
| 127 | } catch (err) { |
| 128 | try { |
| 129 | child.kill("SIGTERM"); |
| 130 | } catch {} |
| 131 | throw err; |
| 132 | } |
Remediation
Log the exception at minimum (`logger.exception(e)`), emit a metric, or re-raise if the error is not recoverable. If you genuinely want to ignore an exception, say so with a comment.
Silent error swallowing detected. An except clause that does pass or ... discards the exception with no log, no metric, and no trace. This blinds incident response and hides real failures.
Evidence
| 151 | if (child.exitCode === null) { |
| 152 | try { |
| 153 | child.kill("SIGKILL"); |
| 154 | } catch {} |
| 155 | } |
| 156 | }, 2000); |
| 157 | }), |
Remediation
Log the exception at minimum (`logger.exception(e)`), emit a metric, or re-raise if the error is not recoverable. If you genuinely want to ignore an exception, say so with a comment.
Silent error swallowing detected. An except clause that does pass or ... discards the exception with no log, no metric, and no trace. This blinds incident response and hides real failures.
Evidence
| 89 | for (const fn of fns) { |
| 90 | try { |
| 91 | fn(fake); |
| 92 | } catch {} |
| 93 | } |
| 94 | }, |
Remediation
Log the exception at minimum (`logger.exception(e)`), emit a metric, or re-raise if the error is not recoverable. If you genuinely want to ignore an exception, say so with a comment.