Skip to content

Build1 publisher3 min readPublished

tokentab executes a fetched module in memory the moment Python imports its CLI

The repo advertised a token-cost dashboard that read agent session logs locally. According to a dev.to post, line 12 of its CLI called into code that pulled a module from a bare IP and exec'd it in RAM, and a later commit moved that payload inline.

The Engineer · Build desk

Illustration accompanying tokentab executes a fetched module in memory the moment Python imports its CLI

What happened

  • crwdla/tokentab hit GitHub Trending in mid-September 2026 at two weeks old with about 1.1k stars, describing itself as a token-cost dashboard that reads agent session logs with nothing leaving the machine.
  • Commit d9e8cb4, dated 2026-09-07, put a CONFIG block in tokentab/setup.py pointing at the bare IP 172.233.51.81 on port 8765, with the keys test123 and secret456 and a client module named manual_mapper.py.
  • Commit 3a7aac5 on 2026-09-19, labelled "add cursor support", deleted the readable version and replaced it with XOR-decoded imports of hmac, zlib and hashlib plus roughly 7 KB of integer arrays.
  • The author's five no-install checks, packaged as audit.sh and audit.py, report four findings on each of the two versions and zero on a benign repo of his own.

Compiled by The EngineerSomething wrong?How this is made

Why it matters

  • constraint Once stage two ships inline, an egress rule or a blocklist entry for that host sees nothing at install time, and the only artefact left to detect is the exec reached during import.
  • exposure The machines most likely to install a token-cost dashboard are the ones holding provider keys, a git token, a registry token and cloud credentials, and their session logs carry prompts, code and absolute paths.
  • decision A pre-install review that reads the import block and the top of setup.py passes both commits, so the routine has to include the last statement of every file in the package.
  • cost A team adopting the author's scripts inherits rules written after reading these two commits, and pays for any payload shaped differently in false negatives it will not see.

Python runs a module top to bottom the first time it is imported. So `from tokentab import setup` at line 12 of cli.py executes that module, and `setup.run_sync()` on the same line fires before argparse parses anything and before any output [7]. The behaviour does not depend on which flag or subcommand you pass. The file is at tokentab/setup.py, inside the package directory, so cli.py imports it as an ordinary module [4][7]. The dev.to post says the call therefore also runs during `pip install .` [8].

What run_sync reached in the 2026-09-07 commit refused to do anything unless `sys.platform` was `win32`, raising `RuntimeError("win32 only")` otherwise, then fetched bytes from `/api/v1/client/manual_mapper.py` on that host using the API key from CONFIG [6]. The loader built a `types.ModuleType`, set its `__file__` to `<ram:manual_mapper.py>`, inserted it into `sys.modules`, and ran `exec(compile(data, name, "exec"), module.__dict__)` [5]. Scanning the files the install wrote to disk would not find the payload, because it exists only as bytes in a module object [23].

Details in the code say a person typed this. The module it fetched was named manual_mapper.py, which has nothing to do with counting tokens [9]. The same file still contained a `Panel.fit(f"[bold cyan]text-humanizer[/bold cyan]")` line and a Deepseek REPL banner, left over from another project [10].

Twelve days later [20] the rewrite kept the behaviour and moved the trigger. The final statement in the file resolves `exec` through `getattr(__import__(<decoded "builtins">), <decoded "exec">)` and calls it on the unpacked blob at line 42, on import [12]. cli.py line 12 became `setup.run_sync(FORCE_SYNC=True)` [13]. Stage two no longer needs the network, because it ships inline and unpacks in memory [13].

The post puts the cost of the check that would have caught this at 20 seconds [19]. For its checker's findings to transfer to the next repo, that repo's payload would have to look like this one: a bare IPv4 literal in source, integer-array blobs, an exec reached at import. The post refers to five checks without listing them individually [22]. The tuning it does describe is the good part. An early version flagged `414.336.75.75` in a clean repo, which was SVG path data matching loosely as an IPv4, and the author added a per-octet range check so each part must be 255 or less [16]. "A checker that cries wolf on viewBox attributes gets ignored by the second week, which is strictly worse than not having one," he wrote [17].

That false positive is the part of this most teams get wrong, and it is worth more than the detection.

Four commits, one account, 1,145 stars in two weeks is about 82 stars a day [3][21]. The author wrote that the lesson cost him a credential rotation, and that the rule is to read the tail of the file rather than the head [14].

What to watch

  • Whether GitHub takes crwdla/tokentab down, and whether 172.233.51.81 stays reachable on port 8765.
  • Any independent account of what manual_mapper.py does on a Windows host once it is loaded.
  • Whether the five checks get published as readable rules, so their false-negative shape can be judged.
Loading claim ledger
Loading source directory links
Loading share composer
Loading topic controls
Loading related stories