CVE-2026-28962
Overview
Background
- Attachment elements
- WebKit’s <attachment> support (ENABLE(ATTACHMENT_ELEMENT)) for embedding file attachments, which can carry local file paths for upload.
- WebPasteboardProxy
- The UI-process side of pasteboard IPC that vends pasteboard data (including file paths) to the WebContent process.
- Allow-list of vended paths
- The set of file paths the UI process actually provided; incoming paths from WebContent must be checked against it.
- Sandbox extension
- A token the UI/Network process grants so a sandboxed process may access a specific file; must only be issued for legitimately vended paths.
Root Cause Analysis
With attachment elements enabled, WebPasteboardProxy (UI process) returns file paths (pathsForFileUpload) obtained from the pasteboard to the WebContent process, but it did not record which paths it had legitimately vended — ‘Missing validation for incoming file paths from web content process when attachment elements are enabled’. Without an allow-list, a malicious or compromised WebContent process could later reference arbitrary file paths (including path-traversal outside the intended files) in attachment/file operations, and the trusted UI/Network process would act on them, disclosing sensitive local files.
The fix adds addAllowedAttachmentFilePaths() and invokes it on every path that vends pasteboard file paths — getPasteboardPathnamesForType, allPasteboardItemInfo (including the iOS HEIC-transcoding continuation, which now captures the IPC::Connection by Ref so the async main-thread hop can still register paths), and informationForItemAtIndex — registering each vended path through WebProcessProxy::addAllowedAttachmentFilePath.
The restored invariant is that the WebContent process may only reference attachment file paths the UI process actually provided, so incoming paths can be validated against this per-process allow-list. INFERENCE: the enforcement check that rejects non-allow-listed incoming paths (the consumer of addAllowedAttachmentFilePath) is outside this hunk; the diff establishes population of the allow-list.
Attack Path
- Enable attachment elements and compromise WebContent On a build with ENABLE(ATTACHMENT_ELEMENT), the attacker controls (or compromises) a WebContent process that speaks the pasteboard/attachment IPC.
- Send an arbitrary file path over IPC WebContent references a file path it never legitimately received (e.g. a path-traversal to a sensitive file) in an attachment/file-upload operation.
- UI/Network process trusts the path Pre-patch, with no allow-list of vended paths, the trusted process accepts the incoming path and grants access / creates a sandbox extension for it.
- Disclose sensitive local files The contents of files outside the intended set are exposed to web content, disclosing sensitive user information.
Impact Assessment
Changed Functions
| Function | Change | Notes |
|---|---|---|
ifSource/WebKit/UIProcess/Cocoa/WebPasteboardProxyCocoa.mm |
modified | |
forSource/WebKit/UIProcess/WebPageProxy.cpp |
modified | |
forTools/TestWebKitAPI/Tests/WebKit/WKWebView/WKAttachmentTests.mm |
modified |
Files Changed
Source/WebKit/UIProcess/Cocoa/WebPasteboardProxyCocoa.mmSource/WebKit/UIProcess/WebPageProxy.cppSource/WebKit/UIProcess/WebProcessProxy.cppSource/WebKit/UIProcess/WebProcessProxy.hTools/TestWebKitAPI/Tests/WebKit/WKWebView/WKAttachmentTests.mm
Audit Directions
- Other IPC accepting file paths from WebContentAudit UI/Network-process IPC endpoints that receive file paths or create SandboxExtension handles from WebContent-supplied strings without checking an allow-list; the tell is SandboxExtension::createHandle(pathFromWebProcess, …).
- Attachment / file-upload path flowsTrace pathsForFileUpload and attachment file paths end-to-end and confirm every consumer validates against addAllowedAttachmentFilePath (the allow-list this commit populates).
- Async IPC continuations dropping the connectionLook for async transcoding/dispatch continuations that previously dropped the IPC::Connection; ensure they capture it (by Ref) so per-connection allow-listing/validation still runs on the main-thread hop.
Patch
diff --git a/Source/WebKit/UIProcess/Cocoa/WebPasteboardProxyCocoa.mm b/Source/WebKit/UIProcess/Cocoa/WebPasteboardProxyCocoa.mm
index d21fdd1127ac..99542418e02d 100644
--- a/Source/WebKit/UIProcess/Cocoa/WebPasteboardProxyCocoa.mm
+++ b/Source/WebKit/UIProcess/Cocoa/WebPasteboardProxyCocoa.mm
@@ -84,6 +84,24 @@ static bool shouldTranscodeHEICImagesForPage(std::optional<WebPageProxyIdentifie
return protect(page->preferences())->needsSiteSpecificQuirks() && Quirks::shouldTranscodeHeicImagesForURL(URL { page->currentURL() });
}
+#if ENABLE(ATTACHMENT_ELEMENT)
+
+static void addAllowedAttachmentFilePaths(const IPC::Connection& connection, std::optional<WebPageProxyIdentifier> pageID, const Vector<String>& paths)
+{
+ if (!pageID)
+ return;
+
+ RefPtr page = WebProcessProxy::webPage(*pageID);
+ if (!page)
+ return;
+
+ for (auto& path : paths)
+ WebProcessProxy::fromConnection(connection)->addAllowedAttachmentFilePath(path);
+}
+
+#endif // ENABLE(ATTACHMENT_ELEMENT)
+
+
void WebPasteboardProxy::grantAccessToCurrentTypes(WebProcessProxy& process, const String& pasteboardName)
{
grantAccess(process, pasteboardName, PasteboardAccessType::Types);
@@ -252,6 +270,9 @@ void WebPasteboardProxy::getPasteboardPathnamesForType(IPC::Connection& connecti
return SandboxExtension::Handle { };
return valueOrDefault(SandboxExtension::createHandle(filename, SandboxExtension::Type::ReadOnly));
});
+#if ENABLE(ATTACHMENT_ELEMENT)
+ addAllowedAttachmentFilePaths(connection, pageID, pathnames);
+#endif
completionHandler(WTF::move(pathnames), WTF::move(sandboxExtensions));
return;
}
@@ -643,12 +664,22 @@ void WebPasteboardProxy::allPasteboardItemInfo(IPC::Connection& connection, cons
#if PLATFORM(IOS_FAMILY)
if (!allInfo || !process) {
+#if ENABLE(ATTACHMENT_ELEMENT)
+ if (allInfo) {
+ for (auto& info : *allInfo)
+ addAllowedAttachmentFilePaths(connection, pageID, info.pathsForFileUpload);
+ }
+#endif
completionHandler(WTF::move(allInfo));
return;
}
auto transcodingInfo = findHEICPathsForTranscoding(*allInfo, pageID);
if (transcodingInfo.isEmpty()) {
+#if ENABLE(ATTACHMENT_ELEMENT)
+ for (auto& info : *allInfo)
+ addAllowedAttachmentFilePaths(connection, pageID, info.pathsForFileUpload);
+#endif
completionHandler(WTF::move(allInfo));
return;
}
@@ -656,12 +687,12 @@ void WebPasteboardProxy::allPasteboardItemInfo(IPC::Connection& connection, cons
auto pathsToTranscode = extractPathsToTranscode(transcodingInfo);
auto [transcodingUTI, transcodingExtension] = heicTranscodingParameters();
- sharedImageTranscodingQueueSingleton().dispatch([process = WTF::move(process), allInfo = WTF::move(*allInfo), pathsToTranscode = crossThreadCopy(WTF::move(pathsToTranscode)), transcodingInfo = WTF::move(transcodingInfo), transcodingUTI = crossThreadCopy(WTF::move(transcodingUTI)), transcodingExtension = crossThreadCopy(WTF::move(transcodingExtension)), completionHandler = WTF::move(completionHandler)] mutable {
+ sharedImageTranscodingQueueSingleton().dispatch([protectedConnection = Ref { connection }, pageID, process = WTF::move(process), allInfo = WTF::move(*allInfo), pathsToTranscode = crossThreadCopy(WTF::move(pathsToTranscode)), transcodingInfo = WTF::move(transcodingInfo), transcodingUTI = crossThreadCopy(WTF::move(transcodingUTI)), transcodingExtension = crossThreadCopy(WTF::move(transcodingExtension)), completionHandler = WTF::move(completionHandler)] mutable {
ASSERT(!RunLoop::isMain());
auto transcodedPaths = transcodeImages(pathsToTranscode, transcodingUTI, transcodingExtension);
- RunLoop::mainSingleton().dispatch([process = WTF::move(process), allInfo = WTF::move(allInfo), transcodedPaths = crossThreadCopy(WTF::move(transcodedPaths)), transcodingInfo = WTF::move(transcodingInfo), completionHandler = WTF::move(completionHandler)] mutable {
+ RunLoop::mainSingleton().dispatch([protectedConnection = WTF::move(protectedConnection), pageID, process = WTF::move(process), allInfo = WTF::move(allInfo), transcodedPaths = crossThreadCopy(WTF::move(transcodedPaths)), transcodingInfo = WTF::move(transcodingInfo), completionHandler = WTF::move(completionHandler)] mutable {
for (size_t i = 0; i < transcodingInfo.size(); ++i) {
if (!transcodedPaths[i].isEmpty())
allInfo[transcodingInfo[i].infoIndex].pathsForFileUpload[transcodingInfo[i].pathIndex] = transcodedPaths[i];
@@ -669,10 +700,21 @@ void WebPasteboardProxy::allPasteboardItemInfo(IPC::Connection& connection, cons
notifyNetworkProcessOfTranscodedFiles(process, transcodedPaths);
+#if ENABLE(ATTACHMENT_ELEMENT)
+ for (auto& info : allInfo)
+ addAllowedAttachmentFilePaths(protectedConnection.get(), pageID, info.pathsForFileUpload);
+#endif
+
completionHandler(WTF::move(allInfo));
});
});
#else
+#if ENABLE(ATTACHMENT_ELEMENT)
+ if (allInfo) {
+ for (auto& info : *allInfo)
+ addAllowedAttachmentFilePaths(connection, pageID, info.pathsForFileUpload);
+ }
+#endif
completionHandler(WTF::move(allInfo));
#endif
});
@@ -693,12 +735,19 @@ void WebPasteboardProxy::informationForItemAtIndex(IPC::Connection& connection,
#if PLATFORM(IOS_FAMILY)
if (!info || !process) {
+#if ENABLE(ATTACHMENT_ELEMENT)
+ if (info)
+ addAllowedAttachmentFilePaths(connection, pageID, info->pathsForFileUpload);
+#endif
completionHandler(WTF::move(info));
return;
}
auto transcodingInfo = findHEICPathsForTranscoding(*info, pageID);
if (transcodingInfo.isEmpty()) {
+#if ENABLE(ATTACHMENT_ELEMENT)
+ addAllowedAttachmentFilePaths(connection, pageID, info->pathsForFileUpload);
+#endif
completionHandler(WTF::move(info));
return;
}
@@ -706,12 +755,12 @@ void WebPasteboardProxy::informationForItemAtIndex(IPC::Connection& connection,
auto pathsToTranscode = extractPathsToTranscode(transcodingInfo);
auto [transcodingUTI, transcodingExtension] = heicTranscodingParameters();
- sharedImageTranscodingQueueSingleton().dispatch([process = WTF::move(process), info = WTF::move(*info), pathsToTranscode = crossThreadCopy(WTF::move(pathsToTranscode)), transcodingInfo = WTF::move(transcodingInfo), transcodingUTI = crossThreadCopy(WTF::move(transcodingUTI)), transcodingExtension = crossThreadCopy(WTF::move(transcodingExtension)), completionHandler = WTF::move(completionHandler)] mutable {
+ sharedImageTranscodingQueueSingleton().dispatch([protectedConnection = Ref { connection }, pageID, process = WTF::move(process), info = WTF::move(*info), pathsToTranscode = crossThreadCopy(WTF::move(pathsToTranscode)), transcodingInfo = WTF::move(transcodingInfo), transcodingUTI = crossThreadCopy(WTF::move(transcodingUTI)), transcodingExtension = crossThreadCopy(WTF::move(transcodingExtension)), completionHandler = WTF::move(completionHandler)] mutable {
ASSERT(!RunLoop::isMain());
auto transcodedPaths = transcodeImages(pathsToTranscode, transcodingUTI, transcodingExtension);
- RunLoop::mainSingleton().dispatch([process = WTF::move(process), info = WTF::move(info), transcodedPaths = crossThreadCopy(WTF::move(transcodedPaths)), transcodingInfo = WTF::move(transcodingInfo), completionHandler = WTF::move(completionHandler)] mutable {
+ RunLoop::mainSingleton().dispatch([protectedConnection = WTF::move(protectedConnection), pageID, process = WTF::move(process), info = WTF::move(info), transcodedPaths = crossThreadCopy(WTF::move(transcodedPaths)), transcodingInfo = WTF::move(transcodingInfo), completionHandler = WTF::move(completionHandler)] mutable {
for (size_t i = 0; i < transcodingInfo.size(); ++i) {
if (!transcodedPaths[i].isEmpty())
info.pathsForFileUpload[transcodingInfo[i].pathIndex] = transcodedPaths[i];
@@ -719,10 +768,18 @@ void WebPasteboardProxy::informationForItemAtIndex(IPC::Connection& connection,
notifyNetworkProcessOfTranscodedFiles(process, transcodedPaths);
+#if ENABLE(ATTACHMENT_ELEMENT)
+ addAllowedAttachmentFilePaths(protectedConnection.get(), pageID, info.pathsForFileUpload);
+#endif
+
completionHandler(WTF::move(info));
});
});
#else
+#if ENABLE(ATTACHMENT_ELEMENT)
+ if (info)
+ addAllowedAttachmentFilePaths(connection, pageID, info->pathsForFileUpload);
+#endif
completionHandler(WTF::move(info));
#endif
});
diff --git a/Source/WebKit/UIProcess/WebPageProxy.cpp b/Source/WebKit/UIProcess/WebPageProxy.cpp
index 38d217dd82f6..84b689dc1fdc 100644
--- a/Source/WebKit/UIProcess/WebPageProxy.cpp
+++ b/Source/WebKit/UIProcess/WebPageProxy.cpp
@@ -4174,6 +4174,11 @@ void WebPageProxy::performDragOperation(DragData& dragData, const String& dragSt
if (!protectedThis->m_mainFrame)
return;
+#if ENABLE(ATTACHMENT_ELEMENT)
+ for (auto& filename : dragData.fileNames())
+ protect(protectedThis->legacyMainFrameProcess())->addAllowedAttachmentFilePath(filename);
+#endif
+
DragData dragDataCopy(dragData);
protectedThis->sendWithAsyncReplyToProcessContainingFrame(protectedThis->m_mainFrame->frameID(), Messages::WebPage::PerformDragOperation(protectedThis->m_mainFrame->frameID(), WTF::move(dragData), WTF::move(sandboxExtensionHandle), WTF::move(sandboxExtensionsForUpload)), [protectedThis, frameID = protectedThis->m_mainFrame->frameID(), dragDataCopy = WTF::move(dragDataCopy), dragStorageName] (DragOperationResult dragOperationResult) mutable {
@@ -16817,6 +16822,7 @@ void WebPageProxy::registerAttachmentIdentifierFromFilePath(IPC::Connection& con
{
MESSAGE_CHECK_BASE(protect(preferences())->attachmentElementEnabled(), connection);
MESSAGE_CHECK_BASE(IdentifierToAttachmentMap::isValidKey(identifier), connection);
+ MESSAGE_CHECK_BASE(WebProcessProxy::fromConnection(connection)->isAllowedAttachmentFilePath(filePath), connection);
if (attachmentForIdentifier(identifier))
return;
@@ -16842,6 +16848,7 @@ void WebPageProxy::registerAttachmentsFromSerializedData(IPC::Connection& connec
MESSAGE_CHECK_BASE(protect(preferences())->attachmentElementEnabled(), connection);
for (auto& serializedData : data) {
+ MESSAGE_CHECK_BASE(IdentifierToAttachmentMap::isValidKey(serializedData.identifier), connection);
auto identifier = WTF::move(serializedData.identifier);
if (!attachmentForIdentifier(identifier)) {
Ref attachment = ensureAttachment(identifier);
diff --git a/Source/WebKit/UIProcess/WebProcessProxy.cpp b/Source/WebKit/UIProcess/WebProcessProxy.cpp
index 2c239bf014cc..1d4609894990 100644
--- a/Source/WebKit/UIProcess/WebProcessProxy.cpp
+++ b/Source/WebKit/UIProcess/WebProcessProxy.cpp
@@ -1513,6 +1513,19 @@ void WebProcessProxy::setIgnoreInvalidMessageForTesting()
}
#endif
+#if ENABLE(ATTACHMENT_ELEMENT)
+void WebProcessProxy::addAllowedAttachmentFilePath(const String& filePath)
+{
+ if (!filePath.isEmpty())
+ m_allowedAttachmentFilePaths.add(filePath);
+}
+
+bool WebProcessProxy::isAllowedAttachmentFilePath(const String& filePath) const
+{
+ return m_allowedAttachmentFilePaths.contains(filePath);
+}
+#endif
+
void WebProcessProxy::didFinishLaunching(ProcessLauncher* launcher, IPC::Connection::Identifier&& connectionIdentifier)
{
WEBPROCESSPROXY_RELEASE_LOG(Process, "didFinishLaunching:");
diff --git a/Source/WebKit/UIProcess/WebProcessProxy.h b/Source/WebKit/UIProcess/WebProcessProxy.h
index df560e2b0fd5..40da3f33e9f2 100644
--- a/Source/WebKit/UIProcess/WebProcessProxy.h
+++ b/Source/WebKit/UIProcess/WebProcessProxy.h
@@ -514,6 +514,11 @@ public:
bool ignoreInvalidMessageForTesting() const { return m_ignoreInvalidMessageForTesting; }
void setIgnoreInvalidMessageForTesting();
#endif
+
+#if ENABLE(ATTACHMENT_ELEMENT)
+ void addAllowedAttachmentFilePath(const String&);
+ bool isAllowedAttachmentFilePath(const String&) const;
+#endif
bool allowTestOnlyIPC() const { return m_allowTestOnlyIPC; }
void setAllowTestOnlyIPC(bool allowTestOnlyIPC) { m_allowTestOnlyIPC = allowTestOnlyIPC; }
@@ -909,6 +914,10 @@ private:
#if ENABLE(IPC_TESTING_API)
bool m_ignoreInvalidMessageForTesting { false };
#endif
+
+#if ENABLE(ATTACHMENT_ELEMENT)
+ HashSet<String> m_allowedAttachmentFilePaths;
+#endif
bool m_allowTestOnlyIPC { false };
diff --git a/Tools/TestWebKitAPI/Tests/WebKit/WKWebView/WKAttachmentTests.mm b/Tools/TestWebKitAPI/Tests/WebKit/WKWebView/WKAttachmentTests.mm
index 4aee71f4c649..4a7e3ea9d288 100644
--- a/Tools/TestWebKitAPI/Tests/WebKit/WKWebView/WKAttachmentTests.mm
+++ b/Tools/TestWebKitAPI/Tests/WebKit/WKWebView/WKAttachmentTests.mm
@@ -45,6 +45,7 @@
#import <WebKit/WKWebViewPrivateForTesting.h>
#import <WebKit/WebArchive.h>
#import <WebKit/WebKitPrivate.h>
+#import <WebKit/_WKFeature.h>
#import <WebKit/_WKUserStyleSheet.h>
#import <ranges>
#import <wtf/RetainPtr.h>
@@ -2746,6 +2747,63 @@ TEST(WKAttachmentTestsIOS, PasteRichTextCopiedFromNotes)
#endif // PLATFORM(IOS_FAMILY)
+#if PLATFORM(MAC) && ENABLE(IPC_TESTING_API)
+
+TEST(WKAttachmentTestsMac, RegisterAttachmentIdentifierFromFilePathWithUnauthorizedPathTerminatesProcess)
+{
+ RetainPtr configuration = adoptNS([[WKWebViewConfiguration alloc] init]);
+ [configuration _setAttachmentElementEnabled:YES];
+
+ WKPreferencesSetCustomPasteboardDataEnabled((__bridge WKPreferencesRef)[configuration preferences], YES);
+ for (_WKFeature *feature in [WKPreferences _features]) {
+ if ([feature.key isEqualToString:@"IPCTestingAPIEnabled"])
+ [[configuration preferences] _setEnabled:YES forFeature:feature];
+ if ([feature.key isEqualToString:@"IgnoreInvalidMessageWhenIPCTestingAPIEnabled"])
+ [[configuration preferences] _setEnabled:NO forFeature:feature];
+ }
+
+ RetainPtr webView = adoptNS([[TestWKWebView alloc] initWithFrame:NSMakeRect(0, 0, 500, 500) configuration:configuration.get()]);
+ [webView synchronouslyLoadHTMLString:attachmentEditingTestMarkup];
+
+ RetainPtr navigationDelegate = adoptNS([TestNavigationDelegate new]);
+ [webView setNavigationDelegate:navigationDelegate.get()];
+
+ [webView evaluateJavaScript:
+ @"IPC.sendMessage('UI', IPC.webPageProxyID, IPC.messages.WebPageProxy_RegisterAttachmentIdentifierFromFilePath.name, ["
+ " {type: 'String', value: 'fake-identifier'},"
+ " {type: 'String', value: 'application/octet-stream'},"
+ " {type: 'String', value: '/etc/passwd'}"
+ "])"
+ completionHandler:nil];
+
+ [navigationDelegate waitForWebContentProcessDidTerminate];
+}
+
+TEST(WKAttachmentTestsMac, PastedFileURLsUseAuthorizedPaths)
+{
+ NSPasteboard *pasteboard = [NSPasteboard generalPasteboard];
+ [pasteboard clearContents];
+ [pasteboard declareTypes:@[NSFilenamesPboardType] owner:nil];
+ [pasteboard setPropertyList:@[testPDFFileURL().path, testImageFileURL().path] forType:NSFilenamesPboardType];
+
+ RetainPtr<NSArray<_WKAttachment *>> insertedAttachments;
+ RetainPtr webView = webViewForTestingAttachments();
+ {
+ ObserveAttachmentUpdatesForScope observer(webView.get());
+ [webView _synchronouslyExecuteEditCommand:@"Paste" argument:nil];
+ insertedAttachments = [observer.observer() inserted];
+ EXPECT_EQ(2U, [insertedAttachments count]);
+ }
+
+ [webView expectElementCount:1 querySelector:@"ATTACHMENT"];
+ [webView expectElementCount:1 querySelector:@"IMG"];
+
+ for (_WKAttachment *attachment in insertedAttachments.get())
+ EXPECT_GT(attachment.info.data.length, 0U);
+}
+
+#endif // PLATFORM(MAC) && ENABLE(IPC_TESTING_API)
+
} // namespace TestWebKitAPI
#endif // PLATFORM(MAC) || PLATFORM(IOS) || PLATFORM(VISION)
Regression Test / PoC
diff --git a/Tools/TestWebKitAPI/Tests/WebKit/WKWebView/WKAttachmentTests.mm b/Tools/TestWebKitAPI/Tests/WebKit/WKWebView/WKAttachmentTests.mm
index 4aee71f4c649..4a7e3ea9d288 100644
--- a/Tools/TestWebKitAPI/Tests/WebKit/WKWebView/WKAttachmentTests.mm
+++ b/Tools/TestWebKitAPI/Tests/WebKit/WKWebView/WKAttachmentTests.mm
@@ -45,6 +45,7 @@
#import <WebKit/WKWebViewPrivateForTesting.h>
#import <WebKit/WebArchive.h>
#import <WebKit/WebKitPrivate.h>
+#import <WebKit/_WKFeature.h>
#import <WebKit/_WKUserStyleSheet.h>
#import <ranges>
#import <wtf/RetainPtr.h>
@@ -2746,6 +2747,63 @@ TEST(WKAttachmentTestsIOS, PasteRichTextCopiedFromNotes)
#endif // PLATFORM(IOS_FAMILY)
+#if PLATFORM(MAC) && ENABLE(IPC_TESTING_API)
+
+TEST(WKAttachmentTestsMac, RegisterAttachmentIdentifierFromFilePathWithUnauthorizedPathTerminatesProcess)
+{
+ RetainPtr configuration = adoptNS([[WKWebViewConfiguration alloc] init]);
+ [configuration _setAttachmentElementEnabled:YES];
+
+ WKPreferencesSetCustomPasteboardDataEnabled((__bridge WKPreferencesRef)[configuration preferences], YES);
+ for (_WKFeature *feature in [WKPreferences _features]) {
+ if ([feature.key isEqualToString:@"IPCTestingAPIEnabled"])
+ [[configuration preferences] _setEnabled:YES forFeature:feature];
+ if ([feature.key isEqualToString:@"IgnoreInvalidMessageWhenIPCTestingAPIEnabled"])
+ [[configuration preferences] _setEnabled:NO forFeature:feature];
+ }
+
+ RetainPtr webView = adoptNS([[TestWKWebView alloc] initWithFrame:NSMakeRect(0, 0, 500, 500) configuration:configuration.get()]);
+ [webView synchronouslyLoadHTMLString:attachmentEditingTestMarkup];
+
+ RetainPtr navigationDelegate = adoptNS([TestNavigationDelegate new]);
+ [webView setNavigationDelegate:navigationDelegate.get()];
+
+ [webView evaluateJavaScript:
+ @"IPC.sendMessage('UI', IPC.webPageProxyID, IPC.messages.WebPageProxy_RegisterAttachmentIdentifierFromFilePath.name, ["
+ " {type: 'String', value: 'fake-identifier'},"
+ " {type: 'String', value: 'application/octet-stream'},"
+ " {type: 'String', value: '/etc/passwd'}"
+ "])"
+ completionHandler:nil];
+
+ [navigationDelegate waitForWebContentProcessDidTerminate];
+}
+
+TEST(WKAttachmentTestsMac, PastedFileURLsUseAuthorizedPaths)
+{
+ NSPasteboard *pasteboard = [NSPasteboard generalPasteboard];
+ [pasteboard clearContents];
+ [pasteboard declareTypes:@[NSFilenamesPboardType] owner:nil];
+ [pasteboard setPropertyList:@[testPDFFileURL().path, testImageFileURL().path] forType:NSFilenamesPboardType];
+
+ RetainPtr<NSArray<_WKAttachment *>> insertedAttachments;
+ RetainPtr webView = webViewForTestingAttachments();
+ {
+ ObserveAttachmentUpdatesForScope observer(webView.get());
+ [webView _synchronouslyExecuteEditCommand:@"Paste" argument:nil];
+ insertedAttachments = [observer.observer() inserted];
+ EXPECT_EQ(2U, [insertedAttachments count]);
+ }
+
+ [webView expectElementCount:1 querySelector:@"ATTACHMENT"];
+ [webView expectElementCount:1 querySelector:@"IMG"];
+
+ for (_WKAttachment *attachment in insertedAttachments.get())
+ EXPECT_GT(attachment.info.data.length, 0U);
+}
+
+#endif // PLATFORM(MAC) && ENABLE(IPC_TESTING_API)
+
} // namespace TestWebKitAPI
#endif // PLATFORM(MAC) || PLATFORM(IOS) || PLATFORM(VISION)