Back to skill

Security audit

Veto Approval (Human-in-the-Loop)

Security checks for vulnerabilities and agentic risk

Overview

This skill does what it says: it asks a human for approval through VetoAPI before continuing, though users should understand that approval details are sent to that service and the wait can be indefinite.

Before installing, confirm you are comfortable sending the approver email, action title, optional context, and VetoAPI key to vetoapi.com. Avoid putting secrets, regulated data, or unnecessary business details in the approval context, and consider adding timeouts or a maximum wait period before using it in automated workflows.

Vulnerability Patterns
  • Insecure DependenciesIntroduces malicious components through unsafe dependency sources
  • 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
Findings (2)

T09 · Insecure Skill Coding Practices

Warning
Location
veto_skill.py:42
Finding

Unbounded HTTP Requests and Polling Can Block the Agent Indefinitely

Content
View full analysis

Vulnerability Details

File Location: veto_skill.py, lines 42–69
Vulnerability Type: Missing network timeouts and approval deadline
Risk Level: Medium

Vulnerable Code

python
        response = requests.post(f"{BASE_URL}/request", json=payload)

        if response.status_code == 401:
            print("Error: Invalid VetoAPI key.")
            return False

        response.raise_for_status()
        request_id = response.json().get("request_id")

        print(f"Approval email sent to {user_email}.")
        print(f"Waiting for decision (Request ID: {request_id})...")

        while True:
            poll = requests.get(f"{BASE_URL}/request/{request_id}")
            poll.raise_for_status()
            status = poll.json().get("status")

            if status == "approved":
                print("\n[APPROVED] Human gave the green light. Proceeding...")
                return True
            elif status == "rejected":
                print("\n[REJECTED] Human denied the request. Aborting.")
                return False

            sys.stdout.write(".")
            sys.stdout.flush()
            time.sleep(5)

Technical Analysis

Both requests.post() and requests.get() are called without connect or read timeouts. A remote endpoint or intermediary can therefore leave a connection open without producing a complete response, causing the calling thread to wait indefinitely.

The approval loop is also unconditional and has no overall deadline, attempt limit, or handling for unexpected terminal states. If the service continually returns a pending or unknown status, the function never returns. In addition, request_id is extracted without validation. A successful response that omits this field causes the code to poll /request/None, which may also remain in a nonterminal state.

Although request exceptions are caught elsewhere in the function, no exception is raised me ...[truncated 946 chars]

Remediation
View remediation

Remediation Suggestions

  • Set explicit connect and read timeouts on every request, for example:
    python
    timeout = (5, 15)
    response = requests.post(
        f"{BASE_URL}/request",
        json=payload,
        timeout=timeout,
    )
    
  • Apply the same timeout policy to polling requests.
  • Enforce a configurable overall approval deadline using a monotonic clock.
  • Set a maximum number of polling attempts and fail closed when it is exceeded.
  • Validate that request_id is a nonempty string before beginning polling.
  • Reject unknown or malformed response statuses rather than polling forever.
  • Catch timeout exceptions explicitly and return False with an appropriate diagnostic.
  • Consider accepting a cancellation signal so callers can terminate approval waits cleanly.

T08 · Insecure Dependencies

Note
Location
README.md:6
Finding

Unpinned Runtime Dependencies Create Supply-Chain and Reproducibility Risk

Content
View full analysis

Vulnerability Details

File Location: README.md, lines 6–8
Vulnerability Type: Unpinned third-party dependencies
Risk Level: Low

Vulnerable Code

bash
pip install requests python-dotenv

Technical Analysis

The installation instructions install requests and python-dotenv without exact version constraints, a lockfile, or package hashes. Consequently, each installation resolves whatever versions are currently available from the configured package index.

The documented package names match the imports in the source code, and there is no evidence of typosquatting or an intentionally malicious dependency. Nevertheless, the installation process implicitly trusts future package releases and the integrity of the selected package index. It also makes builds non-reproducible and can introduce incompatible behavior without any change to this project.

Attack Path

  1. A user follows the documented installation command.
  2. pip queries the environment's configured package index and selects the latest compatible releases.
  3. If an upstream release, package-index account, index mirror, or dependency in the transitive chain is compromised, the affected package is downloaded.
  4. Package installation hooks or imported runtime code execute with the privileges of the user running the skill.
  5. The compromised dependency may access environment variables, including VETO_API_KEY, or alter the skill's network behavior.

This path depends on an upstream or package-index compromise; no such compromise is present in the audited repository.

Impact Assessment

A compromised dependency would execute with the same operating-system privileges as the Python process. It could read files and environment variables accessible to that process, exfiltrate the VetoAPI credential, alter approval results, or perform arbitrary actions allowed to the user. The practical likelihood is reduced because the dependencies are ...[truncated 69 chars]

Remediation
View remediation

Remediation Suggestions

  • Define reviewed, exact dependency versions in a requirements or lock file.
  • Generate and verify cryptographic hashes for all direct and transitive packages.
  • Install with hash enforcement, for example:
    bash
    pip install --require-hashes -r requirements.txt
    
  • Use an automated dependency update process that tests and reviews version changes before merging them.
  • Configure trusted package indexes explicitly and avoid untrusted mirrors.
  • Run dependency vulnerability and provenance checks in continuous integration.
  • Install and execute the skill in a least-privileged virtual environment or container.
Vulnerability Patterns
  • Data ExfiltrationExternal Transmission, Env Variable Harvesting, File System Enumeration
  • Privilege EscalationExcessive Permissions, Sudo/Root Execution, Credential Access
  • Taint TrackingDirect Taint Flow, Variable-Mediated Taint Flow, Credential Exfiltration Chain
  • MCP Least PrivilegeUnderdeclared Capability, Wildcard Permission, Missing Permission Declaration
  • Prompt InjectionInstruction Override, Hidden Instructions, Exfiltration Commands
Findings (7)

Tainted flow: 'payload' from os.getenv (line 31, credential/environment) → requests.post (network output)

Critical
Category
Data Flow
Confidence
90% confidence
Finding

Credentials or environment variables flow to a network sink. This is a high-confidence indicator of credential exfiltration.

Content

Scanner excerpt · veto_skill.py (reported line 40)May include surrounding context.

python
try:
        print(f"Requesting approval for: '{action_title}'...")
        response = requests.post(f"{BASE_URL}/request", json=payload)

        if response.status_code == 401:
            print("Error: Invalid VetoAPI key.")

Credential Access

High
Category
Privilege Escalation
Confidence
60% confidence
Finding

Code accesses credential files (SSH keys, AWS credentials, etc.). This could indicate credential theft attempts.

Content

Scanner excerpt · veto_skill.py (reported line 7)May include surrounding context.

python
import sys
from dotenv import load_dotenv

# Load .env from current or parent directory
load_dotenv()

VETO_API_KEY = os.getenv("VETO_API_KEY")

Credential Access

High
Category
Privilege Escalation
Confidence
60% confidence
Finding

Code accesses credential files (SSH keys, AWS credentials, etc.). This could indicate credential theft attempts.

Content

Scanner excerpt · veto_skill.py (reported line 28)May include surrounding context.

python
import sys
from dotenv import load_dotenv

# Load .env from current or parent directory
load_dotenv()

VETO_API_KEY = os.getenv("VETO_API_KEY")

Undeclared Tool Scope

Medium
Category
MCP Least Privilege
Confidence
94% confidence
Finding

The skill performs environment-variable access and network-based email approval flows, but it does not declare any tool scope or permissions metadata. This creates a transparency and governance gap: an agent or reviewer cannot easily determine that using the skill will access secrets and send data externally, increasing the chance of unintended data exposure or unsafe execution.

Content

No source excerpt is available for this finding.

Missing User Warnings

Medium
Category
Not specified by scanner
Confidence
96% confidence
Finding

The skill description and usage imply that action details and the approver's email are sent to an external service, but they do not warn about the privacy implications. Users may unknowingly transmit sensitive business context, personal data, or operational details to a third party, which is especially risky because the skill is intended for high-risk actions and may include confidential approval context.

Content

No source excerpt is available for this finding.

External Transmission

Medium
Category
Data Exfiltration
Confidence
80% 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 · veto_skill.py (reported line 40)May include surrounding context.

python
try:
        print(f"Requesting approval for: '{action_title}'...")
        response = requests.post(f"{BASE_URL}/request", json=payload)

        if response.status_code == 401:
            print("Error: Invalid VetoAPI key.")

Tainted flow: 'request_id' from requests.post (line 47, network input) → requests.get (network output)

Medium
Category
Data Flow
Confidence
65% confidence
Finding

Data from a source is assigned to a variable that is later passed to a sink, creating a variable-mediated taint flow.

Content

Scanner excerpt · veto_skill.py (reported line 53)May include surrounding context.

python
print(f"Waiting for decision (Request ID: {request_id})...")

        while True:
            poll = requests.get(f"{BASE_URL}/request/{request_id}")
            poll.raise_for_status()
            status = poll.json().get("status")

Static analysis

No suspicious patterns detected.