Skip to content

preferences: default cf hostname to AnyHost + add managed (MDM) subsystem - #446

Merged
doronz88 merged 3 commits into
masterfrom
fix/cfpreferences-default-anyhost
Aug 14, 2026
Merged

doronz88 merged 3 commits into
masterfrom
fix/cfpreferences-default-anyhost

Conversation

@doronz88

@doronz88 doronz88 commented Aug 13, 2026 •

Copy link
Copy Markdown
Owner

Two related preferences changes.

1 — fix: default cfpreferences hostname to AnyHost

The p.preferences.cf.* wrappers defaulted hostname to kCFPreferencesCurrentHost, which scopes reads/writes to the per-device ByHost store — …/Library/Preferences/ByHost/<domain>.<host-UUID>.plist.

Ordinary apps and daemons, the high-level CFPreferencesCopyAppValue, and NSUserDefaults all use kCFPreferencesAnyHost → the plain <domain>.plist. With the old default:

  • cf.get_value(app, key) missed keys stored in the common AnyHost domain (returned None for values that plainly exist).
  • cf.set(...) wrote into a ByHost file that defaults / CopyAppValue never read back.

Defaulting to AnyHost makes the low-level wrappers agree with the high-level API and the common case. Callers needing per-host scope can still pass hostname=kCFPreferencesCurrentHost explicitly. username is unchanged.

2 — feat: managed-preferences (MDM) subsystem

Adds p.preferences.managed, mirroring the cf API (get_value / get_dict / get_keys / set / set_dict / remove / clear) for the MDM Managed Preferences store. cfprefsd merges that store at the front of the read search list, so a managed key wins over anything an app writes via preferences.cf.

  • Reads come off the effective plists under /Library/Managed Preferences[/<user>]/<application_id>.plist.
  • Writes go through profiled — -[MCProfileConnection installProfileData:outError:] / removeProfileWithIdentifier:, the same path Settings and mdmclient use — by installing/removing a com.apple.ManagedClient.preferences (MCX "Custom Settings") profile. The store is not poked directly: profiled owns it, so writing the files behind it would be untracked and clobbered.

Raw primitives are also exposed: install_profile(data) / remove_profile(identifier) / is_profile_installed(identifier).

# force a managed value (builds + installs an MCX profile via profiled)
ident = p.preferences.managed.set("SBDisableHomeButton", True, "com.apple.springboard")
p.preferences.managed.get_dict("com.apple.springboard")   # read the effective managed plist
p.preferences.managed.clear("com.apple.springboard")      # remove_profile(ident)

Non-interactive install requires the calling process to be entitled for profiled access; otherwise the install is queued for user approval. ruff / pre-commit pass and the module imports and builds the MCX profile correctly; the on-device install path is not exercised in this repo's tests.

The CFPreferences wrappers defaulted `hostname` to
`kCFPreferencesCurrentHost`, which scopes reads and writes to the
per-device ByHost store (`.../ByHost/<domain>.<host-UUID>.plist`).

Ordinary preferences — and the high-level `CFPreferencesCopyAppValue` /
`NSUserDefaults` path — use `kCFPreferencesAnyHost`, i.e. the plain
`<domain>.plist`. With the old default, `cf.get_value(app, key)` missed
keys written by normal apps and daemons, and `cf.set(...)` wrote to a
location `defaults`/CopyAppValue never read.

Default to `kCFPreferencesAnyHost` so the wrappers match the common case
and the high-level API. Callers needing per-host scope can still pass
`hostname=kCFPreferencesCurrentHost` explicitly.
@doronz88 doronz88 changed the title fix(rpcclient): default cfpreferences hostname to AnyHost preferences: default cf hostname to AnyHost + add managed (MDM) subsystem Aug 13, 2026
@doronz88
doronz88 force-pushed the fix/cfpreferences-default-anyhost branch 13 times, most recently from fceb92b to 452d99a Compare August 14, 2026 08:31
Expose `p.preferences.managed` on iOS, split by mechanism:

- `managed.profile.*` - the MDM Managed Preferences store
  (`com.apple.ManagedClient.preferences`) plus the raw configuration
  profiles that deliver it. Reads of the effective value come off
  `/Library/Managed Preferences[/<user>]/<domain>.plist`; writes install
  / remove a profile through profiled
  (`-[MCProfileConnection installProfileData:outError:]` /
  `removeProfileWithIdentifier:`). API: get_dict / get_value / get_keys /
  set / set_dict / clear (per-domain) and install / remove / is_installed
  / get / get_data (raw profiles).

- `managed.restricted.*` - the restrictions / MCFeature namespace
  (`com.apple.applicationaccess`), the store behind accessors like
  `DiagnosticLogSubmissionEnabled()` (`allowDiagnosticSubmission`). These
  are not plist keys: the effective value is a merge of the built-in
  default and restrictions from profiles/clients. Read via effective_bool
  / default_bool / effective_value / effective_parameters /
  default_parameters; set directly via set_bool
  (`setBoolValue:forSetting:`) / set_parameters
  (`setParameters:for{Bool,Value}Setting:`), or by installing a
  Restrictions profile via install / remove.

iOS-only: it goes through `MCProfileConnection` /
`ManagedConfiguration.framework`, which does not exist on macOS - so it
lives under `clients/ios/subsystems/` and is attached via an
`IosPreferences` override on `IosClient`; the shared `preferences`
aggregator keeps just `cf` / `sc`. Non-interactive install requires the
calling process to be entitled for profiled access.
@doronz88
doronz88 force-pushed the fix/cfpreferences-default-anyhost branch from 452d99a to 832e5d8 Compare August 14, 2026 08:50
Add `Process.bundle_id()` - the process's code-signing identity via
`csops(CS_OPS_IDENTITY)`, which for a signed app or daemon is its bundle
identifier (com.apple.CrashReporter, com.apple.springboard, ...), i.e. the
domain its NSUserDefaults standardUserDefaults uses. Works for daemons
too, unlike SBSCopyDisplayIdentifierForProcessID.

Add `process.preferences(application_id=None)`, reading/writing a
preference domain the way that *specific* process resolves it. With no
`application_id` it uses `Process.bundle_id()` (its standardUserDefaults
domain); it is NOT kCFPreferencesCurrentApplication, which would be
rpcserver.

Preference location is process-relative (container, uid), so a plain
CFPreferencesCopyValue from rpcserver would resolve in rpcserver's
context. Instead it uses the container-scoped CF SPI
(_CFPreferencesCopyValueWithContainer / SetValueWithContainer /
CopyKeyListWithContainer / SynchronizeWithContainer), passing the
target's container as a CFString path (a CFURL there crashes cfprefsd),
so the request goes through cfprefsd (correct merged/managed view,
cache-coherent writes) but resolves against the target's home. The
container is the target's CFFIXED_USER_HOME, else its uid's home
(/var/root for uid 0, else /var/mobile).

API: get / get_dict / get_keys / set / remove / set_dict / clear, plus
container() / application_id(). get_dict enumerates keys then reads each
(CopyMultiple with keysToFetch=NULL crashes the container variant);
removals pass a NULL value (not kCFNull).

Verified end-to-end on-device against com.apple.springboard,
com.apple.CrashReporter (ReportCrash daemon) and a scratch domain.
@doronz88
doronz88 force-pushed the fix/cfpreferences-default-anyhost branch from 832e5d8 to 5cb4363 Compare August 14, 2026 09:04
@doronz88
doronz88 merged commit 3e79a96 into master Aug 14, 2026
24 checks passed
@doronz88
doronz88 deleted the fix/cfpreferences-default-anyhost branch August 14, 2026 09:17
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant