Back to skill

Security audit

PR Delivery through Slack

Security checks for vulnerabilities and agentic risk

Overview

This skill is review-worthy because its Slack review checker can be fooled by crafted thread text into treating an ordinary message as the trusted review bot's clean result.

Install only if you are willing to treat the included Slack review checker as advisory until it is changed to use structured Slack connector metadata for sender, channel, thread, and timestamp. Manually verify the trusted review bot's actual message before announcing clean results, restrict Bitbucket credentials to the minimum needed PR scope, and review the unused Jira helper before extending the workflow.

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 (1)

T09 · Insecure Skill Coding Practices

Error
Location
scripts/check_review.py:12
Finding

Slack thread text injection permits trusted review-bot impersonation

Content
View full analysis

Vulnerability Details

File Location: scripts/check_review.py, lines 12–18
Vulnerability Type: Improper parsing of untrusted message content as trusted identity metadata
Risk Level: High

Complete Code Snippet

python
MESSAGE = re.compile(
    r"^From: [^\n]*\((U\w+)\)\nTime: [^\n]*\nMessage TS: (\d+\.\d{6})\n(.*?)"
    r"(?=\n+(?:--- Reply \d+ of \d+ ---|=== THREAD REPLIES)|\Z)", re.M | re.S)


def thread_messages(text):
    return [{"user": user, "ts": ts, "text": body.strip()} for user, ts, body in MESSAGE.findall(text)]

The parsed identities are subsequently treated as authoritative:

python
def check(thread_text, handoff, bitbucket, config):
    messages = thread_messages(thread_text)
    expected = set().union(*(urls(pr["url"]) for pr in handoff["pull_requests"]))
    if not messages or urls(messages[0]["text"]) != expected:
        return {"verdict": "needs_attention", "reason": "Review parent message does not match the PR batch"}
    verdict, reason = review_verdict(messages[1:], [pr["url"] for pr in handoff["pull_requests"]],
                                     config["review_bot_user_id"])
    if verdict == "clean":
        stale = changed_commits(bitbucket, handoff)
        if stale:
            return {"verdict": "needs_attention", "reason": stale}
        message = engineering_message(handoff)
        if len(message) > 3900:
            return {"verdict": "needs_attention", "reason": "Shorten the engineering summary"}
        return {"verdict": "clean", "reason": reason, "engineering_message": message}

Technical Analysis

The --thread input is a plain-text serialization of Slack messages. thread_messages() uses record delimiters such as --- Reply N of N --- and then extracts the sender ID from a textual From: line. These fields are not cryptographically or structurally bound to trusted connector metadata.

An ordinary channel participant controls their Slack message body. If that body contains a newli ...[truncated 2345 chars]

Remediation
View remediation

Remediation Suggestions

  1. Stop deriving sender identity, channel identity, thread identity, and timestamps from display text. Consume structured connector records whose metadata fields are supplied separately from message bodies.
  2. Bind each message to immutable connector-provided metadata, including:
    • Sender/user ID
    • Channel ID
    • Parent thread timestamp
    • Message timestamp
  3. Store the expected review channel and parent timestamp in the handoff, then verify both before evaluating any review result.
  4. If a text interchange format is unavoidable, serialize messages as JSON with message bodies represented as JSON strings, or use length-prefixed records. Do not use delimiters that can appear unescaped in message content.
  5. Reject snapshots containing duplicate or malformed records, embedded serialization markers in message bodies, unexpected sender identities, or records outside the expected thread.
  6. Add regression tests in which an untrusted participant embeds --- Reply ... ---, From:, Message TS:, forged job-start text, and forged completion text in a message. Confirm that these values remain message content and cannot produce a clean verdict.
  7. Preserve the existing source- and destination-commit freshness checks as an independent gate after authenticating the review result.
Vulnerability Patterns
  • Data ExfiltrationExternal Transmission, Env Variable Harvesting, File System Enumeration
  • Behavioral ASTexec() Call, eval() Call, Dynamic Import
  • 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
Findings (8)

Tp4

High
Category
MCP Tool Poisoning
Confidence
94% confidence
Finding

