├── .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 |
77 |
--------------------------------------------------------------------------------
/IconOrange.imageset/icon_white_orange.svg:
--------------------------------------------------------------------------------
1 |
2 |
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 |
70 |
--------------------------------------------------------------------------------
/Pareto/Assets.xcassets/IconGray.imageset/icon_white.svg:
--------------------------------------------------------------------------------
1 |
2 |
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 |
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 |
77 |
--------------------------------------------------------------------------------
/Pareto/Assets.xcassets/IconOrange.imageset/icon_white_orange.svg:
--------------------------------------------------------------------------------
1 |
2 |
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 |
--------------------------------------------------------------------------------