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
71 changes: 71 additions & 0 deletions .github/workflows/release-please.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,71 @@
# -----------------------------------------------------------------------
# Signal Sentinel - Release Please Workflow
# Copyright 2026 Signal Coding Limited. All rights reserved.
# Licensed under the Apache License, Version 2.0.
# -----------------------------------------------------------------------
# Owns versioning and tagging. On every push to main, release-please opens (or
# updates) a release PR that bumps the version everywhere and writes CHANGELOG.md.
# Merging that PR creates the tag and the GitHub Release, which gates the reusable
# release workflow below.
#
# The default GITHUB_TOKEN is used deliberately - no PAT, no App. The consequence
# is that a tag pushed by this workflow will NOT trigger a separate tag-triggered
# workflow, which is why release.yml is invoked directly via workflow_call rather
# than left on a 'push: tags' trigger.
#
# Requires: Settings -> Actions -> General -> "Allow GitHub Actions to create and
# approve pull requests" must be enabled, or the release PR cannot be opened.
#
# All actions pinned to SHA hashes for supply chain security.

name: Release Please

on:
push:
branches: [main]

# Deny by default; each job grants only what it needs.
permissions: {}

concurrency:
group: release-please-${{ github.ref }}
cancel-in-progress: false # never cancel a run that may be mid-tag

jobs:
release-please:
name: Release PR / Tag
runs-on: ubuntu-latest
permissions:
contents: write # push the release branch, create the tag and release
pull-requests: write # open and update the release PR
issues: write # the autorelease:* labels it uses to find merged release PRs
outputs:
release_created: ${{ steps.release.outputs.release_created }}
version: ${{ steps.release.outputs.version }}
tag_name: ${{ steps.release.outputs.tag_name }}

steps:
- name: Run release-please
id: release
uses: googleapis/release-please-action@45996ed1f6d02564a971a2fa1b5860e934307cf7 # v5.0.0
with:
token: ${{ secrets.GITHUB_TOKEN }}
config-file: release-please-config.json
manifest-file: .release-please-manifest.json

release:
name: Publish
needs: release-please
if: needs.release-please.outputs.release_created == 'true'
# This block is the ceiling for every job inside release.yml - a called
# workflow can only downgrade what the caller grants, never raise it.
permissions:
contents: write # attach .nupkg assets to the release
packages: write # push to GHCR
security-events: write # upload the Trivy SARIF
actions: read # required by upload-sarif on private repositories
uses: ./.github/workflows/release.yml
with:
version: ${{ needs.release-please.outputs.version }}
tag: ${{ needs.release-please.outputs.tag_name }}
secrets: inherit
65 changes: 54 additions & 11 deletions .github/workflows/release.yml
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,12 @@
# Copyright 2026 Signal Coding Limited. All rights reserved.
# Licensed under the Apache License, Version 2.0.
# -----------------------------------------------------------------------
# Triggered on version tags (v*) - publishes to NuGet and GitHub Container Registry
# Publishes to NuGet and GitHub Container Registry. Two entry points:
# 1. push: tags 'v*' - manual/emergency releases (git tag vX.Y.Z && git push --tags).
# 2. workflow_call - the normal path, invoked by release-please.yml once it has
# created the tag and a draft release. `version`/`tag` inputs are used instead of
# parsing GITHUB_REF, since a workflow_call run's triggering ref is the branch
# that release-please pushed to (main), not the tag itself.
# All actions pinned to SHA hashes for supply chain security.

name: Release
Expand All @@ -12,6 +17,16 @@ on:
push:
tags:
- 'v*'
workflow_call:
inputs:
version:
description: 'Version without the leading v, e.g. 2.6.0'
required: true
type: string
tag:
description: 'Full tag name, e.g. v2.6.0'
required: true
type: string

env:
DOTNET_VERSION: '10.0.x'
Expand Down Expand Up @@ -69,9 +84,14 @@ jobs:
with:
dotnet-version: ${{ env.DOTNET_VERSION }}

- name: Extract version from tag
- name: Determine version
id: version
run: echo "VERSION=${GITHUB_REF#refs/tags/v}" >> $GITHUB_OUTPUT
run: |
if [ -n "${{ inputs.version }}" ]; then
echo "VERSION=${{ inputs.version }}" >> "$GITHUB_OUTPUT"
else
echo "VERSION=${GITHUB_REF#refs/tags/v}" >> "$GITHUB_OUTPUT"
fi

- name: Pack Scanner
run: |
Expand Down Expand Up @@ -128,16 +148,28 @@ jobs:
- name: Checkout
uses: actions/checkout@34e114876b0b11c390a56381ad16ebd13914f8d5 # v4

- name: Extract version from tag
- name: Determine version
id: version
run: echo "VERSION=${GITHUB_REF#refs/tags/v}" >> $GITHUB_OUTPUT
run: |
if [ -n "${{ inputs.version }}" ]; then
echo "VERSION=${{ inputs.version }}" >> "$GITHUB_OUTPUT"
else
echo "VERSION=${GITHUB_REF#refs/tags/v}" >> "$GITHUB_OUTPUT"
fi

- name: Lowercase image name
id: image
run: echo "NAME=${IMAGE_NAME,,}" >> $GITHUB_OUTPUT
env:
IMAGE_NAME: ${{ env.IMAGE_NAME }}

- name: Compute semver components
id: semver
run: |
VERSION="${{ steps.version.outputs.VERSION }}"
echo "MAJOR=${VERSION%%.*}" >> "$GITHUB_OUTPUT"
echo "MAJOR_MINOR=${VERSION%.*}" >> "$GITHUB_OUTPUT"

- name: Set up Docker Buildx
uses: docker/setup-buildx-action@4d04d5d9486b7bd6fa91e7baf45bbb4f8b9deedd # v4.0.0

Expand All @@ -154,9 +186,9 @@ jobs:
with:
images: ${{ env.REGISTRY }}/${{ steps.image.outputs.NAME }}
tags: |
type=semver,pattern={{version}}
type=semver,pattern={{major}}.{{minor}}
type=semver,pattern={{major}}
type=raw,value=${{ steps.version.outputs.VERSION }}
type=raw,value=${{ steps.semver.outputs.MAJOR_MINOR }}
type=raw,value=${{ steps.semver.outputs.MAJOR }}
type=raw,value=latest,enable={{is_default_branch}}

- name: Build and push Docker image
Expand Down Expand Up @@ -201,9 +233,14 @@ jobs:
- name: Checkout
uses: actions/checkout@34e114876b0b11c390a56381ad16ebd13914f8d5 # v4

- name: Extract version from tag
- name: Determine version
id: version
run: echo "VERSION=${GITHUB_REF#refs/tags/v}" >> $GITHUB_OUTPUT
run: |
if [ -n "${{ inputs.version }}" ]; then
echo "VERSION=${{ inputs.version }}" >> "$GITHUB_OUTPUT"
else
echo "VERSION=${GITHUB_REF#refs/tags/v}" >> "$GITHUB_OUTPUT"
fi

- name: Download NuGet artifacts
uses: actions/download-artifact@d3f86a106a0bac45b974a628896c90dbdf5c8093 # v4
Expand All @@ -214,7 +251,14 @@ jobs:
- name: Create Release
uses: softprops/action-gh-release@de2c0eb89ae2a093876385947365aca7b0e5f844 # v1
with:
# release-please already created the tag (and, per release-please-config.json's
# draft:true, an unpublished draft release with its own conventional-commits
# changelog as the body). tag_name is explicit because github.ref is the
# branch that triggered release-please.yml, not the tag, on this path.
tag_name: ${{ inputs.tag || format('v{0}', steps.version.outputs.VERSION) }}
name: Signal Sentinel v${{ steps.version.outputs.VERSION }}
append_body: true
generate_release_notes: ${{ inputs.tag == '' }}
body: |
## Signal Sentinel Scanner v${{ steps.version.outputs.VERSION }}

Expand Down Expand Up @@ -242,4 +286,3 @@ jobs:
./artifacts/*.nupkg
draft: false
prerelease: false
generate_release_notes: true
3 changes: 3 additions & 0 deletions .release-please-manifest.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
{
".": "2.5.0"
}
6 changes: 6 additions & 0 deletions Directory.Build.props
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,12 @@
<EnforceCodeStyleInBuild>true</EnforceCodeStyleInBuild>
<AnalysisLevel>latest-all</AnalysisLevel>

<!-- Single source of truth for the product version. Both packages, the
scanner binary's --version output and the container's OCI version label
all derive from this one line; release-please rewrites it on release.
Never set <Version> in an individual .csproj or via -p:Version. -->
<Version>2.5.0</Version> <!-- x-release-please-version -->

<!-- Package metadata -->
<Authors>Signal Coding Limited</Authors>
<Company>Signal Coding Limited</Company>
Expand Down
38 changes: 23 additions & 15 deletions INSTALLATION_AND_USAGE.md
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
# Signal Sentinel Scanner - Installation and Usage Guide

**Version:** 2.5.0
**Last Updated:** 2026-07-29
**Version:** 2.5.0 <!-- x-release-please-version -->
**Last Updated:** 2026-07-29 <!-- x-release-please-date -->
**Repository:** https://github.com/SignalCoding/signal-sentinel-scanner

---
Expand Down Expand Up @@ -51,9 +51,11 @@ sentinel-scan --version
```

**Expected output:**
<!-- x-release-please-start-version -->
```
Signal Sentinel Scanner v2.5.0
```
<!-- x-release-please-end -->

### Update

Expand Down Expand Up @@ -85,30 +87,30 @@ dotnet tool uninstall -g SignalSentinel.Scanner
### Pull the Image

```bash
docker pull ghcr.io/signalcoding/signal-sentinel-scanner:2.5.0
docker pull ghcr.io/signalcoding/signal-sentinel-scanner:2.5.0 # x-release-please-version
```

### Available Tags

| Tag | Description |
|-----|-------------|
| `2.5.0` | Specific version (recommended for CI/CD) |
| `2.5` | Latest 2.5.x patch version |
| `2` | Latest 2.x.x version |
| `2.5.0` <!-- x-release-please-version --> | Exact release (recommended for CI/CD) |
| `<major>.<minor>` | Latest patch release within that minor line |
| `<major>` | Latest release within that major line |
| `latest` | Latest stable release |

### Verify Installation

```bash
docker run --rm ghcr.io/signalcoding/signal-sentinel-scanner:2.5.0 --version
docker run --rm ghcr.io/signalcoding/signal-sentinel-scanner:2.5.0 --version # x-release-please-version
```

### Image Details

| Property | Value |
|----------|-------|
| Registry | GitHub Container Registry (ghcr.io) |
| Image | `ghcr.io/signalcoding/signal-sentinel-scanner:2.5.0` |
| Image | `ghcr.io/signalcoding/signal-sentinel-scanner:2.5.0` <!-- x-release-please-version --> |
| Base | Alpine Linux (.NET runtime-deps) |
| Architecture | linux/amd64, linux/arm64 |
| User | Non-root (sentinel, uid 1000) |
Expand Down Expand Up @@ -143,15 +145,15 @@ sentinel-scan --skills ~/.claude/skills/
docker run --rm \
-v "$HOME/.cursor:/home/sentinel/.cursor:ro" \
-v "$HOME/.config:/home/sentinel/.config:ro" \
ghcr.io/signalcoding/signal-sentinel-scanner:2.5.0 --discover --skills
ghcr.io/signalcoding/signal-sentinel-scanner:2.5.0 --discover --skills # x-release-please-version
```

**Windows Docker:**
```powershell
docker run --rm `
-v "$env:USERPROFILE\.cursor:/home/sentinel/.cursor:ro" `
-v "$env:APPDATA:/home/sentinel/AppData/Roaming:ro" `
ghcr.io/signalcoding/signal-sentinel-scanner:2.5.0 --discover --skills
ghcr.io/signalcoding/signal-sentinel-scanner:2.5.0 --discover --skills # x-release-please-version
```

### Scan a Specific Config File
Expand All @@ -165,7 +167,7 @@ sentinel-scan --config ~/.cursor/mcp.json
```bash
docker run --rm \
-v "$HOME/.cursor/mcp.json:/config/mcp.json:ro" \
ghcr.io/signalcoding/signal-sentinel-scanner:2.5.0 --config /config/mcp.json
ghcr.io/signalcoding/signal-sentinel-scanner:2.5.0 --config /config/mcp.json # x-release-please-version
```

### Scan a Remote MCP Server
Expand All @@ -176,10 +178,12 @@ sentinel-scan --remote https://mcp.example.com/sse
```

**Docker:**
<!-- x-release-please-start-version -->
```bash
docker run --rm ghcr.io/signalcoding/signal-sentinel-scanner:2.5.0 \
--remote https://mcp.example.com/sse
```
<!-- x-release-please-end -->

---

Expand Down Expand Up @@ -247,13 +251,15 @@ sentinel-scan --discover --format html --output security-report.html
```

**Docker:**
<!-- x-release-please-start-version -->
```bash
docker run --rm \
-v "$HOME/.cursor:/home/sentinel/.cursor:ro" \
-v "$(pwd):/output" \
ghcr.io/signalcoding/signal-sentinel-scanner:2.5.0 \
--discover --skills --format html --output /output/security-report.html
```
<!-- x-release-please-end -->

### Generate JSON for Processing

Expand Down Expand Up @@ -295,6 +301,7 @@ sentinel-scan --discover --format json
```

**Sample JSON structure:**
<!-- x-release-please-start-version -->
```json
{
"scanDate": "2026-07-29T08:00:00Z",
Expand All @@ -312,6 +319,7 @@ sentinel-scan --discover --format json
"owaspCompliance": {...}
}
```
<!-- x-release-please-end -->

### HTML

Expand Down Expand Up @@ -441,7 +449,7 @@ jobs:
security-scan:
runs-on: ubuntu-latest
container:
image: ghcr.io/signalcoding/signal-sentinel-scanner:2.5.0
image: ghcr.io/signalcoding/signal-sentinel-scanner:2.5.0 # x-release-please-version
steps:
- uses: actions/checkout@v4

Expand Down Expand Up @@ -476,7 +484,7 @@ steps:

```yaml
mcp-security-scan:
image: ghcr.io/signalcoding/signal-sentinel-scanner:2.5.0
image: ghcr.io/signalcoding/signal-sentinel-scanner:2.5.0 # x-release-please-version
script:
- sentinel-scan --config ./mcp-config.json --ci --format json --output gl-sast-report.json
artifacts:
Expand Down Expand Up @@ -623,7 +631,7 @@ sentinel-scan --remote https://slow-server.com/mcp --timeout 120
```bash
docker run --rm \
-v "/path/to/config:/config:ro" \
ghcr.io/signalcoding/signal-sentinel-scanner:2.5.0 --config /config/mcp.json
ghcr.io/signalcoding/signal-sentinel-scanner:2.5.0 --config /config/mcp.json # x-release-please-version
```

### "Tool not found" after installation
Expand Down Expand Up @@ -655,4 +663,4 @@ Apache 2.0 - Copyright 2026 Signal Coding Limited

---

*Document generated for Signal Sentinel Scanner v2.5.0*
*Document generated for Signal Sentinel Scanner v2.5.0* <!-- x-release-please-version -->
2 changes: 1 addition & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@
[![License](https://img.shields.io/badge/license-Apache%202.0-blue.svg)](LICENSE)
[![.NET](https://img.shields.io/badge/.NET-10.0-purple.svg)](https://dotnet.microsoft.com/)
[![OWASP](https://img.shields.io/badge/OWASP-ASI%20Top%2010-green.svg)](https://owasp.org/www-project-agentic-ai-top-10/)
[![Version](https://img.shields.io/badge/version-2.5.0-blue.svg)](https://github.com/SignalCoding/signal-sentinel-scanner/releases)
[![Version](https://img.shields.io/static/v1?label=version&color=blue&message=2.5.0)](https://github.com/SignalCoding/signal-sentinel-scanner/releases) <!-- x-release-please-version -->
[![SARIF](https://img.shields.io/badge/SARIF-v2.1.0-orange.svg)](https://docs.oasis-open.org/sarif/sarif/v2.1.0/)

**Signal Sentinel** is a security-first MCP (Model Context Protocol) and Agent Skill security product family, designed to address the critical security gap in the agentic AI ecosystem.
Expand Down
Loading