Skip to content

feat: plugins doble sing#64

Merged
yacosta738 merged 6 commits into
mainfrom
feature/plugins-doble-sing
Feb 22, 2026
Merged

feat: plugins doble sing#64
yacosta738 merged 6 commits into
mainfrom
feature/plugins-doble-sing

Conversation

@yacosta738
Copy link
Copy Markdown
Contributor

@yacosta738 yacosta738 commented Feb 21, 2026

This pull request updates the plugin publishing workflow to exclusively use keyless OIDC-based signing with cosign, removing support for key-based signing. Documentation and code have been updated to reflect this change, and related secrets and logic for key-based signing have been removed. The manifest and catalog formats are also updated to match the new signing approach.

Workflow and Signing Process Updates:

  • The GitHub Actions workflow (publish-plugins.yml) now only supports keyless OIDC signing with cosign, removing all handling and secrets related to key-based signing. The signing and verification steps have been simplified accordingly. [1] [2] [3] [4]
  • The signature entry in plugin manifests and the catalog is now always structured for keyless signing, with both url and certificate_url fields set unconditionally. [1] [2] [3]

Documentation Updates:

  • All documentation and workflow descriptions have been updated to clarify that only keyless OIDC signing is supported; references to key-based signing and related secrets have been removed. [1] [2] [3] [4]

Other Improvements:

  • The semver pattern used for plugin version validation and tag parsing in the workflow has been refactored for consistency and maintainability. [1] [2]

Summary by CodeRabbit

  • New Features

    • Plugin installation now verifies artifact signatures and stores signature/certificate metadata.
    • Plugin source configuration can include identity regexes for signature policy.
  • Documentation

    • Guides updated to consistently describe cosign keyless OIDC signing and removed key-based references.
  • Chores

    • Publishing workflow standardized to keyless OIDC signing, enforces certificate presence and tighter validation.
    • Example manifests and catalogs cleaned to reflect new signing flow.

Add tag-driven plugin release automation with immutable artifact paths, signing, and catalog updates so plugin releases deploy consistently without per-plugin workflow changes.
@cloudflare-workers-and-pages
Copy link
Copy Markdown

cloudflare-workers-and-pages Bot commented Feb 21, 2026

Deploying corvus with  Cloudflare Pages  Cloudflare Pages

Latest commit: 19b89e8
Status: ✅  Deploy successful!
Preview URL: https://a15487b9.corvus-42x.pages.dev
Branch Preview URL: https://feature-plugins-doble-sing.corvus-42x.pages.dev

View logs

@coderabbitai
Copy link
Copy Markdown
Contributor

coderabbitai Bot commented Feb 21, 2026

Warning

Rate limit exceeded

@yacosta738 has exceeded the limit for the number of commits that can be reviewed per hour. Please wait 9 minutes and 53 seconds before requesting another review.

⌛ How to resolve this issue?

After the wait time has elapsed, a review can be triggered using the @coderabbitai review command as a PR comment. Alternatively, push new commits to this PR.

We recommend that you space out your commits to avoid hitting the rate limit.

🚦 How do rate limits work?

CodeRabbit enforces hourly rate limits for each developer per organization.

Our paid plans have higher rate limits than the trial, open-source and free plans. In all cases, we re-allow further reviews after a brief timeout.

Please see our FAQ for further information.

📝 Walkthrough

Walkthrough

Refactors the plugin publish workflow to enforce cosign keyless OIDC signing, adds plugin signature types and policy-driven verification in the agent runtime, updates configuration to accept optional identity regexes, adjusts docs, and removes signature blocks from some published plugin manifests.

Changes

Cohort / File(s) Summary
Workflow Signing Refinement
.github/workflows/publish-plugins.yml, .github/workflows/README.md
Enforces keyless OIDC signing (updated step label), centralizes semver regex, always emits signature+certificate, removes COSIGN_PRIVATE_KEY branching, and upgrades cosign version.
Agent Runtime — Signature & Policy
clients/agent-runtime/src/plugins/mod.rs, clients/agent-runtime/src/config/mod.rs, clients/agent-runtime/src/config/schema.rs
Adds PluginSignature (url, certificate_url), extends PluginManifest/LockedPlugin with signature and source identity fields, introduces SignaturePolicy resolution, verification flows (online/local) using cosign, manifest asset source validation, secure write helpers, and tests.
Docs & Readme
clients/web/apps/docs/src/content/docs/en/guides/plugins.md, clients/web/apps/docs/src/content/docs/es/guides/plugins.md, clients/web/apps/plugins/README.md
Updates signing documentation to describe keyless OIDC-only signing and removes references to key-based COSIGN_PRIVATE_KEY secret.
Published Catalog/Artifacts
clients/web/apps/plugins/public/artifacts/memory.surreal.graphs/0.1.0/plugin-manifest.json, clients/web/apps/plugins/public/catalog.json
Removes signature blocks from published plugin manifest and catalog entries (deleted signature metadata).

Sequence Diagram

sequenceDiagram
    participant Agent as Agent Runtime
    participant Manifest as Manifest Source
    participant Cosign as Cosign Verifier
    participant Storage as Local Storage

    Agent->>Manifest: Fetch plugin manifest & artifact
    Manifest-->>Agent: Return manifest (may include signature metadata)

    Agent->>Agent: Determine SignaturePolicy for source/plugin

    alt Signature required (keyless OIDC)
        Agent->>Manifest: Fetch signature bytes & certificate
        Manifest-->>Agent: Signature + certificate

        Agent->>Cosign: verify_blob_with_cosign(artifact, signature, certificate)
        Cosign-->>Agent: Verification result

        alt Verification succeeds
            Agent->>Storage: Persist artifact, signature, certificate
            Storage-->>Agent: Persisted
        else Verification fails
            Agent-->>Agent: Abort installation
        end
    else No signature required
        Agent->>Storage: Persist artifact only
    end
Loading

Estimated code review effort

🎯 4 (Complex) | ⏱️ ~45 minutes

Possibly related PRs

Suggested labels

codex

🚥 Pre-merge checks | ✅ 1 | ❌ 2

❌ Failed checks (1 warning, 1 inconclusive)

Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 35.00% which is insufficient. The required threshold is 80.00%. Write docstrings for the functions missing them to satisfy the coverage threshold.
Title check ❓ Inconclusive The PR title 'feat: plugins doble sing' is vague and contains an apparent typo ('doble' instead of 'double'), making it unclear what the feature accomplishes despite the substantial changes in the PR. Revise the title to be more descriptive and specific, such as 'feat: switch plugin signing to keyless OIDC only' or 'feat: migrate plugins to keyless OIDC signing'.
✅ Passed checks (1 passed)
Check name Status Explanation
Description check ✅ Passed The PR description is comprehensive and well-structured, covering workflow updates, manifest/catalog changes, documentation updates, and improvements. It clearly explains the motivation and scope of changes.

✏️ Tip: You can configure your own custom pre-merge checks in the settings.

✨ Finishing Touches
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Post copyable unit tests in a comment
  • Commit unit tests in branch feature/plugins-doble-sing

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands and usage tips.

@cloudflare-workers-and-pages
Copy link
Copy Markdown

cloudflare-workers-and-pages Bot commented Feb 21, 2026

Deploying corvus-plugins with  Cloudflare Pages  Cloudflare Pages

Latest commit: 19b89e8
Status: ✅  Deploy successful!
Preview URL: https://c4a8bbb2.corvus-plugins.pages.dev
Branch Preview URL: https://feature-plugins-doble-sing.corvus-plugins.pages.dev

View logs

@github-actions
Copy link
Copy Markdown
Contributor

github-actions Bot commented Feb 21, 2026

✅ Contributor Report

User: @yacosta738
Status: Passed (12/13 metrics passed)

Metric Description Value Threshold Status
PR Merge Rate PRs merged vs closed 89% >= 30%
Repo Quality Repos with ≥100 stars 0 >= 0
Positive Reactions Positive reactions received 9 >= 1
Negative Reactions Negative reactions received 0 <= 5
Account Age GitHub account age 3039 days >= 30 days
Activity Consistency Regular activity over time 108% >= 0%
Issue Engagement Issues with community engagement 0 >= 0
Code Reviews Code reviews given to others 363 >= 0
Merger Diversity Unique maintainers who merged PRs 3 >= 0
Repo History Merge Rate Merge rate in this repo 86% >= 0%
Repo History Min PRs Previous PRs in this repo 46 >= 0
Profile Completeness Profile richness (bio, followers) 90 >= 0
Suspicious Patterns Spam-like activity detection 1 N/A

Contributor Report evaluates based on public GitHub activity. Analysis period: 2025-02-21 to 2026-02-21

Copy link
Copy Markdown
Contributor

@coderabbitai coderabbitai Bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 2

Caution

Some comments are outside the diff and can’t be posted inline due to platform limitations.

⚠️ Outside diff range comments (1)
.github/workflows/publish-plugins.yml (1)

392-412: ⚠️ Potential issue | 🟡 Minor

Update cosign to v2.6.1 or later.

The installation is properly hardened with checksum verification. However, v2.4.1 (used in the code) is outdated; the latest v2.x release is v2.6.1 (Oct 2, 2025), with v3.0.3 now available as the latest stable release (Dec 10, 2025). Update to at least v2.6.1 for security fixes and improvements, or evaluate migration to v3.0.3.

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In @.github/workflows/publish-plugins.yml around lines 392 - 412, Update the
hardcoded COSIGN_VERSION (and derived COSIGN_BASE_URL/COSIGN_BINARY) from
"v2.4.1" to at least "v2.6.1" (or "v3.0.3" if you choose to migrate), and ensure
the downloaded checksum file and grep target still match the new COSIGN_BINARY
name; specifically, change COSIGN_VERSION to the desired release, verify
COSIGN_BINARY equals the release asset name on GitHub for that version, and
confirm the grep/sha256sum steps still locate and validate the correct checksum
for the new release.
🧹 Nitpick comments (5)
.github/workflows/publish-plugins.yml (2)

426-448: Keyless OIDC signing and verification look correct.

The sign-blob + verify-blob pair with --certificate-oidc-issuer and --certificate-identity-regexp is the standard approach for GitHub Actions keyless signing. The id-token: write permission at line 39 is required and present.

One minor note: the . in .github within the --certificate-identity-regexp is an unescaped regex metacharacter. In practice, exploiting this is infeasible given the specificity of the surrounding pattern (repo name + full path), so this is a cosmetic nit rather than a real risk.

🔧 Optional: escape the dot for correctness
-            --certificate-identity-regexp "https://github.com/${GITHUB_REPOSITORY}/.github/workflows/publish-plugins.yml@.*" \
+            --certificate-identity-regexp "https://github\\.com/${GITHUB_REPOSITORY}/\\.github/workflows/publish-plugins\\.yml@.*" \
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In @.github/workflows/publish-plugins.yml around lines 426 - 448, The regex in
the --certificate-identity-regexp argument used by the ./cosign verify-blob step
contains an unescaped dot in ".github"; update the regexp string in the
verify-blob invocation so the dot is escaped (i.e., use "\.github") to make the
match precise and avoid accidental wildcard behavior, ensuring the change is
applied to the verify-blob command's --certificate-identity-regexp argument in
the publish-plugins.yml workflow.

494-497: Leftover conditional certificate checks from key-based flow.

With keyless OIDC signing, the certificate is always produced. These if [ -f ... ] guards (also at line 528) are now dead conditionals — they'll always be true. Removing them makes the intent clearer and ensures a loud failure if the certificate is unexpectedly missing (which would indicate a signing problem).

♻️ Proposed fix for OCI push (lines 494–497)
-          certificate_path="$DIST_DIR/${ARTIFACT_RELATIVE_PATH}.pem"
-          if [ -f "$certificate_path" ]; then
-            oras_args+=("$certificate_path:application/x-pem-file")
-          fi
+          certificate_path="$DIST_DIR/${ARTIFACT_RELATIVE_PATH}.pem"
+          oras_args+=("$certificate_path:application/x-pem-file")
♻️ Proposed fix for staging step (lines 528–530)
-          if [ -f "$DIST_DIR/$CERTIFICATE_RELATIVE_PATH" ]; then
-            cp "$DIST_DIR/$CERTIFICATE_RELATIVE_PATH" "$catalog_public/$CERTIFICATE_RELATIVE_PATH"
-          fi
+          cp "$DIST_DIR/$CERTIFICATE_RELATIVE_PATH" "$catalog_public/$CERTIFICATE_RELATIVE_PATH"

Also applies to: 528-530

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In @.github/workflows/publish-plugins.yml around lines 494 - 497, Remove the
dead conditional that wraps adding the certificate to oras_args; set
certificate_path="$DIST_DIR/${ARTIFACT_RELATIVE_PATH}.pem" then explicitly
assert the file exists and fail loudly if it does not (e.g. test -f
"$certificate_path" || { echo "missing certificate: $certificate_path" >&2; exit
1; }) before unconditionally appending the entry to oras_args
(oras_args+=("$certificate_path:application/x-pem-file")); apply the same change
for the other occurrence that uses the same certificate_path/oras_args pattern.
clients/agent-runtime/src/plugins/mod.rs (3)

1814-1841: Good baseline policy tests; consider expanding coverage.

The two tests validate the main policy branches (official-remote → keyless required, local → not required). Consider adding tests for:

  • Non-official remote sources (the identity_regex: None branch)
  • signature_policy_for_locked_plugin with and without source_url
  • Loopback HTTP sources

These additional tests would help validate the full policy matrix and catch edge cases like the identity_regex: None cosign behavior.

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@clients/agent-runtime/src/plugins/mod.rs` around lines 1814 - 1841, Add unit
tests to cover the missing branches of signature policy resolution: create tests
using PluginSourceConfig and signature_policy_for_source that assert
SignaturePolicy::RequiredKeyless with identity_regex: None for non-official
remote sources, and SignaturePolicy::NotRequired for loopback HTTP sources; also
add tests for signature_policy_for_locked_plugin that exercise both paths when a
locked plugin has a source_url and when it does not (assert expected
SignaturePolicy variants in each case). Ensure each test constructs the
appropriate PluginSourceConfig or locked-plugin input and uses expect/assert
with matches! against the SignaturePolicy variants to validate identity_regex
presence/absence.

930-949: Consider streaming size limits on signature/certificate downloads.

fetch_bytes downloads the full response body before the size check at lines 932 and 943. A malicious server could send an arbitrarily large response (bounded only by the 30s timeout). Since signature and certificate files should be small (64KB / 512KB), consider adding a content-length pre-check or using a size-limited reader to avoid unnecessary memory consumption.

This is a minor concern given the timeout, but it's defense-in-depth.

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@clients/agent-runtime/src/plugins/mod.rs` around lines 930 - 949, The current
fetch_bytes call reads the entire response for signature_url and certificate_url
before checking size (used with MAX_SIGNATURE_BYTES and MAX_CERTIFICATE_BYTES),
so change the download to enforce size limits during streaming: first perform a
HEAD or a lightweight GET to inspect Content-Length and reject if > limit,
and/or read the response body with a size-limited reader (e.g., read up to
limit+1 bytes and bail if exceeded) when fetching in fetch_bytes for the
signature/certificate paths; ensure the error messages still include
candidate.manifest.id and the URL context used in the with_context calls.

1387-1403: Backward-compatible fallback for legacy lock entries.

For locked plugins without source_url, only the "official" source name triggers keyless verification. All other legacy entries silently skip verification. This is a reasonable migration trade-off, but consider logging a warning when source_url is None for a non-local source, so operators are aware that signature verification is being skipped.

💡 Optional: log a diagnostic when signature verification is skipped due to missing source_url
 fn signature_policy_for_locked_plugin(plugin: &LockedPlugin) -> Result<SignaturePolicy> {
     if let Some(source_url) = plugin.source_url.as_deref() {
         let source = PluginSourceConfig {
             name: plugin.source.clone(),
             url: source_url.to_string(),
         };
         return signature_policy_for_source(&source);
     }
 
     if plugin.source == "official" {
         return Ok(SignaturePolicy::RequiredKeyless {
             identity_regex: Some(OFFICIAL_PLUGIN_IDENTITY_REGEX),
         });
     }
 
+    tracing::debug!(
+        "Plugin '{}' locked entry has no source_url; skipping signature verification",
+        plugin.id
+    );
     Ok(SignaturePolicy::NotRequired)
 }
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@clients/agent-runtime/src/plugins/mod.rs` around lines 1387 - 1403, In
signature_policy_for_locked_plugin, detect the legacy case where
plugin.source_url is None and plugin.source is not "local" (and not "official")
and emit a diagnostic warning that signature verification will be skipped for
that plugin; reference the LockedPlugin instance via plugin (e.g., plugin.source
and plugin.name/id) and use the project's logging facility (e.g., tracing::warn!
or log::warn!) to record which plugin/source is affected before returning
SignaturePolicy::NotRequired.
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Inline comments:
In `@clients/agent-runtime/src/plugins/mod.rs`:
- Around line 1483-1509: The cosign invocation currently uses
Command::new(...).output() with no timeout; change to spawn the child (use
command.spawn() to get a Child), then use a timeout mechanism (e.g.,
wait-timeout crate's ChildExt::wait_timeout or a thread + join with
Duration::from_secs(30)) to wait for completion, and on timeout kill the child
(Child.kill()/Child.wait()) and return an error; replace the current
command.output() error handling with this flow so verify-blob invocation
(references: Command::new("cosign"), .arg("verify-blob"), certificate_path,
signature_path, artifact_path, and the output variable) will be bounded (e.g.,
20–30s) and properly report timeout vs execution errors.
- Around line 1362-1385: signature_policy_for_source currently returns
SignaturePolicy::RequiredKeyless { identity_regex: None } for non-official
remote URLs which causes cosign to fail; update signature_policy_for_source (and
PluginSourceConfig) to provide a concrete identity_regex for non-official
remotes: either read a new optional field from PluginSourceConfig (e.g.,
plugin_identity_regex) and use it when constructing
SignaturePolicy::RequiredKeyless, or if that field is absent supply a permissive
fallback like ".*" (or alternatively change the logic to explicitly reject
non-official remotes with a clear application-level error); ensure
OFFICIAL_PLUGIN_CATALOG_HOST and OFFICIAL_PLUGIN_IDENTITY_REGEX logic remains
unchanged for official hosts and that the resulting SignaturePolicy always sets
identity_regex to Some(...) for any case passed to cosign verification.

---

Outside diff comments:
In @.github/workflows/publish-plugins.yml:
- Around line 392-412: Update the hardcoded COSIGN_VERSION (and derived
COSIGN_BASE_URL/COSIGN_BINARY) from "v2.4.1" to at least "v2.6.1" (or "v3.0.3"
if you choose to migrate), and ensure the downloaded checksum file and grep
target still match the new COSIGN_BINARY name; specifically, change
COSIGN_VERSION to the desired release, verify COSIGN_BINARY equals the release
asset name on GitHub for that version, and confirm the grep/sha256sum steps
still locate and validate the correct checksum for the new release.

---

Nitpick comments:
In @.github/workflows/publish-plugins.yml:
- Around line 426-448: The regex in the --certificate-identity-regexp argument
used by the ./cosign verify-blob step contains an unescaped dot in ".github";
update the regexp string in the verify-blob invocation so the dot is escaped
(i.e., use "\.github") to make the match precise and avoid accidental wildcard
behavior, ensuring the change is applied to the verify-blob command's
--certificate-identity-regexp argument in the publish-plugins.yml workflow.
- Around line 494-497: Remove the dead conditional that wraps adding the
certificate to oras_args; set
certificate_path="$DIST_DIR/${ARTIFACT_RELATIVE_PATH}.pem" then explicitly
assert the file exists and fail loudly if it does not (e.g. test -f
"$certificate_path" || { echo "missing certificate: $certificate_path" >&2; exit
1; }) before unconditionally appending the entry to oras_args
(oras_args+=("$certificate_path:application/x-pem-file")); apply the same change
for the other occurrence that uses the same certificate_path/oras_args pattern.

In `@clients/agent-runtime/src/plugins/mod.rs`:
- Around line 1814-1841: Add unit tests to cover the missing branches of
signature policy resolution: create tests using PluginSourceConfig and
signature_policy_for_source that assert SignaturePolicy::RequiredKeyless with
identity_regex: None for non-official remote sources, and
SignaturePolicy::NotRequired for loopback HTTP sources; also add tests for
signature_policy_for_locked_plugin that exercise both paths when a locked plugin
has a source_url and when it does not (assert expected SignaturePolicy variants
in each case). Ensure each test constructs the appropriate PluginSourceConfig or
locked-plugin input and uses expect/assert with matches! against the
SignaturePolicy variants to validate identity_regex presence/absence.
- Around line 930-949: The current fetch_bytes call reads the entire response
for signature_url and certificate_url before checking size (used with
MAX_SIGNATURE_BYTES and MAX_CERTIFICATE_BYTES), so change the download to
enforce size limits during streaming: first perform a HEAD or a lightweight GET
to inspect Content-Length and reject if > limit, and/or read the response body
with a size-limited reader (e.g., read up to limit+1 bytes and bail if exceeded)
when fetching in fetch_bytes for the signature/certificate paths; ensure the
error messages still include candidate.manifest.id and the URL context used in
the with_context calls.
- Around line 1387-1403: In signature_policy_for_locked_plugin, detect the
legacy case where plugin.source_url is None and plugin.source is not "local"
(and not "official") and emit a diagnostic warning that signature verification
will be skipped for that plugin; reference the LockedPlugin instance via plugin
(e.g., plugin.source and plugin.name/id) and use the project's logging facility
(e.g., tracing::warn! or log::warn!) to record which plugin/source is affected
before returning SignaturePolicy::NotRequired.

Comment thread clients/agent-runtime/src/plugins/mod.rs
Comment thread clients/agent-runtime/src/plugins/mod.rs Outdated
Copy link
Copy Markdown
Contributor

@coderabbitai coderabbitai Bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 1

🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Inline comments:
In `@clients/agent-runtime/src/plugins/mod.rs`:
- Around line 1488-1505: The current logic in signature_policy_for_locked_plugin
(handling LockedPlugin) silently returns SignaturePolicy::NotRequired for remote
ResolvedSource::Remote when plugin.source_identity_regex is None, which weakens
security; change this to fail fast or require explicit opt-in: replace the
tracing::warn + Ok(SignaturePolicy::NotRequired) path with an Err return (e.g.,
a descriptive error type indicating missing source_identity_regex for remote
legacy plugin) or guard it behind a clear feature/flag check (inspect a
config/opt_in field or environment) before allowing
SignaturePolicy::NotRequired; ensure callers of
signature_policy_for_locked_plugin handle the new Err case or the opt‑in flag is
validated earlier so remote plugins without identity regex cannot bypass
verification by default.

Comment thread clients/agent-runtime/src/plugins/mod.rs
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant