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
10 changes: 10 additions & 0 deletions .changeset/feat-spectre-dep-doctor.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
---
"layne": major
---

Replace Pi Agent with Spectre and add Dep Doctor scanner.

- **Spectre** replaces Pi Agent as the multi-provider LLM malicious-intent scanner. It makes a single direct LLM call per file (no agent session) and supports Anthropic, OpenAI, Google, Mistral, and Amazon Bedrock via `@mariozechner/pi-ai`. Configurable file cap, diff line cap, min severity, skip paths/extensions, and concurrency.
- **Dep Doctor** is a new dependency health scanner that fires when a lockfile changes. It detects newly-added packages with known CVEs (via OSV-Scanner), abandoned packages, and deprecated packages. Supports npm, PyPI, and Go lockfiles.
- PR comments now use GitHub alert blocks (`[!CAUTION]` / `[!WARNING]`) with a severity-sorted findings table linking directly to the affected file and line. The `{{findings}}` and `{{severitySummary}}` template variables are now available for custom templates.
- `Dockerfile` now installs `osv-scanner` alongside trufflehog and semgrep.
28 changes: 0 additions & 28 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -26,20 +26,6 @@

### Minor Changes

- [#27](https://github.com/RocketChat/layne/pull/27) [`25248cc`](https://github.com/RocketChat/layne/commit/25248cca3658ecbf5aae3cdfd3eff0187af7cb3f) Thanks [@julio-rocketchat](https://github.com/julio-rocketchat)! - Adds a new diff_only mode and allows mode to be configured

- [#35](https://github.com/RocketChat/layne/pull/35) [`23f1b32`](https://github.com/RocketChat/layne/commit/23f1b32cfb0092330d2fe467e7d6c45993bd7a83) Thanks [@julio-rocketchat](https://github.com/julio-rocketchat)! - Rewrites the codebase from JavaScript to TypeScript for improved type safety and developer experience. No behavioral changes; deployment, configuration schema, and all external interfaces are identical.

- [#22](https://github.com/RocketChat/layne/pull/22) [`294d984`](https://github.com/RocketChat/layne/commit/294d9840a732154610e9be8141609a85732f4d63) Thanks [@julio-rocketchat](https://github.com/julio-rocketchat)! - Adds a new feature that allows exceptions to be approved by specific teams or people

- [#29](https://github.com/RocketChat/layne/pull/29) [`2427573`](https://github.com/RocketChat/layne/commit/242757385abd1b45607b8b97ccf7987c4e0cd3e1) Thanks [@julio-rocketchat](https://github.com/julio-rocketchat)! - Makes timeouts configurable on global and per repo levels

- [#34](https://github.com/RocketChat/layne/pull/34) [`8a4126f`](https://github.com/RocketChat/layne/commit/8a4126f07c33621b1b8c58cf57cb7707a61ac67b) Thanks [@julio-rocketchat](https://github.com/julio-rocketchat)! - Adds support for warnings for commenter as well as rule names

## 1.2.0

### Minor Changes

- **Exception Approvals**: Configure specific users or teams who can approve PRs that would otherwise fail the security scan. When an authorized approver approves a PR, Layne automatically re-runs the scan and passes it with a clear audit trail. Features include:
- Automatic re-run on `pull_request_review` webhook when authorized approver approves
- Team membership resolution via GitHub API
Expand All @@ -59,20 +45,6 @@

### Minor Changes

- [#15](https://github.com/RocketChat/layne/pull/15) [`e000196`](https://github.com/RocketChat/layne/commit/e00019655f2e9f5ade9e9be07ff92a176fa93d93) Thanks [@julio-rocketchat](https://github.com/julio-rocketchat)! - Adds support for creating comments in the PRs

- [#13](https://github.com/RocketChat/layne/pull/13) [`4c19ba0`](https://github.com/RocketChat/layne/commit/4c19ba0c4f758c90ab6fb1161f8999a97510865f) Thanks [@julio-rocketchat](https://github.com/julio-rocketchat)! - Adds a new suppressor feature to ignore findings with a "// SECURITY: XYZ" comment

## 1.0.0

### Major Changes

- [#6](https://github.com/RocketChat/layne/pull/6) [`a11a412`](https://github.com/RocketChat/layne/commit/a11a412371973a812e90a563152cf7ac6c0c7f43) Thanks [@julio-rocketchat](https://github.com/julio-rocketchat)! - Adds support for workflow jobs alongside workflow runs

- [#8](https://github.com/RocketChat/layne/pull/8) [`62af89e`](https://github.com/RocketChat/layne/commit/62af89e72a98c597d8524e8d88bcf67c29954a16) Thanks [@julio-rocketchat](https://github.com/julio-rocketchat)! - Fixes a bug in which Layne ends up scanning files that are unrelated to the PR

- [#10](https://github.com/RocketChat/layne/pull/10) [`cafaf75`](https://github.com/RocketChat/layne/commit/cafaf7584beb145977aa5ebcce7672273098fdee) Thanks [@julio-rocketchat](https://github.com/julio-rocketchat)! - Fixes an issue that wouldn't reschedule a Layne scan if there's an existing failed scan

- [`a88504d`](https://github.com/RocketChat/layne/commit/a88504d436bc0aafca94c94ee388560ca89c57c7) Thanks [@julio-rocketchat](https://github.com/julio-rocketchat)! - Changes the documentation to add security architecture and PR guidelines

- [#4](https://github.com/RocketChat/layne/pull/4) [`f2de34f`](https://github.com/RocketChat/layne/commit/f2de34f83c26dd5738cc86b82573496f4e3c565f) Thanks [@julio-rocketchat](https://github.com/julio-rocketchat)! - Adds a new trigger for Layne: workflow_run
Expand Down
14 changes: 10 additions & 4 deletions CLAUDE.md
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@ This file provides guidance to Claude Code (claude.ai/code) when working with co

## What this is

Layne is a self-hosted GitHub App that centralises security scanning across repositories. It receives `pull_request` webhooks, enqueues scan jobs via BullMQ/Redis, posts results back as GitHub Check Run annotations, manages PR labels, and sends chat notifications. It runs four scanners: Semgrep (SAST), Trufflehog (secret detection), Claude (malicious intent detection), and Pi Agent (agentic deep code review).
Layne is a self-hosted GitHub App that centralises security scanning across repositories. It receives `pull_request` webhooks, enqueues scan jobs via BullMQ/Redis, posts results back as GitHub Check Run annotations, manages PR labels, and sends chat notifications. It runs five scanners: Semgrep (SAST), Trufflehog (secret detection), Claude (malicious intent detection), Spectre (multi-provider LLM scanning), and Dep Doctor (dependency health).

## Commands

Expand Down Expand Up @@ -52,7 +52,13 @@ Optional:
```
REDIS_URL # defaults to redis://localhost:6379
PORT # defaults to 3000
ANTHROPIC_API_KEY # required when any repo has claude.enabled: true
ANTHROPIC_API_KEY # required when any repo has claude.enabled: true or spectre with anthropic provider
OPENAI_API_KEY # required when any repo uses spectre with openai provider
GEMINI_API_KEY # required when any repo uses spectre with google provider
MISTRAL_API_KEY # required when any repo uses spectre with mistral provider
AWS_ACCESS_KEY_ID # required when any repo uses spectre with bedrock provider
AWS_SECRET_ACCESS_KEY # required when any repo uses spectre with bedrock provider
AWS_REGION # required when any repo uses spectre with bedrock provider
METRICS_ENABLED # set to "true" to enable Prometheus metrics
METRICS_PORT # worker metrics server port, defaults to 9091
DOMAIN # used for Rocket.Chat icon_url and TLS
Expand Down Expand Up @@ -108,8 +114,8 @@ Two separate Node.js processes:
- `claude.ts` - calls the Anthropic API to detect malicious intent; **disabled by default**, opt in per repo; skips binary files; caps files at 50 KB; batches at 100 KB per API call; errors are caught and logged without failing the scan. Supports two modes (configured per-repo in `config/layne.json`):
- **Prompt mode** (default): single `messages.create` call with a system prompt; use `claude.prompt` to override
- **Skill mode**: uses the Anthropic [API Skills beta](https://platform.claude.com/docs/en/build-with-claude/skills-guide) - adds a `code_execution` tool + an uploaded skill to each batch call, enabling runtime decoding, registry lookups, and richer static analysis; set `claude.skill: { id, version }` to enable; handles `pause_turn` continuations automatically (up to 10 turns per batch)
- `pi-agent.ts` - agentic scanner using `@mariozechner/pi-coding-agent`; **disabled by default**, opt in per repo; runs a full agent session with confined read-only file tools (read, grep, find, ls) so the model can follow imports across file boundaries; configurable thinking level (off/minimal/low/medium/high/xhigh), timeout (default 3 min), and AI provider; supports Anthropic, OpenAI, Google, Mistral, and Amazon Bedrock; ruleId prefix `pi_agent/`; **note:** non-deterministic - the same code may produce different ruleIds or line numbers across runs, which can affect exception approval stability
- `pi-agent-tools.ts` - workspace-confined file exploration tools for Pi Agent; `createConfinedTools()` wraps read/grep/find/ls with path validation to prevent the agent from escaping the workspace via `confinePath()`
- `spectre.ts` - malicious intent scanner; **disabled by default**, opt in per repo; makes a single direct LLM call per file (no agent session, no tools, no import following); supports Anthropic, OpenAI, Google, Mistral, and Amazon Bedrock via `@mariozechner/pi-ai`; tier-prioritised file cap (manifest/CI files always scanned first); configurable per-repo skip paths/extensions, file cap, diff line cap, min severity, and concurrency; ruleId prefix `spectre/`
- `dep-doctor.ts` - dependency health scanner; **disabled by default**, opt in per repo; only fires when a lockfile is changed by the PR; diffs the changed lockfile against the merge-base version (via `git show`) to identify newly-added packages only; runs OSV-Scanner for CVE detection (ruleId `dep-doctor/<CVE-ID>`); checks npm/PyPI registry APIs for abandoned (ruleId `dep-doctor/abandoned`) and deprecated (ruleId `dep-doctor/deprecated`) packages; supported lockfiles: `package-lock.json`, `yarn.lock`, `pnpm-lock.yaml`, `requirements.txt`, `Pipfile.lock`, `poetry.lock`, `uv.lock`, `go.sum`; registry health checks for npm and PyPI only (Go skipped); errors caught and logged without failing the scan; requires `osv-scanner` in PATH
- `helpers.ts` - shared adapter utility functions

**Common finding shape:**
Expand Down
8 changes: 5 additions & 3 deletions CONTRIBUTING.md
Original file line number Diff line number Diff line change
Expand Up @@ -8,11 +8,13 @@ Thanks for your interest in contributing. This document covers the branch model,

| Branch | Purpose |
|---|---|
| `develop` | Default branch. All PRs and releases flow through here. |
| `develop` | Default branch. All PRs target here. |
| `main` | Releases only. Never commit directly. |

**Release flow:**
1. As PRs merge to `develop`, the changeset bot opens and maintains a **"chore: release vX.Y.Z"** PR targeting `develop`
2. Merging that PR to `develop` creates a GitHub release automatically
1. As PRs merge to `develop`, the changeset bot opens and maintains a **"chore: release vX.Y.Z"** PR targeting `main`
2. Merging that PR to `main` creates a GitHub release automatically
3. A **"chore: sync main → develop"** PR is then opened automatically — merge it to keep `develop` up to date

---

Expand Down
12 changes: 10 additions & 2 deletions Dockerfile
Original file line number Diff line number Diff line change
Expand Up @@ -18,21 +18,29 @@ RUN npm run build
FROM node:22-alpine AS runtime

# Pin tool versions for reproducible builds. Update periodically and verify in staging.
# trufflehog and semgrep are the only external binaries Layne shells out to.
# trufflehog, semgrep, and osv-scanner are the external binaries Layne shells out to.
RUN apk add --no-cache \
git \
python3 \
py3-pip \
wget \
ripgrep \
&& python3 -m pip install --break-system-packages semgrep==1.154.0 \
&& ARCH="$(uname -m | sed 's/x86_64/amd64/;s/aarch64/arm64/')" \
&& wget -qO- "https://github.com/trufflesecurity/trufflehog/releases/download/v3.93.7/trufflehog_3.93.7_linux_${ARCH}.tar.gz" \
| tar -xz -C /usr/local/bin trufflehog \
&& chmod +x /usr/local/bin/trufflehog
&& chmod +x /usr/local/bin/trufflehog \
&& wget -qO /usr/local/bin/osv-scanner "https://github.com/google/osv-scanner/releases/download/v2.3.8/osv-scanner_linux_${ARCH}" \
&& chmod +x /usr/local/bin/osv-scanner

# Run as a non-root user so a compromised container cannot write to the host.
RUN addgroup -S layne && adduser -S layne -G layne

# Expose ripgrep at the path pi-coding-agent expects (~/.pi/agent/bin/rg).
RUN mkdir -p /home/layne/.pi/agent/bin \
&& ln -s /usr/bin/rg /home/layne/.pi/agent/bin/rg \
&& chown -R layne:layne /home/layne/.pi

WORKDIR /app

COPY --from=deps /app/node_modules ./node_modules
Expand Down
22 changes: 13 additions & 9 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,7 @@ This tool was based on [Reddit's Implementation](https://web.archive.org/web/202

```
┌─────────────────────────────────┐
│ GITHUB PULL REQUEST │──────────────────────┐
│ GITHUB PULL REQUEST │◀────────────────────────┐
│ (OPEN, SYNC, REOPEN) │ │
└─────────────────────────────────┘ │
│ Check run │
Expand All @@ -29,32 +29,36 @@ This tool was based on [Reddit's Implementation](https://web.archive.org/web/202
│┌─────────────┐ │ Schedules job ┌────────────────┐ ┌────────────┐ │ │
││ LAYNE │◀─┘ ─────────────────▶│ REDIS │───▶│ TRUFFLEHOG │──┐ │ │
││ SERVER │ │ (BULLMQ) │ │ └────────────┘ │ │ │
│└─────────────┘ └────────────────┘ │ ┌────────────┐ │ ┌──────────┐│
│ │─▶│ SEMGREP │──┼─▶│ REPORTER ││
│└─────────────┘ └────────────────┘ │ ┌────────────┐ │ │ │
│ │─▶│ SEMGREP │──┤ │ │
│ │ └────────────┘ │ │ │
│ │ ┌────────────┐ │ ┌──────────┐│
│ ├─▶│ CLAUDE │──┼─▶│ REPORTER ││
│ │ └────────────┘ │ └──────────┘│
│ │ ┌────────────┐ │ │
│ ├─▶│ CLAUDE │──┤ │
│ ├─▶│ SPECTRE │──┤ │
│ │ └────────────┘ │ │
│ │ ┌────────────┐ │ │
│ └─▶│ PI AGENT │──┘ │
│ └─▶│ DEP DOCTOR │──┘ │
│ └────────────┘ │
└──────────────────────────────────────────────────────────────────────────────────────────┘
```

When a PR is opened or updated - or after a workflow/job runs, depending on your configured trigger -, GitHub sends a webhook to Layne. The server immediately enqueues a scan job and returns `200 OK` to GitHub. A worker picks up the job, clones exactly the commit that triggered the event, hands the changed files off to each configured scanner (Semgrep, Trufflehog, Claude, Pi Agent), collects their findings, and posts the results as inline annotations on the Check Run.
When a PR is opened or updated - or after a workflow/job runs, depending on your configured trigger -, GitHub sends a webhook to Layne. The server immediately enqueues a scan job and returns `200 OK` to GitHub. A worker picks up the job, clones exactly the commit that triggered the event, hands the changed files off to each configured scanner (Semgrep, Trufflehog, Claude, Spectre, Dep Doctor), collects their findings, and posts the results as inline annotations on the Check Run.

Only the files modified in the PR are passed to each scanner. Findings in files you did not touch are never reported.

### Scanners

Layne ships with four built-in scanners. You can enable, disable, or configure each one per repository in `config/layne.json`.
Layne ships with five built-in scanners. You can enable, disable, or configure each one per repository in `config/layne.json`.

| Scanner | What it detects | Notes |
|---|---|---|
| [Semgrep](https://semgrep.dev) | SAST - bugs, vulnerabilities, insecure patterns | Runs `semgrep scan --config auto` by default; fully configurable via `extraArgs` |
| [Trufflehog](https://github.com/trufflesecurity/trufflehog) | Secrets, API keys and credentials | Runs `trufflehog filesystem`; use `--only-verified` to reduce noise |
| [Claude](https://www.anthropic.com) | Bugs, vulnerabilities, backdoors, obfuscated payloads, supply-chain attacks (you can define a system prompt or a skill to use) | Disabled by default; opt in per repo; requires `ANTHROPIC_API_KEY` |
| [Pi Agent](https://pi.dev) | Agentic deep code review - autonomously traverses imports and follows suspicious patterns across file boundaries | Disabled by default; opt in per repo; supports multiple AI providers |
| [Claude](https://www.anthropic.com) | Malicious intent, backdoors, obfuscated payloads, supply-chain attacks | Disabled by default; opt in per repo; requires `ANTHROPIC_API_KEY` |
| Spectre | Malicious intent via single LLM call per file | Disabled by default; opt in per repo; supports Anthropic, OpenAI, Google, Mistral, Bedrock |
| Dep Doctor | CVEs, abandoned and deprecated dependencies | Disabled by default; opt in per repo; requires `osv-scanner` in PATH |

You can also add your own scanners. See [Extending Layne](website/docs/extending.md).

Expand Down
13 changes: 3 additions & 10 deletions config/layne.json
Original file line number Diff line number Diff line change
Expand Up @@ -10,8 +10,7 @@
"onFailure": ["needs-security-review"],
"removeOnSuccess": ["needs-security-review"],
"onSuccess": [],
"removeOnFailure": [],
"onException": ["security-exception-used"]
"removeOnFailure": []
}
},
"acme/frontend": {
Expand Down Expand Up @@ -47,19 +46,13 @@
"webhookUrl": "$PAYMENTS_ROCKETCHAT_WEBHOOK_URL",
"template": ":rotating_light: *Payment system alert — {{repo}} PR #{{prNumber}}*\n{{total}} finding(s): {{critical}} critical, {{high}} high, {{medium}} medium, {{low}} low"
}
},
"exceptionApprovers": {
"users": ["payments-security-lead"],
"teams": ["acme/payments-security"]
}
},
"RocketChat/security": {
"claude": {
"enabled": false
},
"piAgent": {
"enabled": true,
"model": "claude-haiku-4-5-20251001"
"model": "claude-sonnet-4-6",
"skill": { "id": "skill_0187mWg6hVHB3eJHkeDmi2Eg", "version": "latest" }
}
},
"RocketChat/Rocket.Chat.ReactNative": {
Expand Down
Loading