Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
6 changes: 6 additions & 0 deletions .github/workflows/agentic-marketplace.yml
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,10 @@ on:
description: 'Version for releases (default: YYYY.MM.DD)'
default: ''
type: string
sign-files:
description: 'Sign generated marketplace files with Sigstore attestation'
default: true
type: boolean
secrets:
token:
description: 'GitHub token for read-only operations'
Expand Down Expand Up @@ -83,6 +87,7 @@ jobs:
permissions:
contents: write
pull-requests: write
id-token: write
steps:
- name: Checkout code
uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4.2.2
Expand All @@ -99,6 +104,7 @@ jobs:
uses: bitcomplete/bc-github-actions/agentic-marketplace/publish@v1
with:
github-token: ${{ secrets.pat }}
sign-files: ${{ inputs.sign-files }}
auto-merge: ${{ inputs.auto-merge }}
create-opencode-release: ${{ inputs.create-opencode-release }}
release-version: ${{ inputs.release-version }}
31 changes: 31 additions & 0 deletions .github/workflows/test.yml
Original file line number Diff line number Diff line change
Expand Up @@ -101,3 +101,34 @@ jobs:
echo "ERROR: marketplace.json not generated"
exit 1
fi

test-sign:
runs-on: ubuntu-latest
permissions:
contents: read
id-token: write
steps:
- name: Checkout code
uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4.2.2

- name: Generate marketplace files
working-directory: test-fixtures/valid
run: ../../scripts/dist/discover-components.cjs generate

- name: Sign marketplace files
uses: always-further/agent-sign@8d70fb7bdcdfb921a1613d0701ca44c7d6c34e6f # v0.0.8
with:
files: 'test-fixtures/valid/.claude-plugin/marketplace.json'
per-file: 'true'
commit: 'false'
verify: 'false'

- name: Verify bundle exists
run: |
if [ -f "test-fixtures/valid/.claude-plugin/marketplace.json.bundle" ]; then
echo "✓ Sigstore attestation bundle created"
else
echo "ERROR: No bundle file found"
find test-fixtures/valid -name "*.bundle" 2>/dev/null
exit 1
fi
19 changes: 15 additions & 4 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,7 @@ Automates Claude Code plugin marketplace management through auto-discovery, vali
- Auto-discovery of plugins and components
- Structure and naming validation
- Automatic marketplace.json generation
- Sigstore attestation signing for supply chain security
- PR-based workflow with optional auto-merge

[View agentic-marketplace action documentation →](agentic-marketplace/README.md)
Expand All @@ -25,8 +26,9 @@ graph LR
A[Push to main] --> B[Discover]
B --> C[Validate]
C --> D[Generate]
D --> E[Create PR]
E --> F[Auto-merge]
D --> E[Sign]
E --> F[Create PR]
F --> G[Auto-merge]

B -->|Finds| B1[Plugins]
B -->|Finds| B2[Commands]
Expand All @@ -38,15 +40,18 @@ graph LR

D -->|Updates| D1[marketplace.json]
D -->|Updates| D2[Component files]

E -->|Creates| E1[.bundle attestation]
```

**How it works:**

1. **Discover** - Scans your repository for plugins, commands, skills, and other components based on your configuration
2. **Validate** - Checks that all components follow naming conventions, have required metadata, and match your validation rules
3. **Generate** - Creates or updates marketplace.json with all discovered components
4. **Create PR** - Opens a pull request with the changes for review
5. **Auto-merge** - Optionally merges the PR automatically if validation passes
4. **Sign** - Creates a Sigstore attestation bundle for marketplace.json, proving it was built in CI from this repository
5. **Create PR** - Opens a pull request with the changes for review
6. **Auto-merge** - Optionally merges the PR automatically if validation passes

This happens automatically on every push to your main branch. No manual JSON editing required.

Expand All @@ -65,13 +70,19 @@ on:
pull_request:
branches: [main]

permissions:
contents: write
pull-requests: write
id-token: write # Required for Sigstore attestation signing

jobs:
update:
uses: bitcomplete/bc-github-actions/.github/workflows/agentic-marketplace.yml@v1
with:
config-path: .claude-plugin/generator.config.toml
secrets:
token: ${{ secrets.GITHUB_TOKEN }}
pat: ${{ secrets.PAT }}
```

Or use individual actions in your own workflow:
Expand Down
129 changes: 127 additions & 2 deletions agentic-marketplace/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -4,11 +4,12 @@ Automates Claude Code plugin marketplace management through auto-discovery, vali

