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/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