Skip to content

fix(secrets): restore unsaved-changes guard for settings tab navigation#4009

Merged
waleedlatif1 merged 6 commits intostagingfrom
fix/env
Apr 7, 2026
Merged

fix(secrets): restore unsaved-changes guard for settings tab navigation#4009
waleedlatif1 merged 6 commits intostagingfrom
fix/env

Conversation

@waleedlatif1
Copy link
Copy Markdown
Collaborator

Summary

  • Add `useSettingsDirtyStore` to track dirty state across the settings sidebar and section components — fixes the root cause where the sidebar's button-based `router.replace()` navigation was bypassing the existing anchor-click/popstate guards entirely
  • Wire `credentials-manager` and `integrations-manager` to sync dirty state to the store and reset on unmount
  • Update settings sidebar to check dirty state before tab switches and Back navigation, showing an Unsaved Changes dialog if needed
  • Remove dead `stores/settings/environment` directory; move `EnvironmentVariable` type into `lib/environment/api` where it belongs
  • Harden Microsoft content URL validation in Teams webhook handler: typed allowlist for SharePoint/OneDrive/CDN domains, parsed-hostname matching instead of loose substring checks, `searchParams` API instead of string splitting

Type of Change

  • Bug fix

Testing

Tested manually

Checklist

  • Code follows project style guidelines
  • Self-reviewed my changes
  • Tests added/updated and passing
  • No new warnings introduced
  • I confirm that I have read and agree to the terms outlined in the Contributor License Agreement (CLA)

@cursor
Copy link
Copy Markdown

cursor bot commented Apr 7, 2026

PR Summary

Medium Risk
Medium risk because it changes settings navigation flow via a new global dirty-state store and adjusts Microsoft Teams attachment URL handling/validation, which could affect navigation UX and webhook attachment fetching.

Overview
Restores an unsaved-changes guard for settings navigation by introducing a shared useSettingsDirtyStore and wiring credentials-manager and integrations-manager to publish/reset dirty state on changes/unmount.

Updates the settings sidebar to block tab switches and Back navigation when dirty, showing a discard-confirmation modal and only navigating after explicit confirmation.

Refactors environment variable typing by moving EnvironmentVariable into lib/environment/api and removing the old stores/settings/environment types, and hardens Microsoft Teams attachment fetching by validating parsed hostnames against a Microsoft domain allowlist (via isMicrosoftContentUrl) and using safer URL parsing for OneDrive/SharePoint link handling.

Reviewed by Cursor Bugbot for commit 522f3e2. Configure here.

@vercel
Copy link
Copy Markdown

vercel bot commented Apr 7, 2026

The latest updates on your projects. Learn more about Vercel for GitHub.

1 Skipped Deployment
Project Deployment Actions Updated (UTC)
docs Skipped Skipped Apr 7, 2026 4:51am

Request Review

- Add useSettingsDirtyStore (stores/settings/dirty) to track dirty state across the settings sidebar and section components
- Wire credentials-manager and integrations-manager to sync dirty state to the store and clean up on unmount; also reset store synchronously in handleDiscardAndNavigate
- Update settings-sidebar to check dirty state before tab switches and Back navigation, showing an Unsaved Changes dialog if needed
- Remove dead stores/settings/environment directory; move EnvironmentVariable type into lib/environment/api
- Add isMicrosoftContentUrl helper with typed allowlist covering SharePoint, OneDrive, and Teams CDN domains
- Replace loose substring checks in Teams webhook handler with parsed-hostname matching to prevent bypass via partial domain names
- Deduplicate OneDrive share-link detection into isOneDriveShareLink flag and use searchParams API instead of string splitting
@greptile-apps
Copy link
Copy Markdown
Contributor

greptile-apps bot commented Apr 7, 2026

Greptile Summary

This PR fixes a navigation guard regression in the settings sidebar: the sidebar's button-based router.replace() calls were bypassing the existing anchor-click/popstate guards entirely. The fix adds a useSettingsDirtyStore Zustand store that credentials-manager and integrations-manager write dirty state into, while SettingsSidebar reads it before any tab switch or Back navigation and shows an "Unsaved Changes" modal when needed.

Key changes:

  • New stores/settings/dirty/store.ts: minimal Zustand store with isDirty, pendingSection, and three guard actions (requestNavigation, confirmNavigation, cancelNavigation); correctly uses devtools middleware and initialState spread pattern
  • settings-sidebar.tsx: shared handleConfirmDiscard elegantly distinguishes Back (pendingSection is null → router.push to back URL) from tab navigation (pendingSection set → router.replace to section); escape/overlay-close correctly fires handleCancelDiscard which resets pendingSection to prevent phantom navigation
  • credentials-manager and integrations-manager: sync isDirty into store via useEffect and call reset() in cleanup — no mount/unmount ordering issues since both only call setDirty inside effects, not synchronously during render
  • Removes dead stores/settings/environment directory; moves EnvironmentVariable and WorkspaceEnvironmentData to lib/environment/api — all consumers (route.ts, tools/utils.ts, env-var-dropdown.tsx, credentials-manager) updated to import from the canonical location
  • Hardens Microsoft Teams content URL validation: replaces loose substring checks with a typed MICROSOFT_CONTENT_SUFFIXES const allowlist and hostname === suffix || hostname.endsWith('.'+suffix) matching in isMicrosoftContentUrl; adds 1drv.com apex to the isOneDriveShareLink routing branch so OneDrive share links are handled by the correct Graph API path

Confidence Score: 5/5

Safe to merge — no P0/P1 issues found across all 13 changed files

All prior review concerns (type re-exports, smba.trafficmanager.net wildcard, mount/unmount race) were addressed in follow-up commits. The unsaved-changes guard logic is correct for both Back and tab-switch paths. The Microsoft content URL allowlist uses parsed-hostname matching which is secure against substring-bypass. The type refactoring is clean with no dangling import paths.

No files require special attention

Important Files Changed

Filename Overview
apps/sim/stores/settings/dirty/store.ts New Zustand store for unsaved-changes navigation guard; follows devtools + initialState spread pattern correctly
apps/sim/app/workspace/[workspaceId]/w/components/sidebar/components/settings-sidebar/settings-sidebar.tsx Integrates dirty store; handleBack and tab-click guards route through a shared dialog and confirmNavigation correctly
apps/sim/app/workspace/[workspaceId]/settings/components/credentials/credentials-manager.tsx Syncs dirty state to store via useEffect; resets on unmount; imports WorkspaceEnvironmentData from canonical lib/environment/api
apps/sim/app/workspace/[workspaceId]/settings/components/integrations/integrations-manager.tsx Same dirty-store wiring pattern as credentials-manager; tracks isDetailsDirty for display name and description fields
apps/sim/lib/core/security/input-validation.ts New isMicrosoftContentUrl with typed MICROSOFT_CONTENT_SUFFIXES allowlist and parsed-hostname matching; secure against substring-bypass
apps/sim/lib/webhooks/providers/microsoft-teams.ts Replaces substring URL checks with isMicrosoftContentUrl; adds 1drv.com to OneDrive share-link branch; attachment routing logic is correct
apps/sim/lib/environment/api.ts Now canonical home for EnvironmentVariable and WorkspaceEnvironmentData types; fetch functions unchanged
apps/sim/hooks/queries/environment.ts Type re-exports removed; keepPreviousData retained only on variable-key workspace query per project rules
apps/sim/app/api/environment/route.ts Import updated to canonical lib/environment/api; existing route logic unchanged
apps/sim/tools/utils.ts EnvironmentVariable import updated to canonical lib/environment/api source
apps/sim/app/workspace/[workspaceId]/w/[workflowId]/components/panel/components/editor/components/sub-block/components/env-var-dropdown.tsx WorkspaceEnvironmentData import updated to canonical lib/environment/api source

Sequence Diagram

sequenceDiagram
    participant User
    participant Sidebar as SettingsSidebar
    participant Store as useSettingsDirtyStore
    participant Manager as credentials/integrations-manager
    participant Router

    User->>Manager: edits form field
    Manager->>Store: setDirty(true) [via useEffect]
    User->>Sidebar: clicks different tab
    Sidebar->>Store: requestNavigation(section)
    Store-->>Sidebar: false (isDirty=true, pendingSection set)
    Sidebar->>Sidebar: setShowDiscardDialog(true)
    alt User confirms discard
        User->>Sidebar: clicks Discard Changes
        Sidebar->>Store: confirmNavigation()
        Store-->>Sidebar: pendingSection
        Sidebar->>Router: replace(getSettingsHref(section))
        Manager->>Store: reset() on unmount
    else User cancels
        User->>Sidebar: clicks Keep Editing
        Sidebar->>Store: cancelNavigation()
        Store-->>Sidebar: pendingSection=null
    end

    Note over Sidebar,Store: Back button path
    User->>Sidebar: clicks Back (isDirty=true)
    Sidebar->>Sidebar: setShowDiscardDialog(true)
    Note over Store: pendingSection stays null
    User->>Sidebar: clicks Discard Changes
    Sidebar->>Store: confirmNavigation()
    Store-->>Sidebar: null (no pending section)
    Sidebar->>Router: push(popSettingsReturnUrl)
Loading

Reviews (4): Last reviewed commit: "fix(teams): add 1drv.com apex to OneDriv..." | Re-trigger Greptile

@waleedlatif1
Copy link
Copy Markdown
Collaborator Author

@greptile

@waleedlatif1
Copy link
Copy Markdown
Collaborator Author

@cursor review

…owlist

The subdomain check for smba.trafficmanager.net was unnecessary — Azure
Traffic Manager does not support nested subdomains of existing profiles,
but the pattern still raised a valid audit concern. Teams bot-framework
attachment URLs from this host fall through to the generic fetchWithDNSPinning
branch, which provides the same protection without the ambiguity.
@waleedlatif1
Copy link
Copy Markdown
Collaborator Author

@greptile

@waleedlatif1
Copy link
Copy Markdown
Collaborator Author

@cursor review

1drv.com (apex) is a short-link domain functionally equivalent to
1drv.ms and requires share-token resolution, not direct fetch.
CDN subdomains (files.1drv.com) are unaffected — the exact-match
check leaves them on the direct-fetch path.
@waleedlatif1
Copy link
Copy Markdown
Collaborator Author

@greptile

@waleedlatif1
Copy link
Copy Markdown
Collaborator Author

@cursor review

Copy link
Copy Markdown

@cursor cursor bot left a comment

Choose a reason for hiding this comment

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

✅ Bugbot reviewed your changes and found no new issues!

Comment @cursor review or bugbot run to trigger another review on this PR

Reviewed by Cursor Bugbot for commit 522f3e2. Configure here.

@waleedlatif1 waleedlatif1 merged commit 7793583 into staging Apr 7, 2026
12 checks passed
@waleedlatif1 waleedlatif1 deleted the fix/env branch April 7, 2026 05:00
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