# Risk map — httpie/cli

> Canonical page: https://codevetter.com/unpack/httpie/risk-map

# Risk map — httpie/cli

Scanned commit 5b604c37c6c67e18e7c3e9aee6c88a8c22b98345 (2024-12-17). Generated by codevetter codevetter 1.12.0 on 2026-09-22.

The sharpest edges are in code that leaves the process or touches the filesystem outside the repo: the update daemon double-forks and re-executes the CLI, the plugin installer shells out to pip and then unlinks files by hand, and man-page display execs `man`. TLS handling has two deliberate-but-loud decisions -- a global `urllib3.disable_warnings()` and a `verify=False` fetch of the version index. Filesystem risk concentrates in download filename derivation and lock-file handling in the system temp dir. Structurally, `core.program()` and `_process_auth()` are both flagged in-repo as needing refactoring, and the argparse Namespace is a mutable god-object threaded through most modules, so changes to parse ordering have wide blast radius.
- `_fetch_updates` performs `requests.get(PACKAGE_INDEX_LINK, verify=False)` against https://packages.httpie.io/latest.json -- an unauthenticated, certificate-unverified network call whose result drives the version-comparison warning shown to users. — [httpie/internal/update_warnings.py#L16](https://github.com/httpie/cli/blob/5b604c37c6c67e18e7c3e9aee6c88a8c22b98345/httpie/internal/update_warnings.py#L16) [httpie/internal/update_warnings.py#L40-52](https://github.com/httpie/cli/blob/5b604c37c6c67e18e7c3e9aee6c88a8c22b98345/httpie/internal/update_warnings.py#L40-L52)
- `client.py` calls `urllib3.disable_warnings()` unconditionally at import, which silences InsecureRequestWarning process-wide -- including for `--verify=no` -- so a user disabling verification gets no library-level warning. — [httpie/client.py#L33](https://github.com/httpie/cli/blob/5b604c37c6c67e18e7c3e9aee6c88a8c22b98345/httpie/client.py#L33) [httpie/client.py#L301-311](https://github.com/httpie/cli/blob/5b604c37c6c67e18e7c3e9aee6c88a8c22b98345/httpie/client.py#L301-L311)
- Daemon mode is decided by a raw substring check (`'--daemon' in args`) before argparse ever runs, so the flag is reachable from ordinary argv; the blast radius is limited only by the `assert options.task_id in DAEMONIZED_TASKS` in `run_daemon_task`, and asserts vanish under `python -O`. — [httpie/internal/daemon_runner.py#L37-49](https://github.com/httpie/cli/blob/5b604c37c6c67e18e7c3e9aee6c88a8c22b98345/httpie/internal/daemon_runner.py#L37-L49) [httpie/core.py#L44-45](https://github.com/httpie/cli/blob/5b604c37c6c67e18e7c3e9aee6c88a8c22b98345/httpie/core.py#L44-L45)
- `_spawn_posix` double-forks, calls `os.setsid()`, closes stdin/stdout/stderr, and then re-enters `httpie.core.main(['http'] + args)` in-process with `suppress(BaseException)` -- a path that is effectively untestable in-process and whose failures are silent by construction. — [httpie/internal/daemons.py#L55-99](https://github.com/httpie/cli/blob/5b604c37c6c67e18e7c3e9aee6c88a8c22b98345/httpie/internal/daemons.py#L55-L99)
- `spawn_daemon` copies the full parent environment into the child process context and injects a computed `PYTHONPATH` derived from `inspect.stack()[0][1]`, so anything in the caller's env (including secrets) is inherited by the detached process. — [httpie/internal/daemons.py#L112-120](https://github.com/httpie/cli/blob/5b604c37c6c67e18e7c3e9aee6c88a8c22b98345/httpie/internal/daemons.py#L112-L120) [httpie/internal/daemons.py#L22-29](https://github.com/httpie/cli/blob/5b604c37c6c67e18e7c3e9aee6c88a8c22b98345/httpie/internal/daemons.py#L22-L29)
- `PluginInstaller._uninstall` deletes files directly with `os.unlink(distribution.locate_file(file))` for every entry in `distribution.files`; the only guard is a check that `self.dir` is in `base_dir.parents`, and the code's own TODO notes failures are not rolled back. — [httpie/manager/tasks/plugins.py#L148-182](https://github.com/httpie/cli/blob/5b604c37c6c67e18e7c3e9aee6c88a8c22b98345/httpie/manager/tasks/plugins.py#L148-L182)
- `_clear_metadata` calls `shutil.rmtree(meta_path)` on `.dist-info`/`.egg-info` directories whose version differs from the freshly installed one, driven by parsing the last line of pip's stdout (`Successfully installed ...`) -- a string-parsing dependency on pip's output format. — [httpie/manager/tasks/plugins.py#L111-146](https://github.com/httpie/cli/blob/5b604c37c6c67e18e7c3e9aee6c88a8c22b98345/httpie/manager/tasks/plugins.py#L111-L146)
- Plugin loading executes arbitrary third-party code at startup: `load_installed_plugins` calls `entry_point.load()` for every matching entry point, catching only `BaseException` into a warning -- so a malicious or broken plugin on the plugins path runs on every `http` invocation. — [httpie/plugins/manager.py#L66-80](https://github.com/httpie/cli/blob/5b604c37c6c67e18e7c3e9aee6c88a8c22b98345/httpie/plugins/manager.py#L66-L80) [httpie/core.py#L47](https://github.com/httpie/cli/blob/5b604c37c6c67e18e7c3e9aee6c88a8c22b98345/httpie/core.py#L47)
- Download filenames are attacker-influenced: `filename_from_content_disposition` takes the server-supplied header and sanitizes it only with `os.path.basename(...).lstrip('.').strip()`, after which `get_unique_filename` opens the result for append in the current working directory. — [httpie/downloads.py#L85-104](https://github.com/httpie/cli/blob/5b604c37c6c67e18e7c3e9aee6c88a8c22b98345/httpie/downloads.py#L85-L104) [httpie/downloads.py#L151-159](https://github.com/httpie/cli/blob/5b604c37c6c67e18e7c3e9aee6c88a8c22b98345/httpie/downloads.py#L151-L159) [httpie/downloads.py#L288-304](https://github.com/httpie/cli/blob/5b604c37c6c67e18e7c3e9aee6c88a8c22b98345/httpie/downloads.py#L288-L304)
- `open_with_lockfile` derives a lock path as base64 of the target path inside the shared system temp dir and relies on `touch(exist_ok=False)` for mutual exclusion; the lock is only removed in the finally block, so a hard kill leaves a stale lock that permanently raises `LockFileError` for that file. — [httpie/utils.py#L270-290](https://github.com/httpie/cli/blob/5b604c37c6c67e18e7c3e9aee6c88a8c22b98345/httpie/utils.py#L270-L290) [httpie/internal/update_warnings.py#L151-171](https://github.com/httpie/cli/blob/5b604c37c6c67e18e7c3e9aee6c88a8c22b98345/httpie/internal/update_warnings.py#L151-L171)
- Credentials flow into session files: when `--auth` is given alongside `--session`, `collect_messages` writes `{'type': auth_type, 'raw_auth': plugin.raw_auth}` into the session JSON, meaning plaintext `user:password` is persisted to disk under the config directory. — [httpie/client.py#L77-82](https://github.com/httpie/cli/blob/5b604c37c6c67e18e7c3e9aee6c88a8c22b98345/httpie/client.py#L77-L82) [httpie/sessions.py#L301-304](https://github.com/httpie/cli/blob/5b604c37c6c67e18e7c3e9aee6c88a8c22b98345/httpie/sessions.py#L301-L304) [httpie/config.py#L110-128](https://github.com/httpie/cli/blob/5b604c37c6c67e18e7c3e9aee6c88a8c22b98345/httpie/config.py#L110-L128)
- `_process_auth` also silently harvests credentials from two implicit sources -- a `user:pass@host` URL and the user's netrc via `get_netrc_auth` (for plugins with `netrc_parse = True`, i.e. basic and digest) -- unless `--ignore-netrc` forces an `ExplicitNullAuth`. — [httpie/cli/argparser.py#L282-355](https://github.com/httpie/cli/blob/5b604c37c6c67e18e7c3e9aee6c88a8c22b98345/httpie/cli/argparser.py#L282-L355) [httpie/plugins/builtin.py#L47-68](https://github.com/httpie/cli/blob/5b604c37c6c67e18e7c3e9aee6c88a8c22b98345/httpie/plugins/builtin.py#L47-L68) [httpie/utils.py#L124-130](https://github.com/httpie/cli/blob/5b604c37c6c67e18e7c3e9aee6c88a8c22b98345/httpie/utils.py#L124-L130)
- Two subprocess shell-outs exist outside the plugin installer: `man_pages.is_available`/`display_for` run `man 1 <program_name>` with `env.program_name` derived from argv[0], and the linux packaging build invokes `pyinstaller` via `subprocess.check_call`. — [httpie/output/ui/man_pages.py#L16-48](https://github.com/httpie/cli/blob/5b604c37c6c67e18e7c3e9aee6c88a8c22b98345/httpie/output/ui/man_pages.py#L16-L48) [httpie/core.py#L40](https://github.com/httpie/cli/blob/5b604c37c6c67e18e7c3e9aee6c88a8c22b98345/httpie/core.py#L40) [extras/packaging/linux/build.py#L23-38](https://github.com/httpie/cli/blob/5b604c37c6c67e18e7c3e9aee6c88a8c22b98345/extras/packaging/linux/build.py#L23-L38)
- Two central functions carry explicit in-repo refactor warnings: `core.program()` is marked 'TODO: Refactor and drastically simplify, especially so that the separator logic is elsewhere' and `_process_auth` is marked 'TODO: refactor & simplify this method' -- both are on every request path. — [httpie/core.py#L174-178](https://github.com/httpie/cli/blob/5b604c37c6c67e18e7c3e9aee6c88a8c22b98345/httpie/core.py#L174-L178) [httpie/cli/argparser.py#L282-284](https://github.com/httpie/cli/blob/5b604c37c6c67e18e7c3e9aee6c88a8c22b98345/httpie/cli/argparser.py#L282-L284) [httpie/cli/argparser.py#L79-81](https://github.com/httpie/cli/blob/5b604c37c6c67e18e7c3e9aee6c88a8c22b98345/httpie/cli/argparser.py#L79-L81)
- `_setup_standard_streams` mutates `env.stdout`/`env.stdout_isatty` in place during parsing (redirecting stdout to stderr for `--download`, to devnull for `--quiet`), so any later code that captured the original stream sees stale state -- a fragile coupling the code itself labels 'FIXME: Come up with a cleaner solution'. — [httpie/cli/argparser.py#L227-267](https://github.com/httpie/cli/blob/5b604c37c6c67e18e7c3e9aee6c88a8c22b98345/httpie/cli/argparser.py#L227-L267) [httpie/output/writer.py#L44-46](https://github.com/httpie/cli/blob/5b604c37c6c67e18e7c3e9aee6c88a8c22b98345/httpie/output/writer.py#L44-L46)
- The legacy session-format modules are live compatibility shims rather than dead code -- `pre_process`/`post_process` run on every session load and save, and the 3.1.0 fixer exists because domainless cookies were a documented security problem. — [httpie/legacy/v3_1_0_session_cookie_format.py#L8-33](https://github.com/httpie/cli/blob/5b604c37c6c67e18e7c3e9aee6c88a8c22b98345/httpie/legacy/v3_1_0_session_cookie_format.py#L8-L33) [httpie/sessions.py#L170-198](https://github.com/httpie/cli/blob/5b604c37c6c67e18e7c3e9aee6c88a8c22b98345/httpie/sessions.py#L170-L198) [httpie/manager/tasks/sessions.py#L11-14](https://github.com/httpie/cli/blob/5b604c37c6c67e18e7c3e9aee6c88a8c22b98345/httpie/manager/tasks/sessions.py#L11-L14)
- `HTTPieCookiePolicy` widens cookie security by treating `localhost` and `*.localhost` as secure origins, so `Secure` cookies are sent over plain http to those hosts -- intentional (mirrors Firefox) but a deviation from the stdlib default that affects every request. — [httpie/cookies.py#L8-25](https://github.com/httpie/cli/blob/5b604c37c6c67e18e7c3e9aee6c88a8c22b98345/httpie/cookies.py#L8-L25) [httpie/compat.py#L9-12](https://github.com/httpie/cli/blob/5b604c37c6c67e18e7c3e9aee6c88a8c22b98345/httpie/compat.py#L9-L12)

Every claim links to its cited files at the pinned commit. Citations identify source evidence; they do not establish runtime correctness. Independent CodeVetter scan — not reviewed by upstream maintainers.

## Public product links

- [CodeVetter](https://codevetter.com/)
- [Download](https://codevetter.com/download)
- [Documentation](https://codevetter.com/docs/)
- [Source](https://github.com/Codevetter/codevetter)
