Skip to content

sec(purchases): remove token from scheduled purchase email query-string links - #581

Merged
cristim merged 3 commits into
feat/multicloud-web-frontendfrom
fix/406-scheduled-purchase-token-leak
May 28, 2026
Merged

cristim merged 3 commits into
feat/multicloud-web-frontendfrom
fix/406-scheduled-purchase-token-leak

Conversation

@cristim

@cristim cristim commented May 20, 2026

Copy link
Copy Markdown
Member

Summary

  • Fixes scheduledPurchaseTemplate in internal/email/templates.go to stop embedding the approval token in Review & Edit and Pause Plan URLs (closes sec: scheduledPurchaseTemplate embeds token in dashboard query-string link (not in direct API call) #406)
  • Review & Edit and Pause Plan links now go to the dashboard root -- no token in URL, user must be authenticated
  • Cancel link uses the direct API path /purchases/cancel/<executionID>?token=<token> (same pattern as purchaseApprovalRequestTemplate), avoiding SPA load with token in Referer header
  • buildNotificationData in internal/purchase/notifications.go now sets ExecutionID on the notification data (was missing, needed for the cancel link)
  • Regression tests: new test in templates_test.go asserts token does not appear on review/pause lines; updated template_renderers_test.go and coverage_test.go to match new template shape

Closes #406

…ng links (closes #406)

scheduledPurchaseTemplate embedded the approval token in all three action
links (Review & Edit, Pause Plan, Cancel) as dashboard SPA query-string
parameters. This caused the token to appear in browser history, CloudFront
access logs, and Referer headers sent to any analytics / font / tracking
resource loaded by the SPA.

Changes:
- Review & Edit and Pause Plan links now point at the dashboard root with
  no token. These actions require an authenticated session; the user logs
  in through the normal flow and takes action from there.
- Cancel This Purchase link now uses the direct API path pattern from the
  companion purchaseApprovalRequestTemplate:
  /purchases/cancel/<executionID>?token=<token>
  This goes to the API endpoint rather than the SPA, so no third-party
  scripts load with the token in the Referer header.
- buildNotificationData now sets ExecutionID on NotificationData so the
  cancel link can render the execution ID (was missing before).
- Three test files updated to match the new template shape:
  template_renderers_test.go, coverage_test.go (existing assertions),
  and templates_test.go (new regression test explicitly asserting the
  token does not appear on review/pause lines).
@cristim cristim added bug Something isn't working triaged Item has been triaged priority/p2 Backlog-worthy severity/medium Moderate harm urgency/this-sprint Within the current sprint impact/few Limited audience labels May 20, 2026
@coderabbitai

coderabbitai Bot commented May 20, 2026 •

Copy link
Copy Markdown
Contributor

Warning

Review limit reached

@cristim, we couldn't start this review because you've reached your PR review rate limit.

More reviews will be available in 59 minutes and 53 seconds. Learn how PR review limits work.

Your organization has run out of usage credits. Purchase more in the billing tab.

⌛ How to resolve this issue?

After more reviews become available, a review can be triggered using the @coderabbitai review command as a PR comment. Alternatively, push new commits to this PR.

We recommend that you space out your commits to avoid hitting the rate limit.

🚦 How do rate limits work?

CodeRabbit enforces hourly rate limits for each developer per organization.

Our paid plans include higher PR review limits than trial, open-source, and free plans. In all cases, reviews become available again over time. During sustained high-volume PR review activity, CodeRabbit may temporarily slow when the next review becomes available.

Please see our Fair Usage Limits Policy for further information.

ℹ️ Review info
⚙️ Run configuration

Configuration used: defaults

Review profile: CHILL

Plan: Pro

Run ID: cfcd8191-0f12-4880-a264-80735c89f97e

📥 Commits

Reviewing files that changed from the base of the PR and between bc8831b and 5fed98d.

📒 Files selected for processing (9)
  • frontend/src/__tests__/history-deeplink.test.ts
  • frontend/src/history.ts
  • frontend/src/styles/tables.css
  • internal/email/coverage_test.go
  • internal/email/sender.go
  • internal/email/template_renderers_test.go
  • internal/email/templates.go
  • internal/email/templates_test.go
  • internal/purchase/notifications.go
✨ Finishing Touches
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Commit unit tests in branch fix/406-scheduled-purchase-token-leak

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

@cristim

cristim commented May 20, 2026

Copy link
Copy Markdown
Member Author

@coderabbitai review

@coderabbitai

coderabbitai Bot commented May 20, 2026

Copy link
Copy Markdown
Contributor
✅ Actions performed

Review triggered.

Note: CodeRabbit is an incremental review system and does not re-review already reviewed commits. This command is applicable only when automatic reviews are paused.

@cristim cristim added the type/security Security finding label May 20, 2026
@cristim

cristim commented May 20, 2026

Copy link
Copy Markdown
Member Author

@coderabbitai review

@coderabbitai

coderabbitai Bot commented May 20, 2026

Copy link
Copy Markdown
Contributor
✅ Actions performed

Review triggered.

Note: CodeRabbit is an incremental review system and does not re-review already reviewed commits. This command is applicable only when automatic reviews are paused.

@cristim

cristim commented May 22, 2026

Copy link
Copy Markdown
Member Author

@coderabbitai review

@coderabbitai

coderabbitai Bot commented May 22, 2026

Copy link
Copy Markdown
Contributor
✅ Actions performed

Review triggered.

Note: CodeRabbit is an incremental review system and does not re-review already reviewed commits. This command is applicable only when automatic reviews are paused.

@cristim

cristim commented May 22, 2026

Copy link
Copy Markdown
Member Author

@coderabbitai review

@coderabbitai

coderabbitai Bot commented May 22, 2026

Copy link
Copy Markdown
Contributor
✅ Actions performed

Review triggered.

Note: CodeRabbit is an incremental review system and does not re-review already reviewed commits. This command is applicable only when automatic reviews are paused.

@cristim

cristim commented May 25, 2026

Copy link
Copy Markdown
Member Author

@coderabbitai review

@coderabbitai

coderabbitai Bot commented May 25, 2026

Copy link
Copy Markdown
Contributor
✅ Actions performed

Review triggered.

Note: CodeRabbit is an incremental review system and does not re-review already reviewed commits. This command is applicable only when automatic reviews are paused.

@cristim

cristim commented May 27, 2026

Copy link
Copy Markdown
Member Author

@coderabbitai review

@coderabbitai

coderabbitai Bot commented May 27, 2026

Copy link
Copy Markdown
Contributor
✅ Actions performed

Review triggered.

Note: CodeRabbit is an incremental review system and does not re-review already reviewed commits. This command is applicable only when automatic reviews are paused.

@cristim

cristim commented May 27, 2026

Copy link
Copy Markdown
Member Author

@coderabbitai full review

@coderabbitai

coderabbitai Bot commented May 27, 2026

Copy link
Copy Markdown
Contributor
✅ Actions performed

Full review triggered.

@cristim

cristim commented May 27, 2026

Copy link
Copy Markdown
Member Author

@coderabbitai full review

@coderabbitai

coderabbitai Bot commented May 27, 2026

Copy link
Copy Markdown
Contributor
✅ Actions performed

Full review triggered.

cristim added 2 commits May 28, 2026 00:30
…chase URLs (refs #406)

Follow-up to #581: PR #581 stripped the approval token from the Review &
Edit and Pause Plan links to fix the token-leak in #406, but degraded
UX by dropping the user at the dashboard root. They had to hunt for the
specific execution they were notified about.

Re-introduce a safe deeplink: the URLs now carry the ExecutionID
(Review & Edit) and PlanID (Pause Plan). Both are UUIDs - not sensitive
on their own; the user still authenticates via the normal session
cookie, and the SPA scrolls to / highlights the matching row.

Changes:
- scheduledPurchaseTemplate Review & Edit now points at
  /purchases#history?execution=<id>, which the existing
  applyExecutionDeepLink handler in history.ts picks up and uses to
  scroll + highlight the matching row in the Purchase History table /
  Approval queue card.
- Pause Plan now points at /plans?plan=<plan-id>. The Plans tab
  deeplink handler is filed as a separate follow-up; for now the user
  lands on the right tab and can act on the row manually.
- NotificationData gains a PlanID field, populated from plan.ID in
  buildNotificationData.
- Regression test extended: assert the new deeplink shapes are present
  AND the token is still NOT embedded in the Review / Pause lines (the
  #406 guard).
…ecution deeplinks

The scheduled-purchase email's Review & Edit link (#581 follow-up) and
the Recommendations suppression badge both deeplink to
#history?execution=<id>. The applyExecutionDeepLink handler already
scrolled + added a `history-row-highlight` class to the matching row,
but two gaps showed up while wiring the email deeplink:

1. The .history-row-highlight CSS class was referenced but never
   defined - the row got tagged but the user saw no visual feedback.
   Add a yellow tint with a brief flash animation; the existing 4-
   second `classList.remove` in history.ts fades it cleanly. Also
   override the table-wide tr:hover so the highlight persists while
   the user mouses over the row.

2. When the deeplink target wasn't in the rendered list (e.g. the
   user's date filter excludes it), the handler returned silently
   and the user was left wondering why the page didn't jump. Show a
   non-blocking info toast pointing at the filter, and strip the
   ?execution= param from the hash so a subsequent re-render doesn't
   re-fire the same toast on every state change.

Also exports `applyExecutionDeepLink` (unit-test coverage) and adds
four new tests:
- no-op when no execution id in hash;
- success path scrolls, highlights, schedules fade-out;
- miss path emits info toast + clears the hash;
- CSS.escape neutralises selector-metacharacter exec IDs.
@cristim

cristim commented May 27, 2026

Copy link
Copy Markdown
Member Author

Added execution + plan deeplinks so the Review & Edit and Pause Plan buttons land users on the specific row they were notified about, instead of the dashboard root.

URL shape (no token — only non-sensitive UUIDs):

  • Review & Edit → {DashboardURL}/purchases#history?execution=<exec-id> — picked up by the existing applyExecutionDeepLink handler in history.ts, which scrolls + flashes a highlight class on the matching row in the Purchase History table / Approval queue card.
  • Pause Plan → {DashboardURL}/plans?plan=<plan-id> — lands on the right tab; richer Plans-tab deeplink (scroll-to-planned-purchase) is out of scope for this PR and will be filed as a separate issue if useful.

Authentication unchanged — the user still authenticates via the normal session cookie. If logged out, the existing login flow's location.reload() preserves the deeplink URL (path + search + hash), so the deeplink fires after auth.

Frontend gaps fixed at the same time:

  • .history-row-highlight CSS was referenced but never defined. Added the yellow tint + brief flash animation in styles/tables.css. (Pre-existing dangling class — the suppression badge linking to #history?execution= also lacked any visual feedback before this commit.)
  • When the deeplink target isn't in the rendered list (date filter excludes it, etc.), now surfaces a non-blocking info toast and strips the ?execution= from the hash so a subsequent re-render doesn't re-toast.

Tests:

@coderabbitai review

@coderabbitai

coderabbitai Bot commented May 27, 2026

Copy link
Copy Markdown
Contributor

@cristim: The deeplink approach looks well-considered — using non-sensitive UUIDs in the URL instead of tokens is exactly the right direction, and the frontend polish (CSS highlight class, toast on miss, hash cleanup) rounds it out nicely. Triggering a review of the latest changes now.

✅ Actions performed

Review triggered.

Note: CodeRabbit is an incremental review system and does not re-review already reviewed commits. This command is applicable only when automatic reviews are paused.

@cristim

cristim commented May 28, 2026

Copy link
Copy Markdown
Member Author

@coderabbitai full review

@coderabbitai

coderabbitai Bot commented May 28, 2026

Copy link
Copy Markdown
Contributor
✅ Actions performed

Full review triggered.

@cristim
cristim merged commit 99542ff into feat/multicloud-web-frontend May 28, 2026
5 checks passed
@cristim
cristim deleted the fix/406-scheduled-purchase-token-leak branch June 3, 2026 21:54
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

bug Something isn't working impact/few Limited audience priority/p2 Backlog-worthy severity/medium Moderate harm triaged Item has been triaged type/security Security finding urgency/this-sprint Within the current sprint

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant