Skip to content

Add the PAM access rule evaluation engine and governing-rule resolver#7992

Draft
Hinton wants to merge 3 commits into
pam/access-rule-apifrom
pam/access-rule-engine
Draft

Add the PAM access rule evaluation engine and governing-rule resolver#7992
Hinton wants to merge 3 commits into
pam/access-rule-apifrom
pam/access-rule-engine

Conversation

@Hinton

@Hinton Hinton commented Jul 16, 2026

Copy link
Copy Markdown
Member

🎟️ Tracking

Part of the PAM POC extraction (peeling pam/poc into small, reviewable slices).

Important

Stacked on # — base branch is pam/access-rule-api, not main. Review/merge this after the base lands. The diff below is only this slice.

📔 Objective

Extract the PAM access-rule evaluation engine and the governing-rule resolver from the POC.

  • AccessRuleEngine (pure, singleton) — evaluates a flat list of AccessConditions against request-time AccessSignals, combining results as deny > requires-approval > allow. It reads no state and issues no leases. Every input it cannot interpret (unknown condition kind, unparseable CIDR, unknown/invalid timezone, malformed time-window bounds) fails closed.
  • GoverningRuleResolver (scoped) — resolves the rule governing a cipher for a caller: load the rules on every collection through which the caller reaches the cipher, pick the oldest (earliest creation date, ties broken on id) so selection is deterministic and structural, then evaluate that rule's conditions through the engine to report whether it requires human approval. Malformed stored conditions fail safe to human approval; a rule that no longer loads (deleted after read) stops governing.

The resolver is the shared entry point the upcoming access-request and lease slices both build on, which is why it's folded in here rather than split out.

All dependencies (Collection.AccessRuleId, the collection/collection-cipher repositories, the condition models) already exist on the base branch — this slice adds no persistence, schema, or package changes.

⏰ Reminders before review

  • Contributor guidelines followed
  • Tests added (fail-closed and edge branches covered — 49 unit tests)
  • No new warnings / build clean

🦮 Reviewer guidelines

Coverage (49 tests, all green):

  • Engine (20): all condition kinds, precedence, empty/unknown-kind, unknown IP/timezone, malformed-but-present CIDR & time bounds (fail closed), later-entry matching in multi-CIDR/multi-window lists.
  • Resolver (15): oldest-wins structural selection (both directions), tie-break on id, empty conditions, fail-safe-to-human-approval, deleted-rule race (sole candidate → no governance, and surviving-newer-rule takeover), time_of_day parsed/evaluated through the resolver's camelCase deserializer.
  • AccessWeekdayJsonConverter (14): token (de)serialization, case-insensitivity, invalid-token-throws, and the AccessWeekdayDayOfWeek numeric-alignment guard the engine's direct cast relies on.

Hinton added 3 commits July 16, 2026 14:41
Extract the pure rule-evaluation core from the PAM POC: AccessRuleEngine
evaluates a flat list of AccessConditions against request-time AccessSignals,
combining results as deny > requires-approval > allow and failing closed on
any condition, timezone, or IP it cannot interpret. The engine reads no state
and issues no leases. Registered as a singleton (pure and stateless).
Resolve the access rule that governs a cipher for a caller: load the rules on
every collection through which the caller reaches the cipher, pick the oldest
(earliest creation date, ties broken on id) so selection is deterministic and
structural, then evaluate that rule's conditions through the engine to report
whether it requires human approval. Malformed stored conditions fail safe to
human approval so access never silently auto-approves. Registered scoped; the
resolver is the shared entry point the access-request and lease flows build on.
…onverter

Close the coverage gaps in the PAM engine slice:
- Engine: malformed-but-present CIDR and time-window bounds (fail closed), and
  matching on a later CIDR/window entry rather than only the first.
- Resolver: a governing rule that no longer loads is skipped, both when it is
  the only candidate (no governance) and when a surviving newer rule takes over;
  and a time_of_day condition is parsed and evaluated through the resolver.
- Port AccessWeekdayJsonConverterTests (source was already on the branch,
  untested), including the AccessWeekday/DayOfWeek numeric-alignment guard.
@Hinton
Hinton requested a review from a team as a code owner July 16, 2026 12:59
@Hinton Hinton added the ai-review Request a Claude code review label Jul 16, 2026
@github-actions

github-actions Bot commented Jul 16, 2026

Copy link
Copy Markdown
Contributor

🤖 Bitwarden Claude Code Review

Overall Assessment: APPROVE

Reviewed the PAM access-rule evaluation engine (AccessRuleEngine) and the GoverningRuleResolver extracted from the POC. The engine is pure and stateless with consistent fail-closed handling for every uninterpretable input (unknown condition kind, unparseable CIDR, invalid timezone, malformed time bounds), and the resolver fails safe to human approval on malformed stored conditions and drops deleted rules from candidacy. DI lifetimes are correct (singleton engine, scoped resolver) and follow the TryAdd/AddScoped conventions, and the 49 unit tests cover the precedence, structural-selection, tie-break, and race paths described in the PR.

Code Review Details

No blocking findings.

Notes considered and intentionally not raised as findings:

  • The resolver loads access rules one-by-one per governed collection (GoverningRuleResolver.cs:55-62). The candidate set is bounded to the collections through which a single caller reaches one cipher, and the loop matches the slice's deliberate clarity-over-cleverness design, so this is not flagged.
  • Time-of-day windows use time >= from && time <= to (AccessRuleEngine.cs:78-80), which does not admit overnight windows (e.g. 22:00→02:00). This fails closed (deny) rather than opening access, and window validation lives on the base branch, so it is not a security or correctness defect in this slice.

@Hinton
Hinton marked this pull request as draft July 16, 2026 13:02
@codecov

codecov Bot commented Jul 16, 2026

Copy link
Copy Markdown

Codecov Report

❌ Patch coverage is 93.83562% with 9 lines in your changes missing coverage. Please review.
⚠️ Please upload report for BASE (pam/access-rule-api@44b46da). Learn more about missing BASE report.

Files with missing lines Patch % Lines
...n_license/src/Services/Pam/Engine/AccessSignals.cs 25.00% 6 Missing ⚠️
...src/Services/Pam/Services/GoverningRuleResolver.cs 96.77% 0 Missing and 2 partials ⚠️
...icense/src/Services/Pam/Engine/AccessEvaluation.cs 95.23% 1 Missing ⚠️
Additional details and impacted files
@@                  Coverage Diff                   @@
##             pam/access-rule-api    #7992   +/-   ##
======================================================
  Coverage                       ?   62.03%           
======================================================
  Files                          ?     2296           
  Lines                          ?   100154           
  Branches                       ?     9048           
======================================================
  Hits                           ?    62130           
  Misses                         ?    35838           
  Partials                       ?     2186           

☔ View full report in Codecov by Harness.
📢 Have feedback on the report? Share it here.

🚀 New features to boost your workflow:
  • ❄️ Test Analytics: Detect flaky tests, report on failures, and find test suite problems.
  • 📦 JS Bundle Analysis: Save yourself from yourself by tracking and limiting bundle sizes in JS merges.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

ai-review Request a Claude code review

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant