Skip to content

Reject search modifiers in SMART v2 clinical scopes#5643

Merged
apurvabhaleMS merged 19 commits into
mainfrom
personal/abhale/scope-regex-drops-search-modifiers
Jun 30, 2026
Merged

Reject search modifiers in SMART v2 clinical scopes#5643
apurvabhaleMS merged 19 commits into
mainfrom
personal/abhale/scope-regex-drops-search-modifiers

Conversation

@apurvabhaleMS

@apurvabhaleMS apurvabhaleMS commented Jun 25, 2026

Copy link
Copy Markdown
Contributor

Description

  • Detect FHIR search modifiers (:not, :missing, :exact, :contains, :in, :not-in, :above, :below, :text, :of-type, :identifier, :iterate) and reject with BadRequest
  • Add try-catch for invalid scope parameter parsing failures
  • Add resource strings: SmartScopeSearchParameterModifiersNotSupported, SmartScopeInvalidSearchParameters
  • Add unit tests for modifier and prefix rejection (22 test cases)

Related issues

Addresses AB196347

Testing

Describe how this change was tested.

FHIR Team Checklist

  • Update the title of the PR to be succinct and less than 65 characters
  • Add a milestone to the PR for the sprint that it is merged (i.e. add S47)
  • Tag the PR with the type of update: Bug, Build, Dependencies, Enhancement, New-Feature or Documentation
  • Tag the PR with Open source, Azure API for FHIR (CosmosDB or common code) or Azure Healthcare APIs (SQL or common code) to specify where this change is intended to be released.
  • Tag the PR with Schema Version backward compatible or Schema Version backward incompatible or Schema Version unchanged if this adds or updates Sql script which is/is not backward compatible with the code.
  • When changing or adding behavior, if your code modifies the system design or changes design assumptions, please create and include an ADR.
  • CI is green before merge Build Status
  • Review squash-merge requirements

Semver Change (docs)

Patch|Skip|Feature|Breaking (reason)

- Update ClinicalScopeRegEx to capture ':' in search parameter names
- Detect FHIR search modifiers (:not, :missing, :exact, :contains, :in, :not-in, :above, :below, :text, :of-type, :identifier, :iterate) and reject with BadRequest
- Detect FHIR search prefixes (eq, ne, gt, lt, ge, le, sa, eb, ap) in parameter values and reject with BadRequest
- Add try-catch for invalid scope parameter parsing failures
- Add resource strings: SmartScopeSearchParameterModifiersNotSupported, SmartScopeInvalidSearchParameters
- Add unit tests for modifier and prefix rejection (22 test cases)
@apurvabhaleMS apurvabhaleMS added Azure API for FHIR Label denotes that the issue or PR is relevant to the Azure API for FHIR Area-SMART Azure Healthcare APIs Label denotes that the issue or PR is relevant to the FHIR service in the Azure Healthcare APIs Schema Version unchanged No-PaaS-breaking-change No-ADR ADR not needed labels Jun 25, 2026
@apurvabhaleMS apurvabhaleMS added this to the FY26\Q4\2Wk\2Wk24 milestone Jun 25, 2026
- Remove prefix rejection logic (SearchPrefixRegex) from middleware
- Prefixes (gt, lt, ge, le, ne, eq, sa, eb, ap) are valid FHIR search syntax
- Restore ge2000 prefix in existing unit test data
- Remove prefix test cases from modifier rejection tests
- Add 12 integration tests validating all FHIR prefixes work with SMART scopes
- Add .http file with modifier examples for all search parameters
…round regex

- Replace hardcoded KnownSearchModifiers HashSet with values from SearchModifierCode enum
- Add 'of-type' and 'iterate' modifiers not present in enum
- Wrap ClinicalScopeRegEx.Matches() in try-catch for RegexMatchTimeoutException
- Throw BadHttpRequestException on regex timeout instead of unhandled 500
- Remove extra 'of-type' and 'iterate' (R5 codes, not R4)
- R4 spec uses 'ofType' (camelCase), R5 changed to 'of-type' (kebab-case)
- Update test to use ':ofType' matching R4 spec
- Revert docs/rest/SMARTv2ScopesWithSearchParametersExample.http to original
- Catch all exceptions (not just RegexMatchTimeoutException) around regex
- Use 'when' filter instead of redundant catch(BadHttpRequestException){throw;}
- Strengthen integration test assertions: Assert.Single + Assert.Equal for exact result validation
- Restore .http file from main (fix trailing newline added by Out-File)
- Remove try-catch around search param parsing - all operations are safe string ops
  (Split, IndexOf, Substring, HashSet.Contains, SearchParams.Add) that cannot throw
- Remove stale 'and prefixes' from SmartScopeSearchParameterModifiersNotSupported message
- Validate error message content in modifier rejection test (Assert.Contains)
- Rename test method to reflect modifiers-only behavior
@apurvabhaleMS apurvabhaleMS requested review from Copilot and feordin June 25, 2026 22:51
@apurvabhaleMS apurvabhaleMS marked this pull request as ready for review June 25, 2026 22:51
@apurvabhaleMS apurvabhaleMS requested a review from a team as a code owner June 25, 2026 22:51
@apurvabhaleMS apurvabhaleMS added the Bug Bug bug bug. label Jun 25, 2026
@apurvabhaleMS apurvabhaleMS changed the title Reject search modifiers and prefixes in SMART v2 clinical scopes Reject search modifiers in SMART v2 clinical scopes Jun 25, 2026

Copilot AI left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

This PR aims to harden SMART v2 granular clinical scopes by rejecting unsupported FHIR search features (modifiers and prefixes) when used in scope search-parameter constraints, returning BadRequest instead of allowing potentially unexpected scope filtering behavior.

Changes:

  • Updates SMART clinical scope parsing to recognize search-parameter modifiers in scope constraints and reject known modifiers with a BadRequest message.
  • Adds new localized resource strings for modifier rejection and invalid search-parameter parsing.
  • Adds/updates tests around SMART scope handling (but current test additions don’t align with the stated “reject prefixes” requirement).

Reviewed changes

Copilot reviewed 4 out of 5 changed files in this pull request and generated 4 comments.

Show a summary per file
File Description
test/Microsoft.Health.Fhir.Shared.Tests.Integration/Features/Smart/SmartSearchTests.cs Adds new integration tests that currently validate prefixes work, which conflicts with the PR’s stated intent to reject prefixes.
src/Microsoft.Health.Fhir.Shared.Api.UnitTests/Features/SMART/SmartClinicalScopesMiddlewareTests.cs Adds a theory for rejecting modifier-based scopes; does not currently include prefix cases despite the PR intent.
src/Microsoft.Health.Fhir.Api/Resources.resx Adds new resource strings for modifier rejection and invalid scope search-parameter parsing.
src/Microsoft.Health.Fhir.Api/Resources.Designer.cs Generated designer updates for the new resource strings.
src/Microsoft.Health.Fhir.Api/Features/SMART/SmartClinicalScopesMiddleware.cs Extends scope parsing to allow : in parameter names and rejects known search modifiers; adds broad try/catch around regex matching.
Files not reviewed (1)
  • src/Microsoft.Health.Fhir.Api/Resources.Designer.cs: Generated file

Comment thread src/Microsoft.Health.Fhir.Api/Features/SMART/SmartClinicalScopesMiddleware.cs Outdated
- Narrow regex catch to RegexMatchTimeoutException (avoid swallowing critical exceptions)
- Detect modifiers in any modifier position (after ':') via ContainsSearchModifier helper, handling chaining ('.'), type modifiers, and _has reverse-chaining
- Exclude base param name so 'identifier'/'type' params are not falsely rejected
- Allow '.' in searchParams regex key class so chained params are captured, not silently dropped
- Add modifier rejection cases (chained + _has) and positive cases (chained, type modifier, base-name collision)
- Chained searches are not enforceable as scope constraints, so reject them instead of silently dropping the constraint (avoids scopes that appear to restrict but do not)
- Detect forward chaining (key contains '.', e.g. subject.name, subject:Patient.name) and reverse chaining (_has: prefix)
- Add SmartScopeSearchParameterChainedSearchNotSupported resource string
- Add dedicated chained-search rejection test with message assertions; move chained+modifier cases there; remove chained positive cases
- Chained keys are rejected earlier by IsChainedSearchParameter, so the inner dot-split loop in ContainsSearchModifier is dead code; remove it
- Eliminates the nested foreach CodeQL flagged (implicit filtering) and simplifies the modifier check to a single HashSet lookup per colon-separated token

Copilot AI left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

Copilot reviewed 4 out of 5 changed files in this pull request and generated 3 comments.

