feat: consumer-pluggable custom analysis rules via a declarative JSON DSL#23
Merged
Conversation
|
|
||
| private static (int Exit, string Stdout) Run(string[] args) | ||
| { | ||
| StringWriter outWriter = new StringWriter(); |
| private static (int Exit, string Stdout) Run(string[] args) | ||
| { | ||
| StringWriter outWriter = new StringWriter(); | ||
| StringWriter errorWriter = new StringWriter(); |
Comment on lines
+100
to
+107
| foreach (DeclarativeRuleSpec spec in parsed.Rules) | ||
| { | ||
| Rule? rule = Compile(spec, file, diagnostics); | ||
| if (rule != null) | ||
| { | ||
| rules.Add(rule); | ||
| } | ||
| } |
|
|
||
| private static string WriteSpec(string json) | ||
| { | ||
| string path = Path.Combine(Path.GetTempPath(), Guid.NewGuid().ToString("N") + ".tmrules.json"); |
|
|
||
| private static MockMessageWriter Evaluate(string specJson, ThreatModel model) | ||
| { | ||
| string path = Path.Combine(Path.GetTempPath(), Guid.NewGuid().ToString("N") + ".tmrules.json"); |
| [TestInitialize] | ||
| public void Initialize() | ||
| { | ||
| this.WorkingDirectory = Path.Combine(Path.GetTempPath(), "tmforge-rules-" + Guid.NewGuid().ToString("N")); |
|
|
||
| private string WriteSpec(string json) | ||
| { | ||
| string path = Path.Combine(this.WorkingDirectory, Guid.NewGuid().ToString("N") + ".tmrules.json"); |
| [TestInitialize] | ||
| public void Initialize() | ||
| { | ||
| this.WorkingDirectory = Path.Combine(Path.GetTempPath(), "tmforge-customrules-" + Guid.NewGuid().ToString("N")); |
|
|
||
| private string Write(string fileName, string content) | ||
| { | ||
| string path = Path.Combine(this.WorkingDirectory, fileName); |
This was referenced Jul 8, 2026
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
feat: consumer-pluggable custom analysis rules via a declarative JSON DSL
What & why
This PR makes the analysis rule set consumer-pluggable: teams can ship their own rules as data a declarative
*.tmrules.jsonspec and load them with--rules, without writing or compiling code. The durable move comes first: the reflection-based rule host already existed, but the rules assembly was hardcoded in five separate load sites, so the PR centralizes all five behind one seam before exposing anything. Custom rules are additive (evaluated alongside the full built-in set, never a replacement), and there are no wire-contract or engine-DTO changes the sharedEngineServicefacade signature and the/v1OpenAPI are unchanged. We went DSL-first rather than loading arbitrary rule assemblies because a spec is inspectable data: safe to share and review, and portable to hosts with no filesystem.Highlights
AnalysisRuleSources.Create()now sits behind all five previously-hardcodedAssembly.Load("ThreatModelForge.Analysis.Rules")sites —analyze, the shared engine facade (/v1+ WASM + Studio),threats, the knowledge-base catalog, andproperties. Default behavior is byte-identical; this removes the duplication that would otherwise make custom rules land inconsistently across surfaces.*.tmrules.json— awhenguard plus anassertrequirement over one DFD primitive's typed properties, extended with flow relations (crossesTrustBoundary, andsource/targetendpoint kind + property).DeclarativeRulereuses the exact model helpers the built-in rules use, so "crosses a boundary" has a single implementation.--ruleson the CLI:analyze,threats, andpropertiesaccept--rules <file-or-dir>(a spec file or a directory searched recursively). Custom rules are appended to the built-ins; astride-bearing custom rule also becomes athreatsthreat.Rule(string id, ...)constructor lets third-party rules use their own prefix (e.g.ACME001) instead of the built-inTM####, so ids never collide in SARIF, suppressions, or JSON; a colliding id is dropped with a warning (the built-in wins).RuleSet.LoadDefaultis hardened to skip abstract / no-constructor rule types and survive bad assemblies; malformed specs and invalid rules are reported to stderr and skipped (never fatal); property names are validated against the typed schema, so a typo likeEncryptionvsEncryptedis flagged instead of silently never matching.properties --explainright next to the built-ins./v1and the in-browser WASM engine load the built-ins only — the shared engine facade is stateless with no per-request rule-source channel, and WASM has no filesystem for the spec loader; opening this over a shared service is a deferred, security-sensitive change.analyze --rulesend-to-end.