├── .checkov.yml
├── .config
└── dotnet-tools.json
├── .dockerignore
├── .editorconfig
├── .gitattributes
├── .github
└── workflows
│ ├── chaos.yaml
│ ├── ci.yaml
│ ├── lint-pr-title.yaml
│ ├── release-please.yaml
│ ├── schedule.yaml
│ └── scorecards.yaml
├── .gitignore
├── .gitleaks.toml
├── .kics.yaml
├── .lychee.toml
├── .mdlrc
├── .mega-linter.yml
├── .pre-commit-config.yaml
├── .prettierignore
├── .protolintrc.yml
├── .release-please-manifest.json
├── .renovaterc.json
├── .stylecop.json
├── .trivyignore
├── .vscode
├── launch.json
├── settings.json
└── tasks.json
├── .yamllint
├── CHANGELOG.md
├── Dockerfile
├── FhirPseudonymizer.sln
├── LICENSE
├── README.md
├── SECURITY.md
├── Taskfile.yaml
├── benchmark
├── bombardier.sh
├── bundle.json
└── observation.json
├── compose.dev.yaml
├── compose
├── README.md
├── anonymization-hipaa.yaml
└── compose.yaml
├── docs
└── img
│ ├── logo.png
│ └── openapi.png
├── hack
├── keycloak
│ └── fhir-pseudonymizer-test-realm-export.json
└── mocks
│ ├── README.md
│ ├── initializer.json
│ └── initializer.yaml
├── release-please-config.json
├── src
├── Directory.Build.props
├── FhirPseudonymizer.StressTests
│ ├── FhirPseudonymizer.StressTests.csproj
│ ├── StressTests.cs
│ └── Usings.cs
├── FhirPseudonymizer.Tests
│ ├── CryptoHashProcessorTests.cs
│ ├── FhirControllerTests.cs
│ ├── FhirPseudonymizer.Tests.csproj
│ ├── Fixtures
│ │ ├── Data
│ │ │ ├── Configs
│ │ │ │ ├── generalize-birth-date.yaml
│ │ │ │ ├── hipaa.yaml
│ │ │ │ ├── pseudonymization.yaml
│ │ │ │ ├── truncate-crypto-hashed-values.yaml
│ │ │ │ └── whitelist-resource-parts.yaml
│ │ │ └── Resources
│ │ │ │ ├── Ashleigh_Olson_9d9b8bed-7b79-7fa9-cea1-f133a6b4d551.json
│ │ │ │ └── patient.json
│ │ └── MII-Pseudonymization
│ │ │ ├── mii-patient.json
│ │ │ └── patient-to-pseuded.yaml
│ ├── IntegrationTests.cs
│ ├── Pseudonymization
│ │ ├── EnticiFhirClientTests.cs
│ │ ├── GPasFhirClientTests.cs
│ │ ├── GPasPseudonymizationProcessorTests.cs
│ │ ├── NoopPseudonymServiceClientTests.cs
│ │ └── VfpsPseudonymServiceClientTests.cs
│ ├── PseudonymizationServiceConfigurationTests.cs
│ ├── ReferenceUtilityTests.cs
│ ├── RequestCompressionTests.cs
│ ├── SnapshotTests.cs
│ ├── Snapshots
│ │ ├── IntegrationTests.PostDeIdentify_WithCryptoHashKeySetViaAppSettingsConfig_ShouldCryptoHashValue.verified.json
│ │ ├── IntegrationTests.PostDeIdentify_WithShouldAddSecurityTagSetToFalse_ShouldNotAddSecurityMetaDataToResult.verified.json
│ │ ├── generalize-birth-date-Ashleigh_Olson_9d9b8bed-7b79-7fa9-cea1-f133a6b4d551.verified.json
│ │ ├── generalize-birth-date-patient.verified.json
│ │ ├── hipaa-Ashleigh_Olson_9d9b8bed-7b79-7fa9-cea1-f133a6b4d551.verified.json
│ │ ├── hipaa-patient.verified.json
│ │ ├── patient-to-pseuded-mii-patient.verified.json
│ │ ├── pseudonymization-Ashleigh_Olson_9d9b8bed-7b79-7fa9-cea1-f133a6b4d551.verified.json
│ │ ├── pseudonymization-patient.verified.json
│ │ ├── truncate-crypto-hashed-values-Ashleigh_Olson_9d9b8bed-7b79-7fa9-cea1-f133a6b4d551.verified.json
│ │ ├── truncate-crypto-hashed-values-patient.verified.json
│ │ ├── whitelist-resource-parts-Ashleigh_Olson_9d9b8bed-7b79-7fa9-cea1-f133a6b4d551.verified.json
│ │ └── whitelist-resource-parts-patient.verified.json
│ ├── Usings.cs
│ ├── WebApplicationFactory.cs
│ └── runsettings.xml
└── FhirPseudonymizer
│ ├── AnonymizerEngineExtensions.cs
│ ├── ApiKeyExtensions.cs
│ ├── CompressionMiddleware.cs
│ ├── Config
│ └── AppConfig.cs
│ ├── Controllers
│ └── FhirController.cs
│ ├── DecryptProcessor.cs
│ ├── FhirFormatter.cs
│ ├── FhirPseudonymizer.csproj
│ ├── Microsoft.Health.Fhir.Anonymizer.R4.Core
│ ├── Constants.cs
│ └── Processors
│ │ └── PerturbProcessor.cs
│ ├── Microsoft.Health.Fhir.Anonymizer.Shared.Core
│ ├── AnonymizerConfigurationManager.cs
│ ├── AnonymizerConfigurations
│ │ ├── AnonymizationFhirPathRule.cs
│ │ ├── AnonymizerConfiguration.cs
│ │ ├── AnonymizerConfigurationErrorsException.cs
│ │ ├── AnonymizerConfigurationValidator.cs
│ │ ├── AnonymizerMethod.cs
│ │ ├── AnonymizerRule.cs
│ │ ├── AnonymizerRuleType.cs
│ │ ├── AnonymizerSettings.cs
│ │ ├── DateShiftScope.cs
│ │ └── ParameterConfiguration.cs
│ ├── AnonymizerEngine.cs
│ ├── AnonymizerLogging.cs
│ ├── Constants.cs
│ ├── Extensions
│ │ ├── ElementNodeExtension.cs
│ │ ├── ElementNodeNavExtensions.cs
│ │ ├── ElementNodeOperationExtensions.cs
│ │ ├── ElementNodeVisitorExtensions.cs
│ │ └── FhirPathSymbolExtensions.cs
│ ├── IAnonymizerEngine.cs
│ ├── Models
│ │ ├── ProcessContext.cs
│ │ ├── ProcessResult.cs
│ │ └── SecurityLabels.cs
│ ├── PartitionedExecution
│ │ ├── BatchAnonymizeProgressDetail.cs
│ │ ├── FhirEnumerableReader.cs
│ │ ├── FhirPartitionedExecutor.cs
│ │ ├── FhirStreamConsumer.cs
│ │ ├── FhirStreamReader.cs
│ │ ├── IFhirDataConsumer.cs
│ │ └── IFhirDataReader.cs
│ ├── Processors
│ │ ├── AnonymizationOperations.cs
│ │ ├── CryptoHashProcessor.cs
│ │ ├── DateShiftProcessor.cs
│ │ ├── EncryptProcessor.cs
│ │ ├── GeneralizeProcessor.cs
│ │ ├── IAnonymizerProcessor.cs
│ │ ├── KeepProcessor.cs
│ │ ├── PerturbProcessor.cs
│ │ ├── RedactProcessor.cs
│ │ ├── Settings
│ │ │ ├── GeneralizeOtherValuesOperation.cs
│ │ │ ├── GeneralizeSetting.cs
│ │ │ ├── PerturbRangeType.cs
│ │ │ ├── PerturbSetting.cs
│ │ │ ├── RuleKeys.cs
│ │ │ └── SubstituteSetting.cs
│ │ └── SubstituteProcessor.cs
│ ├── Utility
│ │ ├── CryptoHashUtility.cs
│ │ ├── DateTimeUtility.cs
│ │ ├── EncryptUtility.cs
│ │ ├── PostalCodeUtility.cs
│ │ └── ReferenceUtility.cs
│ ├── Validation
│ │ ├── AttributeValidator.cs
│ │ ├── ResourceNotValidException.cs
│ │ └── ResourceValidator.cs
│ └── Visitors
│ │ ├── AbstractElementNodeVisitor.cs
│ │ └── AnonymizationVisitor.cs
│ ├── Program.cs
│ ├── Properties
│ └── launchSettings.json
│ ├── Protos
│ ├── google
│ │ └── api
│ │ │ ├── annotations.proto
│ │ │ └── http.proto
│ └── vfps
│ │ └── api
│ │ └── v1
│ │ ├── meta.proto
│ │ └── pseudonyms.proto
│ ├── Pseudonymization
│ ├── Entici
│ │ ├── EnticiExtensions.cs
│ │ ├── EnticiFhirClient.cs
│ │ └── EnticiPseudonymizationRequest.cs
│ ├── GPas
│ │ ├── GPasExtensions.cs
│ │ └── GPasFhirClient.cs
│ ├── IPseudonymServiceClient.cs
│ ├── NoopPseudonymServiceClient.cs
│ ├── PseudonymizationProcessor.cs
│ ├── PseudonymizationServiceType.cs
│ └── Vfps
│ │ ├── VfpsExtensions.cs
│ │ └── VfpsPseudonymServiceClient.cs
│ ├── Startup.cs
│ ├── TracingConfigurationExtensions.cs
│ ├── anonymization.yaml
│ ├── appsettings.Development.json
│ ├── appsettings.json
│ ├── hipaa-anonymization.yaml
│ └── packages.lock.json
├── stylecop.ruleset.xml
├── tests
├── chaos
│ ├── argo-workflows-values.yaml
│ ├── chaos-mesh-rbac.yaml
│ ├── chaos.yaml
│ ├── fhir-pseudonymizer-values.yaml
│ └── workflow.yaml
└── iter8
│ ├── experiment.yaml
│ └── values.yaml
├── trivy.yaml
└── version.txt
/.checkov.yml:
--------------------------------------------------------------------------------
1 | skip-check:
2 | - CKV_DOCKER_3
3 | - CKV_DOCKER_2
4 | # CKV_K8S_21: "The default namespace should not be used" - used for simple testing inside a KinD cluster
5 | - CKV_K8S_21
6 | # CKV_K8S_10: "CPU requests should be set" - ignored for iter8 job pod
7 | - CKV_K8S_10
8 | # CKV_K8S_11: "CPU limits should be set" - ignored for iter8 job pod
9 | - CKV_K8S_11
10 | # CKV_K8S_12: "Memory requests should be set"
11 | - CKV_K8S_12
12 | # CKV_K8S_13: "Memory limits should be set" - ignored for iter8 job pod
13 | - CKV_K8S_13
14 | # CKV_K8S_15: "Image Pull Policy should be Always" - ignored for digest-pinned iter8
15 | - CKV_K8S_15
16 | # CKV_K8S_12: "Memory requests should be set" - ignored for iter8
17 | - CKV_K8S_12
18 | # CKV_K8S_38: "Ensure that Service Account Tokens are only mounted where necessary" - necessary for iter8
19 | - CKV_K8S_38
20 | # CKV_ARGO_2: "Ensure Workflow pods are running as non-root user" - necessary, see inline comments in workflow and Dockerfile
21 | - CKV_ARGO_2
22 | # CKV_SECRET_6: "Base64 High Entropy String" - already covered by gitleaks & co. with more configuration options.
23 | - CKV_SECRET_6
24 |
--------------------------------------------------------------------------------
/.config/dotnet-tools.json:
--------------------------------------------------------------------------------
1 | {
2 | "version": 1,
3 | "isRoot": true,
4 | "tools": {
5 | "dotnet-outdated-tool": {
6 | "version": "4.6.8",
7 | "commands": ["dotnet-outdated"],
8 | "rollForward": false
9 | },
10 | "csharpier": {
11 | "version": "1.0.2",
12 | "commands": ["csharpier"],
13 | "rollForward": false
14 | }
15 | }
16 | }
17 |
--------------------------------------------------------------------------------
/.dockerignore:
--------------------------------------------------------------------------------
1 | *
2 | !src
3 | src/**/bin
4 | src/**/obj
5 | !*stylecop*
6 | !.editorconfig
7 | !tests/chaos
8 | !LICENSE
9 |
--------------------------------------------------------------------------------
/.gitattributes:
--------------------------------------------------------------------------------
1 | * text=auto
2 | *.sh text eol=lf
3 | *.shellcheckrc text eol=lf
4 | *Dockerfile text eol=lf
5 |
6 | *.verified.json text eol=lf working-tree-encoding=UTF-8
7 |
--------------------------------------------------------------------------------
/.github/workflows/chaos.yaml:
--------------------------------------------------------------------------------
1 | name: nightly chaos testing
2 |
3 | on:
4 | workflow_dispatch: {}
5 | schedule:
6 | # daily at 06:23
7 | - cron: "23 06 * * *"
8 |
9 | # Declare default permissions as read only.
10 | permissions:
11 | contents: read
12 |
13 | jobs:
14 | chaos-testing:
15 | name: chaos testing
16 | runs-on: ubuntu-24.04
17 | steps:
18 | - name: Checkout
19 | uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4.2.2
20 | with:
21 | persist-credentials: false
22 |
23 | - name: Set up Docker Buildx
24 | uses: docker/setup-buildx-action@b5ca514318bd6ebac0fb2aedd5d36ec1b5c232a2 # v3.10.0
25 |
26 | - name: Install Task
27 | uses: arduino/setup-task@b91d5d2c96a56797b48ac1e0e89220bf64044611 # v2.0.0
28 | with:
29 | version: 3.x
30 | repo-token: ${{ secrets.GITHUB_TOKEN }}
31 |
32 | - name: Run chaos-test task
33 | run: task chaos-test
34 |
35 | - name: Print cluster logs
36 | if: always()
37 | run: |
38 | kubectl cluster-info dump -o yaml | tee kind-cluster-dump.txt
39 |
40 | - name: Upload cluster dump
41 | if: always()
42 | uses: actions/upload-artifact@ea165f8d65b6e75b540449e92b4886f43607fa02 # v4.6.2
43 | with:
44 | name: kind-cluster-dump.txt
45 | path: |
46 | kind-cluster-dump.txt
47 |
--------------------------------------------------------------------------------
/.github/workflows/lint-pr-title.yaml:
--------------------------------------------------------------------------------
1 | name: "Lint PR"
2 |
3 | on:
4 | pull_request:
5 | types:
6 | - opened
7 | - edited
8 | - synchronize
9 |
10 | permissions:
11 | contents: read
12 |
13 | jobs:
14 | check-pr-title:
15 | name: Validate PR title
16 | runs-on: ubuntu-24.04
17 | permissions:
18 | pull-requests: write
19 | steps:
20 | - uses: amannn/action-semantic-pull-request@0723387faaf9b38adef4775cd42cfd5155ed6017 # v5
21 | env:
22 | GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
23 |
--------------------------------------------------------------------------------
/.github/workflows/release-please.yaml:
--------------------------------------------------------------------------------
1 | name: release-please
2 |
3 | on:
4 | push:
5 | branches:
6 | - master
7 |
8 | permissions:
9 | contents: read
10 |
11 | jobs:
12 | release-please:
13 | runs-on: ubuntu-24.04
14 | permissions:
15 | contents: write
16 | pull-requests: write
17 |
18 | steps:
19 | - uses: googleapis/release-please-action@a02a34c4d625f9be7cb89156071d8567266a2445 # v4.2.0
20 | with:
21 | token: ${{ secrets.MIRACUM_BOT_SEMANTIC_RELEASE_TOKEN }}
22 |
--------------------------------------------------------------------------------
/.github/workflows/schedule.yaml:
--------------------------------------------------------------------------------
1 | name: scheduled
2 |
3 | on:
4 | repository_dispatch: {}
5 | workflow_dispatch: {}
6 | schedule:
7 | - cron: "00 18 * * *"
8 |
9 | permissions:
10 | contents: read
11 |
12 | jobs:
13 | schedule:
14 | uses: miracum/.github/.github/workflows/standard-schedule.yaml@ea119ab4361974cc57f38719dd14ede3a289724a # v1.16.17
15 | permissions:
16 | contents: read
17 | issues: write
18 | security-events: write
19 | secrets:
20 | github-token: ${{ secrets.GITHUB_TOKEN }}
21 |
--------------------------------------------------------------------------------
/.github/workflows/scorecards.yaml:
--------------------------------------------------------------------------------
1 | # This workflow uses actions that are not certified by GitHub. They are provided
2 | # by a third-party and are governed by separate terms of service, privacy
3 | # policy, and support documentation.
4 |
5 | name: Scorecards supply-chain security
6 | on:
7 | workflow_dispatch:
8 | # For Branch-Protection check. Only the default branch is supported. See
9 | # https://github.com/ossf/scorecard/blob/main/docs/checks.md#branch-protection
10 | branch_protection_rule:
11 | # To guarantee Maintained check is occasionally updated. See
12 | # https://github.com/ossf/scorecard/blob/main/docs/checks.md#maintained
13 | schedule:
14 | - cron: "37 19 * * 1"
15 | push:
16 | branches: ["master"]
17 |
18 | # Declare default permissions as read only.
19 | permissions:
20 | contents: read
21 |
22 | jobs:
23 | analysis:
24 | name: Scorecards analysis
25 | runs-on: ubuntu-latest
26 | permissions:
27 | # Needed to upload the results to code-scanning dashboard.
28 | security-events: write
29 | # Needed to publish results and get a badge (see publish_results below).
30 | id-token: write
31 | # Uncomment the permissions below if installing in a private repository.
32 | # contents: read
33 | # actions: read
34 |
35 | steps:
36 | - name: "Checkout code"
37 | uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4.2.2
38 | with:
39 | persist-credentials: false
40 |
41 | - name: "Run analysis"
42 | uses: ossf/scorecard-action@05b42c624433fc40578a4040d5cf5e36ddca8cde # v2.4.2
43 | with:
44 | results_file: results.sarif
45 | results_format: sarif
46 | # (Optional) Read-only PAT token. Uncomment the `repo_token` line below if:
47 | # - you want to enable the Branch-Protection check on a *public* repository, or
48 | # - you are installing Scorecards on a *private* repository
49 | # To create the PAT, follow the steps in https://github.com/ossf/scorecard-action#authentication-with-pat.
50 | # repo_token: ${{ secrets.SCORECARD_READ_TOKEN }}
51 |
52 | # Public repositories:
53 | # - Publish results to OpenSSF REST API for easy access by consumers
54 | # - Allows the repository to include the Scorecard badge.
55 | # - See https://github.com/ossf/scorecard-action#publishing-results.
56 | # For private repositories:
57 | # - `publish_results` will always be set to `false`, regardless
58 | # of the value entered here.
59 | publish_results: true
60 |
61 | # Upload the results as artifacts (optional). Commenting out will disable uploads of run results in SARIF
62 | # format to the repository Actions tab.
63 | - name: "Upload artifact"
64 | uses: actions/upload-artifact@ea165f8d65b6e75b540449e92b4886f43607fa02 # v4.6.2
65 | with:
66 | name: SARIF file
67 | path: results.sarif
68 | retention-days: 5
69 |
70 | # Upload the results to GitHub's code scanning dashboard.
71 | - name: "Upload to code-scanning"
72 | uses: github/codeql-action/upload-sarif@fca7ace96b7d713c7035871441bd52efbe39e27e # v3.28.19
73 | with:
74 | sarif_file: results.sarif
75 |
--------------------------------------------------------------------------------
/.gitleaks.toml:
--------------------------------------------------------------------------------
1 | [allowlist]
2 | paths = ["hack/*"]
3 |
--------------------------------------------------------------------------------
/.kics.yaml:
--------------------------------------------------------------------------------
1 | exclude-paths:
2 | - "tests/"
3 | exclude-queries:
4 | #
5 | # informational, doesn't work any other way with .NET for now.
6 | #
7 | #
8 | - b16e8501-ef3c-44e1-a543-a093238099c9
9 |
--------------------------------------------------------------------------------
/.lychee.toml:
--------------------------------------------------------------------------------
1 | exclude_all_private = true
2 |
--------------------------------------------------------------------------------
/.mdlrc:
--------------------------------------------------------------------------------
1 | rules "~MD013", "~MD033"
2 |
--------------------------------------------------------------------------------
/.mega-linter.yml:
--------------------------------------------------------------------------------
1 | # Configuration file for MegaLinter
2 | # See all available variables at https://oxsecurity.github.io/megalinter/configuration/ and in linters documentation
3 |
4 | APPLY_FIXES: none # all, none, or list of linter keys
5 | # ENABLE: # If you use ENABLE variable, all other languages/formats/tooling-formats will be disabled by default
6 | # ENABLE_LINTERS: # If you use ENABLE_LINTERS variable, all other linters will be disabled by default
7 | DISABLE:
8 | - COPYPASTE # Comment to enable checks of excessive copy-pastes
9 | - SPELL # Comment to enable checks of spelling mistakes
10 |
11 | DISABLE_LINTERS:
12 | - REPOSITORY_DEVSKIM
13 | - MARKDOWN_MARKDOWN_TABLE_FORMATTER
14 | - CSHARP_DOTNET_FORMAT
15 | - MARKDOWN_MARKDOWN_LINK_CHECK
16 | - SPELL_LYCHEE
17 |
18 | SHOW_ELAPSED_TIME: true
19 | FILEIO_REPORTER: false
20 | # DISABLE_ERRORS: true # Uncomment if you want MegaLinter to detect errors but not block CI to pass
21 |
22 | BASH_SHFMT_ARGUMENTS:
23 | - "--indent=2"
24 |
25 | REPOSITORY_TRIVY_ARGUMENTS:
26 | - "--severity=HIGH,CRITICAL"
27 |
28 | REPOSITORY_CHECKOV_ARGUMENTS:
29 | - "--skip-path=tests/iter8"
30 | - "--skip-path=src/FhirPseudonymizer.Tests/Fixtures/Data/Resources/"
31 | - "--skip-path=src/FhirPseudonymizer.Tests/Snapshots/"
32 |
33 | IGNORE_GITIGNORED_FILES: true
34 |
35 | REPOSITORY_KICS_ARGUMENTS:
36 | - --fail-on=HIGH
37 |
38 | REPOSITORY_KICS_CONFIG_FILE: .kics.yaml
39 |
--------------------------------------------------------------------------------
/.pre-commit-config.yaml:
--------------------------------------------------------------------------------
1 | # See https://pre-commit.com for more information
2 | # See https://pre-commit.com/hooks.html for more hooks
3 | repos:
4 | - repo: https://github.com/pre-commit/pre-commit-hooks
5 | rev: v4.0.1
6 | hooks:
7 | - id: trailing-whitespace
8 | - id: end-of-file-fixer
9 | - id: check-yaml
10 | args: [--allow-multiple-documents]
11 | - id: check-added-large-files
12 | - id: fix-byte-order-marker
13 | - id: check-case-conflict
14 | - id: check-executables-have-shebangs
15 | - id: check-json
16 | - repo: https://github.com/jorisroovers/gitlint
17 | rev: v0.15.1
18 | hooks:
19 | - id: gitlint
20 | stages: [commit-msg]
21 | entry: gitlint
22 | args: [--contrib=CT1, --ignore=body-is-missing, --msg-filename]
23 |
--------------------------------------------------------------------------------
/.prettierignore:
--------------------------------------------------------------------------------
1 | src/FhirPseudonymizer.Tests/Snapshots
2 | packages.lock.json
3 |
--------------------------------------------------------------------------------
/.protolintrc.yml:
--------------------------------------------------------------------------------
1 | lint:
2 | rules_option:
3 | max_line_length:
4 | max_chars: 120
5 | indent:
6 | not_insert_newline: true
7 |
--------------------------------------------------------------------------------
/.release-please-manifest.json:
--------------------------------------------------------------------------------
1 | {
2 | ".": "2.22.10"
3 | }
4 |
--------------------------------------------------------------------------------
/.renovaterc.json:
--------------------------------------------------------------------------------
1 | {
2 | "$schema": "https://docs.renovatebot.com/renovate-schema.json",
3 | "extends": ["github>miracum/.github//renovate/default", "schedule:quarterly"],
4 | "kubernetes": {
5 | "managerFilePatterns": ["/tests/iter8/experiment.yaml/"]
6 | },
7 | "ignoreDeps": ["ghcr.io/miracum/fhir-pseudonymizer"]
8 | }
9 |
--------------------------------------------------------------------------------
/.stylecop.json:
--------------------------------------------------------------------------------
1 | {
2 | "$schema": "https://raw.githubusercontent.com/DotNetAnalyzers/StyleCopAnalyzers/master/StyleCop.Analyzers/StyleCop.Analyzers/Settings/stylecop.schema.json",
3 | "settings": {
4 | "documentationRules": {
5 | "documentExposedElements": false,
6 | "documentInterfaces": false,
7 | "documentInternalElements": false,
8 | "documentPrivateElements": false,
9 | "documentPrivateFields": false,
10 | "xmlHeader": false
11 | },
12 | "layoutRules": {
13 | "newlineAtEndOfFile": "require"
14 | },
15 | "indentation": {
16 | "indentationSize": 4,
17 | "useTabs": false
18 | },
19 | "orderingRules": {
20 | "usingDirectivesPlacement": "outsideNamespace",
21 | "elementOrder": [
22 | "kind",
23 | "constant",
24 | "accessibility",
25 | "static",
26 | "readonly"
27 | ]
28 | }
29 | }
30 | }
31 |
--------------------------------------------------------------------------------
/.trivyignore:
--------------------------------------------------------------------------------
1 | # iter8 requires access to secrets
2 | AVD-KSV-0041
3 | KSV041
4 | # necessary, see inline comments in workflow and Dockerfile. Only used for stress-testing
5 | AVD-DS-0002
6 |
7 | # Dockerfiles HEALTHCHECK implementation isn't relevant for Kubernetes-first deployments
8 | AVD-DS-0026
9 |
--------------------------------------------------------------------------------
/.vscode/launch.json:
--------------------------------------------------------------------------------
1 | {
2 | "version": "0.2.0",
3 | "configurations": [
4 | {
5 | "name": ".NET Core Launch (web)",
6 | "type": "coreclr",
7 | "request": "launch",
8 | "preLaunchTask": "build",
9 | "program": "${workspaceFolder}/src/FhirPseudonymizer/bin/Debug/net8.0/FhirPseudonymizer.dll",
10 | "args": [],
11 | "cwd": "${workspaceFolder}/src/FhirPseudonymizer",
12 | "stopAtEntry": false,
13 | "serverReadyAction": {
14 | "action": "openExternally",
15 | "pattern": "\\bNow listening on:\\s+(https?://\\S+)"
16 | },
17 | "env": {
18 | "ASPNETCORE_ENVIRONMENT": "Development"
19 | },
20 | "sourceFileMap": {
21 | "/Views": "${workspaceFolder}/Views"
22 | }
23 | },
24 | {
25 | "name": ".NET Core Attach",
26 | "type": "coreclr",
27 | "request": "attach",
28 | "processId": "${command:pickProcess}"
29 | }
30 | ]
31 | }
32 |
--------------------------------------------------------------------------------
/.vscode/settings.json:
--------------------------------------------------------------------------------
1 | {
2 | "dotnet.defaultSolution": "FhirPseudonymizer.sln"
3 | }
4 |
--------------------------------------------------------------------------------
/.vscode/tasks.json:
--------------------------------------------------------------------------------
1 | {
2 | "version": "2.0.0",
3 | "tasks": [
4 | {
5 | "label": "build",
6 | "command": "dotnet",
7 | "type": "process",
8 | "args": [
9 | "build",
10 | "${workspaceFolder}/src/FhirPseudonymizer/FhirPseudonymizer.csproj",
11 | "/property:GenerateFullPaths=true",
12 | "/consoleloggerparameters:NoSummary"
13 | ],
14 | "problemMatcher": "$msCompile"
15 | },
16 | {
17 | "label": "publish",
18 | "command": "dotnet",
19 | "type": "process",
20 | "args": [
21 | "publish",
22 | "${workspaceFolder}/src/FhirPseudonymizer/FhirPseudonymizer.csproj",
23 | "/property:GenerateFullPaths=true",
24 | "/consoleloggerparameters:NoSummary"
25 | ],
26 | "problemMatcher": "$msCompile"
27 | },
28 | {
29 | "label": "watch",
30 | "command": "dotnet",
31 | "type": "process",
32 | "args": [
33 | "watch",
34 | "run",
35 | "${workspaceFolder}/src/FhirPseudonymizer/FhirPseudonymizer.csproj",
36 | "/property:GenerateFullPaths=true",
37 | "/consoleloggerparameters:NoSummary"
38 | ],
39 | "problemMatcher": "$msCompile"
40 | }
41 | ]
42 | }
43 |
--------------------------------------------------------------------------------
/.yamllint:
--------------------------------------------------------------------------------
1 | extends: default
2 | rules:
3 | document-start: disable
4 | line-length: disable
5 |
--------------------------------------------------------------------------------
/CHANGELOG.md:
--------------------------------------------------------------------------------
1 | # Changelog
2 |
3 | ## [2.22.10](https://github.com/miracum/fhir-pseudonymizer/compare/v2.22.9...v2.22.10) (2025-05-25)
4 |
5 |
6 | ### Miscellaneous Chores
7 |
8 | * **config:** migrate renovate config ([#249](https://github.com/miracum/fhir-pseudonymizer/issues/249)) ([fe2f060](https://github.com/miracum/fhir-pseudonymizer/commit/fe2f060417af768a3f552f89a8b1f7349d9f12af))
9 | * **deps:** nbomber to v6 ([#258](https://github.com/miracum/fhir-pseudonymizer/issues/258)) ([d43602a](https://github.com/miracum/fhir-pseudonymizer/commit/d43602a7e6300662f80d4e08e0f3c56567593723))
10 | * **deps:** update all non-major dependencies ([#239](https://github.com/miracum/fhir-pseudonymizer/issues/239)) ([ab0ed2e](https://github.com/miracum/fhir-pseudonymizer/commit/ab0ed2e1079b949bdc0729f9a8a57c1d31bb87c0))
11 | * **deps:** update dependency aspnetcore.authentication.apikey to v9 ([#251](https://github.com/miracum/fhir-pseudonymizer/issues/251)) ([bf920c2](https://github.com/miracum/fhir-pseudonymizer/commit/bf920c24673b8c7739b7f1546568af2712cb76c9))
12 | * **deps:** update dependency csharpier to v1 ([2408ff2](https://github.com/miracum/fhir-pseudonymizer/commit/2408ff23bb406e3a1d477823b1d242c2773ce4c4))
13 | * **deps:** update dependency swashbuckle.aspnetcore to 8.1.2 ([#250](https://github.com/miracum/fhir-pseudonymizer/issues/250)) ([04e52cf](https://github.com/miracum/fhir-pseudonymizer/commit/04e52cf4a297476333ef9ef50e12e164b4dfebaa))
14 | * **deps:** update dependency verify.xunit to v30 ([#253](https://github.com/miracum/fhir-pseudonymizer/issues/253)) ([499be19](https://github.com/miracum/fhir-pseudonymizer/commit/499be19311dd6a71f03b9991048f7fb2bd5cfec9))
15 | * **deps:** update github-actions ([#248](https://github.com/miracum/fhir-pseudonymizer/issues/248)) ([053544a](https://github.com/miracum/fhir-pseudonymizer/commit/053544ac284b7bf71e163d8ca8879466762528cd))
16 | * **format:** ran csharpier ([#257](https://github.com/miracum/fhir-pseudonymizer/issues/257)) ([4025a6b](https://github.com/miracum/fhir-pseudonymizer/commit/4025a6bc07a66a374908134af1bacbe157359424))
17 |
18 |
19 | ### CI/CD
20 |
21 | * run ci on merge queue ([8a2a34b](https://github.com/miracum/fhir-pseudonymizer/commit/8a2a34b556e3284c3b1a1b47c8b5568782e0083a))
22 | * run ci on merge queue ([8a2a34b](https://github.com/miracum/fhir-pseudonymizer/commit/8a2a34b556e3284c3b1a1b47c8b5568782e0083a))
23 | * switch to release please ([3f72f46](https://github.com/miracum/fhir-pseudonymizer/commit/3f72f46c69a80189371ffc6ab7f3cd052ef9a168))
24 | * switch to release please ([3f72f46](https://github.com/miracum/fhir-pseudonymizer/commit/3f72f46c69a80189371ffc6ab7f3cd052ef9a168))
25 |
--------------------------------------------------------------------------------
/Dockerfile:
--------------------------------------------------------------------------------
1 | # kics false positive "Missing User Instruction":
2 | # kics-scan ignore-line
3 | FROM --platform=$BUILDPLATFORM mcr.microsoft.com/dotnet/aspnet:9.0.5-noble-chiseled@sha256:363d11f5c5979e7894c2eaaa878aed13946d52de68f5bef98d5bae11d2f242b1 AS runtime
4 | WORKDIR /opt/fhir-pseudonymizer
5 | EXPOSE 8080/tcp 8081/tcp
6 | USER 65532:65532
7 | ENV ASPNETCORE_ENVIRONMENT="Production" \
8 | DOTNET_CLI_TELEMETRY_OPTOUT=1 \
9 | ASPNETCORE_URLS="http://*:8080"
10 |
11 | FROM --platform=$BUILDPLATFORM mcr.microsoft.com/dotnet/sdk:9.0.300-noble@sha256:58fa5442c6da3bd654cab866fd6668de2713769511e412a3aa23c14368b84b16 AS build
12 | ENV DOTNET_CLI_TELEMETRY_OPTOUT=1
13 | WORKDIR /build
14 | COPY src/Directory.Build.props .
15 | COPY src/FhirPseudonymizer/FhirPseudonymizer.csproj .
16 | COPY src/FhirPseudonymizer/packages.lock.json .
17 | RUN dotnet restore --locked-mode
18 | COPY . .
19 |
20 | RUN dotnet publish \
21 | -c Release \
22 | -o /build/publish \
23 | -a "$TARGETARCH" \
24 | src/FhirPseudonymizer/FhirPseudonymizer.csproj
25 |
26 | FROM build AS build-test
27 | WORKDIR /build/src/FhirPseudonymizer.Tests
28 | RUN dotnet test \
29 | --configuration=Release \
30 | --collect:"XPlat Code Coverage" \
31 | --results-directory=./coverage \
32 | -l "console;verbosity=detailed" \
33 | --settings=runsettings.xml
34 |
35 | FROM scratch AS test
36 | WORKDIR /build/src/FhirPseudonymizer.Tests/coverage
37 | COPY --from=build-test /build/src/FhirPseudonymizer.Tests/coverage .
38 | ENTRYPOINT [ "true" ]
39 |
40 | FROM build AS build-stress-test
41 | WORKDIR /build/src/FhirPseudonymizer.StressTests
42 | RUN <
64 | # when running as non-root.
65 | # hadolint ignore=DL3002
66 | USER 0:0
67 | ENTRYPOINT ["dotnet"]
68 | CMD ["test", "/opt/fhir-pseudonymizer-stress/FhirPseudonymizer.StressTests.dll", "-l", "console;verbosity=detailed"]
69 |
70 | FROM runtime
71 | COPY LICENSE .
72 | COPY --from=build /build/publish/*anonymization.yaml /etc/
73 | COPY --from=build /build/publish .
74 | COPY --from=build /build/packages.lock.json .
75 |
76 | ENTRYPOINT ["dotnet", "/opt/fhir-pseudonymizer/FhirPseudonymizer.dll"]
77 |
--------------------------------------------------------------------------------
/FhirPseudonymizer.sln:
--------------------------------------------------------------------------------
1 |
2 | Microsoft Visual Studio Solution File, Format Version 12.00
3 | # Visual Studio Version 17
4 | VisualStudioVersion = 17.0.31903.59
5 | MinimumVisualStudioVersion = 10.0.40219.1
6 | Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "src", "src", "{46407B6E-B03E-4146-9139-F1EDF4AEB15B}"
7 | EndProject
8 | Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "FhirPseudonymizer", "src\FhirPseudonymizer\FhirPseudonymizer.csproj", "{8B0AA303-35F3-4D4B-904F-58296B98D15E}"
9 | EndProject
10 | Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "FhirPseudonymizer.Tests", "src\FhirPseudonymizer.Tests\FhirPseudonymizer.Tests.csproj", "{963E71CF-4D0C-43E3-B7A1-152538D40389}"
11 | EndProject
12 | Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "FhirPseudonymizer.StressTests", "src\FhirPseudonymizer.StressTests\FhirPseudonymizer.StressTests.csproj", "{52DDDF0D-51E6-4863-AA1A-052454AB5A43}"
13 | EndProject
14 | Global
15 | GlobalSection(SolutionConfigurationPlatforms) = preSolution
16 | Debug|Any CPU = Debug|Any CPU
17 | Release|Any CPU = Release|Any CPU
18 | EndGlobalSection
19 | GlobalSection(SolutionProperties) = preSolution
20 | HideSolutionNode = FALSE
21 | EndGlobalSection
22 | GlobalSection(ProjectConfigurationPlatforms) = postSolution
23 | {8B0AA303-35F3-4D4B-904F-58296B98D15E}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
24 | {8B0AA303-35F3-4D4B-904F-58296B98D15E}.Debug|Any CPU.Build.0 = Debug|Any CPU
25 | {8B0AA303-35F3-4D4B-904F-58296B98D15E}.Release|Any CPU.ActiveCfg = Release|Any CPU
26 | {8B0AA303-35F3-4D4B-904F-58296B98D15E}.Release|Any CPU.Build.0 = Release|Any CPU
27 | {963E71CF-4D0C-43E3-B7A1-152538D40389}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
28 | {963E71CF-4D0C-43E3-B7A1-152538D40389}.Debug|Any CPU.Build.0 = Debug|Any CPU
29 | {963E71CF-4D0C-43E3-B7A1-152538D40389}.Release|Any CPU.ActiveCfg = Release|Any CPU
30 | {963E71CF-4D0C-43E3-B7A1-152538D40389}.Release|Any CPU.Build.0 = Release|Any CPU
31 | {52DDDF0D-51E6-4863-AA1A-052454AB5A43}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
32 | {52DDDF0D-51E6-4863-AA1A-052454AB5A43}.Debug|Any CPU.Build.0 = Debug|Any CPU
33 | {52DDDF0D-51E6-4863-AA1A-052454AB5A43}.Release|Any CPU.ActiveCfg = Release|Any CPU
34 | {52DDDF0D-51E6-4863-AA1A-052454AB5A43}.Release|Any CPU.Build.0 = Release|Any CPU
35 | EndGlobalSection
36 | GlobalSection(NestedProjects) = preSolution
37 | {8B0AA303-35F3-4D4B-904F-58296B98D15E} = {46407B6E-B03E-4146-9139-F1EDF4AEB15B}
38 | {963E71CF-4D0C-43E3-B7A1-152538D40389} = {46407B6E-B03E-4146-9139-F1EDF4AEB15B}
39 | {52DDDF0D-51E6-4863-AA1A-052454AB5A43} = {46407B6E-B03E-4146-9139-F1EDF4AEB15B}
40 | EndGlobalSection
41 | EndGlobal
42 |
--------------------------------------------------------------------------------
/LICENSE:
--------------------------------------------------------------------------------
1 | MIT License
2 |
3 | Copyright (c) MIRACUM.org
4 |
5 | Permission is hereby granted, free of charge, to any person obtaining a copy
6 | of this software and associated documentation files (the "Software"), to deal
7 | in the Software without restriction, including without limitation the rights
8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9 | copies of the Software, and to permit persons to whom the Software is
10 | furnished to do so, subject to the following conditions:
11 |
12 | The above copyright notice and this permission notice shall be included in all
13 | copies or substantial portions of the Software.
14 |
15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21 | SOFTWARE
22 |
--------------------------------------------------------------------------------
/SECURITY.md:
--------------------------------------------------------------------------------
1 | # Security Policy
2 |
3 | ## Supported Versions
4 |
5 | Only the most recent major version is regularly updated and receives security fixes.
6 |
7 | ## Reporting a Vulnerability
8 |
9 | Please use the project's [private vulnerability reporting feature](https://github.com/miracum/fhir-pseudonymizer/security/advisories)
10 | to report any vulnerabilities. For more information, see
11 |
--------------------------------------------------------------------------------
/Taskfile.yaml:
--------------------------------------------------------------------------------
1 | version: "3"
2 |
3 | tasks:
4 | build-stress-test-image:
5 | desc: build the container image used for stress testing
6 | vars:
7 | BUILDKIT_PROGRESS: plain
8 | cmds:
9 | - docker build -t ghcr.io/miracum/fhir-pseudonymizer/stress-test:v1 --target=stress-test --iidfile=./stress-test-iid.txt .
10 | sources:
11 | - src/FhirPseudonymizer.StressTests/*.cs
12 | generates:
13 | - ./stress-test-iid.txt
14 |
15 | install-argo-cli:
16 | desc: download and install the argo workflows cli
17 | dir: tests/chaos
18 | cmds:
19 | - curl -sL -o - https://github.com/argoproj/argo-workflows/releases/download/v3.5.2/argo-linux-amd64.gz | gunzip > argo
20 | - chmod +x ./argo
21 | - ./argo version
22 | status:
23 | - ./argo version
24 |
25 | create-cluster:
26 | desc: create a KinD cluster
27 | cmds:
28 | - kind create cluster --name kind
29 | status:
30 | - kind get clusters | grep kind
31 |
32 | chaos-test:
33 | desc: run the chaos testing workflow
34 | dir: tests/chaos
35 | deps:
36 | - create-cluster
37 | - install-argo-cli
38 | - build-stress-test-image
39 | cmds:
40 | - helm repo add chaos-mesh https://charts.chaos-mesh.org
41 | - kind load docker-image ghcr.io/miracum/fhir-pseudonymizer/stress-test:v1
42 | - helm upgrade --install --create-namespace -n fhir-pseudonymizer -f fhir-pseudonymizer-values.yaml --wait fhir-pseudonymizer oci://ghcr.io/miracum/charts/fhir-pseudonymizer
43 | - helm upgrade --install chaos-mesh chaos-mesh/chaos-mesh --create-namespace --wait -n chaos-mesh --set chaosDaemon.runtime=containerd --set chaosDaemon.socketPath='/run/containerd/containerd.sock'
44 | - kubectl apply -f chaos-mesh-rbac.yaml
45 | - helm upgrade --install --create-namespace -n argo-workflows -f argo-workflows-values.yaml --wait argo-workflows oci://ghcr.io/argoproj/argo-helm/argo-workflows
46 | - ./argo submit workflow.yaml -n fhir-pseudonymizer --wait --log
47 |
--------------------------------------------------------------------------------
/benchmark/bombardier.sh:
--------------------------------------------------------------------------------
1 | #!/bin/bash
2 |
3 | RESOURCE_PATH=${RESOURCE_PATH:-bundle.json}
4 |
5 | bombardier -f "${RESOURCE_PATH}" \
6 | --timeout=10s \
7 | -H "Content-Type:application/fhir+json" \
8 | -m POST \
9 | -d 60s \
10 | -l \
11 | "http://localhost:5000/fhir/\$de-identify"
12 |
--------------------------------------------------------------------------------
/benchmark/observation.json:
--------------------------------------------------------------------------------
1 | {
2 | "resourceType": "Observation",
3 | "id": "o001",
4 | "identifier": [
5 | {
6 | "use": "official",
7 | "system": "http://www.bmc.nl/zorgportal/identifiers/observations",
8 | "value": "6323"
9 | },
10 | {
11 | "use": "official",
12 | "system": "http://www.bmc.nl/zorgportal/identifiers/observations2",
13 | "value": "6323"
14 | }
15 | ],
16 | "status": "final",
17 | "code": {
18 | "coding": [
19 | {
20 | "system": "http://loinc.org",
21 | "code": "15074-8",
22 | "display": "Glucose [Moles/volume] in Blood"
23 | }
24 | ]
25 | },
26 | "subject": {
27 | "type": "Patient",
28 | "identifier": {
29 | "system": "http://example.com/id",
30 | "value": "f1"
31 | }
32 | },
33 | "effectivePeriod": {
34 | "start": "2013-04-02T09:30:10+01:00"
35 | },
36 | "issued": "2013-04-03T15:30:10+01:00",
37 | "performer": [
38 | {
39 | "reference": "Practitioner/f005",
40 | "display": "A. Langeveld"
41 | }
42 | ],
43 | "valueQuantity": {
44 | "value": 6.3,
45 | "unit": "mmol/l",
46 | "system": "http://unitsofmeasure.org",
47 | "code": "mmol/L"
48 | },
49 | "interpretation": [
50 | {
51 | "coding": [
52 | {
53 | "system": "http://terminology.hl7.org/CodeSystem/v3-ObservationInterpretation",
54 | "code": "H",
55 | "display": "High"
56 | }
57 | ]
58 | }
59 | ],
60 | "referenceRange": [
61 | {
62 | "low": {
63 | "value": 3.1,
64 | "unit": "mmol/l",
65 | "system": "http://unitsofmeasure.org",
66 | "code": "mmol/L"
67 | },
68 | "high": {
69 | "value": 6.2,
70 | "unit": "mmol/l",
71 | "system": "http://unitsofmeasure.org",
72 | "code": "mmol/L"
73 | }
74 | }
75 | ]
76 | }
77 |
--------------------------------------------------------------------------------
/compose.dev.yaml:
--------------------------------------------------------------------------------
1 | services:
2 | jaeger:
3 | image: docker.io/jaegertracing/all-in-one:1.60@sha256:4fd2d70fa347d6a47e79fcb06b1c177e6079f92cba88b083153d56263082135e
4 | restart: unless-stopped
5 | deploy:
6 | resources:
7 | limits:
8 | memory: 2g
9 | cpus: "1"
10 | reservations:
11 | memory: 1g
12 | cpus: "1"
13 | cap_drop:
14 | - ALL
15 | ipc: none
16 | security_opt:
17 | - "no-new-privileges:true"
18 | privileged: false
19 | ports:
20 | - "6831:6831/udp"
21 | - "127.0.0.1:16686:16686"
22 |
23 | vfps-db:
24 | image: docker.io/library/postgres:17.5@sha256:6efd0df010dc3cb40d5e33e3ef84acecc5e73161bd3df06029ee8698e5e12c60
25 | restart: unless-stopped
26 | deploy:
27 | resources:
28 | limits:
29 | memory: 1g
30 | cpus: "1"
31 | reservations:
32 | memory: 1g
33 | cpus: "1"
34 | ipc: private
35 | security_opt:
36 | - "no-new-privileges:true"
37 | privileged: false
38 | environment:
39 | # kics-scan ignore-line
40 | POSTGRES_PASSWORD: postgres
41 | POSTGRES_DB: vfps
42 |
43 | vfps:
44 | image: ghcr.io/miracum/vfps:v1.3.6@sha256:21f45ea0c6f9b08d672b3e8529720b65340183c21609e13f056be25325d50be8
45 | restart: unless-stopped
46 | deploy:
47 | resources:
48 | limits:
49 | memory: 512m
50 | cpus: "2"
51 | reservations:
52 | memory: 512m
53 | cpus: "2"
54 | ipc: none
55 | cap_drop:
56 | - ALL
57 | read_only: true
58 | privileged: false
59 | security_opt:
60 | - "no-new-privileges:true"
61 | environment:
62 | COMPlus_EnableDiagnostics: "0"
63 | ForceRunDatabaseMigrations: "true"
64 | ConnectionStrings__PostgreSQL: "Host=vfps-db:5432;Database=vfps;Timeout=60;Max Auto Prepare=5;Application Name=vfps;Maximum Pool Size=50;"
65 | PGUSER: postgres
66 | # kics-scan ignore-line
67 | PGPASSWORD: postgres
68 | Tracing__IsEnabled: "true"
69 | Tracing__Jaeger__AgentHost: "jaeger"
70 | Pseudonymization__Caching__Namespaces__IsEnabled: "true"
71 | depends_on:
72 | - vfps-db
73 | ports:
74 | # Http1, Http2, Http3
75 | - "127.0.0.1:8080:8080"
76 | # Http2-only for plaintext gRPC
77 | - "127.0.0.1:8081:8081"
78 |
79 | gpas-entici-mock:
80 | image: docker.io/mockserver/mockserver:5.15.0@sha256:0f9ef78c94894ac3e70135d156193b25e23872575d58e2228344964273b4af6b
81 | ipc: none
82 | security_opt:
83 | - "no-new-privileges:true"
84 | cap_drop:
85 | - ALL
86 | privileged: false
87 | deploy:
88 | resources:
89 | limits:
90 | memory: 512m
91 | cpus: "1"
92 | reservations:
93 | memory: 512m
94 | cpus: "1"
95 | environment:
96 | MOCKSERVER_INITIALIZATION_JSON_PATH: /config/initializer.json
97 | MOCKSERVER_WATCH_INITIALIZATION_JSON: "true"
98 | ports:
99 | - 127.0.0.1:1080:1080
100 | volumes:
101 | - ./hack/mocks:/config:ro
102 |
103 | keycloak:
104 | image: quay.io/keycloak/keycloak:26.2.4@sha256:4a81762677f8911c6266e3dea57a2d78fa17bd63408debbf23afd8cc46fe256e
105 | restart: unless-stopped
106 | profiles:
107 | - keycloak
108 | ipc: none
109 | security_opt:
110 | - "no-new-privileges:true"
111 | cap_drop:
112 | - ALL
113 | privileged: false
114 | deploy:
115 | resources:
116 | limits:
117 | memory: 2g
118 | cpus: "1"
119 | reservations:
120 | memory: 2g
121 | cpus: "1"
122 | command:
123 | - start-dev
124 | - --import-realm
125 | environment:
126 | KEYCLOAK_ADMIN: admin
127 | # kics-scan ignore-line
128 | KEYCLOAK_ADMIN_PASSWORD: admin
129 | volumes:
130 | - type: bind
131 | # /opt/keycloak/bin/kc.sh export --file /tmp/fhir-pseudonymizer-test-realm-export.json --realm fhir-pseudonymizer-test
132 | source: ./hack/keycloak/fhir-pseudonymizer-test-realm-export.json
133 | target: /opt/keycloak/data/import/fhir-pseudonymizer-test-realm-export.json
134 | read_only: true
135 | ports:
136 | - "127.0.0.1:8083:8080"
137 |
--------------------------------------------------------------------------------
/compose/README.md:
--------------------------------------------------------------------------------
1 | # Deploy the FHIR Pseudonymizer using Compose
2 |
3 | This uses an example anonymization config based on the [HIPAA Safe Harbor rules](anonymization-hipaa.yaml):
4 |
5 | ```sh
6 | docker compose up
7 | # or
8 | nerdctl compose up
9 | # or
10 | podman-compose up
11 | ```
12 |
13 | Open your browser at . Or simply POST any FHIR resource to .
14 |
--------------------------------------------------------------------------------
/compose/compose.yaml:
--------------------------------------------------------------------------------
1 | services:
2 | fhir-pseudonymizer:
3 | image: ghcr.io/miracum/fhir-pseudonymizer:v2.22.10 # x-release-please-version
4 | restart: unless-stopped
5 | cap_drop:
6 | - ALL
7 | ipc: none
8 | security_opt:
9 | - "no-new-privileges:true"
10 | read_only: true
11 | privileged: false
12 | environment:
13 | DOTNET_EnableDiagnostics: "0"
14 | PseudonymizationService: "None"
15 | AnonymizationEngineConfigPath: "/opt/fhir-pseudonymizer/anonymization-hipaa.yaml"
16 | UseSystemTextJsonFhirSerializer: "true"
17 | volumes:
18 | - "./anonymization-hipaa.yaml:/opt/fhir-pseudonymizer/anonymization-hipaa.yaml:ro"
19 | ports:
20 | - "127.0.0.1:8080:8080"
21 |
--------------------------------------------------------------------------------
/docs/img/logo.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/miracum/fhir-pseudonymizer/83ab36c7f2fe4add39cf7fb7e706a4a32227b3e3/docs/img/logo.png
--------------------------------------------------------------------------------
/docs/img/openapi.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/miracum/fhir-pseudonymizer/83ab36c7f2fe4add39cf7fb7e706a4a32227b3e3/docs/img/openapi.png
--------------------------------------------------------------------------------
/hack/mocks/README.md:
--------------------------------------------------------------------------------
1 | # Generating MockServer's initialization config
2 |
3 | Because it's easier to read, the initializers are managed as YAML and converted to JSON
4 | for MockServer.
5 |
6 | Run:
7 |
8 | ```sh
9 | yq -o json hack/mocks/initializer.yaml > hack/mocks/initializer.json
10 | ```
11 |
12 | to convert.
13 |
--------------------------------------------------------------------------------
/hack/mocks/initializer.json:
--------------------------------------------------------------------------------
1 | [
2 | {
3 | "id": "gpas-pseudonymize",
4 | "httpRequest": {
5 | "method": "POST",
6 | "path": "/ttp-fhir/fhir/gpas/$pseudonymizeAllowCreate"
7 | },
8 | "httpResponseTemplate": {
9 | "templateType": "VELOCITY",
10 | "template": "{\n \"body\": {\n \"resourceType\": \"Parameters\",\n \"parameter\": [\n {\n \"name\": \"pseudonym\",\n \"part\": [\n {\n \"name\": \"original\",\n \"valueIdentifier\": {\n \"system\": \"https://ths-greifswald.de/gpas\",\n \"value\": \"test\"\n }\n },\n {\n \"name\": \"target\",\n \"valueIdentifier\": {\n \"system\": \"https://ths-greifswald.de/gpas\",\n \"value\": \"benchmark\"\n }\n },\n {\n \"name\": \"pseudonym\",\n \"valueIdentifier\": {\n \"system\": \"https://ths-greifswald.de/gpas\",\n #set($jsonBody = $json.parse($!request.body))\n #set($originalValue = \"\")\n #foreach($parameter in $jsonBody.parameter)\n #if($parameter.name == 'original')\n #set($originalValue = $parameter.valueString)\n #end\n #end\n \"value\": \"pseuded-$originalValue\"\n }\n }\n ]\n }\n ]\n }\n}\n"
11 | }
12 | },
13 | {
14 | "id": "entici-pseudonymize",
15 | "httpRequest": {
16 | "method": "POST",
17 | "path": "/entici/$pseudonymize"
18 | },
19 | "httpResponseTemplate": {
20 | "templateType": "VELOCITY",
21 | "template": "{\n \"body\": {\n \"resourceType\": \"Parameters\",\n \"parameter\": [\n {\n \"name\": \"pseudonym\",\n \"valueIdentifier\": {\n \"use\": \"secondary\",\n \"system\": \"urn:fdc:difuture.de:trustcenter.plain\",\n #set($jsonBody = $json.parse($!request.body))\n #set($originalValue = \"\")\n #foreach($parameter in $jsonBody.parameter)\n #if($parameter.name == 'identifier')\n #set($originalValue = $parameter.valueIdentifier.value)\n #end\n #end\n \"value\": \"pseuded-$originalValue\"\n }\n }\n ]\n }\n}\n"
22 | }
23 | }
24 | ]
25 |
--------------------------------------------------------------------------------
/hack/mocks/initializer.yaml:
--------------------------------------------------------------------------------
1 | - id: gpas-pseudonymize
2 | httpRequest:
3 | method: POST
4 | path: /ttp-fhir/fhir/gpas/$pseudonymizeAllowCreate
5 | httpResponseTemplate:
6 | templateType: VELOCITY
7 | template: |
8 | {
9 | "body": {
10 | "resourceType": "Parameters",
11 | "parameter": [
12 | {
13 | "name": "pseudonym",
14 | "part": [
15 | {
16 | "name": "original",
17 | "valueIdentifier": {
18 | "system": "https://ths-greifswald.de/gpas",
19 | "value": "test"
20 | }
21 | },
22 | {
23 | "name": "target",
24 | "valueIdentifier": {
25 | "system": "https://ths-greifswald.de/gpas",
26 | "value": "benchmark"
27 | }
28 | },
29 | {
30 | "name": "pseudonym",
31 | "valueIdentifier": {
32 | "system": "https://ths-greifswald.de/gpas",
33 | #set($jsonBody = $json.parse($!request.body))
34 | #set($originalValue = "")
35 | #foreach($parameter in $jsonBody.parameter)
36 | #if($parameter.name == 'original')
37 | #set($originalValue = $parameter.valueString)
38 | #end
39 | #end
40 | "value": "pseuded-$originalValue"
41 | }
42 | }
43 | ]
44 | }
45 | ]
46 | }
47 | }
48 | - id: entici-pseudonymize
49 | httpRequest:
50 | method: POST
51 | path: /entici/$pseudonymize
52 | httpResponseTemplate:
53 | templateType: VELOCITY
54 | template: |
55 | {
56 | "body": {
57 | "resourceType": "Parameters",
58 | "parameter": [
59 | {
60 | "name": "pseudonym",
61 | "valueIdentifier": {
62 | "use": "secondary",
63 | "system": "urn:fdc:difuture.de:trustcenter.plain",
64 | #set($jsonBody = $json.parse($!request.body))
65 | #set($originalValue = "")
66 | #foreach($parameter in $jsonBody.parameter)
67 | #if($parameter.name == 'identifier')
68 | #set($originalValue = $parameter.valueIdentifier.value)
69 | #end
70 | #end
71 | "value": "pseuded-$originalValue"
72 | }
73 | }
74 | ]
75 | }
76 | }
77 |
--------------------------------------------------------------------------------
/release-please-config.json:
--------------------------------------------------------------------------------
1 | {
2 | "$schema": "https://raw.githubusercontent.com/googleapis/release-please/refs/heads/main/schemas/config.json",
3 | "bump-minor-pre-major": true,
4 | "bump-patch-for-minor-pre-major": true,
5 | "include-v-in-tag": true,
6 | "separate-pull-requests": true,
7 | "extra-label": "release-please",
8 | "release-type": "simple",
9 | "packages": {
10 | ".": {
11 | "release-type": "simple",
12 | "bump-minor-pre-major": true,
13 | "bump-patch-for-minor-pre-major": true,
14 | "changelog-sections": [
15 | { "type": "feat", "section": "Features" },
16 | { "type": "fix", "section": "Bug Fixes" },
17 | { "type": "perf", "section": "Performance Improvements" },
18 | { "type": "docs", "section": "Documentation", "hidden": false },
19 | {
20 | "type": "chore",
21 | "section": "Miscellaneous Chores",
22 | "hidden": false
23 | },
24 | { "type": "build", "section": "Build", "hidden": false },
25 | { "type": "ci", "section": "CI/CD", "hidden": false }
26 | ],
27 | "extra-files": [
28 | {
29 | "type": "generic",
30 | "path": "src/Directory.Build.props"
31 | },
32 | {
33 | "type": "generic",
34 | "path": "compose/compose.yaml"
35 | },
36 | {
37 | "type": "generic",
38 | "path": "README.md"
39 | },
40 | {
41 | "type": "generic",
42 | "path": "tests/chaos/fhir-pseudonymizer-values.yaml"
43 | }
44 | ]
45 | }
46 | }
47 | }
48 |
--------------------------------------------------------------------------------
/src/Directory.Build.props:
--------------------------------------------------------------------------------
1 |
2 |
3 | net9.0
4 | latest
5 |
6 | enable
7 | latest
8 | true
9 | false
10 | miracum.org
11 | A REST service to pseudonymize and anonymize FHIR resources.
12 | © miracum.org. All rights reserved.
13 | en-US
14 | miracum.org
15 |
16 | 2.22.10
17 |
18 |
19 |
20 | true
21 |
22 |
23 |
--------------------------------------------------------------------------------
/src/FhirPseudonymizer.StressTests/FhirPseudonymizer.StressTests.csproj:
--------------------------------------------------------------------------------
1 |
2 |
3 | false
4 | ..\..\stylecop.ruleset.xml
5 |
6 |
7 |
8 |
9 |
10 |
11 |
12 | runtime; build; native; contentfiles; analyzers; buildtransitive
13 | all
14 |
15 |
16 |
17 |
18 |
19 |
20 |
21 |
22 |
23 |
24 |
25 |
26 |
--------------------------------------------------------------------------------
/src/FhirPseudonymizer.StressTests/Usings.cs:
--------------------------------------------------------------------------------
1 | global using FluentAssertions;
2 | global using NBomber.Contracts;
3 | global using NBomber.CSharp;
4 | global using Xunit;
5 |
--------------------------------------------------------------------------------
/src/FhirPseudonymizer.Tests/CryptoHashProcessorTests.cs:
--------------------------------------------------------------------------------
1 | using Hl7.Fhir.ElementModel;
2 | using Hl7.Fhir.FhirPath;
3 | using Hl7.Fhir.Model;
4 | using Microsoft.Health.Fhir.Anonymizer.Core.Extensions;
5 | using Microsoft.Health.Fhir.Anonymizer.Core.Processors;
6 |
7 | namespace FhirPseudonymizer.Tests;
8 |
9 | public class CryptoHashProcessorTests
10 | {
11 | public static IEnumerable