Cerbi.Scanner 1.2.0

dotnet tool install --global Cerbi.Scanner --version 1.2.0
                    
This package contains a .NET tool you can call from the shell/command line.
dotnet new tool-manifest
                    
if you are setting up this repo
dotnet tool install --local Cerbi.Scanner --version 1.2.0
                    
This package contains a .NET tool you can call from the shell/command line.
#tool dotnet:?package=Cerbi.Scanner&version=1.2.0
                    
nuke :add-package Cerbi.Scanner --version 1.2.0
                    

Cerbi Scanner

Static logging governance scanner for C#, Go, Java, Node/TypeScript, and Python repositories.
The scanner also detects common AI dependencies and Cerbi AI runtime governance coverage signals from dependency/configuration manifests inside the explicit scan root. Read-only by default — the scanner never modifies your source code or uploads any data without an explicit opt-in.


Installation

dotnet tool install -g Cerbi.Scanner

Requires the .NET 10 LTS SDK (or .NET 10 runtime for an already-published tool). After install, the cerbi-scanner command is available globally.

To update:

dotnet tool update -g Cerbi.Scanner

Runtime support

Cerbi.Scanner targets net10.0 and is packaged as a framework-dependent .NET global tool. This release does not intentionally change scanner behavior or CLI options, but it does require a .NET 10 LTS runtime on machines that install and run the tool. .NET 8-only hosts must install .NET 10 LTS before running cerbi-scanner.

Breaking change: support for installing/running the global tool on .NET 8-only environments has been removed as part of the move to the current LTS runtime.


Projects

Project Type Purpose
Cerbi.Scanner.Core Library Stable contracts: models, interfaces, rule engine
Cerbi.Scanner.Cli Executable cerbi-scanner CLI — entry point for local and CI use
Cerbi.Scanner.DotNet Library Roslyn-based C# logging-call extractor
Cerbi.Scanner.Go Library Go (zap / zerolog) logging-call extractor
Cerbi.Scanner.Java Library Java (Log4j2 / SLF4J) logging-call extractor
Cerbi.Scanner.Node Library JavaScript / TypeScript (Winston / Pino) logging-call extractor
Cerbi.Scanner.Python Library Python (stdlib logging / structlog) logging-call extractor
Cerbi.Scanner.Reporting Library JSON and HTML report writers
Cerbi.Scanner.Sarif Library SARIF 2.1.0 report writer
Cerbi.Scanner.Telemetry Library Telemetry infrastructure (disabled in v1 — no data is sent)
Cerbi.Scanner.CerbiShieldClient Library Optional CerbiShield upload client

AI Governance Detection

Cerbi Scanner detects AI-related dependencies and configuration from package/configuration manifests in the directory you explicitly scan. It does not perform machine-wide search, subscription crawling, neighbouring repository discovery, or prompt/content capture.

Detected signals include:

  • Azure OpenAI, OpenAI, and Anthropic SDKs.
  • Microsoft Semantic Kernel, LangChain, AutoGen, LlamaIndex, CrewAI, Haystack, LiteLLM, and Vercel AI SDK style orchestration packages.
  • MCP SDKs and MCP configuration files.
  • Common vector database clients such as Qdrant, Pinecone, Weaviate, and Chroma.
  • Known Cerbi/CerbiShield AI runtime governance instrumentation packages.

AI dependency findings are metadata-only. Reports and authenticated uploads do not include raw prompts, responses, source snippets, MCP server values, provider keys, or file contents. If AI dependencies are detected and no known Cerbi runtime governance instrumentation is detected in the same scan scope, the scanner emits a coverage-gap finding so the team can decide whether to instrument or register the application for Pro++ AI Logging Governance.


CLI Usage

Codespaces demo / report-only mode

Use report-only mode for the default public Codespaces walkthrough. It writes JSON, SARIF, and Markdown outputs while returning success even when demo findings are present.

