feat: pluggable rule store, security-analyzer hardening, and CI quali… - #11
Merged
Merged
Conversation
…ty gates ## What and why Acts on a critical review of `/src` and `/.github` (see `plan.md`). Closes the high-, medium- and low-priority items: a pluggable persistence seam, a real security-gate gap, CI/quality hardening, and API conveniences. Fully backward compatible on disk; all changes verified by the test suite. ## Highlights ### Pluggable persistence — `IRuleStore` (feat) - New public `IRuleStore`, immutable `RuleRecord`, `StoredRule`, and default `FileRuleStore`; inject a custom backend via `RuleEngineOptions.Store` so several engine instances can share one store (DB/object storage). `StorePath` remains the default file-store path. - Integrity (source hashing, tamper detection) moved from the store into the engine (`RuleHash`, `IsSourceTampered`), so a custom store is pure persistence. Internal `RuleStore`/`RuleMetadata` removed; the engine now works with immutable records. On-disk format unchanged (`.meta.json` / `.cs` / `.rule.json`). ### Security gate hardening (fix) - `SecurityPolicy.Default` now bans `System.AppContext` — a real gap: the open root `System.*` surface let it (and its `GetData` / `BaseDirectory`) through the analyzer. Confirmed by a failing test first, then fixed. - New adversarial corpus for escapes the existing suite missed: reflection via an instance `GetType()` chain, `typeof(X).Assembly`, a banned type worn as a generic argument, a banned call hidden in a lambda / local function — plus positive controls (Math/Guid/DateTime/Regex still pass). ### Runtime trust boundary (feat + docs) - `Resolve` now documents the boundary: the engine guards its own `AppliesTo`/`Priority` calls, but a resolved implementation's methods run unguarded on the caller's thread; acceptance tests and the approval gate are the real defense. - New opt-in `RuleExecution.TryInvoke<T>(func, timeout, out result, out error)` for a bounded, exception-isolated call. ### Correctness & conveniences - `NewId` now uses a full 128-bit GUID instead of a 48-bit slice — removes a silent-overwrite risk in the store. - `_disposed` marked `volatile` (visible to lock-free `Resolve`). - Async offload wrappers for the compile-bound operations: `AddRuleFromSourceAsync`, `AddJsonRuleFromSourceAsync`, `ApproveAsync`, `EnableAsync`. - `GetRule(id)` — single-rule lookup, cheaper than filtering `GetRules()`. ### CI / quality gates - `TreatWarningsAsErrors` (whole tree, builds 0/0) and `RestorePackagesWithLockFile`; committed `packages.lock.json` for all three projects; CI restore runs `--locked-mode`. - Library gets `AnalysisLevel=latest` + `AnalysisModeSecurity=All` (0 findings). - CodeQL switched to `build-mode: manual` with a real build for deeper dataflow analysis. ## Testing `dotnet test` — **173/173** on net8.0 and net10.0 (was 151). Full solution builds 0 warnings / 0 errors under warnings-as-errors. `dotnet restore --locked-mode` passes. ## Notes for reviewers - **Public API growth** (`IRuleStore`, `RuleRecord`, `StoredRule`, `FileRuleStore`, `RuleEngineOptions.Store`, `RuleExecution`, `GetRule`, four `*Async` methods) — a minor bump. - **Behavior change:** the default policy now rejects `System.AppContext`. Intentional tightening, not an API break. - Cross-instance coordination is still pull-based (an instance sees others' changes on its next `ReloadFromStore`); a notification-driven reload is left as a follow-up a custom store can provide. - `plan.md` is included as the review record — drop it from the PR if you'd rather not ship review notes in the repo.
mkasperczyk90
force-pushed
the
feat/pluggable-store-and-hardening
branch
from
July 19, 2026 09:32
7210eb6 to
471fa44
Compare
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.
What and why
Acts on a critical review of
/srcand/.github(seeplan.md). Closes the high-, medium- andlow-priority items: a pluggable persistence seam, a real security-gate gap, CI/quality hardening,
and API conveniences. Fully backward compatible on disk; all changes verified by the test suite.
Highlights
Pluggable persistence —
IRuleStore(feat)IRuleStore, immutableRuleRecord,StoredRule, and defaultFileRuleStore;inject a custom backend via
RuleEngineOptions.Storeso several engine instances can share onestore (DB/object storage).
StorePathremains the default file-store path.RuleHash,IsSourceTampered), so a custom store is pure persistence. InternalRuleStore/RuleMetadataremoved; the engine now works with immutable records. On-disk format unchanged
(
.meta.json/.cs/.rule.json).Security gate hardening (fix)
SecurityPolicy.Defaultnow bansSystem.AppContext— a real gap: the open rootSystem.*surface let it (and its
GetData/BaseDirectory) through the analyzer. Confirmed by a failingtest first, then fixed.
GetType()chain,typeof(X).Assembly, a banned type worn as a generic argument, a banned callhidden in a lambda / local function — plus positive controls (Math/Guid/DateTime/Regex still pass).
Runtime trust boundary (feat + docs)
Resolvenow documents the boundary: the engine guards its ownAppliesTo/Prioritycalls, buta resolved implementation's methods run unguarded on the caller's thread; acceptance tests and the
approval gate are the real defense.
RuleExecution.TryInvoke<T>(func, timeout, out result, out error)for a bounded,exception-isolated call.
Correctness & conveniences
NewIdnow uses a full 128-bit GUID instead of a 48-bit slice — removes a silent-overwrite riskin the store.
_disposedmarkedvolatile(visible to lock-freeResolve).AddRuleFromSourceAsync,AddJsonRuleFromSourceAsync,ApproveAsync,EnableAsync.GetRule(id)— single-rule lookup, cheaper than filteringGetRules().CI / quality gates
TreatWarningsAsErrors(whole tree, builds 0/0) andRestorePackagesWithLockFile; committedpackages.lock.jsonfor all three projects; CI restore runs--locked-mode.AnalysisLevel=latest+AnalysisModeSecurity=All(0 findings).build-mode: manualwith a real build for deeper dataflow analysis.Testing
dotnet test— 173/173 on net8.0 and net10.0 (was 151). Full solution builds 0 warnings /0 errors under warnings-as-errors.
dotnet restore --locked-modepasses.Notes for reviewers
IRuleStore,RuleRecord,StoredRule,FileRuleStore,RuleEngineOptions.Store,RuleExecution,GetRule, four*Asyncmethods) — a minor bump.System.AppContext. Intentional tightening,not an API break.
ReloadFromStore); a notification-driven reload is left as a follow-up a custom store can provide.plan.mdis included as the review record — drop it from the PR if you'd rather not ship reviewnotes in the repo.