Back to skill

Security audit

Smalltalk

Security checks for vulnerabilities and agentic risk

Overview

The skill is mostly coherent for Smalltalk development, but it includes external LLM code sharing, persistent daemons, and unsafe path/endpoint handling that users should review before installing.

Install only if you are comfortable with a Smalltalk tool that can execute and modify code, start background daemons, and send selected source code to LLM providers. Avoid using LLM commands on proprietary or secret code unless endpoints are trusted, do not set OPENAI_API_BASE to an untrusted or HTTP URL, and use backups/snapshots before dev-mode mutation or delete operations.

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
smalltalk.py:626
Finding

Unrestricted OpenAI-compatible endpoint can expose API credentials and source code

Content
View full analysis
str: """Query OpenAI-compatible chat completions API.""" import urllib.request import urllib.error base_url = os.environ.get("OPENAI_API_BASE", "https://api.openai.com/v1") model = os.environ.get("OPENAI_MODEL", "gpt-4o") messages = [] if system: messages.append({"role": "system", "content": system}) messages.append({"role": "user", "content": prompt}) body = json.dumps({ "model": model, "messages": messages, "temperature": 0.3, "max_tokens": 2048, }).encode() req = urllib.request.Request( f"{base_url}/chat/completions", data=body, headers={ "Authorization": f"Bearer {api_key}", "Content-Type": "application/json", }, ) try: with urllib.request.urlopen(req, timeout=30) as resp: data = json.loads(resp.read()) return data["choices"][0]["message"]["content"] except urllib.error.HTTPError as e: return f"Error: LLM API returned {e.code}: {e.read().decode()[:200]}" except Exception as e: return f"Error: LLM query failed: {e}" ``` ### Technical Analysis The `OPENAI_API_BASE` environment variable is accepted without validating its scheme, hostname, port, or trust level. The resulting URL receives both: 1. The value of `OPENAI_API_KEY` in the `Authorization` header. 2. The complete LLM prompt, which can contain Smalltalk source code obtained from the live image, an arbitrary `--source-file`, standard input, or inline source. The Skill documentation describes OpenAI as an LLM provider but does not document `OPENAI_API_BASE` or clearly warn that a custom endpoint receives the API credential ...[truncated 1999 chars]
Remediation
View remediation

T09 · Insecure Skill Coding Practices

Warning
Location
smalltalk-dev-daemon.py:27
Finding

Path traversal through project names permits arbitrary user-level file modification and process signaling

Content
View full analysis
Remediation
View remediation
Vulnerability Patterns
  • Data ExfiltrationExternal Transmission, Env Variable Harvesting, File System Enumeration
  • Privilege EscalationExcessive Permissions, Sudo/Root Execution, Credential Access
  • Excessive AgencyUnrestricted Tool Access, Autonomous Decision Making, Scope Creep
  • Tool MisuseTool Parameter Abuse, Chaining Abuse, Unsafe Defaults
  • Rogue AgentSelf-Modification, Session Persistence
Findings (43)

Tainted flow: 'req' from os.environ.get (line 649, credential/environment) → urllib.request.urlopen (network output)

Critical
Category
Data Flow
Confidence
94% confidence
Finding

The Anthropic request transmits user-supplied code and method content to an external service together with a credential taken from the environment. While sending to Anthropic is intentional, this is still a real data-exfiltration/privacy risk because the skill description is about local Smalltalk interaction, not external LLM export, and users are not clearly warned that their code leaves the machine.

Content

Scanner excerpt · smalltalk.py (reported line 620)May include surrounding context.

python
)

    try:
        with urllib.request.urlopen(req, timeout=30) as resp:
            data = json.loads(resp.read())
            return data["content"][0]["text"]
    except urllib.error.HTTPError as e:

Tainted flow: 'req' from os.environ.get (line 649, credential/environment) → urllib.request.urlopen (network output)

Critical
Category
Data Flow
Confidence
95% confidence
Finding

The OpenAI-compatible path is more dangerous because OPENAI_API_BASE is environment-controlled, so requests containing user code and the bearer token can be redirected to an arbitrary endpoint. That creates both sensitive data exfiltration and API-key leakage to attacker-controlled infrastructure.

Content

Scanner excerpt · smalltalk.py (reported line 659)May include surrounding context.

python
)

    try:
        with urllib.request.urlopen(req, timeout=30) as resp:
            data = json.loads(resp.read())
            return data["choices"][0]["message"]["content"]
    except urllib.error.HTTPError as e:

Tp4

High
Category
MCP Tool Poisoning
Confidence
96% confidence
Finding

The documented feature set extends beyond image interaction to third-party LLM calls, code and source transmission, destructive modification commands, and debug artifact generation under /tmp. That is a meaningful expansion of risk because it combines code access, persistence, network exfiltration, and mutation of the live image under a skill whose top-level description does not foreground those behaviors.

Content

No source excerpt is available for this finding.

Tp4

High
Category
MCP Tool Poisoning
Confidence
98% confidence
Finding

The documented feature set extends beyond image interaction to third-party LLM calls, code and source transmission, destructive modification commands, and debug artifact generation under /tmp. That is a meaningful expansion of risk because it combines code access, persistence, network exfiltration, and mutation of the live image under a skill whose top-level description does not foreground those behaviors.

Content

No source excerpt is available for this finding.

Tp4

High
Category
MCP Tool Poisoning
Confidence
99% confidence
Finding

The documented feature set extends beyond image interaction to third-party LLM calls, code and source transmission, destructive modification commands, and debug artifact generation under /tmp. That is a meaningful expansion of risk because it combines code access, persistence, network exfiltration, and mutation of the live image under a skill whose top-level description does not foreground those behaviors.

Content

No source excerpt is available for this finding.

Env Variable Harvesting

High
Category
Data Exfiltration
Confidence
60% confidence
Finding

Code enumerates, copies, or searches environment variables for secrets. Bulk environment access can collect credentials unrelated to the skill's stated purpose.

Content

Scanner excerpt · smalltalk-daemon.py (reported line 116)May include surrounding context.

python
# Note: For dev mode, we DON'T redirect changes to /dev/null
    # The Squeak startup code needs modification to not disable changes in dev mode
    # For now, we'll pass an env var to signal dev mode
    env = os.environ.copy()
    env['SMALLTALK_DEV_MODE'] = '1'
    env['SMALLTALK_PROJECT'] = project_name

Env Variable Harvesting

High
Category
Data Exfiltration
Confidence
60% confidence
Finding

Code enumerates, copies, or searches environment variables for secrets. Bulk environment access can collect credentials unrelated to the skill's stated purpose.

Content

Scanner excerpt · smalltalk-dev-daemon.py (reported line 90)May include surrounding context.

python
# Note: For dev mode, we DON'T redirect changes to /dev/null
    # The Squeak startup code needs modification to not disable changes in dev mode
    # For now, we'll pass an env var to signal dev mode
    env = os.environ.copy()
    env['SMALLTALK_DEV_MODE'] = '1'
    env['SMALLTALK_PROJECT'] = project_name

Env Variable Harvesting

High
Category
Data Exfiltration
Confidence
90% confidence
Finding

Copying the full environment into the spawned Squeak process propagates all available secrets and tokens, including LLM API keys, into another process that may not need them. If the VM, plugins, or image are compromised, this broad secret exposure expands the blast radius unnecessarily.

Content

Scanner excerpt · smalltalk.py (reported line 416)May include surrounding context.

python
time.sleep(2)
    
    # Start Squeak
    env = os.environ.copy()
    env["DISPLAY"] = ":98"
    squeak = subprocess.Popen(
        [vm_path, image_path, "--mcp"],

Undeclared Tool Scope

Medium
Category
MCP Least Privilege
Confidence
90% confidence
Finding

The skill declares broad capabilities including shell, network, file read/write, environment access, and MCP interaction, but does not declare any explicit tool scope or permission boundaries. In a skill that can evaluate code, modify classes/methods, and call external APIs, the absence of scoped permissions increases the chance of overbroad execution and unintended access to local or remote resources.

Content

No source excerpt is available for this finding.

Missing User Warnings

Medium
Category
Not specified by scanner
Confidence
90% confidence
Finding

The documentation exposes destructive commands such as delete-method and delete-class without a clear warning that, in dev mode, these changes may be permanent. In a live development image, accidental or unauthorized invocation could remove code and corrupt the user's working state.

Content

No source excerpt is available for this finding.

Session Persistence

Medium
Category
Rogue Agent
Confidence
84% confidence
Finding

Using nohup to launch a background daemon creates session persistence beyond the initiating interaction. A persistent process handling code evaluation and image access increases the attack window, can outlive user expectations, and may continue exposing local sockets, logs, or mutable state after the original task is complete.

Content

Scanner excerpt · SKILL.md (reported line 88)May include surrounding context.

bash
# Start playground daemon
nohup python3 smalltalk-daemon.py start > /tmp/daemon.log 2>&1 &

Dev Mode

Session Persistence

Medium
Category
Rogue Agent
Confidence
88% confidence
Finding

The dev-mode daemon is also launched via nohup, but in this case it is attached to a user-supplied persistent image. That makes the persistence more dangerous because the process can continue to mutate project state across sessions and may retain access to sensitive local development artifacts.

Content

Scanner excerpt · SKILL.md (reported line 97)May include surrounding context.

bash
# Start dev daemon with custom image
nohup python3 smalltalk-daemon.py start --dev --image ~/MyProject.image > /tmp/daemon.log 2>&1 &

Dev mode sets SMALLTALK_DEV_MODE=1 so the MCP server keeps the .changes file

Unsafe Defaults

Medium
Category
Tool Misuse
Confidence
60% confidence
Finding

Tool defaults are unsafe or overly permissive (e.g. disabled TLS verification, no authentication, world-writable permissions). Unsafe defaults widen the attack surface.

Content

Scanner excerpt · SKILL.md (reported line 100)May include surrounding context.

nohup python3 smalltalk-daemon.py start --dev --image ~/MyProject.image > /tmp/daemon.log 2>&1 &

text

Dev mode sets `SMALLTALK_DEV_MODE=1` so the MCP server keeps the .changes file
(instead of redirecting to /dev/null). The supplied image must have a matching
.changes file alongside it.

Description-Behavior Mismatch

Medium
Category
Not specified by scanner
Confidence
95% confidence
Finding

The skill adds LLM-powered explanation, audit, and test-generation functions that go beyond simple Smalltalk image interaction. Because these features may transmit proprietary method source or code context externally and can generate new code into the live image, they materially increase confidentiality and integrity risk compared with the stated scope.

Content

No source excerpt is available for this finding.

Context-Inappropriate Capability

Medium
Category
Not specified by scanner
Confidence
92% confidence
Finding

External-LLM/network-backed functionality is not essential to the core purpose of interacting with a live Smalltalk image, yet it is bundled into the same skill. That creates unnecessary data-exposure paths, especially when users may assume all operations remain local to the image and host.

Content

No source excerpt is available for this finding.

Session Persistence

Medium
Category
Rogue Agent
Confidence
75% confidence
Finding

Skill establishes unauthorized persistence across sessions via cron jobs, startup scripts, or state files. Session persistence allows an attacker to maintain access beyond the current interaction.

Content

Scanner excerpt · smalltalk-daemon.py (reported line 110)May include surrounding context.

python
# JMM-515: Start MCP via startUp: mechanism (not --doit).
        # MCPServer startUp: checks SMALLTALK_MCP_DAEMON env var and runs
        # inline during processStartUpList: — before Morphic blocks under xvfb.
        # No --doit needed; all setup is driven by env vars.
        cmd = [
            "xvfb-run", "-a", self.vm_path, self.image_path,

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 · smalltalk-daemon.py (reported line 124)May include surrounding context.

python
env["SMALLTALK_CHANGES_PATH"] = changes_path

        try:
            self.process = subprocess.Popen(
                cmd,
                stdin=subprocess.PIPE,
                stdout=subprocess.PIPE,

Tainted flow: 'PID_FILE' from os.environ.get (line 40, credential/environment) → open (file write)

Medium
Category
Data Flow
Confidence
75% confidence
Finding

PID_FILE is derived from the USER environment variable and written under /tmp, so an attacker who can influence the daemon's environment may redirect the PID file path or cause filename collisions. In a privileged or shared-user launch context, this can enable clobbering arbitrary files via symlink/path manipulation in /tmp or interfering with daemon state for other users.

Content

Scanner excerpt · smalltalk-daemon.py (reported line 411)May include surrounding context.

python
self.socket.settimeout(1.0)  # Allow periodic checks

        # Write PID file
        with open(PID_FILE, "w") as f:
            f.write(str(os.getpid()))

        print(f"🎧 Listening on {SOCKET_PATH}")

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 · smalltalk-dev-daemon.py (reported line 94)May include surrounding context.

python
env['SMALLTALK_DEV_MODE'] = '1'
    env['SMALLTALK_PROJECT'] = project_name
    
    proc = subprocess.Popen(
        cmd,
        stdout=subprocess.DEVNULL,
        stderr=subprocess.DEVNULL,

Session Persistence

Medium
Category
Rogue Agent
Confidence
65% confidence
Finding

Skill establishes unauthorized persistence across sessions via cron jobs, startup scripts, or state files. Session persistence allows an attacker to maintain access beyond the current interaction.

Content

Scanner excerpt · smalltalk.py (reported line 116)May include surrounding context.

python
print("🚀 Starting Smalltalk daemon...", file=sys.stderr)
    
    # Start daemon in background using nohup to survive parent exit
    try:
        subprocess.Popen(
            ["nohup", sys.executable, daemon_script, "start"],

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 · smalltalk.py (reported line 118)May include surrounding context.

python
# Start daemon in background using nohup to survive parent exit
    try:
        subprocess.Popen(
            ["nohup", sys.executable, daemon_script, "start"],
            stdout=subprocess.DEVNULL,
            stderr=subprocess.DEVNULL,

Sudo/Root Execution

Medium
Category
Privilege Escalation
Confidence
70% confidence
Finding

Commands invoke sudo or root privileges. Verify this elevated access is necessary and justified.

Content

Scanner excerpt · smalltalk.py (reported line 197)May include surrounding context.

python
if shutil.which("xvfb-run"):
        print("✅ xvfb-run found")
    else:
        print("❌ xvfb-run not found - install with: sudo apt install xvfb")
        all_ok = False

    # Check paths

subprocess module call

Medium
Category
Dangerous Code Execution
Confidence
83% confidence
Finding

The version-check path spawns the VM and image based on environment-controlled or auto-discovered paths. Even though the JSON-RPC input is fixed, the executed program is not, so this can still be abused for arbitrary local code execution if path sources are compromised.

Content

Scanner excerpt · smalltalk.py (reported line 238)May include surrounding context.

python
version_str = call_daemon("smalltalk_evaluate", {"code": "MCPServer version"})
            else:
                # No daemon running - spawn a quick VM to check
                result = subprocess.run(
                    ["xvfb-run", "-a", vm_path, image_path, "--mcp"],
                    input='{"jsonrpc":"2.0","id":1,"method":"tools/call","params":{"name":"smalltalk_evaluate","arguments":{"code":"MCPServer version"}}}\n',
                    capture_output=True,

Tainted flow: 'vm_path' from os.environ.get (line 75, credential/environment) → subprocess.run (code execution)

Medium
Category
Data Flow
Confidence
89% confidence
Finding

This is the same underlying issue as the other VM-launch paths: environment-derived vm_path is passed into subprocess.run. Because the skill automatically starts local executables, a manipulated environment can convert a setup check into arbitrary code execution.

Content

Scanner excerpt · smalltalk.py (reported line 238)May include surrounding context.

python
version_str = call_daemon("smalltalk_evaluate", {"code": "MCPServer version"})
            else:
                # No daemon running - spawn a quick VM to check
                result = subprocess.run(
                    ["xvfb-run", "-a", vm_path, image_path, "--mcp"],
                    input='{"jsonrpc":"2.0","id":1,"method":"tools/call","params":{"name":"smalltalk_evaluate","arguments":{"code":"MCPServer version"}}}\n',
                    capture_output=True,

subprocess module call

Medium
Category
Dangerous Code Execution
Confidence
83% confidence
Finding

This code starts the Smalltalk VM using paths ultimately sourced from environment variables or auto-detected files, creating a local code-execution boundary crossing. If an attacker can influence those paths or place a malicious binary/image in searched locations, the skill may execute attacker-controlled code.

Content

Scanner excerpt · smalltalk.py (reported line 296)May include surrounding context.

python
# Use xvfb-run for headless operation
        cmd = ["xvfb-run", "-a", self.vm_path, self.image_path, "--mcp"]

        self.process = subprocess.Popen(
            cmd,
            stdin=subprocess.PIPE,
            stdout=subprocess.PIPE,

Static analysis

No suspicious patterns detected.