## Overview

The agentic-marketplace action provides three composable actions that work together to manage your Claude Code plugin marketplace:
The agentic-marketplace action provides composable actions that work together to manage your Claude Code plugin marketplace:

1. **discover** - Finds plugins, commands, agents, skills, hooks, and MCP servers
2. **validate** - Validates component structure, naming, and metadata
3. **generate** - Creates marketplace.json and plugin.json files, opens PR with auto-merge
3. **generate** - Creates marketplace.json and plugin.json files
4. **publish** - Signs generated files with Sigstore attestation and opens a PR with auto-merge

## Quick Start

Expand All @@ -25,13 +26,19 @@ on:
pull_request:
branches: [main]

permissions:
contents: write
pull-requests: write
id-token: write # Required for Sigstore attestation signing

jobs:
update:
uses: bitcomplete/bc-github-actions/.github/workflows/agentic-marketplace.yml@v1
with:
config-path: .claude-plugin/generator.config.toml
secrets:
token: ${{ secrets.GITHUB_TOKEN }}
pat: ${{ secrets.PAT }}
```

### Using Individual Actions
Expand Down Expand Up @@ -144,6 +151,7 @@ Generates marketplace.json and plugin.json files, then creates a pull request wi

**Generated files:**
- `.claude-plugin/marketplace.json` - Marketplace manifest with all plugins and components
- `.claude-plugin/marketplace.json.bundle` - Sigstore attestation bundle (when signing is enabled)
- `category/plugin-name/.claude-plugin/plugin.json` - Individual plugin metadata

**Example:**
Expand Down Expand Up @@ -201,6 +209,7 @@ The marketplace action expects this structure:
your-marketplace/
├── .claude-plugin/
│ ├── marketplace.json # Generated automatically
│ ├── marketplace.json.bundle # Sigstore attestation (generated)
│ └── generator.config.toml # Your configuration
├── .github/
│ └── workflows/
Expand Down Expand Up @@ -258,6 +267,92 @@ The generate action creates or updates marketplace files:
4. If `auto-merge: true`, enables auto-merge on the PR
5. PR auto-merges when CI checks pass

### Signing and Attestation

The publish action automatically signs generated marketplace files using [Sigstore](https://sigstore.dev) keyless attestation via the [agent-sign](https://github.com/always-further/agent-sign) action. This creates a cryptographic proof that the file was built in your CI pipeline from your repository — not modified after the fact.

**What gets signed:**

- `.claude-plugin/marketplace.json` is signed, producing a `.claude-plugin/marketplace.json.bundle` sidecar file
- The `.bundle` file is included in the auto-generated PR alongside the marketplace manifest

**How signing works:**

1. GitHub Actions mints a short-lived OIDC token identifying the workflow, repository, and branch
2. Sigstore's Fulcio CA issues an ephemeral certificate binding that identity to a signing key
3. The marketplace file is signed with that key, producing a [DSSE envelope](https://github.com/secure-systems-lab/dsse) with an [in-toto](https://in-toto.io/) statement
4. The signature is logged in Sigstore's [Rekor](https://docs.sigstore.dev/logging/overview/) transparency log
5. The resulting `.bundle` file contains the signature, certificate, and log inclusion proof — everything needed for offline verification

No private keys to manage. Identity comes from the CI environment itself.

**How users verify signatures:**

Install the [nono CLI](https://github.com/always-further/nono), then verify against a trust policy:

```bash
nono trust verify --policy trust-policy.json --all
```

A trust policy defines who is allowed to sign which files. Example `trust-policy.json`:

```json
{
"version": 1,
"includes": [".claude-plugin/marketplace.json"],
"publishers": [
{
"name": "marketplace CI",
"issuer": "https://token.actions.githubusercontent.com",
"repository": "your-org/your-marketplace",
"workflow": ".github/workflows/agentic-marketplace.yml",
"ref_pattern": "refs/heads/main"
}
],
"enforcement": "deny"
}
```

Verification checks four things:

1. **Certificate chain** — the signing certificate was issued by Sigstore's CA
2. **Transparency log** — the signature was recorded in Rekor within the certificate's validity window
3. **Signature validity** — the ECDSA signature over the file content is authentic
4. **Publisher identity** — the OIDC claims in the certificate (repository, workflow, branch) match the trust policy

If any check fails, verification fails. With `"enforcement": "deny"`, files matching the `includes` patterns are rejected unless they have a valid signature from a trusted publisher.

**What this protects against:**

- **Tampered marketplace files** — if someone modifies marketplace.json after it was generated in CI, the signature won't match
- **Unauthorized publishing** — only the configured workflow in the configured repository can produce valid signatures
- **Replay attacks** — signatures are timestamped via the transparency log; stale signatures can be rejected
- **Key compromise** — there are no long-lived keys to steal; signing keys are ephemeral and exist only during the CI run

**Runtime enforcement with nono:**

For stronger guarantees, run agents through `nono run` which verifies signatures at the kernel level before the agent can read instruction files:

```bash
nono run --profile claude-code -- claude
```

This prevents time-of-check/time-of-use (TOCTOU) attacks where files are swapped between verification and read.

**Disabling signing:**

Signing is on by default. To disable it:

```yaml
jobs:
update:
uses: bitcomplete/bc-github-actions/.github/workflows/agentic-marketplace.yml@v1
with:
sign-files: false
```

When disabled, no `.bundle` files are created and the `id-token: write` permission is unused.

## Troubleshooting

### No components discovered
Expand Down Expand Up @@ -337,3 +432,33 @@ Use validation output to implement custom logic:
## Examples

See the [main README](../README.md) for complete workflow examples and diagrams.

## FAQ

### Do I need nono to use this workflow?

No. Signing is included by default, but the `.bundle` sidecar files are inert — they sit alongside your marketplace.json and are ignored by everything except nono verification tooling. Claude Code, plugin consumers, and your existing tooling won't read or care about `.bundle` files. You get supply chain security for free if you ever decide to verify later, and zero impact if you don't.

### Will the `.bundle` files break anything?

No. A `.bundle` file is a standalone JSON file containing the Sigstore attestation. It doesn't modify marketplace.json or any other file. Tools that don't know about it will ignore it. It's no different from having a `.gitignore` or `LICENSE` file in the directory — present but harmless.

### Should I add `.bundle` files to `.gitignore`?

No. The `.bundle` files need to be committed alongside the files they attest to. Verification works by comparing the `.bundle` against the file it signs, so both must be present in the repository. If you `.gitignore` them, you lose the ability to verify later.

### Do I need the `id-token: write` permission if I'm not using nono?

The workflow requests this permission to obtain the OIDC token used for keyless signing. It's harmless — the token is scoped to Sigstore's Fulcio CA and can only be used to request a signing certificate. If you disable signing with `sign-files: false`, the permission is unused but still safe to include.

### Can I start verifying signatures later?

Yes. Every `.bundle` file committed to your repository is independently verifiable at any point in the future. The Rekor transparency log entry is permanent. You can adopt nono verification whenever you're ready — the signatures will already be there.

### When should I disable signing?

Most users should leave signing enabled. Reasons to disable:

- Your CI environment doesn't support OIDC tokens (self-hosted runners without OIDC configured)
- You have strict policies against external network calls during CI (Sigstore requires reaching Fulcio and Rekor)
- You're running in an air-gapped environment
15 changes: 15 additions & 0 deletions agentic-marketplace/publish/action.yml
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,10 @@ inputs:
github-token:
description: 'GitHub token for creating PRs'
required: true
sign-files:
description: 'Sign generated marketplace files with Sigstore attestation'
required: false
default: 'true'
auto-merge:
description: 'Enable auto-merge for generated PR'
required: false
Expand Down Expand Up @@ -33,6 +37,16 @@ outputs:
runs:
using: 'composite'
steps:
- name: Sign marketplace files
if: ${{ inputs.sign-files == 'true' }}
uses: always-further/agent-sign@8d70fb7bdcdfb921a1613d0701ca44c7d6c34e6f # v0.0.8
with:
files: '.claude-plugin/marketplace.json'
per-file: 'true'
commit: 'false'
upload-artifacts: 'false'
verify: 'false'

- name: Create Pull Request
id: create-pr
uses: peter-evans/create-pull-request@c5a7806660adbe173f04e3e038b0ccdcd758773c # v6.1.0
Expand All @@ -56,6 +70,7 @@ runs:
**Generated files:**
- `.claude-plugin/marketplace.json` - marketplace manifest with unique source paths per plugin
- `category/plugin-name/.claude-plugin/plugin.json` - individual plugin metadata
- `.claude-plugin/marketplace.json.bundle` - Sigstore attestation bundle (when signing is enabled)

**This PR will auto-merge after CI checks pass.**
branch: auto-update-marketplace
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,7 @@ on:
permissions:
contents: write
pull-requests: write
id-token: write

jobs:
update:
Expand Down
Loading