Extension Attribute: Turning App Auto-Patch Receipts Into Patch Evidence

Complete
Author April 2026

Overview

An extension attribute that walks App Auto-Patch’s receipt directory, reads the per-title JSON each patch attempt leaves behind, and reports the results into Jamf Pro inventory, separated into what succeeded and what failed. It is the evidence half of the Cyber Essentials patching program.

The problem: “deployed” is not “working”

App Auto-Patch does the hard part well. It discovers what patchable software is installed, keeps it current on a schedule, and does not require you to maintain a package for every title.

What it does not do is tell you, fleet-wide, whether it is succeeding.

It writes a receipt per title under /Library/Management/AppAutoPatch/receipts/<label>/latest.json: version, timestamp, exit code, status. Locally, on each Mac. The evidence exists on every device and is visible from none of them.

That gap matters more under a framework with a deadline. Cyber Essentials gives you 14 days to apply critical updates. Knowing that AAP is deployed to a device tells you nothing about whether Chrome patched on it, and a title that has been failing quietly for three weeks looks exactly like one that has been succeeding, until an auditor asks or the unpatched thing gets exploited.

Silent partial failure is the realistic failure mode of any automated patching system. The whole system stopping would get noticed. One title, on some machines, failing every run, would not.

What it does

for f in "$appAutoPatchReceiptsFolder"/*/latest.json; do
  label="$(basename "$(dirname "$f")")"
  version="$(jx "$f" version)"
  timestamp="$(jx "$f" timestamp)"
  exitCode="$(jx "$f" exitCode)"
  patch_status="$(jx "$f" status)"
  …
done

Each receipt becomes one line, label | version | timestamp | exitCode | status, and the output is grouped under Success and Failure headings.

Four decisions do the work.

Failures are separated. A flat list of 40 patched titles is something nobody reads carefully enough to spot the two that failed. Grouping means a smart group can target “has anything under Failure” and the answer is one glance instead of a wall of text.

Status is derived when it is missing. If a receipt has no status field, the exit code decides: zero is success, anything else is failure. Receipt formats change between versions, and an EA that returns nothing useful after a tool update is worse than one that degrades to something sensible.

Output is capped at 50 titles. Extension attributes land in every inventory record forever, and an uncapped list on a developer’s Mac with a long software tail would bloat every recon.

A missing receipts folder reports explicitly instead of returning empty. “AAP has never run here” and “AAP ran and patched nothing” are different problems, and an empty string cannot distinguish them. The privilege elevation attribute says so out loud for the same reason.

plutil -extract … raw reads the JSON instead of pattern-matching it with grep, so a title containing a brace or a quote does not corrupt the parse.

Outcome

Per-title patch evidence across the fleet, with failures surfaced as their own group instead of buried in a success list. Patch compliance became something with per-device, per-application proof behind it instead of an inference from “the tool is installed”.

Lessons Learned

Automation you cannot audit is a belief. The receipts already existed. App Auto-Patch was doing everything right and writing the proof to disk, and the entire gap between having that data and being able to act on it was one extension attribute. That is a recurring shape in endpoint work: the hard engineering is usually already done by the tool, and the missing piece is making its output visible where decisions get made.