diff --git a/.github/workflows/release-please.yml b/.github/workflows/release-please.yml
new file mode 100644
index 0000000..7fbb5f5
--- /dev/null
+++ b/.github/workflows/release-please.yml
@@ -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
diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml
index 3c2325b..a4b7d9e 100644
--- a/.github/workflows/release.yml
+++ b/.github/workflows/release.yml
@@ -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
@@ -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'
@@ -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: |
@@ -128,9 +148,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: Lowercase image name
id: image
@@ -138,6 +163,13 @@ jobs:
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
@@ -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
@@ -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
@@ -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 }}
@@ -242,4 +286,3 @@ jobs:
./artifacts/*.nupkg
draft: false
prerelease: false
- generate_release_notes: true
diff --git a/.release-please-manifest.json b/.release-please-manifest.json
new file mode 100644
index 0000000..78baf5b
--- /dev/null
+++ b/.release-please-manifest.json
@@ -0,0 +1,3 @@
+{
+ ".": "2.5.0"
+}
diff --git a/Directory.Build.props b/Directory.Build.props
index 37799ff..f3bf7a9 100644
--- a/Directory.Build.props
+++ b/Directory.Build.props
@@ -8,6 +8,12 @@
true
latest-all
+
+ 2.5.0
+
Signal Coding Limited
Signal Coding Limited
diff --git a/INSTALLATION_AND_USAGE.md b/INSTALLATION_AND_USAGE.md
index 18f414c..0179a16 100644
--- a/INSTALLATION_AND_USAGE.md
+++ b/INSTALLATION_AND_USAGE.md
@@ -1,7 +1,7 @@
# Signal Sentinel Scanner - Installation and Usage Guide
-**Version:** 2.5.0
-**Last Updated:** 2026-07-29
+**Version:** 2.5.0
+**Last Updated:** 2026-07-29
**Repository:** https://github.com/SignalCoding/signal-sentinel-scanner
---
@@ -51,9 +51,11 @@ sentinel-scan --version
```
**Expected output:**
+
```
Signal Sentinel Scanner v2.5.0
```
+
### Update
@@ -85,22 +87,22 @@ 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` | Exact release (recommended for CI/CD) |
+| `.` | Latest patch release within that minor line |
+| `` | 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
@@ -108,7 +110,7 @@ docker run --rm ghcr.io/signalcoding/signal-sentinel-scanner:2.5.0 --version
| 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` |
| Base | Alpine Linux (.NET runtime-deps) |
| Architecture | linux/amd64, linux/arm64 |
| User | Non-root (sentinel, uid 1000) |
@@ -143,7 +145,7 @@ 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:**
@@ -151,7 +153,7 @@ docker run --rm \
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
@@ -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
@@ -176,10 +178,12 @@ sentinel-scan --remote https://mcp.example.com/sse
```
**Docker:**
+
```bash
docker run --rm ghcr.io/signalcoding/signal-sentinel-scanner:2.5.0 \
--remote https://mcp.example.com/sse
```
+
---
@@ -247,6 +251,7 @@ sentinel-scan --discover --format html --output security-report.html
```
**Docker:**
+
```bash
docker run --rm \
-v "$HOME/.cursor:/home/sentinel/.cursor:ro" \
@@ -254,6 +259,7 @@ docker run --rm \
ghcr.io/signalcoding/signal-sentinel-scanner:2.5.0 \
--discover --skills --format html --output /output/security-report.html
```
+
### Generate JSON for Processing
@@ -295,6 +301,7 @@ sentinel-scan --discover --format json
```
**Sample JSON structure:**
+
```json
{
"scanDate": "2026-07-29T08:00:00Z",
@@ -312,6 +319,7 @@ sentinel-scan --discover --format json
"owaspCompliance": {...}
}
```
+
### HTML
@@ -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
@@ -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:
@@ -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
@@ -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*
diff --git a/README.md b/README.md
index 2c69909..4ecd024 100644
--- a/README.md
+++ b/README.md
@@ -3,7 +3,7 @@
[](LICENSE)
[](https://dotnet.microsoft.com/)
[](https://owasp.org/www-project-agentic-ai-top-10/)
-[](https://github.com/SignalCoding/signal-sentinel-scanner/releases)
+[](https://github.com/SignalCoding/signal-sentinel-scanner/releases)
[](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.
diff --git a/deploy/docker/Dockerfile.scanner b/deploy/docker/Dockerfile.scanner
index e8eb3e8..cae17e1 100644
--- a/deploy/docker/Dockerfile.scanner
+++ b/deploy/docker/Dockerfile.scanner
@@ -84,6 +84,15 @@ ENTRYPOINT ["/app/sentinel-scan"]
# Default to help if no arguments provided
CMD ["--help"]
+# The release workflow passes --build-arg VERSION=. The default
+# below is rewritten by release-please so local and CI builds still label
+# themselves correctly. Dockerfiles have no inline comments, so this needs the
+# block form of the annotation - keep it wrapped tightly around the ARG, since
+# block mode rewrites the first version-shaped token on every line it covers.
+# x-release-please-start-version
+ARG VERSION=2.5.0
+# x-release-please-end
+
# OCI Image Labels (https://github.com/opencontainers/image-spec/blob/main/annotations.md)
LABEL org.opencontainers.image.title="Signal Sentinel Scanner" \
org.opencontainers.image.description="MCP Security Audit Tool - OWASP Agentic AI Top 10 Compliant" \
@@ -92,7 +101,7 @@ LABEL org.opencontainers.image.title="Signal Sentinel Scanner" \
org.opencontainers.image.source="https://github.com/SignalCoding/signal-sentinel-scanner" \
org.opencontainers.image.documentation="https://github.com/SignalCoding/signal-sentinel-scanner#readme" \
org.opencontainers.image.url="https://signalcoding.co.uk/products/sentinel-scanner/" \
- org.opencontainers.image.version="2.5.0" \
+ org.opencontainers.image.version="${VERSION}" \
org.opencontainers.image.base.name="mcr.microsoft.com/dotnet/runtime-deps:10.0-alpine"
# Build-time labels (set via --build-arg)
diff --git a/docs/owasp-ast-mapping.md b/docs/owasp-ast-mapping.md
index b5b27b6..6dfd6c4 100644
--- a/docs/owasp-ast-mapping.md
+++ b/docs/owasp-ast-mapping.md
@@ -40,7 +40,7 @@ the AST code definitions themselves live in
| AST09 | No Governance | No change-management, ownership, or review process for skills. |
| AST10 | Cross-Platform Reuse | Skill mixes incompatible platform semantics unsafely. |
-## Rule-to-AST map (v2.5.0)
+## Rule-to-AST map (v2.5.0)
| Rule ID | ASI | AST | Notes |
|--------------|-------|----------------------|----------------------------------------------------------|
diff --git a/hooks/.pre-commit-hooks.yaml b/hooks/.pre-commit-hooks.yaml
index 8ce0556..b726821 100644
--- a/hooks/.pre-commit-hooks.yaml
+++ b/hooks/.pre-commit-hooks.yaml
@@ -4,7 +4,7 @@
#
# repos:
# - repo: https://github.com/SignalCoding/signal-sentinel-scanner
-# rev: v2.5.0
+# rev: v2.5.0 # x-release-please-version
# hooks:
# - id: signal-sentinel
#
diff --git a/release-please-config.json b/release-please-config.json
new file mode 100644
index 0000000..1e517fb
--- /dev/null
+++ b/release-please-config.json
@@ -0,0 +1,39 @@
+{
+ "$schema": "https://raw.githubusercontent.com/googleapis/release-please/main/schemas/config.json",
+ "bootstrap-sha": "d6b32ad077a4d25b62ffdacc54c1ad41b0f4c108",
+ "include-component-in-tag": false,
+ "draft": true,
+ "force-tag-creation": true,
+ "changelog-sections": [
+ { "type": "feat", "section": "Features" },
+ { "type": "fix", "section": "Bug Fixes" },
+ { "type": "perf", "section": "Performance Improvements" },
+ { "type": "deps", "section": "Dependencies" },
+ { "type": "docs", "section": "Documentation" },
+ { "type": "revert", "section": "Reverts" },
+ { "type": "refactor", "section": "Code Refactoring", "hidden": true },
+ { "type": "test", "section": "Tests", "hidden": true },
+ { "type": "build", "section": "Build System", "hidden": true },
+ { "type": "ci", "section": "Continuous Integration", "hidden": true },
+ { "type": "chore", "section": "Miscellaneous", "hidden": true }
+ ],
+ "packages": {
+ ".": {
+ "release-type": "simple",
+ "package-name": "signal-sentinel-scanner",
+ "extra-files": [
+ "Directory.Build.props",
+ "deploy/docker/Dockerfile.scanner",
+ "hooks/.pre-commit-hooks.yaml",
+ "README.md",
+ "INSTALLATION_AND_USAGE.md",
+ "docs/owasp-ast-mapping.md",
+ {
+ "type": "json",
+ "path": "src/SignalSentinel.Scanner/DefaultRules.json",
+ "jsonpath": "$.version"
+ }
+ ]
+ }
+ }
+}
diff --git a/src/SignalSentinel.Core/SignalSentinel.Core.csproj b/src/SignalSentinel.Core/SignalSentinel.Core.csproj
index f08dba5..d623421 100644
--- a/src/SignalSentinel.Core/SignalSentinel.Core.csproj
+++ b/src/SignalSentinel.Core/SignalSentinel.Core.csproj
@@ -2,7 +2,7 @@
SignalSentinel.Core
- 2.5.0
+
Core library for Signal Sentinel MCP and Agent Skill security tools. Contains MCP protocol models, skill definition models, shared security patterns (injection, exfiltration, credential, obfuscation), and OWASP dual mapping utilities.
mcp;security;ai-agents;owasp;agentic-ai;library;agent-skills
true
diff --git a/src/SignalSentinel.Scanner/DefaultRules.json b/src/SignalSentinel.Scanner/DefaultRules.json
index 719df7e..d482a6f 100644
--- a/src/SignalSentinel.Scanner/DefaultRules.json
+++ b/src/SignalSentinel.Scanner/DefaultRules.json
@@ -181,7 +181,9 @@
"id": "SS-026",
"name": "Instructional Tool/Skill Description",
"owaspCode": "ASI01",
- "astCodes": ["AST04"],
+ "astCodes": [
+ "AST04"
+ ],
"enabled": true,
"severity": "dynamic"
},
@@ -189,7 +191,9 @@
"id": "SS-028",
"name": "Skill Identity/Memory File Write Access",
"owaspCode": "ASI02",
- "astCodes": ["AST03"],
+ "astCodes": [
+ "AST03"
+ ],
"enabled": true,
"severity": "dynamic"
},
@@ -197,7 +201,10 @@
"id": "SS-029",
"name": "Skill Unpinned Dependency Reference (SkillJacking)",
"owaspCode": "ASI04",
- "astCodes": ["AST02", "AST07"],
+ "astCodes": [
+ "AST02",
+ "AST07"
+ ],
"enabled": true,
"severity": "medium"
},
@@ -205,7 +212,9 @@
"id": "SS-INFO-001",
"name": "Non-MCP Endpoint Detected",
"owaspCode": "ASI10",
- "astCodes": ["AST08"],
+ "astCodes": [
+ "AST08"
+ ],
"enabled": true,
"severity": "info"
},
@@ -220,7 +229,9 @@
"id": "SS-INFO-003",
"name": "Untrusted Server Certificate",
"owaspCode": "ASI10",
- "astCodes": ["AST08"],
+ "astCodes": [
+ "AST08"
+ ],
"enabled": true,
"severity": "info"
},
@@ -228,7 +239,9 @@
"id": "SS-INFO-004",
"name": "Legacy MCP Protocol / Transport",
"owaspCode": "ASI04",
- "astCodes": ["AST08"],
+ "astCodes": [
+ "AST08"
+ ],
"enabled": true,
"severity": "info"
}
diff --git a/src/SignalSentinel.Scanner/Program.cs b/src/SignalSentinel.Scanner/Program.cs
index 8dc5f17..1fb0b1b 100644
--- a/src/SignalSentinel.Scanner/Program.cs
+++ b/src/SignalSentinel.Scanner/Program.cs
@@ -1,6 +1,7 @@
using System.Diagnostics;
using System.Globalization;
using System.Net;
+using System.Reflection;
using System.Text.RegularExpressions;
using SignalSentinel.Core.Models;
using SignalSentinel.Core.RuleFormats;
@@ -25,13 +26,20 @@ namespace SignalSentinel.Scanner;
///
public static class Program
{
- // v2.5.0: derived from the assembly version (itself driven by the .csproj
- // property) instead of a hardcoded literal, so this can no longer
- // drift out of sync with the package version across releases the way it did
- // through v2.4.0/v2.4.1/v2.5.0 (this constant was stuck at "2.4.0" while the
- // .csproj moved to 2.5.0).
+ // Derived from the assembly rather than a hardcoded literal, so it cannot
+ // drift from the package version the way it did through v2.4.0/v2.4.1/v2.5.0
+ // (this constant was stuck at "2.4.0" while the project moved to 2.5.0).
+ //
+ // InformationalVersion is preferred over AssemblyVersion because the latter is
+ // a four-part numeric quad: MSBuild strips semver prerelease labels from it, so
+ // a 2.6.0-rc.1 build would report itself as plain "2.6.0". SourceLink appends
+ // "+" to InformationalVersion, hence the split.
private static readonly string Version =
- typeof(Program).Assembly.GetName().Version?.ToString(3) ?? "0.0.0";
+ typeof(Program).Assembly
+ .GetCustomAttribute()
+ ?.InformationalVersion.Split('+')[0]
+ ?? typeof(Program).Assembly.GetName().Version?.ToString(3)
+ ?? "0.0.0";
private const string RubricVersion = "1.0";
// Security: Limits for input validation
diff --git a/src/SignalSentinel.Scanner/SignalSentinel.Scanner.csproj b/src/SignalSentinel.Scanner/SignalSentinel.Scanner.csproj
index 7565c5b..3477b05 100644
--- a/src/SignalSentinel.Scanner/SignalSentinel.Scanner.csproj
+++ b/src/SignalSentinel.Scanner/SignalSentinel.Scanner.csproj
@@ -3,8 +3,8 @@
Exe
SignalSentinel.Scanner
- 2.5.0
- Fast, deterministic, offline-capable first-pass security aid for MCP servers and Agent Skill authors. 32 security rules aligned with OWASP Agentic AI Top 10 (ASI) and OWASP Agentic Skills Top 10 (AST). v2.5 tracks the MCP 2026-07-28 specification (stateless core, legacy transport/protocol currency notice), adds a skill supply-chain rule for unpinned GitHub dependency references (SkillJacking), recognises Universal Skill Format risk_tier and permissions.deny_write fields, and consolidates the full v2.4.x backlog: orchestrator-agnostic scope filtering, phrase-based detection for MCP and skill rule families, corrected OWASP AST10 taxonomy alignment, canonical skill identity for suppression/scope matching, and behavioural auth/TLS/non-MCP-endpoint probing.
+
+ Fast, deterministic, offline-capable first-pass security aid for MCP servers and Agent Skill authors. Security rules aligned with OWASP Agentic AI Top 10 (ASI) and OWASP Agentic Skills Top 10 (AST), covering tool poisoning, rug pulls, prompt injection, credential exfiltration, skill supply-chain risk and MCP specification currency. Ships SARIF and Sigma output, suppressions, triage, baselines and offline mode. See the changelog for what each release adds.
mcp;security;scanner;ai-agents;owasp;agentic-ai;cli;dotnet-tool;websocket;agent-skills;skill-scanning;sarif;sigma;suppressions;ast;triage;pre-commit
@@ -18,126 +18,7 @@
README.md
-
-v2.5.0 - MCP 2026-07-28 Currency, SkillJacking Detection, Universal Skill Format Fields
-- NOTE: first public release since v2.3.0 - v2.4.0 and v2.4.1 were completed but never pushed/tagged; this changelog folds all three releases' changes together so there is no public gap.
-- NEW: SS-INFO-004 Legacy MCP Protocol / Transport - flags servers negotiating a protocolVersion older than the current MCP specification (2026-07-28), or reached over the deprecated legacy HTTP+SSE transport. Informational currency notice, not a vulnerability, ahead of the spec's 12-month deprecation window.
-- NEW: SS-020 OAuth Compliance extended with an advisory finding disclosing that the behavioural auth probe confirms Bearer enforcement but cannot verify RFC 9207 issuer validation or the DCR-to-CIMD migration introduced by the 2026-07-28 spec.
-- NEW: SS-029 Skill Unpinned Dependency Reference - detects skill instructions or bundled scripts referencing a GitHub dependency by a floating branch (main/master/head/develop/dev/latest/trunk) or an unpinned git+https:// install URL instead of a pinned tag/release/commit SHA. Pulls the core "SkillJacking" supply-chain detection forward from the v3.0 roadmap.
-- NEW: Universal Skill Format risk_tier recognition on SS-017 - a low-declared risk tier contradicted by observed dangerous capabilities produces a High "Risk Tier Understated" finding; an undeclared risk_tier with the same signals produces an Info nudge.
-- NEW: Universal Skill Format permissions.deny_write recognition on SS-028 - escalates High to Critical when the identity file written to is also listed in the skill's own deny_write declaration (self-contradiction).
-- FIX: FrontmatterParser dotted-key regex (network.allow, permissions.deny_write) silently failed to parse from real SKILL.md files; the rule logic consuming these fields was already correct, the parser simply never fed it the data.
-- QUALITY: 32 total rules (was 27); 422 tests (was 395); 0 warnings, 0 errors.
-- COMPATIBILITY: No operator-visible breaks. New fields are additive: SkillDefinition.DenyWrite (empty list when undeclared), SecurityGrade.Inconclusive.
-
-v2.4.1 - Credibility Fixes: Grading, TLS Classification, Skill Identity Persistence
-- NEW: Inconclusive grade for scans with zero scannable surface (zero servers, zero skills), replacing a misleading Grade A.
-- NEW: SS-INFO-003 Untrusted Server Certificate - distinguishes a TLS handshake failing on certificate validation from a generic connectivity failure, with operator-actionable remediation text.
-- NEW: SS-028 Skill Identity/Memory File Write Access - detects skills that write to agent identity/memory files (AGENTS.md, CLAUDE.md, MEMORY.md, SOUL.md), the persistence technique behind the ClawHavoc malicious-skill campaign (Jan-Feb 2026, 1,184 malicious skills).
-- UPGRADED: SS-INFO-001 also fires on 404-without-JSON-RPC responses (not just HTML-200); the auth probe no longer emits a finding when the probe itself failed to complete.
-- UPGRADED: SS-026 extended to evaluate skill frontmatter descriptions and body text, not just MCP tool descriptions.
-- UPGRADED: SS-024 recognises inline signature/content_hash frontmatter for integrity verification; SS-017 recognises a boolean network: grant as strictly worse than a declared network.allow domain allowlist.
-- NEW: Canonical skill identity (SkillDefinition.CanonicalSkillName, Finding.CanonicalSkillName) so SuppressionManager and ScopeManager can no longer disagree about which skill a rule fired on.
-- NEW: ScanResult.SuppressionDelta and ScanStatistics.ServersProbed scan-statistics additions.
-- FIX: corrected the OWASP Agentic Skills Top 10 AST05 label to match the real published taxonomy ("Untrusted External Instructions", not the previously invented "Unsafe Deserialisation").
-- QUALITY: 27 total rules (unchanged); 395 tests (up from 366).
-
-v2.4.0 - Context-Aware Detection, Behavioural Probes and Orchestrator-Agnostic Scope
-- NEW: Orchestrator-agnostic scope model - a `.sentinel-scope.json` file (schema v1.0) and matching CLI flags `--scope`, `--include-skills`, `--exclude-skills`, `--include-servers`, `--exclude-servers` let you declare which skills and MCP servers are live attack surface at your orchestrator. Out-of-scope findings are tagged "dormant" and retained in every report format for audit trail, but do not contribute to the grade. The scanner does not parse orchestrator-native config files of any vendor - any orchestrator, CI pipeline, or human can produce the scope file.
-- NEW: Shared `InjectionPatterns` library tightened (SS-001 Tool Poisoning + SS-011 Skill Injection + SS-014 Skill Exfiltration). `InstructionInjection` now requires canonical prompt-injection phrasing (override-intent verbs + target noun; IMPORTANT: labels; role-hijack phrases; SYSTEM PROMPT: markers) rather than bare modal verbs (MUST / ALWAYS / IMPORTANT). `DataExfiltration` now requires an outbound verb paired with a data-object and a `to / via / through` target, or a network fetcher within 80 characters of an explicit `https?://` URL. `PrivilegeEscalation` now requires a canonical escalation verb or a pinned noun phrase, eliminating false positives on bare words `privilege` and `elevate`.
-- NEW: `SkillObfuscationRule.ConditionalTrigger` now requires a covert-action verb within 120 characters of the trigger phrase, so "If the user says X, respond with Y" no longer misclassifies as obfuscation.
-- NEW: `SkillExcessivePermRule.UnrestrictedNetwork` now requires a request / grant / declaration context or a YAML-style `network: unrestricted` declared-capability form, eliminating false positives on "any URL" in descriptive prose.
-- NEW: `ExfiltrationPatterns.HttpDataSend` (SS-014 EXFIL-001) now requires a data-object before `to`, so legitimate `POST to /api/status` no longer fires. URL-adjacent outbound verbs (`POST to https://...`) still fire.
-- NEW: SS-INFO-002 Non-Public Target notice - surfaces an informational finding when the scanner is pointed at a loopback, RFC 1918, link-local or non-public-hostname target. Makes scan scope explicit and exempts transport-posture rules (SS-020) that only make sense against a public endpoint.
-- NEW: SS-026 Instructional Tool Description - Medium-severity rule catching tool descriptions written to drive the agent ("you must call this first", "ignore all previous instructions") rather than describe the tool. Both a tool-poisoning signature and a skill-authoring anti-pattern.
-- NEW: SS-020 behavioural auth probe - sends one deliberate unauthenticated MCP `initialize` request and classifies the server as enforced / open / unclear based on the WWW-Authenticate challenge. Behavioural posture replaces config introspection, so auth configured via v2.3.1 `headers: { Authorization: Bearer ... }` is now recognised alongside `env`-based credentials.
-- NEW: Stale suppression detection - warns the operator when a `.sentinel-suppressions.json` entry has expired or no longer matches any finding in the current scan.
-- UPGRADED: SS-008 (sensitive data) now distinguishes public TLS material (certificate, CA bundle, public key) from secret credential material (private key, password, api key, bearer/access/refresh token, client secret, vault secret). Tools like `tls_expiry` and `get_certificate` are classified correctly.
-- UPGRADED: SS-002 (overbroad permissions) is now phrase-based. Recognises `root access`, `sudo`, `su -`, `setuid`, `admin privilege`, `privilege escalation`, `elevated token`, `runas administrator`, `root shell`, `impersonate`.
-- UPGRADED: SS-005 (code execution) is now phrase-based. Recognises concrete primitives (`eval(`, `exec(`, `Runtime.exec`, `Process.Start`, `os.system`, `subprocess.`, `shell -c`, `bash -c`, `PowerShell -Command`, `cmd.exe /c`) and `runs arbitrary code` phrasing.
-- UPGRADED: SS-006 (memory write) distinguishes agent-memory vocabulary (long-term memory, conversation history, episodic/semantic memory, knowledge base) from host-resource vocabulary (memory usage, /proc/meminfo, heap, swap). Vector store coverage extended to Chroma, FAISS, Pinecone, Weaviate, Qdrant, Milvus, pgvector.
-- QUALITY: 27 total rules (was 26); 366 tests (was 254); 0 warnings, 0 errors. The expanded suite adds 16 scope-manager tests and a substantial pattern-accuracy regression suite that locks in observed production false-positive kills.
-- COMPATIBILITY: No operator-visible breaks. Scoring rubric, suppression schema (v1.0) and existing report shapes unchanged. New fields (`Finding.Scope`, `ScanScope.ScopeSource`, `ScanScope.InScopeSkills`, `ScanScope.DormantSkills`, `ScanScope.InScopeServers`, `ScanScope.DormantServers`) are additive and nullable.
-
-v2.3.0 - Triage, Suppressions, Scope Disclosure, Non-MCP Detection
-- NEW: .sentinel-suppressions.json schema v1.0 - formally accept risk on specific findings, with justification, approver, expiry. Retained in JSON/SARIF/Markdown/HTML for audit trail under an "Accepted Risks" section.
-- NEW: SS-INFO-001 Non-MCP Endpoint Detected - surfaces an informational finding when --remote is pointed at a host that does not implement MCP (e.g. React SPA catch-all returning text/html). No more misleading "Grade A" against web apps.
-- NEW: Confidence-aware triage - --min-confidence <f> filters low-confidence findings; --triage demotes them to Low but keeps them visible; --fail-on <severity> replaces legacy pass/fail semantics.
-- NEW: Scan history + delta - --save-history persists runs to .sentinel/history; sentinel-scan diff <baseline.json> <current.json> shows resolved / new / grade-attribution deltas.
-- NEW: Per-environment scoping - --environment <dev|staging|prod>, suppressions can scope to an environment.
-- NEW: OWASP Agentic Skills Top 10 (AST01..AST10) mapping on every finding - SARIF tags + Markdown/HTML headers show both ASI and AST codes.
-- NEW: Explicit scope disclosure block on every report - tells users what was scanned, what was not, and which complementary tools to combine with (Bandit, Gitleaks, Semgrep, Enkrypt Skill Sentinel by default).
-- NEW: --list-rules prints the rule registry with OWASP/AST/severity columns.
-- NEW: --ignore-rule SS-xxx[,SS-yyy] for ephemeral per-run exclusions.
-- IMPROVED: SS-012 now uses case-insensitive capability matching plus a lemma table ("Network" satisfies "network access", "filesystem" satisfies "filesystem access" etc.) - eliminates mechanical false positives from capitalised sentence starts.
-- IMPROVED: 26 total rules (was 25); 240 tests; 0 warnings, 0 errors.
-- FIX: Non-MCP endpoint detection now runs before HTTP status validation - SS-INFO-001 fires even when the SPA catch-all returns 4xx/5xx with HTML or plain-text bodies.
-- FIX: ScanHistoryManager now deserialises lowercase enum strings in v2.2 JSON baselines so `sentinel-scan diff` works across the 2.2 -> 2.3 boundary.
-- POSITIONING: README and report language softened towards "fast, deterministic, first-pass authoring aid" - full positioning reset lands in v3.0.0.
-- BREAKING: None. Scoring rubric unchanged; CI gates from v2.2 continue to work. Grade-semantics reset is scheduled for v3.0.0 (see ROADMAP_V3.0.md).
-
-v2.2.0 - Rug Pull Detection, SARIF, Sigma Rules, Offline Mode
-- NEW: SS-022 Rug Pull Detection - catches silent tool schema mutations between scans via hashed baselines
-- NEW: SS-023 Shadow Tool Injection - typosquat/Levenshtein detection across configured servers
-- NEW: SS-024 Skill Integrity Verification - hash and signature checks for Agent Skills
-- NEW: SS-025 Excessive Tool Response Size - bounds live MCP tool responses
-- NEW: SARIF v2.1.0 output format (--format sarif) for GitHub Code Scanning and IDE integration
-- NEW: --baseline/--update-baseline flags to persist and compare tool schemas between scans
-- NEW: --offline flag enforces zero-egress operation, verified by dedicated offline-verification CI job
-- NEW: --sigma-rules flag loads Sigma YAML rules for custom MCP/Skill pattern detection
-- NEW: Finding deduplication engine collapses identical matches with OccurrenceCount indicator
-- 25 total security rules (16 MCP + 9 Skill); 195 tests; 0 warnings, 0 errors
-
-v2.1.1 - Security Hardening Release
-- SECURITY: All GitHub Actions pinned to SHA hashes (supply chain protection)
-- SECURITY: SSRF protection on --remote URL (blocks private IPs, cloud metadata)
-- SECURITY: Symlink escape protection in skill parser (resolves symlinks before path checks)
-- SECURITY: Environment variable denylist for stdio MCP transport (blocks PATH, LD_PRELOAD, etc.)
-- SECURITY: TLS 1.2/1.3 enforcement on HTTP connections
-- SECURITY: Bounded stdio reads (10MB limit prevents memory exhaustion)
-- SECURITY: Proper JsonDocument disposal prevents memory leaks
-- SECURITY: WebSocket dispose timeout prevents hangs
-- SECURITY: Regex timeouts added to all 23 MCP rule patterns (consistency with skill rules)
-- SECURITY: Markdown report hardening (escaping + truncation)
-- SECURITY: Trivy scan now blocks release on CRITICAL/HIGH CVEs
-- SECURITY: CI vulnerability check now fails build on detected vulnerabilities
-- FIX: RegexOptions.Compiled removed from source-generated regex (ignored by generator)
-- FIX: HashPinning handles duplicate tool names without crash
-- FIX: Finding.Confidence validates 0.0-1.0 range
-- FIX: Environment.Exit(0) replaced with proper return flow
-- 44 security audit findings addressed (1 Critical, 7 High, 17 Medium, 12 Low)
-
-v2.1.0 - Enhanced Inline Code Block Scanning
-- ENHANCED: SS-016 now scans markdown code blocks (bash, python, etc.) for malicious patterns
-- ENHANCED: SS-016 detects hardcoded absolute user paths (/root/, /home/user/, C:\Users\) in code blocks
-- ENHANCED: SS-012 detects inline code execution (python3 -c, bash -c, node -e) as scope violation
-- These enhancements catch skills that embed executable commands in markdown code fences
-
-v2.0.0 - Agent Skill Scanning + New MCP Rules
-- NEW: Agent Skill scanning (SKILL.md format) with 8 dedicated rules (SS-011 to SS-018)
-- NEW: Skill auto-discovery for Claude Code, Codex CLI, Cursor, Windsurf
-- NEW: Bundled script analysis (.py, .sh, .ps1, .js, .ts)
-- NEW: Credential Hygiene rule (SS-019) - detects hardcoded secrets in MCP configs
-- NEW: OAuth 2.1 Compliance rule (SS-020) - verifies remote server authentication
-- NEW: Package Provenance rule (SS-021) - checks npm/PyPI supply chain
-- NEW: OWASP MCP Top 10 dual mapping alongside ASI01-ASI10
-- NEW: Shared detection patterns (Exfiltration, Credential, Obfuscation)
-- NEW: --skills CLI flag for skill scanning
-- 21 total security rules (13 MCP + 8 Skill)
-- Combined MCP + Skill unified reporting
-
-v1.1.0 - WebSocket Transport Support
-- Added WebSocket transport (ws:// and wss:// URLs)
-- Auto-detection of transport from URL scheme
-- Config file support for websocket transport type
-
-v1.0.0 - Initial Release
-- 10 security rules mapped to OWASP Agentic AI Top 10 (ASI01-ASI10)
-- Auto-discovery for Claude Desktop, Cursor, VS Code, Windsurf, Zed
-- A-F scoring system with OWASP compliance matrix
-- JSON, Markdown, and HTML report generation
-- CI mode with exit codes for automated pipelines
-
+ See https://github.com/SignalCoding/signal-sentinel-scanner/blob/main/CHANGELOG.md
true