Skip to content

Repository files navigation

sentinel — a red-teaming agent, running glyphsoftware/sentinel-r2.2

A red-teaming agent for the terminal — and every command it runs happens inside a container it cannot escape.

Install · Quickstart · The model · Subagents · Safety · Docs


sentinel

Point it at a codebase and it maps the attack surface, audits for vulnerabilities, proves exploitability against targets you have put in scope, red-teams LLM applications against the OWASP LLM Top 10, writes detections mapped to MITRE ATT&CK, and hands back a report with severities and evidence.

It gets there the way an operator does: reading the code, running commands, writing probes and proofs-of-concept. So it has real file and shell tools rather than a canned scanner's checklist — and it asks before it changes anything, with every command confined to a container that cannot see the rest of your machine.

The offensive work is built for authorized engagements with defined rules of engagement, and the agent's briefs say so: the exploit type is told to stop and report rather than proceed when the target or the authorization is unclear.

Built as a ReAct loop directly on the openai SDK. No LangChain, no agent framework: the loop, the tool protocol, the context management, the subagent registry, and the approval gate are all here in code you can read.

Best with glyphsoftware/sentinel-r2.2 — the model this agent is built around
Runs on any OpenAI-compatible endpoint with streaming tool calls
Ships as one standalone binary — macOS (arm64/x64), Linux (arm64/x64), Windows (x64)
Tools 19, native function-calling, no prose parsing
Sandbox podman / docker / nerdctl, image pinned by digest, egress allowlisted
Stage 0.0.3-ALPHA

Install

macOS and Linux:

curl -fsSL https://raw.githubusercontent.com/Glyph-Software/sentinel/main/install.sh | sh

Windows:

irm https://raw.githubusercontent.com/Glyph-Software/sentinel/main/install.ps1 | iex

Either one downloads the build for your platform from the newest release, verifies it against SHA256SUMS.txt, puts sentinel on your PATH, and pulls the sandbox image. See installer options for pinning a version or changing where it lands, and from source to run it out of this repo.

A container runtime is optional but strongly recommended — without one, shell commands fall back to the host and say so loudly. See when there is no runtime.

Quickstart

sentinel

One-shot, then exit:

sentinel "map the attack surface of this service"

Non-interactive — progress on stderr, answer on stdout, no approval prompts:

sentinel -p "audit the auth middleware for broken access control" > findings.md

Pick up where you left off:

sentinel --continue

There is nothing to configure first. The first run opens /provider: pick an endpoint — OpenAI, Anthropic, OpenRouter, Azure, Bedrock, Ollama, LM Studio, vLLM, or anything OpenAI-compatible — and give it a key. The key is encrypted into ~/.sentinel/secrets.db; the rest of the profile goes to ~/.sentinel/settings.json. Several profiles coexist, each with its own key, and --provider <name> switches between them in a word.

No key is read from .env or from your shell environment, deliberately. A variable exported for one tool should not silently become the credential for another — the bug that settled it was a stray OPENAI_API_KEY being handed to AWS Bedrock as a bearer token, which rejected it while SigV4 never got a turn.

The model to run it on

sentinel drives any OpenAI-compatible endpoint, and a frontier general model works well. But it is named after, and built around, glyphsoftware/sentinel-r2.2 — Glyph Software's own offensive-security model, and the pairing this agent is designed for.

Where a general model has to be talked into security work, R2.2 was fine-tuned on nothing else: authorized penetration-test trajectories in which the model is handed a scoped target and a shell tool, then enumerates, finds a foothold, escalates, and writes up the attack path with the root cause of each weakness and how to fix it. That is the same shape as a sentinel turn — reason, call a tool, read the result, iterate — so the loop and the weights want the same thing.

Architecture Gemma 4 26B-A4B sparse MoE — 128 experts, top-8 routing
Parameters ~25.8B total, ~4B active per token
Context up to 262,144 tokens — a long engagement fits in one session
Trained on sentinel-exploit-db: HTB-style machine pentests, CTFs, real CVE chains, AD / cloud / database engagements, V8 exploitation, ATT&CK-mapped red-team lifecycles
Tuned for reasoning and tool calling, which is the whole interface here
Weights full merged bf16 — loads directly, no adapter step

The active-parameter count is the practical headline: 26B of capacity to draw on at roughly the inference cost of a 4B dense model, which is what makes a long agentic loop affordable to run locally.

Pointing sentinel at it. Serve the weights behind any OpenAI-compatible server that streams tool calls — the default profile assumes vLLM on http://localhost:8000/v1 — then:

sentinel --base-url http://localhost:8000/v1 --model glyphsoftware/sentinel-r2.2 --temperature 0.3

Or save it once with /provider and switch to it in a word with sentinel --provider sentinel. No key is needed for a local endpoint.

Two settings are worth getting right, both from the model card: temperature 0.2–0.4 (sentinel defaults to 1, which is too hot for this model — hence the flag above), and enable_thinking=True on the chat template, since with it off the template appends an empty thought block the model was never trained against.

Access and licence. The weights are gated and proprietary — Glyph Proprietary License v1.0, all rights reserved, granted to authorized licensees under a signed agreement. Requesting access means confirming you will only point it at systems you are permitted to test. The previous generation, sentinel-r2.1, has a GGUF build if you want something smaller on llama.cpp.

Read the model card's own limitations before you lean on it: the training set is a specialized 551-conversation slice rather than a broad capability upgrade, it skews toward Linux boxes and web/service CVEs, and no end-to-end engagement success rate against real targets has been published. It is an aid to an operator, not a replacement for one.

Subagents

The registry is where the security work lives. A subagent is a second ReAct loop with its own context window and a narrower toolset, and it reports back a single string — so the parent pays for the conclusion rather than the whole exploration. None of them get the Agent tool, so they cannot spawn further agents.

Type Access Role
recon read-only Map the attack surface — services, endpoints, routes, dependencies, exposed secrets, trust boundaries. Enumerate, do not exploit
vuln read-only Audit for injection, broken access control, auth and session handling, hard-coded secrets, unsafe deserialization, SSRF, crypto misuse — with severity, file:line, data flow, and an exploit scenario per finding
exploit read-write Minimal proof-of-concept against an in-scope target, to confirm exploitability — nothing weaponized, destructive, or persistent
llm-redteam read-write Prompt injection (direct and indirect), jailbreaks, system-prompt and training-data leakage, insecure output handling, excessive agency. Writes the probes, runs them, scores what succeeded
detect read-write Author detections (Sigma, YARA, or the project's own format), hunt for IOCs, map findings to ATT&CK techniques and D3FEND countermeasures
report read-write Executive summary, then each finding with a CVSS-style severity, evidence, reproduction, impact, remediation

Two general-purpose types back them up, for the reading and the fixing that security work turns into:

Type Access Role
explore read-only Search and read code; report findings with file:line
code read-write Carry out one self-contained change

report is told not to invent findings or inflate severity. The briefs are in src/runtime/agents.ts if you would rather read them than take that on trust.

How it works

Each user turn runs a ReAct loop, capped at --max-steps (default 30):

reason  ── the model says what it is about to do        ✳ Checking how input reaches the shell.
  ↓
act     ── it calls one or more tools                   → grep pattern=exec\( glob=**/*.ts
  ↓
observe ── results are appended to the transcript       ✓ grep exec\( → 3 match(es)
  ↓
(repeat, or answer without calling a tool → done)

Tool calls use the model's native function-calling interface, so actions are structured JSON rather than parsed out of prose. Each step the model either calls tools or answers, never both; independent calls in one step run in parallel and each gets its own observation.

The visible reasoning is the assistant text that streams before each set of calls. The system prompt asks for one or two concrete sentences — "checking whether this endpoint validates the token before it reads the id" — not narration of the obvious.

Tools

Nineteen, in four groups. Full descriptions and how to add one are in docs/tools.md.

Group Tools
Files and shell Read Write Edit Glob Grep Bash Write, Edit, and Bash need approval
Tasks TaskCreate TaskList TaskGet TaskUpdate TaskOutput TaskStop A shared list with dependencies, plus background work
Subagents Agent ListAgents SendMessage Spawn, list, and resume with the transcript intact
The outside world Monitor CronCreate CronList CronDelete Streams and schedules that feed events back into the loop

Monitor and the schedulers make the loop reactive: events drain into the transcript between steps, and an event arriving while the REPL sits idle wakes it and drives a turn with no user input — a long-running scan or a tailed log reports back mid-conversation. Monitored output arrives fenced and labelled untrusted, because it came from outside: it is data, not instructions.

Safety

An agent doing this work runs hostile input through your machine all day, so confinement is the feature, not the disclaimer.

Commands run in a container. The workspace is bind-mounted at its own absolute path so paths in output stay correct, and nothing else is — $HOME, ~/.ssh, and the rest of the machine are simply not there. Capabilities are dropped, resources are capped, and the container's own root filesystem is immutable, so a command cannot shim the tools whose output the agent later trusts. The image is pinned by digest, not by tag.

Egress is allowlisted by default. The container sits on an internal network with no route out; its only path to the internet is a proxy permitting a starter list of package and source hosts. Enforcement is the network rather than HTTP_PROXY, so unsetting the proxy variables and opening a raw socket gets "network is unreachable" rather than a connection. Widen it deliberately — sandbox.allow and sandbox.allowPorts for a scope, network: full for none at all, which warns that it has.

The rest, in short:

  • Approval before mutation. Writes, edits, and shell commands prompt with a diff or the command itself. A denial is final — it goes back to the model as an observation, with instructions not to work around it.
  • Unrecoverable commands are refused outright, not merely prompted. This is a guard against accidents, not attacks; the sandbox is what bounds the damage.
  • Paths cannot escape the workspace../, absolute paths, and symlinks pointing out are resolved on disk, not read as text. .git/, .sentinel/, and .ssh/ are never writable by tools, whatever the mode.
  • Tools do not inherit your credentials. Anything credential-shaped is stripped from the environment they get, including the key the agent runs on.
  • A repository cannot configure its way to execution. hooks, statusLine, env, permissions.allow, sandbox, baseURL, and providers are ignored in a project's own settings files — a target you have cloned to audit does not get to weaken its own confinement.

The details, and the reasoning behind each choice, are in docs/safety.md — worth reading before you trust it with anything.

Sessions and context

Sessions save after every turn to ~/.sentinel/sessions. --continue resumes the most recent one for this directory, --resume [id] a specific one; the transcript, task list, and unexpired schedules come back — an engagement can run across days. Background work does not: those processes died with the session, so their tasks return as cancelled rather than pretending to still run.

The status line carries a bar for how full the context window is. At 75% you are asked whether to compact — the transcript is replaced by a summary of what the work depends on. At 80% the oldest tool outputs are blanked in place, which is cheap and keeps the transcript valid. Where there is nobody to ask (-p, a pipe, CI) it compacts rather than run into the window mid-task. See docs/sessions.md.

Configuration

Everything has a flag, most flags have an environment variable, and /provider writes the settings file for you. The common ones:

Flag Effect
-m, --model <id> Model for this run
--provider <name> Switch to a saved profile
--base-url <url> Point at any OpenAI-compatible endpoint
--max-steps <n> Cap the ReAct loop (default 30)
-y, --auto-approve Skip the approval prompts
-C, --cwd <dir> Run against another directory
-c / -r [id] Continue or resume a session

In-session: /provider /model /tools /agents /tasks /cron /sandbox /memory /cost /compact /session /cwd /reset-auth /commands /help /exit. shift+tab cycles the approval mode (ask → auto-accept edits → plan → bypass).

Subcommands: sentinel sandbox status shows the runtime, image, and mode; sentinel sandbox pull pre-fetches the image and exits.

Full tables — every flag, the UI environment variables, provider profiles, settings files, and the key vault — are in docs/configuration.md.

Docs

Architecture The ReAct loop in detail, source layout
Tools All 19 tools, the subagent briefs, and how to add one
Safety Sandbox, egress, approvals, credential handling
Configuration Installer options, flags, providers, slash commands, keys
Sessions and context Events, resuming, compaction

Development

bun install
bun test

419 tests. The tool tests run against real temporary directories; the agent tests drive the full loop against a stub of the OpenAI streaming API, so there is no network call and no key needed. The oci tests are the exception — they start real containers, and need the sandbox image to be pullable.

Script Description
bun run start Start an interactive session
bun run dev Same, with --watch
bun run build Compile standalone executables into dist/
bun test Run the test suite
bun run typecheck Typecheck

Releases are driven by package.json: push a commit that changes version or stage and the release workflow builds and publishes the executables. Every other push to main is a no-op.


Releases

Packages

Contributors

Languages