Skip to content

AdrianDeutsch/ConfigLens

Repository files navigation

ConfigLens

Find the config bug before production does.

A static analyzer for the contract between your configuration and your code. It reads your appsettings*.json and the C# that actually consumes it, cross-references both sides, and reports the mismatches — missing keys, environment drift, dead config, type mismatches and hardcoded secrets — with a Config Health Score and an honest confidence level on every finding.

CI 221 tests .NET 10 C# latest Clean Architecture SARIF 2.1.0 MIT

Quick start · What it finds · How it works · Comparison · CLI · Roadmap


Overview

Every .NET team has lived this: the app works in Development, then crashes in Production because a key was missing from appsettings.Production.json. Secrets scanners look at files. Linters look at code. Nothing looks at both sides of the contract between configuration and code — so the mismatch survives review, survives the build, and surfaces at runtime in the one environment nobody tested.

ConfigLens closes that gap. It scans configuration into a per-environment model, uses Roslyn's semantic model to find every place the code reads a key, and runs a rule engine over the pair. The result is a ranked list of findings and a single Config Health Score (0–100) you can gate CI on.

Why not just a linter or a secrets scanner?

Secrets scanners Roslyn analyzers "grep and pray" ConfigLens
Sees config files
Sees the code that reads config
Cross-references both sides
Environment drift
Hardcoded secrets (pattern + entropy) partial
CI health score & gate
SARIF → GitHub Security tab some

Note

Honest by design. Static analysis cannot resolve everything — dynamic keys (config[$"Feature:{name}"]), reflection, custom providers. ConfigLens never presents a guess as a fact: every finding carries a confidence (High/Medium/Low), and unresolvable key access becomes an informational CL900 note instead of a false positive. See ADR-0002.


🚀 Quick start

dotnet tool install --global configlens

configlens scan .                                  # rich console report + health score
configlens scan . --fail-on error                  # exit 1 fails CI on any error-level finding
configlens scan . --format sarif --output reports  # SARIF for the GitHub Security tab

Gate a pull request with the GitHub Action:

- uses: AdrianDeutsch/ConfigLens@v1
  with:
    path: .
    fail-on: error

Important

Pre-release. The NuGet package and GitHub Action ship with v0.1 (milestone M5). Until then, run it from source:

git clone https://github.com/AdrianDeutsch/ConfigLens.git
dotnet run --project src/ConfigLens.Cli -- scan /path/to/your/solution

Example output

ConfigLens 0.1.0 — scanned /srv/shop

╭───────┬──────────┬────────────┬─────────────────────┬──────────────────────────────────────────╮
│ Rule  │ Severity │ Confidence │ Location            │ Message                                    │
├───────┼──────────┼────────────┼─────────────────────┼──────────────────────────────────────────┤
│ CL001 │ Error    │ High       │ Program.cs:14       │ 'Database:Host' is read in code but        │
│       │          │            │                     │ missing from the configuration.            │
│ CL004 │ Error    │ High       │ appsettings.json:3  │ Possible hardcoded secret in               │
│       │          │            │                     │ 'ConnectionStrings:Default' (value 'Serv…')│
│ CL007 │ Warning  │ Low        │ Program.cs:13       │ 'App:Timeuot' — did you mean 'App:Timeout'?│
╰───────┴──────────┴────────────┴─────────────────────┴──────────────────────────────────────────╯

╭─────────────────────────────╮
│ Config Health Score: 76/100 │
╰─────────────────────────────╯

🔍 What it finds

Each finding has a stable rule ID, a severity, a confidence level, a file/line location and a suggested fix. Click a rule for its full page.

Rule Name Severity What it finds
CL001 Missing key Error Key is read in code but missing from configuration for an environment
CL002 Environment drift Warning Key exists in one environment file but not in another
CL003 Dead configuration Info Key exists in config files but is never read by code
CL004 Hardcoded secret Error Secrets in config, via patterns plus Shannon-entropy analysis
CL005 Type mismatch Error Config value cannot bind to the target IOptions<T> property type
CL006 Unbound options class Error services.Configure<T>(…) points at a section that does not exist
CL007 Case/typo suspicion Warning Config key and code key differ only by casing or a small edit distance
CL900 Unresolvable key access Info Dynamic key access that static analysis cannot resolve — a note, never a false positive

🏗 How it works

flowchart LR
    A["appsettings*.json"] --> B[JSON config scanner]
    C["C# source<br/>(MSBuildWorkspace)"] --> D[Roslyn usage scanner]
    B --> E["Config model<br/>(per environment)"]
    D --> F["Usage model<br/>(+ confidence)"]
    E --> G{{Rule engine<br/>CL001–CL007}}
    F --> G
    G --> H[Score calculator]
    H --> I["Renderers<br/>console · json · html · sarif"]
Loading

The two scanners build a unified model of what configuration exists and what the code reads. The rule engine evaluates that pair — each rule is an isolated, unit-tested IRule. The ScoreCalculator turns findings into the health score, and renderers emit the format you ask for.

  • The Roslyn scanner uses the full semantic model (MSBuildWorkspace) to classify receivers by type, so config["X"] is a finding but dictionary["X"] is not. When a project fails to compile, it degrades gracefully to syntax-only analysis at Low confidence instead of failing the scan (ADR-0003).
  • Clean Architecture with dependency rules enforced by NetArchTest in CI: Domain references nothing, Application only Domain, and Roslyn types never leak past Infrastructure (ADR-0001).

⚖ vs. the alternatives

See the comparison table above. In short: gitleaks and its peers read files, Roslyn analyzers read code, and grep reads whatever you remember to search for. ConfigLens is the only one that reads both sides and checks that they agree — which is where the "works in dev, crashes in prod" class of bug actually lives.


🖥 CLI

configlens scan <path>
  --environments Development,Staging,Production   # drift scope (default: discovered from file names)
  --format console|json|html|sarif                # repeatable (default: console)
  --output <dir>                                  # where file reports are written
  --fail-on error|warning|none                    # CI gate (default: error)
  --baseline <file>                               # suppress findings listed in the baseline
  --write-baseline                                # record current findings as the baseline, exit 0

Exit codes

The exit code is a stable contract from v0.1 on:

Code Meaning
0 Scan completed; no finding reached the --fail-on threshold
1 Scan completed; findings at or above the threshold
2 Tool error (invalid arguments, unreadable input)

Adopting on a legacy codebase

A codebase with hundreds of pre-existing findings shouldn't fail on day one. Record a baseline once, commit it, and from then on only new findings break the build:

configlens scan . --baseline .configlens-baseline.json --write-baseline   # once
configlens scan . --baseline .configlens-baseline.json                    # in CI

Baselines match findings by a line-independent fingerprint, so unrelated edits don't invalidate them (ADR-0006).

Reports

  • Console — Spectre.Console table, per-rule breakdown, health-score panel.
  • JSON — versioned, stable schema (schemaVersion), one fingerprint per finding.
  • HTML — a single self-contained file, no external assets.
  • SARIF 2.1.0 — findings appear in the GitHub Security tab.

📊 Config Health Score

Start at 100, subtract a weighted penalty per finding, floor at 0:

penalty = severityWeight × confidenceFactor
severityWeight:   Error 10 · Warning 3 · Info 1
confidenceFactor: High 1.0 · Medium 0.6 · Low 0.3

Uncertain findings cost less, so honest uncertainty flows all the way into the number. The formula lives in one pure, property-tested ScoreCalculator (ADR-0005).


🧭 Roadmap

  • M0 — Solution skeleton, Clean Architecture, CI on Linux + Windows
  • M1 — JSON config scanner, environment drift (CL002), secrets detection (CL004)
  • M2 — Roslyn usage scanner with confidence levels (CL900)
  • M3 — Cross-referencing rules (CL001/003/005/006/007), health score, baselines
  • M4 — CLI polish, JSON/HTML/SARIF renderers with snapshot tests
  • M5 — GitHub Action, NuGet release v0.1, demo GIF
  • Post-v0.1 — YAML/INI providers, Azure Key Vault / AWS resolution, .configlens.json config file, auto-fix, VS extension

🧱 Project structure

src/
├── ConfigLens.Domain/          # Finding, ConfigKey, ConfigModel, KeyUsage — pure C#, zero deps
├── ConfigLens.Application/      # rule engine, ScoreCalculator, baselines; ports (IScanner, IRule, IReportRenderer)
├── ConfigLens.Infrastructure/   # JSON + Roslyn scanners, console/json/html/sarif renderers
└── ConfigLens.Cli/              # System.CommandLine + Spectre.Console, DI composition root
tests/                           # unit · fixture-based integration · snapshot · CLI e2e · architecture
docs/adr/                        # architecture decision records (MADR)
docs/rules/                      # one page per rule ID

Design decisions are recorded as ADRs; every rule has a reference page.


🤝 Contributing

See CONTRIBUTING.md. Conventional Commits, tests with every change, and the architecture rules are enforced by CI. Bug reports come with a failing fixture where possible.

📄 License

MIT © Adrian Deutsch

🙏 Acknowledgments

Built on Roslyn, System.CommandLine and Spectre.Console. Output interoperates with the SARIF ecosystem and GitHub code scanning.

About

Cross-references your .NET configuration files with the code that actually reads them — and finds the config bugs that only show up in production.

Resources

License

Code of conduct

Contributing

Security policy

Stars

0 stars

Watchers

0 watching

Forks

Releases

No releases published

Packages

 
 
 

Contributors