Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
5 changes: 5 additions & 0 deletions .github/workflows/ci.yml
Original file line number Diff line number Diff line change
Expand Up @@ -191,6 +191,11 @@ jobs:
- name: Verify scan subprocess resources are cleaned on failure
timeout-minutes: 5
run: python3 test-scan-resource-cleanup.py
- name: Compile Finder Open AppleScript and test the safe integration harness
timeout-minutes: 2
run: |
/usr/bin/osacompile -o "$RUNNER_TEMP/FinderOpen.scpt" test-finder-open.applescript
python3 test-finder-open.py -v
- name: Verify native open-event origin classification
run: python3 test-native-request-origin.py
- name: Verify per-request timestamps and duplicate suppression
Expand Down
32 changes: 32 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -121,6 +121,38 @@ The saved preference remains on disk so a retry is possible after a failure.
automatically pass through PDF Magic Opener's malware scan gate.** The separate
post-scan browser preference is unchanged.

## Test Finder's Open command

On a Mac with PDF Magic Opener installed and already registered as the **macOS
default PDF handler**, first choose a **known-clean, non-sensitive local test
PDF**. Run this optional interactive integration check:

```sh
python3 test-finder-open.py --live "/absolute/path/to/harmless-test.pdf"
```

This test invokes Finder's actual **Open** command via the separately bundled
AppleScript (`tell application id "com.apple.finder"`), not `/usr/bin/open` or
a direct call to the native application's delegate. It checks that the request
arrives through the existing scan gate and is attributed to Finder using the
anonymous origin flags already recorded by the installed opener. Observe the
usual scanning progress and the subsequent browser handoff or a legitimate
blocked-scan warning as appropriate; the test **does not** treat the request
record as proof of a clean verdict or successful browser rendering.

This live mode requires an interactive macOS login session and permission
for Terminal or the invoking application to automate Finder. If macOS asks for
Automation access, approve it in the normal system prompt; denied access is a
test failure, not a reason to bypass consent. It refuses to run unless the
read-only macOS PDF-handler lookup reports PDF Magic Opener as the default.
The test never sets the default application, changes user preferences, deletes
quarantine, or bypasses ClamAV. It leaves your supplied test PDF in place.

CI compiles the Finder AppleScript on macOS and checks these safeguards and
invocation semantics with deterministic tests. GitHub-hosted CI does **not**
invoke Finder interactively because Automation consent cannot be silently
granted; use the `--live` command above for a genuine Finder integration run.

## Inspect the registered macOS PDF handler

To see which application macOS currently has registered as the default for
Expand Down
9 changes: 9 additions & 0 deletions test-finder-open.applescript
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
-- Finder's Open verb follows the user's existing PDF association.
-- The document path is supplied as an argument, never interpolated into source.
on run argv
if (count of argv) is not 1 then error "Provide exactly one PDF path."
set requestedPDF to POSIX file (item 1 of argv)
tell application id "com.apple.finder"
open requestedPDF
end tell
end run
184 changes: 184 additions & 0 deletions test-finder-open.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,184 @@
#!/usr/bin/env python3
"""Exercise Finder's genuine Open verb on macOS, only with explicit live opt-in.

Normal CI tests check the command and guardrails without launching Finder,
reading user documents, or changing any file associations.
"""

import json
import os
from pathlib import Path
import stat
import subprocess
import sys
import time
import unittest
from unittest.mock import patch

ROOT = Path(__file__).resolve().parent
APP_ID = "local.studio.brave-pdf-opener"
SCRIPT = ROOT / "test-finder-open.applescript"
RUNTIME = (Path.home() / "Library/Application Support/Studio/allow-and-open")
IDENTIFY = RUNTIME / "identify-pdf-handler"
ORIGINS = RUNTIME / "scan-request-origins.jsonl"


def ensure_default_pdf_handler():
"""Require the configured scan gate; never set or replace the default."""
result = subprocess.run(
[str(IDENTIFY)], capture_output=True, check=True, timeout=10,
)
if result.stdout != (APP_ID + "\n").encode("ascii"):
raise RuntimeError("PDF Magic Opener is not the current default PDF handler.")


def validate_test_pdf(path):
"""Only accept an existing regular local PDF that the operator supplied."""
pdf = Path(path)
if pdf.suffix.lower() != ".pdf" or pdf.is_symlink() or not pdf.is_file():
raise ValueError("Supply an existing, regular, non-symlink PDF test file.")
return pdf.resolve(strict=True)


def origin_log_size(path=ORIGINS):
"""Read only the byte offset for the anonymous scan-origin log."""
try:
metadata = path.lstat()
except FileNotFoundError:
return 0
if not stat.S_ISREG(metadata.st_mode) or metadata.st_uid != os.getuid():
raise RuntimeError("Cannot safely inspect the scan-origin log.")
return metadata.st_size


def finder_request_received(path=ORIGINS, start=0, timeout=15):
"""Look only for newly appended anonymous Finder origin records."""
deadline = time.monotonic() + timeout
while time.monotonic() < deadline:
try:
if origin_log_size(path) > start:
with path.open("rb") as stream:
stream.seek(start)
records = stream.read(32768).splitlines()
for raw in records:
try:
record = json.loads(raw)
except (ValueError, UnicodeDecodeError):
continue
if (record.get("from_finder") is True
and record.get("from_browser") is False
and record.get("from_desktop_application") is False):
return True
except FileNotFoundError:
pass
time.sleep(0.1)
return False


def live_finder_open(path):
"""Invoke Finder without changing macOS associations or touching PDF data."""
if sys.platform != "darwin":
raise RuntimeError("The live Finder Open test requires macOS.")
pdf = validate_test_pdf(path)
ensure_default_pdf_handler()
if not (RUNTIME / "allow-and-open.sh").is_file():
raise RuntimeError("PDF Magic Opener's scan gate is not installed.")
before = origin_log_size()
subprocess.run(
["/usr/bin/osascript", str(SCRIPT), str(pdf)],
capture_output=True, check=True, timeout=30,
)
if not finder_request_received(start=before):
raise RuntimeError("Finder Open did not produce a new Finder-attributed scan request.")
print("PASS: Finder Open reached the installed PDF scan gate.")
print("Inspect the native scan progress and normal allow/block outcome.")


class FinderOpenTests(unittest.TestCase):
def test_finder_script_uses_real_open_verb_and_argv(self):
source = SCRIPT.read_text(encoding="utf-8")
self.assertIn('tell application id "com.apple.finder"', source)
self.assertIn("open requestedPDF", source)
self.assertIn("POSIX file (item 1 of argv)", source)
self.assertNotIn("do shell script", source)
self.assertNotIn("setDefaultApplication", source)

def test_handler_preflight_checks_actual_pdf_default(self):
with patch("subprocess.run", return_value=subprocess.CompletedProcess(
[], 0, (APP_ID + "\n").encode("ascii"), b"")) as run:
ensure_default_pdf_handler()
run.assert_called_once_with(
[str(IDENTIFY)], capture_output=True, check=True, timeout=10)
for actual in (b"com.apple.Preview\n", b"", b"com.google.Chrome\n"):
with self.subTest(actual=actual), patch(
"subprocess.run", return_value=subprocess.CompletedProcess(
[], 0, actual, b"")):
with self.assertRaisesRegex(RuntimeError, "not the current default"):
ensure_default_pdf_handler()

def test_live_mode_refuses_to_change_default_or_read_pdf_on_non_macos(self):
with patch.object(sys, "platform", "linux"), patch("subprocess.run") as run:
with self.assertRaisesRegex(RuntimeError, "requires macOS"):
live_finder_open("/tmp/untouched.pdf")
run.assert_not_called()

def test_invalid_pdf_is_rejected_without_invoking_finder(self):
import tempfile
with tempfile.TemporaryDirectory() as directory:
root = Path(directory)
pdf = root / "fixture.PDF"
pdf.write_bytes(b"%PDF-1.4\n")
self.assertEqual(validate_test_pdf(pdf), pdf.resolve())
alias = root / "alias.pdf"
alias.symlink_to(pdf)
for candidate in (alias, root / "missing.pdf", root / "text.txt"):
with self.subTest(candidate=candidate):
with self.assertRaises(ValueError):
validate_test_pdf(candidate)

def test_only_new_finder_attributed_events_are_accepted(self):
import tempfile
with tempfile.TemporaryDirectory() as directory:
log = Path(directory) / "origin.jsonl"
log.write_text(json.dumps({
"from_finder": True, "from_browser": False,
"from_desktop_application": False,
}) + "\n", encoding="utf-8")
before = origin_log_size(log)
with patch("time.monotonic", side_effect=[0, 1]):
self.assertFalse(finder_request_received(log, start=before, timeout=0.5))
with log.open("a", encoding="utf-8") as out:
out.write(json.dumps({
"from_finder": False, "from_browser": True,
"from_desktop_application": False,
}) + "\n")
with patch("time.monotonic", side_effect=[0, 1]):
self.assertFalse(finder_request_received(log, start=before, timeout=0.5))
with log.open("a", encoding="utf-8") as out:
out.write(json.dumps({
"from_finder": True, "from_browser": False,
"from_desktop_application": False,
}) + "\n")
with patch("time.monotonic", side_effect=[0, 0.1]):
self.assertTrue(finder_request_received(log, start=before, timeout=0.5))

def test_manual_instructions_and_ci_do_not_modify_default_association(self):
readme = (ROOT / "README.md").read_text(encoding="utf-8")
ci = (ROOT / ".github/workflows/ci.yml").read_text(encoding="utf-8")
self.assertIn("python3 test-finder-open.py --live", readme)
self.assertIn("osacompile", ci)
self.assertIn("python3 test-finder-open.py -v", ci)
self.assertIn("Automation consent", readme)
self.assertNotIn("osascript", ci)


if __name__ == "__main__":
if len(sys.argv) > 1 and sys.argv[1] == "--live":
if len(sys.argv) != 3:
sys.exit("Usage: python3 test-finder-open.py --live /path/to/harmless.pdf")
try:
live_finder_open(sys.argv[2])
except (OSError, ValueError, RuntimeError, subprocess.SubprocessError) as error:
sys.exit(f"Finder Open test failed: {error}")
else:
unittest.main()
Loading