Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -101,3 +101,4 @@ Newest first. One line per change. Default-behavior changes marked **CHANGED**.

- Initial release of am-i-compromised.
- Scanner reporting improved, test suite hardened, demo added. npm release procedure documented.

48 changes: 17 additions & 31 deletions README.md
Original file line number Diff line number Diff line change
@@ -1,51 +1,48 @@
# secure-devtools

Plain-shell security tools that check the code you are about to run, and the machine you run it on, for signs of a supply-chain or dev-environment attack. They run locally and in CI, need no account, and have zero npm runtime dependencies.
Plain-shell tools that check the code you are about to run, and the machine you run it on, for signs of a supply-chain or dev-environment attack. They run locally, need no account or API key, and have zero npm runtime dependencies.

```sh
pnpx am-i-hacked . # or: npx am-i-hacked .
npx am-i-hacked
```

[![CI](https://github.com/IsaacBell/secure-devtools/actions/workflows/ci.yml/badge.svg)](https://github.com/IsaacBell/secure-devtools/actions/workflows/ci.yml)
[![npm am-i-hacked](https://img.shields.io/npm/v/am-i-hacked?label=am-i-hacked)](https://www.npmjs.com/package/am-i-hacked)
[![npm am-i-being-recorded](https://img.shields.io/npm/v/am-i-being-recorded?label=am-i-being-recorded)](https://www.npmjs.com/package/am-i-being-recorded)
[![License: MIT](https://img.shields.io/badge/License-MIT-yellow.svg)](LICENSE)
[![skills.sh](https://skills.sh/b/IsaacBell/secure-devtools)](https://skills.sh/IsaacBell/secure-devtools)
[![npm secure-semgrep](https://img.shields.io/npm/v/secure-semgrep?label=secure-semgrep)](https://www.npmjs.com/package/secure-semgrep)

**Status:** actively maintained. `am-i-hacked` 2.0.1 and `am-i-being-recorded` 1.0.0 are the current releases. macOS and Linux.
**Status:** actively maintained, macOS and Linux. Current releases: `am-i-hacked` 2.0.1, `am-i-being-recorded` 1.0.0, `secure-semgrep` 1.0.1. The next `am-i-hacked` release adds a dark-corner system scan, a Python virtualenv integrity check and `--max-findings`; each package's changelog has the detail. Website: <https://isaacbell.github.io/secure-devtools/>.

## Tools

| Tool | What it checks | How to run |
| Tool | What it checks | Run |
| --- | --- | --- |
| [`am-i-hacked`](apps/am-i-hacked/README.md) | A project folder, for malicious-code indicators: editor tasks that run on folder open, payloads disguised as asset files, obfuscation, capture code paired with an exfiltration endpoint, `.env` files in the git index, risky `package.json` scripts. With `--system`, this machine: login and startup items and their code signatures, crontab, shell startup files, AI-tool config, running processes. | `pnpx am-i-hacked` |
| [`am-i-being-recorded`](apps/am-i-being-recorded/README.md) | Which browser extension is behind a screen-recording indicator, and what else on the machine can capture you. | `pnpx am-i-being-recorded` |
| [`secure-semgrep`](apps/secure-semgrep/README.md) | Semgrep rules and loadout packs for AI-agent, bash and web code. Needs `semgrep`. | From this repository |
| [`am-i-hacked`](apps/am-i-hacked/README.md) | A project folder, before you open, install or run it, for malicious-code indicators: editor tasks that run on folder open, payloads disguised as asset files, obfuscation, capture code paired with an exfiltration endpoint, `.env` files in the git index, risky `package.json` scripts, official Yarn releases by SHA256, and Python virtualenv integrity. With `--system`, this machine: login items and their code signatures, crontab, shell startup files, AI-tool config, running processes, and the caches and bin folders where tooling installs code outside any project. | `npx am-i-hacked` |
| [`am-i-being-recorded`](apps/am-i-being-recorded/README.md) | Which browser extension is behind a screen-recording indicator, and what else on the machine can capture you. | `npx am-i-being-recorded` |
| [`secure-semgrep`](apps/secure-semgrep/README.md) | Bundled Semgrep rules you own — AI-agent, bash and SSRF patterns — plus maintained registry loadouts (Python, JS, TS, React, Node, Rust), fetched from the Semgrep registry at scan time. Needs `semgrep` on PATH. | `npx secure-semgrep ./src` |

### Agent skills

Install all seven skills for your user account, or pick one:
Install all seven for your user account, or pick one:

```sh
npx skills add IsaacBell/secure-devtools -g
npx skills add IsaacBell/secure-devtools --skill ssrf-safe-fetch
```

Drop `-g` to install into the current project, and add `--list` to see the skills without installing. `pnpx skills add` works the same way. The `skills` command reports anonymous install counts to skills.sh; set `DISABLE_TELEMETRY=1` to turn that off.
Drop `-g` to install into the current project, and add `--list` to see the skills without installing. The `skills` command reports anonymous install counts to skills.sh; set `DISABLE_TELEMETRY=1` to turn that off.

The skills, in [`skills/`](skills/): [quarantine-review](skills/quarantine-review/SKILL.md) (inspect an untrusted repository without running any of it), [secure-skill-pull](skills/secure-skill-pull/SKILL.md) (vet a skill or plugin from a URL without running its installer), [create-skill](skills/create-skill/SKILL.md) (write, check and publish an agent skill), [skill-publish-review](skills/skill-publish-review/SKILL.md) (review skills before they go into a public repository), [ssrf-safe-fetch](skills/ssrf-safe-fetch/SKILL.md) (validate the URL and the resolved address before a server fetches it), [login-item-triage](skills/login-item-triage/SKILL.md) and [jujutsu](skills/jujutsu/SKILL.md).
The skills, in [`skills/`](skills/): [quarantine-review](skills/quarantine-review/SKILL.md) (inspect an untrusted repository without running any of it), [secure-skill-pull](skills/secure-skill-pull/SKILL.md) (vet a skill or plugin from a URL without running its installer), [create-skill](skills/create-skill/SKILL.md) (write, check and publish an agent skill), [skill-publish-review](skills/skill-publish-review/SKILL.md) (review skills before they go into a public repository), [ssrf-safe-fetch](skills/ssrf-safe-fetch/SKILL.md) (validate the URL and the resolved address before a server fetches it), [login-item-triage](skills/login-item-triage/SKILL.md) (the manual method behind am-i-hacked's login-item signature checks) and [jujutsu](skills/jujutsu/SKILL.md).

## Why this one?

- **Instant.** One command, no signup, no account, no API key: `pnpx am-i-hacked .`
- **CI by copy-paste.** Exit code `1` on findings; the [package README](apps/am-i-hacked/README.md#-usage) has the workflow snippet.
- **Catches what advisory scanners cannot.** `npm audit`, OSV-Scanner and similar tools match your dependencies against published advisories. They cannot see an attack nobody has reported yet, or one that lives in the repository itself: a `.vscode/tasks.json` that runs when you open the folder, a script saved as a font file, a stealer that starts itself at login. `am-i-hacked` reads the files for those indicators before you open, install or run anything. Use both.
- One command from a cold machine: `npx am-i-hacked` needs no signup, account, API key or install step.
- Exit `1` on HIGH or MEDIUM findings, so it works as a pre-commit hook, a `package.json` script or a CI step. The [am-i-hacked README](apps/am-i-hacked/README.md#install-and-run) has the workflow snippet.
- It reads the repository for indicators advisory scanners cannot report: a `.vscode/tasks.json` that runs on folder open, a payload saved as a font file, a launchd or systemd item that starts at login, a capture tool paired with an exfiltration endpoint. `npm audit`, OSV-Scanner and Snyk match your dependencies against published advisories, so an unreported attack, or one committed into the tree, stays invisible to them. Run both.

## What it does not do

It looks for warning signs. It is not antivirus, and it does not look up known viruses. A clean result means it found no warning signs. It does not prove the code or the machine is safe.

Some checks also flag normal code, such as `eval`. When you have checked a line and it is fine, mark it with a short reason. That line still shows up as reviewed on every run, so nothing gets hidden.
The scanners look for warning signs; they are not antivirus and do not look up known viruses. A clean result means no warning signs were found, not that the code or the machine is safe. Some checks also flag ordinary code, such as `eval`; when a line is fine, mark it reviewed with a short reason (`am-i-hacked-ignore: <reason>`) and it still appears as reviewed on every run.

## Develop

Expand All @@ -57,22 +54,11 @@ mise run setup # dev dependencies and git hooks
mise run check # shellcheck, shfmt check and the bats tests: what CI runs
```

`mise run` lists every task. The git hooks run the same checks before each commit. CI in [`.github/workflows/`](.github/workflows/) runs the checks and tests, this repository's own security gate (`mise run gate`), a gitleaks secret scan, Semgrep, dependency review, and CodeAnt AI scan. Releases: [docs/RELEASING.md](docs/RELEASING.md). Changes: each package's `CHANGELOG.md`.

## Semgrep

[`secure-semgrep`](apps/secure-semgrep/README.md) is the repository's static-analysis pack: bundled, owned rules for AI agents and bash, plus Semgrep loadout packs you can point at any codebase. It lives in [`apps/secure-semgrep`](apps/secure-semgrep/README.md) and publishes to npm as `secure-semgrep`, mirroring how `am-i-hacked` is published.

Run it over this repository (review mode records findings without breaking the build):

```bash
$ mise run semgrep
```

To turn findings into a hard gate, run `mise run semgrep-strict`. In any other repository, use it the same way as a post-`npm install` script — see the [package README](apps/secure-semgrep/README.md) for loadouts (`react`, `ts`, `node`, `py`, `rust`) and CI snippets.
`mise run` lists every task. The git hooks run the same checks before each commit. CI in [`.github/workflows/`](.github/workflows/) runs the checks and tests, this repository's own security gate (`mise run gate`), a gitleaks secret scan, Semgrep, dependency review and a CodeAnt AI scan. Releases: [docs/RELEASING.md](docs/RELEASING.md). Static analysis runs as `mise run semgrep` (review mode) or `mise run semgrep-strict` (gate); [secure-semgrep's README](apps/secure-semgrep/README.md) documents the loadouts and CI snippets.

## Contributing, security and support

- Maintainer: [Isaac Bell](https://isaacbell.io) ([@IsaacBell](https://github.com/IsaacBell)).
- Contributing: [CONTRIBUTING.md](CONTRIBUTING.md). Issues and pull requests are welcome.
- Security problems: [report them privately](https://github.com/IsaacBell/secure-devtools/security/advisories/new); [SECURITY.md](SECURITY.md) has the details. Please do not open a public issue. Published fixes are listed under [security advisories](https://github.com/IsaacBell/secure-devtools/security/advisories).
- Sponsor the work: [ko-fi](https://ko-fi.com/ibell). `npm fund` points to the same page.
Expand Down
6 changes: 3 additions & 3 deletions apps/am-i-being-recorded/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,7 @@ Find out **which browser extension is recording your screen** — and what else
the machine can capture you.

```sh
pnpx am-i-being-recorded # or: npx am-i-being-recorded
npx am-i-being-recorded
```

The sibling of [`am-i-hacked`](https://www.npmjs.com/package/am-i-hacked):
Expand All @@ -35,13 +35,13 @@ apt-get install jq
Run without installing:

```sh
pnpx am-i-being-recorded
npx am-i-being-recorded
```

Install as a dev dependency:

```sh
pnpm add -D am-i-being-recorded # or: npm install -D am-i-being-recorded
npm install --save-dev am-i-being-recorded
```

## What it checks
Expand Down
23 changes: 23 additions & 0 deletions apps/am-i-hacked/CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,29 @@

Newest first. Default-behavior changes are marked **CHANGED**.

## Unreleased

**BLUF:** The system scan now checks the fixed places tooling installs code outside any project, the folder scan verifies Python virtualenv integrity, and the findings cap rises to 1000. `bin/host-audit.sh` is renamed to `bin/system-scan.sh`, with the old name kept as a shim.

### Added
- The system scan (`--system`) now also scans a fixed list of "dark corners" where tooling installs code outside any project: Python virtualenvs, the uv and pip caches, pyenv, pipx, the npx cache, the pnpm global store, cargo, bun, deno, and the go and gem bin folders. Each is scanned with the folder scan when present and skipped silently when it does not exist.
- Python virtualenv integrity check in the folder scan, for every venv under the scanned folder: files that no longer match their package `RECORD` hashes are a `HIGH` finding; `RECORD` paths that leave the venv or pass through a symlinked folder (never opened), files and symlinks no package owns, console scripts and symlinks in `bin/` no package claims (each venv checked on its own claims), a console script name containing `|`, `RECORD` entries with no hash, a venv whose `RECORD` files or whole `dist-info` folders were deleted, a `_virtualenv.pth` that is not the stock one-line file, `.pth` and `sitecustomize` hooks that run code, unreadable files and folders (reported rather than skipped), and file names containing newlines are all reported.
- `--max-findings N` sets the maximum findings printed, from 1 to 999999999, default raised from 100 to 1000. **CHANGED:** when findings exceed the cap, the true total and a per-rule count are printed, and higher-severity findings are kept first.
- The system scan reports a check it could not run instead of passing it: `ps` missing or failing, a `crontab -l` error other than "no crontab", `codesign` missing, an AI-tool config `jq` cannot parse (JSONC), an unreadable dark corner, your own relative-path process whose working directory `lsof` could not resolve, and a login item's file that you own but cannot read (a root-owned one stays informational).

### Changed
- **CHANGED:** hex and unicode escape findings inside installed dependency folders (`site-packages`, `dist-packages`, `node_modules`) are reported as one summary count instead of one finding per match. `vendor/` is committed with the project, so its findings are still listed.
- **CHANGED:** `bin/host-audit.sh` is now `bin/system-scan.sh`. `bin/host-audit.sh` remains as a shim, and the `host` command still works.

### Security
- A source file containing a NUL byte was skipped as binary, so one NUL in a comment hid the whole file from the folder scan. Source files are now always read as text.
- A systemd or autostart `Exec` line containing `*` was glob-expanded against the scanner's working directory. It is now split without globbing.
- A hook command containing a tab was cut at the tab, so a dangerous path after it was not checked.
- A persistence script whose file name contains a newline was not read.

### Fixed
- The `ignored null byte in input` warnings are no longer emitted, including for matched lines and build-output detection.

## 2.0.1 - 2026-10-01

**BLUF:** A security release. The scan no longer trusts the tree it is reviewing: a scanned repo's `.git/config` could make it run a command, and payloads in dot-directories, in folders named in `.ignore` or `.rgignore`, in tracked files matched by `.gitignore`, and in tracked build output were not read. It also fixes a skip on extensionless scripts.
Expand Down
Loading
Loading