Skip to content

Validate URL schemes and double extensions on file import#5226

Open
pballart wants to merge 3 commits into
mainfrom
pau/improve-file-import-validation
Open

Validate URL schemes and double extensions on file import#5226
pballart wants to merge 3 commits into
mainfrom
pau/improve-file-import-validation

Conversation

@pballart
Copy link
Copy Markdown
Contributor

@pballart pballart commented Jun 2, 2026

Task/Issue URL: https://app.asana.com/0/0/1210890453697052/f/

Description

Hardens the third-party file/zip import path (security-audit follow-up):

  • Drops bookmarks whose URL uses a javascript:/data: scheme — activating such a bookmark would execute embedded content instead of navigating.
  • Rejects files and zip entries with a disguising double extension (e.g. foo.swift.html).

Both rules live once in BrowserServicesKit (String.hasUnsafeBookmarkImportScheme() / String.hasMultipleFileExtensions) and are invoked from each platform's import paths — the scheme check at the shared bookmark-save chokepoint (iOS BookmarkOrFolder.isInvalidBookmark, macOS LocalBookmarkStore), and the double-extension check at both the archive readers (ImportArchiveReader) and the direct-file entry points (iOS DataImportManager.importFile, macOS SafariArchiveImporter.readContentsForSelectedFile). Archives themselves are exempt — their entries are validated individually.

Testing Steps

  1. Import a bookmarks HTML file with a normal https: bookmark plus javascript:/data: bookmarks (iOS + macOS) → the https: one imports; the unsafe ones are dropped (counted as failed, not saved).
  2. Import a zip / Safari archive containing bookmarks.html + passwords.csv plus a decoy like evil.swift.html → normal files import; the double-extension entry is skipped.
  3. Import a standalone file named with a double extension (e.g. bookmarks.evil.html) directly → it's rejected (nothing imported).
  4. Regression: import a normal Safari/Chrome export and confirm all valid bookmarks still import.

Impact and Risks

Low–Medium — changes which imported bookmarks are persisted (privacy/security hardening; no change to existing stored data).

What could go wrong?

A legitimate item could be dropped. Mitigations: the scheme check is limited to javascript:/data: and is bookmarks-only (passwords/cards unaffected). The double-extension rule is intentionally strict (any filename with >1 dot) — an accepted trade-off agreed in the kick-off — so a legitimately double-dotted archive entry is skipped, and a directly-selected file with that shape is rejected outright (the import fails). The strictness was a deliberate decision over false-positive risk.

Quality Considerations

  • Validation centralized in BSK and reused at every per-platform import sink, so a future import source inherits both checks.
  • Unit tests added in BSK (StringFileExtensionsTests, BookmarkImportSchemeValidationTests), iOS (ImportArchiveReaderTests, DataImportManagerTests), and macOS (LocalBookmarkStoreTests, SafariArchiveImporterTests). iOS tests verified passing locally; macOS + BSK suites run on CI.

Internal references:

Definition of Done | Engineering Expectations | Tech Design Template


Note

Medium Risk
Import behavior changes which bookmarks and files are accepted; scheme filtering is narrow but the double-extension rule may skip legitimate multi-dot filenames (e.g. report.tar.gz).

Overview
Hardens third-party data import (security-audit follow-up) by blocking two abuse paths: executable bookmark targets and disguised filenames.

Unsafe bookmark URLs: Shared helpers in BrowserServicesKit (hasUnsafeBookmarkImportScheme() for javascript: / data:) extend BookmarkOrFolder.isInvalidBookmark, so iOS import via BookmarkCoreDataImporter drops those entries. macOS adds the same filter in LocalBookmarkStore when persisting imports (folders with stray unsafe URLs are kept so children still import).

Double extensions: hasMultipleFileExtensions flags last path components with more than one dot (e.g. foo.swift.html). Direct file picks are rejected on iOS (DataImportManager.importFile returns nil) and macOS standalone Safari imports (SafariArchiveImporter yields empty contents / import failure). Zip entries with that pattern are skipped in both platforms’ ImportArchiveReader while normal .html/.csv/.json entries still load.

Unit tests cover BSK helpers and platform import/archive paths.

Reviewed by Cursor Bugbot for commit d6b7f2a. Bugbot is set up for automated code reviews on this repo. Configure here.

pballart and others added 2 commits June 2, 2026 12:55
Adds two security validations to the third-party file/zip import path,
agreed in the kick-off:

- Drop bookmarks whose URL uses a javascript:/data: scheme (which would
  execute content rather than navigate if the bookmark were activated).
- Reject zip entries with a disguising double extension (e.g. foo.swift.html).

Both rules live once in BrowserServicesKit as the single source of truth
(String.hasUnsafeBookmarkImportScheme / String.hasMultipleFileExtensions) and
are invoked from each platform's existing skip-invalid path: iOS via
BookmarkOrFolder.isInvalidBookmark and the Core ImportArchiveReader, macOS via
the LocalBookmarkStore import loop and the DataImport ImportArchiveReader.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
…imits

Addresses PR review:
- Apply the double-extension guard to directly-selected (non-archive) files, not
  just zip entries: iOS DataImportManager.importFile and macOS
  SafariArchiveImporter.readContentsForSelectedFile now reject a file whose name
  has a disguising double extension (e.g. foo.swift.html). Archives stay exempt —
  their entries are still validated individually inside ImportArchiveReader.
- Add .timeLimit(.minutes(1)) (with @available) to all Swift Testing @test
  declarations, per the codebase convention, to avoid the 60-minute default.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
@pballart pballart force-pushed the pau/improve-file-import-validation branch from 8e304ad to 4125bf5 Compare June 2, 2026 10:55
Copy link
Copy Markdown
Contributor

@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.

Cursor Bugbot has reviewed your changes and found 1 potential issue.

Fix All in Cursor

❌ Bugbot Autofix is OFF. To automatically fix reported issues with cloud agents, have a team admin enable autofix in the Cursor dashboard.

Reviewed by Cursor Bugbot for commit 4125bf5. Configure here.

Comment thread macOS/DuckDuckGo/Bookmarks/Services/LocalBookmarkStore.swift
Addresses PR review: the macOS guard applied to all BookmarkOrFolder types, so a
folder carrying a stray javascript:/data: URL string would be skipped along with
all its children. Exempt folders (matching iOS BookmarkOrFolder.isInvalidBookmark)
so nested bookmarks aren't silently lost, keeping both platforms consistent.

Co-Authored-By: Claude Opus 4.8 (1M context) <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