GitHub Security Advisory: GHSA-83pf-v6qq-pwmr
Package: fickling (PIP)
Severity: LOW (CVSS 0.0)
CWE: CWE-184: Incomplete List of Disallowed Inputs
Vulnerable versions: <= 0.1.7
Patched in: 0.1.8

Summary: Fickling has a detection bypass via stdlib network-protocol constructors

Details:
# Our assessment

`imtplib`, `imaplib`, `ftplib`, `poplib`, `telnetlib`, and `nntplib` were added to the list of unsafe imports (https://github.com/trailofbits/fickling/commit/6d20564d23acf14b42ec883908aed159be7b9ade). The `UnusedVariables` heuristic works as expected.

# Original report 

## Summary

Fickling's `check_safety()` API and `--check-safety` CLI flag incorrectly rate as
`LIKELY_SAFE` pickle files that open outbound TCP connections at deserialization time
using stdlib network-protocol constructors: `smtplib.SMTP`, `imaplib.IMAP4`,
`ftplib.FTP`, `poplib.POP3`, `telnetlib.Telnet`, and `nntplib.NNTP`.

The bypass exploits two independent root causes described below.

---

## Root Cause 1: Incomplete blocklist (fixed in PR #233)

`fickling/fickle.py` (lines 41-97) defines `UNSAFE_IMPORTS`, the primary blocklist.
`fickling/analysis.py` (lines 229-248) defines the parallel
`UnsafeImportsML.UNSAFE_MODULES` dict. Both omitted the following stdlib
network-protocol modules whose constructors open a TCP socket at instantiation time:

| Module | Class | Default port | Constructor side-effect |
|---|---|---|---|
| `smtplib` | `SMTP` | 25 | TCP connect, reads SMTP banner, sends EHLO |
| `imaplib` | `IMAP4` | 143 | TCP connect, reads IMAP capability banner |
| `ftplib` | `FTP` | 21 | TCP connect, reads FTP welcome banner |
| `poplib` | `POP3` | 110 | TCP connect, reads POP3 greeting |
| `telnetlib` | `Telnet` | 23 | TCP connect |
| `nntplib` | `NNTP` | 119 | TCP connect, NNTP handshake |

Because these module names were absent from both blocklists, `UnsafeImportsML`,
`UnsafeImports`, and `NonStandardImports` all stayed silent. All six are genuine
stdlib modules so `is_std_module()` returned `True` and `NonStandardImports` did
not fire.

**Status: patched in PR #233.** The six modules have been added to `UNSAFE_IMPORTS`.

---

## Root Cause 2: Logic flaw in `unused_assignments()` at `fickle.py:1183` (unpatched)

### Description

`unused_assignments()` in `fickling/fickle.py` (lines 1174-1204) identifies variables
that are assigned but never referenced. `UnusedVariables` analysis calls this method
and raises `SUSPICIOUS` for any unreferenced variable -- this would otherwise catch a
bare `REDUCE` opcode that stores its result without using it.

The flaw is at line 1183. The method iterates over `module_body` statements and, when
it encounters the final `result = <expr>` assignment, breaks out of the loop
immediately without first walking the right-hand side expression for `Name` references:

```python
# fickling/fickle.py:1183 (current code -- vulnerable)
if (
    len(statement.targets) == 1
    and isinstance(statement.targets[0], ast.Name)
    and statement.targets[0].id == "result"
):
    # this is the return value of the program
    break   # exits WITHOUT scanning statement.value
```

Any variable that appears only in the RHS of `result = <expr>` is therefore never
added to the `used` set and is incorrectly classified as unused.

### How this enabl