Press Space to see what a macOS app is really doing.
AppScope is a macOS Quick Look preview extension: select any .app bundle in Finder, press Space, and instantly read its code signing, permission declarations, network behavior, filesystem scope, and a 7-dimension security radar chart — then trigger a deep scan for a full audit report. Tap the Bundle ID to copy it, or export a PNG audit card in one click.
Built entirely on Apple frameworks (Swift + SwiftUI + Security.framework + SQLite). Zero third-party dependencies, zero Process calls.
- Quick Look preview: Space on an
.appfor a second-level readout of signing type, notarization status, high-risk permissions, and a 4-dimension instant score. - 7-dimension security score: code signing / permissions / network / script quality / filesystem / data exfiltration / process manipulation, visualized on a radar chart — higher is riskier.
- Deep scan: one click completes all 7 dimensions, adding script review (obfuscation / eval / download-execute) and process-manipulation detection.
- One-tap copy: click the Bundle ID to copy it to the clipboard, with a lightweight toast.
- One-click PNG export: renders a 2× retina audit card (score ring + radar + key metrics) to
~/Downloads. - CLI
appscope:preview/scan/cache, with recursive directory scanning and JSON/Markdown reports. - Four-language i18n: Simplified Chinese / Traditional Chinese / English / Japanese, across UI, reports, and CLI.
- macOS 13.0+
- Xcode (with
xcodegen, used to generate the project fromproject.yml)
git clone <repo-url> && cd appscope
# Core libraries + CLI + tests (SwiftPM)
cd Core
swift build -c release
swift test # 125 tests, all green
# Host app + Quick Look extension (one-shot build + sign + install)
cd ../App
./build.sh Release # installs to /Applications and registers the extension
# Signing identity resolution: $SIGN_IDENTITY env > App/signing.env > ad-hoc (local debugging only)Once installed to /Applications and launched, press Space on any .app in Finder to preview it.
GUI: Space-preview in Finder → tap the Bundle ID to copy / tap "Export PNG" to save a card to ~/Downloads / tap "Deep Scan" for all 7 dimensions.
CLI:
appscope preview "/Applications/iTerm.app" # quick preview (4-dimension score)
appscope scan "/Applications/iTerm.app" --output json # deep scan (7 dimensions + script review)
appscope scan /Applications --recursive # recursive directory scan
appscope cache list | purge | clear # cache management
appscope --lang ja scan "/Applications/iTerm.app" # i18n: zh-Hans / zh-Hant / en / jaEngineering conventions (Quick Look lifecycle rules, extension resource budgets, logging and testing) live in AGENTS.md.
Four targets share one pure-Swift core:
| Target | Kind | Responsibility |
|---|---|---|
AppScopeCore |
Swift library (no UI) | Data collection + scoring engine + cache + report generation |
AppScopeUI |
Swift library (SwiftUI) | Preview UI: score ring / radar / cards / collapsible sections / export card |
AppScopeCLI |
CLI tool appscope |
preview / scan / cache |
AppScopeHost |
macOS host app (menu bar) | Carries the Quick Look extension |
AppScopeQL |
Quick Look preview extension (.appex) |
Renders the native SwiftUI preview |
AppScopeCore layers by responsibility: collectors (Identity / Signature / Entitlement / Content / Network / Filesystem / Capability / Script / Process) → scoring engine → report rendering → SQLite cache, orchestrated by AppScopeAnalyzer (parallel collection via async let).
Finder Space → QLPreviewingController.preparePreviewOfFile
→ AppScopeAnalyzer.quickScan (parallel: identity + signing + permissions + content)
→ network / filesystem (depend on content inventory)
→ RiskScoringEngine.evaluate (4-dimension instant score)
→ NSHostingView renders PreviewRootView (native SwiftUI)
→ deep-scan button → deepScan (7 dimensions + script review + process manipulation) → hot update
Each dimension is 0–3 (higher is riskier), total 0–21:
| Dimension | 0 low risk | 1 medium | 2 high | 3 critical |
|---|---|---|---|---|
| Code signing | Apple-signed | Developer ID, not notarized | Ad-hoc | Unsigned |
| Permissions | none declared | read-only | network + filesystem | full disk / screen recording / accessibility |
| Network | local only | update check | telemetry | suspicious C2 |
| Script quality | no scripts | few, readable | many, unvalidated | obfuscation / eval / download-execute |
| Filesystem | self-contained | user config | user documents | system paths |
| Data exfiltration | no external traffic | — | — | exfiltration pattern detected |
| Process manipulation | none | reads processes | modifies other apps | code injection |
Risk levels: 🟢 0–4 low · 🟡 5–8 medium · 🟠 9–13 high · 🔴 14–21 critical. Quick preview scores only the first 4 dimensions (denominator 12); deep scan completes all 7 (denominator 21).
| Capability | Status | Notes |
|---|---|---|
| Real-time behavior monitoring | ❌ No | No syscall hooking, no runtime network monitoring — that's EndpointSecurity / NetworkExtension territory; AppScope is static analysis only |
| App uninstall / cleanup | ❌ No | Avoids the risk of accidental deletion |
| Malware signatures / YARA | ❌ No | Static analysis + heuristic scoring suffices |
| Inter-app dependency analysis | ❌ No | No XPC call-chain tracing, no cross-process tracking |
| Finder icon badges | ❌ v2 | Deferred to avoid alarm-inducing permission prompts |
| Windows / Linux ports | ❌ No | Deeply tied to macOS Quick Look + codesign + plist |
- Fully native rendering: the spec called for HTML/CSS + WKWebView; the implementation uses native SwiftUI views (
NSHostingView), aCanvas-drawn radar, and SwiftUI animations. Dark mode, accessibility, and Reduce Motion are handled natively by the system. - Zero
Processcalls: a Quick Look extension runs sandboxed and forbidsNSTask/Process. The spec'sspctl/stringswere replaced with native equivalents — signing viaSecCode, string extraction via a pure-SwiftNativeStringScanner, quarantine viagetxattr. Notarization is inferred from signing type + hardened-runtime flag. .qlgenerator→.appex: the spec conflated.qlgeneratorwithQLPreviewingController— two different APIs. The implementation uses the modern Quick Look preview extension (com.apple.quicklook.preview,QLPreviewingController) embedded in the host app'sPlugIns/.- Type renames:
Dimension→RiskDimension,ProcessInfo→ProcessManipulationInfo, to avoid colliding with Foundation'sNSDimension/ProcessInfo. - Performance: endpoint extraction initially used
NSRegularExpressionover the whole file — 70 s on a 93 MB binary; switching to per-token string operations cut it to 4 s. - Quick Look lifecycle (preview stability): Quick Look touches
.view(firingviewDidLoad) beforepreparePreviewOfFile, andhandler(nil)snapshots the root view immediately. Replacingself.viewlater breaks the ViewBridge XPC session (NSCocoaErrorDomain 4099) and fails the preview. So the view mounts once inloadView(),preparenever touches it, and UI state transitions through a@Publishedphase (loading / ready / error). - SwiftUI layout contract:
Layout.sizeThatFitsmust return a finite size for every proposal. The oldFlowLayoutreturned{inf, …}under an unbounded proposal (always used forintrinsicContentSize), making AppKit throwNSInvalidArgumentException— 12/12 previews failed. Fixed by returning the content's natural width, plus disablingNSHostingViewcontent-size constraints (sizingOptions = []) as defense in depth. - Scan budgets: Electron-class apps ship 13k–24k scripts (Cursor / Kimi); scanning every file blows the quick-preview latency budget and the host kills the extension. Script scans are capped (Network / Filesystem / Process 200 each, Script 2000), with
scripts=scanned/totalin the logs to make truncation observable — small apps are unaffected. - PNG export via AppKit bitmap snapshot: not SwiftUI
ImageRenderer— its offscreen render can't captureCanvascontent (the radar is Metal-drawn), returning a nilnsImageand failing the export. UsesNSHostingView.bitmapImageRepForCachingDisplay+cacheDisplay(the same path as theappscope-rendertool), which captures Canvas content fully.
UI copy ships in Simplified Chinese / Traditional Chinese / English / Japanese, carried by a self-contained static table in AppScopeCore/Support/L10n.swift (no resource bundle dependency), auto-selected from the system language:
- The Quick Look preview UI and audit reports (readable fields in Markdown/JSON) follow the system language.
- The CLI switches explicitly via
--lang <zh-Hans|zh-Hant|en|ja>, defaulting to the system language. - Core / CLI / UI share one copy table;
L10nTestsasserts key entries per language to catch omissions.
Core/Tests/AppScopeCoreTests/ holds 125 XCTest cases covering the scoring-engine rule matrix, collector pure functions, Mach-O parsing, report render round-trips, SQLite cache, native string extraction, four-language copy (L10nTests), and end-to-end analyzer runs (temporary .app fixtures + injected doubles).
cd Core && swift testappscope/
├── Core/ # SwiftPM package: AppScopeCore + AppScopeUI + AppScopeCLI + AppScopeRender + tests
├── AppIcon.png # app icon source (1024×1024)
├── App/
│ ├── AppScopeHost/AppIcon.icns # icon generated from AppIcon.png
│ ├── build.sh # one-shot build + sign + install (identity injection)
│ └── project.yml # single source of truth for the project (xcodegen)
├── AGENTS.md # engineering conventions (Quick Look rules / budgets / logging / tests)
├── prd.md / tech-design.md / ux-spec.md / DESIGN.md / basic-capabilities-audit.md # spec documents
└── README.md
Issues and pull requests are welcome. Read AGENTS.md first (build commands, test conventions, Quick Look lifecycle rules). Before opening a PR, ensure swift test is green and ./build.sh Release succeeds.
MIT © 2026 nanzhi