mkdir -p scan-results

cerbi-scanner scan \
  --path . \
  --policy policies/cerbi-policy.yml \
  --fail-on none \
  --format json --output scan-results/findings.json \
  --sarif scan-results/findings.sarif \
  --summary scan-results/build-summary.md

CI/CD gate mode

Use gate mode in CI/CD when high-severity findings should fail the build or release. --fail-on error maps to the scanner's high-severity threshold and returns exit code 1 when matching findings are present.

mkdir -p scan-results

cerbi-scanner scan \
  --path . \
  --policy policies/cerbi-policy.yml \
  --fail-on error \
  --format json --output scan-results/findings.json \
  --sarif scan-results/findings.sarif \
  --summary scan-results/build-summary.md

Backwards-compatible legacy command:

cerbi-scanner audit .
cerbi-scanner scan [path] [options]
cerbi-scanner audit <path> [options]

Options

Option Description
--path <dir> Scan path for scan; defaults to the current directory when omitted
--policy <file> Load a Cerbi YAML policy or JSON governance config (cerbi-policy.yml, cerbi-policy.yaml, cerbi.logging.yml, cerbi_governance.json, or cerbi_governance.generated.json)
--profile <file> Load a legacy Cerbi governance profile JSON file
--format console\|json\|sarif\|markdown\|html Primary output format (default: console)
--output <file> Write report to this file (omit to print JSON to stdout)
--fail-on info\|warning\|low\|medium\|high\|critical\|warn\|error\|blocker\|none Exit 1 if any finding at or above this severity exists; error maps to high, blocker maps to critical, and none disables CI failure
--sarif <file> Also write SARIF output regardless of primary --format
--summary <file> Also write a Markdown CI summary regardless of primary --format
--generate-profile Generate a starter cerbi_governance.json from the scanned codebase
--profile-output <file> Write generated profile to this path
--generate-policy Generate a starter YAML policy; use --output to choose the path
--exclude <pattern> Exclude path patterns (repeatable)
--no-snippets Omit source snippets from findings (privacy mode)
--config <file> Path to cerbi-scanner.json (defaults to cerbi-scanner.json in scan root)
--upload Upload results to CerbiShield — disabled by default, requires CERBI_TOKEN

Examples

# Scan current directory, print JSON to stdout
cerbi-scanner scan .

# Scan with a governance profile
cerbi-scanner scan ./src --profile cerbi_governance.json

# Write a SARIF report
cerbi-scanner scan ./src --format sarif --output cerbi-scan.sarif

# Fail CI on any critical finding
cerbi-scanner scan ./src --fail-on critical

# Generate a starter profile
cerbi-scanner scan ./src --generate-profile --profile-output cerbi_governance.json

# Use a custom config file
cerbi-scanner scan ./src --config path/to/cerbi-scanner.json

# Upload results (explicit opt-in, requires CERBI_TOKEN env var)
CERBI_TOKEN=<key> cerbi-scanner scan ./src --format json --output out.json --upload

Policy-as-code (preview)

The scanner now supports YAML policy files named cerbi-policy.yml, cerbi-policy.yaml, or cerbi.logging.yml in the scan root, or an explicit --policy <file> path. If no policy file exists, the scanner uses safe built-in defaults plus any existing cerbi-scanner.json configuration.

Supported policy settings today:

  • disabledRules — disables rule IDs such as CERBI006 or legacy CERBI-006.
  • severityOverrides — changes scanner severity for a rule.
  • failThreshold — default CI threshold (warning, error, blocker, or none; legacy severities still work).
  • allowedStructuredFields — when non-empty, fields outside the allow-list produce a policy finding.
  • disallowedStructuredFields — fields that must not be logged.
  • sensitiveDataPatterns — additional sensitive field names to detect.

Output formats: console, json, sarif, markdown, and existing html. Markdown is designed for Azure DevOps build summaries. SARIF and JSON share the same scanner finding contract as console output.

