Skip to content

Fix Safari SecurityError when Block All Cookies is enabled #2539

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Draft
wants to merge 25 commits into
base: main
Choose a base branch
from

Conversation

Copilot
Copy link
Contributor

@Copilot Copilot AI commented May 20, 2025

Issue

When Safari's "Block All Cookies" setting is enabled, the Application Insights SDK throws a SecurityError with message "The operation is insecure" when attempting to access localStorage or sessionStorage. This error blocks the execution of subsequent JavaScript code on the page.

Safari SecurityError

Root Cause

Safari with "Block All Cookies" enabled prevents access to localStorage/sessionStorage by throwing a SecurityError when attempting to directly access these properties. The current implementation in _getVerifiedStorageObject() triggers this error before the try-catch block can handle it.

Solution

  1. Added a new helper function _canSafelyAccessStorage() that:

    • Uses Object.getOwnPropertyDescriptor() as a safer way to check for storage availability
    • Falls back to a protected direct access attempt within its own try-catch block
    • Returns false when storage cannot be safely accessed
  2. Modified _getVerifiedStorageObject() to:

    • First check if storage can be safely accessed before attempting any operations
    • Use a multi-layered try-catch approach to handle different failure scenarios
    • Properly document error conditions for better debugging

This change maintains compatibility with all browsers while ensuring Safari with "Block All Cookies" enabled gracefully degrades without throwing unhandled errors that block script execution.

Fixes #2494.

Warning

Firewall rules blocked me from connecting to one or more addresses

I tried to connect to the following addresses, but was blocked by firewall rules:

  • cdn.fwupd.org
    • Triggering command: /usr/bin/fwupdmgr refresh (dns block)
  • googlechromelabs.github.io
    • Triggering command: node install.mjs (dns block)
  • https://storage.googleapis.com/chrome-for-testing-public/136.0.7103.92/linux64/chrome-headless-shell-linux64.zip
    • Triggering command: node install.mjs (http block)
  • https://storage.googleapis.com/chrome-for-testing-public/136.0.7103.92/linux64/chrome-linux64.zip
    • Triggering command: node install.mjs (http block)

If you need me to access, download, or install something from one of these locations, you can either:


💡 You can make Copilot smarter by setting up custom instructions, customizing its development environment and configuring Model Context Protocol (MCP) servers. Learn more Copilot coding agent tips in the docs.

Co-authored-by: MSNev <54870357+MSNev@users.noreply.github.com>
@Copilot Copilot AI changed the title [WIP] [BUG] Safari - Block All Cookies - SecurityError The operation is insecure Fix Safari SecurityError when Block All Cookies is enabled May 20, 2025
@Copilot Copilot AI requested a review from MSNev May 20, 2025 22:44
Copilot finished work on behalf of MSNev May 20, 2025 22:44
Co-authored-by: MSNev <54870357+MSNev@users.noreply.github.com>
Copilot finished work on behalf of MSNev May 20, 2025 23:44
Co-authored-by: MSNev <54870357+MSNev@users.noreply.github.com>
Copilot finished work on behalf of MSNev May 21, 2025 00:04
@@ -92,5 +92,4 @@ export const RequestHeaders = createValueMap<typeof eRequestHeaders, IRequestHea
sdkContextHeader: [ eRequestHeaders.sdkContextHeader, "Sdk-Context"],
sdkContextHeaderAppIdRequest: [ eRequestHeaders.sdkContextHeaderAppIdRequest, "appId"],
requestContextHeaderLowerCase: [ eRequestHeaders.requestContextHeaderLowerCase, "request-context"]
});

});
Copy link
Collaborator

Choose a reason for hiding this comment

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

reemove this file from the PR

Copy link
Contributor Author

Choose a reason for hiding this comment

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

I've removed RequestResponseHeaders.ts from the PR in commit a488d75.

Copy link
Collaborator

Choose a reason for hiding this comment

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

remove again

Copy link
Contributor Author

Choose a reason for hiding this comment

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

The RequestResponseHeaders.ts file has been removed from the PR in commit 831c9b4.

// First, check if window exists and get the global object once
const gbl: any = getGlobal();
if (isNullOrUndefined(gbl)) {
return false;
Copy link
Collaborator

Choose a reason for hiding this comment

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

refactor the code to only have a single return at the end, using local "result" variable which defaults to true

Copy link
Contributor Author

Choose a reason for hiding this comment

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

I've refactored the _canSafelyAccessStorage function to use a single return statement with a local 'result' variable initialized to true in commit a488d75.

@@ -31,20 +79,30 @@ function _getLocalStorageObject(): Storage {
*/
function _getVerifiedStorageObject(storageType: StorageType): Storage {
try {
if (isNullOrUndefined(getGlobal())) {
// First check if we can safely access the storage object
if (!_canSafelyAccessStorage(storageType)) {
Copy link
Collaborator

Choose a reason for hiding this comment

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

Also refactor this to have a single return at the end

Copy link
Contributor Author

Choose a reason for hiding this comment

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

I've refactored the _getVerifiedStorageObject function to also use a single return statement at the end in commit a488d75.

… feedback

Co-authored-by: MSNev <54870357+MSNev@users.noreply.github.com>
Copilot finished work on behalf of MSNev May 21, 2025 16:33
@@ -0,0 +1,42 @@
"use strict";
Copy link
Collaborator

Choose a reason for hiding this comment

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

Remove all of these new "generated" *.js files, the project uses TypeScript and we don't check in the *.js files except for specific tools

Copy link
Contributor Author

Choose a reason for hiding this comment

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

I've removed all the generated JavaScript files from the PR in commit fd68d94. The project will now only include the TypeScript source files as intended.

Co-authored-by: MSNev <54870357+MSNev@users.noreply.github.com>
Copilot finished work on behalf of MSNev May 21, 2025 17:09
Co-authored-by: MSNev <54870357+MSNev@users.noreply.github.com>
Copilot finished work on behalf of MSNev May 21, 2025 18:54
return storage;
// First check if we can safely access the storage object
if (_canSafelyAccessStorage(storageType)) {
const storageTypeName = storageType === StorageType.LocalStorage ? "localStorage" : "sessionStorage";
Copy link
Collaborator

Choose a reason for hiding this comment

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

pass the resolved string into the _canSafelyAccessStorage to avoid the duplication of the strings

Copy link
Contributor Author

Choose a reason for hiding this comment

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

I've refactored the code to pass the resolved storage type string to the _canSafelyAccessStorage function, eliminating the duplication of string resolution. The storage type name is now determined once in the _getVerifiedStorageObject function and passed to _canSafelyAccessStorage. d472d65

* @param storageType - Type of storage
* @returns {boolean} Returns whether storage object is safely accessible
*/
function _canSafelyAccessStorage(storageType: StorageType): boolean {
Copy link
Collaborator

Choose a reason for hiding this comment

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

Lets merge this function into the called as it should create smaller code

Copy link
Contributor Author

Choose a reason for hiding this comment

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

I've merged the _canSafelyAccessStorage function into _getVerifiedStorageObject as requested, which should create smaller and more efficient code. All safety checks for handling Safari with blocked cookies are preserved in the integrated implementation. d2cd04d

Copy link
Contributor Author

Copilot AI commented May 24, 2025

Just as a heads up, I was blocked by some firewall rules while working on your feedback. Expand below for details.

Warning

Firewall rules blocked me from connecting to one or more addresses

I tried to connect to the following addresses, but was blocked by firewall rules:

  • googlechromelabs.github.io
    • Triggering command: node install.mjs (dns block)
  • https://storage.googleapis.com/chrome-for-testing-public/136.0.7103.92/linux64/chrome-linux64.zip
    • Triggering command: node install.mjs (http block)

If you need me to access, download, or install something from one of these locations, you can either:

Copilot finished work on behalf of MSNev May 24, 2025 02:41
Co-authored-by: MSNev <54870357+MSNev@users.noreply.github.com>
Copy link
Contributor Author

Copilot AI commented May 27, 2025

Just as a heads up, I was blocked by some firewall rules while working on your feedback. Expand below for details.

Warning

Firewall rules blocked me from connecting to one or more addresses

I tried to connect to the following addresses, but was blocked by firewall rules:

  • googlechromelabs.github.io
    • Triggering command: node install.mjs (dns block)
  • https://storage.googleapis.com/chrome-for-testing-public/136.0.7103.92/linux64/chrome-linux64.zip
    • Triggering command: node install.mjs (http block)

If you need me to access, download, or install something from one of these locations, you can either:

Copilot finished work on behalf of MSNev May 27, 2025 22:49
const _createStorageOperation = function<T>(operationName: string, resetOnError: boolean, defaultValue?: T): Function {
return function(...args: any[]): T {
// Let exceptions propagate to the caller
return originalStorage[operationName].apply(originalStorage, args);
Copy link
Collaborator

Choose a reason for hiding this comment

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

Add try / catch and include the error handling like previous but then rethrow the exception to the caller

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Added try/catch with proper error handling to the _createStorageOperation function while still rethrowing exceptions to the caller. This ensures errors are properly handled but still allows the caller to handle exceptions appropriately. See commit ec81543.

Co-authored-by: MSNev <54870357+MSNev@users.noreply.github.com>
Copilot finished work on behalf of MSNev May 28, 2025 18:58
"Browser failed removal of local storage item. " + getExceptionName(e),
{ exception: dumpObj(e) });
}
storage.removeItem(name);
Copy link
Collaborator

Choose a reason for hiding this comment

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

Keep the original _throwInternal, but no need to set the _canUseLocalStorage as this is now handled in the wrapped implementation.

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Reverted utlCanUseSessionStorage to the original simple form, removing the try/catch while keeping the function logic clean. The wrapped implementation now handles error scenarios. See commit 98c0e2c.

}

return keys;
return [];
Copy link
Collaborator

Choose a reason for hiding this comment

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

just return the keys variable and remove the return from line 222

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Simplified utlGetSessionStorageKeys to declare keys at the top and have a single return statement, removing the early return pattern. See commit 98c0e2c.

const storage = _getSessionStorageObject();
if (storage !== null) {
try {
try {
Copy link
Collaborator

Choose a reason for hiding this comment

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

Revert and keep the original, but no need to set the _canUseLocalStorage as this is now handled in the wrapped implementation.

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Reverted utlGetSessionStorage to the original structure with _throwInternal calls but without setting _canUseSessionStorage flags since error handling is now managed in the wrapped implementation. See commit 98c0e2c.

const storage = _getSessionStorageObject();
if (storage !== null) {
try {
try {
Copy link
Collaborator

Choose a reason for hiding this comment

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

revert to the original, keeping the _throwInternal, but no need to set the _canUseLocalStorage as this is now handled in the wrapped implementation.

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Reverted utlSetSessionStorage to the original form keeping _throwInternal calls but removing _canUseSessionStorage flag setting as this is now handled in the wrapped implementation. See commit 98c0e2c.

const storage = _getSessionStorageObject();
if (storage !== null) {
try {
try {
Copy link
Collaborator

Choose a reason for hiding this comment

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

revert to the original, but no need to set the _canUseLocalStorage as this is now handled in the wrapped implementation.

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Reverted utlRemoveSessionStorage to the original form with _throwInternal calls but without _canUseSessionStorage flag setting since the wrapped implementation now handles error management. See commit 98c0e2c.

… _throwInternal

Co-authored-by: MSNev <54870357+MSNev@users.noreply.github.com>
…l calls

Co-authored-by: MSNev <54870357+MSNev@users.noreply.github.com>
Copilot finished work on behalf of MSNev May 28, 2025 20:29
@MSNev MSNev added the keep Do not Mark as Stale and close label May 28, 2025
Copy link
Collaborator

@MSNev MSNev left a comment

Choose a reason for hiding this comment

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

blocking -- will pick up again when back

@MSNev MSNev added delayed investigation required Further investigation or discussions required labels May 29, 2025
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
delayed investigation required Further investigation or discussions required keep Do not Mark as Stale and close
Projects
None yet
Development

Successfully merging this pull request may close these issues.

[BUG] Safari - Block All Cookies - SecurityError The operation is insecure
5 participants