$npx -y skills add opendatahub-io/ai-helpers --skill python-packaging-binary-auditScan a Python package repository for compiled/binary files using Fromager-style detection and malcontent YARA analysis, then triage findings with deterministic rules and AI reasoning to produce a structured risk report section.
| 1 | # Python Packaging Binary Audit |
| 2 | |
| 3 | Scans a Python package repository for compiled or binary files using |
| 4 | Fromager-style extension and magic-header detection, then runs malcontent |
| 5 | YARA-based analysis on any detected binaries. Produces a self-contained |
| 6 | "Binary Scan" report section with triaged findings and a risk assessment. |
| 7 | |
| 8 | ## Inputs |
| 9 | |
| 10 | - **repo_path** (required): Local filesystem path to an already-cloned repository |
| 11 | - **output_file** (optional): Write the report section to this file path instead of |
| 12 | returning it inline. The first line of the file must be `RISK_RATING:<value>` so |
| 13 | the orchestrator can parse it without reading the full report. |
| 14 | - **binary_scan** (optional): File path to pre-computed binary scanner JSON output |
| 15 | (`binary-scan.json`). Schema: `{total, findings[{path, match_type, suffix, size, |
| 16 | magic?}]}`. When provided, skip running `scan_binaries.py`. |
| 17 | - **malcontent_results** (optional): File path to pre-computed malcontent JSON |
| 18 | output (`malcontent-results.json`). Schema: `{status, Files?}`. Status values: |
| 19 | `"success"`, `"unavailable"`, `"timeout"`, `"failed"`, `"invalid"`, `"skipped"`. |
| 20 | When provided, skip running `run_malcontent.py`. |
| 21 | |
| 22 | ## Step 1: Obtain Binary Scan Results |
| 23 | |
| 24 | ### Option A - Pre-computed results (CI mode) |
| 25 | |
| 26 | If `binary_scan` is provided and the file exists, read and parse it as JSON. |
| 27 | Use the `total` and `findings` fields directly. Skip running `scan_binaries.py` |
| 28 | and skip creating the staging directory (STAGING_DIR) since binaries are already |
| 29 | analyzed. Each finding has: `path` (relative to repo root), `match_type` |
| 30 | (`"extension"` or `"magic_header"`), `suffix`, `size`, and optionally `magic` |
| 31 | (ELF, MachO, ar_archive, etc.). |
| 32 | |
| 33 | If `total` is 0, skip to the Output section and note "No binary files detected" |
| 34 | in the report. |
| 35 | |
| 36 | ### Option B - Run binary scanner locally (standalone mode) |
| 37 | |
| 38 | If `binary_scan` is not provided, run the binary scanner to find compiled files |
| 39 | using Fromager-style extension and magic-header detection: |
| 40 | |
| 41 | ```bash |
| 42 | STAGING_DIR=$(mktemp -d -t malcontent-staging-XXXXXX) |
| 43 | ./scripts/scan_binaries.py --stage-to "$STAGING_DIR" "<repo-path>" |
| 44 | ``` |
| 45 | |
| 46 | This outputs JSON to stdout with `total` and `findings` fields. Each finding has: |
| 47 | `path`, `match_type` (extension or magic_header), `suffix`, `size`, and optionally |
| 48 | `magic` (ELF, MachO, ar_archive, etc.). |
| 49 | |
| 50 | The `--stage-to` flag copies detected binaries into a staging directory preserving |
| 51 | relative paths for malcontent analysis in the next step. |
| 52 | |
| 53 | If the scanner finds **zero** binaries, skip to the Output section and note |
| 54 | "No binary files detected" in the report. |
| 55 | |
| 56 | ## Step 2: Obtain Malcontent Results |
| 57 | |
| 58 | ### Option A - Pre-computed results (CI mode) |
| 59 | |
| 60 | If `malcontent_results` is provided and the file exists, read and parse it as JSON: |
| 61 | |
| 62 | - If `status` is `"success"`: use the `Files` dict as malcontent output (keyed by |
| 63 | file path, native malcontent `--format json` structure). Proceed to Step 3 |
| 64 | (Triage). |
| 65 | - If `status` is `"unavailable"`, `"timeout"`, `"failed"`, or `"invalid"`: |
| 66 | treat malcontent as unavailable. **Do not re-run malcontent.** The CI runner |
| 67 | already attempted execution under controlled conditions, and re-running in the |
| 68 | agent container would likely hit the same failure. Proceed to |
| 69 | triage using only the binary scan metadata (extension, magic header, size). |
| 70 | Note the degraded status and any `error` message from the JSON in the report. |
| 71 | - If `status` is `"skipped"`: no binaries were detected upstream. If |
| 72 | `binary_scan.total == 0`, this is consistent; skip to Output. If |
| 73 | `binary_scan.total > 0`, this is a data inconsistency from the CI pipeline. |
| 74 | treat malcontent as unavailable and triage using binary scan metadata only; |
| 75 | note the inconsistency in the report. |
| 76 | - For any other `status` value not listed above: treat malcontent as unavailable |
| 77 | and triage using binary scan metadata only, noting the unrecognized status. |
| 78 | |
| 79 | ### Mixed mode - binary_scan only |
| 80 | |
| 81 | If `malcontent_results` is NOT provided but `binary_scan` WAS provided, treat |
| 82 | malcontent as unavailable and triage using binary scan metadata only. |
| 83 | |
| 84 | ### Option B - Run malcontent locally (standalone mode) |
| 85 | |
| 86 | If neither `binary_scan` nor `malcontent_results` is provided, run malcontent |
| 87 | analysis on the staged binaries: |
| 88 | |
| 89 | ```bash |
| 90 | ./scripts/run_malcontent.py "$STAGING_DIR" |
| 91 | malcontent_exit=$? |
| 92 | ``` |
| 93 | |
| 94 | Check the exit code before proceeding: |
| 95 | |
| 96 | - **Exit 0**: malcontent ran successfully. Capture the JSON output with findings. |
| 97 | - **Exit 2**: malcontent (`mal`) is not installed. **Do not fail.** Proceed to |
| 98 | triage using only the binary scan metadata (extension, magic header, size). |
| 99 | Note "malcontent unavailable" in the report output. |
| 100 | - **Exit 1**: malcontent encountered a runtime error (tim |