This repository contains the scanner CLI and docs. No Azure DevOps task-extension source is present in this checkout, so pipeline integration should invoke cerbi-scanner scan directly (or a task wrapper should shell out to it) rather than duplicating detection logic. See cerbi-policy.sample.yml and docs/ci/azure-pipelines-policy-example.yml.

Configuration — cerbi-scanner.json

Place a cerbi-scanner.json in your repository root (or pass --config <path>) to control scanner behaviour without modifying CI scripts.

{
  // Glob patterns excluded from every scan (in addition to --exclude flags)
  "defaultExcludes": ["**/obj/**", "**/bin/**", "**/node_modules/**", "**/*.g.cs"],

  // Files larger than this are skipped (0 = no limit)
  "maxFileSizeKb": 512,

  // Abort scan after this many seconds (0 = no timeout)
  "scanTimeoutSeconds": 120,

  // Equivalent to --fail-on: info | low | medium | high | critical
  "failThreshold": "high",

  // Per-rule severity overrides  (rule ID → new severity)
  "severityOverrides": {
    "CERBI004": "medium"
  },

  // Config-based suppressions (inline suppression via comments is a future feature)
  "suppressions": [
    {
      "ruleId": "CERBI001",           // required
      "filePath": "tests/Fixtures",    // optional substring match (/ or \ normalised)
      "fieldName": "password",         // optional field name match
      "reason": "Test fixture — intentional disallowed field",
      "expiresOn": "2026-12-31"        // optional ISO-8601; suppression ignored after this date
    }
  ]
}

Suppression matching rules

A finding is suppressed when all non-empty fields of a suppression entry match:

Entry field Match rule
ruleId Required. Case-insensitive exact match.
filePath Substring match against the finding's file path (separators normalised to /). Omit to match any file.
fieldName Case-insensitive exact match against the finding's AffectedField. Omit to match any field.
expiresOn When present and past today (UTC), the suppression is ignored and a warning is emitted.

Suppressed findings are excluded from all reports and do not trigger CI failure.


Building

dotnet build Cerbi.Scanner/Cerbi.Scanner.slnx

Running Tests

dotnet test Cerbi.Scanner/Cerbi.Scanner.slnx

Note: Cerbi.Scanner.Cli.Tests runs the compiled cerbi-scanner binary as a child process.
Build the solution before running tests so the binary exists.


Core Contracts

Models (Cerbi.Scanner.Core.Model)

Type Purpose
ScanRequest Input to the scanner pipeline
ScanResult Output of a full repository scan
ScanSummary Aggregated statistics derived from ScanResult
ScanFinding A single governance violation
FindingLocation Source file + line + column
FindingSeverity Info / Low / Medium / High / Critical
FindingConfidence Low / Medium / High
LoggerFramework Detected logging framework enum
LoggingCall A single extracted logging call
DiscoveredField A field found inside a logging call
GovernanceProfile Rules loaded from a profile file
ScannerRule Serialisable rule descriptor
RuleEvaluationContext Inputs passed to a rule during evaluation
RuleEvaluationResult Output of one rule evaluation

Interfaces (Cerbi.Scanner.Core.Abstractions)

Interface Purpose
IRepositoryScanner Scans an entire repository
ILanguageScanner Scans a single file for one language
ILoggingCallExtractor Extracts logging calls from raw source text
IScannerRule A single evaluable governance rule
IReportWriter Writes a ScanResult to a stream
IGovernanceProfileLoader Loads a GovernanceProfile from a source
IScanResultUploader Uploads results to an external system

JSON Output Shape

{
  "schemaVersion": "1.0",
  "scannerVersion": "0.1.0-alpha",
  "repositoryPath": "/path/to/repo",
  "scannedAt": "2024-01-01T00:00:00+00:00",
  "elapsedMs": 42,
  "scannedFileCount": 3,
  "totalLoggingCallsFound": 12,
  "ciThresholdBreached": false,
  "findingCount": 2,
  "findings": [
	{
	  "ruleId": "CERBI001",
	  "ruleName": "DisallowedField",
	  "severity": "High",
	  "confidence": "High",
	  "explanation": "Field 'password' is disallowed by the active profile.",
	  "remediation": "Remove 'password' from the log call.",
	  "filePath": "/path/to/repo/Service.cs",
	  "lineNumber": 42,
	  "column": 12,
	  "sourceSnippet": "_logger.LogInformation(\"User {password} logged in\", password);",
	  "loggerFramework": "Mel"
	}
  ]
}

The schemaVersion field is stable. Consumers should read it and reject versions they do not support.



Enterprise Security Posture

What is and is not included in reports

Every report format (JSON, SARIF, HTML, console) is governed by the same data-minimisation rules:

Data element JSON SARIF HTML Console Notes
Source code Never Never Never Never Source snippets are stripped before serialisation. The --no-snippets flag is a belt-and-suspenders guard but is not required.
File paths Yes — local absolute path Yes Yes Yes Paths reflect the local filesystem at scan time. Normalise/truncate to relative paths before sharing reports externally.
Log message templates Yes Yes Yes Top findings only Templates contain field names only, never field values from runtime logs.
Sensitive literal values Redacted Redacted Redacted Redacted All explanation text passes through ReportRedactor before serialisation. See Report redaction.
Scan metadata Yes Yes Yes Summary line Includes scanner version, UTC timestamp, repo path hash (not the full path), and file count.
Telemetry / analytics Never by default Never by default Never by default Never by default Anonymous telemetry is opt-in and privacy-bucketed; AI dependency findings are not sent through anonymous telemetry.
Uploaded data Never by default Never by default Never by default n/a Upload requires explicit --upload + CERBI_API_KEY. See Upload opt-in.

Data collection

Data Collected? Notes
Source code Never Source snippets are omitted from JSON/SARIF output by default (--no-snippets also suppresses them from HTML).
File paths Yes — local paths in findings Make paths relative before sharing reports externally.
Log message templates Yes — in finding explanations Templates may contain field names but not field values.
Sensitive literal values Redacted Explanation text is passed through ReportRedactor before serialisation. See below.
Telemetry / analytics Never by default Anonymous scanner telemetry is opt-in, bucketed, and does not include AI dependency findings, source code, file paths, repo names, raw findings, secrets, or PII.
Scan results upload Never by default Results are only uploaded when --upload is explicitly passed and CERBI_API_KEY is set.

Report redaction

Before any finding explanation is written to a JSON, SARIF, or HTML report the text is passed through ReportRedactor.Redact() which replaces:

  • Password assignmentspassword="...", pwd='...', secret="...", token="...", etc.
  • Connection-string password fieldsPassword=abc123;Password=[REDACTED]
  • Long Base64-like tokens (≥ 32 chars) after key=, token=, bearer=, etc.

Redaction is best-effort. Do not treat reports as safe for public sharing without a manual review.

Safe handling of file paths and message templates

The scanner touches the filesystem in three places; each is hardened as follows:

  1. Scan root — passed on the CLI and resolved to an absolute, normalised path. The scanner never traverses symlinks that resolve outside the scan root.
  2. Output path — the --output target is written using FileStream with explicit FileMode.Create. Parent directories are created atomically before the file is opened. The path is not interpreted as a format string or shell command.
  3. Message templates in findings — the raw template string (e.g. "User {UserId} logged in") is copied verbatim into the finding MessageTemplate field and then passed through ReportRedactor before serialisation. Template strings are never string.Format-evaluated by the scanner, so there is no format-injection risk.

Upload opt-in

The --upload flag is an explicit opt-in. It will not transmit any data unless:

  1. --upload is present on the command line, and
  2. The CERBI_API_KEY environment variable is set.

Omitting either causes the CLI to exit with an error rather than silently skipping the upload.

Telemetry

The scanner has no telemetry of any kind:

  • DOTNET_CLI_TELEMETRY_OPTOUT=1 is declared in Directory.Build.props for all child processes.
  • No HTTP client, analytics SDK, or background task is wired into the scanner or reporting pipeline.
  • The CerbiShieldUploadClient stub is the only network-capable component; it is a no-op unless explicitly enabled.

Suppression audit trail

When a finding is suppressed via cerbi-scanner.json, the suppression is recorded in the JSON report alongside the finding:

{
  "ruleId": "CERBI001",
  "suppressed": true,
  "suppressionReason": "legacy auth service, tracked in JIRA-123"
}

Suppressions with a past expiresOn date are ignored at scan time and emit a console warning. This ensures suppressions are reviewed periodically rather than silently accumulating forever. The CI workflow treats expired-suppression warnings as advisory (non-blocking) so that teams have time to renew or remove them.

File path safety

  • Report output paths are validated and their parent directories are created atomically.
  • --output is required when --format is sarif or html (stdout is JSON-only) to prevent binary content from accidentally polluting shell pipes.
  • The scanner never follows symlinks outside the scan root.

Binary file protection

Files are checked for null bytes before Roslyn parsing. Any file that appears binary is skipped silently — it will not appear in findings or error output. This prevents false positives from compiled artifacts accidentally included in source trees.

Max file size guard

maxFileSizeKb (default: unlimited) skips files larger than the configured threshold before loading them into memory. Set this to a reasonable value (e.g. 512) in production CI to prevent a single large generated file from causing memory pressure.

Build reproducibility

All projects are built with <Deterministic>true</Deterministic> and <ContinuousIntegrationBuild>true</ContinuousIntegrationBuild> (when GITHUB_ACTIONS=true), producing byte-for-byte identical output across machines for the same inputs.

Suppression expiry

Config-based suppressions with a past expiresOn date are ignored and emit a console warning at scan time. This ensures suppressions are reviewed periodically rather than silently accumulating forever.


Current Scope

Supported languages: C# (.NET), Go, Java, Node/TypeScript, Python
Supported frameworks: MEL, Serilog, NLog, log4net, Cerbi, zap, zerolog, Log4j2, SLF4J, Winston, Pino, stdlib logging, structlog
Rules implemented: disallowed fields, sensitive fields, missing required fields
Output formats: JSON (deterministic), SARIF 2.1.0, HTML, console summary

Non-Goals (this release)

  • No live connection to the CerbiShield platform (CerbiShieldClient requires explicit --upload + API key).
  • No Roslyn semantic analysis (syntax-only; no full compilation).
  • No IDE extension or editor integration.
  • No persistent storage or result history.
  • Inline suppression comments (// cerbi:suppress) — planned for a future release; use cerbi-scanner.json suppressions today.

Architecture Notes

cerbi-scanner audit <path>
  └── MultiLanguageRepositoryScanner
		├── DotNetRepositoryScanner  (.cs  — Roslyn syntax tree)
		├── GoRepositoryScanner      (.go  — regex / AST heuristic)
		├── JavaRepositoryScanner    (.java — regex)
		├── NodeRepositoryScanner    (.js .ts .mjs .cjs — regex)
		├── PythonRepositoryScanner  (.py  — regex)
		└── AiDependencyRepositoryScanner (package/config manifests — metadata only)
  └── RuleEngine                ← applies IScannerRule[] to each LoggingCall
  └── IReportWriter             ← JSON | SARIF | HTML | console
  └── IScanResultUploader       ← no-op unless --upload + CERBI_API_KEY

The Core library has no external dependencies beyond the .NET 10 BCL. DotNet depends on Microsoft.CodeAnalysis.CSharp (source-only, no compilation).


CI

The included GitHub Actions workflow (.github/workflows/ci.yml) runs on every push and pull request:

  1. restoredotnet restore
  2. builddotnet build --no-restore -c Release (warnings-as-errors enabled)
  3. testdotnet test --no-build -c Release with TRX result publication
  4. packdotnet pack --no-build -c Release
  5. self-scancerbi audit against tests/fixtures/SampleDotNetApp (smoke test)
  6. SARIF upload — uploaded to GitHub Code Scanning when CERBI_SARIF_UPLOAD=true is set on the default branch

Policy-as-code and automation outputs

Cerbi Scanner now accepts policy files named cerbi-policy.yml, cerbi-policy.yaml, or cerbi.logging.yml in the scan root, or an explicit --policy <file> path. If no policy file is present, the scanner uses safe built-in defaults for sensitive fields, credential-like fields, risky object logging, high-cardinality field names, dynamic/debug-prone logging paths, and exception/object-state exposure.

Supported stable rule IDs are:

Rule Purpose
CERBI001 Sensitive data appears in a log message or structured field.
CERBI002 Secret-like token, password, API key, JWT, connection string, or credential appears in a logging path.
CERBI003 Structured log field is not allowed by policy.
CERBI004 High-cardinality field is likely to increase observability cost.
CERBI005 Debug/trace or dynamically constructed logging appears in a production-sensitive path.
CERBI006 Exception or object destructuring may expose sensitive object state.
CERBI007 Governance relaxation is in use and should be auditable.
CERBI008 AI provider SDK, agent framework, MCP integration, vector-store dependency, or AI orchestration package detected.
CERBI009 AI dependency detected without known Cerbi AI runtime governance instrumentation in the scan scope.
CERBI010 Cerbi AI runtime governance instrumentation detected in the scan scope.

Automation-friendly formats are available with --format console|json|sarif|markdown|html. JSON and SARIF preserve the stable finding contract; Markdown is intended for Azure DevOps build summaries. Use --fail-on warning|error|blocker|none (legacy severities such as medium, high, and critical are still accepted) to control CI exit behavior.

Examples:

# Demo/report-only: always produce review artifacts without failing the walkthrough.
cerbi-scanner scan --path ./src \
  --policy cerbi-policy.yml \
  --fail-on none \
  --format json --output cerbi.json \
  --sarif cerbi.sarif \
  --summary cerbi-summary.md

# CI/CD gate: fail when high-severity findings are present.
cerbi-scanner scan --path ./src \
  --policy cerbi-policy.yml \
  --fail-on error \
  --format json --output cerbi.json \
  --sarif cerbi.sarif \
  --summary cerbi-summary.md

The scanner can target a solution/repository folder, project folder, ordinary folder, or individual source file. Current language implementations include C#, Go, Java, Node/TypeScript, and Python extractors in this repository; findings use the same language-neutral contract so future scanners can be added without changing downstream automation.

AI dependency detection inspects package/configuration manifests only and is scoped to the explicit scan root. It is intended to reduce adoption friction by showing detected AI integrations and Cerbi runtime governance coverage evidence; it is not enterprise-wide AI discovery.

Product Compatible and additional computed target framework versions.
.NET net10.0 is compatible.  net10.0-android was computed.  net10.0-browser was computed.  net10.0-ios was computed.  net10.0-maccatalyst was computed.  net10.0-macos was computed.  net10.0-tvos was computed.  net10.0-windows was computed. 
Compatible target framework(s)
Included target framework(s) (in package)
Learn more about Target Frameworks and .NET Standard.

This package has no dependencies.

Version Downloads Last Updated
1.2.0 58 9/4/2026
1.1.0 184 7/2/2026
1.0.0 143 6/2/2026

Adds metadata-only AI dependency detection for common provider SDKs, agent frameworks, MCP configuration, vector-store clients, and Cerbi runtime governance instrumentation. Targets .NET 10 LTS.