Skip to content

Folders and files

NameName
Last commit message
Last commit date

Latest commit

 

History

8 Commits
 
 
 
 
 
 
 
 
 
 
 
 

Repository files navigation

jaya-defender

A lightweight, open-source security monitor for macOS developers. Jaya-defender watches your running processes and credential files in real-time, alerting you the moment something looks like a crypto miner, a credential thief, or a living-off-the-land malware binary — with near-zero CPU and memory overhead.

Built specifically for developers on Mac Minis and Apple Silicon machines who run compute-heavy workloads and store SSH keys, AWS credentials, and API tokens locally.


Why this exists

Developer machines are high-value targets. A compromised Mac Mini can:

  • Run a crypto miner in the background, silently consuming your CPU and electricity for weeks
  • Exfiltrate your SSH private keys, AWS credentials, and .env files to a remote server
  • Use macOS system binary names (accountsd, launchd) to hide in plain sight in ps output

Standard antivirus tools are noisy, resource-hungry, and miss the patterns that matter most to developers. Jaya-defender is surgical: it knows what a dev machine looks like at rest and flags only what's genuinely anomalous.


Quick start

Requirements: macOS 12+, Python 3.10+

# 1. Clone
git clone https://github.com/Ojaswy/jaya.git
cd jaya-defender

# 2. Install (creates ~/.jaya/ and registers a launchd daemon)
chmod +x install.sh && ./install.sh

# 3. Verify it's running
launchctl list | grep jaya
tail -f ~/.jaya/jaya.log

The daemon starts immediately and survives reboots via launchd. Alerts appear as macOS Notification Center banners and are written to ~/.jaya/jaya.log.

Manual run (without launchd)

pip install -e ".[dev]"
python -m jaya

Run tests

python -m pytest tests/ -v
# Expected: 55 passed

How it works

Jaya-defender runs two monitors in parallel:

Process sensor — polls all running processes every 3 seconds using psutil (wraps the macOS libproc API). For each process it checks: executable path, name, open network connections, open file handles, and sustained CPU usage. A bounded queue carries findings to the alert dispatcher without blocking the sensor thread.

File watcher — uses watchdog/FSEvents (Apple's native kernel-level file-change notification) to receive instant callbacks when your credential directories (~/.ssh, ~/.aws, ~/.gnupg, ~/.config/gcloud) are modified. Zero polling overhead.

Both monitors feed into a pure rule engine — nine stateless functions that each take a process dict and return an optional finding. No global state, fully unit-tested.


Detection rules

# Rule Severity Description
1 KNOWN_MINER_BINARY HIGH Executable name matches a known crypto miner (xmrig, ethminer, cgminer, nbminer, and 12 others)
2 STRATUM_MINING HIGH Established TCP connection to a known stratum mining port (3333, 4444, 5555, 7777, 9999, and 5 others)
3 CRYPTO_MINER_CPU MEDIUM ⚠️ Process sustains >80% CPU for 5 consecutive scans (15 seconds). Asks for confirmation — could be ML training or video export
4 CREDENTIAL_READ HIGH A non-whitelisted process has an SSH key, AWS credentials file, or GnuPG private key open
5 SUSPICIOUS_SPAWN_PATH MEDIUM ⚠️ Executable launched from /tmp/, /private/tmp/, or /var/folders/. Asks for confirmation
6 DOWNLOADS_EXECUTABLE LOW Binary running from ~/Downloads
7 HIDDEN_DIR_EXECUTABLE HIGH Executable path contains a hidden (.) directory anywhere inside your home folder (e.g. ~/.com.apple.accountsd/payload)
8 LIBRARY_EXECUTABLE HIGH Standalone binary in ~/Library/Application Support, ~/Library/Caches, or ~/Library/Containers that is not inside a .app bundle
9 SYSTEM_NAME_SPOOF HIGH Process name matches a macOS system daemon (accountsd, launchd, securityd, etc.) but runs from a user-writable path

Severity policy

  • HIGH — Instant Notification Center banner, logged, no user interaction needed. Near-zero false positive rate.
  • MEDIUM — Interactive AppleScript dialog (60-second timeout). Click Mark Safe to permanently whitelist the executable for that rule. Click Investigate to open the log in Console.app. Timeout = logged only.
  • LOW — Logged and printed to terminal. No notification.

Use cases

Crypto miner detection

Rules 1, 2, and 3 together cover the full attack surface: known binary names, network fingerprints (stratum protocol), and anomalous CPU usage. Rules 7, 8, and 9 catch custom miners that rename themselves after system processes and drop into ~/Library.

Persistent credential access

Rule 4 fires when a non-whitelisted process has a credential file open during a polling scan. This catches threats that hold the file open for more than the 3-second poll interval — for example, a process that reads, encodes, and uploads your SSH key over a slow connection, or a C2 implant that keeps the keyring open between commands.

What Rule 4 does not catch: a process that reads ~/.ssh/id_rsa, encodes it, exfiltrates it over HTTPS to port 443, and exits — all within 3 seconds. That pattern requires kernel-level interception (Apple's Endpoint Security framework) which jaya-defender does not currently use. See Limitations for details.

Living-off-the-land malware (e.g. .com.apple.accountsd chain)

Real-world macOS malware increasingly avoids custom binaries and instead hides its payloads in paths like ~/Library/Application Support/.com.apple.accountsd/AccountsHelper. This binary has a plausible-sounding name, runs from a data directory, and shows up in ps looking like a system process. Rules 7, 8, and 9 are designed specifically to catch this pattern without relying on any name or port match.

Drop-zone monitoring

Rules 5 and 6 provide low-friction coverage of common dropper paths. If something was downloaded and is now executing, you'll know.


Configuration

On first run, jaya-defender writes default configuration to ~/.jaya/config.json. Edit that file to tune any threshold or list:

{
  "cpu_high_threshold": 80.0,
  "cpu_sustained_samples": 5,
  "stratum_ports": [3333, 4444, 5555, 7777, 9999, 14444, 45560, 3032, 8008, 14433],
  "miner_name_patterns": ["xmrig", "ethminer", "cgminer", "..."],
  "credential_reader_whitelist": ["ssh", "git", "aws", "Code", "Cursor", "..."],
  "extra_dev_dotdirs": [],
  "extra_library_exe_whitelist": [],
  "alert_cooldown_secs": 300,
  "log_dir": "/Users/you/.jaya"
}

Key tuning options

extra_dev_dotdirs — Add hidden directory names that your tooling creates and should not be flagged by HIDDEN_DIR_EXECUTABLE. For example, if you use Deno or Bun:

"extra_dev_dotdirs": [".deno", ".bun", ".uv"]

The built-in list already covers .venv, .cargo, .tox, .nox, .npm, .yarn, .pnpm, .git, .pyenv, .rbenv, .rustup, .gradle, .volta, .direnv, .nodenv, and .m2.

extra_library_exe_whitelist — Add binary basenames that legitimately live as standalone executables inside ~/Library without a .app bundle. The built-in list already covers 18 common LSP daemons (rust-analyzer, clangd, pyright-langserver, gopls, pylsp, typescript-language-server, copilot-language-server, and more). Use this for site-specific tools:

"extra_library_exe_whitelist": ["my-internal-tool"]

credential_reader_whitelist — If a legitimate tool in your workflow reads credential files and you want to stop seeing CREDENTIAL_READ alerts for it, add its executable basename here.

alert_cooldown_secs — The same (pid, rule) pair will not re-alert within this window. Default: 5 minutes.

Persistent whitelist

When you click Mark Safe on a MEDIUM dialog, the executable path is written to ~/.jaya/whitelist.json. That rule will never fire for that binary again, even after daemon restarts. You can inspect or edit the file directly:

{
  "CRYPTO_MINER_CPU": ["/usr/local/bin/stable-diffusion-runner"],
  "SUSPICIOUS_SPAWN_PATH": ["/var/folders/xx/.../my-known-tool"]
}

Limitations

macOS only

Jaya-defender is built on macOS-native APIs (FSEvents via watchdog, osascript for notifications, launchd for auto-start). It will not run on Linux or Windows.

No kernel-level visibility — credential theft coverage is partial

Jaya-defender uses psutil, a userspace library. It can only see what the OS exposes to unprivileged processes via periodic snapshots. It cannot:

  • Intercept individual file reads or system calls as they happen
  • Detect a process that reads a credential file and exits in under 3 seconds
  • Observe encrypted network traffic content (only port numbers and connection state)
  • Detect processes that hide themselves from proc_pidinfo

The practical consequence: fast exfiltration is not covered. An attacker-controlled process can read ~/.ssh/id_rsa, base64-encode it, POST it to a server over HTTPS on port 443, and exit — all in under 1 second. Jaya-defender will not see any of these three events. Covering this threat model requires Apple's Endpoint Security framework (requires a notarised entitlement), which is planned for a future version.

What Rule 4 does catch: processes that hold a credential file open across multiple poll intervals — for example, an implant keeping the keyring open between C2 check-ins, or a slow uploader that takes longer than 3 seconds to complete.

A process running as root (uid=0) is skipped for open_files inspection to avoid noise and speed up scanning. Root-level persistent credential access is not covered by Rule 4.

3-second polling blind spot

The process sensor polls every 3 seconds. A process that launches, completes its work, and exits in under 3 seconds may not be observed. This is an acceptable trade-off for the target threat model (miners and stealers run continuously), but it means jaya-defender is not suited for detecting short-lived payloads.

CPU rule has false positives

CRYPTO_MINER_CPU (Rule 3) fires on any process sustaining >80% CPU for 15 seconds. On a developer machine running ML training, video encoding, or compilation, this will trigger. This is intentional — the MEDIUM severity and interactive confirmation dialog let you mark your known-heavy processes as safe once and never see the alert again.

LSP and developer tool coverage is manually maintained

The built-in _LSP_BASENAMES whitelist for LIBRARY_EXECUTABLE covers the 18 most common language server daemons. New LSP servers (e.g. zls for Zig, kotlin-language-server) are not automatically detected. Use extra_library_exe_whitelist in your config until the built-in list is updated.

No behavioral baseline yet (Phase 2)

The CPU threshold is a fixed value, not adaptive. A machine that routinely pegs one core at 90% for legitimate work will produce repeated MEDIUM alerts until those processes are whitelisted. Phase 2 will introduce an EMA (exponential moving average) behavioral baseline per process, which will allow the rule to fire only when CPU usage deviates significantly from that process's historical norm.

No LLM-based analysis yet (Phase 3)

High-severity findings are reported as raw facts (path, rule, pid). Phase 3 will optionally connect to a local Ollama model to generate a human-readable attack chain narrative (e.g. "This appears to be a persistence payload disguised as a system daemon — here is why and what to do next").

Interactive dialogs require a logged-in GUI session

The MEDIUM confirmation dialogs use osascript and require an active desktop session. On headless Mac Minis accessed only over SSH, the dialog will silently time out and the finding will be logged only. Set cpu_sustained_samples higher or pre-populate your whitelist if running headless.

No automatic response or quarantine

Jaya-defender is detection-only. It logs, notifies, and asks questions — it never kills processes, deletes files, or blocks network connections. Automated response is intentionally out of scope to avoid false-positive damage on developer machines.


Project structure

jaya-defender/
├── jaya/
│   ├── __init__.py        # Package version
│   ├── config.py          # Config dataclass, JSON load/save
│   ├── rules.py           # 9 pure detection rule functions
│   ├── sensor.py          # psutil process sensor thread
│   ├── watcher.py         # watchdog/FSEvents file watcher
│   ├── alert.py           # Alert dispatcher, confirmation dialogs, whitelist
│   ├── daemon.py          # Main event loop, SIGTERM handler
│   └── __main__.py        # python -m jaya entry point
├── tests/
│   └── test_rules.py      # 55 pure unit tests (no OS calls)
├── install.sh             # One-shot install + launchd plist registration
├── pyproject.toml
└── uninstall.sh   

Logs and data

Path Contents
~/.jaya/jaya.log JSON-lines log of every finding with timestamp
~/.jaya/whitelist.json Per-rule exe paths the user has marked safe
~/.jaya/config.json User configuration (created on first run)

Roadmap

Phase 2 — Behavioral baseline (planned) Replace the fixed CPU threshold in CRYPTO_MINER_CPU with a per-process EMA that adapts to each machine's workload. A process that normally runs at 85% CPU will not trigger; a new process pegging at 90% on a machine that's usually idle at 5% will. Correlate with absence of expected network activity to distinguish miners from ML jobs.

Phase 3 — Local LLM analyst (planned) Optional integration with a locally-running Ollama model. When a HIGH finding fires, the LLM receives the full finding context (process tree, path, rule, open files) and produces a human-readable narrative explaining the likely attack chain and recommended response steps. No data leaves the machine.

v5 config audit (planned) Expose _SYSTEM_NAMES (the spoofed daemon name list) and stratum_ports as user-editable lists in config.json, using the same extension pattern as extra_dev_dotdirs.


Contributing

Pull requests welcome. Before submitting:

  1. Add or update tests in tests/test_rules.py — all rules must be tested with pure process dicts (no psutil, no OS calls)
  2. Run python -m pytest tests/ -v and confirm all tests pass
  3. If adding a new detection rule, add an entry to the rules table in this README and document the threat model it addresses
  4. If modifying config.py, update the configuration section above

License

MIT

About

Lightweight line of defense for macOS from crypto mining and other attacks.

Resources

Stars

Watchers

Forks

Releases

Packages

Contributors

Languages