From 71f5ad44548832d95d756de983c655c992462122 Mon Sep 17 00:00:00 2001 From: Claude Date: Mon, 31 Aug 2026 03:51:31 +0000 Subject: [PATCH 1/2] ci: derive the framework ref from the catalog instead of a typed default Three pins name the framework and none of them were related to each other. The catalog declares v0.7.0 in .framework-ref. publish.yml's framework_ref input carried a hardcoded default that sat at v0.4.0 across three releases before it was corrected. generated-only.yml checked out the framework with no ref at all -- floating main. So verify has been checking a fixed, published tree against a *moving* emitter while publish builds from a pinned one. They agree today only because chock main happens to be two CI-only commits past v0.7.0. The next merge that changes emitter output turns this check red on a tree nobody touched, and the failure would read as tampering in the one repository whose entire guarantee is "this content is what the catalog published". Both workflows now derive the ref from chock-catalog/.framework-ref: - generated-only checks out the catalog first, reads .framework-ref into a step output, and checks out the framework at that ref. Verify now reproduces exactly what a publish from that catalog ref would produce. - publish keeps the dispatch input -- a human overriding it is the reason it exists -- but its default is the sentinel `auto`, resolved in a step from the catalog ref being published. A sentinel rather than an empty string so the resolved value is visible in the run log and a blank input resolves the same way. The commit message the publish writes now records the resolved ref rather than the literal input. The comment that failed is replaced rather than restated. It said "Bump this default with each framework release" and was skipped three times; prose does not execute. The new one says what the value means -- the emitter version whose output *is* this repository's content -- and the mechanism now makes the correct value the default. The dry run was hiding every added file. `git --no-pager diff --stat` reports tracked files only, so a publish's additions are invisible and a human reading it to decide whether to publish is shown only what disappears. On the v0.7.0 publish that printed "15 files changed, 14 insertions(+), 2450 deletions(-)" for a rename into a larger file; the real change was 5677 insertions. `git add -A -N .` first, which records paths without staging content, so the worktree and the later commit are unchanged. The Publish step's own gate had the same defect with a worse consequence: on a build whose only change is added files, `git diff --quiet` is clean, so it printed "No change to publish." and exited 0 having published nothing. Reproduced locally -- exit 0 with an untracked plugin directory present, exit 1 once intent-to-add is recorded. Staged there too. Alongside, three hand-maintained files that are hygiene on their own merits and happen to be what a public plugin catalogue asks for: - SECURITY.md, which routes a report to where it can be fixed. This repository is compiled output, so a fix landed here is overwritten by the next publish: emitter and packaging defects go to chock, policy content to chock-catalog, and only distribution-integrity findings are genuinely this repository's. No SLA and no PGP key are invented; it points at chock's advisory route and its timelines. - assets/icon.svg, the org mark from the brand family, unchanged except for explicit width and height so "512x512" is unambiguous to a consumer that does not read viewBox. - .github/workflows/hol-plugin-scanner.yml, running the HOL AI plugin scanner on push and pull_request as a report, not a gate. The score thresholds stay at the action's non-failing defaults deliberately: the scanner flags two of these plugins HIGH for quoting the patterns they block, and a gate that fails on a guardrail describing a guardrail only teaches people to route around it. submission_enabled is pinned false explicitly rather than left to the default, because that input can open issues in third-party catalogue repositories. The action is pinned by commit, not by the `v1` tag its docs give: `v1` has been re-pointed across 560 releases, so `@v1` lets the code running here change without a diff in this repository. None of this touches generated content. Verified per repository by mirroring the generated-only job locally -- dist, catalog and framework as siblings, pip install ./framework, the repository's own build lines -- and confirming `git diff --exit-code` and `git status --porcelain` are both silent, before and after. actionlint and zizmor --offline --min-severity low are clean. Signed-off-by: Claude --- .github/workflows/generated-only.yml | 36 ++++++++++--- .github/workflows/hol-plugin-scanner.yml | 47 +++++++++++++++++ .github/workflows/publish.yml | 67 +++++++++++++++++++----- SECURITY.md | 62 ++++++++++++++++++++++ assets/icon.svg | 7 +++ 5 files changed, 201 insertions(+), 18 deletions(-) create mode 100644 .github/workflows/hol-plugin-scanner.yml create mode 100644 SECURITY.md create mode 100644 assets/icon.svg diff --git a/.github/workflows/generated-only.yml b/.github/workflows/generated-only.yml index 57da5dc..ed7cd47 100644 --- a/.github/workflows/generated-only.yml +++ b/.github/workflows/generated-only.yml @@ -24,19 +24,43 @@ jobs: persist-credentials: false path: dist - - name: Check out the framework + # The catalog is checked out first because it names the framework. `.framework-ref` + # is the emitter version this catalog's published output is defined against, so the + # only build that can prove this tree came from the catalog is a build with that + # framework. Checking out `main` here instead -- which is what this did -- compares a + # fixed tree against a moving emitter: the next merge that changes emitter output + # turns this check red on a tree nobody touched, while `publish` keeps building from + # a pinned ref. Deriving both from one file is what keeps verify and publish honest. + - name: Check out the catalog uses: actions/checkout@08c6903cd8c0fde910a37f88322edcfb5dd907a8 # v5.0.0 with: persist-credentials: false - repository: open-coder-ai/chock - path: framework + repository: open-coder-ai/chock-catalog + path: catalog - - name: Check out the catalog + - name: Read the framework ref the catalog declares + id: framework + working-directory: catalog + run: | + if [ ! -f .framework-ref ]; then + echo "::error::chock-catalog has no .framework-ref, so there is no declared framework to verify this tree against." + exit 1 + fi + ref="$(tr -d '[:space:]' < .framework-ref)" + if [ -z "$ref" ]; then + echo "::error::chock-catalog/.framework-ref is empty, so there is no declared framework to verify this tree against." + exit 1 + fi + echo "ref=$ref" >> "$GITHUB_OUTPUT" + echo "Verifying against framework $ref, from chock-catalog/.framework-ref." + + - name: Check out the framework at the ref the catalog declares uses: actions/checkout@08c6903cd8c0fde910a37f88322edcfb5dd907a8 # v5.0.0 with: persist-credentials: false - repository: open-coder-ai/chock-catalog - path: catalog + repository: open-coder-ai/chock + ref: ${{ steps.framework.outputs.ref }} + path: framework - uses: actions/setup-python@a26af69be951a213d495a4c3e4e4022e16d87065 # v5 with: diff --git a/.github/workflows/hol-plugin-scanner.yml b/.github/workflows/hol-plugin-scanner.yml new file mode 100644 index 0000000..618c2cb --- /dev/null +++ b/.github/workflows/hol-plugin-scanner.yml @@ -0,0 +1,47 @@ +name: HOL plugin scanner + +# Runs hashgraph-online's AI plugin scanner over this repository's published plugins. +# +# It is a report, not a gate, and that is deliberate rather than provisional: `min_score` +# and `fail_on_severity` are left at the action's non-failing defaults because the scanner +# cannot distinguish a guardrail from the attack it names. Two of this repository's plugins +# are flagged HIGH for quoting the very patterns they block -- `block(remote_exec): fetch +# (curl|wget) piped into an interpreter` is the literal text of a *block* rule, scored as a +# remote-code-execution pattern. Arming a gate on that would train people to route around +# it. What the run is worth is the rest: manifest, metadata, licence and hygiene signals +# across every published plugin, on every push and pull request, in one place a reviewer +# can read. +# +# `submission_enabled` is pinned to false explicitly, not left to the default: this action +# can open submission issues in third-party catalogue repositories, and publishing a score +# for a security product is a decision the owner makes deliberately, never a side effect of +# a CI run. + +on: + push: + branches: [main] + pull_request: + +permissions: + contents: read + +jobs: + scan: + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@08c6903cd8c0fde910a37f88322edcfb5dd907a8 # v5.0.0 + with: + persist-credentials: false + + - name: Scan the published plugins + # Pinned by commit rather than by the `v1` tag their docs give: `v1` is a moving + # tag that has been re-pointed across 560 releases, so `@v1` is a promise that the + # code running here can change without a diff in this repository. The SHA below is + # what `v1` resolved to when this was added. + uses: hashgraph-online/ai-plugin-scanner-action@f344c80c093464413432b399f38a7ba2f3365ae2 # v1.2.560 + with: + mode: scan + plugin_dir: . + format: text + write_step_summary: true + submission_enabled: false diff --git a/.github/workflows/publish.yml b/.github/workflows/publish.yml index 1460754..7f90511 100644 --- a/.github/workflows/publish.yml +++ b/.github/workflows/publish.yml @@ -12,15 +12,20 @@ on: type: string default: main framework_ref: - # A tag is the right default -- a release should be reproducible from two pinned - # refs. This names the framework the committed tree was last built with, so a - # dispatch that accepts the defaults reproduces what is published rather than - # rewriting it. It sat at v0.4.0 across three releases while the trees moved to - # v0.5.0, v0.6.0 and v0.7.0, which made the safe-looking default the one that - # would have rewritten every package. Bump it in the same change that republishes. - description: Framework ref to build with + # The framework the committed tree was built with: the emitter version whose output + # *is* this repository's content. Getting it wrong does not produce a stale build, + # it rewrites every published package against a different emitter. + # + # `auto` reads that value from the catalog ref being published, out of its + # `.framework-ref` file, so the default is right by construction. A literal default + # was tried and failed: it sat at v0.4.0 across three releases while the trees moved + # to v0.5.0, v0.6.0 and v0.7.0, because the comment asking a human to bump it does + # not execute. A sentinel is used rather than an empty default so that the resolved + # value is visible in the run log and an accidental blank still resolves the same + # way. An explicit tag, branch or SHA overrides it, which is why the input remains. + description: Framework ref to build with ("auto" = the ref chock-catalog declares) type: string - default: v0.7.0 + default: auto dry_run: description: Build and show the diff without pushing type: boolean @@ -49,12 +54,37 @@ jobs: ref: ${{ inputs.catalog_ref }} path: catalog - - name: Check out the framework + - name: Resolve the framework ref + id: framework + working-directory: catalog + env: + # Dispatch inputs are typed by whoever runs the workflow; expanded by the runner + # into shell text they would execute, through the environment they stay data. + FRAMEWORK_REF_INPUT: ${{ inputs.framework_ref }} + run: | + ref="$FRAMEWORK_REF_INPUT" + origin="the framework_ref dispatch input" + if [ -z "$ref" ] || [ "$ref" = auto ]; then + if [ ! -f .framework-ref ]; then + echo "::error::framework_ref is \"auto\" but the catalog ref being published has no .framework-ref; name a framework ref explicitly." + exit 1 + fi + ref="$(tr -d '[:space:]' < .framework-ref)" + origin="chock-catalog/.framework-ref at the catalog ref being published" + fi + if [ -z "$ref" ]; then + echo "::error::The framework ref resolved to nothing; name a framework ref explicitly." + exit 1 + fi + echo "ref=$ref" >> "$GITHUB_OUTPUT" + echo "Building with framework $ref, from $origin." + + - name: Check out the framework at the resolved ref uses: actions/checkout@08c6903cd8c0fde910a37f88322edcfb5dd907a8 # v5.0.0 with: persist-credentials: false repository: open-coder-ai/chock - ref: ${{ inputs.framework_ref }} + ref: ${{ steps.framework.outputs.ref }} path: framework - uses: actions/setup-python@a26af69be951a213d495a4c3e4e4022e16d87065 # v5 @@ -80,17 +110,30 @@ jobs: - name: Show what would change working-directory: dist - run: git --no-pager diff --stat + run: | + # Record intent-to-add first. `git diff` reports tracked files only, so without + # this every *added* file is invisible and a human reading the dry run to decide + # whether to publish is shown only what disappears. The v0.7.0 dry run printed + # "15 files changed, 14 insertions(+), 2450 deletions(-)" for what was a rename + # into a larger file; staged, the same build reads 5677 insertions(+), 14 + # deletions(-). `-N` records the paths without staging content, so the worktree + # is untouched and the commit below behaves exactly as it did. + git add -A -N . + git --no-pager diff --stat - name: Publish if: ${{ inputs.dry_run == false }} working-directory: dist env: CATALOG_REF: ${{ inputs.catalog_ref }} - FRAMEWORK_REF: ${{ inputs.framework_ref }} + FRAMEWORK_REF: ${{ steps.framework.outputs.ref }} run: | git config user.name "github-actions[bot]" git config user.email "41898282+github-actions[bot]@users.noreply.github.com" + # Same blindness as the dry run, with a worse consequence: on a publish whose + # only change is added files, an unstaged `git diff --quiet` is clean and this + # exits 0 reporting "No change to publish" while publishing nothing. + git add -A -N . if git diff --quiet; then echo "No change to publish." exit 0 diff --git a/SECURITY.md b/SECURITY.md new file mode 100644 index 0000000..616f940 --- /dev/null +++ b/SECURITY.md @@ -0,0 +1,62 @@ +# Security Policy + +## What lives here, and what that means for a report + +`chock-cursor-plugins` is **compiled output**. Every file under `cursor/`, along with the +marketplace index, `chock-market.lock` and `PLUGINS.md`, is generated from policy sources in +[chock-catalog](https://github.com/open-coder-ai/chock-catalog) by +[chock](https://github.com/open-coder-ai/chock), and the +[Generated-only](.github/workflows/generated-only.yml) check regenerates the tree on every +push and pull request and fails on any difference. There is no hand-written code in this +repository to hold a vulnerability of its own — a defect visible here was introduced either +in the emitter or in the policy it emitted, so a fix landed here would be overwritten by the +next publish. **Report it where it can actually be fixed:** + +| What you found | Where it belongs | +|---|---| +| A defect in a guard script, hook wiring, plugin manifest, or anything about how policies are compiled into plugins | [open-coder-ai/chock](https://github.com/open-coder-ai/chock) — see its [SECURITY.md](https://github.com/open-coder-ai/chock/blob/main/SECURITY.md) | +| A defect in **policy content**: a guard that does not match what it claims to block, a pattern that can be trivially evaded, a policy whose description overstates its enforcement | [open-coder-ai/chock-catalog](https://github.com/open-coder-ai/chock-catalog) | +| This repository's tree does not match a rebuild from the catalog — i.e. something here was not published by the catalog | [chock](https://github.com/open-coder-ai/chock)'s private advisory route, as a supply-chain report against this repository | +| A defect in this repository's own workflows (`.github/workflows/`) | [chock](https://github.com/open-coder-ai/chock)'s private advisory route, naming this repository | + +The last two are the only categories that are genuinely *this* repository's, and both are +about distribution integrity rather than about policy behaviour. + +## Reporting a vulnerability + +Use chock's private advisory route: +. Do **not** open a public +issue for an exploitable finding, here or upstream. Include the affected path, how to +reproduce it, and the impact. Acknowledgement and assessment follow the timelines stated in +[chock's SECURITY.md](https://github.com/open-coder-ai/chock/blob/main/SECURITY.md); this +repository does not set its own, and there is no PGP key — GitHub's advisory form is the +private channel. + +Pull requests are closed here automatically with a pointer to the catalog. That applies to +security fixes too: a patch to a generated file cannot survive the next publish. + +## Verifying what you installed + +Two things are checkable without trusting this repository's README: + +- **Every published plugin directory is hashed in `chock-market.lock`** (sha256 per + directory), so a plugin's content can be compared against what the index claims. +- **The tree is reproducible.** Check out this repository, the catalog and chock as + siblings, install chock from source, and run the same two build commands the + [Generated-only](.github/workflows/generated-only.yml) workflow runs. `git diff` and + `git status --porcelain` should both be silent. That workflow derives the framework + version from the catalog's own `.framework-ref`, so a rebuild from the catalog ref you + care about uses the emitter that catalog declares rather than whatever is on a branch. + +## What these plugins do not promise + +Stated here rather than left to the README, because a security file that omits it is +claiming more than the product does: + +- A hook is enforcement **only where the host runs it**. Each plugin's description states + its own fail posture, and several fail **open** — if the hook cannot run, the command is + allowed. That is a property of the host agent, not a bug in the plugin. +- Skills and ambient rules are **advisory** in every client. They are text the model reads. +- Repository-level enforcement — git hooks and a CI gate, which apply with no agent running + — is not part of an installed plugin. It comes from `chock sync` in the target + repository. diff --git a/assets/icon.svg b/assets/icon.svg new file mode 100644 index 0000000..99e2c16 --- /dev/null +++ b/assets/icon.svg @@ -0,0 +1,7 @@ + + + + + + \ No newline at end of file From fdf9a388a49d5c607a3e04cf4a7dd3828ee7c5fd Mon Sep 17 00:00:00 2001 From: Claude Date: Tue, 1 Sep 2026 02:15:53 +0000 Subject: [PATCH 2/2] ci: drop the HOL plugin scanner workflow MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Owner decision 2026-09-01: the scanner's unique value is a semantic check on shipped plugin content independent of the reproduction check, which only matters once the catalog takes external contributions or has real adopters. Today it would run never-exercised third-party code and carry two permanent HIGH false positives (policies quoting the patterns they block) — standing noise that teaches readers to ignore the report. SECURITY.md, the icon, and the framework-ref derivation stay. Signed-off-by: Claude --- .github/workflows/hol-plugin-scanner.yml | 47 ------------------------ 1 file changed, 47 deletions(-) delete mode 100644 .github/workflows/hol-plugin-scanner.yml diff --git a/.github/workflows/hol-plugin-scanner.yml b/.github/workflows/hol-plugin-scanner.yml deleted file mode 100644 index 618c2cb..0000000 --- a/.github/workflows/hol-plugin-scanner.yml +++ /dev/null @@ -1,47 +0,0 @@ -name: HOL plugin scanner - -# Runs hashgraph-online's AI plugin scanner over this repository's published plugins. -# -# It is a report, not a gate, and that is deliberate rather than provisional: `min_score` -# and `fail_on_severity` are left at the action's non-failing defaults because the scanner -# cannot distinguish a guardrail from the attack it names. Two of this repository's plugins -# are flagged HIGH for quoting the very patterns they block -- `block(remote_exec): fetch -# (curl|wget) piped into an interpreter` is the literal text of a *block* rule, scored as a -# remote-code-execution pattern. Arming a gate on that would train people to route around -# it. What the run is worth is the rest: manifest, metadata, licence and hygiene signals -# across every published plugin, on every push and pull request, in one place a reviewer -# can read. -# -# `submission_enabled` is pinned to false explicitly, not left to the default: this action -# can open submission issues in third-party catalogue repositories, and publishing a score -# for a security product is a decision the owner makes deliberately, never a side effect of -# a CI run. - -on: - push: - branches: [main] - pull_request: - -permissions: - contents: read - -jobs: - scan: - runs-on: ubuntu-latest - steps: - - uses: actions/checkout@08c6903cd8c0fde910a37f88322edcfb5dd907a8 # v5.0.0 - with: - persist-credentials: false - - - name: Scan the published plugins - # Pinned by commit rather than by the `v1` tag their docs give: `v1` is a moving - # tag that has been re-pointed across 560 releases, so `@v1` is a promise that the - # code running here can change without a diff in this repository. The SHA below is - # what `v1` resolved to when this was added. - uses: hashgraph-online/ai-plugin-scanner-action@f344c80c093464413432b399f38a7ba2f3365ae2 # v1.2.560 - with: - mode: scan - plugin_dir: . - format: text - write_step_summary: true - submission_enabled: false