Use with caution. Address findings before production.
Scanned 5/3/2026, 7:35:39 PMยทCached resultยทFast Scanยท88 rulesยทHow we decide โ
AIVSS Score
Medium
Severity Breakdown
0
critical
2
high
30
medium
0
low
MCP Server Information
Findings
This package carries a C grade with a safety score of 52/100 and presents 2 high-severity issues alongside 30 medium-severity findings, primarily centered on server configuration weaknesses and ANSI escape injection vulnerabilities. The most concerning problems include insecure deserialization and a vulnerable dependency that could be exploited if this package is integrated into your system. You should address the high-severity issues and review the server configuration recommendations before deployment.
No known CVEs found for this package or its dependencies.
Argument Injection in port_forward tool via space-splitting
mcp-server-kubernetes potential security issue in exec_in_pod tool
mcp-server-kubernetes vulnerable to command injection in several tools
Scan Details
Want deeper analysis?
Fast scan found 32 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 32 findings
32 findings
Unsafe deserialization primitive detected. pickle.load(s), yaml.load (without SafeLoader), marshal.load(s), and shelve.open execute arbitrary code when the input is attacker-controlled.
Evidence
| 501 | return JSON.stringify(masked, null, 2); |
| 502 | } else if (format === "yaml") { |
| 503 | // Parse YAML to JSON, mask, then convert back to YAML |
| 504 | const parsed = yaml.load(output); |
| 505 | const masked = maskDataValues(parsed); |
| 506 | return yaml.dump(masked, { |
| 507 | indent: 2, |
Remediation
Replace pickle with json/msgpack or a schema-validated format (protobuf, cap'n proto). Use yaml.safe_load instead of yaml.load. Never deserialize data from an untrusted source with these APIs.
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
| 197 | # - DNS service labels |
| 198 | # 4. TESTING: Test connectivity after applying: |
| 199 | # kubectl exec -it deployment/mcp-server -- nslookup kubernetes.default |
| 200 | # kubectl exec -it deployment/mcp-server -- curl -k https://kubernetes.default/api |
| 201 | # 5. MONITORING: Monitor NetworkPolicy denials: |
| 202 | # kubectl logs -n kube-system -l app=calico-node | grep denied |
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.
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
| 41 | await server.connect(transport); |
| 42 | await transport.handleRequest(req, res, req.body); |
| 43 | } catch (error) { |
| 44 | console.error("Error handling MCP request:", error); |
| 45 | if (!res.headersSent) { |
| 46 | res.status(500).json({ |
| 47 | jsonrpc: "2.0", |
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
| 72 | // Session termination not needed in stateless mode |
| 73 | app.delete("/mcp", authMiddleware, async (req: express.Request, res: express.Response) => { |
| 74 | console.log("Received DELETE MCP request"); |
| 75 | res.writeHead(405).end( |
| 76 | JSON.stringify({ |
| 77 | jsonrpc: "2.0", |
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
| 57 | // SSE notifications not supported in stateless mode |
| 58 | app.get("/mcp", authMiddleware, async (req: express.Request, res: express.Response) => { |
| 59 | console.log("Received GET MCP request"); |
| 60 | res.writeHead(405).end( |
| 61 | JSON.stringify({ |
| 62 | jsonrpc: "2.0", |
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.
@types/js-yaml==4.0.9 last released 907 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.
Overly permissive file mode or IAM wildcard. chmod 0777 and IAM "Action": "*" or "Resource": "*" grant far more access than a least-privilege MCP server needs.
Evidence
| 100 | # "Version": "2012-10-17", |
| 101 | # "Statement": [ |
| 102 | # { |
| 103 | # "Effect": "Allow", |
| 104 | # "Action": [ |
| 105 | # "eks:DescribeCluster", |
| 106 | # "eks:ListClusters", |
| 107 | # "sts:AssumeRole" |
| 108 | # ], |
| 109 | # "Resource": "*" |
| 110 | # }, |
| 111 | # { |
| 112 | # "Effect": "Allow", |
Remediation
File modes: use 0600 for secrets, 0644 for read-only data, 0755 for directories. IAM: scope Action to the specific service+verb (s3:GetObject) and Resource to exact ARNs. Never use * for both.
MCP tool input schema exposes an unconstrained string/any field with a risky name (command/query/sql/code/script/url/path/expr/ eval). Any caller can pass arbitrary values, which typically widens the tool's blast radius well beyond its intent. Narrow the schema with `.enum()`, `.regex()`, `.max()`, `Literal[...]`, Pydantic `Field(max_length=..., pattern=...)`, or a JSON Schema `enum` / `pattern` / `maxLength`.
Evidence
| 141 | "custom": { |
| 142 | "type": "object", |
| 143 | "properties": { |
| 144 | "command": {"type": "string"}, |
| 145 | "args": {"type": "array", "items": {"type": "string"}} |
| 146 | } |
| 147 | }, |
Remediation
Shape the schema to the tool's actual intent: - Zod: chain `.enum([...])`, `.regex(/.../)`, or `.max(n)`; prefer `z.enum([...])` or `z.literal(...)` when the value set is small. - Pydantic: use `Literal["a", "b"]` or `Field(max_length=..., pattern=r"...")`. - JSON Schema: add `"enum"`, `"pattern"`, or `"maxLength"` to the property. An overbroad schema is an "overpowered tool" โ the model has nothing to prevent it from calling the tool with input far beyond what the tool's prose contract
MCP tool input schema exposes an unconstrained string/any field with a risky name (command/query/sql/code/script/url/path/expr/ eval). Any caller can pass arbitrary values, which typically widens the tool's blast radius well beyond its intent. Narrow the schema with `.enum()`, `.regex()`, `.max()`, `Literal[...]`, Pydantic `Field(max_length=..., pattern=...)`, or a JSON Schema `enum` / `pattern` / `maxLength`.
Evidence
| 58 | "items": { |
| 59 | "type": "object", |
| 60 | "properties": { |
| 61 | "path": {"type": "string"}, |
| 62 | "pathType": {"type": "string", "enum": ["Exact", "Prefix", "ImplementationSpecific"]} |
| 63 | } |
| 64 | } |
Remediation
Shape the schema to the tool's actual intent: - Zod: chain `.enum([...])`, `.regex(/.../)`, or `.max(n)`; prefer `z.enum([...])` or `z.literal(...)` when the value set is small. - Pydantic: use `Literal["a", "b"]` or `Field(max_length=..., pattern=r"...")`. - JSON Schema: add `"enum"`, `"pattern"`, or `"maxLength"` to the property. An overbroad schema is an "overpowered tool" โ the model has nothing to prevent it from calling the tool with input far beyond what the tool's prose contract
MCP tool input schema exposes an unconstrained string/any field with a risky name (command/query/sql/code/script/url/path/expr/ eval). Any caller can pass arbitrary values, which typically widens the tool's blast radius well beyond its intent. Narrow the schema with `.enum()`, `.regex()`, `.max()`, `Literal[...]`, Pydantic `Field(max_length=..., pattern=...)`, or a JSON Schema `enum` / `pattern` / `maxLength`.
Evidence
| 130 | "type": "object", |
| 131 | "properties": { |
| 132 | "name": {"type": "string"}, |
| 133 | "url": {"type": "string", "format": "uri"}, |
| 134 | "extraArgs": {"type": "array", "items": {"type": "string"}} |
| 135 | }, |
| 136 | "required": ["name", "url"] |
Remediation
Shape the schema to the tool's actual intent: - Zod: chain `.enum([...])`, `.regex(/.../)`, or `.max(n)`; prefer `z.enum([...])` or `z.literal(...)` when the value set is small. - Pydantic: use `Literal["a", "b"]` or `Field(max_length=..., pattern=r"...")`. - JSON Schema: add `"enum"`, `"pattern"`, or `"maxLength"` to the property. An overbroad schema is an "overpowered tool" โ the model has nothing to prevent it from calling the tool with input far beyond what the tool's prose contract
MCP tool input schema exposes an unconstrained string/any field with a risky name (command/query/sql/code/script/url/path/expr/ eval). Any caller can pass arbitrary values, which typically widens the tool's blast radius well beyond its intent. Narrow the schema with `.enum()`, `.regex()`, `.max()`, `Literal[...]`, Pydantic `Field(max_length=..., pattern=...)`, or a JSON Schema `enum` / `pattern` / `maxLength`.
Evidence
| 149 | "volume": { |
| 150 | "type": "object", |
| 151 | "properties": { |
| 152 | "path": {"type": "string"}, |
| 153 | "volumeSpec": {"type": "object"}, |
| 154 | "volumeMountSpec": {"type": "object"} |
| 155 | }, |
Remediation
Shape the schema to the tool's actual intent: - Zod: chain `.enum([...])`, `.regex(/.../)`, or `.max(n)`; prefer `z.enum([...])` or `z.literal(...)` when the value set is small. - Pydantic: use `Literal["a", "b"]` or `Field(max_length=..., pattern=r"...")`. - JSON Schema: add `"enum"`, `"pattern"`, or `"maxLength"` to the property. An overbroad schema is an "overpowered tool" โ the model has nothing to prevent it from calling the tool with input far beyond what the tool's prose contract
MCP manifest declares tools but no authentication field is present (none of: auth, authorization, bearer, oauth, mtls, apiKey, api_key, basic, token, authToken). Absence is a weak signal โ confirm whether the server relies on network-layer or host-level auth, or declare the real mechanism explicitly so reviewers can audit it.
Evidence
| 1 | { |
| 2 | "manifest_version": "0.1", |
| 3 | "name": "mcp-server-kubernetes", |
| 4 | "display_name": "Kubernetes MCP Server", |
| 5 | "version": "3.5.1", |
| 6 | "description": "MCP server for interacting with Kubernetes clusters via kubectl", |
| 7 | "long_description": "MCP Server that can connect to a Kubernetes cluster and manage it.\n\nBy default, the server loads kubeconfig from `~/.kube/config`.\n\nThe server will automatically connect to your current kubectl context. Make sure you have:\n\n1. kubectl installed and in your |
Remediation
Declare a real authentication mechanism in the manifest, matching what the running server actually enforces: - `"auth": "bearer"` with a token scheme documented for callers - `"auth": "oauth"` / `"oauth2": { ... }` for delegated flows - `"apiKey": { "header": "X-API-Key", "prefix": "..." }` - `"mtls": true` when client certificates are required If the server is intentionally unauthenticated (stdio-only, local developer tool, trusted-host network), document the assumption in the manifest via a `"
MCP manifest declares tools but no authentication field is present (none of: auth, authorization, bearer, oauth, mtls, apiKey, api_key, basic, token, authToken). Absence is a weak signal โ confirm whether the server relies on network-layer or host-level auth, or declare the real mechanism explicitly so reviewers can audit it.
Evidence
| 1 | # Observability with OpenTelemetry |
| 2 | |
| 3 | The Kubernetes MCP Server includes optional OpenTelemetry integration for distributed tracing, enabling comprehensive observability of tool executions, performance monitoring, and error tracking. |
| 4 | |
| 5 | > **Current Release**: Distributed Tracing โ
|
| 6 | > **Coming Soon**: Metrics and Logs |
| 7 | |
| 8 | ## Table of Contents |
| 9 | |
| 10 | - [Overview](#overview) |
| 11 | - [Quick Start](#quick-start) |
| 12 | - [Configuration](#configuration) |
| 13 | - [Deployment Examples](#deployment-examples) |
| 14 | - [Captured Telemetry](#captu |
Remediation
Declare a real authentication mechanism in the manifest, matching what the running server actually enforces: - `"auth": "bearer"` with a token scheme documented for callers - `"auth": "oauth"` / `"oauth2": { ... }` for delegated flows - `"apiKey": { "header": "X-API-Key", "prefix": "..." }` - `"mtls": true` when client certificates are required If the server is intentionally unauthenticated (stdio-only, local developer tool, trusted-host network), document the assumption in the manifest via a `"
MCP manifest declares tools but no authentication field is present (none of: auth, authorization, bearer, oauth, mtls, apiKey, api_key, basic, token, authToken). Absence is a weak signal โ confirm whether the server relies on network-layer or host-level auth, or declare the real mechanism explicitly so reviewers can audit it.
Evidence
| 1 | { |
| 2 | "name": "mcp-server-kubernetes", |
| 3 | "version": "3.5.1", |
| 4 | "mcpServers": { |
| 5 | "kubernetes": { |
| 6 | "command": "npx", |
| 7 | "args": [ |
| 8 | "mcp-server-kubernetes" |
| 9 | ] |
| 10 | } |
| 11 | } |
| 12 | } |
Remediation
Declare a real authentication mechanism in the manifest, matching what the running server actually enforces: - `"auth": "bearer"` with a token scheme documented for callers - `"auth": "oauth"` / `"oauth2": { ... }` for delegated flows - `"apiKey": { "header": "X-API-Key", "prefix": "..." }` - `"mtls": true` when client certificates are required If the server is intentionally unauthenticated (stdio-only, local developer tool, trusted-host network), document the assumption in the manifest via a `"
MCP manifest declares tools but no authentication field is present (none of: auth, authorization, bearer, oauth, mtls, apiKey, api_key, basic, token, authToken). Absence is a weak signal โ confirm whether the server relies on network-layer or host-level auth, or declare the real mechanism explicitly so reviewers can audit it.
Evidence
| 1 | # MCP Server Kubernetes |
| 2 | |
| 3 | [](https://github.com/yourusername/mcp-server-kubernetes/actions/workflows/ci.yml) |
| 4 | [](https://github.com/yourusername/mcp-server-kubernetes) |
| 5 | [](https://bun.sh) |
| 6 | [, document the assumption in the manifest via a `"
File registers a state-changing HTTP route (POST / PUT / PATCH / DELETE) but no CSRF protection middleware is applied anywhere in the file. If the server uses cookie-based session auth, a cross-site request from any origin can hit this route while the user's cookies ride along. Apply CSRF middleware: - Express: `csurf` / `csrf-csrf` / `lusca.csrf()` - FastAPI: `fastapi-csrf-protect` - Flask: `flask_wtf.csrf.CSRFProtect` Or, if the route is a JSON API authenticated by bearer tokens (no co
Evidence
| 1 | import { Server } from "@modelcontextprotocol/sdk/server/index.js"; |
| 2 | import express from "express"; |
| 3 | import { SSEServerTransport } from "@modelcontextprotocol/sdk/server/sse.js"; |
| 4 | import { createAuthMiddleware, isAuthEnabled } from "./auth.js"; |
| 5 | |
| 6 | export function startSSEServer(server: Server) { |
| 7 | const app = express(); |
| 8 | |
| 9 | // Create auth middleware - when MCP_AUTH_TOKEN is set, requires X-MCP-AUTH header |
| 10 | const authMiddleware = createAuthMiddleware(); |
| 11 | |
| 12 | // Currently just copying from docs & allowin |
Remediation
Apply CSRF middleware at the route or router level: - Express: `app.use(csurf())` / `csrf-csrf` package - FastAPI: `fastapi-csrf-protect` with `Depends(...)` - Flask: `CSRFProtect(app)` from `flask_wtf.csrf` Or move to bearer-token auth and set `SameSite=Strict` / `SameSite=Lax` on any session cookies. Document the choice in the project README so reviewers can confirm intent.
File registers a state-changing HTTP route (POST / PUT / PATCH / DELETE) but no CSRF protection middleware is applied anywhere in the file. If the server uses cookie-based session auth, a cross-site request from any origin can hit this route while the user's cookies ride along. Apply CSRF middleware: - Express: `csurf` / `csrf-csrf` / `lusca.csrf()` - FastAPI: `fastapi-csrf-protect` - Flask: `flask_wtf.csrf.CSRFProtect` Or, if the route is a JSON API authenticated by bearer tokens (no co
Evidence
| 1 | import express from "express"; |
| 2 | import { Server } from "@modelcontextprotocol/sdk/server/index.js"; |
| 3 | import { StreamableHTTPServerTransport } from "@modelcontextprotocol/sdk/server/streamableHttp.js"; |
| 4 | import http from "http"; |
| 5 | import { createAuthMiddleware, isAuthEnabled } from "./auth.js"; |
| 6 | |
| 7 | export function startStreamableHTTPServer(server: Server): http.Server { |
| 8 | const app = express(); |
| 9 | app.use(express.json()); |
| 10 | |
| 11 | // Create auth middleware - when MCP_AUTH_TOKEN is set, requires X-MCP-AUTH header |
Remediation
Apply CSRF middleware at the route or router level: - Express: `app.use(csurf())` / `csrf-csrf` package - FastAPI: `fastapi-csrf-protect` with `Depends(...)` - Flask: `CSRFProtect(app)` from `flask_wtf.csrf` Or move to bearer-token auth and set `SameSite=Strict` / `SameSite=Lax` on any session cookies. Document the choice in the project README so reviewers can confirm intent.
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 | run: bun install |
| 28 | |
| 29 | - name: Set up Helm |
| 30 | uses: azure/setup-helm@v4.3.0 |
| 31 | |
| 32 | - name: Login to GitHub Container Registry |
| 33 | uses: docker/login-action@v3 |
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
| 106 | run: bun run build |
| 107 | |
| 108 | - name: Setup Node.js for NPM publish |
| 109 | uses: actions/setup-node@v4 |
| 110 | with: |
| 111 | node-version: "20" |
| 112 | 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
| 145 | gh release upload v${{ steps.version.outputs.current-version }} mcp-server-kubernetes.zip --clobber |
| 146 | |
| 147 | - name: Set up QEMU |
| 148 | uses: docker/setup-qemu-action@v3 |
| 149 | |
| 150 | - name: Set up Docker Buildx |
| 151 | uses: docker/setup-buildx-action@v3 |
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 | steps: |
| 13 | - uses: actions/checkout@v4 |
| 14 | |
| 15 | - uses: oven-sh/setup-bun@v2 |
| 16 | with: |
| 17 | bun-version: latest |
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
| 148 | uses: docker/setup-qemu-action@v3 |
| 149 | |
| 150 | - name: Set up Docker Buildx |
| 151 | uses: docker/setup-buildx-action@v3 |
| 152 | |
| 153 | - name: Login to Docker Hub |
| 154 | uses: docker/login-action@v3 |
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
| 157 | password: ${{ secrets.DOCKER_HUB_TOKEN }} |
| 158 | |
| 159 | - name: Build and push Docker image |
| 160 | uses: docker/build-push-action@v5 |
| 161 | with: |
| 162 | context: . |
| 163 | platforms: linux/amd64,linux/arm64 |
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
| 14 | deploy: |
| 15 | runs-on: ubuntu-latest |
| 16 | steps: |
| 17 | - uses: actions/checkout@v4 |
| 18 | with: |
| 19 | fetch-depth: 0 |
| 20 | token: ${{ secrets.GITHUB_TOKEN }} |
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
| 73 | sed -i 's|http://localhost:8080|https://192.168.49.2:8443|g' ~/.kube/config |
| 74 | |
| 75 | - name: Update version number |
| 76 | uses: reecetech/version-increment@2024.10.1 |
| 77 | id: version |
| 78 | with: |
| 79 | scheme: semver |
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
| 19 | fetch-depth: 0 |
| 20 | token: ${{ secrets.GITHUB_TOKEN }} |
| 21 | |
| 22 | - uses: oven-sh/setup-bun@v2 |
| 23 | with: |
| 24 | bun-version: latest |
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
| 151 | uses: docker/setup-buildx-action@v3 |
| 152 | |
| 153 | - name: Login to Docker Hub |
| 154 | uses: docker/login-action@v3 |
| 155 | with: |
| 156 | username: ${{ secrets.DOCKER_HUB_USERNAME }} |
| 157 | password: ${{ secrets.DOCKER_HUB_TOKEN }} |
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
| 53 | bun run test --reporter default --reporter junit --outputFile junit-results.xml |
| 54 | |
| 55 | - name: Test Report |
| 56 | uses: dorny/test-reporter@v2 |
| 57 | if: always() |
| 58 | with: |
| 59 | name: Bun Tests # Name of the check run which will be created |
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 | test: |
| 11 | runs-on: ubuntu-latest |
| 12 | steps: |
| 13 | - uses: actions/checkout@v4 |
| 14 | |
| 15 | - uses: oven-sh/setup-bun@v2 |
| 16 | with: |
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
| 30 | uses: azure/setup-helm@v4.3.0 |
| 31 | |
| 32 | - name: Login to GitHub Container Registry |
| 33 | uses: docker/login-action@v3 |
| 34 | with: |
| 35 | registry: ghcr.io |
| 36 | username: ${{ github.actor }} |
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
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 | import * as fs from "fs"; |
| 2 | import * as os from "os"; |
| 3 | import * as path from "path"; |
| 4 | |
| 5 | import * as k8s from "@kubernetes/client-node"; |
| 6 | |
| 7 | import { ResourceTracker, PortForwardTracker, WatchTracker } from "../types.js"; |
| 8 | |
| 9 | export class KubernetesManager { |
| 10 | private resources: ResourceTracker[] = []; |
| 11 | private portForwards: PortForwardTracker[] = []; |
| 12 | private watches: WatchTracker[] = []; |
| 13 | private kc: k8s.KubeConfig; |
| 14 | private k8sApi: k8s.CoreV1Api; |
| 15 | private k8sAppsApi: k8s.AppsV1Api; |
| 16 | private k8sBatc |
Remediation
Replace check-then-use with action-then-handle: Python: `try: with open(p) as f: ... except FileNotFoundError: ...` Node: `try { fs.readFileSync(p); } catch (e) { if (e.code === "ENOENT") ... }` For atomic file creation: Python: `os.open(p, os.O_RDWR | os.O_CREAT | os.O_EXCL)` Node: `fs.open(p, "wx")` โ fails if file exists, no race. When you genuinely must check first, use `flock` (Python `fcntl`) or a similar per-process advisory lock to make the window uninteresting to a
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 | #!/usr/bin/env node |
| 2 | |
| 3 | import { readFileSync, writeFileSync, existsSync } from 'fs'; |
| 4 | import { join } from 'path'; |
| 5 | |
| 6 | const version = process.argv[2]; |
| 7 | if (!version) { |
| 8 | console.error('Usage: node scripts/update-version.js <version>'); |
| 9 | console.error('Example: node scripts/update-version.js 2.6.0'); |
| 10 | process.exit(1); |
| 11 | } |
| 12 | |
| 13 | // Validate version format (basic semver check) |
| 14 | if (!/^\d+\.\d+\.\d+(-[\w\.-]+)?$/.test(version)) { |
| 15 | console.error(`Invalid version format: ${version}`); |
| 16 | console.error('Expected f |
Remediation
Replace check-then-use with action-then-handle: Python: `try: with open(p) as f: ... except FileNotFoundError: ...` Node: `try { fs.readFileSync(p); } catch (e) { if (e.code === "ENOENT") ... }` For atomic file creation: Python: `os.open(p, os.O_RDWR | os.O_CREAT | os.O_EXCL)` Node: `fs.open(p, "wx")` โ fails if file exists, no race. When you genuinely must check first, use `flock` (Python `fcntl`) or a similar per-process advisory lock to make the window uninteresting to a
prod-us-west-2
env-cluster