Use with caution. Address findings before production.
Scanned 5/8/2026, 3:52:12 PM·Cached result·Deep Scan·91 rules·How we decide ↗
AIVSS Score
Medium
Severity Breakdown
0
critical
1
high
27
medium
20
low
MCP Server Information
Findings
This package carries a C-grade security rating with 27 medium-severity issues and 1 high-severity finding, primarily concentrated in server configuration (24 findings) and readiness concerns (20 findings). The high-severity issue involves a command injection vulnerability that could allow arbitrary code execution, alongside two ansi escape injection flaws and a resource exhaustion risk. While the safety score of 73/100 suggests it's not critically broken, the configuration weaknesses and injection vectors warrant careful review and hardening before deployment in production environments.
AIPer-finding remediation generated by bedrock-claude-haiku-4-5 — 40 of 48 findings. Click any finding to read.
No known CVEs found for this package or its dependencies.
Scan Details
Done
Sign in to save scan history and re-scan automatically on new commits.
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 48 findings
48 findings
Command injection risk. Shell-execution sink called with interpolated / attacker-controllable input. Use list-arg subprocess with shell=False, or escape every variable via shlex.quote (Python) / shell-escape (Node).
Evidence
| 290 | "test:integration": "bun -e \"process.env.MCP_STATA_INTEGRATION='1'; require('./test/integration/runTest');\"", |
| 291 | "test:integration:parallel": "TEST_SHARD_TOTAL=4 bun -e \"process.env.MCP_STATA_INTEGRATION='1'; require('./test/integration/runTest');\"", |
| 292 | "lint:commits": "commitlint --from HEAD~20 --to HEAD", |
| 293 | "vscode:prepublish": "node -e \"const t=process.env.npm_config_target || process.env.BUILD_TARGET; const s=process.env.SENTRY_UPLOAD; require('child_process').execSync('bun run bund |
RemediationAI
The problem is that npm scripts in package.json execute shell commands with interpolated environment variables and string concatenation, allowing command injection if variables contain shell metacharacters. Replace the inline shell execution with a dedicated Node.js script file that uses the `child_process` module with `shell: false` and pass arguments as an array, or wrap all variable interpolations with `require('shell-escape')([value])`. This eliminates injection by preventing shell metacharacter interpretation. Verify by attempting to run a test script with a malicious value like `MCP_STATA_INTEGRATION='1; rm -rf /'` and confirm it fails or is safely escaped.
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
| 231 | }); |
| 232 | |
| 233 | console.log('[mcp-log-watch] stage: task-done'); |
| 234 | console.log('[mcp-log-watch] task_done payload:', payload); |
| 235 | } |
| 236 | |
| 237 | main().catch((err) => { |
RemediationAI
The problem is that `console.log()` outputs the `payload` object directly without sanitizing ANSI escape sequences, allowing an attacker to inject cursor-control codes (e.g., `\x1b[2J` to clear screen) that manipulate terminal display or hide commands. Replace `console.log('[mcp-log-watch] task_done payload:', payload)` with `console.log('[mcp-log-watch] task_done payload:', JSON.stringify(payload).replace(/\x1b/g, '\\x1b'))` or use a library like `strip-ansi` to remove escape sequences before logging. This fix ensures malicious ANSI codes in the payload cannot alter terminal state or hide output. Test by logging a payload containing `\x1b[2J` and verify the escape sequence appears escaped or stripped in the output.
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
| 155 | const parsed = tryParseJson(text); |
| 156 | if (parsed && typeof parsed === 'object') { |
| 157 | const event = parsed.event || 'unknown'; |
| 158 | console.log(`[event:${event}]`, parsed); |
| 159 | const taskId = parsed.task_id || parsed.taskId; |
| 160 | if (taskId && pending.has(taskId)) { |
| 161 | if (event === 'tool_error') { |
RemediationAI
The problem is that the `event` variable extracted from parsed JSON is logged directly in a template string without sanitizing ANSI escape sequences, allowing injection of cursor-control codes. Replace `console.log(\`[event:${event}]\`, parsed)` with `console.log(\`[event:${String(event).replace(/\x1b/g, '\\x1b')}]\`, parsed)` or use `strip-ansi(event)` before interpolation. This prevents ANSI injection by escaping or removing control characters. Verify by passing a malicious event name like `test\x1b[2J` and confirm the escape sequence is neutralized in the output.
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
| 54 | name: `GET /api/users/${userId}`, |
| 55 | }, |
| 56 | async () => { |
| 57 | const response = await fetch(`/api/users/${userId}`); |
| 58 | const data = await response.json(); |
| 59 | return data; |
| 60 | }, |
RemediationAI
The problem is that the `fetch()` call has no timeout, so a slow or unresponsive upstream server can hang indefinitely and exhaust connection pools. Add an explicit timeout by wrapping the fetch in `Promise.race([fetch(...), new Promise((_, reject) => setTimeout(() => reject(new Error('timeout')), 5000))])` or use the `AbortController` API: `const controller = new AbortController(); const timeout = setTimeout(() => controller.abort(), 5000); const response = await fetch(..., { signal: controller.signal }); clearTimeout(timeout)`. This ensures the request fails fast if the server is unresponsive. Test by pointing the fetch at a non-responsive endpoint and confirm it rejects after the timeout period.
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
| 298 | "package:zip": "bun pack", |
| 299 | "publish:ovsx": "bun run bundle && bunx ovsx publish -p $OVSX_TOKEN", |
| 300 | "release": "semantic-release", |
| 301 | "prepare": "husky" |
| 302 | }, |
| 303 | "capabilities": { |
| 304 | "untrustedWorkspaces": { |
RemediationAI
The problem is that the `prepare` script runs `husky` at install time, executing arbitrary code whenever the package is installed, which poses a supply-chain risk if husky or its hooks are compromised. Remove the `prepare` key from package.json or replace it with a manual setup step documented in CONTRIBUTING.md that developers run explicitly after cloning. If husky is necessary, consider shipping it as an optional dev dependency with clear documentation that developers must run `husky install` manually. This eliminates automatic code execution during installation. Verify by running `npm install` in a clean environment and confirming no husky hooks execute automatically.
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
| 156 | bun-version: latest |
| 157 | |
| 158 | - name: Cache Bun dependencies |
| 159 | uses: actions/cache@v5 |
| 160 | with: |
| 161 | path: | |
| 162 | ~/.bun/install/cache |
RemediationAI
The problem is that `actions/cache@v5` uses a mutable tag that can be rewritten by maintainers or attackers, allowing silent substitution of malicious code into the CI pipeline. Replace `uses: actions/cache@v5` with `uses: actions/cache@1bd1e32a3f3d3eea4da3a8c3a3c3a3c3a3c3a3c # v5.1.0` (use the actual commit SHA for the version you want). This pins the action to a specific immutable commit, preventing tampering. Verify by checking the GitHub Actions audit log or running `git ls-remote https://github.com/actions/cache refs/tags/v5` to find the correct SHA.
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
| 32 | SENTRY_AUTH_TOKEN: ${{ secrets.SENTRY_AUTH_TOKEN }} |
| 33 | steps: |
| 34 | - name: Checkout |
| 35 | uses: actions/checkout@v6 |
| 36 | with: |
| 37 | fetch-depth: 0 |
RemediationAI
The problem is that `actions/checkout@v6` uses a mutable tag that can be rewritten, allowing code injection into the CI pipeline. Replace `uses: actions/checkout@v6` with `uses: actions/checkout@8e5e7e5ab8b370d6c329ec480221332ada57f0ab # v6.1.0` (substitute the actual commit SHA for v6). This pins the action to an immutable commit hash. Verify by running `git ls-remote https://github.com/actions/checkout refs/tags/v6` and confirming the SHA matches the pinned value.
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 | runs-on: ubuntu-latest |
| 23 | steps: |
| 24 | - name: Checkout |
| 25 | uses: actions/checkout@v6 |
| 26 | with: |
| 27 | fetch-depth: 0 |
RemediationAI
The problem is that `actions/checkout@v6` uses a mutable tag, allowing maintainer compromise or tag rewrite to inject malicious code. Replace `uses: actions/checkout@v6` with `uses: actions/checkout@8e5e7e5ab8b370d6c329ec480221332ada57f0ab # v6.1.0` (use the correct SHA for your target version). This immutably pins the action to a specific commit. Verify the SHA by consulting the GitHub Actions repository release page or using `git ls-remote`.
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
| 141 | target: [win32-x64, win32-arm64, linux-x64, linux-arm64, darwin-x64, darwin-arm64] |
| 142 | steps: |
| 143 | - name: Checkout |
| 144 | uses: actions/checkout@v6 |
| 145 | with: |
| 146 | ref: v${{ needs.release.outputs.new_release_version }} |
RemediationAI
The problem is that `actions/checkout@v6` uses a mutable version tag. Replace `uses: actions/checkout@v6` with `uses: actions/checkout@8e5e7e5ab8b370d6c329ec480221332ada57f0ab # v6.1.0` (substitute the actual commit SHA). This prevents tag rewriting attacks. Verify by cross-referencing the SHA against the official GitHub Actions checkout repository.
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
| 38 | node-version: latest |
| 39 | |
| 40 | - name: Setup Bun |
| 41 | uses: oven-sh/setup-bun@v2 |
| 42 | with: |
| 43 | bun-version: latest |
RemediationAI
The problem is that `oven-sh/setup-bun@v2` uses a mutable tag, allowing code injection. Replace `uses: oven-sh/setup-bun@v2` with `uses: oven-sh/setup-bun@8f2f3c3a3c3a3c3a3c3a3c3a3c3a3c3a3c3a3c3a # v2.0.0` (use the actual commit SHA for v2). This pins to an immutable commit. Verify by checking the oven-sh/setup-bun repository for the correct SHA.
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
| 47 | bun-version: latest |
| 48 | |
| 49 | - name: Cache Bun dependencies |
| 50 | uses: actions/cache@v5 |
| 51 | with: |
| 52 | path: | |
| 53 | ~/.bun/install/cache |
RemediationAI
The problem is that `actions/cache@v5` uses a mutable tag. Replace `uses: actions/cache@v5` with `uses: actions/cache@1bd1e32a3f3d3eea4da3a8c3a3c3a3c3a3c3a3c # v5.1.0` (use the actual commit SHA). This prevents tampering via tag rewrite. Verify the SHA against the official actions/cache repository.
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
| 205 | continue-on-error: true # OVSX can be flaky |
| 206 | |
| 207 | - name: Upload to GitHub Release |
| 208 | uses: softprops/action-gh-release@v3 |
| 209 | with: |
| 210 | files: dist/stata-workbench-${{ matrix.target }}.vsix |
| 211 | tag_name: v${{ needs.release.outputs.new_release_version }} |
RemediationAI
The problem is that `softprops/action-gh-release@v3` uses a mutable tag. Replace `uses: softprops/action-gh-release@v3` with `uses: softprops/action-gh-release@de2461bef7665dcc462ab57004c4d7604b86591f # v3.0.0` (substitute the actual commit SHA). This immutably pins the action. Verify by checking the softprops/action-gh-release repository.
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
| 67 | - name: Release |
| 68 | id: semantic |
| 69 | uses: cycjimmy/semantic-release-action@v6 |
| 70 | with: |
| 71 | extra_plugins: | |
| 72 | @semantic-release/changelog |
RemediationAI
The problem is that `cycjimmy/semantic-release-action@v6` uses a mutable tag. Replace `uses: cycjimmy/semantic-release-action@v6` with `uses: cycjimmy/semantic-release-action@cb925f2a2c1fcddcd4f86f4af4b6e421e47c3e12 # v6.3.0` (use the actual commit SHA for v6). This prevents code injection via tag rewrite. Verify the SHA against the cycjimmy/semantic-release-action repository.
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
| 74 | fetch-depth: 0 |
| 75 | |
| 76 | - name: Setup Node |
| 77 | uses: actions/setup-node@v6 |
| 78 | with: |
| 79 | node-version: latest |
RemediationAI
The problem is that `actions/setup-node@v6` uses a mutable tag. Replace `uses: actions/setup-node@v6` with `uses: actions/setup-node@8e5e7e5ab8b370d6c329ec480221332ada57f0ab # v6.0.0` (use the correct commit SHA). This pins to an immutable commit. Verify by checking the actions/setup-node repository.
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
| 79 | node-version: latest |
| 80 | |
| 81 | - name: Setup Bun |
| 82 | uses: oven-sh/setup-bun@v2 |
| 83 | with: |
| 84 | bun-version: latest |
RemediationAI
The problem is that `oven-sh/setup-bun@v2` uses a mutable tag. Replace `uses: oven-sh/setup-bun@v2` with `uses: oven-sh/setup-bun@8f2f3c3a3c3a3c3a3c3a3c3a3c3a3c3a3c3a3c3a # v2.0.0` (use the actual commit SHA). This prevents tampering. Verify the SHA against the oven-sh/setup-bun repository.
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
| 33 | fetch-depth: 0 |
| 34 | |
| 35 | - name: Setup Node |
| 36 | uses: actions/setup-node@v6 |
| 37 | with: |
| 38 | node-version: latest |
RemediationAI
The problem is that `actions/setup-node@v6` uses a mutable tag. Replace `uses: actions/setup-node@v6` with `uses: actions/setup-node@8e5e7e5ab8b370d6c329ec480221332ada57f0ab # v6.0.0` (use the correct commit SHA). This immutably pins the action. Verify the SHA against the official repository.
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
| 146 | ref: v${{ needs.release.outputs.new_release_version }} |
| 147 | |
| 148 | - name: Setup Node |
| 149 | uses: actions/setup-node@v6 |
| 150 | with: |
| 151 | node-version: latest |
RemediationAI
The problem is that `actions/setup-node@v6` uses a mutable tag. Replace `uses: actions/setup-node@v6` with `uses: actions/setup-node@8e5e7e5ab8b370d6c329ec480221332ada57f0ab # v6.0.0` (substitute the actual commit SHA). This prevents code injection. Verify by checking the actions/setup-node repository.
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
| 69 | SENTRY_UPLOAD: "false" |
| 70 | steps: |
| 71 | - name: Checkout |
| 72 | uses: actions/checkout@v6 |
| 73 | with: |
| 74 | fetch-depth: 0 |
RemediationAI
The problem is that `actions/checkout@v6` uses a mutable tag. Replace `uses: actions/checkout@v6` with `uses: actions/checkout@8e5e7e5ab8b370d6c329ec480221332ada57f0ab # v6.1.0` (use the correct commit SHA). This pins to an immutable commit. Verify the SHA against the GitHub Actions checkout repository.
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
| 42 | node-version: latest |
| 43 | |
| 44 | - name: Setup Bun |
| 45 | uses: oven-sh/setup-bun@v2 |
| 46 | with: |
| 47 | bun-version: latest |
RemediationAI
The problem is that `oven-sh/setup-bun@v2` uses a mutable tag. Replace `uses: oven-sh/setup-bun@v2` with `uses: oven-sh/setup-bun@8f2f3c3a3c3a3c3a3c3a3c3a3c3a3c3a3c3a3c3a # v2.0.0` (use the actual commit SHA). This prevents tampering via tag rewrite. Verify the SHA against the oven-sh/setup-bun repository.
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
| 28 | SENTRY_UPLOAD: "false" |
| 29 | steps: |
| 30 | - name: Checkout |
| 31 | uses: actions/checkout@v6 |
| 32 | with: |
| 33 | fetch-depth: 0 |
RemediationAI
The problem is that `actions/checkout@v6` uses a mutable tag. Replace `uses: actions/checkout@v6` with `uses: actions/checkout@8e5e7e5ab8b370d6c329ec480221332ada57f0ab # v6.1.0` (use the correct commit SHA). This immutably pins the action. Verify by checking the official repository.
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
| 43 | bun-version: latest |
| 44 | |
| 45 | - name: Cache Bun dependencies |
| 46 | uses: actions/cache@v5 |
| 47 | with: |
| 48 | path: | |
| 49 | ~/.bun/install/cache |
RemediationAI
The problem is that `actions/cache@v5` uses a mutable tag. Replace `uses: actions/cache@v5` with `uses: actions/cache@1bd1e32a3f3d3eea4da3a8c3a3c3a3c3a3c3a3c # v5.1.0` (use the actual commit SHA). This prevents code injection. Verify the SHA against the actions/cache repository.
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
| 84 | bun-version: latest |
| 85 | |
| 86 | - name: Cache Bun dependencies |
| 87 | uses: actions/cache@v5 |
| 88 | with: |
| 89 | path: | |
| 90 | ~/.bun/install/cache |
RemediationAI
The problem is that `actions/cache@v5` uses a mutable tag. Replace `uses: actions/cache@v5` with `uses: actions/cache@1bd1e32a3f3d3eea4da3a8c3a3c3a3c3a3c3a3c # v5.1.0` (substitute the actual commit SHA). This pins to an immutable commit. Verify the SHA against the official repository.
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
| 27 | fetch-depth: 0 |
| 28 | |
| 29 | - name: Setup Bun |
| 30 | uses: oven-sh/setup-bun@v2 |
| 31 | with: |
| 32 | bun-version: latest |
RemediationAI
The problem is that `oven-sh/setup-bun@v2` uses a mutable tag. Replace `uses: oven-sh/setup-bun@v2` with `uses: oven-sh/setup-bun@8f2f3c3a3c3a3c3a3c3a3c3a3c3a3c3a3c3a3c3a # v2.0.0` (use the actual commit SHA). This prevents tampering. Verify the SHA against the oven-sh/setup-bun repository.
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
| 151 | node-version: latest |
| 152 | |
| 153 | - name: Setup Bun |
| 154 | uses: oven-sh/setup-bun@v2 |
| 155 | with: |
| 156 | bun-version: latest |
RemediationAI
The problem is that `oven-sh/setup-bun@v2` uses a mutable tag. Replace `uses: oven-sh/setup-bun@v2` with `uses: oven-sh/setup-bun@8f2f3c3a3c3a3c3a3c3a3c3a3c3a3c3a3c3a3c3a # v2.0.0` (use the correct commit SHA). This immutably pins the action. Verify the SHA against the official repository.
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
| 32 | bun-version: latest |
| 33 | |
| 34 | - name: Cache Bun dependencies |
| 35 | uses: actions/cache@v5 |
| 36 | with: |
| 37 | path: | |
| 38 | ~/.bun/install/cache |
RemediationAI
The problem is that `actions/cache@v5` uses a mutable tag. Replace `uses: actions/cache@v5` with `uses: actions/cache@1bd1e32a3f3d3eea4da3a8c3a3c3a3c3a3c3a3c # v5.1.0` (use the actual commit SHA). This prevents code injection via tag rewrite. Verify the SHA against the actions/cache repository.
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
| 37 | fetch-depth: 0 |
| 38 | |
| 39 | - name: Setup Node |
| 40 | uses: actions/setup-node@v6 |
| 41 | with: |
| 42 | node-version: latest |
RemediationAI
The problem is that `actions/setup-node@v6` uses a mutable tag. Replace `uses: actions/setup-node@v6` with `uses: actions/setup-node@8e5e7e5ab8b370d6c329ec480221332ada57f0ab # v6.0.0` (use the correct commit SHA). This pins to an immutable commit. Verify the SHA against the official repository.
Time-of-check-to-time-of-use race. Code calls `os.path.exists` / `fs.existsSync` to check a path, then `open` / `readFileSync` / `unlink` on the same name within a few lines — without a lock or atomic-open. An attacker who can race the filesystem (symlink, file replacement) between the check and the use gets the action applied to a different target. Replace the check-then-use pattern with the action's own error handling: try the open and catch FileNotFoundError / ENOENT. For atomic creation use
Evidence
| 1 | // Instrument Sentry must be first to capture all errors |
| 2 | // and ensure native modules find their binaries before evaluation. |
| 3 | require("./instrument.js"); |
| 4 | const Sentry = require("@sentry/node"); |
| 5 | const path = require('path'); |
| 6 | const os = require('os'); |
| 7 | const { getVscode } = require('./runtime-context'); |
| 8 | const { getEnv, getFs, getChildProcess, getMcpClient, createDepProxy } = require('./runtime-context'); |
| 9 | const pkg = require('../package.json'); |
| 10 | const { TerminalPanel } = require('./terminal-panel'); |
| 11 | c |
RemediationAI
The problem is that the code likely checks if a file exists with `fs.existsSync()` before reading or modifying it, creating a race window where an attacker can replace the file with a symlink or different file between the check and use. Replace the check-then-use pattern by removing the `existsSync()` call and wrapping the file operation (e.g., `fs.readFileSync()`) in a try-catch that handles the `ENOENT` error directly. This eliminates the race condition by making the operation atomic. Verify by attempting to race a symlink replacement between a hypothetical check and read operation and confirming the code safely handles the missing file.
Time-of-check-to-time-of-use race. Code calls `os.path.exists` / `fs.existsSync` to check a path, then `open` / `readFileSync` / `unlink` on the same name within a few lines — without a lock or atomic-open. An attacker who can race the filesystem (symlink, file replacement) between the check and the use gets the action applied to a different target. Replace the check-then-use pattern with the action's own error handling: try the open and catch FileNotFoundError / ENOENT. For atomic creation use
Evidence
| 1 | const { EventEmitter } = require('events'); |
| 2 | const Sentry = require("@sentry/node"); |
| 3 | const path = require('path'); |
| 4 | const os = require('os'); |
| 5 | const { getVscode, getChildProcess, getEnv, getFs, createDepProxy } = require('./runtime-context'); |
| 6 | const vscode = createDepProxy(getVscode); |
| 7 | const fs = createDepProxy(getFs); |
| 8 | const pkg = require('../package.json'); |
| 9 | const https = require('https'); |
| 10 | const { filterMcpLogs } = require('./log-utils'); |
| 11 | const MCP_PACKAGE_NAME = 'mcp-stata'; |
| 12 | const MCP_PACKAGE_SPEC = |
RemediationAI
The problem is that the code checks file existence before operating on it, creating a TOCTOU race where an attacker can swap the file or inject a symlink between check and use. Replace any `fs.existsSync(path)` checks with direct try-catch around the file operation (e.g., `fs.readFileSync()`, `fs.statSync()`) and handle `ENOENT` errors in the catch block. This makes the operation atomic and eliminates the race window. Verify by attempting to race a symlink injection between the check and operation and confirming the code safely rejects or handles the unexpected file.
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
| 176 | runState._graphArtifacts = artifacts; |
| 177 | if (typeof runState.onGraphReady === 'function') { |
| 178 | for (const a of artifacts) { |
| 179 | try { runState.onGraphReady(a); } catch (_e) { } |
| 180 | } |
| 181 | } |
| 182 | } |
RemediationAI
The problem is that the catch block `catch (_e) { }` silently discards exceptions from `runState.onGraphReady(a)` with no logging, making it impossible to debug failures or detect attacks. Replace `catch (_e) { }` with `catch (_e) { console.error('[mcp-client] onGraphReady error:', _e); Sentry.captureException(_e); }` to log and report the error. This ensures failures are visible for incident response and debugging. Verify by triggering an error in `onGraphReady()` and confirming it appears in logs and Sentry.
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
| 783 | mcpClient.dispose(); |
| 784 | try { |
| 785 | await Sentry.flush(2000); |
| 786 | } catch (_err) { } |
| 787 | } |
| 788 | |
| 789 | function updateStatusBar(status) { |
RemediationAI
The problem is that the catch block `catch (_err) { }` silently discards the Sentry flush error with no logging, hiding potential issues with error reporting. Replace `catch (_err) { }` with `catch (_err) { console.error('[extension] Sentry.flush error:', _err); }` to log the error. This ensures flush failures are visible for debugging. Verify by simulating a Sentry flush failure and confirming the error is logged.
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
| 1024 | taskDoneStdout = raw; |
| 1025 | } |
| 1026 | debugLog(`[RunFile] task_done logSize=${logSize}`); |
| 1027 | } catch (_err) { } |
| 1028 | } |
| 1029 | if (runId) TerminalPanel.notifyTaskDone(runId, logPath, logSize, taskDoneStdout, payload?.rc); |
| 1030 | }, |
RemediationAI
The problem is that the catch block `catch (_err) { }` silently discards errors from task_done log processing with no logging, hiding failures in log extraction. Replace `catch (_err) { }` with `catch (_err) { debugLog('[RunFile] task_done log error:', _err); }` to log the error. This ensures log processing failures are visible. Verify by triggering a log parsing error and confirming it appears in debug output.
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
| 425 | if (refreshed && refreshed !== mcpPackageVersion) { |
| 426 | mcpPackageVersion = refreshed; |
| 427 | appendLine(`mcp-stata updated to ${mcpPackageVersion}`); |
| 428 | try { Sentry.setTag("mcp.version", mcpPackageVersion); } catch (_e) { } |
| 429 | // No longer need to sync manually; next run will use the new version. |
| 430 | } |
| 431 | } catch (_err) { |
RemediationAI
The problem is that the catch block `catch (_e) { }` silently discards errors from `Sentry.setTag()` with no logging, hiding potential Sentry configuration issues. Replace `catch (_e) { }` with `catch (_e) { console.error('[extension] Sentry.setTag error:', _e); }` to log the error. This ensures Sentry errors are visible. Verify by simulating a Sentry error and confirming it is logged.
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
| 1446 | run._tailCancelled = true; |
| 1447 | run._fastDrain = true; |
| 1448 | run._taskDoneReject(err); |
| 1449 | } catch (_err) { |
| 1450 | } |
| 1451 | } |
| 1452 | } |
RemediationAI
The problem is that the catch block `catch (_err) { }` silently discards errors from task rejection with no logging, hiding failures in error handling. Replace `catch (_err) { }` with `catch (_err) { console.error('[mcp-client] taskDoneReject error:', _err); }` to log the error. This ensures rejection failures are visible. Verify by triggering an error during task rejection and confirming it is logged.
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
| 1073 | if (run._tailPromise) { |
| 1074 | try { |
| 1075 | await run._tailPromise; |
| 1076 | } catch (_err) { |
| 1077 | } |
| 1078 | } |
| 1079 | |
| 1080 | const maxEmptyReads = run._fastDrain ? 1 : 10; |
RemediationAI
The problem is that the catch block `catch (_err) { }` silently discards errors from the tail promise with no logging, hiding failures in stream cleanup. Replace `catch (_err) { }` with `catch (_err) { console.error('[mcp-client] tail promise error:', _err); }` to log the error. This ensures cleanup failures are visible. Verify by triggering an error in the tail promise and confirming it is logged.
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
| 345 | if (TerminalPanel._testOutgoingCapture) { |
| 346 | TerminalPanel._testOutgoingCapture(msg); |
| 347 | } |
| 348 | } catch (_err) { |
| 349 | } |
| 350 | return originalPostMessage(msg); |
| 351 | }; |
| 352 | webview.__stataWorkbenchWrapped = true; |
RemediationAI
The problem is that the catch block `catch (_err) { }` silently discards errors from the test capture callback with no logging, hiding test infrastructure failures. Replace `catch (_err) { }` with `catch (_err) { console.error('[terminal-panel] test capture error:', _err); }` to log the error. This ensures test failures are visible. Verify by triggering an error in the test capture and confirming it is logged.
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
| 1007 | if (this._transport && typeof this._transport.close === 'function') { |
| 1008 | await this._transport.close(); |
| 1009 | } |
| 1010 | } catch (_err) { |
| 1011 | } |
| 1012 | this._resetClientState(); |
| 1013 | } |
RemediationAI
The problem is that the catch block `catch (_err) { }` silently discards errors from transport close with no logging, hiding connection cleanup failures. Replace `catch (_err) { }` with `catch (_err) { console.error('[mcp-client] transport close error:', _err); }` to log the error. This ensures cleanup failures are visible. Verify by triggering a close error and confirming it is logged.
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
| 511 | for (const cb of Array.from(listeners)) { |
| 512 | try { |
| 513 | cb(reason); |
| 514 | } catch (_err) { |
| 515 | } |
| 516 | } |
| 517 | return true; |
| 518 | }; |
RemediationAI
The problem is that the catch block `catch (_err) { }` silently discards errors from listener callbacks with no logging, hiding callback failures. Replace `catch (_err) { }` with `catch (_err) { console.error('[mcp-client] listener callback error:', _err); }` to log the error. This ensures callback failures are visible. Verify by triggering an error in a listener and confirming it is logged.
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
| 445 | for (const source of currentSources) { |
| 446 | try { |
| 447 | source.cancel('user cancelled all'); |
| 448 | } catch (_err) { } |
| 449 | } |
| 450 | |
| 451 | // 4. Send break_session to the server |
RemediationAI
The problem is that the catch block `catch (_err) { }` silently discards errors from source cancellation with no logging, hiding cancellation failures. Replace `catch (_err) { }` with `catch (_err) { console.error('[mcp-client] source cancel error:', _err); }` to log the error. This ensures cancellation failures are visible. Verify by triggering a cancel error and confirming it is logged.
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
| 1041 | const stats = fs.statSync(result.logPath); |
| 1042 | result.logSize = stats.size; |
| 1043 | debugLog(`[RunFile] logSize=${result.logSize}`); |
| 1044 | } catch (_err) { } |
| 1045 | } |
| 1046 | logRunToOutput(result, commandText); |
| 1047 | TerminalPanel.finishStreamingEntry(runId, result); |
RemediationAI
The problem is that the catch block `catch (_err) { }` silently discards errors from `fs.statSync()` with no logging, hiding file stat failures. Replace `catch (_err) { }` with `catch (_err) { debugLog('[RunFile] stat error:', _err); }` to log the error. This ensures file operation failures are visible. Verify by triggering a stat error and confirming it is logged.
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
| 415 | if (stats.size > 0 && stats.size <= 50000) { |
| 416 | stdout = fs.readFileSync(payload.logPath, 'utf8'); |
| 417 | } |
| 418 | } catch (_err) { |
| 419 | } |
| 420 | } |
| 421 | TerminalPanel.notifyTaskDone(runId, payload?.logPath, payload?.logSize, stdout, payload?.rc); |
| 422 | }, |
RemediationAI
The problem is that the catch block `catch (_err) { }` silently discards errors from `fs.readFileSync()` with no logging, hiding file read failures. Replace `catch (_err) { }` with `catch (_err) { console.error('[terminal-panel] readFileSync error:', _err); }` to log the error. This ensures read failures are visible. Verify by triggering a read error and confirming it is logged.
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
| 125 | debugLog(`[Installer] Found local installer: ${localPath}`); |
| 126 | } |
| 127 | } |
| 128 | } catch (_err) {} |
| 129 | |
| 130 | if (platform === 'win32') { |
| 131 | const display = localPath |
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
| 1160 | } |
| 1161 | }).join(' '); |
| 1162 | vscode.postMessage({ type: 'log', level, message }); |
| 1163 | } catch (_err) { |
| 1164 | } |
| 1165 | }; |
| 1166 | console.log = (...args) => { |
| 1167 | originalConsole.log(...args); |
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
| 374 | result += window.stataUI.escapeHtml(char); |
| 375 | currentLineLen += 1; |
| 376 | } |
| 377 | } catch (e) {} |
| 378 | } |
| 379 | continue; |
| 380 | } |
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
| 262 | runState._graphArtifacts = artifacts; |
| 263 | if (typeof runState.onGraphReady === 'function') { |
| 264 | for (const a of artifacts) { |
| 265 | try { runState.onGraphReady(a); } catch (_e) { } |
| 266 | } |
| 267 | } |
| 268 | } |
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
| 923 | const stats = fs.statSync(result.logPath); |
| 924 | result.logSize = stats.size; |
| 925 | debugLog(`[RunSelection] logSize=${result.logSize}`); |
| 926 | } catch (_err) { } |
| 927 | } |
| 928 | logRunToOutput(result, text); |
| 929 | TerminalPanel.finishStreamingEntry(runId, result); |
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
| 416 | appendLine(`mcp-stata version: ${mcpPackageVersion}`); |
| 417 | try { |
| 418 | Sentry.setTag("mcp.version", mcpPackageVersion); |
| 419 | } catch (_err) { } |
| 420 | |
| 421 | // Defer the slow network refresh (uvx --refresh, up to 10s) to after activation returns |
| 422 | setImmediate(() => { |
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
| 421 | const code = json.error?.code; |
| 422 | const msg = json.error?.message || err.message; |
| 423 | throw new ApiError(msg, status, code, json); |
| 424 | } catch (e) { } |
| 425 | } |
| 426 | log(`API Proxy Failed: ${err.message}`, true); |
| 427 | throw err; |
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
| 266 | } |
| 267 | } |
| 268 | } |
| 269 | } catch (_err) { |
| 270 | } |
| 271 | } |
| 272 | meta.logText = runState._logBuffer || ''; |
| 273 | meta.logPath = runState.logPath || null; |
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.