From 4446aae59e48875da1afad051dcb09727c0855b0 Mon Sep 17 00:00:00 2001 From: Arjuna Keshavan <33526713+arjkesh@users.noreply.github.com> Date: Mon, 23 Feb 2026 10:28:54 -0800 Subject: [PATCH 1/3] Scan for CVEs in wheels, fix python versions --- .github/workflows/build-wheel.yml | 30 ++++++++++++++++++++++++++++++ .github/workflows/tilegym-ci.yml | 30 ++++++++++++++++++++++++++++-- setup.py | 8 ++++++-- 3 files changed, 64 insertions(+), 4 deletions(-) diff --git a/.github/workflows/build-wheel.yml b/.github/workflows/build-wheel.yml index a072305..47fbc2e 100644 --- a/.github/workflows/build-wheel.yml +++ b/.github/workflows/build-wheel.yml @@ -51,6 +51,11 @@ on: required: false type: string default: '' + run-pip-audit: + description: 'Run pip-audit to check for known CVEs in dependencies (disabled by default)' + required: false + type: boolean + default: false pre-build-script: description: 'Optional path to a script (relative to repo root) to run before building the wheel (e.g. copy deps). Run with: python .' required: false @@ -103,6 +108,31 @@ jobs: fi twine check dist/* + - name: Audit dependencies for CVEs + if: ${{ inputs.run-pip-audit }} + run: | + pip install pip-audit + # Extract installable deps from wheel metadata. + # Skips git+ deps and extras-only deps which can't be resolved in a plain environment. + python - <<'EOF' + import zipfile, glob + whl = glob.glob('dist/*.whl')[0] + with zipfile.ZipFile(whl) as z: + meta = next(n for n in z.namelist() if n.endswith('/METADATA')) + lines = z.read(meta).decode().splitlines() + deps = [ + l.split('Requires-Dist: ')[1].split(';')[0].strip() + for l in lines + if l.startswith('Requires-Dist:') + and 'extra ==' not in l + and '@' not in l.split('Requires-Dist: ')[1] + ] + with open('audit-requirements.txt', 'w') as f: + f.write('\n'.join(deps)) + print('Auditing deps:\n' + '\n'.join(deps)) + EOF + pip-audit -r audit-requirements.txt + - name: Test import if: ${{ !inputs.skip-import-test }} run: | diff --git a/.github/workflows/tilegym-ci.yml b/.github/workflows/tilegym-ci.yml index 4cd1931..26643e8 100644 --- a/.github/workflows/tilegym-ci.yml +++ b/.github/workflows/tilegym-ci.yml @@ -168,6 +168,7 @@ jobs: architectures: '["x86_64", "arm64"]' # Build for both architectures (6 wheels total) retention-days: 7 # All wheels kept for 7 days; only tested wheel gets -verified (30 days) skip-import-test: true # TileGym requires CUDA, test in Docker instead + run-pip-audit: true # Optional: Override default runners (ubuntu-latest for x86_64, ubuntu-24.04-arm for arm64) # runner-x86-64: [self-hosted, linux, x64, gpu] @@ -249,6 +250,29 @@ jobs: type=registry,ref=ghcr.io/${{ steps.vars.outputs.owner_lower }}/tilegym:latest cache-to: type=gha,mode=max + sanity-check: + name: sanity-check + needs: [config, build] + if: needs.config.outputs.build == 'true' + runs-on: ubuntu-latest + steps: + - name: Login to GHCR + uses: docker/login-action@v3 + with: + registry: ghcr.io + username: ${{ github.actor }} + password: ${{ secrets.GITHUB_TOKEN }} + + - name: Pull image + run: | + OWNER_LOWER=$(echo '${{ github.repository_owner }}' | tr '[:upper:]' '[:lower:]') + IMAGE="ghcr.io/${OWNER_LOWER}/${{ needs.config.outputs.image_name }}:${{ needs.config.outputs.image_tag }}" + docker pull ${IMAGE} + echo "IMAGE=${IMAGE}" >> $GITHUB_ENV + + - name: pip check + run: docker run --rm ${IMAGE} pip check + test-ops: name: test-ops needs: [config, build] @@ -537,10 +561,11 @@ jobs: publish-wheel: name: publish-verified-wheel - needs: [config, build-wheel, test-ops, test-benchmark] + needs: [config, build-wheel, sanity-check, test-ops, test-benchmark] if: | always() && needs.build-wheel.result == 'success' && + needs.sanity-check.result == 'success' && needs.test-ops.result == 'success' && needs.test-benchmark.result == 'success' # Note: Only marks the py310-x86_64 wheel as "verified" because that's the wheel @@ -555,11 +580,12 @@ jobs: promote-to-latest: name: promote-to-latest - needs: [config, build, test-ops, test-benchmark] + needs: [config, build, sanity-check, test-ops, test-benchmark] if: | always() && needs.config.outputs.is_pr == 'false' && needs.build.result == 'success' && + needs.sanity-check.result == 'success' && needs.test-ops.result == 'success' && needs.test-benchmark.result == 'success' runs-on: ubuntu-latest diff --git a/setup.py b/setup.py index 885dbce..8bdbb39 100644 --- a/setup.py +++ b/setup.py @@ -23,10 +23,12 @@ classifiers=[ "Development Status :: 4 - Beta", "Programming Language :: Python :: 3", - "Programming Language :: Python :: 3.9", + "Programming Language :: Python :: 3.10", + "Programming Language :: Python :: 3.11", + "Programming Language :: Python :: 3.12", "Operating System :: OS Independent", ], - python_requires=">=3.9", + python_requires=">=3.10", install_requires=[ # Note: torch and triton should be pre-installed in your environment "transformers==4.56.2", @@ -39,6 +41,8 @@ "numpy", "cuda-tile", "cuda-tile-experimental @ git+https://github.com/NVIDIA/cutile-python.git#subdirectory=experimental", + "filelock>=3.20.3", # CVE fix: GHSA-w853-jp5j-5j7f, GHSA-qmgc-5h2g-mvrw + "pillow>=12.1.1", # CVE fix: GHSA-cfh3-3jmp-rvhc # 'nvidia-ml-py', # optional ], extras_require={ From c1da9ba7c41c26c1d995b4f24bc6418ca5e2798c Mon Sep 17 00:00:00 2001 From: Arjuna Keshavan <33526713+arjkesh@users.noreply.github.com> Date: Mon, 23 Feb 2026 10:38:26 -0800 Subject: [PATCH 2/3] update config --- .github/infra_tests/test_parse_pr_config.py | 4 ++-- .github/pull_request_template.md | 2 +- .github/scripts/parse_pr_config.py | 5 +++-- .github/workflows/tilegym-ci.yml | 8 +++++++- setup.py | 2 +- 5 files changed, 14 insertions(+), 7 deletions(-) diff --git a/.github/infra_tests/test_parse_pr_config.py b/.github/infra_tests/test_parse_pr_config.py index a3fde20..de03d70 100644 --- a/.github/infra_tests/test_parse_pr_config.py +++ b/.github/infra_tests/test_parse_pr_config.py @@ -20,7 +20,7 @@ def test_get_default_config(self): """Test default configuration.""" config = parse_pr_config.get_default_config() assert config["build"] is True - assert config["test"] == ["ops", "benchmark"] + assert config["test"] == ["ops", "benchmark", "sanity"] def test_extract_yaml_from_pr_body(self): """Test YAML extraction from PR body.""" @@ -67,4 +67,4 @@ def test_resolve_config_empty_pr_body(self): """Test config resolution with empty PR body.""" config = parse_pr_config.resolve_config("") assert config["build"] is True # defaults - assert config["test"] == ["ops", "benchmark"] + assert config["test"] == ["ops", "benchmark", "sanity"] diff --git a/.github/pull_request_template.md b/.github/pull_request_template.md index 87a4d2b..7c6d0bc 100644 --- a/.github/pull_request_template.md +++ b/.github/pull_request_template.md @@ -10,7 +10,7 @@ ```yaml config: build: true - # valid options are "ops" and "benchmark" + # valid options are "ops", "benchmark", and "sanity" test: [] ``` diff --git a/.github/scripts/parse_pr_config.py b/.github/scripts/parse_pr_config.py index 54f0267..f8f3ddb 100644 --- a/.github/scripts/parse_pr_config.py +++ b/.github/scripts/parse_pr_config.py @@ -20,7 +20,7 @@ def get_default_config(): """Return default CI configuration.""" - return {"build": True, "test": ["ops", "benchmark"]} + return {"build": True, "test": ["ops", "benchmark", "sanity"]} def extract_yaml_from_pr_body(pr_body): @@ -77,6 +77,7 @@ def write_github_outputs(config): "build": str(config["build"]).lower(), "run_ops": str("ops" in test_list).lower(), "run_benchmark": str("benchmark" in test_list).lower(), + "run_sanity": str("sanity" in test_list).lower(), } # Write outputs @@ -85,7 +86,7 @@ def write_github_outputs(config): # Log final config print( - f"Config: build={outputs['build']}, run_ops={outputs['run_ops']}, run_benchmark={outputs['run_benchmark']}", + f"Config: build={outputs['build']}, run_ops={outputs['run_ops']}, run_benchmark={outputs['run_benchmark']}, run_sanity={outputs['run_sanity']}", file=sys.stderr, ) diff --git a/.github/workflows/tilegym-ci.yml b/.github/workflows/tilegym-ci.yml index 26643e8..5ba65f7 100644 --- a/.github/workflows/tilegym-ci.yml +++ b/.github/workflows/tilegym-ci.yml @@ -32,6 +32,7 @@ jobs: build: ${{ steps.parse.outputs.build }} run_ops: ${{ steps.parse.outputs.run_ops }} run_benchmark: ${{ steps.parse.outputs.run_benchmark }} + run_sanity: ${{ steps.parse.outputs.run_sanity }} image_tag: ${{ steps.parse.outputs.image_tag }} image_name: ${{ steps.parse.outputs.image_name }} is_pr: ${{ steps.context.outputs.is_pr }} @@ -144,11 +145,13 @@ jobs: echo "build=false" >> $GITHUB_OUTPUT echo "run_ops=false" >> $GITHUB_OUTPUT echo "run_benchmark=false" >> $GITHUB_OUTPUT + echo "run_sanity=false" >> $GITHUB_OUTPUT else echo "🔨 Building new image and running tests" echo "build=true" >> $GITHUB_OUTPUT echo "run_ops=true" >> $GITHUB_OUTPUT echo "run_benchmark=true" >> $GITHUB_OUTPUT + echo "run_sanity=true" >> $GITHUB_OUTPUT fi fi @@ -253,7 +256,10 @@ jobs: sanity-check: name: sanity-check needs: [config, build] - if: needs.config.outputs.build == 'true' + if: | + always() && + needs.config.outputs.run_sanity == 'true' && + (needs.build.result == 'success' || needs.build.result == 'skipped') runs-on: ubuntu-latest steps: - name: Login to GHCR diff --git a/setup.py b/setup.py index 8bdbb39..570ebfe 100644 --- a/setup.py +++ b/setup.py @@ -16,7 +16,7 @@ description="TileGym", long_description=README, long_description_content_type="text/markdown", - url="https://github.com/NVIDIA/tilegym", + url="https://github.com/NVIDIA/TileGym", packages=setuptools.find_packages(where="src"), package_dir={"": "src"}, license="MIT", From 6b95eb5606dd6e5dbfa3e537ad7f8ecf218b994f Mon Sep 17 00:00:00 2001 From: Arjuna Keshavan <33526713+arjkesh@users.noreply.github.com> Date: Mon, 23 Feb 2026 11:19:44 -0800 Subject: [PATCH 3/3] update pip check --- .github/workflows/tilegym-ci.yml | 28 ++++++++++++++-------------- 1 file changed, 14 insertions(+), 14 deletions(-) diff --git a/.github/workflows/tilegym-ci.yml b/.github/workflows/tilegym-ci.yml index 5ba65f7..9eb8cc3 100644 --- a/.github/workflows/tilegym-ci.yml +++ b/.github/workflows/tilegym-ci.yml @@ -255,29 +255,29 @@ jobs: sanity-check: name: sanity-check - needs: [config, build] + needs: [config, build-wheel] if: | always() && needs.config.outputs.run_sanity == 'true' && - (needs.build.result == 'success' || needs.build.result == 'skipped') + (needs.build-wheel.result == 'success' || needs.build-wheel.result == 'skipped') runs-on: ubuntu-latest steps: - - name: Login to GHCR - uses: docker/login-action@v3 + - name: Set up Python 3.10 + uses: actions/setup-python@v5 with: - registry: ghcr.io - username: ${{ github.actor }} - password: ${{ secrets.GITHUB_TOKEN }} + python-version: "3.10" - - name: Pull image - run: | - OWNER_LOWER=$(echo '${{ github.repository_owner }}' | tr '[:upper:]' '[:lower:]') - IMAGE="ghcr.io/${OWNER_LOWER}/${{ needs.config.outputs.image_name }}:${{ needs.config.outputs.image_tag }}" - docker pull ${IMAGE} - echo "IMAGE=${IMAGE}" >> $GITHUB_ENV + - name: Download wheel (Python 3.10, x86_64) + uses: actions/download-artifact@v4 + with: + name: ${{ needs.build-wheel.outputs.artifact-name }}-py310-x86_64 + path: ./wheel - name: pip check - run: docker run --rm ${IMAGE} pip check + run: | + python -m venv /tmp/sanity-env + /tmp/sanity-env/bin/pip install --quiet ./wheel/*.whl + /tmp/sanity-env/bin/pip check test-ops: name: test-ops