Back to skill

Security audit

Redmine Issue

Security checks for vulnerabilities and agentic risk

Overview

This Redmine skill mostly does what it documents, but it is presented as read-oriented while also allowing immediate authenticated issue updates.

Install only if you are comfortable giving this skill Redmine credentials that may read and modify issues. Use a narrowly scoped API key, prefer read-only credentials for planning workflows, require HTTPS Redmine URLs, and avoid letting agents run the update command without an explicit human approval step.

Vulnerability Patterns
  • Insecure Skill Coding PracticesFinds exploitable flaws such as hardcoded secrets or command injection
  • Skill Instruction HijackingAlters the agent's session goals or safety constraints when the skill loads
  • Agent Memory PoisoningWrites attacker-controlled rules into memory that affect later sessions
  • Remote Payload Retrieval and ExecutionFetches external code whose behavior can change after review
  • Embedded Malicious CodeShips malicious scripts inside the skill and executes them locally
Findings (2)

T09 · Insecure Skill Coding Practices

Error
Location
scripts/issues.mjs:32
Finding
Redmine credentials can be transmitted over unencrypted HTTP<![CDATA[ ## Vulnerability Details **File Location**: `scripts/issues.mjs:32-68` **Related Documentation**: `SKILL.md:9-14, 39-43` **Vulnerability Type**: Missing transport security enforcement for sensitive credentials **Risk Level**: High ### Vulnerable Code ```js const REDMINE_URL = process.env.REDMINE_URL?.replace(/\/$/, ""); const REDMINE_API_KEY = process.env.REDMINE_API_KEY; const REDMINE_USERNAME = process.env.REDMINE_USERNAME; const REDMINE_PASSWORD = process.env.REDMINE_PASSWORD; if (!REDMINE_URL) { console.error("Missing REDMINE_URL"); process.exit(2); } const headers = { "Content-Type": "application/json", "Accept": "application/json", }; if (REDMINE_API_KEY) { headers["X-Redmine-API-Key"] = REDMINE_API_KEY; } const auth = (!REDMINE_API_KEY && REDMINE_USERNAME && REDMINE_PASSWORD) ? `Basic ${Buffer.from(`${REDMINE_USERNAME}:${REDMINE_PASSWORD}`).toString("base64")}` : null; if (auth) headers["Authorization"] = auth; if (!headers["X-Redmine-API-Key"] && !headers["Authorization"]) { console.error("Missing auth: set REDMINE_API_KEY or REDMINE_USERNAME+REDMINE_PASSWORD"); process.exit(3); } async function requestJson(path, { method = "GET", params = {}, body = undefined } = {}) { const url = new URL(`${REDMINE_URL}${path}`); for (const [k, v] of Object.entries(params)) { if (v !== undefined && v !== null && `${v}`.length > 0) url.searchParams.set(k, `${v}`); } const res = await fetch(url, { headers, method, body: body !== undefined ? JSON.stringify(body) : undefined, }); ``` ### Technical Analysis The script obtains an API key or username and password from environment variables and attaches them to every Redmine request. This authentication behavior is necessary for accessing protected Redmine resources, but the configured `REDMINE_URL` is not validated to require HTTPS. If `REDMINE_URL` uses `http:`, the API key or HTTP Basic Authorization header is transmitted without transport encryption. Bas ...[truncated 1957 chars]
Remediation
<![CDATA[ ## Remediation Suggestions 1. Parse and validate `REDMINE_URL` before creating authentication headers or making requests: ```js let redmineBaseUrl; try { redmineBaseUrl = new URL(process.env.REDMINE_URL); } catch { console.error("REDMINE_URL must be a valid URL"); process.exit(2); } if (redmineBaseUrl.protocol !== "https:") { console.error("REDMINE_URL must use HTTPS"); process.exit(2); } if (redmineBaseUrl.username || redmineBaseUrl.password) { console.error("REDMINE_URL must not contain embedded credentials"); process.exit(2); } redmineBaseUrl.pathname = redmineBaseUrl.pathname.replace(/\/$/, ""); ``` 2. Construct request URLs relative to the validated base URL rather than concatenating unvalidated strings. 3. If HTTP support is essential for isolated local development, require a separate explicit opt-in such as `REDMINE_ALLOW_INSECURE_HTTP=true`, limit it to loopback addresses where possible, and emit a prominent warning. 4. Review redirect behavior and reject redirects to a different origin or a non-HTTPS destination before credentials can be forwarded. 5. Recommend narrowly scoped API keys rather than account passwords, and use read-only credentials for `get` and `list` workflows. 6. Document that users must trust the configured Redmine host and verify its TLS certificate. ]]>

other

Warning
Location
scripts/issues.mjs:121
Finding
Read-oriented Skill metadata exposes undocumented-at-selection issue mutation privileges<![CDATA[ ## Vulnerability Details **File Location**: `scripts/issues.mjs:121-158` **Related Documentation**: `SKILL.md:2-3, 28-34` and `scripts/issues.mjs:21` **Vulnerability Type**: Excessive capability and declared-scope mismatch **Risk Level**: Medium ### Vulnerable Code The Skill metadata describes reading issues: ```yaml name: redmine-issue description: Read Redmine issues from any Redmine server via REST API with configurable URL and credentials. Use when you need to fetch a single issue, list/filter issues, or inspect issue fields for change planning; supports deployment to different Redmine instances via environment variables. ``` The executable script also supports consequential writes: ```js if (command === "update") { const id = getArg("id"); if (!id) usage(); const issue = {}; const maybe = (key, val) => { if (val !== undefined && val !== null && `${val}`.length > 0) issue[key] = val; }; maybe("subject", getArg("subject")); maybe("description", getArg("description")); maybe("status_id", getArg("status-id")); maybe("priority_id", getArg("priority-id")); maybe("assigned_to_id", getArg("assigned-to-id")); const doneRatio = getArg("done-ratio"); if (doneRatio !== undefined) { const n = Number(doneRatio); if (!Number.isFinite(n) || n < 0 || n > 100) { console.error("--done-ratio must be a number between 0 and 100"); process.exit(4); } issue.done_ratio = n; } maybe("notes", getArg("notes")); if (Object.keys(issue).length === 0) { console.error("No update fields provided"); process.exit(5); } await requestJson(`/issues/${encodeURIComponent(id)}.json`, { method: "PUT", body: { issue }, }); const refreshed = await requestJson(`/issues/${encodeURIComponent(id)}.json`, { params: { include: "attachments,journals,children,relations,watchers" }, }); console.log(JSON.stringify(refreshed, null, 2)); return; } ``` ### Technical Analysis The Skill name a ...[truncated 2300 chars]
Remediation
<![CDATA[ ## Remediation Suggestions 1. Separate read and write functionality into distinct Skills or executables. The read Skill should expose only `get` and `list`, while a clearly named write Skill should expose `update`. 2. If update functionality remains in this Skill, revise the name and top-level metadata to explicitly state that the Skill can modify Redmine issues. 3. Require explicit user confirmation immediately before an authenticated `PUT`, showing: - The target server; - The issue identifier; - Every field to be changed; - The proposed new values. 4. Add a `--dry-run` mode that prints the target URL and update body without transmitting the request. 5. Encourage separate credentials: - A read-only API key for `get` and `list`; - A narrowly scoped write credential used only after explicit authorization. 6. Consider disabling `update` by default and requiring an explicit environment setting such as `REDMINE_ENABLE_UPDATES=true`. 7. Validate numeric identifiers and update fields before sending them, and apply server-side workflow restrictions wherever possible. ]]>
Vulnerability Patterns
  • Data ExfiltrationExternal Transmission, Env Variable Harvesting, File System Enumeration
  • MCP Least PrivilegeUnderdeclared Capability, Wildcard Permission, Missing Permission Declaration
  • MCP Tool PoisoningHidden Instructions, Unicode Deception, Parameter Description Injection
  • Prompt InjectionInstruction Override, Hidden Instructions, Exfiltration Commands
  • Privilege EscalationExcessive Permissions, Sudo/Root Execution, Credential Access
Findings (7)

Description-Behavior Mismatch

High
Confidence
99% confidence
Finding
The manifest and description position this as a read-only issue inspection skill, but the documentation includes update commands that modify remote Redmine issues. This semantic mismatch is dangerous because an agent or user may invoke the skill expecting safe reads and instead perform state-changing actions against production issue trackers.

Description-Behavior Mismatch

High
Confidence
99% confidence
Finding
The skill metadata describes a read-only Redmine issue reader, but the script explicitly supports an `update` command that performs authenticated PUT requests to `/issues/{id}.json`. This mismatch is dangerous because users or higher-level agents may grant trust, permissions, or approval assuming read-only behavior, while the code can modify issue state, descriptions, assignments, and notes on any configured Redmine instance.

Lp3

Medium
Category
MCP Least Privilege
Confidence
91% confidence
Finding
The skill requires environment variables for credentials and communicates with arbitrary Redmine servers, but the manifest declares no explicit tool scope or permission boundaries. That mismatch can cause callers or hosting systems to treat the skill as lower-risk read-only documentation while it actually depends on env access and network access, increasing the chance of unintended credential exposure or unauthorized remote operations.

Intent-Code Divergence

Medium
Confidence
96% confidence
Finding
The opening description establishes a read-only trust boundary, then later the same document instructs users to perform updates. In skill ecosystems, misleading top-level descriptions can drive unsafe delegation decisions, especially when operators rely on the summary to decide whether a skill is safe to run automatically.

Missing User Warnings

Medium
Confidence
95% confidence
Finding
The skill provides concrete update commands but does not warn that they will mutate remote Redmine data, potentially on any configured server. Without explicit warnings, dry-run guidance, or confirmation steps, users and agents may unintentionally alter live tickets, assignments, priorities, notes, or status in production systems.

Missing User Warnings

Medium
Confidence
93% confidence
Finding
The script sends issue updates immediately once arguments are supplied, with no interactive confirmation, dry-run mode, or explicit warning that a state-changing action is about to occur. In an agent setting, this increases the chance of unintended modifications from prompt confusion, bad tool selection, or malicious instruction injection, especially because the skill is presented as a reader while actually having write capability.

Natural-Language Policy Violations

Low
Confidence
88% confidence
Finding
The example commands hard-code non-English note text (`開始處理`, `完成一半`) in a way that suggests a specific language preference without any opt-in or explanation. The policy requires avoiding language or locale constraints unless the skill offers a choice or documents a justified regional scope.

Static analysis

Detected: suspicious.env_credential_access

Environment variable access combined with network send.

Critical
Code
suspicious.env_credential_access
Location
scripts/issues.mjs:26