├── .github ├── ISSUE_TEMPLATE │ └── report_bug.yml ├── bin │ └── profile ├── fixtures │ ├── bad.txt │ └── default.txt ├── pkg │ └── postinstall ├── profiles │ ├── bad.sh │ └── default.sh └── workflows │ ├── release.yml │ └── test.yml ├── .gitignore ├── .periphery.yml ├── .swiftlint.yml ├── API.playground ├── Contents.swift ├── contents.xcplayground └── timeline.xctimeline ├── CLAUDE.md ├── CODE_OF_CONDUCT.md ├── CONTRIBUTING.md ├── IconOrange.imageset ├── Contents.json ├── icon_black_orange.svg └── icon_white_orange.svg ├── JWT.playground ├── Contents.swift ├── contents.xcplayground └── timeline.xctimeline ├── LICENSE ├── Makefile ├── Mintfile ├── Pareto Security.xcodeproj ├── project.pbxproj ├── project.xcworkspace │ ├── contents.xcworkspacedata │ └── xcshareddata │ │ └── IDEWorkspaceChecks.plist └── xcshareddata │ └── xcschemes │ └── Pareto Security.xcscheme ├── Pareto Security.xctestplan ├── Pareto ├── AppHandlers.swift ├── AppInfo.swift ├── Assets.xcassets │ ├── AccentColor.colorset │ │ └── Contents.json │ ├── AppIcon.appiconset │ │ ├── Contents.json │ │ ├── icon_128x128.png │ │ ├── icon_128x128@2x.png │ │ ├── icon_16x16.png │ │ ├── icon_16x16@2x.png │ │ ├── icon_256x256.png │ │ ├── icon_256x256@2x.png │ │ ├── icon_32x32.png │ │ ├── icon_32x32@2x.png │ │ ├── icon_512x512.png │ │ └── icon_512x512@2x.png │ ├── Contents.json │ ├── Icon.imageset │ │ ├── Contents.json │ │ └── icon.png │ ├── IconGray.imageset │ │ ├── Contents.json │ │ ├── icon_black.svg │ │ └── icon_white.svg │ ├── IconGrayActive.imageset │ │ ├── Contents.json │ │ ├── icon_black_gray.svg │ │ └── icon_white_gray.svg │ ├── IconGreen.imageset │ │ ├── Contents.json │ │ ├── icon_black_green.svg │ │ └── icon_white_green.svg │ └── IconOrange.imageset │ │ ├── Contents.json │ │ ├── icon_black_orange.svg │ │ └── icon_white_orange.svg ├── Checks │ ├── Access Security │ │ ├── Autologin.swift │ │ ├── NoAdminUser.swift │ │ ├── NoUnusedUsers.swift │ │ ├── PasswordAfterSleep.swift │ │ ├── PasswordManager.swift │ │ ├── PasswordtoUnlock.swift │ │ ├── SSHKeys.swift │ │ ├── SSHKeysStrength.swift │ │ └── Screensaver.swift │ ├── AppCheck.swift │ ├── Application Updates │ │ ├── 1Password7.swift │ │ ├── 1Password8.swift │ │ ├── AdobeReader.swift │ │ ├── AppUpdateCheck.tmpl │ │ ├── Bitwarden.swift │ │ ├── Brave Browser.swift │ │ ├── Cyberduck.swift │ │ ├── Dashlane.swift │ │ ├── Docker.swift │ │ ├── Enpass.swift │ │ ├── Grammarly.swift │ │ ├── MicrosoftTeams.swift │ │ ├── NordLayer.swift │ │ ├── ParetoUpdated.swift │ │ ├── Slack.swift │ │ ├── Tailscale.swift │ │ ├── WireGuard.swift │ │ ├── gen.py │ │ └── iTerm.swift │ ├── Checks.swift │ ├── Claim.swift │ ├── Firefox.swift │ ├── Firewall and Sharing │ │ ├── AirDrop.swift │ │ ├── AirPlay.swift │ │ ├── FileSharingCheck.swift │ │ ├── Firewall.swift │ │ ├── FirewallStealth.swift │ │ ├── InternetSharing.swift │ │ ├── MediaShare.swift │ │ ├── PrinterSharingCheck.swift │ │ ├── RemoteLogin.swift │ │ └── RemoteManagment.swift │ ├── GoogleChrome.swift │ ├── IntegrationCheck.swift │ ├── LibreOffice.swift │ ├── LuLu.swift │ ├── ParetoCheck.swift │ ├── Signal.swift │ ├── SublimeText.swift │ ├── System Integrity │ │ ├── Boot.swift │ │ ├── FileVault.swift │ │ ├── Gatekeeper.swift │ │ ├── OpenWiFi.swift │ │ ├── SecureTerminal.swift │ │ ├── SecureiTerm.swift │ │ ├── TimeMachine.swift │ │ ├── TimeMachineHasBackup.swift │ │ └── TimeMachineIsEncrypted.swift │ ├── VSCode.swift │ ├── Zoom.swift │ └── macOS Updates │ │ ├── AutoUpdateAppCheck.swift │ │ ├── AutoUpdateCheck.swift │ │ ├── AutomaticDownloadCheck.swift │ │ ├── AutomaticInstallCheck.swift │ │ ├── SecurityUpdateCheck.swift │ │ ├── SystemUpdatesCheck.swift │ │ └── macOSVersion.swift ├── Defaults.swift ├── Extensions │ ├── Bundle.swift │ ├── ButtonStyle.swift │ ├── Color.swift │ ├── Date.swift │ ├── NSBackgroundActivityScheduler.swift │ ├── NSImage.swift │ ├── NSWorkspace.swift │ ├── NetworkHandler.swift │ ├── SSHCheck.swift │ ├── String.swift │ ├── Trim.swift │ ├── URL.swift │ └── User.swift ├── Flags.swift ├── Info.plist ├── Log.swift ├── Models │ ├── Apps.swift │ └── TimeMachineBackup.swift ├── Pareto.entitlements ├── ParetoApp.swift ├── Preview Content │ └── Preview Assets.xcassets │ │ └── Contents.json ├── StatusBar │ └── StatusBarController.swift ├── Teams.swift ├── Updater.swift ├── Utils.swift ├── Views │ ├── ClipButton.swift │ ├── DebugView.swift │ ├── Settings │ │ ├── AboutSettingsView.swift │ │ ├── ChecksSettingsView.swift │ │ ├── GeneralSettingsView.swift │ │ ├── PermissionsSettingsView.swift │ │ ├── SettingsView.swift │ │ └── TeamSettings.swift │ ├── StatusBarIcon.swift │ └── Welcome │ │ ├── ChecksView.swift │ │ ├── EndView.swift │ │ ├── IntroView.swift │ │ ├── PermissionsView.swift │ │ └── WelcomeView.swift └── setappPublicKey.pem ├── ParetoSecurityTests ├── ApplicationUpdatesTest.swift ├── CheckIntegrationTest.swift ├── ParetoAppTest.swift ├── ParetoSecurityTests.swift ├── SettingsTests.swift ├── TeamsTest.swift ├── UpdaterTest.swift └── WelcomeTest.swift ├── ParetoSecurityUITests └── ParetoSecurityUITests.swift ├── README.md ├── assets ├── Mac_128pt.png ├── Mac_128pt@2x.png ├── Mac_16pt.png ├── Mac_16pt@2x.png ├── Mac_256pt.png ├── Mac_256pt@2x.png ├── Mac_32pt.png ├── Mac_32pt@2x.png ├── Mac_512pt.png ├── Mac_512pt@2x.png ├── icon.png ├── icon.svg ├── screenshot.png └── transparent.png ├── codecov.yml ├── default.profraw ├── exportOptions.plist ├── exportOptionsDev.plist └── renovate.json /.github/ISSUE_TEMPLATE/report_bug.yml: -------------------------------------------------------------------------------- 1 | name: Bug Report 2 | description: File a bug report 3 | title: "[Bug]: " 4 | labels: [bug, triage] 5 | assignees: 6 | - dz0ny 7 | body: 8 | - type: markdown 9 | attributes: 10 | value: | 11 | Thanks for taking the time to fill out this bug report! 12 | - type: textarea 13 | id: what-happened 14 | attributes: 15 | label: What happened? 16 | description: Also tell us, what did you expect to happen? 17 | placeholder: Tell us what you see! 18 | value: "A bug happened!" 19 | validations: 20 | required: true 21 | - type: textarea 22 | id: version 23 | attributes: 24 | label: Version 25 | description: What version of our software are you running? 26 | validations: 27 | required: true 28 | - type: textarea 29 | id: logs 30 | attributes: 31 | label: Relevant log output 32 | description: Please copy and paste any relevant log output. This will be automatically formatted into code, so no need for backticks. 33 | render: shell 34 | -------------------------------------------------------------------------------- /.github/bin/profile: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | 3 | set -x 4 | 5 | report="profile.txt" 6 | snapshot=".github/fixtures/$1.txt" 7 | profile=".github/profiles/$1.sh" 8 | 9 | ls -all 10 | 11 | source $profile 12 | 13 | unzip ParetoSecurity.app.zip 14 | mv Pareto\ Security.app /Applications 15 | 16 | exit 0 17 | 18 | /Applications/Pareto\ Security.app/Contents/MacOS/Pareto\ Security -report | tee $report 19 | diff --ignore-all-space --ignore-blank-lines --side-by-side "$snapshot" "$report" 20 | -------------------------------------------------------------------------------- /.github/fixtures/bad.txt: -------------------------------------------------------------------------------- 1 | f962c423-fdf5-428a-a57a-816abc9b253e:false 2 | 3 | f962c423-fdf5-428a-a57a-816abc9b252d:false 4 | 5 | 37dee029-605b-4aab-96b9-5438e5aa44d8:true 6 | 7 | 13e4dbf1-f87f-4bd9-8a82-f62044f002f4:false 8 | 9 | ef69f752-0e89-46e2-a644-310429ae5f45:true 10 | 11 | b6aaec0f-d76c-429e-aecf-edab7f1ac400:true 12 | 13 | 0cd3ad3c-c41f-4291-82f8-c05dd23c0a9b:true 14 | 15 | 2e46c89a-5461-4865-a92e-3b799c12034a:false 16 | 17 | 2e46c89a-5461-4865-a92e-3b799c12034b:false 18 | 19 | 4ced961d-7cfc-4e7b-8f80-195f6379446e:false 20 | 21 | 05423213-50e7-4535-ac88-60cc21626378:false 22 | 23 | b96524e0-850b-4bb8-abc7-517051b6c14e:true 24 | 25 | 0cd3ad3c-c41f-4291-82f8-c05dd23c0a9a:true 26 | 27 | b96524e0-150b-4bb8-abc7-517051b6c14e:true 28 | 29 | 1fa68e99-e152-4ac1-8362-e6b7c54cc4b4:false 30 | 31 | 284162c2-f911-4b5e-8c81-30b2cf1ba73f:true 32 | 33 | dba1dea4-8c96-4b33-95f4-63d68bd0387e:false 34 | 35 | c491097c-6b8a-4cad-ae08-87c8bdfce100:false 36 | 37 | b56b3b65-b296-489d-bbf2-defc9fee8abd:false 38 | 39 | 541f82b2-db88-588f-9389-a41b81973b45:false 40 | 41 | 5c6cdb30-2e84-55b0-9d8e-754067b5094e:false 42 | 43 | 42e4fce2-b34f-5220-990a-33ba64e9ffa0:false 44 | 45 | 765b6f80-7a20-5d60-a8c4-013ea360c28e:false 46 | 47 | 4cb5ec2d-b3c5-5f72-bed1-aae0c26201b9:false 48 | 49 | ee11fe36-a372-5cba-a1b4-151748fc2fa7:false 50 | 51 | ca6c8ed7-6d22-5342-908a-f010e3eb102f:false 52 | 53 | 768a574c-75a2-536d-8785-ef9512981184:false 54 | 55 | d34ee340-67a7-5e3e-be8b-aef4e3133de0:false 56 | 57 | 1a925bda-7c49-52de-a970-a84b53ea2d7b:false 58 | 59 | 5726931a-264a-5758-b7dd-d09285ac4b7f:false 60 | 61 | 1b282abb-888b-4f82-bba4-db8ce46a8e2a:false 62 | 63 | a0fb7240-191e-4d27-84bd-175f2b61bec1:false 64 | 65 | 567eb82b-a407-5998-a620-ca8165d74852:false 66 | 67 | b621849f-c1ed-5483-ba71-a90968b1fa7b:false 68 | 69 | 9894a05b-964d-5c19-bef7-53112207d271:false 70 | 71 | 0ae675c9-1fbe-5fcc-8e4a-c0f53f4d8b4d:false 72 | 73 | 3f70f103-2cd1-50f0-a053-5eb91c891ec8:false 74 | 75 | febd20fb-3dec-5834-a0ba-58f3342df58c:false 76 | 77 | b0e0e64a-2d8c-50d1-947c-b037773827c9:false 78 | 79 | c65cb89b-6c53-54af-995c-ec8ba358ecf6:false 80 | 81 | b96524e0-850b-4bb9-abc7-517051b6c14e:true 82 | 83 | c3aee29a-f16d-4573-a861-b3ba0d860067:false 84 | 85 | b59e172e-6a2d-4309-94ed-11e8722836b3:true 86 | 87 | 355a56c1-1098-4b5d-ac8a-a1e8fc52dcfd:false 88 | 89 | bcf5196e-6757-422d-8ac3-99ebdb243afa:true 90 | -------------------------------------------------------------------------------- /.github/fixtures/default.txt: -------------------------------------------------------------------------------- 1 | f962c423-fdf5-428a-a57a-816abc9b253e:false 2 | 3 | f962c423-fdf5-428a-a57a-816abc9b252d:false 4 | 5 | 37dee029-605b-4aab-96b9-5438e5aa44d8:true 6 | 7 | 13e4dbf1-f87f-4bd9-8a82-f62044f002f4:false 8 | 9 | ef69f752-0e89-46e2-a644-310429ae5f45:true 10 | 11 | b6aaec0f-d76c-429e-aecf-edab7f1ac400:true 12 | 13 | 0cd3ad3c-c41f-4291-82f8-c05dd23c0a9b:true 14 | 15 | 2e46c89a-5461-4865-a92e-3b799c12034a:true 16 | 17 | 2e46c89a-5461-4865-a92e-3b799c12034b:true 18 | 19 | 4ced961d-7cfc-4e7b-8f80-195f6379446e:false 20 | 21 | 05423213-50e7-4535-ac88-60cc21626378:false 22 | 23 | b96524e0-850b-4bb8-abc7-517051b6c14e:true 24 | 25 | 0cd3ad3c-c41f-4291-82f8-c05dd23c0a9a:true 26 | 27 | b96524e0-150b-4bb8-abc7-517051b6c14e:true 28 | 29 | 1fa68e99-e152-4ac1-8362-e6b7c54cc4b4:true 30 | 31 | 284162c2-f911-4b5e-8c81-30b2cf1ba73f:true 32 | 33 | dba1dea4-8c96-4b33-95f4-63d68bd0387e:false 34 | 35 | c491097c-6b8a-4cad-ae08-87c8bdfce100:false 36 | 37 | b56b3b65-b296-489d-bbf2-defc9fee8abd:true 38 | 39 | 541f82b2-db88-588f-9389-a41b81973b45:false 40 | 41 | 5c6cdb30-2e84-55b0-9d8e-754067b5094e:false 42 | 43 | 42e4fce2-b34f-5220-990a-33ba64e9ffa0:false 44 | 45 | 765b6f80-7a20-5d60-a8c4-013ea360c28e:false 46 | 47 | 4cb5ec2d-b3c5-5f72-bed1-aae0c26201b9:false 48 | 49 | ee11fe36-a372-5cba-a1b4-151748fc2fa7:false 50 | 51 | ca6c8ed7-6d22-5342-908a-f010e3eb102f:false 52 | 53 | 768a574c-75a2-536d-8785-ef9512981184:false 54 | 55 | d34ee340-67a7-5e3e-be8b-aef4e3133de0:false 56 | 57 | 1a925bda-7c49-52de-a970-a84b53ea2d7b:false 58 | 59 | 5726931a-264a-5758-b7dd-d09285ac4b7f:false 60 | 61 | 1b282abb-888b-4f82-bba4-db8ce46a8e2a:false 62 | 63 | a0fb7240-191e-4d27-84bd-175f2b61bec1:false 64 | 65 | 567eb82b-a407-5998-a620-ca8165d74852:false 66 | 67 | b621849f-c1ed-5483-ba71-a90968b1fa7b:false 68 | 69 | 9894a05b-964d-5c19-bef7-53112207d271:false 70 | 71 | 0ae675c9-1fbe-5fcc-8e4a-c0f53f4d8b4d:false 72 | 73 | 3f70f103-2cd1-50f0-a053-5eb91c891ec8:false 74 | 75 | febd20fb-3dec-5834-a0ba-58f3342df58c:false 76 | 77 | b0e0e64a-2d8c-50d1-947c-b037773827c9:false 78 | 79 | c65cb89b-6c53-54af-995c-ec8ba358ecf6:false 80 | 81 | b96524e0-850b-4bb9-abc7-517051b6c14e:true 82 | 83 | c3aee29a-f16d-4573-a861-b3ba0d860067:false 84 | 85 | b59e172e-6a2d-4309-94ed-11e8722836b3:true 86 | 87 | 355a56c1-1098-4b5d-ac8a-a1e8fc52dcfd:false 88 | 89 | bcf5196e-6757-422d-8ac3-99ebdb243afa:true 90 | -------------------------------------------------------------------------------- /.github/pkg/postinstall: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | 3 | # 1. Before Installing, create a file named 'BundledActivation.txt' at the path /Library/Pareto Security/. 4 | # 2. Install Pareto Security with the installer app. 5 | # 3. The file must contain activation token in first line starting with eyJ0eXAiOiJKV1QiLCJhbGciOiJSUzUxMiJ9. 6 | 7 | if [ -f /Applications/Pareto\ Security.app/Contents/Info.plist ]; then 8 | if [ -f /Library/Pareto\ Security/BundledInvite.txt ]; then 9 | echo "Activating with team invite" 10 | /Applications/Pareto\ Security.app/Contents/MacOS/Pareto\ Security -mdmTeam "$( 2 | 3 | 4 | -------------------------------------------------------------------------------- /API.playground/timeline.xctimeline: -------------------------------------------------------------------------------- 1 | 2 | 4 | 5 | 10 | 11 | 16 | 17 | 22 | 23 | 24 | 25 | -------------------------------------------------------------------------------- /CLAUDE.md: -------------------------------------------------------------------------------- 1 | # Pareto Security Development Guide 2 | 3 | ## Build & Test Commands 4 | - Build: `make build` 5 | - Run tests: `make test` 6 | - Run single test: `NSUnbufferedIO=YES xcodebuild -project "Pareto Security.xcodeproj" -scheme "Pareto Security" -test-timeouts-enabled NO -only-testing:ParetoSecurityTests/TestClassName/testMethodName -destination platform=macOS test` 7 | - Lint: `make lint` or `mint run swiftlint .` 8 | - Format: `make fmt` or `mint run swiftformat --swiftversion 5 . && mint run swiftlint . --fix` 9 | 10 | ## Code Style 11 | - **Imports**: Group imports alphabetically, Foundation/SwiftUI first, then third-party libraries 12 | - **Naming**: Use camelCase for variables/functions, PascalCase for types; be descriptive 13 | - **Error Handling**: Use Swift's do/catch with specific error enums 14 | - **Types**: Prefer explicit typing, especially for collections 15 | - **Formatting**: Max line length 120 chars, use Swift's standard indentation (4 spaces) 16 | - **Comments**: Only add comments for complex logic; include header comment for files 17 | - **Code Organization**: Group related functionality with MARK comments 18 | - **Testing**: All new features should include tests 19 | - **Logging**: Use `os_log` for logging, with appropriate log levels 20 | 21 | This project uses SwiftLint for style enforcement and SwiftFormat for auto-formatting. -------------------------------------------------------------------------------- /CONTRIBUTING.md: -------------------------------------------------------------------------------- 1 | ## How to build 2 | - Install XCode from App store 3 | - Run ```make build``` 4 | 5 | ## Release 6 | 7 | See https://developer.apple.com/documentation/security/notarizing_macos_software_before_distribution 8 | -------------------------------------------------------------------------------- /IconOrange.imageset/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "images" : [ 3 | { 4 | "idiom" : "universal" 5 | }, 6 | { 7 | "appearances" : [ 8 | { 9 | "appearance" : "luminosity", 10 | "value" : "light" 11 | } 12 | ], 13 | "filename" : "icon_black_orange.svg", 14 | "idiom" : "universal" 15 | }, 16 | { 17 | "appearances" : [ 18 | { 19 | "appearance" : "luminosity", 20 | "value" : "dark" 21 | } 22 | ], 23 | "filename" : "icon_white_orange.svg", 24 | "idiom" : "universal" 25 | } 26 | ], 27 | "info" : { 28 | "author" : "xcode", 29 | "version" : 1 30 | }, 31 | "properties" : { 32 | "preserves-vector-representation" : true, 33 | "template-rendering-intent" : "original" 34 | } 35 | } 36 | -------------------------------------------------------------------------------- /IconOrange.imageset/icon_black_orange.svg: -------------------------------------------------------------------------------- 1 | 2 | 14 | 33 | 35 | 37 | 38 | 42 | 45 | 47 | 51 | 55 | 59 | 63 | 67 | 73 | 74 | 75 | 76 | 77 | -------------------------------------------------------------------------------- /IconOrange.imageset/icon_white_orange.svg: -------------------------------------------------------------------------------- 1 | 2 | 14 | 33 | 35 | 37 | 38 | 42 | 45 | 47 | 51 | 55 | 59 | 63 | 67 | 73 | 74 | 75 | 76 | 77 | -------------------------------------------------------------------------------- /JWT.playground/Contents.swift: -------------------------------------------------------------------------------- 1 | import CoreFoundation 2 | import Foundation 3 | import JWTDecode 4 | import Security 5 | import SwiftUI 6 | 7 | let rsaPublicKey = """ 8 | MIICIjANBgkqhkiG9w0BAQEFAAOCAg8AMIICCgKCAgEAwGh64DK49GOq1KX+ojyg 9 | Y9JSAZ4cfm5apavetQ42D2gTjfhDu1kivrDRwhjqj7huUWRI2ExMdMHp8CzrJI3P 10 | zpzutEUXTEHloe0vVMZqPoP/r2f1cl4bmDkFZyHr6XTgiYPE4GgMjxUc04J2ksqU 11 | /XbNwOVsBiuy1T2BduLYiYr1UyIx8VqEb+3tunQKlyRKF7a5LoEZatt5F/5vaMMI 12 | 4zp1yIc2PMoBdlBH4/tpJmC/PiwjBuwgp5gMIle4Hy7zwW4+rIJzF5P3Tg+Am+Lg 13 | davB8TIZDBlqIWV7zK1kWBPj364a5cnaUP90BnOriMJBh7zPG0FNGTXTiJED2qDM 14 | fajDrji3oAPO24mJsCCzSd8LIREK5c6iAf1X4UI/UFP+UhOBCsANrhNSXRpO2KyM 15 | +60JYzFpMvyhdK9zMo7Tc+KM6R0YRNmBCYK/ePAGk3WU6qxN5+OmSjdTvFrqC4JQ 16 | FyK51WJI80PKvp3B7ZB7XpH5B24wr/OhMRh5YZOcrpuBykfHaMozkDCudgaj/V+x 17 | K79CqMF/BcSxCSBktWQmabYCM164utpmJaCSpZyDtKA4bYVv9iRCGTqFQT7jX+/h 18 | Z37gmg/+TlIdTAeB5TG2ffHxLnRhT4AAhUgYmk+QP3a1hxP5xj2otaSTZ3DxQd6F 19 | ZaoGJg3y8zjrxYBQDC8gF6sCAwEAAQ== 20 | """ 21 | 22 | let pubKeyData = Data(base64Encoded: rsaPublicKey.replacingOccurrences(of: "\n", with: ""), options: Data.Base64DecodingOptions.ignoreUnknownCharacters)! as CFData 23 | 24 | var error: Unmanaged? 25 | let attributes: [String: Any] = [ 26 | kSecAttrKeyType as String: kSecAttrKeyTypeRSA, 27 | kSecAttrKeyClass as String: kSecAttrKeyClassPublic 28 | ] 29 | let publicKey = SecKeyCreateWithData(pubKeyData, attributes as CFDictionary, &error)! 30 | print(publicKey) 31 | -------------------------------------------------------------------------------- /JWT.playground/contents.xcplayground: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | -------------------------------------------------------------------------------- /JWT.playground/timeline.xctimeline: -------------------------------------------------------------------------------- 1 | 2 | 4 | 5 | 9 | 10 | 14 | 15 | 19 | 20 | 21 | 22 | -------------------------------------------------------------------------------- /Makefile: -------------------------------------------------------------------------------- 1 | SHELL := bash 2 | .ONESHELL: 3 | .SHELLFLAGS := -eu -o pipefail -c 4 | .DELETE_ON_ERROR: 5 | MAKEFLAGS += --warn-undefined-variables 6 | MAKEFLAGS += --no-builtin-rules 7 | 8 | test: 9 | @rm -rf test.xcresult 10 | NSUnbufferedIO=YES xcodebuild -project "Pareto Security.xcodeproj" -clonedSourcePackagesDirPath SourcePackages -scheme "Pareto Security" -configuration Debug -resultBundlePath test.xcresult -destination platform=macOS test 2>&1 | mint run xcbeautify --report junit 11 | mv build/reports/junit.xml . 12 | 13 | build: 14 | NSUnbufferedIO=YES xcodebuild -project "Pareto Security.xcodeproj" -clonedSourcePackagesDirPath SourcePackages -scheme "Pareto Security" -configuration Debug -destination platform=macOS build 15 | 16 | archive-debug: 17 | NSUnbufferedIO=YES xcodebuild -project "Pareto Security.xcodeproj" -clonedSourcePackagesDirPath SourcePackages -scheme "Pareto Security" -destination platform=macOS archive -archivePath app.xcarchive -configuration Debug -allowProvisioningUpdates 18 | NSUnbufferedIO=YES xcodebuild -exportArchive -archivePath app.xcarchive -exportPath Export -exportOptionsPlist exportOptionsDev.plist 19 | 20 | archive-debug-setapp: 21 | NSUnbufferedIO=YES xcodebuild -project "Pareto Security.xcodeproj" -clonedSourcePackagesDirPath SourcePackages -scheme "Pareto Security SetApp" -destination platform=macOS archive -archivePath setapp.xcarchive -configuration Debug -allowProvisioningUpdates 22 | NSUnbufferedIO=YES xcodebuild -exportArchive -archivePath setapp.xcarchive -exportPath SetAppExport -exportOptionsPlist exportOptionsDev.plist 23 | mv SetAppExport/Pareto\ Security\ SetApp.app SetAppExport/Pareto\ Security.app 24 | 25 | archive-release: 26 | NSUnbufferedIO=YES xcodebuild -project "Pareto Security.xcodeproj" -clonedSourcePackagesDirPath SourcePackages -scheme "Pareto Security" -destination platform=macOS archive -archivePath app.xcarchive -configuration Release -allowProvisioningUpdates 27 | NSUnbufferedIO=YES xcodebuild -exportArchive -archivePath app.xcarchive -exportPath Export -exportOptionsPlist exportOptions.plist 28 | 29 | 30 | archive-release-setapp: 31 | rm -rf SetAppExport 32 | NSUnbufferedIO=YES xcodebuild -project "Pareto Security.xcodeproj" -clonedSourcePackagesDirPath SourcePackages -scheme "Pareto Security SetApp" -destination platform=macOS archive -archivePath setapp.xcarchive -configuration Release -allowProvisioningUpdates 33 | NSUnbufferedIO=YES xcodebuild -exportArchive -archivePath setapp.xcarchive -exportPath SetAppExport -exportOptionsPlist exportOptions.plist 34 | mv SetAppExport/Pareto\ Security\ SetApp.app SetAppExport/Pareto\ Security.app 35 | 36 | build-release-setapp: 37 | # rm -f ParetoSecuritySetApp.app.zip 38 | # rm -rf SetAppExport/Release 39 | # mkdir -p SetAppExport/Release 40 | # cp assets/Mac_512pt@2x.png SetAppExport/Release/AppIcon.png 41 | # cp -vr SetAppExport/Pareto\ Security.app SetAppExport/Release/Pareto\ Security.app 42 | # cd SetAppExport; ditto -c -k --sequesterRsrc --keepParent Release ../ParetoSecuritySetApp.app.zip 43 | cp -f assets/Mac_512pt@2x.png AppIcon.png 44 | zip -u ParetoSecuritySetApp.app.zip AppIcon.png 45 | rm -f AppIcon.png 46 | 47 | dmg: 48 | create-dmg --overwrite Export/Pareto\ Security.app Export && mv Export/*.dmg ParetoSecurity.dmg 49 | 50 | pkg: 51 | productbuild --scripts ".github/pkg" --component Export/Pareto\ Security.app / ParetoSecurityPlain.pkg 52 | productsign --sign "Developer ID Installer: Niteo GmbH" ParetoSecurityPlain.pkg ParetoSecurity.pkg 53 | 54 | lint: 55 | mint run swiftlint . 56 | 57 | fmt: 58 | mint run swiftformat --swiftversion 5 . 59 | mint run swiftlint . --fix 60 | 61 | notarize: 62 | xcrun notarytool submit ParetoSecurity.dmg --team-id PM784W7B8X --progress --wait 63 | 64 | clean: 65 | rm -rf SourcePackages 66 | rm -rf Export 67 | rm -rf SetAppExport 68 | 69 | sentry-debug-upload: 70 | sentry-cli --auth-token ${SENTRY_AUTH_TOKEN} upload-dif app.xcarchive --org teamniteo --project pareto-mac 71 | 72 | sentry-debug-upload-setapp: 73 | sentry-cli --auth-token ${SENTRY_AUTH_TOKEN} upload-dif setapp.xcarchive --org teamniteo --project pareto-mac 74 | -------------------------------------------------------------------------------- /Mintfile: -------------------------------------------------------------------------------- 1 | nicklockwood/SwiftFormat@0.55.6 2 | realm/SwiftLint@0.59.1 3 | ChargePoint/xcparse@2.3.2 4 | tuist/xcbeautify@2.28.0 5 | -------------------------------------------------------------------------------- /Pareto Security.xcodeproj/project.xcworkspace/contents.xcworkspacedata: -------------------------------------------------------------------------------- 1 | 2 | 4 | 6 | 7 | 8 | -------------------------------------------------------------------------------- /Pareto Security.xcodeproj/project.xcworkspace/xcshareddata/IDEWorkspaceChecks.plist: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | IDEDidComputeMac32BitWarning 6 | 7 | 8 | 9 | -------------------------------------------------------------------------------- /Pareto Security.xctestplan: -------------------------------------------------------------------------------- 1 | { 2 | "configurations" : [ 3 | { 4 | "id" : "889B0DFB-1770-41A5-982E-086C58186B4C", 5 | "name" : "Configuration 1", 6 | "options" : { 7 | 8 | } 9 | } 10 | ], 11 | "defaultOptions" : { 12 | 13 | }, 14 | "testTargets" : [ 15 | { 16 | "parallelizable" : false, 17 | "target" : { 18 | "containerPath" : "container:Pareto Security.xcodeproj", 19 | "identifier" : "4FC81A4826C41DC8006EABA8", 20 | "name" : "ParetoSecurityTests" 21 | } 22 | }, 23 | { 24 | "enabled" : false, 25 | "target" : { 26 | "containerPath" : "container:Pareto Security.xcodeproj", 27 | "identifier" : "4F6A92EE26C555C6006C2F2D", 28 | "name" : "ParetoSecurityUITests" 29 | } 30 | } 31 | ], 32 | "version" : 1 33 | } 34 | -------------------------------------------------------------------------------- /Pareto/Assets.xcassets/AccentColor.colorset/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "colors" : [ 3 | { 4 | "idiom" : "universal" 5 | } 6 | ], 7 | "info" : { 8 | "author" : "xcode", 9 | "version" : 1 10 | } 11 | } 12 | -------------------------------------------------------------------------------- /Pareto/Assets.xcassets/AppIcon.appiconset/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "images" : [ 3 | { 4 | "filename" : "icon_16x16.png", 5 | "idiom" : "mac", 6 | "scale" : "1x", 7 | "size" : "16x16" 8 | }, 9 | { 10 | "filename" : "icon_16x16@2x.png", 11 | "idiom" : "mac", 12 | "scale" : "2x", 13 | "size" : "16x16" 14 | }, 15 | { 16 | "filename" : "icon_32x32.png", 17 | "idiom" : "mac", 18 | "scale" : "1x", 19 | "size" : "32x32" 20 | }, 21 | { 22 | "filename" : "icon_32x32@2x.png", 23 | "idiom" : "mac", 24 | "scale" : "2x", 25 | "size" : "32x32" 26 | }, 27 | { 28 | "filename" : "icon_128x128.png", 29 | "idiom" : "mac", 30 | "scale" : "1x", 31 | "size" : "128x128" 32 | }, 33 | { 34 | "filename" : "icon_128x128@2x.png", 35 | "idiom" : "mac", 36 | "scale" : "2x", 37 | "size" : "128x128" 38 | }, 39 | { 40 | "filename" : "icon_256x256.png", 41 | "idiom" : "mac", 42 | "scale" : "1x", 43 | "size" : "256x256" 44 | }, 45 | { 46 | "filename" : "icon_256x256@2x.png", 47 | "idiom" : "mac", 48 | "scale" : "2x", 49 | "size" : "256x256" 50 | }, 51 | { 52 | "filename" : "icon_512x512.png", 53 | "idiom" : "mac", 54 | "scale" : "1x", 55 | "size" : "512x512" 56 | }, 57 | { 58 | "filename" : "icon_512x512@2x.png", 59 | "idiom" : "mac", 60 | "scale" : "2x", 61 | "size" : "512x512" 62 | } 63 | ], 64 | "info" : { 65 | "author" : "xcode", 66 | "version" : 1 67 | } 68 | } 69 | -------------------------------------------------------------------------------- /Pareto/Assets.xcassets/AppIcon.appiconset/icon_128x128.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ParetoSecurity/pareto-mac/2420d35209b02c90a26bd415b31f1f9beea72378/Pareto/Assets.xcassets/AppIcon.appiconset/icon_128x128.png -------------------------------------------------------------------------------- /Pareto/Assets.xcassets/AppIcon.appiconset/icon_128x128@2x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ParetoSecurity/pareto-mac/2420d35209b02c90a26bd415b31f1f9beea72378/Pareto/Assets.xcassets/AppIcon.appiconset/icon_128x128@2x.png -------------------------------------------------------------------------------- /Pareto/Assets.xcassets/AppIcon.appiconset/icon_16x16.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ParetoSecurity/pareto-mac/2420d35209b02c90a26bd415b31f1f9beea72378/Pareto/Assets.xcassets/AppIcon.appiconset/icon_16x16.png -------------------------------------------------------------------------------- /Pareto/Assets.xcassets/AppIcon.appiconset/icon_16x16@2x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ParetoSecurity/pareto-mac/2420d35209b02c90a26bd415b31f1f9beea72378/Pareto/Assets.xcassets/AppIcon.appiconset/icon_16x16@2x.png -------------------------------------------------------------------------------- /Pareto/Assets.xcassets/AppIcon.appiconset/icon_256x256.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ParetoSecurity/pareto-mac/2420d35209b02c90a26bd415b31f1f9beea72378/Pareto/Assets.xcassets/AppIcon.appiconset/icon_256x256.png -------------------------------------------------------------------------------- /Pareto/Assets.xcassets/AppIcon.appiconset/icon_256x256@2x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ParetoSecurity/pareto-mac/2420d35209b02c90a26bd415b31f1f9beea72378/Pareto/Assets.xcassets/AppIcon.appiconset/icon_256x256@2x.png -------------------------------------------------------------------------------- /Pareto/Assets.xcassets/AppIcon.appiconset/icon_32x32.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ParetoSecurity/pareto-mac/2420d35209b02c90a26bd415b31f1f9beea72378/Pareto/Assets.xcassets/AppIcon.appiconset/icon_32x32.png -------------------------------------------------------------------------------- /Pareto/Assets.xcassets/AppIcon.appiconset/icon_32x32@2x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ParetoSecurity/pareto-mac/2420d35209b02c90a26bd415b31f1f9beea72378/Pareto/Assets.xcassets/AppIcon.appiconset/icon_32x32@2x.png -------------------------------------------------------------------------------- /Pareto/Assets.xcassets/AppIcon.appiconset/icon_512x512.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ParetoSecurity/pareto-mac/2420d35209b02c90a26bd415b31f1f9beea72378/Pareto/Assets.xcassets/AppIcon.appiconset/icon_512x512.png -------------------------------------------------------------------------------- /Pareto/Assets.xcassets/AppIcon.appiconset/icon_512x512@2x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ParetoSecurity/pareto-mac/2420d35209b02c90a26bd415b31f1f9beea72378/Pareto/Assets.xcassets/AppIcon.appiconset/icon_512x512@2x.png -------------------------------------------------------------------------------- /Pareto/Assets.xcassets/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "info" : { 3 | "author" : "xcode", 4 | "version" : 1 5 | } 6 | } 7 | -------------------------------------------------------------------------------- /Pareto/Assets.xcassets/Icon.imageset/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "images" : [ 3 | { 4 | "filename" : "icon.png", 5 | "idiom" : "universal", 6 | "scale" : "1x" 7 | }, 8 | { 9 | "idiom" : "universal", 10 | "scale" : "2x" 11 | }, 12 | { 13 | "idiom" : "universal", 14 | "scale" : "3x" 15 | } 16 | ], 17 | "info" : { 18 | "author" : "xcode", 19 | "version" : 1 20 | } 21 | } 22 | -------------------------------------------------------------------------------- /Pareto/Assets.xcassets/Icon.imageset/icon.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ParetoSecurity/pareto-mac/2420d35209b02c90a26bd415b31f1f9beea72378/Pareto/Assets.xcassets/Icon.imageset/icon.png -------------------------------------------------------------------------------- /Pareto/Assets.xcassets/IconGray.imageset/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "images" : [ 3 | { 4 | "idiom" : "universal" 5 | }, 6 | { 7 | "appearances" : [ 8 | { 9 | "appearance" : "luminosity", 10 | "value" : "light" 11 | } 12 | ], 13 | "filename" : "icon_black.svg", 14 | "idiom" : "universal" 15 | }, 16 | { 17 | "appearances" : [ 18 | { 19 | "appearance" : "luminosity", 20 | "value" : "dark" 21 | } 22 | ], 23 | "filename" : "icon_white.svg", 24 | "idiom" : "universal" 25 | } 26 | ], 27 | "info" : { 28 | "author" : "xcode", 29 | "version" : 1 30 | }, 31 | "properties" : { 32 | "preserves-vector-representation" : true, 33 | "template-rendering-intent" : "original" 34 | } 35 | } 36 | -------------------------------------------------------------------------------- /Pareto/Assets.xcassets/IconGray.imageset/icon_black.svg: -------------------------------------------------------------------------------- 1 | 2 | 14 | 32 | 34 | 36 | 37 | 41 | 44 | 46 | 50 | 54 | 58 | 62 | 66 | 67 | 68 | 69 | 70 | -------------------------------------------------------------------------------- /Pareto/Assets.xcassets/IconGray.imageset/icon_white.svg: -------------------------------------------------------------------------------- 1 | 2 | 14 | 33 | 35 | 37 | 38 | 44 | 47 | 49 | 53 | 57 | 61 | 65 | 69 | 70 | 71 | 72 | 73 | -------------------------------------------------------------------------------- /Pareto/Assets.xcassets/IconGrayActive.imageset/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "images" : [ 3 | { 4 | "idiom" : "universal" 5 | }, 6 | { 7 | "appearances" : [ 8 | { 9 | "appearance" : "luminosity", 10 | "value" : "light" 11 | } 12 | ], 13 | "filename" : "icon_black_gray.svg", 14 | "idiom" : "universal" 15 | }, 16 | { 17 | "appearances" : [ 18 | { 19 | "appearance" : "luminosity", 20 | "value" : "dark" 21 | } 22 | ], 23 | "filename" : "icon_white_gray.svg", 24 | "idiom" : "universal" 25 | } 26 | ], 27 | "info" : { 28 | "author" : "xcode", 29 | "version" : 1 30 | }, 31 | "properties" : { 32 | "preserves-vector-representation" : true, 33 | "template-rendering-intent" : "original" 34 | } 35 | } 36 | -------------------------------------------------------------------------------- /Pareto/Assets.xcassets/IconGrayActive.imageset/icon_black_gray.svg: -------------------------------------------------------------------------------- 1 | 2 | 14 | 33 | 35 | 37 | 38 | 42 | 45 | 47 | 51 | 55 | 59 | 63 | 67 | 74 | 75 | 76 | 77 | 78 | -------------------------------------------------------------------------------- /Pareto/Assets.xcassets/IconGreen.imageset/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "images" : [ 3 | { 4 | "idiom" : "universal" 5 | }, 6 | { 7 | "appearances" : [ 8 | { 9 | "appearance" : "luminosity", 10 | "value" : "light" 11 | } 12 | ], 13 | "filename" : "icon_black_green.svg", 14 | "idiom" : "universal" 15 | }, 16 | { 17 | "appearances" : [ 18 | { 19 | "appearance" : "luminosity", 20 | "value" : "dark" 21 | } 22 | ], 23 | "filename" : "icon_white_green.svg", 24 | "idiom" : "universal" 25 | } 26 | ], 27 | "info" : { 28 | "author" : "xcode", 29 | "version" : 1 30 | }, 31 | "properties" : { 32 | "preserves-vector-representation" : true, 33 | "template-rendering-intent" : "original" 34 | } 35 | } 36 | -------------------------------------------------------------------------------- /Pareto/Assets.xcassets/IconOrange.imageset/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "images" : [ 3 | { 4 | "idiom" : "universal" 5 | }, 6 | { 7 | "appearances" : [ 8 | { 9 | "appearance" : "luminosity", 10 | "value" : "light" 11 | } 12 | ], 13 | "filename" : "icon_black_orange.svg", 14 | "idiom" : "universal" 15 | }, 16 | { 17 | "appearances" : [ 18 | { 19 | "appearance" : "luminosity", 20 | "value" : "dark" 21 | } 22 | ], 23 | "filename" : "icon_white_orange.svg", 24 | "idiom" : "universal" 25 | } 26 | ], 27 | "info" : { 28 | "author" : "xcode", 29 | "version" : 1 30 | }, 31 | "properties" : { 32 | "preserves-vector-representation" : true, 33 | "template-rendering-intent" : "original" 34 | } 35 | } 36 | -------------------------------------------------------------------------------- /Pareto/Assets.xcassets/IconOrange.imageset/icon_black_orange.svg: -------------------------------------------------------------------------------- 1 | 2 | 14 | 33 | 35 | 37 | 38 | 42 | 45 | 47 | 51 | 55 | 59 | 63 | 67 | 73 | 74 | 75 | 76 | 77 | -------------------------------------------------------------------------------- /Pareto/Assets.xcassets/IconOrange.imageset/icon_white_orange.svg: -------------------------------------------------------------------------------- 1 | 2 | 14 | 33 | 35 | 37 | 38 | 42 | 45 | 47 | 51 | 55 | 59 | 63 | 67 | 73 | 74 | 75 | 76 | 77 | -------------------------------------------------------------------------------- /Pareto/Checks/Access Security/Autologin.swift: -------------------------------------------------------------------------------- 1 | // 2 | // Autologin.swift 3 | // Pareto Security 4 | // 5 | // Created by Janez Troha on 19/07/2021. 6 | // 7 | 8 | class AutologinCheck: ParetoCheck { 9 | static let sharedInstance = AutologinCheck() 10 | override var UUID: String { 11 | "f962c423-fdf5-428a-a57a-816abc9b253e" 12 | } 13 | 14 | override var TitleON: String { 15 | "Automatic login is off" 16 | } 17 | 18 | override var TitleOFF: String { 19 | "Automatic login is on" 20 | } 21 | 22 | override public var showSettingsWarnEvents: Bool { 23 | return true 24 | } 25 | 26 | override func checkPasses() -> Bool { 27 | // possible:{require password to wake:false, class:security preferences object, secure virtual memory:false, require password to unlock:false, automatic login:false, log out when inactive:false, log out when inactive interval:60} 28 | let script = "tell application \"System Events\" to tell security preferences to get automatic login" 29 | let out = runOSA(appleScript: script) ?? "false" 30 | return out.contains("false") 31 | } 32 | } 33 | -------------------------------------------------------------------------------- /Pareto/Checks/Access Security/NoAdminUser.swift: -------------------------------------------------------------------------------- 1 | // 2 | // NoAdminUser.swift 3 | // Pareto Security 4 | // 5 | // Created by Janez Troha on 19/07/2021. 6 | // 7 | 8 | class NoAdminUser: ParetoCheck { 9 | static let sharedInstance = NoAdminUser() 10 | override var UUID: String { 11 | "b092ab27-c513-43cc-8b52-e89c4cb30114" 12 | } 13 | 14 | override var TitleON: String { 15 | "Current user is not admin" 16 | } 17 | 18 | override var TitleOFF: String { 19 | "Current user is admin" 20 | } 21 | 22 | var isAdmin: Bool { 23 | return runCMD(app: "/usr/bin/id", args: ["-Gn"]).components(separatedBy: " ").contains("admin") 24 | } 25 | 26 | override func checkPasses() -> Bool { 27 | return !isAdmin 28 | } 29 | } 30 | -------------------------------------------------------------------------------- /Pareto/Checks/Access Security/NoUnusedUsers.swift: -------------------------------------------------------------------------------- 1 | // 2 | // NoUnusedUsers.swift 3 | // Pareto Security 4 | // 5 | // Created by Janez Troha on 15/07/2021. 6 | // 7 | 8 | import Foundation 9 | 10 | class NoUnusedUsers: ParetoCheck { 11 | static let sharedInstance = NoUnusedUsers() 12 | override var UUID: String { 13 | "c6559a48-c7ad-450b-a9eb-765f031ef49e" 14 | } 15 | 16 | override var TitleON: String { 17 | "No unused user accounts are present" 18 | } 19 | 20 | override var TitleOFF: String { 21 | "Unused user accounts are present" 22 | } 23 | 24 | var accounts: [String] { 25 | let adminUsers = runCMD(app: "/usr/bin/dscl", args: [".", "-read", "/Groups/admin", "GroupMembership"]).replacingAllMatches(of: "\n", with: "").components(separatedBy: " ") 26 | let output = runCMD(app: "/usr/bin/dscl", args: [".", "-list", "/Users"]).components(separatedBy: "\n") 27 | let local = output.filter { u in 28 | !u.hasPrefix("_") && u.count > 1 && u != "root" && u != "nobody" && u != "daemon" 29 | } 30 | let users = local.filter { u in 31 | !adminUsers.contains(u) 32 | } 33 | return users 34 | } 35 | 36 | override var details: String { 37 | let accounts = self.accounts 38 | if accounts.isEmpty { 39 | return "None" 40 | } 41 | return accounts.map { "- \($0)" }.joined(separator: "\n") 42 | } 43 | 44 | var isAdmin: Bool { 45 | return runCMD(app: "/usr/bin/id", args: ["-Gn"]).components(separatedBy: " ").contains("admin") 46 | } 47 | 48 | func lastLoginRecent(user: String) -> Bool { 49 | let output = runCMD(app: "/usr/bin/last", args: ["-w", "-y", user]).components(separatedBy: "\n") 50 | let log = output.filter { u in 51 | u.contains(user) 52 | } 53 | 54 | let entry = log.first?.components(separatedBy: " ").filter { i in 55 | i.count > 1 56 | } 57 | if (log.first?.contains("still logged in")) != nil { 58 | return true 59 | } 60 | // parse string to date 61 | if entry?.count ?? 0 > 1 { 62 | let dateFormatter = DateFormatter() 63 | dateFormatter.locale = Locale(identifier: "en_US_POSIX") // Use a POSIX locale 64 | dateFormatter.dateFormat = "EEE MMM d yyyy HH:mm" 65 | 66 | if let date = dateFormatter.date(from: entry![2]) { 67 | let currentDate = Date() 68 | let calendar = Calendar.current 69 | 70 | let components = calendar.dateComponents([.month], from: date, to: currentDate) 71 | 72 | if let monthDifference = components.month, monthDifference <= 1 { 73 | return true 74 | } else { 75 | return false 76 | } 77 | } 78 | } 79 | 80 | return false 81 | } 82 | 83 | override func checkPasses() -> Bool { 84 | if !isAdmin { 85 | return accounts.count == 1 86 | } 87 | return accounts.allSatisfy { u in 88 | lastLoginRecent(user: u) 89 | } 90 | } 91 | 92 | override public var isRunnable: Bool { 93 | isActive && isAdmin 94 | } 95 | } 96 | -------------------------------------------------------------------------------- /Pareto/Checks/Access Security/PasswordAfterSleep.swift: -------------------------------------------------------------------------------- 1 | // 2 | // Screensaver.swift 3 | // Pareto Security 4 | // 5 | // Created by Janez Troha on 19/07/2021. 6 | // 7 | import Foundation 8 | 9 | class PasswordAfterSleepCheck: ParetoCheck { 10 | static let sharedInstance = PasswordAfterSleepCheck() 11 | override var UUID: String { 12 | "37dee029-605b-4aab-96b9-5438e5aa44d8" 13 | } 14 | 15 | override var TitleON: String { 16 | "Password after sleep or screensaver is on" 17 | } 18 | 19 | override var TitleOFF: String { 20 | "Password after sleep or screensaver is off" 21 | } 22 | 23 | override var CIS: String { 24 | "1-5.8" 25 | } 26 | 27 | @objc func getGracePeriod() -> Int64 { 28 | // credit Victor Vrantchan 29 | let handle = dlopen( 30 | "/System/Library/PrivateFrameworks/MobileKeyBag.framework/Versions/Current/MobileKeyBag", 31 | RTLD_LAZY 32 | ) 33 | let symbol = dlsym(handle, "MKBDeviceGetGracePeriod") 34 | typealias GetterFunction = @convention(c) (Any?) -> NSDictionary 35 | let MKBDeviceGetGracePeriod = unsafeBitCast(symbol, to: GetterFunction.self) 36 | let x = MKBDeviceGetGracePeriod([:]) 37 | return x["GracePeriod"] as? Int64 ?? 0 38 | } 39 | 40 | override func checkPasses() -> Bool { 41 | let out = getGracePeriod() 42 | // when disabled 2147483647 == max 32bit integer 43 | return out >= 0 && out <= 60 * 15 44 | } 45 | } 46 | -------------------------------------------------------------------------------- /Pareto/Checks/Access Security/PasswordManager.swift: -------------------------------------------------------------------------------- 1 | import AppKit 2 | import Foundation 3 | import os.log 4 | 5 | class PasswordManager: ParetoCheck { 6 | static let sharedInstance = PasswordManager() 7 | 8 | override var UUID: String { 9 | "f962c423-fdf5-428a-a57a-827abc9b253e" 10 | } 11 | 12 | override var TitleON: String { 13 | "Password manager is installed" 14 | } 15 | 16 | override var TitleOFF: String { 17 | "Password manager is not installed" 18 | } 19 | 20 | override func checkPasses() -> Bool { 21 | return checkInstalledApplications() || checkForBrowserExtensions() 22 | } 23 | 24 | private func checkInstalledApplications() -> Bool { 25 | let passwordManagers = [ 26 | "1Password.app", 27 | "1Password 8.app", 28 | "1Password 7.app", 29 | "Bitwarden.app", 30 | "Dashlane.app", 31 | "KeePassXC.app", 32 | "KeePassX.app", 33 | "KeePassium.app" 34 | ] 35 | 36 | let searchPaths = [ 37 | "/Applications", 38 | "/System/Applications", 39 | "/Applications/Setapp", 40 | "\(NSHomeDirectory())/Applications" 41 | ] 42 | 43 | for path in searchPaths { 44 | let appDirectory = URL(fileURLWithPath: path) 45 | if let contents = try? FileManager.default.contentsOfDirectory(at: appDirectory, includingPropertiesForKeys: nil) { 46 | for app in contents { 47 | if passwordManagers.contains(app.lastPathComponent) { 48 | os_log(.info, "Found %{public}@", app.lastPathComponent) 49 | return true 50 | } 51 | } 52 | } 53 | } 54 | return false 55 | } 56 | 57 | private func checkForBrowserExtensions() -> Bool { 58 | let browserExtensions = [ 59 | "hdokiejnpimakedhajhdlcegeplioahd", // LastPass 60 | "ghmbeldphafepmbegfdlkpapadhbakde", // ProtonPass 61 | "eiaeiblijfjekdanodkjadfinkhbfgcd", // nordpass 62 | "nngceckbapebfimnlniiiahkandclblb", // bitwarden 63 | "aeblfdkhhhdcdjpifhhbdiojplfjncoa", // 1password 64 | "fdjamakpfbbddfjaooikfcpapjohcfmg" // dashlane 65 | ] 66 | 67 | let browsers = [ 68 | "Google/Chrome/Default/", 69 | "BraveSoftware/Brave-Browser/Default/", 70 | "Microsoft Edge/Default/", 71 | "Arc/User Data/Default/" 72 | ] 73 | 74 | for browser in browsers { 75 | if let appSupportDirectory = FileManager.default.urls(for: .applicationSupportDirectory, in: .userDomainMask).first { 76 | let extensionsPath = appSupportDirectory.appendingPathComponent(browser).appendingPathComponent("Extensions").path 77 | for ext in browserExtensions { 78 | if FileManager.default.fileExists(atPath: extensionsPath + "/" + ext) { 79 | os_log(.info, "Found %{public}@ %{public}@", browser, ext) 80 | return true 81 | } 82 | } 83 | } 84 | } 85 | 86 | return false 87 | } 88 | } 89 | -------------------------------------------------------------------------------- /Pareto/Checks/Access Security/PasswordtoUnlock.swift: -------------------------------------------------------------------------------- 1 | // 2 | // PasswordtoUnlock.swift 3 | // PasswordtoUnlock.swift 4 | // 5 | // Created by Janez Troha on 17/08/2021. 6 | // 7 | 8 | class RequirePasswordToUnlock: ParetoCheck { 9 | static let sharedInstance = RequirePasswordToUnlock() 10 | override var UUID: String { 11 | "f962c423-fdf5-428a-a57a-816abc9b252d" 12 | } 13 | 14 | override var TitleON: String { 15 | "Password to unlock preferences" 16 | } 17 | 18 | override var TitleOFF: String { 19 | "No password to unlock preferences" 20 | } 21 | 22 | override public var showSettingsWarnEvents: Bool { 23 | return true 24 | } 25 | 26 | override func checkPasses() -> Bool { 27 | // possible:{require password to wake:false, class:security preferences object, secure virtual memory:false, require password to unlock:false, automatic login:false, log out when inactive:false, log out when inactive interval:60} 28 | let script = "tell application \"System Events\" to tell security preferences to get require password to unlock" 29 | let out = runOSA(appleScript: script) ?? "false" 30 | return out.contains("true") 31 | } 32 | } 33 | -------------------------------------------------------------------------------- /Pareto/Checks/Access Security/SSHKeys.swift: -------------------------------------------------------------------------------- 1 | // 2 | // Gatekeeper.swift 3 | // Pareto Security 4 | // 5 | // Created by Janez Troha on 15/07/2021. 6 | // 7 | import Foundation 8 | import os.log 9 | 10 | class SSHKeysCheck: SSHCheck { 11 | static let sharedInstance = SSHKeysCheck() 12 | 13 | override var UUID: String { 14 | "ef69f752-0e89-46e2-a644-310429ae5f45" 15 | } 16 | 17 | override var TitleON: String { 18 | "SSH keys require a password" 19 | } 20 | 21 | override var TitleOFF: String { 22 | return "SSH key is missing a password" 23 | } 24 | 25 | override var details: String { 26 | let keys = keysWithNoPassword() 27 | if keys.isEmpty { 28 | return "None" 29 | } 30 | return keys.map { "- \($0)" }.joined(separator: "\n") 31 | } 32 | 33 | func isPasswordEnabled(withKey path: String) -> Bool { 34 | let output = runCMD(app: getSSHKeygenPath(), args: ["-P", "''", "-y", "-f", path]) 35 | return output.contains("incorrect passphrase supplied") 36 | } 37 | 38 | func keysWithNoPassword() -> [String] { 39 | var keys: [String] = [] 40 | do { 41 | let files = try FileManager.default.contentsOfDirectory(at: sshPath, includingPropertiesForKeys: nil).filter { $0.pathExtension == "pub" } 42 | for pub in files { 43 | let privateKey = pub.path.replacingOccurrences(of: ".pub", with: "") 44 | if !itExists(privateKey) { 45 | continue 46 | } 47 | if !isPasswordEnabled(withKey: privateKey) { 48 | os_log("Checking %{public}s", log: Log.check, pub.absoluteURL.path) 49 | keys.append(privateKey) 50 | } 51 | } 52 | } catch { 53 | os_log("Failed to check SSH keys %{public}s", error.localizedDescription) 54 | } 55 | return keys 56 | } 57 | 58 | override func checkPasses() -> Bool { 59 | keysWithNoPassword().isEmpty 60 | } 61 | } 62 | -------------------------------------------------------------------------------- /Pareto/Checks/Access Security/Screensaver.swift: -------------------------------------------------------------------------------- 1 | // 2 | // Screensaver.swift 3 | // Pareto Security 4 | // 5 | // Created by Janez Troha on 19/07/2021. 6 | // 7 | 8 | class ScreensaverCheck: ParetoCheck { 9 | static let sharedInstance = ScreensaverCheck() 10 | override var UUID: String { 11 | "13e4dbf1-f87f-4bd9-8a82-f62044f002f4" 12 | } 13 | 14 | override var TitleON: String { 15 | "Screensaver or screen lock shows in under 20min" 16 | } 17 | 18 | override var TitleOFF: String { 19 | "Screensaver or screen lock shows in more than 20min" 20 | } 21 | 22 | override public var showSettingsWarnEvents: Bool { 23 | return true 24 | } 25 | 26 | func checkScreensaver() -> Bool { 27 | let script = "tell application \"System Events\" to tell screen saver preferences to get delay interval" 28 | let out = Int(runOSA(appleScript: script)?.trim() ?? "0") ?? 0 29 | return out >= 0 && out <= 60 * 20 30 | } 31 | 32 | // https://github.com/usnistgov/macos_security/blob/e22bb0bc02290c54cb968bc3749942fa37ad752b/rules/os/os_screensaver_timeout_loginwindow_enforce.yaml#L4 33 | func checkLock() -> Bool { 34 | let script = """ 35 | function run() { 36 | let timeout = ObjC.unwrap($.NSUserDefaults.alloc.initWithSuiteName('com.apple.screensaver')\ 37 | .objectForKey('loginWindowIdleTime')) 38 | return timeout; 39 | } 40 | """ 41 | let val = runOSAJS(appleScript: script) 42 | let out = Int(val?.trim() ?? "0") ?? 0 43 | return out >= 0 && out <= 60 * 20 44 | } 45 | 46 | override func checkPasses() -> Bool { 47 | return checkScreensaver() && checkLock() 48 | } 49 | } 50 | -------------------------------------------------------------------------------- /Pareto/Checks/Application Updates/1Password7.swift: -------------------------------------------------------------------------------- 1 | // 2 | // 1Password7.swift 3 | // Pareto Security 4 | // 5 | // Created by Janez Troha on 11/11/2021. 6 | // 7 | 8 | class App1Password7Check: AppCheck { 9 | static let sharedInstance = App1Password7Check() 10 | 11 | override var appName: String { "1Password 7" } 12 | override var appMarketingName: String { "1Password 7" } 13 | override var appBundle: String { "com.agilebits.onepassword7" } 14 | 15 | override var UUID: String { 16 | "541f82b2-db88-588f-9389-a41b81973b45" 17 | } 18 | } 19 | -------------------------------------------------------------------------------- /Pareto/Checks/Application Updates/1Password8.swift: -------------------------------------------------------------------------------- 1 | // 2 | // 1Password8.swift 3 | // Pareto Security 4 | // 5 | // Created by Janez Troha on 11/11/2022. 6 | // 7 | import Alamofire 8 | import os.log 9 | import Regex 10 | 11 | class App1Password8Check: AppCheck { 12 | static let sharedInstance = App1Password8Check() 13 | 14 | override var appName: String { "1Password" } 15 | override var appMarketingName: String { "1Password" } 16 | override var appBundle: String { "com.1password.1password" } 17 | 18 | override var UUID: String { 19 | "0a076668-f8c6-4d53-b275-6806afcddca8" 20 | } 21 | 22 | override func getLatestVersion(completion: @escaping (String) -> Void) { 23 | let url = "https://releases.1password.com/mac/8.9/" 24 | let versionRegex = Regex("([\\d\\.]+)") 25 | os_log("Requesting %{public}s", url) 26 | AF.request(url).responseString(queue: AppCheck.queue, completionHandler: { response in 27 | if response.data != nil { 28 | let result = versionRegex.allMatches(in: response.value ?? "8.9.1") 29 | 30 | completion(result.last?.groups.first?.value ?? "0.0.0") 31 | } else { 32 | os_log("%{public}s failed: %{public}s", self.appBundle, response.error.debugDescription) 33 | self.hasError = true 34 | completion("0.0.0") 35 | } 36 | 37 | }) 38 | } 39 | } 40 | -------------------------------------------------------------------------------- /Pareto/Checks/Application Updates/AdobeReader.swift: -------------------------------------------------------------------------------- 1 | // 2 | // AdobeReader.swift 3 | // Pareto Security 4 | // 5 | // Created by Janez Troha on 11/11/2021. 6 | // 7 | 8 | import Alamofire 9 | import Foundation 10 | import os.log 11 | import OSLog 12 | import Regex 13 | import Version 14 | 15 | class AdobeReaderCheck: AppCheck { 16 | static let sharedInstance = AdobeReaderCheck() 17 | 18 | override var appName: String { "Adobe Acrobat Reader DC" } 19 | override var appMarketingName: String { "Adobe Reader" } 20 | override var appBundle: String { "com.adobe.Reader" } 21 | 22 | override var UUID: String { 23 | "5c6cdb30-2e84-55b0-9d8e-754067b5094e" 24 | } 25 | 26 | override func getLatestVersion(completion: @escaping (String) -> Void) { 27 | let url = viaEdgeCache("https://helpx.adobe.com/acrobat/release-note/release-notes-acrobat-reader.html") 28 | let linksRegex = Regex("(.+)") 29 | let versionRegex = Regex(".+\\((.+)\\)") 30 | os_log("Requesting %{public}s", url) 31 | AF.request(url).responseString(queue: AppCheck.queue, completionHandler: { response in 32 | if response.data != nil { 33 | let links = linksRegex.firstMatch(in: response.value ?? "") 34 | let result = versionRegex.firstMatch(in: links?.groups.first?.value ?? "DC Sep 2021 (21.007.2009x)") 35 | completion(result?.groups.first?.value ?? "0.0.0") 36 | } else { 37 | os_log("%{public}s failed: %{public}s", self.appBundle, response.error.debugDescription) 38 | self.hasError = true 39 | completion("0.0.0") 40 | } 41 | 42 | }) 43 | } 44 | } 45 | -------------------------------------------------------------------------------- /Pareto/Checks/Application Updates/AppUpdateCheck.tmpl: -------------------------------------------------------------------------------- 1 | // 2 | // $safename.swift 3 | // Pareto Security 4 | // 5 | // Created by Janez Troha on 11/11/2021. 6 | // 7 | 8 | class $safenameclass: AppCheck { 9 | 10 | static let sharedInstance = $safenameclass() 11 | 12 | override var appName: String { "$app" } 13 | override var appMarketingName: String { "$app_name" } 14 | override var appBundle: String { "$bundle" } 15 | override var sparkleURL: String { "$SUFeedURL" } 16 | 17 | override var UUID: String { 18 | "$uuid" 19 | } 20 | 21 | override var TitleON: String { 22 | "$app is up-to-date" 23 | } 24 | 25 | override var TitleOFF: String { 26 | "$app has an available update" 27 | } 28 | 29 | } -------------------------------------------------------------------------------- /Pareto/Checks/Application Updates/Bitwarden.swift: -------------------------------------------------------------------------------- 1 | // 2 | // Bitwarden.swift 3 | // Pareto Security 4 | // 5 | // Created by Janez Troha on 11/11/2021. 6 | // 7 | 8 | class AppBitwardenCheck: AppCheck { 9 | static let sharedInstance = AppBitwardenCheck() 10 | 11 | override var appName: String { "Bitwarden" } 12 | override var appMarketingName: String { "Bitwarden" } 13 | override var appBundle: String { "com.bitwarden.desktop" } 14 | 15 | override var UUID: String { 16 | "42e4fce2-b34f-5220-990a-33ba64e9ffa0" 17 | } 18 | } 19 | -------------------------------------------------------------------------------- /Pareto/Checks/Application Updates/Brave Browser.swift: -------------------------------------------------------------------------------- 1 | // 2 | // Brave Browser.swift 3 | // Pareto Security 4 | // 5 | // Created by Janez Troha on 11/11/2021. 6 | // 7 | import Version 8 | 9 | class AppBraveBrowserCheck: AppCheck { 10 | static let sharedInstance = AppBraveBrowserCheck() 11 | 12 | override var appName: String { "Brave Browser" } 13 | override var appMarketingName: String { "Brave Browser" } 14 | override var appBundle: String { "com.brave.Browser" } 15 | override var sparkleURL: String { viaEdgeCache("https://updates.bravesoftware.com/sparkle/Brave-Browser/stable/appcast.xml") } 16 | 17 | override var UUID: String { 18 | "64026bd2-54c2-4d3e-8696-559091457dde" 19 | } 20 | 21 | // Special treatment follows 22 | // Use build number as definite version comparator 23 | // https://www.chromium.org/developers/version-numbers 24 | 25 | override var currentVersion: Version { 26 | if applicationPath == nil { 27 | return Version(0, 0, 0) 28 | } 29 | let v = appVersion(path: applicationPath!, key: "CFBundleVersion")! 30 | 31 | return Version("\(v).0") ?? Version(0, 0, 0) 32 | } 33 | } 34 | -------------------------------------------------------------------------------- /Pareto/Checks/Application Updates/Cyberduck.swift: -------------------------------------------------------------------------------- 1 | // 2 | // Cyberduck.swift 3 | // Pareto Security 4 | // 5 | // Created by Janez Troha on 11/11/2021. 6 | // 7 | 8 | class AppCyberduckCheck: AppCheck { 9 | static let sharedInstance = AppCyberduckCheck() 10 | 11 | override var appName: String { "Cyberduck" } 12 | override var appMarketingName: String { "Cyberduck" } 13 | override var appBundle: String { "ch.sudo.cyberduck" } 14 | override var sparkleURL: String { "https://version.cyberduck.io/changelog.rss" } 15 | 16 | override var UUID: String { 17 | "765b6f80-7a20-5d60-a8c4-013ea360c28e" 18 | } 19 | } 20 | -------------------------------------------------------------------------------- /Pareto/Checks/Application Updates/Dashlane.swift: -------------------------------------------------------------------------------- 1 | // 2 | // Dashlane.swift 3 | // Pareto Security 4 | // 5 | // Created by Janez Troha on 11/11/2021. 6 | // 7 | 8 | class AppDashlaneCheck: AppCheck { 9 | static let sharedInstance = AppDashlaneCheck() 10 | 11 | override var appName: String { "Dashlane" } 12 | override var appMarketingName: String { "Dashlane" } 13 | override var appBundle: String { "com.dashlane.dashlanephonefinal" } 14 | 15 | override var UUID: String { 16 | "4cb5ec2d-b3c5-5f72-bed1-aae0c26201b9" 17 | } 18 | } 19 | -------------------------------------------------------------------------------- /Pareto/Checks/Application Updates/Docker.swift: -------------------------------------------------------------------------------- 1 | // 2 | // Docker.swift 3 | // Pareto Security 4 | // 5 | // Created by Janez Troha on 11/11/2021. 6 | // 7 | 8 | import Alamofire 9 | import Foundation 10 | import os.log 11 | import OSLog 12 | import Regex 13 | import Version 14 | 15 | class AppDockerCheck: AppCheck { 16 | static let sharedInstance = AppDockerCheck() 17 | 18 | override var appName: String { "Docker" } 19 | override var appMarketingName: String { "Docker" } 20 | override var appBundle: String { "com.docker.docker" } 21 | 22 | override var UUID: String { 23 | "ee11fe36-a372-5cba-a1b4-151748fc2fa7" 24 | } 25 | 26 | override func getLatestVersion(completion: @escaping (String) -> Void) { 27 | let url = viaEdgeCache("https://raw.githubusercontent.com/docker/docker.github.io/master/desktop/mac/release-notes/index.md") 28 | let versionRegex = Regex("## Docker Desktop ([\\d.]+)") 29 | os_log("Requesting %{public}s", url) 30 | AF.request(url).responseString(queue: AppCheck.queue, completionHandler: { response in 31 | if response.data != nil { 32 | let result = versionRegex.firstMatch(in: response.value ?? "") 33 | completion(result?.groups.first?.value ?? "0.0.0") 34 | } else { 35 | os_log("%{public}s failed: %{public}s", self.appBundle, response.error.debugDescription) 36 | self.hasError = true 37 | completion("0.0.0") 38 | } 39 | 40 | }) 41 | } 42 | } 43 | -------------------------------------------------------------------------------- /Pareto/Checks/Application Updates/Enpass.swift: -------------------------------------------------------------------------------- 1 | // 2 | // Enpass.swift 3 | // Pareto Security 4 | // 5 | // Created by Janez Troha on 11/11/2021. 6 | // 7 | 8 | class AppEnpassCheck: AppCheck { 9 | static let sharedInstance = AppEnpassCheck() 10 | 11 | override var appName: String { "Enpass" } 12 | override var appMarketingName: String { "Enpass" } 13 | override var appBundle: String { "in.sinew.Enpass-Desktop" } 14 | 15 | override var UUID: String { 16 | "ca6c8ed7-6d22-5342-908a-f010e3eb102f" 17 | } 18 | } 19 | -------------------------------------------------------------------------------- /Pareto/Checks/Application Updates/Grammarly.swift: -------------------------------------------------------------------------------- 1 | // 2 | // Grammarly.swift 3 | // Pareto Security 4 | // 5 | // Created by Janez Troha on 11/11/2022. 6 | // 7 | import Alamofire 8 | import os.log 9 | import Regex 10 | 11 | class AppGrammarlyCheck: AppCheck { 12 | static let sharedInstance = AppGrammarlyCheck() 13 | 14 | override var appName: String { "Grammarly Desktop" } 15 | override var appMarketingName: String { "Grammarly" } 16 | override var appBundle: String { "com.grammarly.ProjectLlama" } 17 | override var sparkleURL: String { "https://download-mac.grammarly.com/appcast.xml" } 18 | 19 | override var UUID: String { 20 | "be6e677f-231a-431c-9c80-861c867b8920" 21 | } 22 | } 23 | -------------------------------------------------------------------------------- /Pareto/Checks/Application Updates/MicrosoftTeams.swift: -------------------------------------------------------------------------------- 1 | // 2 | // MicrosoftTeams.swift 3 | // Pareto Security 4 | // 5 | // Created by Janez Troha on 11/11/2021. 6 | // 7 | 8 | import Alamofire 9 | import Foundation 10 | import os.log 11 | import Regex 12 | 13 | // MARK: - Welcome 14 | 15 | struct TeamsResponse: Codable { 16 | let buildSettings: BuildSettings? 17 | } 18 | 19 | // MARK: - BuildSettings 20 | 21 | struct BuildSettings: Codable { 22 | let webView2: WebView2? 23 | } 24 | 25 | // MARK: - WebView2 26 | 27 | struct WebView2: Codable { 28 | let macOS: MACOSClass? 29 | } 30 | 31 | // MARK: - MACOSClass 32 | 33 | struct MACOSClass: Codable { 34 | let latestVersion: String? 35 | } 36 | 37 | class AppMicrosoftTeamsCheck: AppCheck { 38 | static let sharedInstance = AppMicrosoftTeamsCheck() 39 | 40 | override var appName: String { "Microsoft Teams" } 41 | override var appMarketingName: String { "Microsoft Teams" } 42 | override var appBundle: String { "com.microsoft.teams" } 43 | 44 | override var UUID: String { 45 | "a0fb7240-191e-4d27-84bd-175f2b61bec1" 46 | } 47 | 48 | override func getLatestVersion(completion: @escaping (String) -> Void) { 49 | let url = viaEdgeCache("https://config.teams.microsoft.com/config/v1/MicrosoftTeams/1415_1.0.0.0?environment=prod&audienceGroup=general&teamsRing=general&agent=TeamsBuilds") 50 | 51 | os_log("Requesting %{public}s", url) 52 | AF.request(url).responseDecodable(of: TeamsResponse.self, queue: AppCheck.queue, completionHandler: { response in 53 | if response.error == nil { 54 | let v = response.value?.buildSettings?.webView2?.macOS?.latestVersion 55 | completion(v ?? "0.0.0") 56 | } else { 57 | os_log("%{public}s failed: %{public}s", self.appBundle, response.error.debugDescription) 58 | self.hasError = true 59 | completion("0.0.0") 60 | } 61 | }) 62 | } 63 | } 64 | -------------------------------------------------------------------------------- /Pareto/Checks/Application Updates/NordLayer.swift: -------------------------------------------------------------------------------- 1 | // 2 | // NordLayer.swift 3 | // Pareto Security 4 | // 5 | // Created by Janez Troha on 11/11/2021. 6 | // 7 | 8 | class AppNordLayerCheck: AppCheck { 9 | static let sharedInstance = AppNordLayerCheck() 10 | 11 | override var appName: String { "NordLayer" } 12 | override var appMarketingName: String { "NordLayer" } 13 | override var appBundle: String { "com.nordvpn.macos.teams" } 14 | 15 | override var UUID: String { 16 | "567eb82b-a407-5998-a620-ca8165d74852" 17 | } 18 | } 19 | -------------------------------------------------------------------------------- /Pareto/Checks/Application Updates/Slack.swift: -------------------------------------------------------------------------------- 1 | // 2 | // Slack.swift 3 | // Pareto Security 4 | // 5 | // Created by Janez Troha on 11/11/2021. 6 | // 7 | 8 | import Alamofire 9 | import os.log 10 | import Regex 11 | 12 | class AppSlackCheck: AppCheck { 13 | static let sharedInstance = AppSlackCheck() 14 | 15 | override var appName: String { "Slack" } 16 | override var appMarketingName: String { "Slack" } 17 | override var appBundle: String { "com.tinyspeck.slackmacgap" } 18 | 19 | override var UUID: String { 20 | "9894a05b-964d-5c19-bef7-53112207d271" 21 | } 22 | 23 | override func getLatestVersion(completion: @escaping (String) -> Void) { 24 | // If installed from the app store, assume it's managed via releases API 25 | getLatestVersionAppStore(completion: completion) 26 | } 27 | } 28 | -------------------------------------------------------------------------------- /Pareto/Checks/Application Updates/Tailscale.swift: -------------------------------------------------------------------------------- 1 | // 2 | // Tailscale.swift 3 | // Pareto Security 4 | // 5 | // Created by Janez Troha on 11/11/2021. 6 | // 7 | 8 | class AppTailscaleCheck: AppCheck { 9 | static let sharedInstance = AppTailscaleCheck() 10 | 11 | override var appName: String { "Tailscale" } 12 | override var appMarketingName: String { "Tailscale" } 13 | override var appBundle: String { "io.tailscale.ipn.macos" } 14 | 15 | override var UUID: String { 16 | "3f70f103-2cd1-50f0-a053-5eb91c891ec8" 17 | } 18 | } 19 | -------------------------------------------------------------------------------- /Pareto/Checks/Application Updates/WireGuard.swift: -------------------------------------------------------------------------------- 1 | // 2 | // WireGuard.swift 3 | // Pareto Security 4 | // 5 | // Created by Janez Troha on 11/11/2021. 6 | // 7 | 8 | class AppWireGuardCheck: AppCheck { 9 | static let sharedInstance = AppWireGuardCheck() 10 | 11 | override var appName: String { "WireGuard" } 12 | override var appMarketingName: String { "WireGuard" } 13 | override var appBundle: String { "com.wireguard.macos" } 14 | 15 | override var UUID: String { 16 | "b0e0e64a-2d8c-50d1-947c-b037773827c9" 17 | } 18 | } 19 | -------------------------------------------------------------------------------- /Pareto/Checks/Application Updates/gen.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python3 2 | # -*- coding: utf-8 -*- 3 | 4 | import os 5 | from string import Template 6 | from pathlib import Path 7 | from uuid import uuid5, NAMESPACE_URL 8 | import re 9 | import requests 10 | 11 | current = Path(__file__).parent.resolve() 12 | template = Template(current.joinpath("AppUpdateCheck.tmpl").read_text()) 13 | 14 | apps = set([ 15 | "1Password 7", 16 | "Firefox", 17 | "Bitwarden", 18 | "Cyberduck", 19 | "Dashlane", 20 | "Docker", 21 | "Dropbox", 22 | "Enpass", 23 | "Firefox", 24 | "GitHub Desktop", 25 | "Google Chrome", 26 | "iTerm", 27 | "LibreOffice", 28 | "Muzzle", 29 | "NordLayer", 30 | "Resilio Sync", 31 | "Signal", 32 | "Slack", 33 | "Sublime Text", 34 | "Tailscale", 35 | "Visual Studio Code", 36 | "WireGuard", 37 | "zoom.us", 38 | ]) 39 | 40 | possible_uuids = [] 41 | 42 | for app in apps: 43 | 44 | global_loc = Path(f"/Applications/{app}.app/Contents/Info.plist").resolve() 45 | user_loc = ( 46 | Path(f"~/Applications/{app}.app/Contents/Info.plist").expanduser().resolve() 47 | ) 48 | if not (global_loc.is_file() or user_loc.is_file()): 49 | continue 50 | 51 | full_path = str((global_loc if global_loc.is_file() else user_loc).absolute()) 52 | safename = re.sub(r"\s", "", app) 53 | safename = re.sub(r"[\.\-\_]", "", safename) 54 | safenameclass = f"App{safename}Check" 55 | uuid = uuid5(NAMESPACE_URL, safename) 56 | print(f"{safename}={uuid}") 57 | filename = f"{safename}.swift" 58 | possible_uuids.append(uuid) 59 | print("\n\n") 60 | 61 | if current.joinpath(filename).is_file(): 62 | continue 63 | 64 | SUFeedURL = ( 65 | os.popen(f"/usr/libexec/PlistBuddy -c Print:SUFeedURL '{full_path}'") 66 | .read() 67 | .strip("\n") 68 | ) 69 | bundle = ( 70 | os.popen(f"/usr/libexec/PlistBuddy -c Print:CFBundleIdentifier '{full_path}'") 71 | .read() 72 | .strip("\n") 73 | ) 74 | app_name = ( 75 | os.popen(f"/usr/libexec/PlistBuddy -c Print:CFBundleName '{full_path}'") 76 | .read() 77 | .strip("\n") 78 | ) 79 | 80 | if not SUFeedURL: 81 | try: 82 | app_store = requests.get( 83 | f"https://itunes.apple.com/lookup?bundleId={bundle}&country=us&entity=macSoftware&limit=1" 84 | ) 85 | app_store.raise_for_status() 86 | data = app_store.json()["results"][0] 87 | devices = data.get("supportedDevices", []) 88 | # Catalyst apps 89 | if devices and "MacDesktop-MacDesktop" not in data["supportedDevices"]: 90 | print(f"{app} is not supported via app store") 91 | continue 92 | except Exception: 93 | print(f"{app} is not supported") 94 | continue 95 | 96 | print(f"Adding {safenameclass} with uuid:{uuid} to {filename}") 97 | current.joinpath(filename).write_text( 98 | template.substitute( 99 | safename=safename, 100 | safenameclass=safenameclass, 101 | app=app, 102 | uuid=uuid, 103 | bundle=bundle, 104 | app_name=app_name, 105 | SUFeedURL=SUFeedURL, 106 | ) 107 | ) 108 | 109 | print("\n\n") 110 | 111 | for uuid in possible_uuids: 112 | print( 113 | f"""""" 114 | ) 115 | -------------------------------------------------------------------------------- /Pareto/Checks/Application Updates/iTerm.swift: -------------------------------------------------------------------------------- 1 | // 2 | // iTerm.swift 3 | // Pareto Security 4 | // 5 | // Created by Janez Troha on 11/11/2021. 6 | // 7 | 8 | class AppiTermCheck: AppCheck { 9 | static let sharedInstance = AppiTermCheck() 10 | 11 | override var appName: String { "iTerm" } 12 | override var appMarketingName: String { "iTerm2" } 13 | override var appBundle: String { "com.googlecode.iterm2" } 14 | override var sparkleURL: String { "https://iterm2.com/appcasts/final_modern.xml" } 15 | 16 | override var UUID: String { 17 | "1a925bda-7c49-52de-a970-a84b53ea2d7b" 18 | } 19 | 20 | override public var supportsRecentlyUsed: Bool { 21 | return false 22 | } 23 | } 24 | -------------------------------------------------------------------------------- /Pareto/Checks/Checks.swift: -------------------------------------------------------------------------------- 1 | // 2 | // Checks.swift 3 | // Pareto Security 4 | // 5 | // Created by Janez Troha on 21/12/2021. 6 | // 7 | 8 | import Defaults 9 | import Foundation 10 | 11 | class Claims: ObservableObject { 12 | var customChecks: [ParetoCheck] = [] 13 | var all: [Claim] = [] 14 | 15 | static let global = Claims() 16 | 17 | init() { 18 | refresh() 19 | } 20 | 21 | func refresh() { 22 | all = [ 23 | Claim(withTitle: "macOS Updates", withChecks: [ 24 | MacOSVersionCheck.sharedInstance, 25 | SecurityUpdateCheck.sharedInstance, 26 | AutomaticDownloadCheck.sharedInstance, 27 | SystemUpdatesCheck.sharedInstance, 28 | AutoUpdateCheck.sharedInstance 29 | ]), 30 | Claim(withTitle: "Access Security", withChecks: [ 31 | AutologinCheck.sharedInstance, 32 | PasswordManager.sharedInstance, 33 | RequirePasswordToUnlock.sharedInstance, 34 | ScreensaverCheck.sharedInstance, 35 | SSHKeysCheck.sharedInstance, 36 | SSHKeysStrengthCheck.sharedInstance, 37 | PasswordAfterSleepCheck.sharedInstance, 38 | NoUnusedUsers.sharedInstance, 39 | NoAdminUser.sharedInstance 40 | ]), 41 | Claim(withTitle: "Firewall & Sharing", withChecks: [ 42 | FirewallCheck.sharedInstance, 43 | FirewallStealthCheck.sharedInstance, 44 | FileSharingCheck.sharedInstance, 45 | PrinterSharingCheck.sharedInstance, 46 | RemoteManagementCheck.sharedInstance, 47 | RemoteLoginCheck.sharedInstance, 48 | AirPlayCheck.sharedInstance, 49 | AirDropCheck.sharedInstance, 50 | MediaShareCheck.sharedInstance, 51 | InternetShareCheck.sharedInstance 52 | ]), 53 | Claim(withTitle: "System Integrity", withChecks: [ 54 | GatekeeperCheck.sharedInstance, 55 | FileVaultCheck.sharedInstance, 56 | BootCheck.sharedInstance, 57 | OpenWiFiCheck.sharedInstance, 58 | TimeMachineCheck.sharedInstance, 59 | SecureTerminalCheck.sharedInstance, 60 | SecureiTermCheck.sharedInstance, 61 | TimeMachineHasBackupCheck.sharedInstance, 62 | TimeMachineIsEncryptedCheck.sharedInstance 63 | ]), 64 | Claim(withTitle: "Application Updates", withChecks: Claims.updateChecks + [AutoUpdateAppCheck.sharedInstance, ParetoUpdated.sharedInstance]) 65 | ].sorted(by: { $0.title.lowercased() < $1.title.lowercased() }) 66 | } 67 | 68 | static let updateChecks = [ 69 | App1Password7Check.sharedInstance, 70 | App1Password8Check.sharedInstance, 71 | AppGrammarlyCheck.sharedInstance, 72 | AppBitwardenCheck.sharedInstance, 73 | AppCyberduckCheck.sharedInstance, 74 | AppDashlaneCheck.sharedInstance, 75 | AppDockerCheck.sharedInstance, 76 | AppEnpassCheck.sharedInstance, 77 | AppFirefoxCheck.sharedInstance, 78 | AppGoogleChromeCheck.sharedInstance, 79 | AppiTermCheck.sharedInstance, 80 | AppNordLayerCheck.sharedInstance, 81 | AppSlackCheck.sharedInstance, 82 | AppTailscaleCheck.sharedInstance, 83 | AppZoomCheck.sharedInstance, 84 | AppSignalCheck.sharedInstance, 85 | AppWireGuardCheck.sharedInstance, 86 | AppLibreOfficeCheck.sharedInstance, 87 | AppSublimeTextCheck.sharedInstance, 88 | AppVSCodeCheck.sharedInstance, 89 | AdobeReaderCheck.sharedInstance, 90 | AppLuLuCheck.sharedInstance, 91 | AppMicrosoftTeamsCheck.sharedInstance 92 | ] 93 | } 94 | -------------------------------------------------------------------------------- /Pareto/Checks/Claim.swift: -------------------------------------------------------------------------------- 1 | // 2 | // Claim.swift 3 | // Claim 4 | // 5 | // Created by Janez Troha on 16/08/2021. 6 | // 7 | 8 | import AppKit 9 | import Defaults 10 | import Foundation 11 | import os.log 12 | import OSLog 13 | import SwiftUI 14 | 15 | class Claim: Hashable { 16 | static func == (lhs: Claim, rhs: Claim) -> Bool { 17 | lhs.title == rhs.title 18 | } 19 | 20 | func hash(into hasher: inout Hasher) { 21 | hasher.combine(title) 22 | } 23 | 24 | public var title: String 25 | public var checks: [ParetoCheck] 26 | 27 | public var checksSorted: [ParetoCheck] { 28 | checks.sorted(by: { $0.Title.lowercased() < $1.Title.lowercased() }) 29 | } 30 | 31 | init(withTitle title: String, withChecks checks: [ParetoCheck]) { 32 | self.title = title 33 | self.checks = checks 34 | } 35 | 36 | var checksPassed: Bool { checks.allSatisfy { $0.isRunnable ? $0.checkPassed : true } } 37 | var checksNoError: Bool { checks.allSatisfy { $0.isRunnable ? !$0.hasError : true } } 38 | 39 | func addSubmenu(withTitle: String, action: Selector?) -> NSMenuItem { 40 | let item = NSMenuItem(title: withTitle, action: action, keyEquivalent: "") 41 | item.target = self 42 | return item 43 | } 44 | 45 | func menu() -> NSMenuItem { 46 | let item = NSMenuItem(title: title, action: nil, keyEquivalent: "") 47 | let submenu = NSMenu() 48 | for check in checksSorted { 49 | if check.isRunnableCached() { 50 | submenu.addItem(check.menu()) 51 | if Defaults[.snoozeTime] > 0 { 52 | item.image = NSImage.SF(name: "shield.fill").tint(color: .systemGray) 53 | } else { 54 | if checksPassed && checksNoError { 55 | item.image = NSImage.SF(name: "checkmark.circle.fill").tint(color: Defaults.OKColor()) 56 | } else { 57 | item.image = NSImage.SF(name: "xmark.diamond.fill").tint(color: Defaults.FailColor()) 58 | } 59 | } 60 | } 61 | } 62 | 63 | // item.submenu = submenu 64 | item.submenu = submenu 65 | return item 66 | } 67 | 68 | func run() { 69 | for check in checks { 70 | let startTime = Date() 71 | check.run() 72 | let endTime = Date() 73 | let timeInterval = endTime.timeIntervalSince(startTime) 74 | Logger().log("uuid=\(check.UUID, privacy: .public) timeInterval=\(timeInterval, privacy: .public)") 75 | } 76 | } 77 | 78 | func configure() { 79 | for check in checks { 80 | check.configure() 81 | } 82 | UserDefaults.standard.synchronize() 83 | } 84 | } 85 | -------------------------------------------------------------------------------- /Pareto/Checks/Firefox.swift: -------------------------------------------------------------------------------- 1 | // 2 | // Firefox.swift 3 | // Pareto Security 4 | // 5 | // Created by Janez Troha on 30/11/2021. 6 | // 7 | 8 | import Alamofire 9 | import AppKit 10 | import Combine 11 | import Foundation 12 | import os.log 13 | import OSLog 14 | import Version 15 | 16 | struct FirefoxVersions: Decodable { 17 | let LATEST_FIREFOX_VERSION: String 18 | } 19 | 20 | class AppFirefoxCheck: AppCheck { 21 | static let sharedInstance = AppFirefoxCheck() 22 | 23 | override var appName: String { "Firefox" } 24 | override var appMarketingName: String { "Firefox" } 25 | override var appBundle: String { "org.mozilla.firefox" } 26 | 27 | override var UUID: String { 28 | "768a574c-75a2-536d-8785-ef9512981184" 29 | } 30 | 31 | override var currentVersion: Version { 32 | if applicationPath == nil { 33 | return Version(0, 0, 0) 34 | } 35 | let v = appVersion(path: applicationPath!)!.split(separator: ".") 36 | if v.count == 2 { 37 | return Version(Int(v[0]) ?? 0, Int(v[1]) ?? 0, 0) 38 | } 39 | return Version(Int(v[0]) ?? 0, Int(v[1]) ?? 0, Int(v[2]) ?? 0) 40 | } 41 | 42 | override func getLatestVersion(completion: @escaping (String) -> Void) { 43 | let url = viaEdgeCache("https://product-details.mozilla.org/1.0/firefox_versions.json") 44 | os_log("Requesting %{public}s", url) 45 | AF.request(url).responseDecodable(of: FirefoxVersions.self, queue: AppCheck.queue) { response in 46 | if response.error == nil { 47 | let version = response.value?.LATEST_FIREFOX_VERSION ?? "0.0.0" 48 | os_log("%{public}s version=%{public}s", self.appBundle, version) 49 | completion(version) 50 | } else { 51 | os_log("%{public}s failed: %{public}s", self.appBundle, response.error.debugDescription) 52 | self.hasError = true 53 | completion("0.0.0") 54 | } 55 | } 56 | } 57 | } 58 | -------------------------------------------------------------------------------- /Pareto/Checks/Firewall and Sharing/AirDrop.swift: -------------------------------------------------------------------------------- 1 | // 2 | // AirDrop.swift 3 | // AirDrop 4 | // 5 | // Created by Janez Troha on 21/09/2021. 6 | // 7 | 8 | import Foundation 9 | 10 | class AirDropCheck: ParetoCheck { 11 | static let sharedInstance = AirDropCheck() 12 | override var UUID: String { 13 | "58e8267b-05eb-490a-9c91-77ac6499af8f" 14 | } 15 | 16 | override var TitleON: String { 17 | "AirDrop is secured" 18 | } 19 | 20 | override var TitleOFF: String { 21 | "AirDrop is not secured" 22 | } 23 | 24 | override func checkPasses() -> Bool { 25 | // Contacts Only is the default 26 | let discoverableMode = readDefaultsNative(path: "com.apple.sharingd", key: "DiscoverableMode") ?? "Contacts Only" 27 | if discoverableMode.contains("Contacts Only") { 28 | return true 29 | } 30 | if discoverableMode.contains("Off") { 31 | return true 32 | } 33 | return false 34 | } 35 | } 36 | -------------------------------------------------------------------------------- /Pareto/Checks/Firewall and Sharing/AirPlay.swift: -------------------------------------------------------------------------------- 1 | // 2 | // AirPlay.swift 3 | // AirPlay 4 | // 5 | // Created by Janez Troha on 21/09/2021. 6 | // 7 | 8 | import Foundation 9 | 10 | class AirPlayCheck: ParetoCheck { 11 | static let sharedInstance = AirPlayCheck() 12 | override var UUID: String { 13 | "0cd3ad3c-c41f-4291-82f8-c05dd23c0a9b" 14 | } 15 | 16 | override var TitleON: String { 17 | "AirPlay receiver is off" 18 | } 19 | 20 | override var TitleOFF: String { 21 | "AirPlay receiver is on" 22 | } 23 | 24 | override func checkPasses() -> Bool { 25 | return isNotListening(withCommand: "ControlCenter", withPort: 5000) && isNotListening(withCommand: "ControlCenter", withPort: 7000) 26 | } 27 | } 28 | -------------------------------------------------------------------------------- /Pareto/Checks/Firewall and Sharing/FileSharingCheck.swift: -------------------------------------------------------------------------------- 1 | // 2 | // FileSharingCheck.swift 3 | // FileSharingCheck 4 | // 5 | // Created by Janez Troha on 19/08/2021. 6 | // 7 | 8 | class FileSharingCheck: ParetoCheck { 9 | static let sharedInstance = FileSharingCheck() 10 | override var UUID: String { 11 | "b96524e0-850b-4bb8-abc7-517051b6c14e" 12 | } 13 | 14 | override var TitleON: String { 15 | "Sharing files is off" 16 | } 17 | 18 | override var TitleOFF: String { 19 | "Sharing files is on" 20 | } 21 | 22 | override func checkPasses() -> Bool { 23 | return isNotListening(withPort: 445) 24 | } 25 | } 26 | -------------------------------------------------------------------------------- /Pareto/Checks/Firewall and Sharing/Firewall.swift: -------------------------------------------------------------------------------- 1 | // 2 | // Firewall.swift 3 | // Pareto Security 4 | // 5 | // Created by Janez Troha on 15/07/2021. 6 | // 7 | 8 | class FirewallCheck: ParetoCheck { 9 | static let sharedInstance = FirewallCheck() 10 | override var UUID: String { 11 | "2e46c89a-5461-4865-a92e-3b799c12034a" 12 | } 13 | 14 | override var TitleON: String { 15 | "Firewall is on" 16 | } 17 | 18 | override var TitleOFF: String { 19 | "Firewall is off" 20 | } 21 | 22 | override public var isCritical: Bool { 23 | return true 24 | } 25 | 26 | override public var hasDebug: Bool { 27 | return true 28 | } 29 | 30 | override public func debugInfo() -> String { 31 | let systemextensionsctl = runCMD(app: "/usr/bin/systemextensionsctl", args: ["list", "com.apple.system_extension.network_extension"]) 32 | return "systemextensionsctl:\n\(systemextensionsctl)" 33 | } 34 | 35 | func extensionActive(name: String) -> Bool { 36 | let list = runCMD(app: "/usr/bin/systemextensionsctl", args: ["list", "com.apple.system_extension.network_extension"]) 37 | for app in list.split(separator: "\n") { 38 | if app.contains(name) { 39 | return app.contains("activated enabled") 40 | } 41 | } 42 | return false 43 | } 44 | 45 | var isLittleSnitchActive: Bool { 46 | extensionActive(name: "at.obdev.littlesnitch.networkextension") 47 | } 48 | 49 | var isLuluActive: Bool { 50 | extensionActive(name: "com.objective-see.lulu.extension") 51 | } 52 | 53 | override func checkPasses() -> Bool { 54 | if #available(macOS 15, *) { 55 | let out = runCMD(app: "/usr/libexec/ApplicationFirewall/socketfilterfw", args: ["--getglobalstate"]) 56 | return out.contains("State = 1") || out.contains("State = 2") 57 | } else { 58 | let native = readDefaultsFile(path: "/Library/Preferences/com.apple.alf.plist") 59 | 60 | if let globalstate = native?.value(forKey: "globalstate") as? Int { 61 | return globalstate >= 1 62 | } 63 | 64 | return false 65 | } 66 | } 67 | } 68 | -------------------------------------------------------------------------------- /Pareto/Checks/Firewall and Sharing/FirewallStealth.swift: -------------------------------------------------------------------------------- 1 | // 2 | // FirewallStealth.swift 3 | // Pareto Security 4 | // 5 | // Created by Janez Troha on 15/07/2021. 6 | // 7 | 8 | class FirewallStealthCheck: ParetoCheck { 9 | static let sharedInstance = FirewallStealthCheck() 10 | override var UUID: String { 11 | "2e46c89a-5461-4865-a92e-3b799c12034b" 12 | } 13 | 14 | override var TitleON: String { 15 | "Firewall stealth mode is enabled" 16 | } 17 | 18 | override var TitleOFF: String { 19 | "Firewall stealth mode is disabled" 20 | } 21 | 22 | override public var isRunnable: Bool { 23 | return FirewallCheck.sharedInstance.isActive && isActive 24 | } 25 | 26 | override public var hasDebug: Bool { 27 | return true 28 | } 29 | 30 | override public func debugInfo() -> String { 31 | let dictionary = readDefaultsFile(path: "/Library/Preferences/com.apple.alf.plist") 32 | let out = runCMD(app: "/usr/libexec/ApplicationFirewall/socketfilterfw", args: ["--getstealthmode"]) 33 | return "com.apple.alf.plist:\n\(dictionary.debugDescription)\nsocketfilterfw:\n\(out)" 34 | } 35 | 36 | override public var showSettings: Bool { 37 | if teamEnforced { 38 | return false 39 | } 40 | return FirewallCheck.sharedInstance.isActive 41 | } 42 | 43 | override func checkPasses() -> Bool { 44 | let out = runCMD(app: "/usr/libexec/ApplicationFirewall/socketfilterfw", args: ["--getstealthmode"]) 45 | return out.contains("enabled") || out.contains("mode is on") 46 | } 47 | } 48 | -------------------------------------------------------------------------------- /Pareto/Checks/Firewall and Sharing/InternetSharing.swift: -------------------------------------------------------------------------------- 1 | // 2 | // InternetSharing.swift 3 | // InternetShare 4 | // 5 | // Created by Janez Troha on 21/09/2021. 6 | // 7 | 8 | import Foundation 9 | 10 | class InternetShareCheck: ParetoCheck { 11 | static let sharedInstance = InternetShareCheck() 12 | override var UUID: String { 13 | "2ed19e08-6ea7-4c53-b735-321cebefa1b4" 14 | } 15 | 16 | override var TitleON: String { 17 | "Sharing internet is off" 18 | } 19 | 20 | override var TitleOFF: String { 21 | "Sharing internet is on" 22 | } 23 | 24 | override var hasDebug: Bool { 25 | true 26 | } 27 | 28 | override func debugInfo() -> String { 29 | let data = readDefaultsFile(path: "/Library/Preferences/SystemConfiguration/com.apple.nat.plist") 30 | return "plist: \(String(describing: data))" 31 | } 32 | 33 | override func checkPasses() -> Bool { 34 | if let dict = readDefaultsFile(path: "/Library/Preferences/SystemConfiguration/com.apple.nat.plist") { 35 | if let NAT = (dict.value(forKey: "NAT") as? NSDictionary) { 36 | var natPrimaryDisabled = true 37 | if let primary = NAT.value(forKey: "PrimaryInterface") as? NSDictionary { 38 | natPrimaryDisabled = primary.value(forKey: "Enabled") as? Int == 0 39 | } 40 | var natAirPortDisabled = true 41 | if let airport = NAT.value(forKey: "AirPort") as? NSDictionary { 42 | natAirPortDisabled = airport.value(forKey: "Enabled") as? Int == 0 43 | } 44 | 45 | let natDisabled = NAT.value(forKey: "Enabled") as? Int == 0 46 | return natDisabled && natAirPortDisabled && natPrimaryDisabled 47 | } 48 | } 49 | // can also be missing if it never changed, but defaults to true 50 | return true 51 | } 52 | } 53 | -------------------------------------------------------------------------------- /Pareto/Checks/Firewall and Sharing/MediaShare.swift: -------------------------------------------------------------------------------- 1 | // 2 | // MediaShare.swift 3 | // MediaShare 4 | // 5 | // Created by Janez Troha on 21/09/2021. 6 | // 7 | 8 | import Foundation 9 | 10 | class MediaShareCheck: ParetoCheck { 11 | static let sharedInstance = MediaShareCheck() 12 | override var UUID: String { 13 | "0cd3ad3c-c41f-4291-82f8-c05dd23c0a9a" 14 | } 15 | 16 | override var TitleON: String { 17 | "Sharing media is off" 18 | } 19 | 20 | override var TitleOFF: String { 21 | "Sharing media is on" 22 | } 23 | 24 | override func checkPasses() -> Bool { 25 | return isNotListening(withPort: 3689) 26 | } 27 | } 28 | -------------------------------------------------------------------------------- /Pareto/Checks/Firewall and Sharing/PrinterSharingCheck.swift: -------------------------------------------------------------------------------- 1 | // 2 | // PrinterSharingCheck.swift 3 | // PrinterSharingCheck 4 | // 5 | // Created by Janez Troha on 19/08/2021. 6 | // 7 | 8 | class PrinterSharingCheck: ParetoCheck { 9 | static let sharedInstance = PrinterSharingCheck() 10 | override var UUID: String { 11 | "b96524e0-150b-4bb8-abc7-517051b6c14e" 12 | } 13 | 14 | override var TitleON: String { 15 | "Sharing printers is off" 16 | } 17 | 18 | override var TitleOFF: String { 19 | "Sharing printers is on" 20 | } 21 | 22 | override func checkPasses() -> Bool { 23 | let output = runCMD(app: "/usr/sbin/cupsctl", args: []) 24 | let settings = ["_share_printers=0", "_remote_admin=0", "_remote_any=0"] 25 | return settings.allSatisfy { 26 | output.contains($0) 27 | } 28 | } 29 | } 30 | -------------------------------------------------------------------------------- /Pareto/Checks/Firewall and Sharing/RemoteLogin.swift: -------------------------------------------------------------------------------- 1 | // 2 | // RemoteLogin.swift 3 | // RemoteLogin 4 | // 5 | // Created by Janez Troha on 08/09/2021. 6 | // 7 | 8 | import Foundation 9 | 10 | class RemoteLoginCheck: ParetoCheck { 11 | static let sharedInstance = RemoteLoginCheck() 12 | override var UUID: String { 13 | "4ced961d-7cfc-4e7b-8f80-195f6379446e" 14 | } 15 | 16 | override var TitleON: String { 17 | "Remote Login is off" 18 | } 19 | 20 | override var TitleOFF: String { 21 | "Remote Login is on" 22 | } 23 | 24 | override func checkPasses() -> Bool { 25 | return isNotListening(withPort: 22) 26 | } 27 | } 28 | -------------------------------------------------------------------------------- /Pareto/Checks/Firewall and Sharing/RemoteManagment.swift: -------------------------------------------------------------------------------- 1 | // 2 | // RemoteManagment.swift 3 | // RemoteManagementCheck 4 | // 5 | // Created by Janez Troha on 08/09/2021. 6 | // 7 | 8 | import Foundation 9 | 10 | class RemoteManagementCheck: ParetoCheck { 11 | static let sharedInstance = RemoteManagementCheck() 12 | 13 | override var UUID: String { 14 | "05423213-50e7-4535-ac88-60cc21626378" 15 | } 16 | 17 | override var TitleON: String { 18 | "Remote Management is off" 19 | } 20 | 21 | override var TitleOFF: String { 22 | "Remote Management is on" 23 | } 24 | 25 | override func checkPasses() -> Bool { 26 | return isNotListening(withPort: 3283) 27 | } 28 | } 29 | -------------------------------------------------------------------------------- /Pareto/Checks/GoogleChrome.swift: -------------------------------------------------------------------------------- 1 | // 2 | // GoogleChrome.swift 3 | // Pareto Security 4 | // 5 | // Created by Janez Troha on 11/11/2021. 6 | // 7 | 8 | import Alamofire 9 | import AppKit 10 | import Combine 11 | import Foundation 12 | import os.log 13 | import OSLog 14 | import Version 15 | 16 | // MARK: - GoogleResponse 17 | 18 | private struct GoogleResponse: Codable { 19 | let releases: [ChromeVersion] 20 | } 21 | 22 | // MARK: - Version 23 | 24 | private struct ChromeVersion: Codable { 25 | let version: String 26 | let fraction: Float 27 | } 28 | 29 | class AppGoogleChromeCheck: AppCheck { 30 | static let sharedInstance = AppGoogleChromeCheck() 31 | 32 | override var appName: String { "Google Chrome" } 33 | override var appMarketingName: String { "Google Chrome" } 34 | override var appBundle: String { "com.google.Chrome" } 35 | 36 | override var UUID: String { 37 | "d34ee340-67a7-5e3e-be8b-aef4e3133de0" 38 | } 39 | 40 | // Special treatment follows 41 | // Use build number as definite version comparator 42 | // https://www.chromium.org/developers/version-numbers 43 | 44 | override var currentVersion: Version { 45 | if applicationPath == nil { 46 | return Version(0, 0, 0) 47 | } 48 | let v = appVersion(path: applicationPath ?? "1.2.3.4")!.split(separator: ".") 49 | return Version(Int(v[0]) ?? 0, Int(v[1]) ?? 0, Int(v[2]) ?? 0) 50 | } 51 | 52 | override func getLatestVersion(completion: @escaping (String) -> Void) { 53 | let url = viaEdgeCache("https://versionhistory.googleapis.com/v1/chrome/platforms/mac/channels/stable/versions/all/releases?filter=endtime=none") 54 | os_log("Requesting %{public}s", url) 55 | AF.request(url).responseDecodable(of: GoogleResponse.self, queue: AppCheck.queue, completionHandler: { response in 56 | if response.error == nil { 57 | let v = response.value?.releases.filter { v in 58 | v.fraction >= 0.9 59 | }.first?.version.split(separator: ".") ?? ["0", "0", "0"] 60 | completion("\(v[0]).\(v[1]).\(v[2])") 61 | } else { 62 | os_log("%{public}s failed: %{public}s", self.appBundle, response.error.debugDescription) 63 | self.hasError = true 64 | completion("0.0.0") 65 | } 66 | }) 67 | } 68 | } 69 | -------------------------------------------------------------------------------- /Pareto/Checks/IntegrationCheck.swift: -------------------------------------------------------------------------------- 1 | // 2 | // IntegrationCheck.swift 3 | // IntegrationCheck 4 | // 5 | // Created by Janez Troha on 12/08/2021. 6 | // 7 | 8 | import Foundation 9 | import os.log 10 | 11 | class IntegrationCheck: ParetoCheck { 12 | override var UUID: String { 13 | "aaaaaaaa-bbbb-cccc-dddd-abcdef123456" 14 | } 15 | 16 | override var TitleON: String { 17 | "Unit test mock ON" 18 | } 19 | 20 | override var TitleOFF: String { 21 | "Unit test mock OFF" 22 | } 23 | 24 | override func checkPasses() -> Bool { 25 | return true 26 | } 27 | } 28 | 29 | class IntegrationCheckFails: ParetoCheck { 30 | override var UUID: String { 31 | "aaaaaaaa-bbbb-cccc-dddd-abcdef000002" 32 | } 33 | 34 | override var TitleON: String { 35 | "Unit test mock fails ON" 36 | } 37 | 38 | override var TitleOFF: String { 39 | "Unit test mock fails OFF" 40 | } 41 | 42 | override func checkPasses() -> Bool { 43 | return false 44 | } 45 | } 46 | -------------------------------------------------------------------------------- /Pareto/Checks/LibreOffice.swift: -------------------------------------------------------------------------------- 1 | // 2 | // LibreOffice.swift 3 | // Pareto Security 4 | // 5 | // Created by Janez Troha on 11/11/2021. 6 | // 7 | 8 | import Alamofire 9 | import AppKit 10 | import Combine 11 | import Foundation 12 | import os.log 13 | import OSLog 14 | import Regex 15 | import Version 16 | 17 | class AppLibreOfficeCheck: AppCheck { 18 | static let sharedInstance = AppLibreOfficeCheck() 19 | 20 | override var appName: String { "LibreOffice" } 21 | override var appMarketingName: String { "LibreOffice" } 22 | override var appBundle: String { "org.libreoffice.script" } 23 | 24 | override var UUID: String { 25 | "5726931a-264a-5758-b7dd-d09285ac4b7f" 26 | } 27 | 28 | override var currentVersion: Version { 29 | if applicationPath == nil { 30 | return Version(0, 0, 0) 31 | } 32 | let v = appVersion(path: applicationPath ?? "1.2.3.4")!.split(separator: ".") 33 | return Version(Int(v[0]) ?? 0, Int(v[1]) ?? 0, Int(v[2]) ?? 0) 34 | } 35 | 36 | func getLatestVersions(completion: @escaping ([String]) -> Void) { 37 | let url = viaEdgeCache("https://www.libreoffice.org/download/download/") 38 | os_log("Requesting %{public}s", url) 39 | let versionRegex = Regex("?([\\.\\d]+)") 40 | AF.request(url).responseString(queue: AppCheck.queue, completionHandler: { response in 41 | if response.error == nil { 42 | let html = response.value ?? "1.2.4" 43 | let versions = versionRegex.allMatches(in: html).map { $0.groups.first?.value ?? "1.2.4" } 44 | completion(versions) 45 | } else { 46 | os_log("%{public}s failed: %{public}s", self.appBundle, response.error.debugDescription) 47 | self.hasError = true 48 | completion(["0.0.0"]) 49 | } 50 | }) 51 | } 52 | 53 | public var latestVersions: [Version] { 54 | var tempVersions = [Version(0, 0, 0)] 55 | let lock = DispatchSemaphore(value: 0) 56 | getLatestVersions { versions in 57 | tempVersions = versions.map { Version($0) ?? Version(0, 0, 0) } 58 | lock.signal() 59 | } 60 | lock.wait() 61 | return tempVersions 62 | } 63 | 64 | override func checkPasses() -> Bool { 65 | if NetworkHandler.sharedInstance().currentStatus != .satisfied { 66 | return checkPassed 67 | } 68 | 69 | for version in latestVersions { 70 | if currentVersion >= version { 71 | return true 72 | } 73 | } 74 | 75 | return false 76 | } 77 | } 78 | -------------------------------------------------------------------------------- /Pareto/Checks/LuLu.swift: -------------------------------------------------------------------------------- 1 | // 2 | // LuLu.swift 3 | // Pareto Security 4 | // 5 | // Created by Janez Troha on 11/11/2021. 6 | // 7 | 8 | import Alamofire 9 | import AppKit 10 | import Combine 11 | import Foundation 12 | import os.log 13 | import OSLog 14 | import Regex 15 | import Version 16 | 17 | class AppLuLuCheck: AppCheck { 18 | static let sharedInstance = AppLuLuCheck() 19 | 20 | override var appName: String { "LuLu" } 21 | override var appMarketingName: String { "LuLu" } 22 | override var appBundle: String { "com.objective-see.lulu.app" } 23 | 24 | override var UUID: String { 25 | "1b282abb-888b-4f82-bba4-db8ce46a8e2a" 26 | } 27 | 28 | override func getLatestVersion(completion: @escaping (String) -> Void) { 29 | let url = viaEdgeCache("https://objective-see.com/products/changelogs/LuLu.txt") 30 | let versionRegex = Regex("VERSION ([\\.\\d]+) ") 31 | os_log("Requesting %{public}s", url) 32 | 33 | AF.request(url).responseString(queue: AppCheck.queue, completionHandler: { response in 34 | if response.error == nil { 35 | let txt = response.value ?? "VERSION 1.4.1 (11/01/2021)" 36 | let version = versionRegex.firstMatch(in: txt)?.groups.first?.value ?? "1.4.1" 37 | os_log("%{public}s version=%{public}s", self.appBundle, version) 38 | completion(version) 39 | } else { 40 | os_log("%{public}s failed: %{public}s", self.appBundle, response.error.debugDescription) 41 | self.hasError = true 42 | completion("0.0.0") 43 | } 44 | 45 | }) 46 | } 47 | } 48 | -------------------------------------------------------------------------------- /Pareto/Checks/Signal.swift: -------------------------------------------------------------------------------- 1 | // 2 | // Signal.swift 3 | // Pareto Security 4 | // 5 | // Created by Janez Troha on 11/11/2021. 6 | // 7 | 8 | import Alamofire 9 | import AppKit 10 | import Combine 11 | import Foundation 12 | import os.log 13 | import OSLog 14 | import Regex 15 | import Version 16 | 17 | class AppSignalCheck: AppCheck { 18 | static let sharedInstance = AppSignalCheck() 19 | 20 | override var appName: String { "Signal" } 21 | override var appMarketingName: String { "Signal" } 22 | override var appBundle: String { "org.whispersystems.signal-desktop" } 23 | 24 | override var UUID: String { 25 | "b621849f-c1ed-5483-ba71-a90968b1fa7b" 26 | } 27 | 28 | override func getLatestVersion(completion: @escaping (String) -> Void) { 29 | let url = viaEdgeCache("https://updates.signal.org/desktop/latest-mac.yml") 30 | let versionRegex = Regex("version: ?([\\.\\d]+)") 31 | os_log("Requesting %{public}s", url) 32 | 33 | AF.request(url).responseString(queue: AppCheck.queue, completionHandler: { response in 34 | if response.error == nil { 35 | let yaml = response.value ?? "version: 1.25.0" 36 | let version = versionRegex.firstMatch(in: yaml)?.groups.first?.value ?? "1.25.0" 37 | os_log("%{public}s version=%{public}s", self.appBundle, version) 38 | completion(version) 39 | } else { 40 | os_log("%{public}s failed: %{public}s", self.appBundle, response.error.debugDescription) 41 | self.hasError = true 42 | completion("0.0.0") 43 | } 44 | 45 | }) 46 | } 47 | } 48 | -------------------------------------------------------------------------------- /Pareto/Checks/SublimeText.swift: -------------------------------------------------------------------------------- 1 | // 2 | // SublimeText.swift 3 | // Pareto Security 4 | // 5 | // Created by Janez Troha on 11/11/2021. 6 | // 7 | 8 | import Alamofire 9 | import AppKit 10 | import Combine 11 | import Foundation 12 | import os.log 13 | import OSLog 14 | import Regex 15 | import Version 16 | 17 | class AppSublimeTextCheck: AppCheck { 18 | static let sharedInstance = AppSublimeTextCheck() 19 | 20 | override var appName: String { "Sublime Text" } 21 | override var appMarketingName: String { "Sublime Text" } 22 | override var appBundle: String { "com.sublimetext.4" } 23 | 24 | override var UUID: String { 25 | "0ae675c9-1fbe-5fcc-8e4a-c0f53f4d8b4d" 26 | } 27 | 28 | override var currentVersion: Version { 29 | if applicationPath == nil { 30 | return Version(0, 0, 0) 31 | } 32 | let version = appVersion(path: applicationPath ?? "Build 3121")!.split(separator: " ")[1] 33 | return Version("\(version.prefix(1)).\(version.suffix(3)).0") ?? Version(0, 0, 0) 34 | } 35 | 36 | override func getLatestVersion(completion: @escaping (String) -> Void) { 37 | let url = viaEdgeCache("https://www.sublimetext.com/download") 38 | let versionRegex = Regex("Build (\\d+)") 39 | os_log("Requesting %{public}s", url) 40 | 41 | AF.request(url).responseString(queue: AppCheck.queue, completionHandler: { response in 42 | if response.error == nil { 43 | let yaml = response.value ?? "Build 3121" 44 | let version = versionRegex.firstMatch(in: yaml)?.groups.first?.value ?? "3121" 45 | os_log("%{public}s version=%{public}s", self.appBundle, version) 46 | completion("\(version.prefix(1)).\(version.suffix(3)).0") 47 | } else { 48 | os_log("%{public}s failed: %{public}s", self.appBundle, response.error.debugDescription) 49 | self.hasError = true 50 | completion("0.0.0") 51 | } 52 | 53 | }) 54 | } 55 | } 56 | -------------------------------------------------------------------------------- /Pareto/Checks/System Integrity/Boot.swift: -------------------------------------------------------------------------------- 1 | // 2 | // Boot.swift 3 | // Boot 4 | // 5 | // Created by Janez Troha on 18/08/2021. 6 | // 7 | 8 | import Foundation 9 | import IOKit 10 | 11 | class BootCheck: ParetoCheck { 12 | static let sharedInstance = BootCheck() 13 | override var UUID: String { 14 | "b96524e0-850b-4bb9-abc7-517051b6c14e" 15 | } 16 | 17 | override var TitleON: String { 18 | "Boot is secure" 19 | } 20 | 21 | override var TitleOFF: String { 22 | "Boot is unsecure" 23 | } 24 | 25 | func GetNVRAM(_ name: String) -> String { 26 | let port = IOServiceGetMatchingService(kIOMasterPortDefault, nil) 27 | let gOptionsRef = IORegistryEntryFromPath(port, "IODeviceTree:/options") 28 | 29 | let nameRef = CFStringCreateWithCString(kCFAllocatorDefault, name, CFStringBuiltInEncodings.UTF8.rawValue) 30 | 31 | let valueRef = IORegistryEntryCreateCFProperty(gOptionsRef, nameRef, kCFAllocatorDefault, 0) 32 | 33 | if valueRef != nil { 34 | // Read as NSData 35 | if let data = valueRef?.takeUnretainedValue() as? Data { 36 | return NSString(data: data, encoding: String.Encoding.ascii.rawValue)! as String 37 | } 38 | // Read as String 39 | return valueRef!.takeRetainedValue() as! String 40 | } else { 41 | return "" 42 | } 43 | } 44 | 45 | override func checkPasses() -> Bool { 46 | // only returns something if AMFI is disabled 47 | return GetNVRAM("boot-args") == "" 48 | } 49 | } 50 | -------------------------------------------------------------------------------- /Pareto/Checks/System Integrity/FileVault.swift: -------------------------------------------------------------------------------- 1 | // 2 | // FileVault.swift 3 | // Pareto Security 4 | // 5 | // Created by Janez Troha on 15/07/2021. 6 | // 7 | 8 | class FileVaultCheck: ParetoCheck { 9 | static let sharedInstance = FileVaultCheck() 10 | override var UUID: String { 11 | "c3aee29a-f16d-4573-a861-b3ba0d860067" 12 | } 13 | 14 | override var TitleON: String { 15 | "FileVault is on" 16 | } 17 | 18 | override var TitleOFF: String { 19 | "FileVault is off" 20 | } 21 | 22 | override func checkPasses() -> Bool { 23 | let output = runCMD(app: "/usr/bin/fdesetup", args: ["status"]) 24 | return output.contains("FileVault is On") 25 | } 26 | } 27 | -------------------------------------------------------------------------------- /Pareto/Checks/System Integrity/Gatekeeper.swift: -------------------------------------------------------------------------------- 1 | // 2 | // Gatekeeper.swift 3 | // Pareto Security 4 | // 5 | // Created by Janez Troha on 15/07/2021. 6 | // 7 | import Foundation 8 | import os.log 9 | 10 | class GatekeeperCheck: ParetoCheck { 11 | static let sharedInstance = GatekeeperCheck() 12 | override var UUID: String { 13 | "b59e172e-6a2d-4309-94ed-11e8722836b3" 14 | } 15 | 16 | override var TitleON: String { 17 | "Gatekeeper is on" 18 | } 19 | 20 | override var TitleOFF: String { 21 | "Gatekeeper is off" 22 | } 23 | 24 | func isSandboxingEnabled() -> Bool { 25 | let environment = ProcessInfo.processInfo.environment 26 | return environment["APP_SANDBOX_CONTAINER_ID"] != nil 27 | } 28 | 29 | override func checkPasses() -> Bool { 30 | let path = "/var/db/SystemPolicy-prefs.plist" 31 | if isSandboxingEnabled() { 32 | os_log("Running in sandbox %{public}s - %{public}s", log: Log.check, UUID, Title) 33 | // if enabled one cannot read system file even with entitlement 34 | if NSDictionary(contentsOfFile: path) != nil { 35 | os_log("Gatekeeper file not found", log: Log.check) 36 | return true 37 | } 38 | return false 39 | } 40 | 41 | // If we are not sandboxed 42 | let dictionary = readDefaultsFile(path: path) 43 | if let enabled = dictionary?.object(forKey: "enabled") as? String { 44 | os_log("Gatekeeper file found, status %{enabled}s", log: Log.check, enabled) 45 | return enabled == "yes" 46 | } 47 | 48 | // falback if global file is not present (probably due to the upgrade) 49 | let output = runCMD(app: "/usr/sbin/spctl", args: ["--status"]) 50 | os_log("spctl fallback, status %{enabled}s", log: Log.check, output) 51 | return output.contains("assessments enabled") 52 | } 53 | } 54 | -------------------------------------------------------------------------------- /Pareto/Checks/System Integrity/OpenWiFi.swift: -------------------------------------------------------------------------------- 1 | // 2 | // OpenWiFi.swift 3 | // Pareto Security 4 | // 5 | // Created by Janez Troha on 28/09/2021. 6 | // 7 | 8 | import CoreWLAN 9 | import Foundation 10 | 11 | class OpenWiFiCheck: ParetoCheck { 12 | static let sharedInstance = OpenWiFiCheck() 13 | 14 | override var UUID: String { 15 | "bcf5196e-6757-422d-8ac3-99ebdb243afa" 16 | } 17 | 18 | override var TitleON: String { 19 | "WiFi connection is secure" 20 | } 21 | 22 | override var TitleOFF: String { 23 | "WiFi connection is not secure" 24 | } 25 | 26 | var isWiFiActive: Bool { 27 | return CWWiFiClient.shared().interface(withName: nil)?.serviceActive() ?? false 28 | } 29 | 30 | var isWiFiSecured: Bool { 31 | return CWWiFiClient.shared().interface(withName: nil)?.security() != CWSecurity.none 32 | } 33 | 34 | override public var isCritical: Bool { 35 | return true 36 | } 37 | 38 | var isRoutedViaVPN: Bool { 39 | let data = runCMD(app: "/usr/sbin/netstat", args: ["-rnt"]) 40 | 41 | for line in data.split(separator: "\n") { 42 | if line.split(separator: " ").count > 3 { 43 | let linedata = String(line) 44 | let regex = try! NSRegularExpression(pattern: " +", options: NSRegularExpression.Options.caseInsensitive) 45 | let range = NSRange(location: 0, length: linedata.count) 46 | let normalized = regex.stringByReplacingMatches(in: linedata, options: [], range: range, withTemplate: "\t") 47 | let info = normalized.split(separator: "\t") 48 | // most vpns use default gateway as indiation that they route over extension 49 | if info[0] == "default", info[1].contains("link#") { 50 | return true 51 | } 52 | // ExpressVPN uses netlink rule to route all traffic over virtual device 53 | if info[0] == "0/1", info[3].contains("utun") { 54 | return true 55 | } 56 | } 57 | } 58 | return false 59 | } 60 | 61 | override func checkPasses() -> Bool { 62 | // Wifi is not active 63 | if !isWiFiActive { 64 | return true 65 | } 66 | return isWiFiSecured || isRoutedViaVPN 67 | } 68 | } 69 | -------------------------------------------------------------------------------- /Pareto/Checks/System Integrity/SecureTerminal.swift: -------------------------------------------------------------------------------- 1 | // 2 | // SecureTerminal.swift 3 | // Pareto Security 4 | // 5 | // Created by Janez Troha on 15/07/2021. 6 | // 7 | 8 | import Foundation 9 | import os.log 10 | import Regex 11 | import Version 12 | 13 | class SecureTerminalCheck: ParetoCheck { 14 | static let sharedInstance = SecureTerminalCheck() 15 | override var UUID: String { 16 | "5cbe1cfd-ff28-4cc7-8998-5d72e608b28d" 17 | } 18 | 19 | override var TitleON: String { 20 | "Terminal uses secure entry" 21 | } 22 | 23 | override var TitleOFF: String { 24 | "Terminal is not using secure entry" 25 | } 26 | 27 | override var hasDebug: Bool { 28 | true 29 | } 30 | 31 | override func debugInfo() -> String { 32 | readDefaultsNative(path: "com.apple.Terminal", key: "SecureKeyboardEntry") ?? "No data" 33 | } 34 | 35 | override func checkPasses() -> Bool { 36 | if let enabled = readDefaultsNative(path: "com.apple.Terminal", key: "SecureKeyboardEntry") { 37 | os_log("SecureKeyboardEntry, status %{enabled}s", log: Log.check, enabled) 38 | return enabled.contains("1") 39 | } 40 | return false 41 | } 42 | } 43 | -------------------------------------------------------------------------------- /Pareto/Checks/System Integrity/SecureiTerm.swift: -------------------------------------------------------------------------------- 1 | // 2 | // SecureiTerm.swift 3 | // Pareto Security 4 | // 5 | // Created by Janez Troha on 15/07/2021. 6 | // 7 | 8 | import Foundation 9 | import os.log 10 | import Regex 11 | import Version 12 | 13 | class SecureiTermCheck: ParetoCheck { 14 | static let sharedInstance = SecureiTermCheck() 15 | override var UUID: String { 16 | "6cbe1cfd-ff28-4cc7-8998-5d72e608b28d" 17 | } 18 | 19 | override var TitleON: String { 20 | "iTerm uses secure entry" 21 | } 22 | 23 | override var TitleOFF: String { 24 | "iTerm is not using secure entry" 25 | } 26 | 27 | override var isRunnable: Bool { 28 | AppiTermCheck.sharedInstance.isInstalled && isActive 29 | } 30 | 31 | override func checkPasses() -> Bool { 32 | if let enabled = readDefaultsNative(path: "com.googlecode.iterm2", key: "Secure Input") { 33 | os_log("Secure Input, status %{enabled}s", log: Log.check, enabled) 34 | return enabled == "1" 35 | } 36 | // can also be missing if it never changed, but defaults to false 37 | return false 38 | } 39 | } 40 | -------------------------------------------------------------------------------- /Pareto/Checks/System Integrity/TimeMachine.swift: -------------------------------------------------------------------------------- 1 | // 2 | // TimeMachine.swift 3 | // Pareto Security 4 | // 5 | // Created by Janez Troha on 15/07/2021. 6 | // 7 | import Foundation 8 | import os.log 9 | 10 | class TimeMachineCheck: ParetoCheck { 11 | static let sharedInstance = TimeMachineCheck() 12 | override var UUID: String { 13 | "355a56c1-1098-4b5d-ac8a-a1e8fc52dcfd" 14 | } 15 | 16 | override var TitleON: String { 17 | "Time Machine is on" 18 | } 19 | 20 | override var TitleOFF: String { 21 | "Time Machine is off" 22 | } 23 | 24 | override public var hasDebug: Bool { 25 | return true 26 | } 27 | 28 | override public func debugInfo() -> String { 29 | let tmutil = runCMD(app: "/usr/bin/tmutil", args: ["destinationinfo"]) 30 | return "tmutil:\n\(tmutil)\nTimeMachine: \(readDefaultsFile(path: "/Library/Preferences/com.apple.TimeMachine.plist") as! [String: Any]? as AnyObject)" 31 | } 32 | 33 | override public var isRunnable: Bool { 34 | guard let config = readDefaultsFile(path: "/Library/Preferences/com.apple.TimeMachine.plist") as! [String: Any]? else { 35 | return false 36 | } 37 | return config.count > 1 && isActive 38 | } 39 | 40 | var isConfigured: Bool { 41 | let status = runCMD(app: "/usr/bin/tmutil", args: ["destinationinfo"]) 42 | return status.contains("ID") && status.contains("Name") 43 | } 44 | 45 | override func checkPasses() -> Bool { 46 | guard let settings = readDefaultsFile(path: "/Library/Preferences/com.apple.TimeMachine.plist") as! [String: Any]? else { 47 | os_log("/Library/Preferences/com.apple.TimeMachine.plist use fallback") 48 | let status = runCMD(app: "/usr/bin/tmutil", args: ["status"]) 49 | return isConfigured && !status.contains("Stopping = 1") 50 | } 51 | let tmConf = TimeMachineConfig(dict: settings) 52 | return tmConf.AutoBackup && !tmConf.Destinations.isEmpty && !tmConf.LastDestinationID.isEmpty 53 | } 54 | } 55 | -------------------------------------------------------------------------------- /Pareto/Checks/System Integrity/TimeMachineHasBackup.swift: -------------------------------------------------------------------------------- 1 | // 2 | // TimeMachine.swift 3 | // Pareto Security 4 | // 5 | // Created by Janez Troha on 15/07/2021. 6 | // 7 | import Foundation 8 | import os.log 9 | 10 | class TimeMachineHasBackupCheck: ParetoCheck { 11 | static let sharedInstance = TimeMachineHasBackupCheck() 12 | override var UUID: String { 13 | "455a56c1-1098-4b5d-ac8a-a1e8fc52dcfd" 14 | } 15 | 16 | override var TitleON: String { 17 | "Time Machine has up to date backup" 18 | } 19 | 20 | override var TitleOFF: String { 21 | "Time Machine is missing up to date backup" 22 | } 23 | 24 | override public var isRunnable: Bool { 25 | return TimeMachineCheck.sharedInstance.isRunnable && isActive 26 | } 27 | 28 | override public var showSettings: Bool { 29 | return readDefaultsFile(path: "/Library/Preferences/com.apple.TimeMachine.plist") as! [String: Any]? != nil 30 | } 31 | 32 | override public var showSettingsWarnDiskAccess: Bool { 33 | return true && readDefaultsFile(path: "/Library/Preferences/com.apple.TimeMachine.plist") as! [String: Any]? == nil 34 | } 35 | 36 | override func checkPasses() -> Bool { 37 | guard let settings = readDefaultsFile(path: "/Library/Preferences/com.apple.TimeMachine.plist") as! [String: Any]? else { 38 | os_log("/Library/Preferences/com.apple.TimeMachine.plist is empty") 39 | hasError = true 40 | return false 41 | } 42 | let tmConf = TimeMachineConfig(dict: settings) 43 | return tmConf.AutoBackup && tmConf.upToDateBackup 44 | } 45 | } 46 | -------------------------------------------------------------------------------- /Pareto/Checks/System Integrity/TimeMachineIsEncrypted.swift: -------------------------------------------------------------------------------- 1 | // 2 | // TimeMachine.swift 3 | // Pareto Security 4 | // 5 | // Created by Janez Troha on 15/07/2021. 6 | // 7 | import Foundation 8 | import os.log 9 | 10 | class TimeMachineIsEncryptedCheck: ParetoCheck { 11 | static let sharedInstance = TimeMachineIsEncryptedCheck() 12 | override var UUID: String { 13 | "555a56c1-1098-4b5d-ac8a-a1e8fc52dcfd" 14 | } 15 | 16 | override var TitleON: String { 17 | "Time Machine backup is encrypted" 18 | } 19 | 20 | override var TitleOFF: String { 21 | "Time Machine backup is not encrypted" 22 | } 23 | 24 | override public var isRunnable: Bool { 25 | if TimeMachineCheck.sharedInstance.isRunnable && isActive { 26 | let dict = readDefaultsFile(path: "/Library/Preferences/com.apple.TimeMachine.plist") as! [String: Any]? 27 | guard let settings = dict else { 28 | return false 29 | } 30 | let tmConf = TimeMachineConfig(dict: settings) 31 | return tmConf.canCheckIsEncryptedBackup 32 | } 33 | 34 | return false 35 | } 36 | 37 | override public var showSettings: Bool { 38 | return readDefaultsFile(path: "/Library/Preferences/com.apple.TimeMachine.plist") as! [String: Any]? != nil 39 | } 40 | 41 | override public var showSettingsWarnDiskAccess: Bool { 42 | return true && readDefaultsFile(path: "/Library/Preferences/com.apple.TimeMachine.plist") as! [String: Any]? == nil 43 | } 44 | 45 | override func checkPasses() -> Bool { 46 | guard let settings = readDefaultsFile(path: "/Library/Preferences/com.apple.TimeMachine.plist") as! [String: Any]? else { 47 | os_log("/Library/Preferences/com.apple.TimeMachine.plist is empty") 48 | hasError = true 49 | return false 50 | } 51 | let tmConf = TimeMachineConfig(dict: settings) 52 | return tmConf.AutoBackup && tmConf.isEncryptedBackup 53 | } 54 | } 55 | -------------------------------------------------------------------------------- /Pareto/Checks/VSCode.swift: -------------------------------------------------------------------------------- 1 | // 2 | // VSCode.swift 3 | // Pareto Security 4 | // 5 | // Created by Janez Troha on 11/11/2021. 6 | // 7 | 8 | import Alamofire 9 | import AppKit 10 | import Combine 11 | import Foundation 12 | import os.log 13 | import OSLog 14 | import Regex 15 | import Version 16 | 17 | class AppVSCodeCheck: AppCheck { 18 | static let sharedInstance = AppVSCodeCheck() 19 | 20 | override var appName: String { "Visual Studio Code" } 21 | override var appMarketingName: String { "Visual Studio Code" } 22 | override var appBundle: String { "com.microsoft.VSCode" } 23 | 24 | override var UUID: String { 25 | "febd20fb-3dec-5834-a0ba-58f3342df58c" 26 | } 27 | 28 | override func getLatestVersion(completion: @escaping (String) -> Void) { 29 | let url = viaEdgeCache("https://code.visualstudio.com/updates/") 30 | let versionRegex = Regex("Update ([\\.\\d]+)") 31 | os_log("Requesting %{public}s", url) 32 | 33 | AF.request(url).responseString(queue: AppCheck.queue, completionHandler: { response in 34 | if response.error == nil { 35 | let html = response.value ?? "Update 1.12.1" 36 | let versions = versionRegex.allMatches(in: html).map { $0.groups.first!.value } 37 | let version = versions.sorted(by: { $0.lowercased() < $1.lowercased() }).last ?? "1.12.1" 38 | os_log("%{public}s version=%{public}s", self.appBundle, version) 39 | completion(version) 40 | } else { 41 | os_log("%{public}s failed: %{public}s", self.appBundle, response.error.debugDescription) 42 | self.hasError = true 43 | completion("0.0.0") 44 | } 45 | 46 | }) 47 | } 48 | } 49 | -------------------------------------------------------------------------------- /Pareto/Checks/Zoom.swift: -------------------------------------------------------------------------------- 1 | // 2 | // Zoom.swift 3 | // Pareto Security 4 | // 5 | // Created by Janez Troha on 11/11/2021. 6 | // 7 | 8 | import Alamofire 9 | import AppKit 10 | import Combine 11 | import Foundation 12 | import os.log 13 | import OSLog 14 | import Regex 15 | import Version 16 | 17 | class AppZoomCheck: AppCheck { 18 | static let sharedInstance = AppZoomCheck() 19 | 20 | override var appName: String { "Zoom.us" } 21 | override var appMarketingName: String { "Zoom" } 22 | override var appBundle: String { "us.zoom.xos" } 23 | 24 | override var UUID: String { 25 | "c65cb89b-6c53-54af-995c-ec8ba358ecf6" 26 | } 27 | 28 | override var currentVersion: Version { 29 | if applicationPath == nil { 30 | return Version(0, 0, 0) 31 | } 32 | let v = appVersion(path: applicationPath ?? "1.2.3 (1234)")!.split(separator: " ") 33 | return Version(v[0]) ?? Version(0, 0, 0) 34 | } 35 | 36 | override func getLatestVersion(completion: @escaping (String) -> Void) { 37 | #if arch(arm64) 38 | let url = "https://zoom.us/client/latest/Zoom.pkg?archType=arm64" 39 | #else 40 | let url = "https://zoom.us/client/latest/Zoom.pkg" 41 | #endif 42 | let versionRegex = Regex("prod\\/(\\d+\\.\\d+\\.\\d+).\\d+\\/") 43 | os_log("Requesting %{public}s", url) 44 | 45 | AF.request(url, method: .head).redirect(using: Redirector(behavior: .doNotFollow)).response(queue: AppCheck.queue, completionHandler: { response in 46 | 47 | if response.error == nil { 48 | let location = response.response?.allHeaderFields["Location"] as? String ?? "https://cdn.zoom.us/prod/1.8.6.2879/ZoomInstallerIT.pkg" 49 | let version = versionRegex.firstMatch(in: location)?.groups.first?.value ?? "1.8.6.2879" 50 | os_log("%{public}s version=%{public}s", self.appBundle, version) 51 | completion(version) 52 | } else { 53 | os_log("%{public}s failed: %{public}s", self.appBundle, response.error.debugDescription) 54 | self.hasError = true 55 | completion("0.0.0") 56 | } 57 | 58 | }) 59 | } 60 | } 61 | -------------------------------------------------------------------------------- /Pareto/Checks/macOS Updates/AutoUpdateAppCheck.swift: -------------------------------------------------------------------------------- 1 | // 2 | // AutoUpdateAppCheck.swift 3 | // Pareto Security 4 | // 5 | // Created by Janez Troha on 15/07/2021. 6 | // 7 | 8 | import Foundation 9 | import os.log 10 | import Regex 11 | import Version 12 | 13 | class AutoUpdateAppCheck: ParetoCheck { 14 | static let sharedInstance = AutoUpdateAppCheck() 15 | override var UUID: String { 16 | "940e7a88-2dd4-4a50-bf9c-3d842e0a2c94" 17 | } 18 | 19 | override var TitleON: String { 20 | "App Store updates are automatic" 21 | } 22 | 23 | override var TitleOFF: String { 24 | "App Store updates are not automatic" 25 | } 26 | 27 | override func checkPasses() -> Bool { 28 | let path = "/Library/Preferences/com.apple.commerce.plist" 29 | if let enabled = readDefaultsNative(path: path, key: "AutoUpdate") { 30 | os_log("AutoUpdate, status %{enabled}s", log: Log.check, enabled) 31 | return enabled == "1" 32 | } 33 | // can also be missing if it never changed, but defaults to true 34 | return true 35 | } 36 | } 37 | -------------------------------------------------------------------------------- /Pareto/Checks/macOS Updates/AutoUpdateCheck.swift: -------------------------------------------------------------------------------- 1 | // 2 | // AutoUpdateCheck.swift 3 | // Pareto Security 4 | // 5 | // Created by Janez Troha on 15/07/2021. 6 | // 7 | 8 | import Foundation 9 | import os.log 10 | import Regex 11 | import Version 12 | 13 | class AutoUpdateCheck: ParetoCheck { 14 | static let sharedInstance = AutoUpdateCheck() 15 | override var UUID: String { 16 | "dba1dea4-8c96-4b33-95f4-63d68bd0387e" 17 | } 18 | 19 | override var TitleON: String { 20 | "macOS updates are automatic" 21 | } 22 | 23 | override var TitleOFF: String { 24 | "macOS updates are not automatic" 25 | } 26 | 27 | override func checkPasses() -> Bool { 28 | let path = "/Library/Preferences/com.apple.SoftwareUpdate" 29 | if let enabled = readDefaultsNative(path: path, key: "AutomaticallyInstallMacOSUpdates") { 30 | os_log("AutomaticallyInstallMacOSUpdates, status %{public}s", log: Log.check, enabled) 31 | return enabled == "1" 32 | } 33 | // can also be missing if it never changed, but defaults to true 34 | return true 35 | } 36 | } 37 | -------------------------------------------------------------------------------- /Pareto/Checks/macOS Updates/AutomaticDownloadCheck.swift: -------------------------------------------------------------------------------- 1 | // 2 | // AutomaticDownloadCheck.swift 3 | // Pareto Security 4 | // 5 | // Created by Janez Troha on 15/07/2021. 6 | // 7 | 8 | import Foundation 9 | import os.log 10 | import Regex 11 | import Version 12 | 13 | class AutomaticDownloadCheck: ParetoCheck { 14 | static let sharedInstance = AutomaticDownloadCheck() 15 | override var UUID: String { 16 | "1fa68e99-e152-4ac1-8362-e6b7c54cc4b4" 17 | } 18 | 19 | override var TitleON: String { 20 | "Downloading new updates is enabled" 21 | } 22 | 23 | override var TitleOFF: String { 24 | "Downloading new updates is disabled" 25 | } 26 | 27 | override func checkPasses() -> Bool { 28 | let path = "/Library/Preferences/com.apple.SoftwareUpdate" 29 | if let enabled = readDefaultsNative(path: path, key: "AutomaticDownload") { 30 | os_log("AutomaticDownload, status %{enabled}s", log: Log.check, enabled) 31 | return enabled == "1" 32 | } 33 | // can also be missing if it never changed, but defaults to true 34 | return true 35 | } 36 | } 37 | -------------------------------------------------------------------------------- /Pareto/Checks/macOS Updates/AutomaticInstallCheck.swift: -------------------------------------------------------------------------------- 1 | // 2 | // AutomaticInstallCheck.swift 3 | // Pareto Security 4 | // 5 | // Created by Janez Troha on 15/07/2021. 6 | // 7 | 8 | import Foundation 9 | import os.log 10 | import Regex 11 | import Version 12 | 13 | class AutomaticInstallCheck: ParetoCheck { 14 | static let sharedInstance = AutomaticInstallCheck() 15 | override var UUID: String { 16 | "1fa68e99-e152-4ac1-8362-e6b7c54cc4b4" 17 | } 18 | 19 | override var TitleON: String { 20 | "Automatic install of new updates" 21 | } 22 | 23 | override var TitleOFF: String { 24 | "Automatic install of new updates is disabled" 25 | } 26 | 27 | override func checkPasses() -> Bool { 28 | let path = "/Library/Preferences/com.apple.SoftwareUpdate" 29 | if let enabled = readDefaultsNative(path: path, key: "AutomaticallyInstallMacOSUpdates") { 30 | os_log("AutomaticDownload, status %{enabled}s", log: Log.check, enabled) 31 | return enabled == "1" 32 | } 33 | return false 34 | } 35 | } 36 | -------------------------------------------------------------------------------- /Pareto/Checks/macOS Updates/SecurityUpdateCheck.swift: -------------------------------------------------------------------------------- 1 | // 2 | // SecurityUpdateCheck.swift 3 | // Pareto Security 4 | // 5 | // Created by Janez Troha on 15/07/2021. 6 | // 7 | 8 | import Foundation 9 | import os.log 10 | import Regex 11 | import Version 12 | 13 | class SecurityUpdateCheck: ParetoCheck { 14 | static let sharedInstance = SecurityUpdateCheck() 15 | override var UUID: String { 16 | "c491097c-6b8a-4cad-ae08-87c8bdfce100" 17 | } 18 | 19 | override var TitleON: String { 20 | "Security updates are enabled" 21 | } 22 | 23 | override var TitleOFF: String { 24 | "Security updates are disabled" 25 | } 26 | 27 | override func checkPasses() -> Bool { 28 | let path = "/Library/Preferences/com.apple.SoftwareUpdate" 29 | if let enabled = readDefaultsNative(path: path, key: "AutomaticCheckEnabled") { 30 | os_log("AutomaticCheckEnabled, status %{enabled}s", log: Log.check, enabled) 31 | return enabled == "1" 32 | } 33 | // can also be missing if it never changed, but defaults to true 34 | return true 35 | } 36 | } 37 | -------------------------------------------------------------------------------- /Pareto/Checks/macOS Updates/SystemUpdatesCheck.swift: -------------------------------------------------------------------------------- 1 | // 2 | // SystemUpdatesCheck.swift 3 | // Pareto Security 4 | // 5 | // Created by Janez Troha on 15/07/2021. 6 | // 7 | 8 | import Foundation 9 | import os.log 10 | import Regex 11 | import Version 12 | 13 | class SystemUpdatesCheck: ParetoCheck { 14 | static let sharedInstance = SystemUpdatesCheck() 15 | override var UUID: String { 16 | "b56b3b65-b296-489d-bbf2-defc9fee8abd" 17 | } 18 | 19 | override var TitleON: String { 20 | "Security updates are automatic" 21 | } 22 | 23 | override var TitleOFF: String { 24 | "Security updates are not automatic" 25 | } 26 | 27 | override func checkPasses() -> Bool { 28 | let path = "/Library/Preferences/com.apple.SoftwareUpdate" 29 | 30 | // can also be missing if it never changed, but defaults to true 31 | var CriticalUpdateInstall = true 32 | var ConfigDataInstall = true 33 | 34 | if let CriticalUpdateInstallRaw = readDefaultsNative(path: path, key: "CriticalUpdateInstall") { 35 | CriticalUpdateInstall = (CriticalUpdateInstallRaw == "1") 36 | } 37 | if let ConfigDataInstallRaw = readDefaultsNative(path: path, key: "ConfigDataInstall") { 38 | ConfigDataInstall = (ConfigDataInstallRaw == "1") 39 | } 40 | 41 | return CriticalUpdateInstall && ConfigDataInstall 42 | } 43 | } 44 | -------------------------------------------------------------------------------- /Pareto/Checks/macOS Updates/macOSVersion.swift: -------------------------------------------------------------------------------- 1 | // 2 | // macOSVersionCheck.swift 3 | // Pareto Security 4 | // 5 | // Created by Janez Troha on 15/07/2021. 6 | // 7 | import Alamofire 8 | import Foundation 9 | import os.log 10 | import Regex 11 | import Version 12 | 13 | class MacOSVersionCheck: ParetoCheck { 14 | static let sharedInstance = MacOSVersionCheck() 15 | override var UUID: String { 16 | "284162c2-f911-4b5e-8c81-30b2cf1ba73f" 17 | } 18 | 19 | override var TitleON: String { 20 | "macOS is up-to-date" 21 | } 22 | 23 | override var TitleOFF: String { 24 | "macOS is not up-to-date" 25 | } 26 | 27 | var currentVersion: Version { 28 | let os = ProcessInfo.processInfo.operatingSystemVersion 29 | return Version(os.majorVersion, os.minorVersion, os.patchVersion) 30 | } 31 | 32 | func getLatestVersion(doc: String, completion: @escaping (String) -> Void) { 33 | let url = "https://support.apple.com/en-us/\(doc)" 34 | let versionRegex = Regex("macOS.+ ([\\.\\d]+)<\\/h2>") 35 | os_log("Requesting %{public}s", url) 36 | AF.request(url).responseString(queue: AppCheck.queue, completionHandler: { response in 37 | if response.error == nil { 38 | let html = response.value ?? "

macOS 1.2.5

" 39 | var version = versionRegex.firstMatch(in: html)?.groups.first?.value ?? "1.2.3" 40 | if version.components(separatedBy: ".").count == 2 { 41 | version = "\(version).0" 42 | } 43 | completion(version) 44 | } else { 45 | completion("0.0.0") 46 | } 47 | 48 | }) 49 | } 50 | 51 | var latestXX: Version { 52 | var doc = "HT211896" 53 | if #available(macOS 12, *) { 54 | doc = "HT212585" 55 | } 56 | if #available(macOS 13, *) { 57 | doc = "HT213268" 58 | } 59 | if #available(macOS 14, *) { 60 | doc = "HT213895" 61 | } 62 | if #available(macOS 15, *) { 63 | doc = "120283" 64 | } 65 | var tempVersion = "0.0.0" 66 | let lock = DispatchSemaphore(value: 0) 67 | getLatestVersion(doc: doc) { version in 68 | tempVersion = version 69 | lock.signal() 70 | } 71 | lock.wait() 72 | return Version(tempVersion) ?? Version(0, 0, 0) 73 | } 74 | 75 | override func checkPasses() -> Bool { 76 | return currentVersion >= latestXX 77 | } 78 | } 79 | -------------------------------------------------------------------------------- /Pareto/Extensions/Bundle.swift: -------------------------------------------------------------------------------- 1 | // 2 | // Bundle.swift 3 | // Bundle 4 | // 5 | // Created by Janez Troha on 27/08/2021. 6 | // 7 | 8 | import Foundation 9 | 10 | extension Bundle { 11 | var isCodeSigned: Bool { 12 | return !runCMD(app: "/usr/bin/codesign", args: ["-dv", bundlePath]).contains("Error") 13 | } 14 | 15 | var codeSigningIdentity: String? { 16 | let lines = runCMD(app: "/usr/bin/codesign", args: ["-dvvv", bundlePath]).split(separator: "\n") 17 | for line in lines { 18 | if line.hasPrefix("Authority=") { 19 | return String(line.dropFirst(10)) 20 | } 21 | } 22 | return nil 23 | } 24 | } 25 | -------------------------------------------------------------------------------- /Pareto/Extensions/ButtonStyle.swift: -------------------------------------------------------------------------------- 1 | // 2 | // ButtonStyle.swift 3 | // ButtonStyle 4 | // 5 | // Created by Janez Troha on 19/09/2021. 6 | // 7 | 8 | import Foundation 9 | import SwiftUI 10 | 11 | struct HighlightButtonStyle: ButtonStyle { 12 | let hPadding: CGFloat 13 | let vPadding: CGFloat 14 | let cornerRadius: CGFloat 15 | let color: Color 16 | let font: Font 17 | 18 | public init( 19 | h: CGFloat = 15, 20 | v: CGFloat = 12, 21 | cornerRadius: CGFloat = 4, 22 | color: Color, 23 | font: Font = .body 24 | ) { 25 | hPadding = h 26 | vPadding = v 27 | self.cornerRadius = cornerRadius 28 | self.color = color 29 | self.font = font 30 | } 31 | 32 | public func makeBody(configuration: Self.Configuration) -> some View { 33 | configuration.label 34 | .padding(.horizontal, hPadding) 35 | .padding(.vertical, vPadding) 36 | .contentShape(Rectangle()) 37 | .font(font) 38 | .frame(minWidth: 0, maxWidth: .infinity, alignment: .center) 39 | .background(RoundedRectangle(cornerRadius: cornerRadius, style: .continuous) 40 | .fill(configuration.isPressed ? Color.separator : color)) 41 | .cornerRadius(cornerRadius) 42 | } 43 | } 44 | -------------------------------------------------------------------------------- /Pareto/Extensions/Color.swift: -------------------------------------------------------------------------------- 1 | // 2 | // Color.swift 3 | // Color 4 | // 5 | // Created by Janez Troha on 18/09/2021. 6 | // 7 | 8 | import Foundation 9 | import SwiftUI 10 | 11 | @available(iOS 13.0, OSX 10.15, tvOS 13.0, watchOS 6.0, *) 12 | public extension SwiftUI.Color { 13 | init(hex: Int, alpha: Double = 1) { 14 | let components = ( 15 | R: Double((hex >> 16) & 0xFF) / 255, 16 | G: Double((hex >> 08) & 0xFF) / 255, 17 | B: Double((hex >> 00) & 0xFF) / 255 18 | ) 19 | 20 | self.init( 21 | .sRGB, 22 | red: components.R, 23 | green: components.G, 24 | blue: components.B, 25 | opacity: alpha 26 | ) 27 | } 28 | } 29 | 30 | public extension Color { 31 | static func dynamic(dark: Int, light: Int) -> Color { 32 | let isDark = UserDefaults.standard.string(forKey: "AppleInterfaceStyle") == "Dark" 33 | return isDark ? Color(hex: dark) : Color(hex: light) 34 | } 35 | 36 | // MARK: - App Colors 37 | 38 | static var mainColor = Color(hex: 0x4C84E2) 39 | 40 | // MARK: - Text Colors 41 | 42 | static let placeholderText = Color(NSColor.placeholderTextColor) 43 | 44 | // MARK: - Label Colors 45 | 46 | static let label = Color(NSColor.labelColor) 47 | static let secondaryLabel = Color(NSColor.secondaryLabelColor) 48 | static let tertiaryLabel = Color(NSColor.tertiaryLabelColor) 49 | static let quaternaryLabel = Color(NSColor.quaternaryLabelColor) 50 | 51 | // MARK: - Gray Colors 52 | 53 | static let systemGray = Color(NSColor.systemGray) 54 | static let systemGray2 = Color.dynamic(dark: 0x636366, light: 0xAEAEB2) 55 | static let systemGray3 = Color.dynamic(dark: 0x48484A, light: 0xC7C7CC) 56 | static let systemGray4 = Color.dynamic(dark: 0x3A3A3C, light: 0xD1D1D6) 57 | static let systemGray5 = Color.dynamic(dark: 0x2C2C2E, light: 0xE5E5EA) 58 | static let systemGray6 = Color.dynamic(dark: 0x1C1C1E, light: 0xF2F2F7) 59 | 60 | // MARK: - Other Colors 61 | 62 | static let separator = Color(NSColor.separatorColor) 63 | static let link = Color(NSColor.linkColor) 64 | 65 | // MARK: System Colors 66 | 67 | static let systemBlue = Color(NSColor.systemBlue) 68 | static let systemPurple = Color(NSColor.systemPurple) 69 | static let systemGreen = Color(NSColor.systemGreen) 70 | static let systemYellow = Color(NSColor.systemYellow) 71 | static let systemOrange = Color(NSColor.systemOrange) 72 | static let systemPink = Color(NSColor.systemPink) 73 | static let systemRed = Color(NSColor.systemRed) 74 | static let systemTeal = Color(NSColor.systemTeal) 75 | static let systemIndigo = Color(NSColor.systemIndigo) 76 | } 77 | -------------------------------------------------------------------------------- /Pareto/Extensions/Date.swift: -------------------------------------------------------------------------------- 1 | import Foundation 2 | 3 | extension Date { 4 | public static let DayInMs = (60 * 60 * 24 * 1000) 5 | public static let HourInMs = (60 * 60 * 1000) 6 | 7 | func currentTimeMs() -> Int { 8 | return Int(timeIntervalSince1970 * 1000) 9 | } 10 | 11 | static func fromTimeStamp(timeStamp: Int) -> Date { 12 | return NSDate(timeIntervalSince1970: TimeInterval(timeStamp / 1000)) as Date 13 | } 14 | 15 | func timeAgoDisplay() -> String { 16 | let formatter = RelativeDateTimeFormatter() 17 | formatter.unitsStyle = .full 18 | return formatter.localizedString(for: self, relativeTo: Date()) 19 | } 20 | 21 | func as3339String() -> String { 22 | let RFC3339DateFormatter = DateFormatter() 23 | RFC3339DateFormatter.locale = Locale(identifier: "en_US_POSIX") 24 | RFC3339DateFormatter.dateFormat = "yyyy-MM-dd'T'HH:mm:ssZZZZZ" 25 | RFC3339DateFormatter.timeZone = TimeZone(secondsFromGMT: 0) 26 | 27 | return RFC3339DateFormatter.string(from: self) 28 | } 29 | } 30 | -------------------------------------------------------------------------------- /Pareto/Extensions/NSBackgroundActivityScheduler.swift: -------------------------------------------------------------------------------- 1 | // 2 | // NSBackgroundActivityScheduler.swift 3 | // NSBackgroundActivityScheduler 4 | // 5 | // Created by Janez Troha on 25/08/2021. 6 | // 7 | 8 | import Foundation 9 | import SwiftUI 10 | 11 | extension NSBackgroundActivityScheduler { 12 | static func repeating(withName name: String, withInterval: TimeInterval, _ fn: @escaping (NSBackgroundActivityScheduler.CompletionHandler) -> Void) { 13 | let activity = NSBackgroundActivityScheduler(identifier: "\(Bundle.main.bundleIdentifier!).\(name)") 14 | activity.repeats = true 15 | activity.interval = withInterval 16 | activity.qualityOfService = .userInteractive 17 | activity.tolerance = TimeInterval(1) 18 | activity.schedule { (completion: NSBackgroundActivityScheduler.CompletionHandler) in 19 | fn(completion) 20 | } 21 | } 22 | } 23 | -------------------------------------------------------------------------------- /Pareto/Extensions/NSImage.swift: -------------------------------------------------------------------------------- 1 | import AppKit 2 | import Combine 3 | import Foundation 4 | import os.log 5 | import SwiftUI 6 | 7 | extension NSImage { 8 | static func SF(name: String) -> NSImage { 9 | let icon = NSImage(systemSymbolName: name, accessibilityDescription: nil)! 10 | icon.isTemplate = true 11 | return icon 12 | } 13 | 14 | func tint(color: NSColor) -> NSImage { 15 | if isTemplate == false { 16 | return self 17 | } 18 | 19 | guard let image = copy() as? NSImage else { 20 | return self 21 | } 22 | 23 | image.lockFocus() 24 | 25 | color.set() 26 | 27 | let imageRect = NSRect(origin: .zero, size: image.size) 28 | imageRect.fill(using: .sourceIn) 29 | 30 | image.unlockFocus() 31 | image.isTemplate = false 32 | 33 | return image 34 | } 35 | } 36 | -------------------------------------------------------------------------------- /Pareto/Extensions/NSWorkspace.swift: -------------------------------------------------------------------------------- 1 | // 2 | // NSWorkspace.swift 3 | // NSWorkspace 4 | // 5 | // Created by Janez Troha on 25/08/2021. 6 | // 7 | 8 | import Foundation 9 | import SwiftUI 10 | 11 | extension NSWorkspace { 12 | static func onWakeup(_ fn: @escaping (Notification) -> Void) { 13 | NSWorkspace.shared.notificationCenter.addObserver(forName: NSWorkspace.didWakeNotification, 14 | object: nil, 15 | queue: nil, 16 | using: fn) 17 | } 18 | 19 | static func onSleep(_ fn: @escaping (Notification) -> Void) { 20 | NSWorkspace.shared.notificationCenter.addObserver(forName: NSWorkspace.willSleepNotification, 21 | object: nil, 22 | queue: nil, 23 | using: fn) 24 | } 25 | } 26 | -------------------------------------------------------------------------------- /Pareto/Extensions/NetworkHandler.swift: -------------------------------------------------------------------------------- 1 | // 2 | // NetworkHandler.swift 3 | // Pareto Security 4 | // 5 | // Created by Janez Troha on 28/09/2021. 6 | // 7 | 8 | import Foundation 9 | import Network 10 | 11 | protocol NetworkHandlerObserver: AnyObject { 12 | func statusDidChange(status: NWPath.Status) 13 | } 14 | 15 | class NetworkHandler { 16 | struct NetworkHandlerObservation { 17 | weak var observer: NetworkHandlerObserver? 18 | } 19 | 20 | /// NWPathMonitor instance 21 | private var monitor = NWPathMonitor() 22 | 23 | /// NetworkHandler shared instance 24 | private static let _sharedInstance = NetworkHandler() 25 | 26 | /// Observer collection 27 | private var observers = [ObjectIdentifier: NetworkHandlerObservation]() 28 | 29 | /// Current NWPathMonitor Status 30 | var currentStatus: NWPath.Status { 31 | return monitor.currentPath.status 32 | } 33 | 34 | class func sharedInstance() -> NetworkHandler { 35 | return _sharedInstance 36 | } 37 | 38 | init() { 39 | monitor.pathUpdateHandler = { [unowned self] path in 40 | /// Initialise observers 41 | for (id, observations) in self.observers { 42 | /// If any observer is nil, remove it from the list of observers 43 | guard let observer = observations.observer else { 44 | self.observers.removeValue(forKey: id) 45 | continue 46 | } 47 | 48 | /// Async execution of statusDidChange 49 | DispatchQueue.main.async { 50 | observer.statusDidChange(status: path.status) 51 | } 52 | } 53 | } 54 | monitor.start(queue: DispatchQueue.global(qos: .background)) 55 | } 56 | 57 | /// Add Observer 58 | func addObserver(observer: NetworkHandlerObserver) { 59 | let id = ObjectIdentifier(observer) 60 | observers[id] = NetworkHandlerObservation(observer: observer) 61 | } 62 | 63 | /// Remove Observer 64 | func removeObserver(observer: NetworkHandlerObserver) { 65 | let id = ObjectIdentifier(observer) 66 | observers.removeValue(forKey: id) 67 | } 68 | } 69 | -------------------------------------------------------------------------------- /Pareto/Extensions/SSHCheck.swift: -------------------------------------------------------------------------------- 1 | // 2 | // SSHCheck.swift 3 | // Pareto Security 4 | // 5 | // Created by Lane Shukhov on 20.02.2023. 6 | // 7 | 8 | import Foundation 9 | import os.log 10 | 11 | class SSHCheck: ParetoCheck { 12 | let sshPath = FileManager.default.homeDirectoryForCurrentUser.appendingPathComponent(".ssh").resolvingSymlinksInPath() 13 | private var sshKeygenPath = "" 14 | 15 | private var itExistsCache = [String: Bool]() 16 | 17 | func itExists(_ path: String) -> Bool { 18 | if let cachedResult = itExistsCache[path] { 19 | return cachedResult 20 | } 21 | let exists = FileManager.default.fileExists(atPath: path) 22 | itExistsCache[path] = exists 23 | return exists 24 | } 25 | 26 | func getSSHKeygenPath() -> String { 27 | if !sshKeygenPath.isEmpty { 28 | return sshKeygenPath 29 | } 30 | 31 | if itExists("/opt/homebrew/bin/ssh-keygen") { 32 | sshKeygenPath = "/opt/homebrew/bin/ssh-keygen" 33 | } else { 34 | sshKeygenPath = "/usr/bin/ssh-keygen" 35 | } 36 | 37 | return sshKeygenPath 38 | } 39 | 40 | override var isRunnable: Bool { 41 | if !itExists(getSSHKeygenPath()) { 42 | os_log("Not found /opt/homebrew/bin/ssh-keygen or /usr/bin/ssh-keygen, check disabled", log: Log.check) 43 | } 44 | if !itExists(sshPath.path) { 45 | os_log("Not found ~/.ssh, check disabled", log: Log.check) 46 | } 47 | return itExists(getSSHKeygenPath()) && itExists(sshPath.path) && isActive 48 | } 49 | } 50 | -------------------------------------------------------------------------------- /Pareto/Extensions/String.swift: -------------------------------------------------------------------------------- 1 | // 2 | // String.swift 3 | // String 4 | // 5 | // Created by Janez Troha on 16/09/2021. 6 | // 7 | 8 | import Foundation 9 | 10 | extension String { 11 | var isUUID: Bool { 12 | return UUID(uuidString: self) != nil 13 | } 14 | 15 | /// Escapes the string for safe shell usage. 16 | func shellEscaped() -> String { 17 | // Replace any single quote with an escaped version 18 | let escaped = replacingOccurrences(of: "'", with: "'\\''") 19 | return "'\(escaped)'" 20 | } 21 | } 22 | -------------------------------------------------------------------------------- /Pareto/Extensions/Trim.swift: -------------------------------------------------------------------------------- 1 | // 2 | // Trim.swift 3 | // Pareto Security 4 | // 5 | // Created by Janez Troha on 22. 7. 24. 6 | // 7 | 8 | extension String { 9 | func trim() -> String { 10 | return replacingOccurrences(of: "\n", with: "").replacingOccurrences(of: "\t", with: "") 11 | } 12 | } 13 | -------------------------------------------------------------------------------- /Pareto/Extensions/URL.swift: -------------------------------------------------------------------------------- 1 | // 2 | // URL.swift 3 | // URL 4 | // 5 | // Created by Janez Troha on 10/09/2021. 6 | // 7 | 8 | import Foundation 9 | 10 | extension URL { 11 | func queryParams() -> [String: String] { 12 | let queryItems = URLComponents(url: self, resolvingAgainstBaseURL: false)?.queryItems 13 | let queryTuples: [(String, String)] = queryItems?.compactMap { 14 | guard let value = $0.value else { return nil } 15 | return ($0.name, value) 16 | } ?? [] 17 | return Dictionary(uniqueKeysWithValues: queryTuples) 18 | } 19 | 20 | var attributes: [FileAttributeKey: Any]? { 21 | do { 22 | return try FileManager.default.attributesOfItem(atPath: path) 23 | } catch let error as NSError { 24 | print("FileAttribute error: \(error)") 25 | } 26 | return nil 27 | } 28 | 29 | var fileSize: Int { 30 | return attributes?[.size] as? Int ?? Int(0) 31 | } 32 | } 33 | -------------------------------------------------------------------------------- /Pareto/Extensions/User.swift: -------------------------------------------------------------------------------- 1 | // 2 | // User.swift 3 | // Pareto Security 4 | // 5 | // Created by Janez Troha on 25/08/2022. 6 | // 7 | 8 | import Foundation 9 | 10 | struct SystemUser { 11 | static let current = SystemUser() 12 | var isAdmin: Bool { 13 | let groups = runCMD(app: "/usr/bin/id", args: ["-Gn", NSUserName()]).components(separatedBy: " ") 14 | return groups.contains("admin") 15 | } 16 | } 17 | -------------------------------------------------------------------------------- /Pareto/Flags.swift: -------------------------------------------------------------------------------- 1 | // 2 | // Flags.swift 3 | // Flags 4 | // 5 | // Created by Janez Troha on 14/09/2021. 6 | // 7 | 8 | import Alamofire 9 | import Combine 10 | import Foundation 11 | import os.log 12 | 13 | class FlagsUpdater: ObservableObject { 14 | @Published var dashboardMenu: Bool = true 15 | @Published var dashboardMenuAll: Bool = true 16 | @Published var useEdgeCache: Bool = true 17 | @Published var teamAPI: Bool = true 18 | @Published var slowerTeamUpdate: Bool = true 19 | @Published var setappUpdate: Int = 4 20 | } 21 | -------------------------------------------------------------------------------- /Pareto/Info.plist: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | CFBundleDevelopmentRegion 6 | $(DEVELOPMENT_LANGUAGE) 7 | CFBundleExecutable 8 | $(EXECUTABLE_NAME) 9 | CFBundleIdentifier 10 | $(PRODUCT_BUNDLE_IDENTIFIER) 11 | CFBundleInfoDictionaryVersion 12 | 6.0 13 | CFBundleName 14 | Pareto Security 15 | CFBundlePackageType 16 | $(PRODUCT_BUNDLE_PACKAGE_TYPE) 17 | CFBundleShortVersionString 18 | $(MARKETING_VERSION) 19 | CFBundleURLTypes 20 | 21 | 22 | CFBundleURLSchemes 23 | 24 | paretosecurity 25 | 26 | 27 | 28 | CFBundleVersion 29 | 5709 30 | LSApplicationCategoryType 31 | public.app-category.utilities 32 | LSMinimumSystemVersion 33 | $(MACOSX_DEPLOYMENT_TARGET) 34 | LSUIElement 35 | 36 | LaunchOnlyOnce 37 | 38 | NSAppleEventsUsageDescription 39 | Pareto needs access to your System Events to run some checks 40 | NSSupportsAutomaticTermination 41 | 42 | NSSupportsSuddenTermination 43 | 44 | NSSystemAdministrationUsageDescription 45 | The requested operation in Pareto Security requires elevation to be executed 46 | NSUpdateSecurityPolicy 47 | 48 | AllowProcesses 49 | 50 | MEHY5QF425 51 | 52 | com.setapp.DesktopClient.SetappAgent 53 | 54 | 55 | 56 | com.apple.security.files.bookmarks.app-scope 57 | 58 | com.apple.security.files.user-selected.read-write 59 | 60 | 61 | 62 | -------------------------------------------------------------------------------- /Pareto/Log.swift: -------------------------------------------------------------------------------- 1 | import os 2 | 3 | private let subsystem = "niteo.co.Pareto" 4 | 5 | enum Log { 6 | static let app = OSLog(subsystem: subsystem, category: "App") 7 | static let check = OSLog(subsystem: subsystem, category: "Check") 8 | static let api = OSLog(subsystem: subsystem, category: "API") 9 | } 10 | -------------------------------------------------------------------------------- /Pareto/Models/Apps.swift: -------------------------------------------------------------------------------- 1 | // 2 | // Apps.swift 3 | // Pareto Security 4 | // 5 | // Created by Janez Troha on 14. 03. 24. 6 | // 7 | 8 | import Foundation 9 | 10 | struct PublicApp: Codable { 11 | let name: String 12 | let bundle: String 13 | let version: String 14 | 15 | static var all: [PublicApp] { 16 | var detectedApps: [PublicApp] = [] 17 | let allApps = try! FileManager.default.contentsOfDirectory(at: URL(string: "/Applications")!, includingPropertiesForKeys: [.isApplicationKey]) 18 | for app in allApps { 19 | let plist = PublicApp.readPlistFile(fileURL: app.appendingPathComponent("Contents/Info.plist")) 20 | if let appName = plist?["CFBundleName"] as? String, 21 | let appBundle = plist?["CFBundleIdentifier"] as? String { 22 | let bundleApp = PublicApp( 23 | name: appName, 24 | bundle: appBundle, 25 | version: plist?["CFBundleShortVersionString"] as? String ?? "Unknown" 26 | ) 27 | detectedApps.append(bundleApp) 28 | } 29 | } 30 | 31 | // user apps 32 | let homeDirURL = FileManager.default.homeDirectoryForCurrentUser 33 | let localPath = URL(fileURLWithPath: "\(homeDirURL.path)/Applications/") 34 | if (try? localPath.checkResourceIsReachable()) ?? false { 35 | let userApps = try! FileManager.default.contentsOfDirectory(at: localPath, includingPropertiesForKeys: [.isApplicationKey]) 36 | for app in userApps { 37 | let plist = PublicApp.readPlistFile(fileURL: app.appendingPathComponent("Contents/Info.plist")) 38 | if let appName = plist?["CFBundleName"] as? String, 39 | let appBundle = plist?["CFBundleIdentifier"] as? String { 40 | let bundleApp = PublicApp( 41 | name: appName, 42 | bundle: appBundle, 43 | version: plist?["CFBundleShortVersionString"] as? String ?? "Unknown" 44 | ) 45 | detectedApps.append(bundleApp) 46 | } 47 | } 48 | } 49 | 50 | return detectedApps 51 | } 52 | 53 | static func readPlistFile(fileURL: URL) -> [String: Any]? { 54 | guard let data = try? Data(contentsOf: fileURL) else { 55 | return nil 56 | } 57 | guard let result = try? PropertyListSerialization.propertyList(from: data, options: [], format: nil) as? [String: Any] else { 58 | return nil 59 | } 60 | return result 61 | } 62 | 63 | static func asJSON() -> String? { 64 | var export: [PublicApp] = [] 65 | 66 | for app in PublicApp.all.sorted(by: { lha, rha in 67 | lha.name.lowercased() < rha.name.lowercased() 68 | }) { 69 | export.append(app) 70 | } 71 | 72 | let jsonEncoder = JSONEncoder() 73 | jsonEncoder.outputFormatting = .prettyPrinted 74 | let jsonData = try! jsonEncoder.encode(export) 75 | guard let json = String(data: jsonData, encoding: String.Encoding.utf8) else { return nil } 76 | 77 | return json 78 | } 79 | } 80 | -------------------------------------------------------------------------------- /Pareto/Models/TimeMachineBackup.swift: -------------------------------------------------------------------------------- 1 | // 2 | // TimeMachineBackup.swift 3 | // Pareto Security 4 | // 5 | // Created by Janez Troha on 03/05/2022. 6 | // 7 | 8 | import Foundation 9 | 10 | enum EncryptionState: String { 11 | case Encrypted, NotEncrypted, Unknown = "" 12 | } 13 | 14 | struct TimeMachineDestinations { 15 | let LastKnownEncryptionState: EncryptionState 16 | let DestinationID: String 17 | let DiskImageKeychainUUID: String 18 | let ReferenceLocalSnapshotDate: Date // Last time of backup 19 | let BackupAlias: Data 20 | let IsNAS: Bool 21 | 22 | init(obj: [String: Any]?) { 23 | guard let dict = obj else { 24 | LastKnownEncryptionState = EncryptionState.Unknown 25 | DestinationID = "" 26 | DiskImageKeychainUUID = "" 27 | ReferenceLocalSnapshotDate = Date.distantPast 28 | BackupAlias = Data(capacity: 0) 29 | IsNAS = false 30 | return 31 | } 32 | BackupAlias = dict["BackupAlias"] as? Data ?? Data(capacity: 0) 33 | let backup = String(decoding: BackupAlias, as: UTF8.self) 34 | IsNAS = backup.contains("afp://") || backup.contains("smb://") 35 | LastKnownEncryptionState = EncryptionState(rawValue: dict["LastKnownEncryptionState"] as? String ?? "") ?? EncryptionState.Unknown 36 | DestinationID = dict["DestinationID"] as? String ?? "" 37 | DiskImageKeychainUUID = dict["DiskImageKeychainUUID"] as? String ?? "" 38 | ReferenceLocalSnapshotDate = dict["ReferenceLocalSnapshotDate"] as? Date ?? Date.distantPast 39 | } 40 | 41 | var isEncrypted: Bool { 42 | return LastKnownEncryptionState == EncryptionState.Encrypted || !DiskImageKeychainUUID.isEmpty 43 | } 44 | 45 | var isUpToDateBackup: Bool { 46 | let weekAgo = Date().addingTimeInterval(-(7 * 24 * 60 * 60)) 47 | return ReferenceLocalSnapshotDate >= weekAgo 48 | } 49 | } 50 | 51 | struct TimeMachineConfig { 52 | let AutoBackup: Bool 53 | let Destinations: [TimeMachineDestinations] 54 | let LastDestinationID: String 55 | 56 | init(dict: [String: Any]) { 57 | AutoBackup = dict["AutoBackup"] as? Bool ?? false 58 | LastDestinationID = dict["LastDestinationID"] as? String ?? "" 59 | Destinations = (dict["Destinations"] as? [[String: Any]?] ?? []).map { dict in 60 | TimeMachineDestinations(obj: dict) 61 | } 62 | } 63 | 64 | var upToDateBackup: Bool { 65 | // no backup made yet 66 | if LastDestinationID.isEmpty { 67 | return false 68 | } 69 | 70 | return Destinations.contains { d in 71 | d.isUpToDateBackup 72 | } 73 | } 74 | 75 | var isEncryptedBackup: Bool { 76 | return Destinations.allSatisfy { d in 77 | d.isEncrypted 78 | } 79 | } 80 | 81 | var canCheckIsEncryptedBackup: Bool { 82 | // no backup made yet 83 | if LastDestinationID.isEmpty { 84 | return false 85 | } 86 | 87 | let lastBackupDestination = Destinations.filter { dests in 88 | dests.DestinationID == LastDestinationID 89 | } 90 | 91 | return !(lastBackupDestination.first?.IsNAS ?? false) 92 | } 93 | } 94 | -------------------------------------------------------------------------------- /Pareto/Pareto.entitlements: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | com.apple.security.app-sandbox 6 | 7 | com.apple.security.automation.apple-events 8 | 9 | com.apple.security.temporary-exception.apple-events 10 | 11 | com.apple.systemevents 12 | 13 | com.apple.security.temporary-exception.files.absolute-path.read-only 14 | 15 | /Library/Preferences/com.apple.alf.plist 16 | 17 | com.apple.security.network.client 18 | 19 | 20 | 21 | -------------------------------------------------------------------------------- /Pareto/Preview Content/Preview Assets.xcassets/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "info" : { 3 | "author" : "xcode", 4 | "version" : 1 5 | } 6 | } 7 | -------------------------------------------------------------------------------- /Pareto/Utils.swift: -------------------------------------------------------------------------------- 1 | // 2 | // Utils.swift 3 | // Utils 4 | // 5 | // Created by Janez Troha on 30/08/2021. 6 | // 7 | 8 | import Foundation 9 | import os.log 10 | 11 | func runCMD(app: String, args: [String]) -> String { 12 | let task = Process() 13 | let pipe = Pipe() 14 | 15 | task.standardOutput = pipe 16 | task.standardError = pipe 17 | task.arguments = args 18 | task.launchPath = app 19 | task.launch() 20 | task.waitUntilExit() 21 | 22 | let data = pipe.fileHandleForReading.readDataToEndOfFile() 23 | let output = String(data: data, encoding: .utf8)! 24 | return output 25 | } 26 | 27 | func runShell(args: [String]) -> String { 28 | let task = Process() 29 | let pipe = Pipe() 30 | 31 | task.standardOutput = pipe 32 | task.arguments = args 33 | task.standardInput = nil 34 | task.executableURL = URL(fileURLWithPath: "/bin/bash") 35 | do { 36 | try task.run() 37 | task.waitUntilExit() 38 | } catch { 39 | return error.localizedDescription 40 | } 41 | 42 | let data = pipe.fileHandleForReading.readDataToEndOfFile() 43 | let output = String(data: data, encoding: .utf8)! 44 | return output 45 | } 46 | 47 | func runOSA(appleScript: String) -> String? { 48 | let out = runCMD(app: "/usr/bin/osascript", args: ["-e", appleScript]) 49 | return out 50 | } 51 | 52 | func runOSAJS(appleScript: String) -> String? { 53 | let out = runCMD(app: "/usr/bin/osascript", args: ["-l", "JavaScript", "-e", appleScript]) 54 | return out 55 | } 56 | 57 | func lsof(withCommand cmd: String, withPort port: Int) -> Bool { 58 | let out = runCMD(app: "/usr/sbin/lsof", args: ["-i", "TCP:\(port)", "-P", "+L", "-O", "-T", "+c", "0", "-nPM"]) 59 | for line in out.components(separatedBy: "\n") { 60 | if line.hasPrefix(cmd), line.hasSuffix("*:\(port)") { 61 | return true 62 | } 63 | } 64 | return false 65 | } 66 | 67 | func memoize(_ function: @escaping (Input) -> Output) -> (Input) -> Output { 68 | // our item cache 69 | var storage = [Input: Output]() 70 | 71 | // send back a new closure that does our calculation 72 | return { input in 73 | if let cached = storage[input] { 74 | return cached 75 | } 76 | 77 | let result = function(input) 78 | storage[input] = result 79 | return result 80 | } 81 | } 82 | 83 | func viaEdgeCache(_ url: String) -> String { 84 | if AppInfo.Flags.useEdgeCache { 85 | return url.replacingOccurrences(of: "https://", with: "https://pareto-cache.team-niteo.workers.dev/") 86 | } 87 | return url 88 | } 89 | -------------------------------------------------------------------------------- /Pareto/Views/ClipButton.swift: -------------------------------------------------------------------------------- 1 | // 2 | // ClipButton.swift 3 | // Pareto Updater 4 | // 5 | // Created by Janez Troha on 16/05/2022. 6 | // 7 | 8 | import Foundation 9 | import SwiftUI 10 | 11 | struct ClipButton: ButtonStyle { 12 | func makeBody(configuration: ButtonStyle.Configuration) -> some View { 13 | MyButton(configuration: configuration) 14 | } 15 | 16 | struct MyButton: View { 17 | let configuration: ButtonStyle.Configuration 18 | 19 | @Environment(\.isEnabled) private var isEnabled: Bool 20 | @State private var isOver: Bool = false 21 | 22 | var backgroundColor: Color { 23 | if !isEnabled { 24 | return Color.gray.opacity(0.0) 25 | } 26 | if isOver { 27 | return Color.gray.opacity(0.5) 28 | } else { 29 | return Color.gray.opacity(0.0) 30 | } 31 | } 32 | 33 | var body: some View { 34 | configuration.label 35 | .padding(5) 36 | .foregroundColor(.primary) 37 | .background(backgroundColor) 38 | .cornerRadius(8.0) 39 | .onHover { over in 40 | isOver = over 41 | }.frame(minHeight: 15.0) 42 | .opacity(isEnabled ? configuration.isPressed ? 0.8 : 1.0 : 0.4) 43 | } 44 | } 45 | } 46 | 47 | #if DEBUG 48 | 49 | struct ClipButton_Previews: PreviewProvider { 50 | static var previews: some View { 51 | Group { 52 | Button {} label: { 53 | Image(systemName: "app") 54 | }.buttonStyle(ClipButton()) 55 | }.padding(30) 56 | } 57 | } 58 | #endif 59 | -------------------------------------------------------------------------------- /Pareto/Views/DebugView.swift: -------------------------------------------------------------------------------- 1 | // 2 | // DebugView.swift 3 | // Pareto Security 4 | // 5 | // Created by Janez Troha on 10/06/2022. 6 | // 7 | 8 | import SwiftUI 9 | 10 | struct DebugView: View { 11 | @State private var data = "" 12 | var body: some View { 13 | Text(data).fixedSize(horizontal: false, vertical: true).frame(width: 640, height: 480).onAppear {} 14 | } 15 | } 16 | 17 | #Preview { 18 | DebugView() 19 | } 20 | -------------------------------------------------------------------------------- /Pareto/Views/Settings/PermissionsSettingsView.swift: -------------------------------------------------------------------------------- 1 | // 2 | // PermissionsSettingsView.swift 3 | // GeneralSettingsView 4 | // 5 | // Created by Janez Troha on 10/09/2021. 6 | // 7 | 8 | import Defaults 9 | import LaunchAtLogin 10 | import os.log 11 | import SwiftUI 12 | 13 | struct PermissionsSettingsView: View { 14 | @ObservedObject private var atLogin = LaunchAtLogin.observable 15 | 16 | @Default(.betaChannel) var betaChannel 17 | @Default(.showBeta) var showBeta 18 | @Default(.checkForUpdatesRecentOnly) var checkForUpdatesRecentOnly 19 | @Default(.disableChecksEvents) var disableChecksEvents 20 | @Default(.myChecks) var myChecks 21 | @Default(.myChecksURL) var myChecksURL 22 | @Default(.hideWhenNoFailures) var hideWhenNoFailures 23 | 24 | @ObservedObject fileprivate var checker = PermissionsChecker() 25 | 26 | func authorizeOSAClick() { 27 | NSWorkspace.shared.open(URL(string: "x-apple.systempreferences:com.apple.preference.security?Privacy_Automation")!) 28 | } 29 | 30 | func authorizeFDAClick() { 31 | if #available(macOS 13.0, *) { 32 | NSWorkspace.shared.open(URL(string: "x-apple.systempreferences:com.apple.preference.security?Privacy_FullDisk")!) 33 | } else { 34 | NSWorkspace.shared.open(URL(string: "x-apple.systempreferences:com.apple.preference.security?Privacy_AllFiles")!) 35 | } 36 | NSApp.activate(ignoringOtherApps: true) 37 | } 38 | 39 | var body: some View { 40 | VStack { 41 | HStack { 42 | VStack(alignment: .leading) { 43 | Text("System Events Access").font(.headline) 44 | Text("App requires read-only access to system events so that it can react on connectivity changes, settings changes, and to run checks. [Learn more](https://paretosecurity.com/docs/mac/permissions)").font(.footnote).padding([.top], 1) 45 | } 46 | 47 | Button(action: authorizeOSAClick, label: { 48 | if checker.ran { 49 | if checker.osaAuthorized { 50 | Text("Authorized").frame(width: 70) 51 | } else { 52 | Text("Authorize").frame(width: 70) 53 | } 54 | } else { 55 | Text("Verifying").frame(width: 70) 56 | } 57 | }).disabled(checker.osaAuthorized) 58 | 59 | }.frame(width: 380, alignment: .leading) 60 | HStack { 61 | VStack(alignment: .leading) { 62 | Text("Full Disk Access").font(.headline) 63 | Text("App requires full disk access if you want to use the Time Machine checks. [Learn more](https://paretosecurity.com/docs/mac/permissions)").font(.footnote).padding([.top], 1) 64 | } 65 | Button(action: authorizeFDAClick, label: { 66 | if checker.ran { 67 | if checker.fdaAuthorized { 68 | Text("Authorized").frame(width: 70) 69 | } else { 70 | Text("Authorize").frame(width: 70) 71 | } 72 | } else { 73 | Text("Verifying").frame(width: 70) 74 | } 75 | }).disabled(checker.fdaAuthorized) 76 | 77 | }.frame(width: 380, alignment: .leading) 78 | }.frame(maxWidth: 380, minHeight: 120).padding(25).onAppear { 79 | checker.start() 80 | }.onDisappear { 81 | checker.stop() 82 | } 83 | } 84 | } 85 | 86 | struct PermissionsSettingsView_Previews: PreviewProvider { 87 | static var previews: some View { 88 | PermissionsSettingsView() 89 | } 90 | } 91 | -------------------------------------------------------------------------------- /Pareto/Views/Settings/SettingsView.swift: -------------------------------------------------------------------------------- 1 | // 2 | // SettingsView.swift 3 | // Pareto 4 | // 5 | // Created by Janez Troha on 14/07/2021. 6 | // 7 | import AppKit 8 | import Defaults 9 | 10 | import SwiftUI 11 | 12 | struct SettingsView: View { 13 | @State var selected: Tabs 14 | @Default(.teamID) var teamID 15 | 16 | enum Tabs: Hashable { 17 | case general, about, teams, checks, permissions 18 | } 19 | 20 | var body: some View { 21 | TabView(selection: $selected) { 22 | GeneralSettingsView() 23 | .tabItem { 24 | Label("General", systemImage: "gear") 25 | } 26 | .tag(Tabs.general) 27 | PermissionsSettingsView() 28 | .tabItem { 29 | Label("Permissions", systemImage: "hand.raised.square.fill") 30 | } 31 | .tag(Tabs.permissions) 32 | #if !SETAPP_ENABLED 33 | TeamSettingsView(teamSettings: AppInfo.TeamSettings) 34 | .tabItem { 35 | Label("Link", systemImage: "person.3.fill") 36 | } 37 | .tag(Tabs.teams) 38 | #endif 39 | ChecksSettingsView() 40 | .tabItem { 41 | Label("Checks", systemImage: "checkmark.seal") 42 | } 43 | .tag(Tabs.checks) 44 | AboutSettingsView() 45 | .tabItem { 46 | Label("About", systemImage: "info") 47 | } 48 | .tag(Tabs.about) 49 | }.onAppear(perform: decideTab).padding(25) 50 | } 51 | 52 | private func decideTab() { 53 | if Defaults[.updateNag] { 54 | selected = .about 55 | } 56 | } 57 | } 58 | 59 | struct SettingsView_Previews: PreviewProvider { 60 | static var previews: some View { 61 | Group { 62 | SettingsView(selected: SettingsView.Tabs.general) 63 | } 64 | } 65 | } 66 | -------------------------------------------------------------------------------- /Pareto/Views/Welcome/ChecksView.swift: -------------------------------------------------------------------------------- 1 | // 2 | // ChecksView.swift 3 | // Pareto Security 4 | // 5 | // Created by Janez Troha on 09/11/2021. 6 | // 7 | 8 | import Defaults 9 | import SwiftUI 10 | 11 | struct ChecksView: View { 12 | @Binding var step: Steps 13 | @Default(.lastCheck) var lastCheck 14 | 15 | var body: some View { 16 | VStack { 17 | VStack { 18 | Image("Logo") 19 | .resizable() 20 | .aspectRatio(contentMode: .fit) 21 | .frame(minHeight: 180, alignment: .center) 22 | .accessibility(hidden: true) 23 | } 24 | Spacer(minLength: 30) 25 | if lastCheck > 0 { 26 | Text("Done!").font(.largeTitle) 27 | Spacer(minLength: 20) 28 | Text("The checks will be performed in the background from now on.").font(.body) 29 | Spacer(minLength: 30) 30 | Button("Continue") { 31 | step = Steps.End 32 | }.buttonStyle(HighlightButtonStyle(color: .mainColor)) 33 | 34 | } else { 35 | Text("Pareto Security is checking your computer's security for the first time.").font(.body) 36 | Spacer(minLength: 20) 37 | ProgressView() 38 | Spacer(minLength: 30) 39 | Button("Please wait a few seconds") {} 40 | .buttonStyle(HighlightButtonStyle(color: .gray, font: .headline)) 41 | } 42 | 43 | }.frame(width: 320, alignment: .center).padding(20).onAppear { 44 | NSApp.sendAction(#selector(AppDelegate.runChecksDelayed), to: nil, from: nil) 45 | } 46 | } 47 | } 48 | 49 | #if DEBUG 50 | struct ChecksViewPreviewsBinding: View { 51 | @State var step = Steps.Checks 52 | 53 | var body: some View { 54 | ChecksView(step: $step) 55 | } 56 | } 57 | 58 | struct ChecksView_Previews: PreviewProvider { 59 | static var previews: some View { 60 | ChecksViewPreviewsBinding() 61 | } 62 | } 63 | #endif 64 | -------------------------------------------------------------------------------- /Pareto/Views/Welcome/EndView.swift: -------------------------------------------------------------------------------- 1 | // 2 | // EndView.swift 3 | // Pareto Security 4 | // 5 | // Created by Janez Troha on 09/11/2021. 6 | // 7 | 8 | import Defaults 9 | import SwiftUI 10 | 11 | struct EndView: View { 12 | @Default(.checksPassed) var checksPassed 13 | 14 | var body: some View { 15 | VStack { 16 | Spacer(minLength: 20) 17 | if checksPassed { 18 | Image(systemName: "checkmark.shield.fill") 19 | .resizable() 20 | .foregroundColor(.green) 21 | .aspectRatio(contentMode: .fit) 22 | .frame(maxHeight: 90, alignment: .center) 23 | .accessibility(hidden: true) 24 | Spacer(minLength: 20) 25 | Text("Your computer is secure. Congrats!").font(.title) 26 | } else { 27 | Image(systemName: "xmark.shield.fill") 28 | .resizable() 29 | .foregroundColor(.orange) 30 | .aspectRatio(contentMode: .fit) 31 | .frame(maxHeight: 90, alignment: .center) 32 | .accessibility(hidden: true) 33 | Spacer(minLength: 20) 34 | Text("There are things you need to reconfigure on your computer.").font(.title) 35 | } 36 | 37 | Spacer(minLength: 20) 38 | Button("Show me") { 39 | NSApp.sendAction(#selector(AppDelegate.showMenu), to: nil, from: nil) 40 | NSApplication.shared.keyWindow?.close() 41 | }.buttonStyle(HighlightButtonStyle(color: .mainColor, font: .headline)) 42 | }.frame(width: 320, alignment: .center).padding(20) 43 | } 44 | } 45 | 46 | struct EndView_Previews: PreviewProvider { 47 | static var previews: some View { 48 | EndView() 49 | } 50 | } 51 | -------------------------------------------------------------------------------- /Pareto/Views/Welcome/IntroView.swift: -------------------------------------------------------------------------------- 1 | // 2 | // IntroView.swift 3 | // Pareto Security 4 | // 5 | // Created by Janez Troha on 09/11/2021. 6 | // 7 | 8 | import Defaults 9 | import LaunchAtLogin 10 | import SwiftUI 11 | 12 | struct IntroView: View { 13 | @Binding var step: Steps 14 | @ObservedObject private var atLogin = LaunchAtLogin.observable 15 | 16 | var body: some View { 17 | VStack { 18 | VStack { 19 | Image("Logo") 20 | .resizable() 21 | .aspectRatio(contentMode: .fit) 22 | .frame(minHeight: 180, alignment: .center) 23 | .accessibility(hidden: true) 24 | 25 | Text("Welcome to") 26 | .fontWeight(.medium) 27 | .font(.system(size: 36)) 28 | 29 | Text("Pareto Security") 30 | .fontWeight(.medium) 31 | .font(.system(size: 36)) 32 | .foregroundColor(.mainColor) 33 | } 34 | Spacer(minLength: 20) 35 | 36 | Text("Pareto Security is an app that regularly checks your Mac's security configuration. It helps you take care of 20% of security tasks that prevent 80% of problems.").font(.body).padding(5) 37 | Spacer(minLength: 20) 38 | Form { 39 | VStack(alignment: .leading) { 40 | Toggle("Automatically launch on system startup", isOn: $atLogin.isEnabled) 41 | } 42 | } 43 | Spacer(minLength: 20) 44 | Button("Get Started") { 45 | step = Steps.Permissions 46 | }.buttonStyle(HighlightButtonStyle(color: .mainColor)) 47 | }.frame(width: 320, height: 490, alignment: .center).padding(20) 48 | } 49 | } 50 | 51 | #if DEBUG 52 | struct IntroViewPreviewsBinding: View { 53 | @State var step = Steps.Welcome 54 | 55 | var body: some View { 56 | IntroView(step: $step) 57 | } 58 | } 59 | 60 | struct IntroView_Previews: PreviewProvider { 61 | static var previews: some View { 62 | IntroViewPreviewsBinding() 63 | } 64 | } 65 | #endif 66 | -------------------------------------------------------------------------------- /Pareto/Views/Welcome/WelcomeView.swift: -------------------------------------------------------------------------------- 1 | // 2 | // WelcomeView.swift 3 | // WelcomeView 4 | // 5 | // Created by Janez Troha on 09/09/2021. 6 | // 7 | 8 | import Defaults 9 | import SwiftUI 10 | 11 | enum Steps { 12 | case Welcome 13 | case Permissions 14 | case Checks 15 | case End 16 | } 17 | 18 | struct WelcomeView: View { 19 | @State var step = Steps.Welcome 20 | 21 | var body: some View { 22 | Group { 23 | switch step { 24 | case Steps.Welcome: 25 | IntroView(step: $step) 26 | case Steps.Permissions: 27 | PermissionsView(step: $step) 28 | case Steps.Checks: 29 | ChecksView(step: $step) 30 | case Steps.End: 31 | EndView() 32 | } 33 | }.onAppear { 34 | step = Steps.Welcome 35 | } 36 | } 37 | } 38 | -------------------------------------------------------------------------------- /Pareto/setappPublicKey.pem: -------------------------------------------------------------------------------- 1 | -----BEGIN PUBLIC KEY----- 2 | MIICIjANBgkqhkiG9w0BAQEFAAOCAg8AMIICCgKCAgEAuFNhwEaVQ8zUkXWXKMog 3 | fj9g67KgYK7u1QprgVlSAiQPXTvrRlaOxZTebuBvW10g47CbEMgeLh3Y12PoRrvE 4 | YzJaD+4jVeWh36bIxR5R77B/FIBX3dA26sARSdkc/CAAazKJM8Gp4ThiKM1XQ0iW 5 | V2TUzxgos2vYCHsOPTGUX3/m7j5giS3AkkTM8UaVbYzlEFViCYzIpRztgTcD0p9i 6 | R7x3lAFv6JNoE+hG63ctrRuzIx6Su5iSG0j/nKgdEnUmeq03FUqx+o85oF+M1QG+ 7 | sUFNDXpGD8/yVrRugLzt3LaYxQrzIwQ/rvmOcJOwCHixpNmMsCQJkOHCKCj+iLW5 8 | 3rF+jfbnhncoQtsy5cDT7+lvrbDyY3s1A+EjH6iltKeqiSqdvTULX8IzvWjVbGmj 9 | Se6ZdzaZ3X2bLM20NOe029oiSetr5M7ig/1qS3Vjhwp/sa7d5zNCA6l67X/2qu6l 10 | wg9tRI32G5n9FgRDM5X2ny3TSKIWs3wT2hVxdBbTiwubH/2CLtxlAYbFlZ7x2ahQ 11 | iq2hcSlYROCtjssoN/Lj2jkHWo3AJk7hR836NuMHFw9U6o8CpgDZvfP3y4+70YPy 12 | GNqbPm+Gv3SCg0cNVA4IZkegMHSS6dJr/VacYkkQD5QGm8v8pmyLwKL23uJu1pxf 13 | ZdwkukkNOqij4C3i7x2kuGkCAwEAAQ== 14 | -----END PUBLIC KEY----- -------------------------------------------------------------------------------- /ParetoSecurityTests/ApplicationUpdatesTest.swift: -------------------------------------------------------------------------------- 1 | // 2 | // ApplicationUpdatesTest.swift 3 | // ParetoSecurityTests 4 | // 5 | // Created by Janez Troha on 07/12/2021. 6 | // 7 | 8 | @testable import Pareto_Security 9 | import XCTest 10 | 11 | class ApplicationUpdatesTest: XCTestCase { 12 | func testAppVersionFetchers() throws { 13 | var redirects = [String]() 14 | var names = [String]() 15 | for app in Claims.updateChecks.sorted(by: { $0.appMarketingName.lowercased() < $1.appMarketingName.lowercased() }) { 16 | XCTAssertNotEqual(app.usedRecently, nil) 17 | XCTAssertNotEqual(app.latestVersion, nil) 18 | XCTAssertNotEqual(app.UUID, nil) 19 | XCTAssertNotEqual(app.currentVersion, nil) 20 | XCTAssertFalse(app.reportIfDisabled) 21 | redirects.append("") 22 | names.append("
  • \(app.appMarketingName)\"
  • ") 23 | } 24 | print(redirects.joined(separator: "\n")) 25 | print(names.joined(separator: "\n")) 26 | } 27 | } 28 | -------------------------------------------------------------------------------- /ParetoSecurityTests/CheckIntegrationTest.swift: -------------------------------------------------------------------------------- 1 | // 2 | // CheckIntegrationTest.swift 3 | // CheckIntegrationTest 4 | // 5 | // Created by Janez Troha on 12/08/2021. 6 | // 7 | 8 | @testable import Pareto_Security 9 | import XCTest 10 | 11 | class CheckIntegrationTest: XCTestCase { 12 | func testInit() throws { 13 | let check = IntegrationCheck() 14 | check.configure() 15 | XCTAssertEqual(check.UUID, "aaaaaaaa-bbbb-cccc-dddd-abcdef123456") 16 | 17 | // Ensure keys are not changed without migration 18 | XCTAssertEqual(check.PassesKey, "ParetoCheck-aaaaaaaa-bbbb-cccc-dddd-abcdef123456-Passes") 19 | XCTAssertEqual(check.EnabledKey, "ParetoCheck-aaaaaaaa-bbbb-cccc-dddd-abcdef123456-Enabled") 20 | XCTAssertEqual(check.TimestampKey, "ParetoCheck-aaaaaaaa-bbbb-cccc-dddd-abcdef123456-TS") 21 | } 22 | 23 | func testCheckPasses() throws { 24 | let check = IntegrationCheck() 25 | XCTAssertTrue(check.checkPasses()) 26 | } 27 | 28 | func testClaimLogic() throws { 29 | let claim = Claim(withTitle: "Test", withChecks: [IntegrationCheck(), IntegrationCheckFails()]) 30 | claim.configure() 31 | claim.run() 32 | XCTAssertEqual(claim.checks.count, 2) 33 | XCTAssertFalse(claim.checksPassed) 34 | XCTAssertNotEqual(claim.menu(), nil) 35 | } 36 | } 37 | -------------------------------------------------------------------------------- /ParetoSecurityTests/ParetoAppTest.swift: -------------------------------------------------------------------------------- 1 | // 2 | // ParetoAppTest.swift 3 | // ParetoAppTest 4 | // 5 | // Created by Janez Troha on 20/09/2021. 6 | // 7 | 8 | @testable import Pareto_Security 9 | import XCTest 10 | 11 | class ParetoAppTest: XCTestCase { 12 | func testActionEnrollTeam() throws { 13 | let app = AppHandlers() 14 | app.processAction(URL(string: "paretosecurity://enrollTeam")!) 15 | } 16 | 17 | func testActionApp() throws { 18 | let app = AppHandlers() 19 | app.runApp() 20 | } 21 | } 22 | -------------------------------------------------------------------------------- /ParetoSecurityTests/ParetoSecurityTests.swift: -------------------------------------------------------------------------------- 1 | // 2 | // ParetoSecurityTests.swift 3 | // ParetoSecurityTests 4 | // 5 | // Created by Janez Troha on 11/08/2021. 6 | // 7 | 8 | import Defaults 9 | @testable import Pareto_Security 10 | import XCTest 11 | 12 | class ParetoSecurityTests: XCTestCase { 13 | func testThatUUIDsAreUnique() throws { 14 | var uuids: [String] = [] 15 | for claim in Claims.global.all { 16 | for check in claim.checksSorted { 17 | if uuids.contains(check.UUID) { 18 | XCTFail("Duplicate UUID found \(check.UUID)") 19 | } 20 | uuids.append(check.UUID) 21 | } 22 | } 23 | } 24 | 25 | func testAppInfo() throws { 26 | XCTAssertTrue(AppInfo.bugReportURL().absoluteString.contains("paretosecurity.com")) 27 | } 28 | 29 | func testSnooze() throws { 30 | let bar = StatusBarController() 31 | bar.snoozeOneDay() 32 | XCTAssertGreaterThan(bar.snoozeTime, 0) 33 | bar.snoozeOneHour() 34 | XCTAssertGreaterThan(bar.snoozeTime, 3600) 35 | bar.snoozeOneWeek() 36 | XCTAssertGreaterThan(bar.snoozeTime, 3600 * 24 * 7) 37 | bar.unsnooze() 38 | XCTAssertEqual(bar.snoozeTime, 0) 39 | } 40 | } 41 | -------------------------------------------------------------------------------- /ParetoSecurityTests/SettingsTests.swift: -------------------------------------------------------------------------------- 1 | // 2 | // SettingsTests.swift 3 | // Settings 4 | // 5 | // Created by Janez Troha on 17/08/2021. 6 | // 7 | 8 | @testable import Pareto_Security 9 | import ViewInspector 10 | import XCTest 11 | 12 | class SettingsViewTests: XCTestCase { 13 | func testSettingsView() throws { 14 | let subject = SettingsView(selected: SettingsView.Tabs.general) 15 | let sub = try subject.inspect().implicitAnyView() 16 | XCTAssertEqual(try sub.tabView().count, 5) 17 | } 18 | 19 | func testAbout() throws { 20 | let subject = AboutSettingsView() 21 | let text = try subject.inspect().implicitAnyView().hStack()[1].vStack()[5].text().string() 22 | 23 | XCTAssertEqual(text, "Made with ❤️ at [Niteo](https://paretosecurity.com/about)") 24 | } 25 | 26 | func testTeam() throws { 27 | let subject = TeamSettingsView(teamSettings: AppInfo.TeamSettings) 28 | let one = try subject.inspect().implicitAnyView().form()[0].text().string() 29 | XCTAssertEqual(one, "The Teams subscription will give you a web dashboard for an overview of the company’s devices.") 30 | } 31 | } 32 | -------------------------------------------------------------------------------- /ParetoSecurityTests/UpdaterTest.swift: -------------------------------------------------------------------------------- 1 | // 2 | // UpdaterTest.swift 3 | // UpdaterTest 4 | // 5 | // Created by Janez Troha on 31/08/2021. 6 | // 7 | 8 | @testable import Pareto_Security 9 | import XCTest 10 | 11 | class UpdaterTest: XCTestCase { 12 | func testParsing() throws { 13 | let asset = [Release.Asset(name: "", browser_download_url: URL(string: "foo://")!, size: 1, content_type: Release.Asset.ContentType.zip)] 14 | let releases = [ 15 | Release(tag_name: "1.1.1", body: "", prerelease: true, html_url: URL(string: "foo://")!, assets: asset), 16 | Release(tag_name: "1.0.0", body: "", prerelease: false, html_url: URL(string: "foo://")!, assets: asset) 17 | ] 18 | XCTAssertEqual(try! releases.findViableUpdate(prerelease: false)?.tag_name, "1.0.0") 19 | XCTAssertEqual(try! releases.findViableUpdate(prerelease: true)?.tag_name, "1.1.1") 20 | } 21 | } 22 | -------------------------------------------------------------------------------- /ParetoSecurityTests/WelcomeTest.swift: -------------------------------------------------------------------------------- 1 | // 2 | // WelcomeTest.swift 3 | // WelcomeTest 4 | // 5 | // Created by Janez Troha on 20/09/2021. 6 | // 7 | 8 | @testable import Pareto_Security 9 | import SwiftUI 10 | import ViewInspector 11 | import XCTest 12 | 13 | class WelcomeTest: XCTestCase { 14 | @State var step = Steps.Welcome 15 | 16 | func testWelcomeViewShow() throws { 17 | _ = WelcomeView() 18 | } 19 | 20 | func testIntroViewShow() throws { 21 | _ = IntroView(step: $step) 22 | } 23 | 24 | func testPermissionsViewShow() throws { 25 | _ = PermissionsView(step: $step) 26 | } 27 | 28 | func testChecksViewShow() throws { 29 | _ = ChecksView(step: $step) 30 | } 31 | 32 | func testEndViewShow() throws { 33 | _ = EndView() 34 | } 35 | } 36 | -------------------------------------------------------------------------------- /assets/Mac_128pt.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ParetoSecurity/pareto-mac/2420d35209b02c90a26bd415b31f1f9beea72378/assets/Mac_128pt.png -------------------------------------------------------------------------------- /assets/Mac_128pt@2x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ParetoSecurity/pareto-mac/2420d35209b02c90a26bd415b31f1f9beea72378/assets/Mac_128pt@2x.png -------------------------------------------------------------------------------- /assets/Mac_16pt.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ParetoSecurity/pareto-mac/2420d35209b02c90a26bd415b31f1f9beea72378/assets/Mac_16pt.png -------------------------------------------------------------------------------- /assets/Mac_16pt@2x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ParetoSecurity/pareto-mac/2420d35209b02c90a26bd415b31f1f9beea72378/assets/Mac_16pt@2x.png -------------------------------------------------------------------------------- /assets/Mac_256pt.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ParetoSecurity/pareto-mac/2420d35209b02c90a26bd415b31f1f9beea72378/assets/Mac_256pt.png -------------------------------------------------------------------------------- /assets/Mac_256pt@2x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ParetoSecurity/pareto-mac/2420d35209b02c90a26bd415b31f1f9beea72378/assets/Mac_256pt@2x.png -------------------------------------------------------------------------------- /assets/Mac_32pt.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ParetoSecurity/pareto-mac/2420d35209b02c90a26bd415b31f1f9beea72378/assets/Mac_32pt.png -------------------------------------------------------------------------------- /assets/Mac_32pt@2x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ParetoSecurity/pareto-mac/2420d35209b02c90a26bd415b31f1f9beea72378/assets/Mac_32pt@2x.png -------------------------------------------------------------------------------- /assets/Mac_512pt.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ParetoSecurity/pareto-mac/2420d35209b02c90a26bd415b31f1f9beea72378/assets/Mac_512pt.png -------------------------------------------------------------------------------- /assets/Mac_512pt@2x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ParetoSecurity/pareto-mac/2420d35209b02c90a26bd415b31f1f9beea72378/assets/Mac_512pt@2x.png -------------------------------------------------------------------------------- /assets/icon.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ParetoSecurity/pareto-mac/2420d35209b02c90a26bd415b31f1f9beea72378/assets/icon.png -------------------------------------------------------------------------------- /assets/screenshot.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ParetoSecurity/pareto-mac/2420d35209b02c90a26bd415b31f1f9beea72378/assets/screenshot.png -------------------------------------------------------------------------------- /assets/transparent.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ParetoSecurity/pareto-mac/2420d35209b02c90a26bd415b31f1f9beea72378/assets/transparent.png -------------------------------------------------------------------------------- /codecov.yml: -------------------------------------------------------------------------------- 1 | coverage: 2 | range: 10..60 3 | status: 4 | # do not run coverage on patch nor changes 5 | patch: 6 | default: 7 | enabled: false 8 | project: 9 | default: 10 | target: 14% 11 | informational: false 12 | only_pulls: false 13 | if_ci_failed: error 14 | -------------------------------------------------------------------------------- /default.profraw: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ParetoSecurity/pareto-mac/2420d35209b02c90a26bd415b31f1f9beea72378/default.profraw -------------------------------------------------------------------------------- /exportOptions.plist: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | compileBitcode 6 | 7 | embedOnDemandResourcesAssetPacksInBundle 8 | 9 | iCloudContainerEnvironment 10 | Production 11 | method 12 | developer-id 13 | onDemandResourcesAssetPacksBaseURL 14 | 0 15 | teamID 16 | PM784W7B8X 17 | uploadBitcode 18 | 19 | uploadSymbols 20 | 21 | 22 | -------------------------------------------------------------------------------- /exportOptionsDev.plist: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | compileBitcode 6 | 7 | embedOnDemandResourcesAssetPacksInBundle 8 | 9 | iCloudContainerEnvironment 10 | "" 11 | method 12 | mac-application 13 | onDemandResourcesAssetPacksBaseURL 14 | 0 15 | teamID 16 | "" 17 | uploadBitcode 18 | 19 | uploadSymbols 20 | 21 | signingStyle 22 | manual 23 | 24 | -------------------------------------------------------------------------------- /renovate.json: -------------------------------------------------------------------------------- 1 | { 2 | "$schema": "https://docs.renovatebot.com/renovate-schema.json", 3 | "extends": [ 4 | "config:recommended" 5 | ] 6 | } 7 | --------------------------------------------------------------------------------