← Back to Blog

The Mac With No Malware On It: When Consent Is the Attack Path

11 August 2026 Incident Response 11 min read
WHAT EACH MACOS CONTROL ACTUALLY INSPECTS GATEKEEPER Checks signature and notarization at first launch PASSES. Tool is genuine. XPROTECT Matches known malicious content signatures PASSES. Nothing to match. QUARANTINE Flags files a browser or mail client downloaded SILENT. Never downloaded. ANTIVIRUS Scans for malicious code on disk and in memory PASSES. No code involved. Every result above is correct. None of them inspects the thing that granted access. TCC: FULL DISK ACCESS Granted by the user at a prompt. Recorded in a database, not a file. Defeats the file protections that would otherwise contain a process. No scanner reads it. No signature describes it. The access mechanism is a decision, and decisions do not have hashes.

You open the image and go looking for the malicious binary, because that is what twenty years of Windows work trained you to do. There is not one.

What there is instead: a remote support application in /Applications, correctly signed, correctly notarized, with a valid installer receipt. A property list in ~/Library/LaunchAgents that starts it at login. And a row in a database at ~/Library/Application Support/com.apple.TCC/ recording that the user granted it Full Disk Access five weeks ago.

That is the whole intrusion. No dropper, no packed payload, no injected dylib. The tool is the real product from the real vendor, and it is doing exactly what it was built to do for somebody who is not your user.

This is the macOS case that Windows habits handle badly, and it is becoming the common one rather than the exotic one. Here is what to read, in what order, and why the clean scanner result is not the anomaly it looks like.

Every clean result on that host is correct

Start by taking the negative findings seriously, because dismissing them as tooling failure sends you looking for something that is not there.

Gatekeeper evaluates signature and notarization when an application first launches. The tool is genuinely signed and genuinely notarized, so Gatekeeper passed, and it was right to. XProtect matches known-malicious content against signatures. There is no malicious content, so it matched nothing. Your managed antivirus scans for malicious code on disk and in memory, finds none, and reports clean.

None of those four controls inspects consent. They inspect code, provenance and reputation, and the access here was granted by a human being clicking a button. A decision has no hash.

So the absence of detections is not the gap in your investigation. It is the first substantive finding, and it tells you the access mechanism is not a file.

Read the TCC grant before anything else

Transparency, Consent and Control is the subsystem that gates access to protected resources: the camera, the microphone, the screen, Documents and Desktop, and the whole disk. A grant is a row in a database rather than a setting in a file, which is why a configuration review never surfaces it and a file-integrity check never notices it change.

Full Disk Access is the one that matters here. It removes the file protections that would otherwise contain a process, so a program holding it reads mail stores, browser data, other users' home directories and the databases behind Messages and Notes. Nothing about the program needs to be malicious for that to be catastrophic. It needs to be operated by the wrong person.

On a mounted image, the user grants live in the user's own container and the system grants live under /Library:

# User-scope grants, one database per user
~/Library/Application Support/com.apple.TCC/TCC.db

# System-scope grants, including Full Disk Access
/Library/Application Support/com.apple.TCC/TCC.db

Read the service, the client identifier, the authorization value and the timestamp. What you want to establish is narrow: which service was granted, to what, when, and whether the grant came from a user prompt or was pushed by management. A grant that management did not push and the user does not clearly remember consenting to is the centre of your investigation.

In this case the timestamp lands five weeks before the incident was reported, during a support call the user describes as being about a licensing problem with her CAD software. She remembers approving something. She does not remember what it said.

The persistence is in the user domain, which is why your sweep missed it

There are two places a launchd job lives, and the distinction decides whether you find it.

A LaunchDaemon runs as root, system-wide, from /Library/LaunchDaemons. That is where a Windows-trained responder looks first, because it is the closest analogue to a service, and it is where most macOS hardening guidance points.

A LaunchAgent runs as the logged-in user, from ~/Library/LaunchAgents, and it is entirely sufficient for this attack. The operator does not need root. The user's own privileges plus Full Disk Access reach everything of value on the machine, and staying in the user domain keeps the job out of every sweep scoped to system paths.

# Persistence: check both domains, and check them per user
ls -la /Library/LaunchDaemons/ /Library/LaunchAgents/
ls -la ~/Library/LaunchAgents/

# Read the job rather than trusting the filename
plutil -p ~/Library/LaunchAgents/com.vendor.remotehelper.plist

The plist gives you the label, the program arguments and the run conditions. What it does not give you is evidence that it ever ran. That is a separate question with a separate source, and conflating the two is how a report ends up claiming execution it never established.

Execution and the missing quarantine flag

Persistence proves intent to run. The unified log proves running.

# Did the job actually start, and how often
log show system_logs.logarchive --predicate 'process == "remotehelper"' --info --style compact

# Session and launchd activity around the consent timestamp
log show system_logs.logarchive --predicate 'eventMessage CONTAINS "remotehelper"' --info

Now the artifact that is absent, and its absence is informative. macOS attaches a quarantine flag to files that a browser or mail client downloaded, which is what drives the first-launch warning and the Gatekeeper evaluation. Check it:

xattr -p com.apple.quarantine /Applications/RemoteHelper.app

There is no such attribute, because the application was not fetched by a browser. It arrived during a screen-sharing session in which the operator was already driving the machine, so there was no download event to flag.

That matters beyond this one host. Any hunt you have built around quarantine provenance, and there are a lot of them, returns nothing for this delivery method by design. It is not tuned wrong. It is watching a path the attack did not use.

Scoping what was read, not what was published

The pressure at this point is to report the loss, and the number in front of you is almost certainly too small.

What surfaced externally were three design drawings in a public paste. What the file access record shows is fourteen files read by the tool's process in a single four-minute window, during which the user was in a meeting without her laptop. Eleven of those fourteen have never appeared anywhere.

So the public exposure is a subset of the access, and scoping to the subset understates the incident. This is the same discipline as any exfiltration case and it is easier to get wrong on macOS, because the tool's reads look identical to the user's reads in most artifacts. Separating them means tying access timestamps to the process rather than to the account, then corroborating with something outside the machine: a calendar entry, a badge record, a session log that places the human somewhere else.

# File-access ordering on the volume, read against the session windows
mdls -name kMDItemLastUsedDate -name kMDItemFSName /path/to/file

# Then corroborate the window against activity the user could not have produced
log show system_logs.logarchive --predicate 'process == "remotehelper"' --start "2026-07-15 11:00:00" --end "2026-07-15 12:00:00" --info

Report the fourteen as evidenced access and the three as confirmed publication, and say which is which. A report that collapses those two into one figure will be corrected later by somebody outside your team, and a correction on a loss estimate costs more credibility than the gap it closes.

The log gap you should expect to find

Work the unified log around the operator's session times and check the continuity of the record rather than only its contents.

The unified log rotates, so gaps are normal and most of them are retention. What is not normal is a gap whose boundaries align to session activity rather than to a rotation boundary, with entries either side sitting comfortably inside the retention window.

# Establish the boundaries of the available record
log stats --archive system_logs.logarchive

# Then read across the suspected gap rather than up to it
log show system_logs.logarchive --start "2026-07-03 14:00:00" --end "2026-07-03 16:00:00" --info

If the gap is removal rather than rotation, two things follow and both belong in the report. Every timeline entry sourced only from the log across that window carries reduced confidence and must be graded accordingly. And the tampering is a finding in its own right, because it establishes that somebody with Full Disk Access acted to limit what you could reconstruct.

The symptom appeared on a device you do not control

The detail that broke this case open was not on the Mac.

The user reported that a document she had not opened in months showed as recently viewed on her personal iPad. Her instinct was that this was a sync glitch. It was the only user-visible symptom of the intrusion: the file was opened on the Mac by the tool's process, and the recent-items entry propagated across her Apple Account to every device signed into it.

Ecosystem linkage between managed and personal devices is a data boundary question that most estates have never answered. For your investigation it creates two specific problems. Recent-activity artifacts exist on hardware you have no authority to image, which is a legal and organizational constraint rather than a technical one. And a user's report of something odd on a personal device may be the earliest signal you get, arriving through a channel no alert pipeline watches.

What actually detects this class

Nothing on the endpoint was malicious, so behavioral detection has to attach to the consent and the configuration rather than to code.

Alert on Full Disk Access grants to software that management did not deploy. That single signal would have caught this five weeks before the drawings appeared, and it is a small enough volume in most estates to work by hand.

Extend persistence hunting into the user domain, per user, rather than sweeping system paths and calling the estate clean. A root-focused sweep is a partial sweep and its output does not say so.

Treat remote-support tooling as an inventory problem. If the estate has one sanctioned tool, then any other one appearing is itself the signal, and you no longer need to reason about whether a particular product is trustworthy.

Ship unified log data off the endpoint. A local record that an operator with Full Disk Access can trim is not an evidential record, and the cost of centralizing it is far lower than the cost of a graded-down timeline in a report somebody is relying on.

Take this further

The habit worth building from this is reading the negative result as evidence. Four controls returned clean on that host and every one of them was accurate; the mistake available to you was concluding that accurate meant benign. When a scanner tells you there is no malicious code, believe it, and then ask what mechanism would produce this outcome without any.

That question generalizes past macOS. Consent, delegation and standing authorization are access paths in every platform your estate runs, and none of them looks like an intrusion in the tooling built to find intrusions. A signed tool with a user grant, an OAuth application with a delegated scope, a service account with a permission nobody reviews: the artifact is a decision in a database, the control set inspects files and behavior, and the two never meet.

Our macOS Endpoint Investigation course works this end to end, including the acquisition decisions that determine what you can establish later and the unified log analysis that separates a retention gap from a removed one. The first module is free and needs no account.

Ridgeline Cyber Defence Written by security professionals. Published weekly on Tuesdays.

Documentation toolkit

Operationalize this in production

Production-ready documentation built from the same practice. One-time purchase, fully editable, twelve months of updates.

Cyber Incident Response Pack Run an incident end to end: playbooks, evidence-collection scripts, and the templates to brief executives, regulators, and insurers. $299 View toolkit →

Related Articles

3 May 2026

We Open-Sourced Our Incident Response Toolkit: 28 Use Cases, One Binary, Zero Install

VanGuard: open-source DFIR toolkit that replaces the 45-minute tooling scramble at incident start. 28 use cases, cross-p

13 May 2026

Native Windows Forensic Commands for Incident Response: When You Have Nothing but the OS

You're on a compromised Windows host with no forensic tools installed. Capture volatile evidence, processes, and network

3 May 2026

How to Investigate an M365 Identity Compromise from Sign-in Logs to Containment

The sign-in log tells you how they got in. The audit log tells you what they did. Here's the sequence that turns both in