04-Oct-2026
No preference keys, defaults, or precedence rules changed.
- Hardened DDM declaration trust. When
/var/db/softwareupdate/SoftwareUpdateDDMStatePersistence.plistis readable,install.logdeclaration candidates must match a persisted active declaration'sTargetOSVersionandTargetLocalDateTime.- Declarations that do not match are ignored and logged as
[WARNING] Ignoring N install.log DDM declaration(s) absent from softwareupdate DDM state. - When no corroborated candidate remains, the resolver reports
missing, which stays eligible for DDM Emergency Fallback. - When the plist is missing or has an unrecognized structure, resolution works as before and the runtime logs
[WARNING] softwareupdate DDM state is <unavailable|unrecognized> at '…'; trusting uncorroborated install.log declarations.;SECURITY.mddocuments this as an accepted residual risk. Resources/JamfEA-Pending_OS_Update_Date.zshandResources/JamfEA-Pending_OS_Update_Version.zshapply the same corroboration.
- Declarations that do not match are ignored and logged as
- Added same-day reminder suppression after the user schedules the required update with Update Tonight. Normal reminders stop until local midnight once
/var/log/install.logconfirms that macOS accepted the request (Issue #133).- Detection requires daemon-side success evidence:
SUOSUInstallTonightManager: Queued … macOS <version>must exactly match the active DDM (or DDM Emergency Fallback) target version, and a followingSUOSUScheduler: ARMED (… simulated=NO)line must appear. TheClicked to queue available updates for laterbutton event and untimestamped continuation lines (for exampleScheduleUpdateForLater = 1;) are ignored, because the user can still cancel the authentication prompt. - Only
QueuedandARMEDevidence logged bysoftwareupdatedis accepted. Evidence from other senders is skipped and logged at[NOTICE], and invalidating lines from any sender still fail closed. - Evidence must be from the current local day and newer than the last boot. Any later disarm, dequeue,
Updated install tonight state (enabled = false …), different-version queue, or unrecognizedSUOSUScheduler:/SUOSUInstallTonightManager:line fails closed, and the invalidating line is logged at[NOTICE]. - Pre-deadline threshold reminders, past-deadline aggressive mode, and Force mode bypass suppression. Suppression does not apply when the effective deadline is at or before local midnight, because DDM enforcement would come before the overnight installation window.
- Suppressed runs schedule the first
DailyReminderTimesslot after midnight, or an earlier pending pre-deadline threshold. Expiration never forces an immediate dialog. - Starter-launched runs record
UpdateTonightSuppressionUntilindor-state.plistand log[NOTICE]when suppression activates, expires, or is cleared. Manual and demo runs do not write scheduler state.
- Detection requires daemon-side success evidence:
- swiftDialog now runs from its root-owned app bundle (
/Library/Application Support/Dialog/Dialog.app/Contents/MacOS/dialogcli) instead of/usr/local/bin/dialog./usr/localand/usr/local/binwere removed from thePATHof the runtime, the deployer,dor-starter.zsh, and the LaunchDaemon.- swiftDialog validation now reinstalls when the installed version cannot be read, instead of treating an empty version as current.
- A swiftDialog Team ID verification failure is now logged as
[FATAL ERROR]with the expected and received Team IDs, and the error dialog appears in the console user's session.
- Hardened runtime temporary-file handling. Each reminder run now creates a per-run, root-owned
/var/tmp/dorm.XXXXXXdirectory (named after the runtime'sorganizationScriptName), mode0755so swiftDialog can read it as the console user. Downloaded icons, the swiftDialog command file (passed with--commandfile), and the threshold-refresh marker live in that directory, and every exit path, including fatal errors, removes it. A missingdialogcliis detected before the directory is created or icons are downloaded.- The runtime no longer writes fixed names such as
/var/tmp/icon.png,/var/tmp/overlayicon.png, or/var/tmp/dialog.log. - The runtime no longer deletes the default swiftDialog command file used by other swiftDialog workflows.
- Concurrent root runs no longer share icon or command files.
- The runtime no longer writes fixed names such as
- Hardened console-user handling.
- The runtime now waits for a console user other than
loginwindow,_mbsetupuser, orroot, so a Mac still in Setup Assistant no longer resolves_mbsetupuseras the dialog target. The deployer's Team ID error dialog andResources/reminderDialogPreferenceTest.zshalso skip_mbsetupuser. - The Info button URL and the System Settings activation now open in the console user's session with
launchctl asuser, instead ofsu -with an interpolated shell command. - The wait for System Settings after Open Software Update is bounded to 30 seconds, so a blocked or crashed System Settings can no longer hold
dor.pidand stop all later reminders until reboot. - The macOS icon download now has a 10-second
--max-time.
- The runtime now waits for a console user other than
- Fixed
DailyReminderTimesvalues that mix valid and invalid entries (for example,8:00,17:00) resolving to no baseline reminder slots. The invalid-entry warning text was captured into the resolved value instead of the log, so the valid17:00entry was dropped, the08:00,12:00,16:00default was not applied, and Remind Me Later fell through to the first pre-deadline threshold (Issue #139; thanks, @TechTrekkie!)- Invalid entries are now logged as
[WARNING] Ignoring invalid DailyReminderTimes entry '…'and skipped, valid entries are kept, and a fully invalid value still falls back to the default with the existingdefaulting to '…'warning. - Affected standalone and deployed runs alike; deployed
dor.zshadditionally lost the warning line entirely because it logs through the LaunchDaemon's standard output. HH:MMstill requires zero-padded hours;8:00is rejected (and now logged), not normalized.- Applied the same fix to
MinutesBeforeDeadlineReminderSchedule, where mixed input such as45,abcpreviously disabled all pre-deadline threshold reminders. - Applied the same fix to
Resources/reminderDialogPreferenceTest.zsh.
- Invalid entries are now logged as
- Fixed baseline reminder-slot resolution skipping an entire day of
DailyReminderTimesslots when a run occurred after 23:00 on the night before a spring-forward DST transition; next-day slots now resolve by calendar day instead of adding 86,400 seconds, which also covers Update Tonight suppression scheduling and preserves configured00:00slots (PR #137 review; thanks, Copilot!) - Hardened language-code handling for localized dialog text and deadline date formats.
- Unrecognized language values now fall back to English and the global
DateFormatDeadlineHumanReadable, and log a[WARNING]. - Localized preference key names are validated before they are applied; keys with an unrecognized language code are skipped and logged at
[WARNING]. - Applied the same hardening to
Resources/reminderDialogPreferenceTest.zsh.
- Unrecognized language values now fall back to English and the global
- Hardened deployment.
dor.zshanddor-starter.zshare written to adjacent temporary files, validated withzsh -n, and moved into place atomically, so the heartbeat can never launch a partially written script.- Deployment now changes ownership only of the organization directory and DDM OS Reminder's own runtime assets, instead of recursively running
chownacross/Library/Management/<rdnn>.
- Upgrade note:
AllandScriptredeployments now keep the aggressive-mode support kill switch/Library/Management/<rdnn>/dor-aggressive-killand log a[NOTICE]; onlyUninstallremoves it. Script Parameter 4 still defaults toAllwhen blank; the in-script comment now says so. - Hardened prior-plist import in
assemble.zsh. An importedScriptLogis now kept only when it is a plain absolute path (letters, digits,.,_,-, and/; no..,., or empty segments) with a<rdnn>.logbasename. An unsafe path logs⚠️ Imported ScriptLog '…' is not a safe absolute path, a mismatched basename logs⚠️ Imported ScriptLog basename '…' does not match '<rdnn>.log', and both fall back to/var/log/<rdnn>.log.- Assembly refuses to write an unsafe
scriptLogpath into the generated script. - The assembled script is checked again with
zsh -nafter the finalscriptLogrewrite; previously the only syntax check ran before it. assemble.zshnow re-prompts on an invalid deployment-mode selection instead of defaulting to production, and exits when no selection can be read.
- Assembly refuses to write an unsafe
Resources/createSelfExtracting.zsh(2.4.0) generated wrappers have these changes:- They extract into a private
mktemp -ddirectory. - They forward all MDM script parameters, so Parameters 4–6 now reach the deployer: reset mode, DDM Emergency Fallback, and
Uninstall. - They remove the extracted payload on exit.
- Upgrade note: Parameter values that earlier wrappers ignored now take effect; review wrapper-based policy parameters before rollout.
- They extract into a private
Resources/Jamf-getDDMstatusFromCSV.zsh(1.4.0) has four changes:- API credentials and bearer tokens reach
curlthrough stdin configuration instead of process arguments. - Debug logs no longer include token responses.
- Entered passwords are no longer stripped of quote characters.
- Supplying the password as a positional argument now prints a warning.
- API credentials and bearer tokens reach
Resources/monitorRemoteSession.zsh(1.1.1) also recognizes swiftDialog processes launched from the app bundle path.
--
What's Changed
- 5.0.0 by @dan-snelson in #142
Full Changelog: v4.2.0...v5.0.0