Skip to content

Plugin signing#1180

Open
clement-tourriere wants to merge 2 commits intomainfrom
plugin-signing
Open

Plugin signing#1180
clement-tourriere wants to merge 2 commits intomainfrom
plugin-signing

Conversation

@clement-tourriere
Copy link
Copy Markdown
Member

Context

The plugin system shipped in 1.48.0 used a --force flag to skip security warnings when installing from non-GitGuardian sources. This was a placeholder — it displayed a text warning but performed no actual
cryptographic verification.

This PR replaces that mechanism with real sigstore-based signature verification for plugin wheels, using OIDC identity-based trust (keyless). It also fixes a bug where the verification mode was not forwarded
through the GitHub release and artifact download paths.

What has been done

  • Add ggshield.core.plugin.signature module: keyless signature verification using sigstore bundles. Wheels are verified against a configurable set of trusted GitHub Actions workflow identities (OIDC). Three modes:
    STRICT (block unsigned/invalid), WARN (log and continue), DISABLED (skip).
  • Replace --force with --allow-unsigned: the new flag downgrades verification from STRICT to WARN instead of silently skipping a text warning. Verification mode is also configurable via plugin_signature_mode in
    enterprise config.
  • Forward signature_mode through all download paths: download_from_github_release and download_from_github_artifact now accept and forward signature_mode. The artifact path also calls verify_wheel_signature before
    writing the manifest (was missing entirely).
  • Show signature status in plugin status: installed plugins now display their signature status (e.g. valid (GitGuardian/satori), missing).
  • Suppress signature/loader log noise at startup: logger levels are temporarily raised during plugin loading in main.py to avoid noisy output before logging is configured.
  • Collapse repetitive tests in test_install.py: 12 near-duplicate error-handling tests reduced to 4 parameterized tests + 1 standalone. Merged the two test_install_unavailable_plugin variants into a single
    parameterized test.
  • Add sigstore dependency to pyproject.toml.

Validation

pdm run pytest tests/unit/core/plugin/ tests/unit/cmd/plugin/ -x -q

All 246 tests pass. To manually test signature verification:

  • ggshield plugin install tokenscanner — should verify signature in STRICT mode (default)
  • ggshield plugin install tokenscanner --allow-unsigned — should warn but proceed
  • ggshield plugin install ./unsigned.whl — should fail in STRICT mode with a clear error pointing to --allow-unsigned
  • ggshield plugin status — should show signature status for installed plugins

PR check list

  • As much as possible, the changes include tests (unit and/or functional)
  • If the changes affect the end user (new feature, behavior change, bug fix) then the PR has a changelog entry (see doc/dev/getting-started.md). If the changes do not affect the end user, then the skip-changelog
    label has been added to the PR.

@clement-tourriere clement-tourriere requested review from a team as code owners February 19, 2026 15:02
@codecov
Copy link
Copy Markdown

codecov bot commented Feb 19, 2026

Codecov Report

❌ Patch coverage is 97.98995% with 4 lines in your changes missing coverage. Please review.
✅ Project coverage is 92.25%. Comparing base (b605af2) to head (75bde61).

Files with missing lines Patch % Lines
ggshield/core/plugin/downloader.py 94.64% 3 Missing ⚠️
ggshield/core/plugin/loader.py 91.66% 1 Missing ⚠️
Additional details and impacted files
@@            Coverage Diff             @@
##             main    #1180      +/-   ##
==========================================
+ Coverage   92.16%   92.25%   +0.08%     
==========================================
  Files         160      161       +1     
  Lines        7698     7863     +165     
==========================================
+ Hits         7095     7254     +159     
- Misses        603      609       +6     
Flag Coverage Δ
unittests 92.25% <97.98%> (+0.08%) ⬆️

Flags with carried forward coverage won't be shown. Click here to find out more.

☔ View full report in Codecov by Sentry.
📢 Have feedback on the report? Share it here.

🚀 New features to boost your workflow:
  • ❄️ Test Analytics: Detect flaky tests, report on failures, and find test suite problems.

Copy link
Copy Markdown
Collaborator

@sevbch sevbch left a comment

Choose a reason for hiding this comment

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

I only reviewed files outside of **/plugin/* paths, since those will be reviewed by @GitGuardian/nhi-governance

from dataclasses import dataclass, field
from pathlib import Path
from typing import Any, Dict, Optional
from typing import TYPE_CHECKING, Any, Dict, Optional
Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

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

You can put this under the if TYPE_CHECKING: below and add from __future__ import annotations at the top of the file

plugins[name] = PluginConfig(enabled=True)

return cls(plugins=plugins)
plugin_signature_mode = data.get("plugin_signature_mode", "strict")
Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

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

"plugin_signature_mode" should probably be a const and "strict" part of an enum


def get_signature_mode(self) -> "SignatureVerificationMode":
"""Get the signature verification mode."""
from ggshield.core.plugin.signature import SignatureVerificationMode
Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

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

to move at the top

@@ -0,0 +1,7 @@
### Added

- Add sigstore signature verification for plugin wheels, enforcing identity-based trust via OIDC. Plugins can be verified in STRICT, WARN, or DISABLED mode, configurable through enterprise settings or the `--allow-unsigned` flag.
Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

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

--allow-unsigned is yet another option: did you look into merging it with the already existing --insecure option?

Comment on lines +73 to +74
# Suppress signature/loader loggers during startup to avoid noisy output
# before logging is configured
Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

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

This looks like a workaround for sth that should be addressed at the source: configure properly the concerned loggers from the start

…re_mode

Add keyless signature verification for plugin wheels using sigstore
bundles with OIDC identity-based trust. Replace --force flag with
--allow-unsigned and enterprise config signature mode support.

Forward signature_mode through all download paths (GitHub release,
GitHub artifact, URL, local wheel). Collapse repetitive install error
tests into parametrized ones.
@sevbch sevbch removed their request for review February 24, 2026 14:30
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.

2 participants