
Extensible MacOS system telemetry generator.
<div style="text-align: center;">
<img src="https://assets.kitploit.com/production/public/readmes/12216/015e563e6db740a8025fff16e60052d282eafc29855ed07fd23f58cd1a3fc0e5.png" alt="Description of image" style="max-width: 100%; height: auto; display: block; margin: 0 auto;">
</div>
---
[](https://github.com/0xv1n/macnoise/actions/workflows/ci.yaml)
[](https://github.com/0xv1n/macnoise/releases/latest)
# MacNoise
MacNoise generates real macOS telemetry: network connections, file writes, process spawns, plist mutations, TCC probes, and more. Point it at a machine running your EDR, SIEM, or firewall stack and see what actually fires - not what the vendor datasheet claims will fire.
For background on the motivation and design, see the [release blog post](https://0xv1n.github.io/posts/macnoise/).
## Quick Start
```bash
# Build (add build-amd64 / build-arm64 to cross-compile for Darwin, or release for both)
make build
# List available modules
./macnoise list
# Run a single module
./macnoise run net_connect --param target=127.0.0.1 --param port=8080
# Preview without executing
./macnoise run svc_launch_agent --dry-run
# Run all network modules
./macnoise run --category network
# Run a scenario
./macnoise scenario configs/scenarios/edr_validation.yaml
# Emit structured JSONL output
./macnoise scenario configs/scenarios/file_flow.yaml --format jsonl --output /tmp/events.jsonl
```
## Telemetry Categories
| Category | Description |
|----------|-------------|
| `network` | TCP connections, HTTP, listeners, reverse shells, DNS, and TLS |
| `process` | Exact execution, signal delivery, dylib injection, Gatekeeper bypass, and osascript |
| `file` | Bounded discovery, literal reads/copies, creation, modification, archiving, hiding, and decoy encryption |
| `tcc` | TCC permission probes with exact Full Disk Access, Contacts, Accessibility, or Screen Recording requirements |
| `credential` | Native credential-store access |
| `volume` | Disk-image creation and mounted-volume lifecycle |
| `service` | Launchd enumeration, LaunchAgent/Daemon persistence, cron, shell profile, and Login Items |
| `plist` | Plist creation and modification |
| `evasion` | Log clearing, timestomping, history removal, and masquerading |
See the generated [module catalog](https://github.com/0xv1n/macnoise/blob/main/docs/module-catalog.md) for every module, parameter, output, event type, privilege, and ATT&CK mapping.
## Commands
```
macnoise run <module> [--param key=val ...] Run a specific module
macnoise run --category <cat> Run all modules in a category
macnoise run --all Run all modules
macnoise list [--category <cat>] List modules
macnoise info <module> Show module details, params, MITRE
macnoise scenario <file.yaml> [--input key=val] [--report report.json]
Run a YAML scenario
macnoise categories List categories with counts
macnoise version Print version
```
### Global Flags
| Flag | Default | Description |
|------|---------|-------------|
| `--format` | `human` | Output format: `human` or `jsonl` |
| `--output` | (none) | Write output to file (in addition to stdout) |
| `--verbose` | false | Verbose output including cleanup errors |
| `--dry-run` | false | Preview actions without executing |
| `--no-cleanup` | false | Leave module artifacts in place (see below) |
| `--timeout` | `30` | Per-module timeout in seconds |
| `--audit-log` | (none) | Write OCSF 1.7.0 audit records to a JSONL file |
| `--config` | (none) | Load defaults from a YAML config file |
| `--run-id` | generated | Set the correlation identifier for this run |
### Scenario dataflow
Scenario files use `version: 1`. Inputs and module outputs are typed, and a
later step references them with explicit mappings rather than string
interpolation:
```yaml
version: 1
name: Archive one generated artifact
on_error: stop
inputs:
content:
type: string
required: true
steps:
# Custom modules declare these outputs through OutputSpecs.
- id: create
module: custom_create
params:
content:
input: content
- id: archive
module: custom_archive
params:
source:
output: create.path
outputs:
archive:
output: archive.path
```
Only outputs declared by a module can be referenced. Local scenarios can be
reused with an `include` step; includes are relative, cannot traverse above
the root scenario directory, are cycle-checked, and are limited to eight levels.
MacNoise validates the complete graph before execution, gives the run one
private workspace, and cleans invoked modules in reverse order. Use
`--input content=value` to supply inputs and `--report report.json` for the
versioned execution report.
## Leaving Artifacts In Place
By default every module reverses itself when it finishes. That is usually what you want, but it means a detection only ever sees the *install* event. To validate that your stack detects the persistence itself - a LaunchAgent sitting in `~/Library/LaunchAgents`, a cron entry, a modified shell profile - the artifact has to still be there when the scan runs:
```bash
./macnoise run svc_launch_agent --no-cleanup
```
Each module that skips cleanup prints a line naming itself, and the audit log records `cleanup_result: skipped` rather than `ok`, so a run that left persistence behind is never mistaken for one that tidied up. Use `macnoise info <module>` to see what a given module creates.
**You are responsible for removing these yourself.** Re-running the same module without the flag will clean up only what that run created, not what a previous `--no-cleanup` run left behind.
## Audit Logging
MacNoise writes two separate streams. Telemetry events - what your EDR/SIEM actually sees - go to stdout or `--output`. A second, optional stream records what MacNoise itself did: which modules ran, prereq/cleanup outcomes, and MITRE mappings, in [OCSF 1.7.0](https://schema.ocsf.io/) JSONL.
```bash
./macnoise scenario configs/scenarios/amos_atomic_stealer.yaml --audit-log /tmp/audit.jsonl
```
Every telemetry event carries one authoritative `outcome` and one typed `subject` (schema 2.0). The outcome says what happened to the action MacNoise attempted, while the subject identifies the file, process, network endpoint, service, or resource involved:
| `outcome` | Meaning | Human marker |
|---|---|---|
| `executed` | The action ran and did what the module claims | `[+]` |
| `denied` | The action ran and the environment refused it | `[-]` |
| `indeterminate` | The action ran, but nothing can be concluded | `[?]` |
| `error` | MacNoise itself failed to carry the action out | `[!]` |
A denied TCC probe or a beacon to a dead C2 is the telemetry this tool exists to generate, so it is distinct from `error`, which means MacNoise itself failed. The audit log records the same value at `unmapped.outcome`. Parameters declared sensitive are replaced with `[REDACTED]` in managed audit records and command-line identity.
The audit log opens in append mode, so records from multiple runs pile up in one file for batch analysis. If you're adding a module and want to know how a new event type gets classified into OCSF, see [CONTRIBUTING.md](https://github.com/0xv1n/macnoise/blob/main/CONTRIBUTING.md#primitive-modules).
## Module Reference
The generated [module catalog](https://github.com/0xv1n/macnoise/blob/main/docs/module-catalog.md) is the authoritative reference for names, parameters, outputs, event types, privileges, and ATT&CK mappings. Category notes explain platform behavior and operational boundaries:
| Category | README |
|----------|--------|
| `network` | [modules/network/README.md](https://github.com/0xv1n/macnoise/blob/main/modules/network/README.md) |
| `process` | [modules/process/README.md](https://github.com/0xv1n/macnoise/blob/main/modules/process/README.md) |
| `file` | [modules/file/README.md](https://github.com/0xv1n/macnoise/blob/main/modules/file/README.md) |
| `tcc` | [modules/tcc/README.md](https://github.com/0xv1n/macnoise/blob/main/modules/tcc/README.md) |
| `credential` | [modules/credential/README.md](https://github.com/0xv1n/macnoise/blob/main/modules/credential/README.md) |
| `volume` | [modules/volume/README.md](https://github.com/0xv1n/macnoise/blob/main/modules/volume/README.md) |
| `service` | [modules/service/README.md](https://github.com/0xv1n/macnoise/blob/main/modules/service/README.md) |
| `plist` | [modules/plist/README.md](https://github.com/0xv1n/macnoise/blob/main/modules/plist/README.md) |
| `evasion` | [modules/evasion/README.md](https://github.com/0xv1n/macnoise/blob/main/modules/evasion/README.md) |
## Scenarios
Scenarios chain modules into ordered sequences - a single YAML file that replays a multi-stage intrusion pattern against your detections.
| File | Description |
|------|-------------|
| `network_only.yaml` | Composed TCP, listener, DNS, HTTP beacon, and HTTP exfiltration operations |
| `edr_validation.yaml` | Comprehensive EDR detection coverage |
| `full_sweep.yaml` | All categories |
| `lazarus_group.yaml` | Lazarus Group: dylib injection, service discovery, reverse shell, LaunchAgent persistence |
| `amos_atomic_stealer.yaml` | AMOS / Atomic Stealer: MaaS infostealer, Gatekeeper bypass, keychain dump, ZIP exfil, backdoor persistence |
| `clickfix.yaml` | ClickFix: obfuscated one-liner pasted into Terminal, base64 decode, second-stage fetch, LaunchAgent persistence |
| `ransomware.yaml` | Ransomware impact: stage plaintext decoys, encrypt them, then drop a ransom note |
| `discovery.yaml` | Composed argv-based system, account, network, and security software discovery recipes |
| `process_chain.yaml` | Three-process shell chain built from an explicit argument vector |
| `file_flow.yaml` | Connected create, modify, bounded discovery, read, copy, and archive flow |
| `mounted_execution.yaml` | Create and execute a payload from an observed disk-image mount point |
The two APT scenarios follow real documented intrusion sequences, technique by technique - each YAML file cites the actual threat intel it's built from and annotates every step with the MITRE technique it exercises, so start there for the full breakdown rather than a retelling here.
**Dry-run first:**
```bash
./macnoise scenario configs/scenarios/<scenario>.yaml --dry-run
```
**Cross-reference with your SIEM/EDR:** each step comment names the technique it should trigger. No matching alert after a real run is a gap in your coverage.
**Writing your own:**
```yaml
version: 1
name: My Custom Scenario
on_error: stop
steps:
- module: net_connect
params:
target: "192.168.1.1"
port: 443
- module: file_create
params:
base_dir: "/tmp/test"
```
Parameters are checked against each module's declared string, integer, boolean,
path, or list type before preview or execution. Unknown names and invalid values
are rejected. `on_error` defaults to `stop`. Set it to `continue` only when a
coverage sweep should attempt later module invocations after a failure.
Start from the [scenario template](https://github.com/0xv1n/macnoise/blob/main/docs/templates/scenario.yaml) for typed inputs, outputs, and connected dataflow.
## Version 1 compatibility
Version 1.0 defines the supported CLI commands and flags, module names and contracts, scenario schema 1, telemetry schema 2.0, and scenario-report schema 1.0. Future incompatible changes to those interfaces require a new major release.
Existing users should read [Migrating from v0.6.0 to v1.0.0](https://github.com/0xv1n/macnoise/blob/main/docs/migration-v1.md). It maps every removed module and describes the scenario, JSONL, and Go API changes.
## Contributing
See [CONTRIBUTING.md](https://github.com/0xv1n/macnoise/blob/main/CONTRIBUTING.md) for the primitive, scenario, and core-change paths.
Releases are automated - [release-please](https://github.com/googleapis/release-please) cuts a new version straight from your [Conventional Commit](https://www.conventionalcommits.org/) PR title, so `feat: add net_tls module` or `fix: correct beacon jitter` is both your PR title and your changelog entry.
## Disclaimer
MacNoise is intended for **authorized** security testing, EDR validation, and detection engineering on systems you own or have explicit written permission to test. The authors assume no liability for misuse.
## AI Code Policy
AI Code contributions are fine, but please keep in mind that code review is currently going to be a human-led process which means there is only so much code we can review. Please limit PRs to a specific fix, or new telemetry module. PRs with extensive changes are likely going to be closed.