Skip to content

feat: pluggable rule store, security-analyzer hardening, and CI quali… - #11

Merged
mkasperczyk90 merged 8 commits into
mainfrom
feat/pluggable-store-and-hardening
Jul 19, 2026
Merged

mkasperczyk90 merged 8 commits into
mainfrom
feat/pluggable-store-and-hardening

Conversation

@mkasperczyk90

Copy link
Copy Markdown
Owner

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.

…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.
Comment thread src/RuleCraft/FileRuleStore.cs Fixed
Comment thread src/RuleCraft/FileRuleStore.cs Fixed
@mkasperczyk90
mkasperczyk90 force-pushed the feat/pluggable-store-and-hardening branch from 7210eb6 to 471fa44 Compare July 19, 2026 09:32
@mkasperczyk90
mkasperczyk90 merged commit a8ae419 into main Jul 19, 2026
4 of 5 checks passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants