diff --git a/.github/workflows/marketplace.yml b/.github/workflows/marketplace.yml index 3cc7602..b5cbd37 100644 --- a/.github/workflows/marketplace.yml +++ b/.github/workflows/marketplace.yml @@ -22,8 +22,8 @@ jobs: python-version: '3.12' - name: Check marketplace contracts run: python scripts/check-marketplace.py - - name: Validate complete plugin packaging - run: python scripts/test-plugin-packaging.py + - name: Validate runtime packaging + run: python scripts/test-runtime-packaging.py - name: Select component validation id: plan run: python scripts/ci-plan.py @@ -38,12 +38,16 @@ jobs: dotnet-version: '10.0.401' cache: true cache-dependency-path: src/HyperVControl/packages.lock.json - - name: Build self-contained EXE and plugin + - name: Build self-contained runtime shell: pwsh run: ./scripts/build-hyperv-control.ps1 - name: Verify pinned runtime reproducibility shell: pwsh - run: git diff --exit-code -- plugins/hyper-v-control/runtime.json + run: | + git diff --exit-code -- plugins/hyper-v-control/runtime.json + if ($LASTEXITCODE -ne 0) { exit $LASTEXITCODE } + python scripts/test-runtime-packaging.py --hyperv-release build/hyperv-control/runtime-release + exit $LASTEXITCODE - name: Unit and stdio protocol validation shell: pwsh run: | @@ -52,6 +56,7 @@ jobs: dotnet run --project tests/HyperVControl.Tests -c Release -- smoke $PWD if ($LASTEXITCODE -ne 0) { exit $LASTEXITCODE } ./scripts/test-release-isolation.ps1 + ./scripts/test-hyperv-launcher.ps1 exit $LASTEXITCODE - uses: actions/upload-artifact@v4 if: github.event_name == 'workflow_dispatch' @@ -59,7 +64,7 @@ jobs: name: hyper-v-control-win-x64 if-no-files-found: error path: | - build/hyperv-control/release/hyper-v-control-win-x64-v*.exe - build/hyperv-control/release/hyper-v-control-plugin-v*-win-x64.zip - build/hyperv-control/release/hyper-v-control-v*-release.json - build/hyperv-control/release/hyper-v-control-v*-SHA256SUMS.txt + build/hyperv-control/runtime-release/hyper-v-control-win-x64-v*.exe + build/hyperv-control/runtime-release/hyper-v-control-v*-NOTICES.txt + build/hyperv-control/runtime-release/hyper-v-control-v*-release.json + build/hyperv-control/runtime-release/hyper-v-control-v*-SHA256SUMS.txt diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md index b9219ce..f8d6c5b 100644 --- a/CONTRIBUTING.md +++ b/CONTRIBUTING.md @@ -1,6 +1,6 @@ # Maintaining and extending the marketplace -Each plugin is an independent product. Keep its installed files self-contained, its dependencies explicit, and its releases separate. Shared code belongs at the repository level only when multiple plugins actually use it. +Each plugin is an independent product installed through the marketplace. Keep its installed files self-contained and its runtime dependencies explicit. GitHub Releases carry native runtimes; do not publish separate plugin ZIPs or skill downloads. Shared code belongs at the repository level only when multiple plugins actually use it. ## Repository map @@ -11,7 +11,7 @@ Each plugin is an independent product. Keep its installed files self-contained, | `src//` | Native application or MCP implementation; currently Hyper-V Control | | `tests/.Tests/` | Native protocol and behavior validation | | `scripts/` | Maintainer builds, packaging, and repository checks; never an installed plugin dependency | -| `docs/` | Build/release procedures and recorded validation evidence | +| `docs/` | [Documentation index](docs/README.md): guides, references, and validation records grouped by component and run | | `sources/`, `patches/`, `bundle.lock.json` | Pinned Rizin upstream sources, compatibility patches, and build dependencies | | `build/` | Ignored generated files, downloads, binaries, and local experiment output | @@ -23,12 +23,12 @@ Within Hyper-V Control, `HyperVTools*` exposes the MCP contract, `Services/` own - Edit `plugins//plugin.json` for plugin identity, version, and interface metadata. Run `python scripts/check-marketplace.py --sync` to generate `.codex-plugin/plugin.json`; do not maintain the compatibility copy independently. - Compiled downloads use the plugin's `runtime.json` for the exact artifact and hash. Rizin's independently versioned runtime and build inputs are in `bundle.lock.json`. Keep dependency versions in metadata and lockfiles. -- Hyper-V's project, CLI, and protocol versions must match its plugin manifest. The repository checker rejects partial updates; the build verifies the executable version and runtime hash. -- README files explain purpose, setup, and usage. Keep current version numbers, release dates, and changelogs out of them. The [download catalog](docs/releases.md#release-index) is the maintained entrypoint for current releases. +- Hyper-V's project, CLI, and protocol versions must match its runtime pin. The plugin version can advance independently for launcher or skill updates. The repository checker rejects inconsistent runtime updates; the build verifies the executable version and hash. +- README files explain purpose, setup, and usage. Keep current version numbers, release dates, and changelogs out of them. The [download catalog](docs/guides/releases.md#release-index) is the maintained entrypoint for current releases. - Skills explain how an agent should choose tools and interpret results. Put detailed task procedures in their `references/` directory and scripts needed at runtime inside the plugin. - Record experiments in validation reports. Imported Frida material under `references/legacy/` and `docs/migration/` is historical evidence, not the maintained entrypoint; preserve its provenance and licenses. -Links inside distributed plugin documentation must resolve inside that plugin or use an absolute repository/web URL. A relative link to `../../docs/` works in a checkout but breaks when the plugin ZIP is extracted. +Links inside distributed plugin documentation must resolve inside that plugin or use an absolute repository/web URL. A relative link to `../../docs/` works in a checkout but breaks in the installed plugin cache. ## Add a plugin @@ -36,19 +36,19 @@ Links inside distributed plugin documentation must resolve inside that plugin or 2. Add a local catalog entry pointing to `./plugins/`, then run the manifest sync/check command. New catalog plugins are discovered by the checker automatically. 3. If it needs a native executable, put its source and tests under `src/` and `tests/`. Give it a dedicated builder, verified installer, and versioned installation directory. A source-only skill/CLI plugin does not need an MCP server or binary release. 4. Add component-specific validation to the existing PR workflow. Keep expensive job selection in `scripts/ci-plan.py`; extend it for the component's actual inputs. Do not introduce a second push or tag workflow for the same checks. -5. Test an extracted package in a fresh directory. It must work without the repository checkout, another plugin, or a developer's absolute paths. Follow the [release guide](docs/releases.md) for publication and asset naming. +5. Test the marketplace-installed plugin from a fresh directory. It must work without the repository checkout, another plugin, or a developer's absolute paths. Publish runtime assets only when the plugin requires them; follow the [release guide](docs/guides/releases.md). ## Check a change ```powershell python scripts/check-marketplace.py -python scripts/test-plugin-packaging.py +python scripts/test-runtime-packaging.py ``` -These fast checks validate the catalog, compatibility manifests, bundled skill paths, runtime pins, README policy, entrypoint links, and Rizin plugin packaging. They do not install plugin dependencies or operate a VM. +These fast checks validate the catalog, compatibility manifests, bundled skill paths, runtime pins, documentation layout/links, README policy, and runtime packaging. They do not install plugin dependencies or operate a VM. The single PR workflow runs these checks for all changes. It runs Windows build/protocol/installer checks only when their inputs change. Pure documentation changes avoid native builds, newer commits cancel obsolete runs, and merging or tagging does not repeat CI. Use a manual workflow run when downloadable build artifacts are needed. -For behavior changes, also run the affected component's documented tests: [Hyper-V Control](plugins/hyper-v-control/README.md#development-and-validation), [Rizin](docs/build.md), [Frida](scripts/frida-use-experiments/README.md), or [Capstone](plugins/capstone-binary-patching/README.md#validation-and-boundaries). Live tests must have a defined target, restoration plan, and recorded limits; CI never discovers and operates on a developer's VMs automatically. +For behavior changes, also run the affected component's documented tests: [Hyper-V Control](plugins/hyper-v-control/README.md#development-and-validation), [Rizin](docs/guides/rizin-build.md), [Frida](scripts/frida-use-experiments/README.md), or [Capstone](plugins/capstone-binary-patching/README.md#validation-and-boundaries). Live tests must have a defined target, restoration plan, and recorded limits; CI never discovers and operates on a developer's VMs automatically. Before removing a release, check both runtime installers and build lockfiles for references. Preserve active dependencies first. Published primary EXEs and ZIPs are immutable; publish changed bytes under a new version. Documentation-only edits do not require rebuilding an unchanged runtime. diff --git a/README.md b/README.md index a453aaa..9c81984 100644 --- a/README.md +++ b/README.md @@ -2,40 +2,39 @@ **Windows reverse engineering and automation for AI agents.** -A collection of focused plugins for understanding software, investigating live behavior, making verifiable changes, and running repeatable experiments. Each plugin combines specialist tools with guidance that helps your agent choose a useful workflow and interpret the results. +Understand unfamiliar software, investigate live behavior, make verifiable changes, and run repeatable experiments. Each plugin pairs specialist tools with practical workflows for your agent. -## The plugins +## Plugins -### [Rizin RE Toolkit](plugins/rizin-re-toolkit) +| Plugin | What it helps you do | +| --- | --- | +| [Rizin RE Toolkit](plugins/rizin-re-toolkit) | Understand executables and memory dumps through disassembly, multiple decompilers, signatures, and pattern matching. | +| [Frida Use](plugins/frida-use) | Observe running programs, trace API calls, inspect runtime values, and test reversible behavior changes. | +| [Capstone Binary Patching](plugins/capstone-binary-patching) | Compare instructions, create precise binary patches, verify the result, and roll changes back. | +| [Hyper-V Control](plugins/hyper-v-control) | Manage VMs and checkpoints, interact with guest desktops, transfer files, and debug applications or the Windows kernel. | -Explore native executables and memory dumps without starting from scratch. Disassembly, multiple decompilers, signatures, and pattern matching help you understand unfamiliar code and cross-check what you find. A good starting point for binary triage, program analysis, and investigating captured memory. +Use them independently or together: understand a program, observe it running, verify a change, and repeat the experiment from a known VM state. -### [Frida Use](plugins/frida-use) +## Install -See what a program actually does while it runs. Trace API calls, inspect arguments and return values, and try reversible changes to live behavior. Use it to investigate a suspicious call, follow a runtime-only code path, or test a hypothesis from static analysis. - -### [Capstone Binary Patching](plugins/capstone-binary-patching) - -Turn a proposed binary change into a precise, verifiable patch. Compare bytes and instructions, construct patches, check the result, and roll changes back. Useful when you need to explain exactly what changed, port a patch between versions, or preserve the original file for comparison. - -### [Hyper-V Control](plugins/hyper-v-control) +```powershell +codex plugin marketplace add JerryLinLinLin/jerry-plugin-marketplace --ref main +``` -Give your agent control of a Windows VM lab: manage VM lifecycles and checkpoints, interact with guest desktops, transfer files, and debug applications or the Windows kernel. Checkpoints make experiments repeatable, while desktop interaction and debugging let you investigate the same VM from several angles. +Open **Jerry's Plugin Marketplace** in Codex and install the plugins you need. Follow each plugin's setup guide, then describe your task to the agent. -Use any plugin on its own, or combine them: understand a binary with Rizin, inspect its behavior with Frida, verify a patch with Capstone, and use Hyper-V to repeat the experiment from a known state. +## Releases -## Get started +[Releases](https://github.com/JerryLinLinLin/jerry-plugin-marketplace/releases) -```powershell -codex plugin marketplace add JerryLinLinLin/jerry-plugin-marketplace --ref main -``` +## Contributing -In Codex, open **Jerry's Plugin Marketplace** and install the plugins you need. Each plugin includes its skill. Follow the linked setup guides above for runtime requirements, then describe the task to your agent. +[Contribution guide](CONTRIBUTING.md) -## Downloads +## Release guidelines -The [download catalog](docs/releases.md#release-index) lists prebuilt Windows packages: a standalone EXE or complete plugin for Hyper-V Control, and a complete plugin plus portable runtime for Rizin RE Toolkit. +[Release guide](docs/guides/releases.md) -Frida Use and Capstone Binary Patching are available through the marketplace. Install only what you need; plugins have independent setup and releases. +## License -[Contributing](CONTRIBUTING.md) · [Release guide](docs/releases.md) · [MIT license](LICENSE). Bundled third-party components retain their own licenses. +[MIT](LICENSE) diff --git a/docs/README.md b/docs/README.md new file mode 100644 index 0000000..b417a40 --- /dev/null +++ b/docs/README.md @@ -0,0 +1,34 @@ +# Documentation + +## Guides + +- [Contributing and repository layout](../CONTRIBUTING.md) +- [Runtime releases and distribution](guides/releases.md) +- [Build the Rizin runtime](guides/rizin-build.md) +- [Use the portable Rizin runtime](guides/rizin-runtime.md) + +Plugin setup and agent workflows belong to each plugin's own directory; start from the [plugin catalog](../README.md#plugins). + +## Reference + +- [Rizin third-party components and licenses](reference/rizin-third-party.md) +- [Hyper-V feature inventory](../plugins/hyper-v-control/docs/extraction.md) + +## Validation records + +These are dated or versioned records of what was tested, including environment details and known limits. They are historical evidence, not installation guides. + +| Component | Record | +| --- | --- | +| Rizin | [Runtime validation](validation/rizin/runtime-v0.3.1/report.md) | +| Capstone Binary Patching | [Native patching experiments](validation/capstone-binary-patching/plugin-v0.1.0/report.md) | +| Frida Use | [Windows instrumentation experiments and evidence](validation/frida-use/2026-10-01/report.md) | +| Hyper-V Control | [VM, console, and debugger validation](validation/hyper-v-control/runtime-v0.1.0/report.md) | + +## Where new documents belong + +- `guides/`: maintained task procedures. +- `reference/`: component facts, formats, and notices. +- `validation///`: one report and its retained evidence for each run. + +Keep this directory root limited to this index. Link to existing plugin documentation rather than duplicating it. Add new validation runs alongside previous records; update links when moving documents, and keep generated binaries and large captures under ignored `build/`. diff --git a/docs/guides/releases.md b/docs/guides/releases.md new file mode 100644 index 0000000..31fcc0e --- /dev/null +++ b/docs/guides/releases.md @@ -0,0 +1,51 @@ +# Runtime releases + +Install plugins through **Jerry's Plugin Marketplace** in Codex. The marketplace distributes their manifests, skills, launchers, and setup helpers. GitHub Releases distribute native runtimes and their supporting notices, checksums, and build metadata. Do not publish plugin ZIPs or standalone skill files. + +This repository uses the [official marketplace installation workflow](https://developers.openai.com/plugins/build/plugins#add-a-marketplace-from-the-cli). + +## Release index + +| Runtime | Release | Download | +| --- | --- | --- | +| Hyper-V Control | [Windows x64](https://github.com/JerryLinLinLin/jerry-plugin-marketplace/releases/tag/hyper-v-control-v0.1.0) | Self-contained MCP executable | +| Rizin | [Windows x64](https://github.com/JerryLinLinLin/jerry-plugin-marketplace/releases/tag/rizin-v0.3.1) | Portable runtime; RetDec build SDK is a separate maintainer asset | + +Frida and Capstone dependencies are installed by their plugin setup workflows. They do not need a binary release in this repository. + +## Versions and installation + +Plugin versions live in `plugins//plugin.json`. Native runtime versions are independent: Hyper-V records its executable pin in `runtime.json`, and Rizin records its runtime/build inputs in `bundle.lock.json`. A launcher or skill update can ship through the marketplace without rebuilding an unchanged executable. + +Hyper-V's launcher downloads and verifies a missing runtime before starting the MCP. Rizin's skill invokes its verified runtime installer when needed. Each component uses its own versioned installation directory. Never use the repository-wide Latest release to resolve a runtime: pin an exact asset, or filter and paginate within the intended runtime stream. + +## Asset naming + +| Item | Pattern | +| --- | --- | +| Runtime tag | `-v` | +| Executable | `--v.exe` | +| Runtime archive | `-runtime--v.zip` | +| Notices | `-v-NOTICES.txt` | +| Asset inventory | `-v-release.json` | +| Checksums | `-v-SHA256SUMS.txt` | + +Use platform identifiers such as `win-x64`. Keep established Rizin runtime filenames and `SHA256SUMS` compatible with its installer. Runtime archives contain executable dependencies and support data; marketplace content stays under `plugins/`. + +## Build and publish + +1. Build and test the runtime. Hyper-V uses `scripts/build-hyperv-control.ps1`; its SDK and framework are pinned in the CI workflow and project. Rizin uses the [build guide](rizin-build.md). +2. Commit matching runtime pins and metadata. Run the repository checks and affected component tests, then merge the PR. +3. Tag the merged commit. Publish only the builder's runtime assets, notices, inventory, and checksums. Inspect explicit filenames; do not upload an entire shared build directory. +4. Download the published files, verify their hashes, and exercise the installer in a fresh directory. +5. Update the release index above. Keep release notes to a short introduction, user-facing changes, and download/setup links. + +Hyper-V outputs four files in `build/hyperv-control/runtime-release`: the EXE, notices, inventory, and checksum file. Rizin outputs its runtime ZIP, manifest, checksums, and any separately produced verification report. Packaging rejects unrelated files in the output directory. + +Publish changed runtime bytes under a new version. Preserve current runtime URLs and hashes during metadata corrections. Before retiring an old release, check both installers and build lockfiles and migrate any active dependency. Keep source tags and historical validation records. + +## CI + +One workflow runs for PRs targeting `main`. Fast marketplace/runtime-packaging checks run for every PR; `scripts/ci-plan.py` selects expensive Windows validation when its inputs change. Updated PRs cancel obsolete runs. Pushes, merge commits, and tags do not repeat validation. + +Manual workflow runs can upload runtime artifacts. They do not publish a GitHub Release automatically. See [CONTRIBUTING](../../CONTRIBUTING.md) for component boundaries and local checks. diff --git a/docs/build.md b/docs/guides/rizin-build.md similarity index 73% rename from docs/build.md rename to docs/guides/rizin-build.md index 44420ae..78d39b3 100644 --- a/docs/build.md +++ b/docs/guides/rizin-build.md @@ -38,20 +38,14 @@ After packaging, extract the archive into a new path with spaces and Unicode and ## Packaging ```powershell -powershell -NoProfile -ExecutionPolicy Bypass -File scripts\package-bundle.ps1 -Version 0.3.1 +powershell -NoProfile -ExecutionPolicy Bypass -File scripts\package-bundle.ps1 ``` -This creates a runtime ZIP, complete plugin ZIP, runtime manifest, and SHA256SUMS. The command reproduces the currently locked runtime version. For a new CLI release, increment the bundle version in the lock file and tag the tested source commit with a new `rizin-v` tag. Other marketplace plugins can use their own tag prefixes. +This creates only the runtime ZIP, runtime manifest, and SHA256SUMS, plus a verification report when one has been produced separately. Use a clean output directory. The archive excludes marketplace manifests and skills. -Plugin releases are independent of the CLI bundle version. The runtime ZIP uses `bundle.lock.json` and the requested runtime version; the complete plugin ZIP uses the version in `plugins/rizin-re-toolkit/plugin.json`. Skills are included inside the plugin ZIP, with no separate skill archive. +The runtime version comes from `bundle.lock.json`. For a new runtime release, increment it and tag the tested source commit with `rizin-v`. Published runtime archives are immutable. -For a skill-only or metadata update, package without rebuilding or republishing the runtime: - -```powershell -powershell -NoProfile -ExecutionPolicy Bypass -File scripts\package-bundle.ps1 -PluginOnly -Version 0.4.1 -OutputDir build\plugin-release-0.4.1 -``` - -This reproduces the current plugin version locally; do not overwrite its published ZIP. For a new release, bump the plugin version and publish under the matching `rizin-re-toolkit-v` tag. The CLI installer searches release assets for the runtime prefix, so a newer plugin-only release is not mistaken for a runtime download. The native runtime remains at `rizin-v0.3.1` until a separate CLI release is built and tested. Earlier validation reports retain their original plugin IDs and versions as historical evidence. +Plugin and skill changes ship through the marketplace source under `plugins/rizin-re-toolkit/`; they do not create a separate plugin ZIP or GitHub Release. Earlier validation reports retain their original IDs and versions as historical evidence. Validate discovery without changing user configuration: diff --git a/docs/guides/rizin-runtime.md b/docs/guides/rizin-runtime.md new file mode 100644 index 0000000..de1e7a2 --- /dev/null +++ b/docs/guides/rizin-runtime.md @@ -0,0 +1,14 @@ +# Portable Rizin runtime + +Extract the complete `rizin` folder and keep its contents together. On Windows x64, run the CLI by absolute path: + +```powershell +& 'C:\Tools\rizin\bin\rizin.exe' -v +& 'C:\Tools\rizin\bin\rizin.exe' -q -N -e scr.color=0 -c iI sample.exe +``` + +Runtime libraries and analysis support data are included. No administrator installation or system PATH change is required. + +For agent workflows, install **Rizin RE Toolkit** through [Jerry's Plugin Marketplace](https://github.com/JerryLinLinLin/jerry-plugin-marketplace#install). The plugin and its skill are distributed through the marketplace. + +`bundle-manifest.json` records component versions, source revisions, and build inputs. See `THIRD-PARTY.md` and `licenses/` for component licenses and notices. diff --git a/docs/third-party.md b/docs/reference/rizin-third-party.md similarity index 100% rename from docs/third-party.md rename to docs/reference/rizin-third-party.md diff --git a/docs/releases.md b/docs/releases.md deleted file mode 100644 index 247f5e6..0000000 --- a/docs/releases.md +++ /dev/null @@ -1,87 +0,0 @@ -# Releases and binary distribution - -This repository is a marketplace of independently versioned plugins. A GitHub Release belongs to one plugin or one explicitly separate runtime stream; it is not a version of the whole marketplace. GitHub assets are a flat list, so filenames carry their own identity. - -## Release index - -| Stream | Version and tag | Downloads | Installation | -| --- | --- | --- | --- | -| Hyper-V Control | [0.1.0](https://github.com/JerryLinLinLin/jerry-plugin-marketplace/releases/tag/hyper-v-control-v0.1.0) | C# EXE and complete Windows x64 plugin ZIP | `%LOCALAPPDATA%\Programs\HyperVControl\0.1.0` or the ZIP's `bin` directory | -| Rizin RE Toolkit | [0.4.1](https://github.com/JerryLinLinLin/jerry-plugin-marketplace/releases/tag/rizin-re-toolkit-v0.4.1) | Complete plugin ZIP, including its skill | Codex plugin directory | -| Rizin portable runtime | [0.3.1](https://github.com/JerryLinLinLin/jerry-plugin-marketplace/releases/tag/rizin-v0.3.1) | Rizin 0.9.1 Windows x64 runtime bundle | `%LOCALAPPDATA%\Programs\Rizin\rizin-v0.3.1` | -| Capstone Binary Patching | Plugin `0.1.0`, marketplace source | Skill/scripts; dependencies installed by uv | Its uv-managed environment; no repository binary release required | -| Frida Use | Plugin `1.0.0`, marketplace source | Skill/scripts/references; Frida installed separately | The selected analysis environment; no repository binary release required | - -The marketplace's source of truth for plugin identity/version remains `plugins//plugin.json`. A compiled plugin additionally owns its runtime pin, installer, launcher and release builder. Do not couple unrelated plugin versions. - -## Naming rules for new releases - -| Item | Pattern | Hyper-V example | -| --- | --- | --- | -| Git tag | `-v` | `hyper-v-control-v0.1.0` | -| Release title | ` ` | `Hyper-V Control 0.1.0` | -| Standalone executable | `--v.exe` | `hyper-v-control-win-x64-v0.1.0.exe` | -| Complete plugin ZIP | `-plugin-v[-].zip` | `hyper-v-control-plugin-v0.1.0-win-x64.zip` | -| Multi-file runtime ZIP, when needed | `-runtime--v.zip` | Use when an EXE requires adjacent runtime files | -| Checksums | `-v-SHA256SUMS.txt` | `hyper-v-control-v0.1.0-SHA256SUMS.txt` | -| Asset inventory | `-v-release.json` | `hyper-v-control-v0.1.0-release.json` | - -Use .NET-style runtime identifiers such as `win-x64`, `win-arm64`, `linux-x64`, and `osx-arm64`. A filename should stay unambiguous when copied into a shared Downloads directory. An installed executable may have a stable local name such as `HyperVControl.exe`; the installer maps the namespaced download to that name inside the plugin's own versioned directory. - -Skills belong inside the complete plugin ZIP and are installed with the plugin. Do not publish a redundant standalone `.skill` download. - -For a genuinely independent runtime version, use a separate `-runtime-v` stream and pin the relationship from the consuming plugin. A plugin may update documentation without rebuilding a large, unchanged runtime. Existing Rizin runtime tags (`rizin-v0.3.1`) and asset names are legacy compatibility contracts and are not renamed. - -## Download and upgrade isolation - -- Never use repository-wide `/releases/latest` or `/releases/latest/download/...` from an installer. The newest release may belong to a different plugin. -- Prefer an exact release tag, platform, asset filename and SHA-256 pin in the consuming plugin. Hyper-V's `runtime.json` contains all four; its installer rejects a mismatched plugin, tag, platform or asset URL before downloading. -- When latest-in-stream discovery is necessary, filter both tag family and asset name, skip drafts/prereleases, paginate until the stream is found, and verify the selected asset checksum. Rizin's installer follows this path and retains its legacy `SHA256SUMS` lookup. -- Keep install directories separate and versioned. Reuse a matching verified installation; do not overwrite a different checksum in place or alter a global PATH implicitly. Rizin's user PATH change remains opt-in; Hyper-V uses an absolute executable path. -- Keep published runtime binaries and plugin ZIPs immutable. Fix their contents with a new patch version. Release notes and inventories may be corrected, and redundant ancillary downloads may be removed with their checksum entries; these editorial changes must preserve the executable/ZIP hashes and existing runtime URLs. -- Publish independent plugin releases with `--latest=false`. The global GitHub Latest badge is not a plugin index and may change when obsolete releases are removed. The table above is the entrypoint for choosing a component. - -## Release retention - -Keep the current release for each component and any runtime release that an active plugin still requires. Remove superseded releases and their assets; keep Git tags and source history. Compare versions within their own stream: Rizin RE Toolkit 0.4.1 still uses the Rizin 0.3.1 runtime, so both remain current. The retired `v0.1.0`, `rizin-v0.3.0`, and `rizin-re-toolkit-v0.4.0` releases are no longer part of the download catalog. - -The retained Rizin runtime release contains only its runtime, build SDK, and integrity/provenance files. Its obsolete plugin and standalone skill downloads have been removed. The RetDec SDK is a separate maintainer asset pinned in `bundle.lock.json`; its original files and licenses are preserved, allowing the v0.1.0 release to be retired without breaking clean builds. - -## Release notes - -Use a short product/version title, a one-sentence introduction, and at most four bullets describing changes that matter to users. Link directly to the primary downloads and setup guide. Keep implementation details, asset inventories, installation paths, and test logs in the documentation or attached metadata. - -Keep repository and plugin READMEs evergreen: describe purpose, setup, and usage without repeating current versions, release dates, revision tables, or versioned download URLs. Link to the download catalog. Plugin versions and dependency pins belong in manifests/lockfiles; version-specific changes and measured results belong in release notes and validation reports. - -## CI without duplicate runs - -Automatic validation runs only for pull requests targeting `main`. The fast catalog/packaging checks cover every plugin; `scripts/ci-plan.py` selects Windows validation only when its implementation, installer, or build inputs change. Branch pushes, merge commits, and tags do not launch another copy. Markdown-only changes skip the native build. Updating a PR cancels its obsolete run, and NuGet packages are cached between runs. - -PR validation builds the executable and plugin, checks the pinned executable hash, and runs protocol, installer-isolation, and packaging tests. Large build artifacts are uploaded only for an explicit `workflow_dispatch` run, which publishes no GitHub Release by itself. Use that manual action when a downloadable build is needed; ordinary PRs do not need a second run after merging. - -See [the maintenance guide](../CONTRIBUTING.md) for repository ownership, adding plugins, and local checks. - -## Packaging and publication - -1. Build/test the component and its installer in an isolated directory. For Hyper-V, use .NET SDK `10.0.401` and `scripts/build-hyperv-control.ps1`. The runtime patch is pinned to `10.0.12`; no compiler or .NET installation is needed by end users. -2. Commit the runtime pin together with the implementation and packaging. CI verifies that rebuilding the EXE does not change the committed pin. Source paths and Git revision metadata are excluded from the binary identity; GitHub's release tag records the source commit. This avoids a circular dependency between a commit ID and the binary checksum committed within it. -3. Open and validate the PR, merge it, and tag the exact merged commit. Include only the component's assets in its release. Do not upload all files in a shared `build` directory. -4. Prepare a draft release with explicit tag/title/target, upload the named EXE/ZIP, asset inventory and checksum file, then publish it without moving the repository-wide Latest badge. -5. Download the published assets, verify their hashes against the committed pin and inventory, and exercise the real installer in a temporary directory before reporting completion. - -For Hyper-V, upload these four files from `build/hyperv-control/release`: - -```text -hyper-v-control-win-x64-v0.1.0.exe -hyper-v-control-plugin-v0.1.0-win-x64.zip -hyper-v-control-v0.1.0-release.json -hyper-v-control-v0.1.0-SHA256SUMS.txt -``` - -The ZIP contains the EXE, manifests, skill, documentation, launcher, installer, and licenses. The EXE-only download is for direct stdio MCP configuration. Local convenience aliases (`HyperVControl.exe`, `SHA256SUMS`) are not additional public Hyper-V assets. The inventory records primary asset names, roles, platforms, sizes and hashes; the checksum file also covers the inventory and does not hash itself. - -Rizin RE Toolkit 0.4.1 is an installer-only maintenance release; its runtime remains 0.3.1. Its installation package is the complete plugin ZIP. The release also provides a namespaced asset inventory/checksum file and retains its existing `SHA256SUMS` URL for compatibility. The `rizin-v0.3.1` runtime ZIP and its checksum entry remain unchanged. Future Rizin runtime releases retain the existing runtime asset family and legacy `SHA256SUMS` while older runtime installers are supported; new plugin-only releases use the namespaced checksum filename. - -## Validation - -`scripts/test-release-isolation.ps1` substitutes only HTTP responses while performing real hash checks, extraction, version execution and installation. It tests a full page of unrelated releases before Rizin, a misleading tag with a Rizin-looking asset, drafts, prereleases, corrupt downloads, idempotent Hyper-V installation and coexistence of installation directories. The small synthetic Rizin version probe validates installer behavior; the actual published Rizin runtime is separately downloaded and executed for release verification. diff --git a/docs/validation-capstone-binary-patching-v0.1.0.md b/docs/validation/capstone-binary-patching/plugin-v0.1.0/report.md similarity index 90% rename from docs/validation-capstone-binary-patching-v0.1.0.md rename to docs/validation/capstone-binary-patching/plugin-v0.1.0/report.md index 9729dc8..57984dd 100644 --- a/docs/validation-capstone-binary-patching-v0.1.0.md +++ b/docs/validation/capstone-binary-patching/plugin-v0.1.0/report.md @@ -1,6 +1,6 @@ # Capstone Binary Patching 0.1.0: Local Validation -Validation date: 2026-10-01. Plugin: [Capstone Binary Patching](../plugins/capstone-binary-patching). +Validation date: 2026-10-01. Plugin: [Capstone Binary Patching](../../../../plugins/capstone-binary-patching). This record comes from local compilation, execution and real CLI invocations. Behavior validation was performed against actual artifacts and CPU emulation. @@ -25,7 +25,7 @@ uv python install --no-bin --no-registry 3.13 uv run --managed-python --locked --python 3.13 '.\plugins\capstone-binary-patching\experiments\run_experiments.py' --out '.\build\capstone-experiments\new-run' ``` -The [experiment harness](../plugins/capstone-binary-patching/experiments/run_experiments.py) +The [experiment harness](../../../../plugins/capstone-binary-patching/experiments/run_experiments.py) invokes the plugin's actual CLI entry point. Base and experiment scripts have separate uv locks; the optional Keystone dependency is supplied through a version-pinned `--with`. The first two rounds also exercised uv environments using an existing CPython 3.13.15 @@ -33,7 +33,7 @@ installation. The final round explicitly used uv-managed Python. ## Native Windows program execution -MSVC compiled the EXE locally from [fixture.c](../plugins/capstone-binary-patching/experiments/fixture.c). +MSVC compiled the EXE locally from [fixture.c](../../../../plugins/capstone-binary-patching/experiments/fixture.c). A locally generated Kernel32 import library provides console output and process exit; no test EXE was downloaded. The exported `patch_target` supplies the function RVA. The CLI maps its VA to a file offset, decodes with Capstone, assembles replacement bytes with Keystone, creates @@ -90,9 +90,9 @@ patched files retained their hashes. Complete local records: -- [summary.json](../build/capstone-experiments/run-03/summary.json): group results, actual versions, interpreter paths and implementation hashes. -- [transcript.jsonl](../build/capstone-experiments/run-03/transcript.jsonl): argv, stdout, stderr and exit codes for 98 commands. -- [pe-plan.json](../build/capstone-experiments/run-03/pe-plan.json): the actual patch plan and before/after instructions. +- `build/capstone-experiments/run-03/summary.json`: group results, actual versions, interpreter paths and implementation hashes. +- `build/capstone-experiments/run-03/transcript.jsonl`: argv, stdout, stderr and exit codes for 98 commands. +- `build/capstone-experiments/run-03/pe-plan.json`: the actual patch plan and before/after instructions. The `build/` artifacts remain on this machine; binaries and caches are not committed. The reproduction harness and this report are retained in source control. diff --git a/docs/frida-use-validation.md b/docs/validation/frida-use/2026-10-01/report.md similarity index 82% rename from docs/frida-use-validation.md rename to docs/validation/frida-use/2026-10-01/report.md index 8fb7fc6..1cdd4ef 100644 --- a/docs/frida-use-validation.md +++ b/docs/validation/frida-use/2026-10-01/report.md @@ -9,27 +9,27 @@ under `plugins/frida-use`. - Windows 11 x64, build 26200. - Python 3.13.15 x64, Frida 17.19.0, frida-tools 14.10.4. - MSVC 14.51.36231, native x64 and x86 EXEs/DLLs compiled from the checked-in fixtures. -- [Integration results](validation/frida-use/2026-10-01/integration.json): 25 passed cases. -- [CLI results](validation/frida-use/2026-10-01/cli.json): 4 passed cases. -- [Relocation result](validation/frida-use/2026-10-01/portable.json): 1 passed case. -- [Selected runner evidence](validation/frida-use/2026-10-01/runner-evidence.json) and - [source/binary SHA-256 manifest](validation/frida-use/2026-10-01/artifact-hashes.json). +- [Integration results](integration.json): 25 passed cases. +- [CLI results](cli.json): 4 passed cases. +- [Relocation result](portable.json): 1 passed case. +- [Selected runner evidence](runner-evidence.json) and + [source/binary SHA-256 manifest](artifact-hashes.json). The 30 cases execute actual processes and hooks. Raw local captures remain under `build/frida-use-run-05`, `build/frida-cli-run-02`, and `build/frida-portable-run`. The retained JSON evidence is accompanied by the CLI transcripts in the same validation directory. Reproduction commands and fixture source are in -[scripts/frida-use-experiments](../scripts/frida-use-experiments). +[scripts/frida-use-experiments](../../../../scripts/frida-use-experiments). ## Observed behavior | Experiment | Observed result | | --- | --- | -| x64/x86 × QJS/V8 spawn | Ready before resume, successful Unicode file-open events, one chosen API boundary, owned target cleanup | +| x64/x86 × QJS/V8 spawn | Ready before resume, successful Unicode file-open events, one chosen API boundary, owned target cleanup | | PID attach on both architectures | Capture completes and target remains alive after detach | -| Native hook + return modification | `demo_score(5)`: 12 → 99 → 12 after listener removal | -| Whole-function replacement | 12 → 105 → 12 after Interceptor.revert | -| Executable-memory byte patch | Private scratch function: 7 → 42 → 7; original six bytes restored | +| Native hook + return modification | `demo_score(5)`: 12 → 99 → 12 after listener removal | +| Whole-function replacement | 12 → 105 → 12 after Interceptor.revert | +| Executable-memory byte patch | Private scratch function: 7 → 42 → 7; original six bytes restored | | x86 stdcall / x64 win64 | SystemFunction returns value 0 with lastError 1234 | | DLL observer | A newly loaded DLL is hooked, unloaded, reloaded and hooked again; native result remains 18 | | V8 Inspector, both architectures | Debugger.paused arrives in debugProbe; resume completes the RPC with 42 | @@ -78,7 +78,7 @@ checks, and 48 maintained local links passed. The 31 imported files and executab helpers retain their recorded hashes. The original artifact hash manifest above remains a record of the experiment snapshot; documentation changes and their current hashes are recorded in -[english-content-checks.json](validation/frida-use/2026-10-01/english-content-checks.json). +[english-content-checks.json](english-content-checks.json). ## Interpreter-path follow-up @@ -88,7 +88,7 @@ This was checked in a fresh isolated environment with Frida 17.19.0 and frida-tools 14.10.4. From the skill directory, the environment's interpreter ran the doctor successfully and captured four real file-open events from the native fixture, exiting with code 0. See -[setup-interpreter-check.json](validation/frida-use/2026-10-01/setup-interpreter-check.json). +[setup-interpreter-check.json](setup-interpreter-check.json). ## Limits diff --git a/plugins/hyper-v-control/docs/validation.md b/docs/validation/hyper-v-control/runtime-v0.1.0/report.md similarity index 97% rename from plugins/hyper-v-control/docs/validation.md rename to docs/validation/hyper-v-control/runtime-v0.1.0/report.md index c77a3c4..70f99d1 100644 --- a/plugins/hyper-v-control/docs/validation.md +++ b/docs/validation/hyper-v-control/runtime-v0.1.0/report.md @@ -38,7 +38,7 @@ Cleanup completed: the VM is Off, Secure Boot was read back as enabled, both COM - Live create/remove/import/export, large disk resizing, multi-host management, every guest OS version, and every debugger command/extension were not exhaustively exercised. The local host is the management boundary; unsupported guest services return errors. - KD needs a real Windows console even when its streams are redirected. The final backend uses a private hidden console per session and Ctrl+Break scoped to it. The earlier `-wake`/remote-interrupt experiments are not the shipped backend. - Symbol download and first attachment can outlast a single call. Pending commands remain in the same session for poll/break; a launched process or successful BCD update is not reported as a verified attachment. -- No GitHub Release is published by the local build. `scripts/install.ps1` requires the matching published asset; the prebuilt plugin ZIP contains the EXE and works without downloading it. +- No GitHub Release is published by the local build. Runtime downloads require the matching published asset. Plugin ZIP distribution has been retired in favor of marketplace installation. ## Review regression checks diff --git a/docs/validation-v0.3.1.md b/docs/validation/rizin/runtime-v0.3.1/report.md similarity index 100% rename from docs/validation-v0.3.1.md rename to docs/validation/rizin/runtime-v0.3.1/report.md diff --git a/plugins/capstone-binary-patching/skills/capstone-binary-patching/references/sources.md b/plugins/capstone-binary-patching/skills/capstone-binary-patching/references/sources.md index 5ead9fd..8f9df7e 100644 --- a/plugins/capstone-binary-patching/skills/capstone-binary-patching/references/sources.md +++ b/plugins/capstone-binary-patching/skills/capstone-binary-patching/references/sources.md @@ -50,5 +50,5 @@ also requires checking the installed version and implementation. - [Unicorn 2.1.4](https://pypi.org/project/unicorn/2.1.4/): CPU emulator used in experiments. Consult the plugin's `experiments/` directory and repository report -`docs/validation-capstone-binary-patching-v0.1.0.md` for local execution results. Listing an +`docs/validation/capstone-binary-patching/plugin-v0.1.0/report.md` for local execution results. Listing an optional tool here does not mean it was installed or tested on this host. diff --git a/plugins/hyper-v-control/.codex-plugin/plugin.json b/plugins/hyper-v-control/.codex-plugin/plugin.json index 4facdc9..1ba5f11 100644 --- a/plugins/hyper-v-control/.codex-plugin/plugin.json +++ b/plugins/hyper-v-control/.codex-plugin/plugin.json @@ -1,6 +1,6 @@ { "name": "hyper-v-control", - "version": "0.1.0", + "version": "0.1.1", "description": "Control Hyper-V VMs, graphical consoles, guest automation and user/kernel debugging through a native C# stdio MCP server.", "author": { "name": "JerryLinLinLin", diff --git a/plugins/hyper-v-control/README.md b/plugins/hyper-v-control/README.md index 5ba8a71..12b315e 100644 --- a/plugins/hyper-v-control/README.md +++ b/plugins/hyper-v-control/README.md @@ -14,22 +14,22 @@ Manage a Hyper-V lab from your AI agent. Create and restore experiments, interac Requires a Windows x64 host with Hyper-V. The distributed executable is self-contained and needs no separate .NET or Python installation. -Install **Hyper-V Control** from **Jerry's Plugin Marketplace**, or get a complete plugin ZIP from the [download catalog](https://github.com/JerryLinLinLin/jerry-plugin-marketplace/blob/main/docs/releases.md#release-index). The ZIP includes the executable and skill. For a marketplace installation, run the plugin's `scripts/install.ps1` to download its verified runtime, then reconnect the MCP. +Install **Hyper-V Control** from **Jerry's Plugin Marketplace** in Codex. The plugin includes its skill and launcher. On first start, the launcher downloads and verifies the required executable automatically; subsequent starts reuse the installed runtime. -For another stdio MCP client, point its configuration at the executable's absolute path. This example uses the stable filename inside the extracted plugin: +For another stdio MCP client, download the EXE from the [runtime releases](https://github.com/JerryLinLinLin/jerry-plugin-marketplace/blob/main/docs/guides/releases.md#release-index) and point its configuration at the executable's absolute path. This example assumes you saved it as `C:\Tools\HyperVControl.exe`: ```json { "mcpServers": { "hyper-v-control": { - "command": "C:\\Tools\\hyper-v-control\\bin\\HyperVControl.exe", + "command": "C:\\Tools\\HyperVControl.exe", "args": ["--elevate"] } } } ``` -The standalone EXE is also available from the download catalog; use its downloaded filename in your configuration. +For an offline machine, use the plugin's `scripts/install.ps1` in your preparation workflow or copy the matching, verified executable into the plugin's `bin` directory before starting it. ## Permissions and guest setup @@ -41,7 +41,7 @@ Install Microsoft WinDbg separately for debugging. Its modern debugger engine is ## Development and validation -Build and test from the repository root using the SDK specified by the [release guide](https://github.com/JerryLinLinLin/jerry-plugin-marketplace/blob/main/docs/releases.md): +Build and test from the repository root using the SDK specified by the [release guide](https://github.com/JerryLinLinLin/jerry-plugin-marketplace/blob/main/docs/guides/releases.md): ```powershell ./scripts/build-hyperv-control.ps1 @@ -49,4 +49,4 @@ dotnet run --project tests/HyperVControl.Tests -c Release -- unit . dotnet run --project tests/HyperVControl.Tests -c Release -- smoke . ``` -[Feature inventory](docs/extraction.md) · [Validation and known limits](docs/validation.md) · [Third-party notices](THIRD-PARTY-NOTICES.md) +[Feature inventory](docs/extraction.md) · [Validation records and known limits](https://github.com/JerryLinLinLin/jerry-plugin-marketplace/blob/main/docs/README.md#validation-records) · [Third-party notices](THIRD-PARTY-NOTICES.md) diff --git a/plugins/hyper-v-control/plugin.json b/plugins/hyper-v-control/plugin.json index 0149556..9cf3d94 100644 --- a/plugins/hyper-v-control/plugin.json +++ b/plugins/hyper-v-control/plugin.json @@ -1,7 +1,7 @@ { "$schema": "https://agent-plugins.org/schemas/1.0.0/plugin.schema.json", "name": "hyper-v-control", - "version": "0.1.0", + "version": "0.1.1", "description": "Control Hyper-V VMs, graphical consoles, guest automation and user/kernel debugging through a native C# stdio MCP server.", "author": {"name": "JerryLinLinLin", "url": "https://github.com/JerryLinLinLin"}, "repository": "https://github.com/JerryLinLinLin/jerry-plugin-marketplace", diff --git a/plugins/hyper-v-control/scripts/start.ps1 b/plugins/hyper-v-control/scripts/start.ps1 index 492da38..a374986 100644 --- a/plugins/hyper-v-control/scripts/start.ps1 +++ b/plugins/hyper-v-control/scripts/start.ps1 @@ -7,8 +7,9 @@ if (-not (Test-Path -LiteralPath $exe)) { $exe = Join-Path $env:LOCALAPPDATA ('Programs\HyperVControl\' + $runtime.version + '\HyperVControl.exe') } if (-not (Test-Path -LiteralPath $exe)) { - [Console]::Error.WriteLine('Hyper-V Control EXE is not installed. Run this plugin''s scripts\install.ps1, or extract the prebuilt plugin ZIP. No compiler is required.') - exit 1 + # Capture installer output so MCP stdout contains only protocol messages. + $ProgressPreference = 'SilentlyContinue' + $exe = & (Join-Path $PSScriptRoot 'install.ps1') } $hashStream = [IO.File]::OpenRead($exe) $hasher = [Security.Cryptography.SHA256]::Create() diff --git a/plugins/hyper-v-control/skills/hyper-v-control/references/runtime.md b/plugins/hyper-v-control/skills/hyper-v-control/references/runtime.md index 83428e6..08585c2 100644 --- a/plugins/hyper-v-control/skills/hyper-v-control/references/runtime.md +++ b/plugins/hyper-v-control/skills/hyper-v-control/references/runtime.md @@ -1,6 +1,6 @@ # Runtime and permissions -The prebuilt plugin ZIP contains `bin/HyperVControl.exe`. A source-only marketplace install uses `scripts/install.ps1` to download the version/checksum pinned in `runtime.json`; then reconnect the MCP. The release must exist before this download path works. Development builds use the repository build script, never a Python MCP server. +Install the plugin through the marketplace. Its launcher invokes `scripts/install.ps1` when the required executable is missing, downloads the exact runtime pinned in `runtime.json`, verifies its checksum, and then starts the MCP. Later starts reuse that installation. A development or offline installation may supply a matching `bin/HyperVControl.exe`. Plugin and runtime versions are independent; a launcher or skill update does not require a new native executable. The launcher verifies the EXE hash and starts `--elevate`. The parent keeps MCP stdin/stdout; a same-user worker receives them over a private named pipe. Only the current Windows user's SID has access, and both ends verify process IDs. UAC consent can appear once when a split-token administrator starts it. Cancellation is an error with no VM mutation. Do not disable UAC or grant permanent group membership to make a call work. diff --git a/plugins/rizin-re-toolkit/README.md b/plugins/rizin-re-toolkit/README.md index a02ec2b..ac2dea4 100644 --- a/plugins/rizin-re-toolkit/README.md +++ b/plugins/rizin-re-toolkit/README.md @@ -15,7 +15,7 @@ Install **Rizin RE Toolkit** from **Jerry's Plugin Marketplace** in Codex. The i The [PowerShell installer](skills/rizin-re-toolkit/scripts/install.ps1) downloads and verifies the runtime into a user-writable directory. It supports a custom destination and optional user PATH registration. -For manual installation, use the [download catalog](https://github.com/JerryLinLinLin/jerry-plugin-marketplace/blob/main/docs/releases.md#release-index), verify the archive checksum, and extract the complete `rizin` folder. Run `rizin\bin\rizin.exe` by absolute path. Keep the folder together when moving it; required runtime libraries and support data are included. +For manual installation, use the [download catalog](https://github.com/JerryLinLinLin/jerry-plugin-marketplace/blob/main/docs/guides/releases.md#release-index), verify the archive checksum, and extract the complete `rizin` folder. Run `rizin\bin\rizin.exe` by absolute path. Keep the folder together when moving it; required runtime libraries and support data are included. ## Use @@ -33,6 +33,6 @@ This plugin uses Rizin and its bundled analysis engines. Tool-specific project f ## More information -[Agent workflow](skills/rizin-re-toolkit/SKILL.md) · [Build guide](https://github.com/JerryLinLinLin/jerry-plugin-marketplace/blob/main/docs/build.md) · [Releases and component versions](https://github.com/JerryLinLinLin/jerry-plugin-marketplace/blob/main/docs/releases.md) +[Agent workflow](skills/rizin-re-toolkit/SKILL.md) · [Build guide](https://github.com/JerryLinLinLin/jerry-plugin-marketplace/blob/main/docs/guides/rizin-build.md) · [Releases and component versions](https://github.com/JerryLinLinLin/jerry-plugin-marketplace/blob/main/docs/guides/releases.md) Plugin code and instructions use the MIT license. Runtime components retain their own licenses, included with the bundle. diff --git a/scripts/build-bundle.ps1 b/scripts/build-bundle.ps1 index d125c68..e9a09cd 100644 --- a/scripts/build-bundle.ps1 +++ b/scripts/build-bundle.ps1 @@ -3,7 +3,7 @@ param([switch]$AssembleOnly, [int]$Jobs = 8) $ErrorActionPreference = 'Stop' . (Join-Path $PSScriptRoot 'enter-build-env.ps1') $python = Join-Path $repoRoot 'build\tools\Scripts\python.exe' -if (-not (Test-Path -LiteralPath $python)) { throw 'Create build\tools and install scripts\build-requirements.txt first; see docs/build.md.' } +if (-not (Test-Path -LiteralPath $python)) { throw 'Create build\tools and install scripts\build-requirements.txt first; see docs/guides/rizin-build.md.' } $buildArgs = @((Join-Path $PSScriptRoot 'build_bundle.py'), '--jobs', $Jobs) if ($AssembleOnly) { $buildArgs += '--assemble-only' } & $python @buildArgs diff --git a/scripts/build-hyperv-control.ps1 b/scripts/build-hyperv-control.ps1 index 0684230..bab8a01 100644 --- a/scripts/build-hyperv-control.ps1 +++ b/scripts/build-hyperv-control.ps1 @@ -11,15 +11,15 @@ if ($NoRestore) { $arguments += '--no-restore' } & $DotNet @arguments if ($LASTEXITCODE -ne 0) { throw 'Hyper-V Control publish failed.' } $exe = Join-Path $out 'HyperVControl.exe' -$metadata = Get-Content -LiteralPath (Join-Path $plugin 'plugin.json') -Raw | ConvertFrom-Json -$version = $metadata.version -if ($version -notmatch '^\d+\.\d+\.\d+$') { throw 'Invalid plugin version.' } +$projectXml = [xml](Get-Content -LiteralPath $project -Raw) +$version = [string]$projectXml.Project.PropertyGroup.Version +if ($version -notmatch '^\d+\.\d+\.\d+$') { throw 'Invalid runtime version.' } $tag = "hyper-v-control-v$version" $exeName = "hyper-v-control-win-x64-v$version.exe" -$zipName = "hyper-v-control-plugin-v$version-win-x64.zip" +$noticesName = "$tag-NOTICES.txt" $manifestName = "$tag-release.json" $checksumsName = "$tag-SHA256SUMS.txt" -if ((& $exe --version) -ne "Hyper-V Control $version") { throw 'Published version does not match plugin.' } +if ((& $exe --version) -ne "Hyper-V Control $version") { throw 'Published version does not match the project.' } $sha = (Get-FileHash -LiteralPath $exe -Algorithm SHA256).Hash.ToLowerInvariant() New-Item -ItemType Directory -Force -Path (Join-Path $plugin 'bin') | Out-Null Copy-Item -LiteralPath $exe -Destination (Join-Path $plugin 'bin\HyperVControl.exe') -Force @@ -31,28 +31,29 @@ $manifest = [ordered]@{ } $manifestJson = ($manifest | ConvertTo-Json).Replace("`r`n", "`n") + "`n" [IO.File]::WriteAllText((Join-Path $plugin 'runtime.json'), $manifestJson, [Text.UTF8Encoding]::new($false)) -$release = Join-Path $repo 'build\hyperv-control\release' +$release = Join-Path $repo 'build\hyperv-control\runtime-release' New-Item -ItemType Directory -Force -Path $release | Out-Null -# Retain this local convenience path; public assets always use the namespaced name. -Copy-Item -LiteralPath $exe -Destination (Join-Path $release 'HyperVControl.exe') -Force +$publicNames = @($exeName, $noticesName, $manifestName, $checksumsName) +if (@(Get-ChildItem -LiteralPath $release -Force | Where-Object { $_.PSIsContainer -or $_.Name -notin $publicNames }).Count) { + throw 'Use a clean runtime-release directory without unrelated or obsolete assets.' +} Copy-Item -LiteralPath $exe -Destination (Join-Path $release $exeName) -Force -Copy-Item -LiteralPath (Join-Path $repo 'LICENSE'),(Join-Path $plugin 'THIRD-PARTY-NOTICES.md') -Destination $release -Force -Copy-Item -LiteralPath (Join-Path $plugin 'licenses') -Destination $release -Recurse -Force -$zip = Join-Path $release $zipName -Add-Type -AssemblyName System.IO.Compression.FileSystem -if(Test-Path -LiteralPath $zip){Remove-Item -LiteralPath $zip -Force} -[IO.Compression.ZipFile]::CreateFromDirectory($plugin, $zip, [IO.Compression.CompressionLevel]::Optimal, $false) -$zipSha=(Get-FileHash -LiteralPath $zip -Algorithm SHA256).Hash.ToLowerInvariant() +$noticeInputs = @((Join-Path $plugin 'LICENSE'), (Join-Path $plugin 'THIRD-PARTY-NOTICES.md')) +$noticeInputs += @(Get-ChildItem -LiteralPath (Join-Path $plugin 'licenses') -File -Recurse | Sort-Object FullName | Select-Object -ExpandProperty FullName) +$noticeText = [Text.StringBuilder]::new() +foreach ($notice in $noticeInputs) { + [void]$noticeText.AppendLine("===== $($notice.Substring($plugin.Length + 1).Replace('\', '/')) =====") + [void]$noticeText.AppendLine([IO.File]::ReadAllText($notice)) +} +[IO.File]::WriteAllText((Join-Path $release $noticesName), $noticeText.ToString().Replace("`r`n", "`n"), [Text.UTF8Encoding]::new($false)) +$noticesSha = (Get-FileHash -LiteralPath (Join-Path $release $noticesName) -Algorithm SHA256).Hash.ToLowerInvariant() $assets = @( [ordered]@{name=$exeName;kind='executable';rid='win-x64';sha256=$sha;size=(Get-Item $exe).Length}, - [ordered]@{name=$zipName;kind='plugin';rid='win-x64';sha256=$zipSha;size=(Get-Item $zip).Length} + [ordered]@{name=$noticesName;kind='notices';sha256=$noticesSha;size=(Get-Item (Join-Path $release $noticesName)).Length} ) $releaseManifest = [ordered]@{schemaVersion=1;repository='JerryLinLinLin/jerry-plugin-marketplace';plugin='hyper-v-control';version=$version;tag=$tag;assets=$assets} [IO.File]::WriteAllText((Join-Path $release $manifestName), (($releaseManifest | ConvertTo-Json -Depth 6).Replace("`r`n", "`n") + "`n"), [Text.UTF8Encoding]::new($false)) -$manifestSha=(Get-FileHash -LiteralPath (Join-Path $release $manifestName) -Algorithm SHA256).Hash.ToLowerInvariant() -$sums=@("$sha $exeName", "$zipSha $zipName", "$manifestSha $manifestName") -[IO.File]::WriteAllLines((Join-Path $release $checksumsName),$sums,[Text.Encoding]::ASCII) -# Local compatibility only. Upload the four namespaced files, never this alias. -@("$sha HyperVControl.exe", "$zipSha $zipName") | Set-Content -LiteralPath (Join-Path $release 'SHA256SUMS') -Encoding ASCII -Write-Output "Public assets: $exeName, $zipName, $manifestName, $checksumsName" +$manifestSha = (Get-FileHash -LiteralPath (Join-Path $release $manifestName) -Algorithm SHA256).Hash.ToLowerInvariant() +[IO.File]::WriteAllLines((Join-Path $release $checksumsName), @("$sha $exeName", "$noticesSha $noticesName", "$manifestSha $manifestName"), [Text.Encoding]::ASCII) +Write-Output "Public assets: $($publicNames -join ', ')" Write-Output "Release artifacts: $release" diff --git a/scripts/build_bundle.py b/scripts/build_bundle.py index 860c2e0..5c3b94d 100644 --- a/scripts/build_bundle.py +++ b/scripts/build_bundle.py @@ -184,9 +184,8 @@ def assemble(): manifest['windowsManifest'] = {'file': 'scripts/windows-utf8.manifest', 'sha256': hashlib.sha256((ROOT / 'scripts/windows-utf8.manifest').read_bytes()).hexdigest(), 'minimumWindows': 'Windows 10 version 1903 (x64)'} manifest['patches'] = {p.name: hashlib.sha256(p.read_bytes()).hexdigest() for p in (ROOT / 'patches').glob('*.patch')} (target / 'bundle-manifest.json').write_text(json.dumps(manifest, indent=2) + '\n', encoding='utf-8') - copy_tree(ROOT / 'plugins/rizin-re-toolkit/skills', target / 'skills') - shutil.copy2(ROOT / 'plugins/rizin-re-toolkit/README.md', target / 'README.md') - shutil.copy2(ROOT / 'docs/third-party.md', target / 'THIRD-PARTY.md') + shutil.copy2(ROOT / 'docs/guides/rizin-runtime.md', target / 'README.md') + shutil.copy2(ROOT / 'docs/reference/rizin-third-party.md', target / 'THIRD-PARTY.md') print(f'Portable runtime: {target}', flush=True) diff --git a/scripts/check-marketplace.py b/scripts/check-marketplace.py index 1cf20d3..5a43bfa 100644 --- a/scripts/check-marketplace.py +++ b/scripts/check-marketplace.py @@ -33,8 +33,13 @@ def require(condition, message): require(len(names) == len(set(names)), "Marketplace plugin names must be unique") folders = {path.name for path in (ROOT / "plugins").iterdir() if path.is_dir()} require(set(names) == folders, "Marketplace entries must match the directories under plugins/") - documents = [ROOT / path for path in ("README.md", "CONTRIBUTING.md", "docs/build.md", "docs/releases.md")] + documents = [ROOT / "README.md", ROOT / "CONTRIBUTING.md"] + sorted((ROOT / "docs").rglob("*.md")) + require({path.name for path in (ROOT / "docs").iterdir() if path.is_file()} == {"README.md"}, + "docs/ root must contain only README.md; place documents under guides/, reference/, or validation/") + require({path.name for path in (ROOT / "docs").iterdir() if path.is_dir()} <= {"guides", "reference", "validation"}, + "Unexpected docs/ directory; follow the documentation index") readmes = [ROOT / "README.md"] + repository_urls = set() for entry in catalog["plugins"]: name = entry["name"] @@ -45,6 +50,8 @@ def require(condition, message): if not plugin.is_dir(): continue manifest = read_json(plugin / "plugin.json") + if manifest.get("repository"): + repository_urls.add(manifest["repository"].rstrip("/") + "/blob/main/") require(manifest["name"] == name, f"{name}: manifest identity mismatch") require(bool(SEMVER.fullmatch(manifest["version"])), f"{name}: invalid semantic version") expected = compatibility_manifest(manifest) @@ -92,12 +99,12 @@ def require(condition, message): # Existing native version literals remain part of the published binary identity. # Catch partial version bumps without rewriting an already released executable. - hyperv_version = read_json(ROOT / "plugins/hyper-v-control/plugin.json")["version"] + hyperv_version = read_json(ROOT / "plugins/hyper-v-control/runtime.json")["version"] project = ET.parse(ROOT / "src/HyperVControl/HyperVControl.csproj") - require(project.findtext(".//Version") == hyperv_version, "Hyper-V project and plugin versions disagree") + require(project.findtext(".//Version") == hyperv_version, "Hyper-V project and runtime versions disagree") program = (ROOT / "src/HyperVControl/Program.cs").read_text(encoding="utf-8-sig") for literal in re.findall(r'(?:Hyper-V Control |Version = ")(\d+\.\d+\.\d+)', program): - require(literal == hyperv_version, "Hyper-V CLI/protocol version disagrees with its plugin") + require(literal == hyperv_version, "Hyper-V CLI/protocol version disagrees with its runtime pin") for readme in readmes: content = readme.read_text(encoding="utf-8-sig") @@ -108,6 +115,12 @@ def require(condition, message): content = document.read_text(encoding="utf-8-sig") for link in re.findall(r"\[[^\]\n]+\]\(([^)\n]+)\)", content): url = urlsplit(link.strip("<>")) + own_prefix = next((prefix for prefix in repository_urls if link.startswith(prefix)), None) + if own_prefix: + relative = unquote(urlsplit(link[len(own_prefix):]).path) + target = (ROOT / relative).resolve() + require(target.is_relative_to(ROOT) and target.exists(), f"{document.relative_to(ROOT)}: broken repository link {link}") + continue if url.scheme or url.netloc or not url.path: continue target = (document.parent / unquote(url.path)).resolve() diff --git a/scripts/ci-plan.py b/scripts/ci-plan.py index 083349a..c6478c2 100644 --- a/scripts/ci-plan.py +++ b/scripts/ci-plan.py @@ -10,6 +10,7 @@ WINDOWS_FILES = { ".gitattributes", "HyperVControl.slnx", "scripts/ci-plan.py", "scripts/build-hyperv-control.ps1", "scripts/test-installer.ps1", "scripts/test-release-isolation.ps1", + "scripts/test-hyperv-launcher.ps1", "scripts/test-runtime-packaging.py", "plugins/rizin-re-toolkit/skills/rizin-re-toolkit/scripts/install.ps1", } diff --git a/scripts/frida-use-experiments/README.md b/scripts/frida-use-experiments/README.md index dc862bd..13828a2 100644 --- a/scripts/frida-use-experiments/README.md +++ b/scripts/frida-use-experiments/README.md @@ -32,6 +32,6 @@ The separate CLI experiment launches `frida` and `frida-trace` directly and chec their real output/generated handlers. On frida-tools 14.10.4 a traced target's natural termination yields tool exit 1; that is explicitly checked, not hidden. -See [the retained run report](../../docs/frida-use-validation.md) for the executed +See [the retained run report](../../docs/validation/frida-use/2026-10-01/report.md) for the executed environment, evidence and remaining coverage limits. These results are not part of the installed skill's instructional material. diff --git a/scripts/package-bundle.ps1 b/scripts/package-bundle.ps1 index cf4cb03..9834081 100644 --- a/scripts/package-bundle.ps1 +++ b/scripts/package-bundle.ps1 @@ -2,22 +2,16 @@ param( [string]$Version = '', [string]$RuntimeDir = '', - [string]$OutputDir = '', - [switch]$PluginOnly + [string]$OutputDir = '' ) $ErrorActionPreference = 'Stop' $repoRoot = Split-Path -Parent $PSScriptRoot if (-not $RuntimeDir) { $RuntimeDir = Join-Path $repoRoot 'build\portable\rizin' } if (-not $OutputDir) { $OutputDir = Join-Path $repoRoot 'build\release' } if (-not $Version) { - if ($PluginOnly) { - $Version = (Get-Content (Join-Path $repoRoot 'plugins\rizin-re-toolkit\plugin.json') -Raw | ConvertFrom-Json).version - } else { - $Version = (Get-Content (Join-Path $repoRoot 'bundle.lock.json') -Raw | ConvertFrom-Json).bundleVersion - } + $Version = (Get-Content (Join-Path $repoRoot 'bundle.lock.json') -Raw | ConvertFrom-Json).bundleVersion } $python = Join-Path $repoRoot 'build\tools\Scripts\python.exe' -$packageArgs = @((Join-Path $PSScriptRoot 'package_bundle.py'), '--version', $Version, '--output', $OutputDir) -if ($PluginOnly) { $packageArgs += '--plugin-only' } else { $packageArgs += @('--runtime', $RuntimeDir) } +$packageArgs = @((Join-Path $PSScriptRoot 'package_bundle.py'), '--version', $Version, '--output', $OutputDir, '--runtime', $RuntimeDir) & $python @packageArgs if ($LASTEXITCODE -ne 0) { throw "Packaging failed: $LASTEXITCODE" } diff --git a/scripts/package_bundle.py b/scripts/package_bundle.py index de72943..80f6358 100644 --- a/scripts/package_bundle.py +++ b/scripts/package_bundle.py @@ -1,4 +1,4 @@ -"""Package the runtime and complete plugin archives, including their skills.""" +"""Package the portable Rizin runtime; plugins are installed from the marketplace.""" import argparse import hashlib import json @@ -15,88 +15,53 @@ def sha256(path): return hashlib.file_digest(stream, 'sha256').hexdigest() -def archive(source, output, include_root=False): +def archive(source, output): with zipfile.ZipFile(output, 'w', zipfile.ZIP_DEFLATED, compresslevel=6) as result: for file in sorted(source.rglob('*')): if not file.is_file(): continue if file.is_symlink() or '.git' in file.parts: raise RuntimeError(f'Unexpected link or Git metadata: {file}') - base = source.parent if include_root else source - result.write(file, file.relative_to(base).as_posix()) + result.write(file, (Path('rizin') / file.relative_to(source)).as_posix()) def main(): parser = argparse.ArgumentParser() parser.add_argument('--version', required=True) - parser.add_argument('--runtime', type=Path) + parser.add_argument('--runtime', type=Path, required=True) parser.add_argument('--output', type=Path, required=True) - parser.add_argument('--plugin-only', action='store_true') args = parser.parse_args() if not re.fullmatch(r'[0-9]+\.[0-9]+\.[0-9]+', args.version): raise RuntimeError('Use a numeric semantic version') output = args.output.resolve() if not output.is_relative_to(ROOT / 'build'): raise RuntimeError('Output must be inside the repository build directory') - plugin = ROOT / 'plugins/rizin-re-toolkit' - metadata = json.loads((plugin / 'plugin.json').read_text(encoding='utf-8-sig')) - plugin_version = metadata['version'] - if not re.fullmatch(r'[0-9]+\.[0-9]+\.[0-9]+', plugin_version): - raise RuntimeError('Plugin manifest must use a numeric semantic version') - packages = [] - if args.plugin_only: - if plugin_version != args.version: - raise RuntimeError('Plugin and requested versions disagree') - else: - if args.runtime is None: - raise RuntimeError('--runtime is required unless --plugin-only is set') - runtime = args.runtime.resolve() - if not runtime.is_relative_to(ROOT / 'build') or output.is_relative_to(runtime): - raise RuntimeError('Runtime must be inside build, and output must be outside the runtime') - manifest = json.loads((runtime / 'bundle-manifest.json').read_text(encoding='utf-8-sig')) - if manifest['bundleVersion'] != args.version: - raise RuntimeError('Runtime and requested versions disagree') - packages.append((runtime, f'rizin-windows-x64-bundle-v{args.version}.zip', True)) - packages.append((plugin, f'rizin-re-toolkit-plugin-v{plugin_version}.zip', False)) + runtime = args.runtime.resolve() + if not runtime.is_relative_to(ROOT / 'build') or output.is_relative_to(runtime): + raise RuntimeError('Runtime must be inside build, and output must be outside the runtime') + manifest = json.loads((runtime / 'bundle-manifest.json').read_text(encoding='utf-8-sig')) + if manifest['bundleVersion'] != args.version: + raise RuntimeError('Runtime and requested versions disagree') + if any((runtime / name).exists() for name in ('skills', 'plugin.json', '.codex-plugin', '.mcp.json', 'mcp.json')): + raise RuntimeError('Runtime contains plugin files; reassemble it without marketplace content') + name = f'rizin-windows-x64-bundle-v{args.version}.zip' output.mkdir(parents=True, exist_ok=True) - artifacts = [] - for folder, name, root in packages: - destination = output / name - archive(folder, destination, root) - artifacts.append(destination) - print(f'{destination.name}: {destination.stat().st_size:,} bytes', flush=True) - if not args.plugin_only: - manifest_path = output / 'bundle-manifest.json' - shutil.copy2(runtime / 'bundle-manifest.json', manifest_path) - artifacts.append(manifest_path) - # Package evidence only when it was produced by the verifier, never a dump. - evidence = output / 'verification.json' - if evidence.is_file(): - artifacts.append(evidence) - if args.plugin_only: - tag = f'rizin-re-toolkit-v{plugin_version}' - # A reused build directory may still contain the former duplicate export. - (output / f'{tag}.skill').unlink(missing_ok=True) - runtime_version = json.loads((ROOT / 'bundle.lock.json').read_text(encoding='utf-8-sig'))['bundleVersion'] - inventory = { - 'schemaVersion': 1, - 'repository': 'JerryLinLinLin/jerry-plugin-marketplace', - 'plugin': 'rizin-re-toolkit', 'version': plugin_version, 'tag': tag, - 'runtimeTag': f'rizin-v{runtime_version}', - 'assets': [ - {'name': file.name, 'kind': 'plugin', 'sha256': sha256(file), 'size': file.stat().st_size} - for file in artifacts - ], - } - inventory_path = output / f'{tag}-release.json' - inventory_path.write_text(json.dumps(inventory, indent=2) + '\n', encoding='utf-8') - artifacts.append(inventory_path) - checksum_name = f'{tag}-SHA256SUMS.txt' - else: - # Existing runtime installers require this legacy filename. - checksum_name = 'SHA256SUMS' + allowed = {name, 'bundle-manifest.json', 'verification.json', 'SHA256SUMS'} + if any(path.name not in allowed or not path.is_file() for path in output.iterdir()): + raise RuntimeError('Use a clean runtime release directory without unrelated or obsolete assets') + destination = output / name + archive(runtime, destination) + print(f'{name}: {destination.stat().st_size:,} bytes', flush=True) + manifest_path = output / 'bundle-manifest.json' + shutil.copy2(runtime / 'bundle-manifest.json', manifest_path) + artifacts = [destination, manifest_path] + # Package evidence only when it was produced by the verifier, never a dump. + evidence = output / 'verification.json' + if evidence.is_file(): + artifacts.append(evidence) sums = ''.join(f'{sha256(file)} {file.name}\n' for file in artifacts) - (output / checksum_name).write_text(sums, encoding='ascii') + # Existing runtime installers require this filename. + (output / 'SHA256SUMS').write_text(sums, encoding='ascii') if __name__ == '__main__': diff --git a/scripts/test-hyperv-launcher.ps1 b/scripts/test-hyperv-launcher.ps1 new file mode 100644 index 0000000..89991b2 --- /dev/null +++ b/scripts/test-hyperv-launcher.ps1 @@ -0,0 +1,67 @@ +[CmdletBinding()] +param() +$ErrorActionPreference = 'Stop' +$repo = Split-Path -Parent $PSScriptRoot +$scratch = Join-Path $repo ('build\launcher-test-' + [guid]::NewGuid().ToString('N')) +$plugin = Join-Path $scratch 'plugin' +New-Item -ItemType Directory -Force -Path (Join-Path $plugin 'scripts') | Out-Null +Copy-Item -LiteralPath (Join-Path $repo 'plugins\hyper-v-control\scripts\start.ps1'),(Join-Path $repo 'plugins\hyper-v-control\scripts\install.ps1') -Destination (Join-Path $plugin 'scripts') +$runtime = Get-Content -LiteralPath (Join-Path $repo 'plugins\hyper-v-control\runtime.json') -Raw | ConvertFrom-Json +$source = Join-Path $scratch 'LauncherProbe.cs' +$probeSource = @' +using System; +class LauncherProbe { + static int Main(string[] args) { + if (args.Length == 1 && args[0] == "--version") { + Console.WriteLine("Hyper-V Control __VERSION__"); return 0; + } + if (args.Length == 1 && args[0] == "--elevate") { + Console.WriteLine("{\"jsonrpc\":\"2.0\",\"method\":\"fixture/started\"}"); return 0; + } + return 2; + } +} +'@ +$probeSource.Replace('__VERSION__', $runtime.version) | Set-Content -LiteralPath $source -Encoding UTF8 +$probe = Join-Path $scratch 'LauncherProbe.exe' +$compiler = Join-Path $env:WINDIR 'Microsoft.NET\Framework64\v4.0.30319\csc.exe' +& $compiler /nologo /target:exe ("/out:" + $probe) $source +if ($LASTEXITCODE -ne 0) { throw 'Launcher fixture compilation failed.' } +# A controlled executable exercises setup and stdout routing without UAC or VM operations. +$runtime.sha256 = (Get-FileHash -LiteralPath $probe -Algorithm SHA256).Hash.ToLowerInvariant() +$runtime | ConvertTo-Json | Set-Content -LiteralPath (Join-Path $plugin 'runtime.json') -Encoding UTF8 +$fixture = [pscustomobject]@{url=$runtime.url;exe=$probe;calls=0;corrupt=$false} +Set-Item Function:Invoke-WebRequest -Value ({ + param($Uri,$OutFile,[switch]$UseBasicParsing) + if ($Uri -ne $fixture.url) { throw 'Launcher requested an unpinned runtime.' } + $fixture.calls++ + if ($fixture.corrupt) { [IO.File]::WriteAllText($OutFile, 'corrupt download') } + else { Copy-Item -LiteralPath $fixture.exe -Destination $OutFile } +}.GetNewClosure()) +$originalLocalAppData = $env:LOCALAPPDATA +$launcher = Join-Path $plugin 'scripts\start.ps1' +$expected = '{"jsonrpc":"2.0","method":"fixture/started"}' +try { + $env:LOCALAPPDATA = Join-Path $scratch 'profile' + $first = @(& $launcher) + if ($LASTEXITCODE -ne 0 -or $first.Count -ne 1 -or $first[0] -ne $expected -or $fixture.calls -ne 1) { + throw 'First launch must install once and emit only protocol output.' + } + $second = @(& $launcher) + if ($LASTEXITCODE -ne 0 -or $second.Count -ne 1 -or $second[0] -ne $expected -or $fixture.calls -ne 1) { + throw 'Second launch must reuse the verified runtime.' + } + $env:LOCALAPPDATA = Join-Path $scratch 'bad-profile' + $fixture.corrupt = $true + $rejected = $false + try { & $launcher | Out-Null } + catch { if ($_.Exception.Message -like '*SHA-256 verification failed*') { $rejected = $true } else { throw } } + $installed = Join-Path $env:LOCALAPPDATA "Programs\HyperVControl\$($runtime.version)\HyperVControl.exe" + if (-not $rejected -or (Test-Path -LiteralPath $installed)) { throw 'Corrupt first-run download was not rejected.' } + $fixture.corrupt = $false + $retry = @(& $launcher) + if ($LASTEXITCODE -ne 0 -or $retry.Count -ne 1 -or $retry[0] -ne $expected) { throw 'Retry after failed download did not recover.' } +} finally { + $env:LOCALAPPDATA = $originalLocalAppData +} +Write-Output 'PASS: automatic setup, verified reuse, clean MCP stdout, corrupt-download rejection, and retry' diff --git a/scripts/test-plugin-packaging.py b/scripts/test-plugin-packaging.py deleted file mode 100644 index b677b39..0000000 --- a/scripts/test-plugin-packaging.py +++ /dev/null @@ -1,28 +0,0 @@ -"""Verify complete plugin ZIPs without redundant standalone skill downloads.""" -import hashlib -import json -from pathlib import Path -import subprocess -import sys -import uuid -import zipfile - -root = Path(__file__).resolve().parents[1] -version = json.loads((root / 'plugins/rizin-re-toolkit/plugin.json').read_text(encoding='utf-8-sig'))['version'] -tag = f'rizin-re-toolkit-v{version}' -output = root / 'build' / f'plugin-packaging-test-{uuid.uuid4().hex}' -output.mkdir(parents=True) -(output / f'{tag}.skill').write_bytes(b'obsolete standalone export') -subprocess.run([sys.executable, str(root / 'scripts/package_bundle.py'), '--plugin-only', '--version', version, '--output', str(output)], check=True) -zip_name = f'rizin-re-toolkit-plugin-v{version}.zip' -expected = {zip_name, f'{tag}-release.json', f'{tag}-SHA256SUMS.txt'} -assert {p.name for p in output.iterdir()} == expected, 'Only the plugin ZIP, inventory and checksum file should be published' -with zipfile.ZipFile(output / zip_name) as archive: - assert 'skills/rizin-re-toolkit/SKILL.md' in archive.namelist(), 'Skill must remain bundled in the plugin' - assert 'plugin.json' in archive.namelist(), 'Plugin manifest is required' -inventory = json.loads((output / f'{tag}-release.json').read_text()) -assert len(inventory['assets']) == 1 and inventory['assets'][0]['name'] == zip_name -for line in (output / f'{tag}-SHA256SUMS.txt').read_text().splitlines(): - digest, name = line.split(' ', 1) - assert hashlib.sha256((output / name).read_bytes()).hexdigest() == digest -print('PASS: plugin includes its skill; no standalone skill asset; inventory and checksums match') diff --git a/scripts/test-runtime-packaging.py b/scripts/test-runtime-packaging.py new file mode 100644 index 0000000..f593a5a --- /dev/null +++ b/scripts/test-runtime-packaging.py @@ -0,0 +1,78 @@ +"""Verify runtime-only release assets and reject mixed plugin/runtime output.""" +import argparse +import hashlib +import json +from pathlib import Path +import subprocess +import sys +import uuid +import zipfile + +ROOT = Path(__file__).resolve().parents[1] + + +def digest(path): + with path.open('rb') as stream: + return hashlib.file_digest(stream, 'sha256').hexdigest() + + +def check_sums(folder, filename, expected): + entries = {} + for line in (folder / filename).read_text().splitlines(): + checksum, name = line.split(' ', 1) + assert name not in entries + assert checksum == digest(folder / name), name + entries[name] = checksum + assert set(entries) == expected + + +def test_rizin(): + version = json.loads((ROOT / 'bundle.lock.json').read_text())['bundleVersion'] + scratch = ROOT / 'build' / f'runtime-packaging-test-{uuid.uuid4().hex}' + runtime, output = scratch / 'custom-runtime-directory', scratch / 'release' + (runtime / 'bin').mkdir(parents=True) + (runtime / 'bin/rizin.exe').write_bytes(b'packaging fixture; never executed') + (runtime / 'bundle-manifest.json').write_text(json.dumps({'bundleVersion': version})) + command = [sys.executable, str(ROOT / 'scripts/package_bundle.py'), '--runtime', str(runtime), + '--version', version, '--output', str(output)] + subprocess.run(command, check=True) + name = f'rizin-windows-x64-bundle-v{version}.zip' + assert {p.name for p in output.iterdir()} == {name, 'bundle-manifest.json', 'SHA256SUMS'} + with zipfile.ZipFile(output / name) as archive: + assert set(archive.namelist()) == {'rizin/bin/rizin.exe', 'rizin/bundle-manifest.json'} + check_sums(output, 'SHA256SUMS', {name, 'bundle-manifest.json'}) + before = {p.name: digest(p) for p in output.iterdir()} + obsolete = output / 'obsolete-plugin.zip' + obsolete.write_bytes(b'old plugin output') + rejected = subprocess.run(command, capture_output=True, text=True) + assert rejected.returncode and 'clean runtime release directory' in rejected.stderr + assert all(digest(output / name) == checksum for name, checksum in before.items()) + obsolete.unlink() + (runtime / 'skills').mkdir() + rejected = subprocess.run(command, capture_output=True, text=True) + assert rejected.returncode and 'Runtime contains plugin files' in rejected.stderr + print('PASS: Rizin runtime-only archive, checksums, mixed-output rejection, and plugin-content rejection') + + +def test_hyperv(folder): + runtime = json.loads((ROOT / 'plugins/hyper-v-control/runtime.json').read_text()) + tag = runtime['tag'] + expected = {runtime['asset'], f'{tag}-NOTICES.txt', f'{tag}-release.json', f'{tag}-SHA256SUMS.txt'} + assert {p.name for p in folder.iterdir()} == expected + inventory = json.loads((folder / f'{tag}-release.json').read_text()) + assert inventory['version'] == runtime['version'] + assert {a['kind'] for a in inventory['assets']} == {'executable', 'notices'} + assert {a['name'] for a in inventory['assets']} == {runtime['asset'], f'{tag}-NOTICES.txt'} + for asset in inventory['assets']: + assert digest(folder / asset['name']) == asset['sha256'] + assert (folder / asset['name']).stat().st_size == asset['size'] + assert digest(folder / runtime['asset']) == runtime['sha256'] + check_sums(folder, f'{tag}-SHA256SUMS.txt', expected - {f'{tag}-SHA256SUMS.txt'}) + print('PASS: Hyper-V executable, notices, inventory, and checksums; no plugin archive') + + +if __name__ == '__main__': + parser = argparse.ArgumentParser(description=__doc__) + parser.add_argument('--hyperv-release', type=Path) + args = parser.parse_args() + test_hyperv(args.hyperv_release) if args.hyperv_release else test_rizin()