Skip to content
Closed
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
60 changes: 60 additions & 0 deletions CONTRIBUTING.md
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,46 @@ npm start --active # active scan — enables form fills and authz replay

See **Running Modes** in [README.md](README.md) for the full distinction.

## How to add a new oracle

Step-by-step:

1. Create `src/agent/oracles/myOracle.js` and export `checkMyOracle(options)`
2. Add signal constants to `HARD_SIGNALS` in `src/agent/expectations.js`
3. Wire it in `src/index.js`: step-level oracles go in the `ORACLE_REGISTRY` in `src/agent/oracles/registry.js`; run-level oracles (called once after the arm loop) go in `main()` alongside `authzReplay`
4. Write `tests/unit/myOracle.test.js`

Code template:

```js
/**
* @param {{ captures: object[], allowedDomains: string[], config: object, client?: object }} options
* @returns {Promise<{ signal: string|null, detail?: string }>}
*/
export async function checkMyOracle({ captures, allowedDomains, config }) {
if (!config.oracle?.myOracle?.enabled) return { signal: null };
// detection logic here
return { signal: null }; // or { signal: 'MY_SIGNAL', detail: 'description' }
}
```

## Oracle contract

Every oracle returns `{ signal: string|null, detail?: string }`.

Two tiers:

- **auto-assert**: fires when the finding has NO ambiguous legitimate interpretation — creates a `BUG/` artifact. Examples: HTTP 500, duplicate resource IDs from an idempotency-key replay. Rule: do not use auto-assert if any legitimate server behavior can produce the same signal.
- **flag-for-review**: fires when a human must confirm whether the finding is a real bug — creates a `FLAGGED/` artifact. Examples: missing security header, CORS misconfiguration, authorization leak.

## How to add a new action type

Three-file change:

1. Create `src/actions/myAction.js` exporting `async function myAction(page, opts)`
2. Register it in `src/actions/macro.js` (or equivalent action dispatcher)
3. Add a weight entry in `config.yaml` under `actions.weights`

## Pull request conventions

- One file per commit. Commit message format: `type(scope component): description`
Expand All @@ -28,6 +68,26 @@ See **Running Modes** in [README.md](README.md) for the full distinction.
- Never bundle unrelated files in one commit.
- `npm test` must pass before merging.

## Commit message format

```
type(scope component): description
```

Single line only. Types: `feat` `fix` `chore` `docs` `test` `refactor` `perf` `ci`

Scopes: `agent` `browser` `perception` `actions` `llm` `observability` `triage` `config` `infra` `docs`

Never add `Co-Authored-By` trailers. Never commit files under `docs/`.

## Running tests

```bash
npm test # unit tests (vitest)
npx playwright install chromium --with-deps # one-time setup for integration smoke
npx vitest run tests/smoke/integration.test.mjs # integration smoke
```

## Ethical use

Do not run this tool against web apps you do not own or have written permission to test.
36 changes: 36 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
@@ -1,9 +1,15 @@
![CI](https://github.com/AngelGalindo7/heuristic-monkey/actions/workflows/ci.yml/badge.svg)

# Heuristic Monkey

A Monte Carlo Tree Search bug hunter for web apps. The agent explores a target site stochastically, asks an LLM to predict what each action *should* do, and rewards itself when reality diverges from the prediction. Crashes, 5xx responses, broken images, and silent JS errors override the LLM and force a maximal "surprise" reward — so the search aggressively zooms in on real bugs.

Local-only. ~$0/month plus OpenAI usage (cents per run).

## How it works

Heuristic Monkey uses MCTS (Monte Carlo Tree Search) to explore a web app — a tree search algorithm that balances exploring new parts of your app with revisiting areas where bugs were found. Each unique page state is represented as a snapshot of the browser's accessibility tree (the same semantic structure screen readers use), so two pages that look visually different but share the same interactive elements are treated as the same state. Hard signals — HTTP 500 responses, uncaught JavaScript errors, and broken image loads — detect bugs deterministically without any interpretation: when one fires, the agent scores that path maximally and focuses exploration there. The LLM layer is optional; all hard-signal detection works without an API key, and the LLM only adds soft "surprise" scoring to catch regressions that don't crash outright.

## Quick Start

```bash
Expand Down Expand Up @@ -55,10 +61,40 @@ Edit `config.yaml`:
| `llm.enabled` / `llm.model` | Disable to run in pure hard-signal mode |
| `auth.cookies` | Pre-login cookies applied before first `goto` |

## Targeting your app

Point the monkey at any SPA by setting the target URL and allowed domains in `config.yaml`:

```yaml
target:
url: https://your-app.example.com
allowedDomains: ["your-app.example.com", "api.your-app.example.com"]
```

For apps requiring authentication, supply cookies in `config.yaml`:

```yaml
auth:
cookies:
- name: session
value: "your-session-token"
domain: "your-app.example.com"
```

Use passive mode (default) for apps you do not own — it never submits forms or mutates state. Use active mode (`--active`) for apps you own to enable form submission and authorization probes.

## Environment Variables

See `.env.example` for the full list. Key vars:

- `OPENAI_API_KEY` — required for LLM-guided exploration
- `GITHUB_TOKEN` + `GITHUB_REPO_OWNER` + `GITHUB_REPO_NAME` — auto-file GitHub issues on bugs
- `LIGHTPANDA_BIN` — path to Lightpanda binary (optional, Linux only)

## Troubleshooting

**Auth cookies have expired** — re-export fresh cookies from your browser DevTools (Application > Cookies) and update the `auth.cookies` section in `config.yaml`. Cookies are applied before each run.

**Forms are not being submitted** — the tool runs in passive mode by default, which never submits forms. Run with `--active` to enable form submission and write-dependent oracles.

**No bugs found** — this may mean the app is well-built, or the exploration did not reach the buggy path. Increase `mcts.maxSteps` in `config.yaml` to explore more paths. Change `mcts.seed` to explore a statistically independent trajectory.
Loading