The core implemented behavior partially matches the Bitbucket PR preparation portion of the description: the script inspects repositories, checks branch state, finds existing PRs, creates or updates PRs, and verifies reviewer assignment. However, significant declared functionality is absent. There is no Slack integration, no request-review messaging through connected tools, and no post-review announcement flow. The description also says announcement should occur only after a verified clean review for unchanged commits, but this code only verifies branch and commit consistency at PR preparation time and never evaluates review outcomes. Additionally, the script performs local git cleanliness and branch publication checks, which are ancillary but not harmful. Because major declared capabilities are missing, the description does not accurately represent the supplied code chunk.

Content

No source excerpt is available for this finding.

Tp4

High
Category
MCP Tool Poisoning
Confidence
93% confidence
Finding

The declared description presents an action-oriented publishing workflow: create/reuse PRs, assign reviewers, trigger review requests, and announce after a clean review. This code chunk does none of those actions. It only reads/parses message text and computes a status/verdict about whether a cross-review job finished cleanly for a set of expected PR URLs. While this could be a supporting component within a larger workflow, its actual behavior is specifically review-result validation and state tracking, which is materially different from the declared primary purpose and omits the core claimed capabilities.

Content

No source excerpt is available for this finding.

Tp4

High
Category
MCP Tool Poisoning
Confidence
95% confidence
Finding

This code chunk is primarily configuration and authentication setup, not PR publication/review orchestration. The most notable undeclared capability is Jira access: it validates a jira_url and creates a Jira-authenticated session using JIRA_EMAIL and JIRA_API_TOKEN, while the declared purpose mentions Bitbucket and Slack only. Although helper/setup code can be a supporting detail, adding an external integration with Jira is an inconsistent resource access not reflected in the description. Also, the chunk does not perform the main declared actions, so the observed behavior does not accurately match the stated purpose.

Content

No source excerpt is available for this finding.

Undeclared Tool Scope

Medium
Category
MCP Least Privilege
Confidence
95% confidence
Finding

The skill directs the agent to use sensitive capabilities including shell, network access, filesystem reads/writes, and environment-derived private configuration, but it does not declare any explicit tool scope or permission boundaries. That makes the operational trust model ambiguous and increases the risk of over-broad execution, accidental misuse of credentials, or use of unintended tools during PR creation and Slack messaging workflows.

Content

No source excerpt is available for this finding.

External Transmission

Medium
Category
Data Exfiltration
Confidence
60% confidence
Finding

Data is being sent to an external URL. This could be legitimate telemetry or data exfiltration. Manual review is recommended.

Content

Scanner excerpt · scripts/clients.py (reported line 30)May include surrounding context.

python
self.session = session if session is not None else bitbucket_session()

    def request(self, method, path, **kwargs):
        url = path if path.startswith("https://") else "https://api.bitbucket.org/2.0/" + path
        if urlparse(url).hostname != "api.bitbucket.org":
            raise WorkflowError("Unexpected Bitbucket API host")
        try:

subprocess module call

Medium
Category
Dangerous Code Execution
Confidence
70% confidence
Finding

subprocess module calls execute external commands. Without careful input validation, this enables command injection.

Content

Scanner excerpt · scripts/prepare_prs.py (reported line 13)May include surrounding context.

python
def git(repository, *arguments):
    result = subprocess.run(["git", "-C", str(repository), *arguments], capture_output=True, text=True)
    if result.returncode:
        raise WorkflowError("Git command failed: " + " ".join(arguments))
    return result.stdout.strip()

Natural-Language Policy Violations

Medium
Category
Not specified by scanner
Confidence
97% confidence
Finding

The check at L094 rejects any PR content unless it is in English, which is a natural-language locale restriction. This file provides no user choice or justification for the language constraint, so it appears to violate the policy against forcing a specific language without opt-in.

Content

No source excerpt is available for this finding.

Missing User Warnings

Medium
Category
Not specified by scanner
Confidence
90% confidence
Finding

The code reads sensitive environment variables for JIRA and Bitbucket authentication (JIRA_EMAIL, JIRA_API_TOKEN, BITBUCKET_USERNAME, BITBUCKET_API_TOKEN) but provides no user-facing print/log message, comment, or docstring disclosing that credentials will be accessed. Under the code-file criteria, access to sensitive environment variables should have some visible warning unless clearly documented elsewhere in this file, which it is not.

Content

No source excerpt is available for this finding.

Static analysis

No suspicious patterns detected.