Skip to content

build_store_data ships __pycache__ into app zips: the glob has no filter #63

Description

@ClaydeCode

Summary

build_store_data.py zips every file under an app folder with no filter, so anything sitting in the working tree at build time is shipped inside <app>.zip — including __pycache__/*.pyc left behind by the update checker.

The code

for p in app_path.glob('**/*'):
    if p.is_dir():
        continue
    if p.name == zip_file.name:
        continue
    z.write(p, p.relative_to(app_path))

Two exclusions: directories, and the zip being written. Everything else is included.

Reproduction

update/update.py loads each app's checker via importlib (spec_from_file_locationexec_module, lines 30–32), which writes apps/<app>/__pycache__/update_check.cpython-*.pyc. This checkout currently has 40 such directories.

Running the documented update sequence in one working tree — update.py check, then update.py apply, then build_store_data — therefore produces contaminated zips. Demonstrated against the current tree:

files build_store_data would put in paperless-ngx.zip:
    __pycache__/update_check.cpython-313.pyc     <-- junk
    app_meta.json
    docker-compose.yml.template
    icon.svg
    update_check.py

junk entries: 1 of 5

One in five files in that app's zip is a stale bytecode artifact.

Why it matters, and why it is not urgent

The zips are what actually ship: deploy.yml uploads apps/ to blob storage, and shards install by fetching <app>/<app>.zip. So a contaminated zip reaches real installs.

That said, the deploy and preview CI jobs check out fresh and run only build_store_data.py, so they are clean today. The exposure is local builds and any future job that runs the update checker and the zip builder in the same workspace — which is exactly the sequence the update pipeline documents. So this is currently latent rather than actively broken, and worth fixing before something reorders those steps.

Nothing here is a security problem: a .pyc of a file already in the zip leaks nothing. It is correctness and hygiene — installs carry bytecode compiled for whatever Python happened to be on the builder's machine (cpython-313 here), which is neither used nor meaningful on the shard.

Suggested fix

Filter the glob. Something like an explicit deny for __pycache__, *.pyc, .DS_Store and dotfiles, or better, an allow-list of what an app zip is actually supposed to contain — app_meta.json, docker-compose.yml.template, the icon, update_check.py and any template assets. An allow-list also makes the zip's contents a deliberate decision rather than whatever the directory happens to hold.

A check with teeth

A test that asserts a built zip contains only expected entries would fail against today's tree, because the __pycache__ dirs are already there. A test that merely asserts the expected files are present would pass with the bug intact, so it must assert the absence of everything else.


Found independently by two agents while migrating mosquitto and filebrowser off the private registry (PRs #61 and #62); neither fixed it, since it affects every app rather than theirs. Verified separately against the current tree before filing.

Activity

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Type

    No type

    Projects

    No projects

      Milestone

      No milestone

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions