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
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 |
macOS and Linux:
curl -fsSL https://raw.githubusercontent.com/Glyph-Software/sentinel/main/install.sh | shWindows:
irm https://raw.githubusercontent.com/Glyph-Software/sentinel/main/install.ps1 | iexEither 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.
sentinelOne-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.mdPick up where you left off:
sentinel --continueThere 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.
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.3Or 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.
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.
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.
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.
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, andprovidersare 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 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.
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.
| 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 |
bun install
bun test419 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.