Skip to content

[Social Work] - Update NPDB Categories for Adverse Actions - #1789

Open
landonshumway-ia wants to merge 12 commits into
csg-org:mainfrom
InspiringApps:feat/sw-encumbrance-categories
Open

[Social Work] - Update NPDB Categories for Adverse Actions#1789
landonshumway-ia wants to merge 12 commits into
csg-org:mainfrom
InspiringApps:feat/sw-encumbrance-categories

Conversation

@landonshumway-ia

@landonshumway-ia landonshumway-ia commented Jul 28, 2026

Copy link
Copy Markdown
Collaborator

This updates the list of NPDB categories and disciplinary action types to be used by the Social Work API. It also updates the API schema to support accepting a list of multiple NPDB categories for one encumbrance.

In addition, this updates the public provider details endpoint for Social Work to return a subset of the adverse action data so that the frontend can display which records are under discipline, as well as when they were encumbered and when encumbrances were lifted.

This also updates the Social Work non-prod OpenSearch configuration to use 3 data nodes instead of 1 to avoid test/demo outages in those environments.

Finally, as part of enhancing our security posture, this sets the DEBUG env var to false in all environments and removes or masks PII in all non-debug level logs from all three backend APIs (JCC, Cosmetology, and Social Work).

Requirements List

  • Any existing encumbrances in the Social Work test environment (if there are any) will need to be deleted as they will be using unsupported categories that will break the schema.

Testing List

  • yarn test:unit:all should run without errors or warnings
  • yarn serve should run without errors or warnings
  • yarn build should run without errors or warnings
  • For API configuration changes: CDK tests added/updated in backend/compact-connect/tests/unit/test_api.py
  • For API endpoint changes: OpenAPI spec updated to show latest endpoint configuration run compact-connect/bin/download_oas30.py
  • Code review

Closes #1786 #1760 #1706 #1742

Summary by CodeRabbit

  • New Features

    • Expanded clinical privilege action categories and encumbrance types with more detailed options.
    • Adverse action requests can now include multiple categories.
    • Public provider responses now include sanitized adverse action details for licenses and privileges.
    • Non-production search infrastructure now uses multi-zone, three-node configurations for improved resilience.
  • Bug Fixes

    • Reduced exposure of sensitive personal information in application logs through masking and lower-detail logging.
    • Improved operational traceability by using non-sensitive identifiers in error and status logs.

@coderabbitai

coderabbitai Bot commented Jul 28, 2026

Copy link
Copy Markdown
Contributor

Review Change Stack

📝 Walkthrough

Walkthrough

The pull request expands Social Work adverse-action contracts and public responses, changes non-production OpenSearch topology, reduces PII exposure in logs across applications, masks email addresses, disables debug logging by default, and updates related examples, fixtures, and tests.

Changes

Sensitive logging and email masking

Layer / File(s) Summary
Email masking and debug defaults
backend/common-cdk/..., backend/*/lambdas/nodejs/lib/email/*, backend/*/lambdas/nodejs/cognito-emails/*
Email addresses are masked in Cognito and ingest-event logs. Shared Lambda debug logging defaults to false.
Python PII logging controls
backend/*/lambdas/python/common/*, backend/*/lambdas/python/provider-data-v1/*, backend/*/lambdas/python/search/*, backend/*/lambdas/python/staff-user-pre-token/*, backend/*/stacks/persistent_stack/*
PII-bearing fields move to DEBUG, or logs use identifiers and query parameter names.

Social Work adverse-action categories and public responses

Layer / File(s) Summary
Adverse-action contracts and public schemas
backend/social-work-app/lambdas/python/common/cc_common/data_model/schema/*, backend/social-work-app/stacks/api_stack/v1_api/api_model.py, backend/social-work-app/stacks/search_api_stack/v1_api/api_model.py
Clinical privilege categories and encumbrance types expand. Multiple categories are accepted. Sanitized adverse actions are added to public license and privilege responses.
Adverse-action persistence and response validation
backend/social-work-app/lambdas/python/common/tests/*, backend/social-work-app/lambdas/python/provider-data-v1/tests/*, backend/social-work-app/lambdas/python/search/tests/*
Fixtures and tests cover expanded values, multiple-category persistence, non-empty validation, and sanitized public responses.
Generated API and Postman artifacts
backend/social-work-app/docs/*, backend/social-work-app/tests/resources/snapshots/*, backend/social-work-app/tests/smoke/*
OpenAPI, Postman examples, JSON schema snapshots, and smoke payloads use the updated contracts and response shape.

Non-production search topology

Layer / File(s) Summary
Search domain capacity and networking
backend/social-work-app/stacks/search_persistent_stack/*
Non-production OpenSearch uses three data nodes, three availability zones, zone awareness, and three private isolated subnets.
Search topology tests and defaults
backend/social-work-app/lambdas/python/search/*, backend/social-work-app/tests/app/test_search_persistent_stack.py
Replica defaults and stack assertions match the three-node, multi-subnet topology.

Estimated code review effort: 5 (Critical) | ~120 minutes

Sequence Diagram(s)

sequenceDiagram
  participant ProviderClient
  participant PublicProviderLookup
  participant ProviderDataStore
  participant PublicResponseSchema
  ProviderClient->>PublicProviderLookup: request provider data
  PublicProviderLookup->>ProviderDataStore: load licenses, privileges, and adverse actions
  ProviderDataStore-->>PublicProviderLookup: return provider records
  PublicProviderLookup->>PublicResponseSchema: serialize sanitized adverseActions
  PublicResponseSchema-->>ProviderClient: return public provider response
Loading

Possibly related PRs

Suggested reviewers: jusdino, jlkravitz

🚥 Pre-merge checks | ✅ 3 | ❌ 2

❌ Failed checks (2 warnings)

Check name Status Explanation Resolution
Out of Scope Changes check ⚠️ Warning The PR includes public adverse-action responses, OpenSearch scaling, and cross-API logging changes that are not covered by the provided issue #1786. Split unrelated changes into separate pull requests or link issues that explicitly cover the public endpoint, OpenSearch, and logging-security objectives.
Docstring Coverage ⚠️ Warning Docstring coverage is 75.00% which is insufficient. The required threshold is 80.00%. Write docstrings for the functions missing them to satisfy the coverage threshold.
✅ Passed checks (3 passed)
Check name Status Explanation
Title check ✅ Passed The title clearly identifies the primary Social Work change: updating NPDB adverse-action categories.
Description check ✅ Passed The description explains the main changes and includes requirements, testing commands, and linked issues, but omits the template's Description List section.
Linked Issues check ✅ Passed The PR updates the required #1786 categories across schemas, API documentation, Postman examples, and automated tests.
✨ Finishing Touches 💡 1
🛠️ Fix failing CI checks 💡
  • Create stacked PR
  • Commit on current branch
🧪 Generate unit tests (beta)
  • Create PR with unit tests

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands.

@landonshumway-ia
landonshumway-ia marked this pull request as ready for review July 29, 2026 15:52

@coderabbitai coderabbitai Bot 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.

Actionable comments posted: 3

Caution

Some comments are outside the diff and can’t be posted inline due to platform limitations.

⚠️ Outside diff range comments (2)
backend/social-work-app/tests/resources/snapshots/PRIVILEGE_ENCUMBRANCE_REQUEST_SCHEMA.json (1)

32-48: 🗄️ Data Integrity & Integration | 🟠 Major | ⚡ Quick win

Publish the non-empty array constraint.

The runtime schema rejects [], but this request schema permits an empty array because it lacks "minItems": 1. Generated clients can therefore send a payload the API rejects. Update the API-schema generation/model and regenerate artifacts.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In
`@backend/social-work-app/tests/resources/snapshots/PRIVILEGE_ENCUMBRANCE_REQUEST_SCHEMA.json`
around lines 32 - 48, Update the model or API-schema generation source for
clinicalPrivilegeActionCategories to require at least one item by setting the
array constraint to minItems 1, then regenerate the affected schema artifacts so
the published request schema rejects empty arrays.
backend/social-work-app/docs/internal/api-specification/latest-oas30.json (1)

4759-4949: 🗄️ Data Integrity & Integration | 🟠 Major | ⚡ Quick win

Regenerate public provider GET docs — both are stale after the new adverseActions field was added. LicensePublicResponseSchema and PrivilegePublicResponseSchema now serialize a sanitized adverseActions list (verified correct in the PUBLIC_GET_PROVIDER_RESPONSE_SCHEMA.json test snapshot), but the generated API docs for /v1/public/compacts/{compact}/providers/{providerId} were not regenerated to match, leaving one shared documentation gap across two artifacts.

  • backend/social-work-app/docs/internal/api-specification/latest-oas30.json#L4759-L4949: add adverseActions (using the AdverseActionPublicResponseSchema shape) to both the licenses and privileges item definitions of TestALicenI8ObIK8aJtpE.
  • backend/social-work-app/docs/internal/postman/postman-collection.json#L4062-L4165: regenerate/update the /v1/public/compacts/:compact/providers/:providerId example response body to include adverseActions entries in licenses/privileges, matching the pattern already present in the general provider GET example (line 1280).
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@backend/social-work-app/docs/internal/api-specification/latest-oas30.json`
around lines 4759 - 4949, Regenerate the public provider GET documentation to
include the sanitized adverseActions field in both license and privilege
responses. In
backend/social-work-app/docs/internal/api-specification/latest-oas30.json lines
4759-4949, add adverseActions using the AdverseActionPublicResponseSchema shape
to both licenses and privileges item definitions under TestALicenI8ObIK8aJtpE;
in backend/social-work-app/docs/internal/postman/postman-collection.json lines
4062-4165, update the provider response example to include matching
adverseActions entries, following the existing general provider GET example.
🧹 Nitpick comments (3)
backend/social-work-app/tests/app/test_search_persistent_stack.py (1)

139-145: 📐 Maintainability & Code Quality | 🔵 Trivial | ⚡ Quick win

Assert replicas and zone awareness as well.

This only guards InstanceCount; add synthesized-template assertions for three-AZ zone awareness and numberOfReplicas: 2 on the index-manager custom resource to cover the full non-prod topology contract.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@backend/social-work-app/tests/app/test_search_persistent_stack.py` around
lines 139 - 145, Extend the sandbox OpenSearch assertions in
test_search_persistent_stack.py to verify three-AZ zone awareness on the
AWS::OpenSearchService::Domain, alongside the existing InstanceCount check. Also
assert that the index-manager custom resource sets numberOfReplicas to 2, using
the synthesized template’s corresponding resource properties.
backend/social-work-app/stacks/search_persistent_stack/provider_search_domain.py (1)

316-328: 🔒 Security & Privacy | 🔵 Trivial | ⚡ Quick win

Update the non-prod CDK-Nag justifications.

This three-node configuration invalidates the “single-node non-prod/test” rationale still used for OS4 and OS7 suppressions. Describe the actual no-dedicated-master/no-standby cost trade-off so the generated compliance evidence remains accurate.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In
`@backend/social-work-app/stacks/search_persistent_stack/provider_search_domain.py`
around lines 316 - 328, Update the CDK-Nag suppression justifications associated
with OS4 and OS7 in the non-production capacity configuration to remove the
outdated single-node/test rationale. Document that the cluster uses three data
nodes without dedicated master nodes or multi-AZ standby as an intentional cost
trade-off, while preserving the existing suppression behavior.
backend/cosmetology-app/lambdas/nodejs/lib/email/base-email-service.ts (1)

67-80: 🔒 Security & Privacy | 🔵 Trivial | ⚡ Quick win

Add focused tests for both email-masking helpers.

The duplicated privacy-sensitive implementations should share coverage for valid addresses, malformed inputs, and recipient arrays.

  • backend/cosmetology-app/lambdas/nodejs/lib/email/base-email-service.ts#L67-L80: add unit tests for maskEmail and maskEmails.
  • backend/social-work-app/lambdas/nodejs/lib/email/base-email-service.ts#L67-L80: add equivalent unit tests for the Social Work implementation.
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@backend/cosmetology-app/lambdas/nodejs/lib/email/base-email-service.ts`
around lines 67 - 80, Add focused unit tests for maskEmail and maskEmails in
both
backend/cosmetology-app/lambdas/nodejs/lib/email/base-email-service.ts:67-80 and
backend/social-work-app/lambdas/nodejs/lib/email/base-email-service.ts:67-80.
Cover valid addresses, malformed inputs, and arrays of recipients, verifying
both implementations produce the expected masked output and preserve privacy
behavior.
🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Inline comments:
In
`@backend/social-work-app/docs/search-internal/postman/postman-collection.json`:
- Line 468: Replace every impossible calendar value in the response body’s
format-date fields with valid ISO 8601 calendar dates, including invalid
day/month combinations such as November 31 and February 31. Preserve the
response schema and placeholder fields, and ensure all date examples represent
real calendar days.

In `@backend/social-work-app/stacks/api_stack/v1_api/api_model.py`:
- Around line 787-797: Reject empty NPDB category arrays by adding min_items=1
to the outer category-array JsonSchema in api_model.py. Regenerate the
corresponding schema snapshots with "minItems": 1 in
backend/social-work-app/tests/resources/snapshots/LICENSE_ENCUMBRANCE_REQUEST_SCHEMA.json
(39-55), PATCH_LICENSE_INVESTIGATION_REQUEST_SCHEMA.json (54-70), and
PATCH_PRIVILEGE_INVESTIGATION_REQUEST_SCHEMA.json (40-56); no other changes are
needed.

In `@backend/social-work-app/stacks/search_persistent_stack/index_manager.py`:
- Around line 18-22: Update OpenSearchIndexManager.on_update to apply the
configured numberOfReplicas to existing indexes, including indexes whose aliases
already exist and therefore bypass create_provider_index_with_alias(). Reuse the
existing index and alias management symbols to update settings in place,
preserving existing data and the current creation behavior.

---

Outside diff comments:
In `@backend/social-work-app/docs/internal/api-specification/latest-oas30.json`:
- Around line 4759-4949: Regenerate the public provider GET documentation to
include the sanitized adverseActions field in both license and privilege
responses. In
backend/social-work-app/docs/internal/api-specification/latest-oas30.json lines
4759-4949, add adverseActions using the AdverseActionPublicResponseSchema shape
to both licenses and privileges item definitions under TestALicenI8ObIK8aJtpE;
in backend/social-work-app/docs/internal/postman/postman-collection.json lines
4062-4165, update the provider response example to include matching
adverseActions entries, following the existing general provider GET example.

In
`@backend/social-work-app/tests/resources/snapshots/PRIVILEGE_ENCUMBRANCE_REQUEST_SCHEMA.json`:
- Around line 32-48: Update the model or API-schema generation source for
clinicalPrivilegeActionCategories to require at least one item by setting the
array constraint to minItems 1, then regenerate the affected schema artifacts so
the published request schema rejects empty arrays.

---

Nitpick comments:
In `@backend/cosmetology-app/lambdas/nodejs/lib/email/base-email-service.ts`:
- Around line 67-80: Add focused unit tests for maskEmail and maskEmails in both
backend/cosmetology-app/lambdas/nodejs/lib/email/base-email-service.ts:67-80 and
backend/social-work-app/lambdas/nodejs/lib/email/base-email-service.ts:67-80.
Cover valid addresses, malformed inputs, and arrays of recipients, verifying
both implementations produce the expected masked output and preserve privacy
behavior.

In
`@backend/social-work-app/stacks/search_persistent_stack/provider_search_domain.py`:
- Around line 316-328: Update the CDK-Nag suppression justifications associated
with OS4 and OS7 in the non-production capacity configuration to remove the
outdated single-node/test rationale. Document that the cluster uses three data
nodes without dedicated master nodes or multi-AZ standby as an intentional cost
trade-off, while preserving the existing suppression behavior.

In `@backend/social-work-app/tests/app/test_search_persistent_stack.py`:
- Around line 139-145: Extend the sandbox OpenSearch assertions in
test_search_persistent_stack.py to verify three-AZ zone awareness on the
AWS::OpenSearchService::Domain, alongside the existing InstanceCount check. Also
assert that the index-manager custom resource sets numberOfReplicas to 2, using
the synthesized template’s corresponding resource properties.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: defaults

Review profile: CHILL

Plan: Pro Plus

Run ID: 8e44f03d-7418-44d7-81d2-de88c23586db

📥 Commits

Reviewing files that changed from the base of the PR and between faed2aa and 519280b.

⛔ Files ignored due to path filters (3)
  • backend/compact-connect/lambdas/nodejs/yarn.lock is excluded by !**/yarn.lock, !**/*.lock
  • backend/cosmetology-app/lambdas/nodejs/yarn.lock is excluded by !**/yarn.lock, !**/*.lock
  • backend/social-work-app/lambdas/nodejs/yarn.lock is excluded by !**/yarn.lock, !**/*.lock
📒 Files selected for processing (68)
  • backend/common-cdk/common_constructs/stack.py
  • backend/compact-connect/lambdas/nodejs/cognito-emails/lambda.ts
  • backend/compact-connect/lambdas/nodejs/lib/email/base-email-service.ts
  • backend/compact-connect/lambdas/nodejs/lib/email/ingest-event-email-service.ts
  • backend/compact-connect/lambdas/nodejs/package.json
  • backend/compact-connect/lambdas/python/common/cc_common/data_model/data_client.py
  • backend/compact-connect/lambdas/python/common/cc_common/data_model/user_client.py
  • backend/compact-connect/lambdas/python/common/cc_common/signature_auth.py
  • backend/compact-connect/lambdas/python/common/cc_common/utils.py
  • backend/compact-connect/lambdas/python/provider-data-v1/handlers/bulk_upload.py
  • backend/compact-connect/lambdas/python/provider-data-v1/handlers/privileges.py
  • backend/compact-connect/lambdas/python/provider-data-v1/handlers/registration.py
  • backend/compact-connect/lambdas/python/search/handlers/populate_provider_documents.py
  • backend/compact-connect/lambdas/python/staff-user-pre-token/main.py
  • backend/compact-connect/stacks/persistent_stack/staff_users.py
  • backend/cosmetology-app/lambdas/nodejs/cognito-emails/lambda.ts
  • backend/cosmetology-app/lambdas/nodejs/lib/email/base-email-service.ts
  • backend/cosmetology-app/lambdas/nodejs/lib/email/ingest-event-email-service.ts
  • backend/cosmetology-app/lambdas/nodejs/package.json
  • backend/cosmetology-app/lambdas/python/common/cc_common/data_model/data_client.py
  • backend/cosmetology-app/lambdas/python/common/cc_common/data_model/user_client.py
  • backend/cosmetology-app/lambdas/python/common/cc_common/signature_auth.py
  • backend/cosmetology-app/lambdas/python/common/cc_common/utils.py
  • backend/cosmetology-app/lambdas/python/provider-data-v1/handlers/bulk_upload.py
  • backend/cosmetology-app/lambdas/python/search/handlers/populate_provider_documents.py
  • backend/cosmetology-app/lambdas/python/staff-user-pre-token/main.py
  • backend/cosmetology-app/stacks/persistent_stack/staff_users.py
  • backend/social-work-app/docs/internal/api-specification/latest-oas30.json
  • backend/social-work-app/docs/internal/postman/postman-collection.json
  • backend/social-work-app/docs/search-internal/api-specification/latest-oas30.json
  • backend/social-work-app/docs/search-internal/postman/postman-collection.json
  • backend/social-work-app/lambdas/nodejs/cognito-emails/lambda.ts
  • backend/social-work-app/lambdas/nodejs/lib/email/base-email-service.ts
  • backend/social-work-app/lambdas/nodejs/lib/email/ingest-event-email-service.ts
  • backend/social-work-app/lambdas/nodejs/package.json
  • backend/social-work-app/lambdas/python/common/cc_common/data_model/data_client.py
  • backend/social-work-app/lambdas/python/common/cc_common/data_model/schema/adverse_action/api.py
  • backend/social-work-app/lambdas/python/common/cc_common/data_model/schema/common.py
  • backend/social-work-app/lambdas/python/common/cc_common/data_model/schema/license/api.py
  • backend/social-work-app/lambdas/python/common/cc_common/data_model/schema/privilege/api.py
  • backend/social-work-app/lambdas/python/common/cc_common/data_model/schema/provider/api.py
  • backend/social-work-app/lambdas/python/common/cc_common/data_model/user_client.py
  • backend/social-work-app/lambdas/python/common/cc_common/signature_auth.py
  • backend/social-work-app/lambdas/python/common/cc_common/utils.py
  • backend/social-work-app/lambdas/python/common/common_test/test_constants.py
  • backend/social-work-app/lambdas/python/common/tests/resources/api/adverse-action-post.json
  • backend/social-work-app/lambdas/python/common/tests/unit/test_data_model/test_schema/test_adverse_action.py
  • backend/social-work-app/lambdas/python/common/tests/unit/test_data_model/test_schema/test_investigation.py
  • backend/social-work-app/lambdas/python/provider-data-v1/tests/function/test_handlers/test_encumbrance.py
  • backend/social-work-app/lambdas/python/provider-data-v1/tests/function/test_handlers/test_public_lookup.py
  • backend/social-work-app/lambdas/python/search/handlers/populate_provider_documents.py
  • backend/social-work-app/lambdas/python/search/tests/function/test_public_search_providers.py
  • backend/social-work-app/lambdas/python/staff-user-pre-token/main.py
  • backend/social-work-app/stacks/api_stack/v1_api/api_model.py
  • backend/social-work-app/stacks/persistent_stack/staff_users.py
  • backend/social-work-app/stacks/search_api_stack/v1_api/api_model.py
  • backend/social-work-app/stacks/search_persistent_stack/index_manager.py
  • backend/social-work-app/stacks/search_persistent_stack/provider_search_domain.py
  • backend/social-work-app/tests/app/test_search_persistent_stack.py
  • backend/social-work-app/tests/resources/snapshots/GET_PROVIDER_RESPONSE_SCHEMA.json
  • backend/social-work-app/tests/resources/snapshots/LICENSE_ENCUMBRANCE_REQUEST_SCHEMA.json
  • backend/social-work-app/tests/resources/snapshots/PATCH_LICENSE_INVESTIGATION_REQUEST_SCHEMA.json
  • backend/social-work-app/tests/resources/snapshots/PATCH_PRIVILEGE_INVESTIGATION_REQUEST_SCHEMA.json
  • backend/social-work-app/tests/resources/snapshots/PRIVILEGE_ENCUMBRANCE_REQUEST_SCHEMA.json
  • backend/social-work-app/tests/resources/snapshots/PROVIDER_USER_RESPONSE_SCHEMA.json
  • backend/social-work-app/tests/resources/snapshots/PUBLIC_GET_PROVIDER_RESPONSE_SCHEMA.json
  • backend/social-work-app/tests/smoke/encumbrance_smoke_tests.py
  • backend/social-work-app/tests/smoke/investigation_smoke_tests.py

Comment thread backend/social-work-app/stacks/api_stack/v1_api/api_model.py
@landonshumway-ia
landonshumway-ia requested review from ChiefStief and jlkravitz and removed request for ChiefStief July 29, 2026 17:34
@landonshumway-ia

Copy link
Copy Markdown
Collaborator Author

@jlkravitz This is ready for your review. I have verified that debug logs are now not emitted with the env var set to false. Please perform another scan to verify we are not logging any sensitive fields in non-debug logs. Thanks

@jlkravitz jlkravitz left a comment

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

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

Looks good! A few findings from claude below...

Sev Location Level What's logged
High common/cc_common/email_service_client.py:110 (compact-connect); :99 and :105 (cosmetology, social-work) ERROR Full notification payloadspecificEmails (provider's email) + templateVariables with providerFirstName/providerLastName. Fires on any email-lambda failure or FunctionError.
Med lambdas/nodejs/lib/email/email-notification-service.ts:351, 408, 441, 469 INFO ×4 providerEmail, recipients, providerEmail, oldEmailAddress. This PR made maskEmail() public but applied it only in ingest-event-email-service.ts and cognito-emails/lambda.ts — same class hierarchy, so it's a drop-in this.maskEmail(...).
Med cognito-backup/handlers/cognito_backup.py:108, :171 — ×3 apps ERROR username= — Cognito Username is the email here (user_client.py:312, 326, 334, 400, 407, 436). This lambda iterates the whole user pool, so one systemic S3 failure enumerates every user's email. Suggest sub.
Med provider-data-v1/handlers/provider_s3_events.py:12 and :18 INFO ×2 S3 key embedding the user-supplied filename (provider_users.py:181) — typically self-identifying (JSmith_DD214.pdf), revealing name + military-affiliation status. Both lines need fixing; :18 logs the key separately as s3_url.
Med provider-data-v1/handlers/licenses.py:103 (compact-connect), :94 (cosmetology), :98 (social-work) ERROR failed_license_numbers= (built at cc_common/utils.py:907). Fires on SQS batch-send failure, dumping up to a full batch. Suggest count + failure codes.
Med provider-data-v1/handlers/provider_users.py:396 WARNING new_email= on the AliasExistsException branch. compact + provider_id are already on the line, so this links a pseudonymous ID to a real address.
Low common/cc_common/data_model/transaction_client.py:263 ERROR matching_privilege_records= — raw items. Mostly pseudonymous, but PrivilegeRecordSchema nests DeactivationDetailsSchema.deactivatedByStaffUserName (schema/privilege/record.py:55) — unverified whether populated on this path.
Low custom-resources/handlers/ses_email_identity_verification_handler.py:29 — ×3 apps INFO Full CFN custom-resource event incl. presigned ResponseURL (AWSAccessKeyId + X-Amz-Signature). Not PII, but 8.7 also covers "sensitive authentication data that could assist an attacker." Deploy-time only.

Comment thread backend/common-cdk/common_constructs/stack.py
@landonshumway-ia

Copy link
Copy Markdown
Collaborator Author

@jlkravitz Are license numbers actually considered sensitive without other PII? Wouldn't that be more akin to the providerId, an id that tech support can use to lookup an individual without exposing their PII in the logs?

@jlkravitz

Copy link
Copy Markdown
Collaborator

@landonshumway-ia Sorry, I missed that one in my scan. I agree that license numbers are not PII since they are public.

@coderabbitai coderabbitai Bot 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.

Actionable comments posted: 2

🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Inline comments:
In
`@backend/compact-connect/lambdas/python/common/cc_common/email_service_client.py`:
- Around line 104-120: Remove the full payload DEBUG log in the exception path
of the email service client, or replace it with an explicitly allowlisted
diagnostic payload that excludes verificationCode, recoveryToken, email
addresses, provider names, and other credentials or PII. Keep the existing
non-PII ERROR fields and exception handling unchanged.

In
`@backend/compact-connect/lambdas/python/custom-resources/handlers/ses_email_identity_verification_handler.py`:
- Around line 29-32: Remove the full-event DEBUG logging from the SES email
identity verification handler, or sanitize the event to exclude ResponseURL and
other credentials/tokens before logging, across the handlers at
backend/compact-connect/lambdas/python/custom-resources/handlers/ses_email_identity_verification_handler.py
lines 29-32,
backend/cosmetology-app/lambdas/python/custom-resources/handlers/ses_email_identity_verification_handler.py
lines 29-32, and
backend/social-work-app/lambdas/python/custom-resources/handlers/ses_email_identity_verification_handler.py
lines 29-32; keep only an allowlisted, non-sensitive event subset in the ses
email identity verification handler.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: defaults

Review profile: CHILL

Plan: Pro Plus

Run ID: 50e69234-9433-45ef-9a54-c84de7a41be9

📥 Commits

Reviewing files that changed from the base of the PR and between 519280b and 08d57d0.

📒 Files selected for processing (19)
  • backend/common-cdk/common_constructs/stack.py
  • backend/compact-connect/lambdas/python/cognito-backup/handlers/cognito_backup.py
  • backend/compact-connect/lambdas/python/common/cc_common/data_model/transaction_client.py
  • backend/compact-connect/lambdas/python/common/cc_common/email_service_client.py
  • backend/compact-connect/lambdas/python/custom-resources/handlers/ses_email_identity_verification_handler.py
  • backend/compact-connect/lambdas/python/provider-data-v1/handlers/provider_s3_events.py
  • backend/compact-connect/lambdas/python/provider-data-v1/handlers/provider_users.py
  • backend/cosmetology-app/lambdas/python/cognito-backup/handlers/cognito_backup.py
  • backend/cosmetology-app/lambdas/python/common/cc_common/email_service_client.py
  • backend/cosmetology-app/lambdas/python/custom-resources/handlers/ses_email_identity_verification_handler.py
  • backend/social-work-app/lambdas/python/cognito-backup/handlers/cognito_backup.py
  • backend/social-work-app/lambdas/python/common/cc_common/email_service_client.py
  • backend/social-work-app/lambdas/python/custom-resources/handlers/ses_email_identity_verification_handler.py
  • backend/social-work-app/stacks/api_stack/v1_api/api_model.py
  • backend/social-work-app/tests/resources/snapshots/GET_PROVIDER_RESPONSE_SCHEMA.json
  • backend/social-work-app/tests/resources/snapshots/LICENSE_ENCUMBRANCE_REQUEST_SCHEMA.json
  • backend/social-work-app/tests/resources/snapshots/PATCH_LICENSE_INVESTIGATION_REQUEST_SCHEMA.json
  • backend/social-work-app/tests/resources/snapshots/PATCH_PRIVILEGE_INVESTIGATION_REQUEST_SCHEMA.json
  • backend/social-work-app/tests/resources/snapshots/PRIVILEGE_ENCUMBRANCE_REQUEST_SCHEMA.json
💤 Files with no reviewable changes (1)
  • backend/compact-connect/lambdas/python/provider-data-v1/handlers/provider_users.py
🚧 Files skipped from review as they are similar to previous changes (7)
  • backend/common-cdk/common_constructs/stack.py
  • backend/social-work-app/tests/resources/snapshots/PATCH_PRIVILEGE_INVESTIGATION_REQUEST_SCHEMA.json
  • backend/social-work-app/tests/resources/snapshots/GET_PROVIDER_RESPONSE_SCHEMA.json
  • backend/social-work-app/tests/resources/snapshots/PRIVILEGE_ENCUMBRANCE_REQUEST_SCHEMA.json
  • backend/social-work-app/stacks/api_stack/v1_api/api_model.py
  • backend/social-work-app/tests/resources/snapshots/LICENSE_ENCUMBRANCE_REQUEST_SCHEMA.json
  • backend/social-work-app/tests/resources/snapshots/PATCH_LICENSE_INVESTIGATION_REQUEST_SCHEMA.json

Comment on lines +104 to +120
self._logger.error(error_message, template=payload.get('template'))
raise CCInternalException(error_message)

return response
except Exception as e:
error_message = f'Error invoking email notification service lambda: {str(e)}'
self._logger.error(error_message, payload=payload, exception=str(e))
# payload may contain PII (specificEmails, templateVariables with provider names), so it is only
# logged at DEBUG level; the non-PII fields below are sufficient for triage at ERROR level
self._logger.error(
error_message,
template=payload.get('template'),
compact=payload.get('compact'),
jurisdiction=payload.get('jurisdiction'),
recipient_type=payload.get('recipientType'),
exception=str(e),
)
self._logger.debug('Email notification service invocation payload', payload=payload)

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.

🔒 Security & Privacy | 🟠 Major | ⚡ Quick win

Do not log recovery credentials in the complete DEBUG payload.

send_provider_email_verification_code passes verificationCode, and send_provider_account_recovery_confirmation_email passes recoveryToken. This debug log records the entire payload after an invocation failure. DEBUG=false is not a sufficient control for bearer credentials. Remove the payload log or pass an allowlisted payload that excludes tokens, codes, email addresses, and provider names.

Suggested fix
-            self._logger.debug('Email notification service invocation payload', payload=payload)

Based on learnings, redact credentials, tokens, and PII before diagnostic logging.

📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
self._logger.error(error_message, template=payload.get('template'))
raise CCInternalException(error_message)
return response
except Exception as e:
error_message = f'Error invoking email notification service lambda: {str(e)}'
self._logger.error(error_message, payload=payload, exception=str(e))
# payload may contain PII (specificEmails, templateVariables with provider names), so it is only
# logged at DEBUG level; the non-PII fields below are sufficient for triage at ERROR level
self._logger.error(
error_message,
template=payload.get('template'),
compact=payload.get('compact'),
jurisdiction=payload.get('jurisdiction'),
recipient_type=payload.get('recipientType'),
exception=str(e),
)
self._logger.debug('Email notification service invocation payload', payload=payload)
self._logger.error(error_message, template=payload.get('template'))
raise CCInternalException(error_message)
return response
except Exception as e:
error_message = f'Error invoking email notification service lambda: {str(e)}'
# payload may contain PII (specificEmails, templateVariables with provider names), so it is only
# logged at DEBUG level; the non-PII fields below are sufficient for triage at ERROR level
self._logger.error(
error_message,
template=payload.get('template'),
compact=payload.get('compact'),
jurisdiction=payload.get('jurisdiction'),
recipient_type=payload.get('recipientType'),
exception=str(e),
)
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In
`@backend/compact-connect/lambdas/python/common/cc_common/email_service_client.py`
around lines 104 - 120, Remove the full payload DEBUG log in the exception path
of the email service client, or replace it with an explicitly allowlisted
diagnostic payload that excludes verificationCode, recoveryToken, email
addresses, provider names, and other credentials or PII. Keep the existing
non-PII ERROR fields and exception handling unchanged.

Source: Learnings

Comment on lines +29 to +32
# The full event includes a pre-signed 'ResponseURL' (with an access key id and signature) used to signal
# CloudFormation, so it is only logged at DEBUG level.
logger.info('Entering SES email identity verification handler', request_type=event.get('RequestType'))
logger.debug('SES email identity verification handler event', event=json.dumps(event))

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.

🔒 Security & Privacy | 🟠 Major | ⚡ Quick win

Remove pre-signed CloudFormation URLs from DEBUG logs.

Each handler serializes the complete event. The event contains the pre-signed ResponseURL, which includes credentials. Log only an allowlisted subset of the event.

  • backend/compact-connect/lambdas/python/custom-resources/handlers/ses_email_identity_verification_handler.py#L29-L32: remove ResponseURL before logging, or remove the full-event DEBUG log.
  • backend/cosmetology-app/lambdas/python/custom-resources/handlers/ses_email_identity_verification_handler.py#L29-L32: remove ResponseURL before logging, or remove the full-event DEBUG log.
  • backend/social-work-app/lambdas/python/custom-resources/handlers/ses_email_identity_verification_handler.py#L29-L32: remove ResponseURL before logging, or remove the full-event DEBUG log.

Based on learnings, redact credentials and tokens instead of relying only on a debug-level threshold.

🧰 Tools
🪛 ast-grep (0.45.0)

[info] 31-31: use jsonify instead of json.dumps for JSON output
Context: json.dumps(event)
Note: [CWE-116] Improper Encoding or Escaping of Output.

(use-jsonify)

📍 Affects 3 files
  • backend/compact-connect/lambdas/python/custom-resources/handlers/ses_email_identity_verification_handler.py#L29-L32 (this comment)
  • backend/cosmetology-app/lambdas/python/custom-resources/handlers/ses_email_identity_verification_handler.py#L29-L32
  • backend/social-work-app/lambdas/python/custom-resources/handlers/ses_email_identity_verification_handler.py#L29-L32
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In
`@backend/compact-connect/lambdas/python/custom-resources/handlers/ses_email_identity_verification_handler.py`
around lines 29 - 32, Remove the full-event DEBUG logging from the SES email
identity verification handler, or sanitize the event to exclude ResponseURL and
other credentials/tokens before logging, across the handlers at
backend/compact-connect/lambdas/python/custom-resources/handlers/ses_email_identity_verification_handler.py
lines 29-32,
backend/cosmetology-app/lambdas/python/custom-resources/handlers/ses_email_identity_verification_handler.py
lines 29-32, and
backend/social-work-app/lambdas/python/custom-resources/handlers/ses_email_identity_verification_handler.py
lines 29-32; keep only an allowlisted, non-sensitive event subset in the ses
email identity verification handler.

Source: Learnings

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.

SW - update encumbrance categories (Backend)

2 participants