Files not reviewed (1)
  • src/Microsoft.Health.Fhir.Api/Resources.Designer.cs: Generated file

- SearchModifierCode enum does not contain the _include/_revinclude modifiers, so iterate (R4) and recurse (STU3) are added explicitly to KnownSearchModifiers
- Add unit tests for _include:iterate and _revinclude:recurse rejection
- Addresses Copilot review feedback on PR #5643
- Test _include:iterate and _revinclude:recurse are rejected with the modifier message
- Complements the iterate/recurse additions to KnownSearchModifiers
Harden SmartClinicalScopesMiddleware scope parsing so malformed scopes are rejected rather than silently enforcing a truncated/partial scope (over-broad / fail-open):

- Reject scopes whose token is only partially matched by the regex (non-whitespace immediately after a match), e.g. 'patient/Observation.read-only', 'patient/Observation.rs?active', 'patient/Observation.rsXYZ'.

- Reject malformed search-parameter constraints containing an extra '=' that previously bypassed the modifier/chained checks and were dropped, e.g. 'code:exact=a=b', 'identifier=x?mrn=12345'. Pairs are now split on the first '=' only and validated.

Adds GetMalformedScopes data provider and GivenMalformedSmartScope_WhenInvoked_ThenBadRequestIsThrown theory.
- Reject SMART clinical scopes whose resource type is not a known FHIR resource type (e.g. typo 'Observaton'); previously stored as a ScopeRestriction matching nothing, masking client misconfiguration. The 'all'/'*' wildcard is excluded from the check.

- Wrap Uri.UnescapeDataString defensively so any decode failure returns BadRequest instead of an unhandled 500 (UnescapeDataString is lenient on .NET 8/9, so this is defense-in-depth).

- Add SmartScopeUnknownResourceType resource string and unit tests for unknown resource types.
Result parameters _include and _revinclude pull in additional resources and can broaden a response to resource types the scope does not otherwise grant, so they are not enforceable as SMART clinical scope constraints. Reject them (plain and modifier forms) with BadRequest instead of accepting them.

Adds SmartScopeSearchParameterIncludesNotSupported resource string, IsIncludeParameter helper, GetScopesWithIncludes data provider and rejection test. Moves the former positive _include/_revinclude cases to the rejection set.
The [cruds]+ regex accepts repeated letters (e.g. 'rrrrrs', 'rss'), which idempotently OR the same DataActions flag but are malformed. Reject a v2 access level whose letters are not all distinct so malformed scopes are not silently accepted.

Adds rrrrrs/rss/crudss cases to GetMalformedScopes.
Covers additional duplicate-letter access levels (rrrrssssss, crudsc) via the distinct-letter check and a trailing-junk variant (rssszz) that exercises the partial-match rejection path.
@codecov-commenter

codecov-commenter commented Jun 30, 2026

Copy link
Copy Markdown

Codecov Report

✅ All modified and coverable lines are covered by tests.
⚠️ Please upload report for BASE (main@95e1385). Learn more about missing BASE report.

Additional details and impacted files

Impacted file tree graph

@@           Coverage Diff           @@
##             main    #5643   +/-   ##
=======================================
  Coverage        ?   77.59%           
=======================================
  Files           ?     1004           
  Lines           ?    36856           
  Branches        ?     5587           
=======================================
  Hits            ?    28600           
  Misses          ?     6899           
  Partials        ?     1357           
🚀 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.

feordin
feordin previously approved these changes Jun 30, 2026
…-regex-drops-search-modifiers

# Conflicts:
#	src/Microsoft.Health.Fhir.SqlServer/Features/Search/Expressions/Visitors/ScalarTemporalEqualityRewriter.cs
@apurvabhaleMS apurvabhaleMS merged commit 7618fcc into main Jun 30, 2026
49 checks passed
@apurvabhaleMS apurvabhaleMS deleted the personal/abhale/scope-regex-drops-search-modifiers branch June 30, 2026 19:12
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

Area-SMART Azure API for FHIR Label denotes that the issue or PR is relevant to the Azure API for FHIR Azure Healthcare APIs Label denotes that the issue or PR is relevant to the FHIR service in the Azure Healthcare APIs Bug Bug bug bug. No-ADR ADR not needed No-PaaS-breaking-change Schema Version unchanged

Projects

None yet

Development

Successfully merging this pull request may close these issues.

5 participants