MacSecLabs
Phase 01 · Module 1.4 · Intermediate

Installer-Based Execution: PKG Internals, Scripts, and Receipts on Golden Gate

Module 1.2 directly ran a shell script, a .command file, and a binary. It built the other formats but did not launch or install them. A pkg has a different execution path: the installer places files, can run lifecycle scripts, and can register a receipt. Its effective authority depends on the installation domain and authorization granted, not on the .pkg extension.

This module opens the pkg you built in 1.2, walks its install lifecycle, examines the postinstall script and the auth declaration. The user-home install was run successfully on the macOS 27 validation VM while tester01 owned the active console. A login-window host may fail this target; check the session rather than claiming the command works everywhere.


Build the Installer Package

This module reuses the Phase 01 pack. The Makefile's pkg target now bundles a postinstall proof script alongside the payload:

Continue in the Phase 01 lab workspace prepared in module 1.1.

build the package target
% export EVIDENCE="${MSL_EVIDENCE_ROOT:-$HOME/macseclabs/evidence}/1.4-installer-scripts"
% mkdir -p "$EVIDENCE"
% cd payload
% make -B pkg > /tmp/msl-14-pkgbuild.log 2>&1
% grep '^pkgbuild:' /tmp/msl-14-pkgbuild.log
pkgbuild: Inferring bundle components from contents of build/pkgroot
pkgbuild: Adding top-level postinstall script
pkgbuild: Wrote package to build/msl-payload.pkg

The pkg is now sitting in build/msl-payload.pkg, freshly built with a scripts directory inside.


PKG Internals: What Is Inside the File

A pkg is a xar archive. Module 1.2 showed file calling it exactly that. Open it with the platform's own tool and see the structure:

expand the pkg
% EXPAND_DIR=/tmp/msl-14-expand
% if [ -e "$EXPAND_DIR" ]; then
% echo "Existing expansion directory; inspect it before continuing: $EXPAND_DIR" >&2
% exit 1
% fi
% pkgutil --expand build/msl-payload.pkg "$EXPAND_DIR"
% find /tmp/msl-14-expand -type f | sort
/tmp/msl-14-expand/Bom
/tmp/msl-14-expand/PackageInfo
/tmp/msl-14-expand/Payload
/tmp/msl-14-expand/Scripts/postinstall

Four files, each with a job. Payload holds the files to be installed. Bom is the bill of materials describing payload files. It can support receipt queries, but it is not an automatic uninstaller. PackageInfo declares package identity, location, and scripts. Scripts/postinstall is the script the installer would run after placing the payload.

Read the manifest, because it declares authority:

the package manifest
% cat /tmp/msl-14-expand/PackageInfo
<?xml version="1.0" encoding="utf-8"?>
<pkg-info overwrite-permissions="true" relocatable="false" identifier="com.macseclabs.msl-payload" postinstall-action="none" version="1.0" format-version="2" generator-version="InstallCmds-883 (26A434)" install-location="/macseclabs-pkg-lab" auth="root">
  <payload numberOfFiles="2" installKBytes="1"/>
  <bundle-version/>
  <upgrade-bundle/>
  <update-bundle/>
  <atomic-update-bundle/>
  <strict-identifier/>
  <relocate/>
  <scripts>
      <postinstall file="./postinstall" timeout="600"/>
  </scripts>
</pkg-info>

The auth="root" attribute declares an authorization level for this component package. It does not prove that this package was installed, nor that the postinstall ran as root. Installation domain and authorization decide the actual context. Apple's distribution-package reference describes current-user-home installs as running as the current user and unable to write outside that home. The later capture must be read with its target and UID attached; do not infer privilege from this XML alone.

Also read your own script, carried inside the pkg, verbatim:

the postinstall the pkg carries
% cat /tmp/msl-14-expand/Scripts/postinstall
#!/bin/sh
# postinstall proof script for the Phase 01 lab pkg.
# Records the execution context the installer gave it, as a whoami foothold.
E="$HOME/macseclabs/evidence/1.4-installer-scripts"
mkdir -p "$E"
printf '{"ts":"%s","event":"postinstall-executed","uid":%s,"user":"%s","euid":%s}
' \
"$(date -u +%Y-%m-%dT%H:%M:%SZ)" "$(id -ru)" "$(id -un)" "$(id -u)" \
>> "$E/postinstall-proof.jsonl"
{
echo "trigger: pkg-postinstall"
echo "whoami: $(whoami)"
echo "id: $(id)"
echo "date: $(date -u +%Y-%m-%dT%H:%M:%SZ)"
} >> "$E/postinstall-foothold.txt"
exit 0

One job, in two parts: append a line recording who the installer let it be, and write a whoami/id foothold the report can later cite. Keep the expansion as evidence:

keep the manifest
% cp /tmp/msl-14-expand/PackageInfo "$EVIDENCE/PackageInfo.xml"

The Installation Lifecycle

The installer's flow is placement, then scripts, then receipt. Run the install against a user-home target; this VM did not prompt for a password. CurrentUserHomeDirectory depends on the logged-in user's per-session installer service, so run this from Terminal in the active graphical desktop. The validation VM had tester01 at the console and completed this command. An SSH-only host at the login window may not provide the same per-user installer service. Confirm your own session before running the install:

install the lab pkg
% if [ -e "$HOME/macseclabs-pkg-lab" ] || pkgutil --volume "$HOME" --pkg-info com.macseclabs.msl-payload >/dev/null 2>&1; then
% echo "Existing lab install or receipt; inspect it before continuing" >&2
% exit 1
% fi
% installer -pkg build/msl-payload.pkg -target CurrentUserHomeDirectory
installer: Package name is msl-payload
installer: Installing at base path /Users/tester01
installer: The install was successful.

Three lines, and each is a lifecycle stage. The installer read the package name from the manifest, resolved the base path from the target, placed the payload, ran the postinstall, and recorded success. Now read what the postinstall proof captured:

The payload is now at ~/macseclabs-pkg-lab/msl-payload.sh. That explicit lab-only prefix makes the installation easy to inspect and safe to remove at the end.

the execution context proof
% cat "$EVIDENCE/postinstall-proof.jsonl"
{"ts":"2026-10-02T01:06:29Z","event":"postinstall-executed","uid":501,"user":"tester01","euid":501}

Read the foothold the postinstall wrote:

the postinstall foothold
% cat "$EVIDENCE/postinstall-foothold.txt"
trigger: pkg-postinstall
whoami: tester01
id: uid=501(tester01) gid=20(staff) groups=20(staff),12(everyone),61(localaccounts),79(_appserverusr),80(admin),81(_appserveradm),701(com.apple.sharepoint.group.1),33(_appstore),98(_lpadmin),100(_lpoperator),204(_developer),250(_analyticsusers),395(com.apple.access_ftp),398(com.apple.access_screensharing),399(com.apple.access_ssh),400(com.apple.access_remote_ae)
date: 2026-10-02T01:06:29Z

This VM run reports UID 501 for the user-home target. It does not validate a system-domain installation. Cross-reference the auth declaration, target, and observed UID before asserting the script's authority. Phase 06 revisits the privilege boundary with a separate scenario.


The Receipt: The Forensic Gift That Outlives the Payload

This user-home install produced a receipt in the user domain. Inspect it before cleanup:

the install receipt
% plutil -p ~/Library/Receipts/com.macseclabs.msl-payload.plist
{
"InstallDate" => 2026-10-02 01:06:29 +0000
"InstallPrefixPath" => "macseclabs-pkg-lab"
"InstallProcessName" => "installer"
"InstallToken" => "A00AD2F4-A8A8-486B-8713-1EA610A91BF5"
"PackageFileName" => "msl-payload.pkg"
"PackageIdentifier" => "com.macseclabs.msl-payload"
"PackageVersion" => "1.0"
}

The captured fields identify this package, install prefix, process, and recorded time. A receipt can outlive payload deletion, but retention depends on cleanup, system state, and management policy. A system-domain install may create different receipt evidence; this user-domain capture does not prove that scenario.

Keep the receipt as evidence, then clean the install off the box:

keep the receipt, remove the install
% cp ~/Library/Receipts/com.macseclabs.msl-payload.plist "$EVIDENCE/receipt.plist"
% if [ -f "$HOME/macseclabs-pkg-lab/msl-payload.sh" ]; then
% rm -rf -- "$HOME/macseclabs-pkg-lab"
% fi
% if [ -f /tmp/msl-14-expand/PackageInfo ]; then
% rm -rf -- /tmp/msl-14-expand
% fi
% pkgutil --volume "$HOME" --forget com.macseclabs.msl-payload
Forgot package 'com.macseclabs.msl-payload' on '/Users/tester01'.

The receipt copy in your evidence preserves the package metadata you read. The user-home receipt also has a BOM. Removing only its plist leaves registration behind and makes a repeat run an upgrade. pkgutil --forget removes the receipt registration for this exact lab package; the bounded directory removal above handles its payload files. Do not call it the only surviving record: installer logs, unified logging, filesystem metadata, backups, and management telemetry may also retain evidence. On an engagement, enumerate those sources before making a retention claim.


The PKG Proof Pattern: Authority in Exchange for a Receipt

The lab pkg illustrates a common installer-script pattern. The payload places files, and the postinstall runs code with the authority the target granted, inside the installer's process context, after the user performed the most legitimate-looking interaction in software: running an installer. The chain differences from module 1.2's formats are structural:

PropertyApp bundle launchPKG install
User interactiondouble-clickauthenticate or open installer
Execution contextuser session if opened as an appinstaller script context depends on target and authorization
Lasting recordprocess and policy evidence depend on launchreceipt if installation succeeds and records it
Script timingat each observed app launch, if the app executes codeduring the observed install; upgrades may run it again

The receipt row is an operator tradeoff. A pkg can offer an installation workflow with elevated authority when properly authorized, while creating package and script evidence. The design question from module 1.1 returns: does the engagement need that installation context, and what evidence does the actual target record?


Detection engineering

An install may leave a receipt, installer logs, file activity, and a postinstall process. Which of these a defender can see depends on the host's collection and retention. Correlate package identifier, script path, UID, and time before describing one installation as a complete chain.

The user-home and system-domain paths have different authority and possible records. An unfamiliar package identifier can be a triage lead, not an automatic malicious verdict. If management telemetry exists, compare it with the local receipt and installer logs before claiming deployment provenance.

The postinstall proof file reports what the script wrote about its own context. An independent process sensor would be needed to establish the parent chain, and this module did not measure any commercial EDR.


Closing the Workspace

verify the workspace
% cd ~/macseclabs/labs/p01/p01-initial-access-lab && ./verify/verify-workspace.sh 1.4-installer-scripts
== MacSec Labs workspace check: 1.4-installer-scripts ==
PASS  evidence directory exists: /Users/tester01/macseclabs/evidence/1.4-installer-scripts
INFO  non-empty evidence files: 4

evidence contents:
/Users/tester01/macseclabs/evidence/1.4-installer-scripts/PackageInfo.xml
/Users/tester01/macseclabs/evidence/1.4-installer-scripts/postinstall-foothold.txt
/Users/tester01/macseclabs/evidence/1.4-installer-scripts/postinstall-proof.jsonl
/Users/tester01/macseclabs/evidence/1.4-installer-scripts/receipt.plist

result: workspace check passed

The four artifacts came from the current VM's successful user-home install. The exact lab install directory and receipt were removed after the evidence copies were made; no system-domain install was attempted.


Checkpoint

Before module 1.5, confirm:

  1. What four files sit inside the expanded pkg, and what is each one's job?
  2. What does auth="root" in PackageInfo declare, and what decided the actual uid of your postinstall?
  3. What three lifecycle stages did the installer output show?
  4. Which receipt fields survive deletion of the payload, and why do they matter to an auditor?
  5. Why does the pkg trade stealth for authority compared to a bundle launch?
  6. Where did your lab install's receipt live, and where would a system install's receipt live?

Module 1.5 changes the interaction entirely: no download, no installer, just a link the user clicks and a handler registered by an app. URL schemes and document types are another initial-access surface to test against the current host's registration and user-choice behavior.

This is one free module of many.

Get every module across all ten phases, with working code and the detection engineering to catch each technique.

Get full access