Skip to content

fix(SITES-44243): server-derive executedBy and add executedByUser audit trail#2445

Open
sandsinh wants to merge 15 commits into
mainfrom
SITES-44243
Open

fix(SITES-44243): server-derive executedBy and add executedByUser audit trail#2445
sandsinh wants to merge 15 commits into
mainfrom
SITES-44243

Conversation

@sandsinh
Copy link
Copy Markdown
Contributor

@sandsinh sandsinh commented May 20, 2026

Summary

  • Adds an executedByUser audit-trail field on GET /sites/:siteId/opportunities/:opportunityId/fixes — resolves each fix's executedBy IMS user ID to { firstName, lastName, email } via the IMS admin profile API at read time.
  • SITES-44243 security fix: closes a PII exposure path where any org-admin could PATCH/POST a fix with an arbitrary executedBy IMS user ID and exfiltrate that user's profile via the GET endpoint. executedBy is now server-derived from the authenticated caller's JWT (profile.user_id ?? profile.sub); client-supplied values are treated as intent signals only and overridden in createFixes / patchFix. executedBy is removed from the FixUpdate OpenAPI schema.
  • Adds spec + implementation plan under docs/specs/ and docs/plans/, and unit tests covering enrichment happy path, IMS lookup failure, missing imsClient, empty executedBy set, and synchronous throw inside the Promise.allSettled mapping.

Test plan

  • npm test — 107 unit tests pass (incl. 5 enrichment cases in test/controllers/fixes.test.js).
  • npm run docs:lint — OpenAPI validates with executedBy removed from FixUpdate.
  • PATCH /sites/:siteId/opportunities/:opportunityId/fixes/:fixId with { "executedBy": "<attacker-ims-id>", "status": "DEPLOYED" } — verify the stored executedBy equals the JWT caller's user_id, not the supplied attacker value.
  • POST /sites/:siteId/opportunities/:opportunityId/fixes with [{ "executedBy": "<attacker-ims-id>", ... }] — verify the same JWT override on every created fix.
  • GET /sites/:siteId/opportunities/:opportunityId/fixes — verify fixes with server-minted executedBy are enriched with executedByUser: { firstName, lastName, email }; legacy/malformed values silently skip enrichment.

🤖 Generated with Claude Code

@codecov
Copy link
Copy Markdown

codecov Bot commented May 20, 2026

Codecov Report

✅ All modified and coverable lines are covered by tests.

📢 Thoughts on this report? Let us know!

@github-actions
Copy link
Copy Markdown

This PR will trigger a patch release when merged.

Sandesh Sinha and others added 2 commits May 20, 2026 11:08
…utedByName

Co-Authored-By: Claude <noreply@anthropic.com>
Adds two test cases to close the coverage gap on src/controllers/fixes.js:
the early return when no fix has executedBy, and the outer catch that logs
a warning when the IMS lookup throws synchronously.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
@sandsinh sandsinh changed the title fix: Add audit trail for manually marked “deployed” optimization sugg… fix(SITES-44243): server-derive executedBy and add executedByUser audit trail May 25, 2026
Sandesh Sinha and others added 2 commits May 26, 2026 14:49
…d harden enrichment

- Strip client-supplied executedBy from safeFixData unconditionally in createFixes so
  it cannot pass through when callerUserId is unresolvable (residual security gap)
- Extract static #resolveCallerId(context) helper (user_id ?? sub ?? email) used by both
  createFixes and patchFix, closing the email-fallback gap for IMS-authenticated callers
- patchFix now returns 400 with a descriptive message when executedBy intent signal is
  sent but no caller identity can be resolved (was silently producing a misleading 400)
- Add IMS ID format validation in #enrichFixesWithUserNames before calling
  getImsAdminProfile, blocking pre-fix attacker-supplied legacy values
- Redact IMS user IDs in warning logs; use null instead of dash/empty placeholders
- Add executedByUser to OpenAPI Fix response schema (readOnly, list-endpoint-only note)
- Fix stale TrialUser comment in fix.js DTO
- Add comments to getByStatus and getByID explaining intentional enrichment omission
- Tests: strip-on-no-authInfo, sub fallback (createFixes + patchFix), 400-on-unresolvable

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
The enrichment filter only allowed named account type suffixes (AdobeID,
AdobeOrg, etc.) but IMS user IDs stored via the sub claim use a 24-char
hex org ID suffix (e.g. B90A1...@9bcd5ee55f43b6f30a494212), causing
executedByUser to be silently omitted for all real-world fixes.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
# Conflicts:
#	src/controllers/fixes.js
getByID was missing the #enrichFixesWithUserNames call, producing an
inconsistent API surface where the same fix returned executedByUser via
getAllForOpportunity but not via the single-fetch endpoint. Added
enrichment and a matching test.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
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.

1 participant