MoonshotAI / MoonshotAI/kimi-cli

[Security] Auto-updater downloads + executes binary with no integrity verification (no sha256/signature) and uses unfiltered `tarfile.extractall`

Open
#2,273 1 comment 0 reactions 0 assignees View on GitHub

Nobody has claimed this yet.

Dominant language
Python
Stars
11.4k
Forks
1.3k
Avg merge
9h 47m
Merged PRs (30d)
2

Description

Summary

The bundled-binary auto-updater (src/kimi_cli/ui/shell/update.py) downloads a tarball from https://cdn.kimi.com/binaries/kimi-cli/{version}/..., extracts it with unfiltered tarfile.extractall, copies the kimi binary out, marks it executable, and the user runs it on next launch — all without verifying:

  • the SHA-256 of the tarball against a published manifest,
  • a signature (minisign / cosign / GPG) over the tarball or the binary,
  • or even the byte length against a manifest.

HTTPS protects the channel against passive MITM, but it does not protect against:

  1. A compromise or misconfiguration of cdn.kimi.com (cache poisoning, accidental publish of a wrong artifact).
  2. A user/enterprise environment with a custom root CA installed (corporate proxies, mitmproxy testing setups, malware-installed root CAs).
  3. A future migration to a different CDN where TLS settings differ.

The first one is the real concern: a single bad publish silently pushes a compromised binary to every kimi-cli user on next auto-update. Because the binary is then chmod +x and executed as the user, this is straightforwardly RCE-on-end-user equivalent.

This pattern is well-understood; modern release tooling (GoReleaser, cargo-dist, Homebrew bottles, npm-with-provenance) all ship a manifest with sha256 sums + optional signatures specifically to break this trust dependency.

Secondary issue (smaller): unfiltered tarfile.extractall(tmpdir)

Python 3.12 deprecated extractall() without an explicit filter= argument; Python 3.14 (which .python-version pins) emits a DeprecationWarning, and a future release will raise. CVE-2007-4559 (the original "tar slip" bug) made filter='data' the recommended default — even for trusted sources, since it's defense-in-depth and silences the deprecation:

- tar.extractall(tmpdir)
+ tar.extractall(tmpdir, filter="data")  # PEP 706

I noticed kimi-cli's other archive-extraction sites (src/kimi_cli/cli/plugin.py:77, src/kimi_cli/vis/api/sessions.py:657) both have explicit path-traversal checks before extractall. The auto-updater is the lone outlier.

Repro

I have not exploited this — there is nothing to exploit on a healthy cdn.kimi.com. The repro is structural: read src/kimi_cli/ui/shell/update.py and search for any call into hashlib, cryptography, signify, minisign, gpg, etc. There are none. The download → extract → chmod +x → execute path runs unconditionally if the HTTPS download itself succeeds.

Proposed fix

Two parts.

1. Publish + verify a sha256 manifest. Alongside each kimi-{version}-{target}.tar.gz on the CDN, publish kimi-{version}-{target}.tar.gz.sha256 (and ideally a single manifest.json per version with all targets). The updater fetches both, verifies, and only then extracts:

import hashlib

# After downloading tar to tar_path, also fetch the .sha256 sidecar.
async with session.get(download_url + ".sha256") as resp:
    resp.raise_for_status()
    expected_sha256 = (await resp.text()).strip().split()[0]

actual_sha256 = hashlib.sha256(Path(tar_path).read_bytes()).hexdigest()
if actual_sha256 != expected_sha256:
    logger.error(
        "Checksum mismatch for {url}: expected {expected}, got {actual}",
        url=download_url, expected=expected_sha256, actual=actual_sha256,
    )
    _print("[red]Update aborted: checksum mismatch.[/red]")
    return UpdateResult.FAILED

2. (Stronger, optional follow-up) sign the manifest with a long-lived release key (cosign / minisign / sigstore). This protects against CDN compromise even if the attacker can replace both the tarball and its .sha256 sidecar.

3. Add filter="data" to the extractall call as a 1-line defense-in-depth + Python-3.14 future-proof fix.

Why I'm filing this publicly

There's no live exploit to disclose privately — this is a structural defense-in-depth gap, observable directly from the public source. Auto-updaters are a textbook supply-chain target, and a 30-line defensive change today saves you from a hard-to-detect incident later. Happy to send a PR if you'd like — I'd start with the sha256 sidecar since it doesn't require key management.

Contributor guide

Open the contributing guide

First steps

  1. Read the whole issue, then the project's contributing guide.
  2. Comment on the issue to say you are picking it up — it saves two people doing the same work.
  3. Fork the repository and make your change on a branch.
  4. Open a pull request that references the issue number.

Research direction

Start with src/kimi_cli/ui/shell/update.py and trace the download, extraction, chmod, and next-launch execution path. Compare its tar handling with src/kimi_cli/cli/plugin.py:77 and src/kimi_cli/vis/api/sessions.py:657, then inspect the release artifacts described in the issue. Done means the update verifies the published checksum before extraction and uses the safe tar extraction filter.

Written by the indexing model from the issue text.

Assessment

Tech stack
python
Domain
cli, security
Issue type
Bug
Difficulty
4/5
Estimated time
3-5 days
Activity status
Quiet
Clarity
Mostly clear
Newbie friendliness
48/100

Get new issues in your inbox

A short digest of beginner-friendly GitHub issues.