Cerbi.Scanner 1.0.0

There is a newer version of this package available.
See the version list below for details.
dotnet tool install --global Cerbi.Scanner --version 1.0.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.0.0
                    
This package contains a .NET tool you can call from the shell/command line.
#tool dotnet:?package=Cerbi.Scanner&version=1.0.0
                    
nuke :add-package Cerbi.Scanner --version 1.0.0
                    

Cerbi Scanner

Static logging governance scanner for C#, Go, Java, Node/TypeScript, and Python repositories.
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 .NET 8 SDK or later. After install, the cerbi-scanner command is available globally.

To update:

dotnet tool update -g Cerbi.Scanner

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

CLI Usage

cerbi audit <path> [options]

Options

Option Description
--profile <file> Load a Cerbi governance profile JSON file
--format json\|sarif\|html Output format (default: json)
--output <file> Write report to this file (omit to print JSON to stdout)
--fail-on info\|low\|medium\|high\|critical Exit 1 if any finding at or above this severity exists
--generate-profile Generate a starter cerbi_governance.json from the scanned codebase
--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_API_KEY

Examples

# Scan current directory, print JSON to stdout
cerbi audit .

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

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

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

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

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

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

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": {
    "CERBI-004": "medium"
  },

  // Config-based suppressions (inline suppression via comments is a future feature)
  "suppressions": [
    {
      "ruleId": "CERBI-001",           // 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": "CERBI-001",
	  "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 Never Never Never No telemetry sink exists in the scanner pipeline.
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 DOTNET_CLI_TELEMETRY_OPTOUT=1 is set in Directory.Build.props; no telemetry sink is wired into the scanner pipeline.
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": "CERBI-001",
  "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)
  └── 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 8 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
Product Compatible and additional computed target framework versions.
.NET net8.0 is compatible.  net8.0-android was computed.  net8.0-browser was computed.  net8.0-ios was computed.  net8.0-maccatalyst was computed.  net8.0-macos was computed.  net8.0-tvos was computed.  net8.0-windows was computed.  net9.0 was computed.  net9.0-android was computed.  net9.0-browser was computed.  net9.0-ios was computed.  net9.0-maccatalyst was computed.  net9.0-macos was computed.  net9.0-tvos was computed.  net9.0-windows was computed.  net10.0 was computed.  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.1.0 120 7/2/2026
1.0.0 133 6/2/2026