Repository navigation
Releases: ianclemence/lantern
Release list
lantern 0.1.6
lantern 0.1.6
Every item on the 0.1.5 roadmap is now built, plus two security-hardening
passes over the agent's own attack surface, bounded multi-agent delegation,
full session CRUD, and a real daemon mode.
Roadmap, closed
- AD / Impacket-style tooling (
--offensive-gated):GetUserSPNs.py
(Kerberoasting),GetNPUsers.py(AS-REP roasting),crackmapexec
(SMB/WinRM/LDAP enumeration - a closed allowlist of read-only modules,
no execution method ever accepted),bloodhound-python(AD relationship
collection). - API-schema-aware testing:
api_schema_scanwalks an OpenAPI/Swagger
document and resolves the real auth requirement per endpoint, honoring
operation-levelsecurity: []overriding a document default.
graphql_introspectreports whether introspection is enabled and, if so,
the schema's types and root names. Both passive - they read a schema the
target already serves and call nothing it describes. - Cloud posture / container exposure:
cloud_bucket_check(anonymous
S3/Azure Blob/GCS listing, credential-free),container_expose_check
(unauthenticated Docker daemon API or anonymous Kubelet API, one read-only
request),host_kubehunter(active,--offensive-gated). - Daemon mode:
lantern queue add/list/removepluslantern daemon,
a polling loop against Lantern's own SQLite file (no socket, no HTTP
server) that claims and runs jobs one at a time and stops cleanly on
SIGINT/SIGTERM. - Bounded multi-agent delegation: researcher/coder/pentester can call
delegate_taskto spin off one sub-investigation. Four properties hold by
construction, not just by docstring: a delegated task's tools are a
subset of the calling role's own list, it shares the parent's scope/
--offensivegrant exactly, it cannot delegate again, and it's capped at
5 steps per call / 10 total per role turn. Now rendered inchatas a
nested block (↳ researcher delegates: "...") and logged to the audit
trail as its own event. - Full session CRUD:
lantern delete <flow-id> --yes;chat's/new
and/delete <flow-id> yes, exercised live under a pty.
Five more native tools, and role-scoped tool schemas
waf_fingerprint (WAF/CDN signature match from one ordinary response - no
payloads - so a zero-finding scan can tell "clean" apart from "a WAF ate
every probe"), secret_scan (regex match for cloud/SaaS keys and JWTs on
text another tool already captured, every hit masked), host_testssl,
host_gobuster, and host_amass (active, --offensive-gated - distinct
from the passive subdomain_enum below).
Registry::defs_for(focus) now scopes the tool schemas sent to the model
to each role's own focus list instead of sending all 26 tools' schemas to
every role - pentester's per-call overhead drops to ~2,656 tokens,
researcher ~1,898, coder ~953, reflector ~444, down from a flat worst case
that was already over 3,400 before this release added anything.
Passive subdomain discovery, and prompts that earn their own budget
subdomain_enum (certificate-transparency lookup via crt.sh - touches no
target infrastructure). A role whose prompt repeats enough of its own
vocabulary (named injection classes, GraphQL/IAM/Kerberoasting/JWT/
container-escape, ...) now earns up to 4 extra steps over its static
default. Engagement-profile prompts (WebApp/Api/InternalAd/Cloud/
NetworkInfra) add a short block naming the right taxonomy (OWASP, MITRE
ATT&CK, CIS) and what the toolset genuinely cannot check for that profile,
so a role says "no tool for that" instead of narrating a check it never ran.
Security hardening
- Scope bypass, closed: a bare CIDR target (e.g. msfconsole's RHOSTS)
was checked against only its network's base address, so a scope of
10.0.0.0/24waved through a target of10.0.0.0/8. A CIDR target is
now checked as the whole range it names. - DNS drift pinned to the audit trail: host-tool targets are resolved
immediately before execution; a hostname that now resolves outside every
declared scope range is refused, and the resolved IPs land in the command
audit row (newresolved_ipscolumn) and inlantern reporteither way. - Runtime limits now actually device-derived: concurrency and child
memory limits were computed from the live device profile and then
discarded in favor of hardcoded defaults. They now derive from the host's
real cores/RAM unlessLANTERN_CONCURRENCY/LANTERN_CHILD_MEM_MBis set. - Unbounded reads, closed on both sides: every in-process HTTP fetch
facing a target (http_probe,waf_fingerprint,dir_bruteforce,
subdomain/search lookups) buffered a full response before truncating -
a hostile or oversized response could grow this process's own heap
without limit. New capped readers stop at the limit instead of reading
past it first. The same pattern is fixed on the model-provider side
(Anthropic/OpenAI-compatible/Ollama embedding clients, the setup wizard's
model-list fetch). - Target-facing
User-Agentis now configurable (LANTERN_USER_AGENT),
self-identifying by default. - Cross-process flow-id collision, fixed: flow IDs mixed only a
millisecond timestamp and a per-process counter that resets on every
start - twolantern runprocesses launched in the same millisecond
produced the same flow ID and shared one row. The OS pid is now mixed in;
verified against 20 real concurrent processes, 20 distinct IDs. chatTUI: the approval card's default highlight flipped from
"allow once" to "deny" (matching the module's own "deny-by-inaction"
doc comment - a reflexive Enter no longer grants active testing);
str::floor_char_boundaryreimplemented locally rather than relying on
a newer Rust than the declared 1.85 minimum; aMutexguard held across
an.awaitin the steering-queue drain is fixed (clippy
await_holding_lock).
README
Rewritten twice for structure and to close out every section the roadmap
items touch - "Roles and delegation," "Session lifecycle," "Concurrency,
sessions, and the daemon," and a corrected "Detectability, traces, and the
agent's own attack surface" - plus GETTING_STARTED.md kept in sync with
the current tool and test counts.
Verified
- 317 tests pass, 0 failed, 1 ignored (the live NVD query) across the
workspace: lantern-core 58, lantern-llm 33, lantern-tools 109,
lantern-agent 65, lantern-cli 52. cargo check --workspace --all-targets --release: zero warnings.lantern doctoron the reference device (4-core aarch64, 8.5 GiB RAM):
reports the device-derived runtime (concurrency 4, child limit 503 MB,
token budget 6000) and exits in ~91 ms.- Built and tested on this device at 2.8 GB / 29 GB free (under its own 20%
floor) -doctorcorrectly reportsUNDER THE FLOOR, confirming the
0.1.5 behavior (the floor guardssetup, notrun/ask/chat) still
holds. - Release binary: 9,464,360 bytes (aarch64).
Upgrade
git pull
cargo build --release # ~/.cargo/bin/cargo if it is not on PATH
./target/release/lantern doctorSchema migrates automatically to v3 (the resolved_ips column from 0.1.5's
work and the new queue table) - no manual step, and flows recorded on
earlier versions still render.
Attached: lantern-v0.1.6-aarch64-linux-gnu (9,464,360 bytes), built on
Debian 13 / aarch64, kernel 6.18. For anything else, build from source as
above.
lantern 0.1.5
lantern 0.1.5
A conversational terminal over the assessment flow — and assessments are no
longer blocked by the disk floor.
lantern chat
The same pipeline as run and ask, behind a screen. The transcript stays in
the terminal's own scrollback (no alternate buffer, so the terminal's native
scrolling reaches every previous message) while a ten-row viewport at the
bottom holds the status line, the live activity trail and the input:
example.com |example.com recon|deepseek-flash |idle
› Check TLS and headers, defensive only
Enter send · Esc abort · / commands · ^C quit
/palette —/target /scope /roles /offensive /dry-run /steps /flows /clear /quit, filtered as you type, Tab completes, an exactly typed name
wins over the highlighted row, and commands that need arguments stay
composed instead of erroring on an empty one.- Live progress — the flow reports plan, role starts, each tool call and
role finishes as they happen; the status line carries the running role, the
step count and elapsed time. - Inline approval — active testing requested while the session does not
allow it raises a card instead of a wall of text:1allows it for this
flow only,2(or Esc, which is deny-by-inaction) keeps reconnaissance.
The session grant is recorded on the per-flow config, so the runtime's own
check agrees with the card. - Steering — a line typed while a flow runs joins the working set and
cross-role memory at the next step boundary, so the roles still to run act
on it. - Abort — Esc or Ctrl-C sets a flag the runtime checks per model step and
per tool call. The flow reportsaborted, keeps what it finished, and the
stored report saysabortedtoo rather thancompleted.
Nothing here is trusted either: without a terminal chat refuses and names
ask; there is still no prompt that can hang; roles cannot stop for questions
in chat (the UI owns stdin), so flows run non-interactively by design.
The disk floor guards provisioning, not work
The 20% free-space floor refused run, ask and chat on machines that were
already configured. Past setup, commands are bounded by the data-root and log
caps, so the floor was blocking work over unrelated disk usage.
It now guards the one step that can actually consume gigabytes —
lantern setup, which downloads packages and builds john from source. The
refusal names setup for the same reason. Reports stay informational:
lantern doctor still prints UNDER THE FLOOR, every startup line still
prints the numbers, and lantern gc still relieves the pressure.
Dependencies
The terminal is ratatui + crossterm (+ unicode-width), which is the Rust
counterpart of the framework this screen was modelled on. No new build tooling
enters the aarch64 build: no cmake, no ring, no bundled libclang.
Verified
- 234 tests pass, 1 ignored (the live NVD query), 0 warnings — up from 227
at 0.1.4. Seven new: composer editing on char boundaries, history, palette
matching and picking, approval keys, line wrapping, plus a runtime test for
progress events and one for abort semantics. - Driven through a real pty that answers cursor-position queries like an
emulator: intent card, live trail, approval allow-once,Escmid-flow
ending inflow flw_… abortedwith anabortedrow and report in SQLite,
and a clean session end. A real bug surfaced this way — the session grant
was not reaching the runtime's process-level check, so a correctly allowed
flow still warned about--offensive; fixed before release. - Floor behaviour verified at 5.21 GB free against the 6.16 GB floor:
setuprefuses naming itself, whilerun,askandchatstart, complete
and write their reports. - Binary: 8,939,992 bytes (the screen added ~262 KB over the 0.1.4 asset's
8,612,232).lantern doctorexits in 36–41 ms. run --dry-runmeasured 0.29–0.94 s idle and up to 15 s while this host was
loaded (load average 4.8 on four cores) — the spread is the host, not the
binary, and it is stated here rather than averaged away.- No warnings across
cargo check --workspace --all-targets, and no line
added in this window exceeds the project's 100-column style.
Upgrade
git pull
cargo build --release # ~/.cargo/bin/cargo if it is not on PATH
./target/release/lantern doctorNo database schema change: chat adds a flow status (aborted) and reads the
options already stored, so flows recorded on 0.1.0–0.1.4 still render. No new
required variables, and no change to run or ask flags.
Attached: lantern-v0.1.5-aarch64-linux-gnu (8,939,992 bytes), built on Debian
13 / glibc 2.41 / aarch64. For anything else, build from source as above.
lantern 0.1.4
lantern 0.1.4
The assessment flow now takes an instruction in words, not just flags.
lantern ask runs the same pipeline as lantern run, but the operator's
directive — a sentence or a whole framework — travels with the flow instead
of a fixed mission string.
lantern ask
lantern ask --target shop.example.com --scope "shop.example.com" --file framework.md
lantern ask --target example.com --scope "example.com" --prompt "Check TLS and headers, defensive only"
echo "Check DNS and TLS on example.com" | lantern ask --target example.com --scope "example.com"The full text is stored verbatim as a retention-tracked directive.md
artifact with the flow; each role objective and the plan phase see its
3,000-character head, so a 35-section framework fits the 6,000-token working
set. A framework excerpt measured here reports 7,257 in / 16 out tokens (scripted - estimate) with peak 369/6,000 — headroom confirmed, not assumed.
Intent is read, authority is not moved
The prompt is reduced to intent deterministically — no model call, no tokens,
covered by tests:
- action verbs and gated tool names (
exploit,hydra,sqlmap, active
testing) mean active testing; coverage nouns alone do not (a framework
listing "privilege escalation" as a checklist item is a topic, not an
instruction to escalate); - an explicit restraint (
defensive only,do not exploit,read-only)
always wins — even over--offensive; - contract fields (
SYSTEM_NAMEthroughSPECIAL_INSTRUCTIONS) parse out,
and--targetstays the authority: if it disagrees with the contract's
TARGETS line, the run warns naming both and the flag governs.
The rule, stated once: the prompt can restrain --offensive, never grant
it. Ask for exploitation without the flag and you get reconnaissance plus the
flag named; pass the flag with a defensive-only prompt and the prompt wins.
Both cases say why, on screen, before anything runs:
instruction : Exploit the login form with hydra and run sqlmap against search
intent : active testing requested
contract : none declared (TARGETS etc. all empty)
note : prompt asks for active testing but --offensive was not passed: running reconnaissance only (re-run with --offensive to allow it)
run and ask report a finished flow through one shared outcome block, so
the two lines at the end — spend and context — read identically either way.
Verified
- 227 tests pass, 1 ignored (the live NVD query), 0 warnings — up from 213
at 0.1.3. Fourteen new: ten for intent (including restraint-beats-verbs and
nouns-are-not-instructions), three for theaskgate, one proving the
directive artifact lands and the flow options record its length. - All four gate paths run live against
--dry-run: recon restraint,
exploit-ask downgrade, flag-vs-restraint, contract parse with eight fields,
--targetmismatch warning, and piped-stdin instruction. run --dry-run: 0.34–0.99 s for the full pipeline;ask --dry-run:
0.36–0.66 s.lantern doctorexits 0 in 36–39 ms.- No warnings across
cargo check --workspace --all-targets, and no line
added here passes the 100-column style. - The 20% disk floor did its job on the author again: at 5.82 GB free against
a 6.16 GB floor,runrefused with both numbers in the message — and the
first "benchmark" pass of this release measured those 5 ms refusals, not
runs. The timings above are re-measured after clearing regenerable build
weight.
Upgrade
git pull
cargo build --release # ~/.cargo/bin/cargo if it is not on PATH
./target/release/lantern doctorNo database schema change: the directive length joins the flow-options blob,
so flows recorded on 0.1.0–0.1.3 still render. No new required variables and
no new required flags — ask reuses --target, --scope, --roles,
--steps, --interactive and --offensive with their existing meanings.
Attached: lantern-v0.1.4-aarch64-linux-gnu (8,612,232 bytes), built on Debian
13 / glibc 2.41 / aarch64. For anything else, build from source as above.
lantern 0.1.3
lantern 0.1.3
A run now ends with what it cost and whether the token budget got in the way, a
scripted setup saves what the environment already knows, and the report
describes a flow that has actually finished.
The cost of a run, measured
findings: 3
report : /home/ianclemence/.local/share/lantern/reports/flw_1a0eb8865620000.md
model : 2,468 in / 112 out tokens - $0.0008
context : peak 91 / 6,000 tokens, 0 summarization(s), 0 budget stop(s)
Every model call now goes through one counted path, so the tokens are the
endpoint's own usage block rather than a number reconstructed afterwards.
Tokens print on every run; money prints only when you say what a token costs
where you buy it, because a rate recalled from memory would be a fiction with
a dollar sign on it:
export LANTERN_PRICE_INPUT_PER_MTOK=0.27 # USD per million input tokens
export LANTERN_PRICE_OUTPUT_PER_MTOK=1.10 # ...and per million output tokensThe context line is the instrument the defaults were missing: the peak working
set, how often it was compressed, and how often LANTERN_TOKEN_BUDGET ended a
role before it said it was done. A budget stop was previously invisible — the
role simply stopped.
A --dry-run prints the same line labelled (scripted - estimate, nothing billed), and that estimate now counts the tool schemas too, so it is a real
pre-flight size check instead of a floor.
The report stopped describing a flow that had ended
The report was rendered before the flow's own counters landed, so every
stored report claimed Status: running and quoted whatever the rows
happened to offer at that moment — the 16-step, 38-tool-call example.com run
recorded 5 model steps and 0 tool invocations in its own report.
Status, counters and spend are now written to the row before it is read:
- Status: completed
## Summary
0 finding(s), 0 tool invocation(s), 2 model step(s), 2,468 in / 112 out tokens.
Context peaked at 91 of 6,000 tokens: 0 summarization(s), 0 budget stop(s).
A flow from before this release has no measured spend, so its summary omits the
token clause rather than claiming it spent nothing.
Setup, with and without a keyboard
With a terminal the three questions are unchanged. Without one there was
nobody to ask, so setup printed a generic "export the key" line and left the
machine unconfigured — the two questions it existed to answer were simply
dropped.
It now takes provider, model, endpoint and key from the environment (the same
environment-beats-file-beats-preset merge the run itself uses), checks them
with one real call, and stores them: config.json plus credentials at
0600. A second run reports already configured and calls nothing. When the
environment cannot finish the job, nothing is written and the message names
the one variable that would:
[skip] AI provider: no terminal here - export DEEPSEEK_API_KEY; run `lantern setup` in a terminal to be walked through provider, model and key
Also new since 0.1.2: lantern setup links the binary into ~/.local/bin so
the next terminal simply types lantern. It never edits your startup files —
if that directory is not on PATH yet, the export line is printed — and it
refuses to clobber a real file already called lantern.
GETTING_STARTED.md
The walkthrough is now in the repository rather than in a chat log: clone,
build, setup (both paths), doctor, the free dry run, the real read-only
assessment, reading the report, and what the two lines at the end of a run are
telling you to change — with a table saying where each default came from.
Verified
- 213 tests pass, 1 ignored (the live NVD query), 0 warnings — up from 198
at 0.1.2. README and walkthrough quote the same number. - Measured on this host: a call starts at 3,034–3,290 tokens before the
conversation has said anything — 2,770 tokens of schema for the 20 tool
definitions sent on every request, plus 264–520 for the system prompt. A
test now fails if that overhead grows past 4,000. - Cost line proven against a local stand-in endpoint (no key, no spend):
2,468 in / 112 outat 0.27/1.10 renders as$0.0008, and the stored flow
options carry the same numbers. - Both scripted-setup paths run live: complete environment →
connected in 1 ms→config.json+credentialsat0600; no key → the skip line above
and nothing written. run --dry-run: 0.3–0.5 s for the full pipeline (one cold run 1.6 s), report
written, nothing billed.lantern doctorexits 0 in 36–38 ms.- The 20% disk floor earned its keep while this was being built: at 5.98 GB
free against a 6.16 GB floor,runrefused with both numbers in the message
rather than starting and failing halfway. - No warnings across
cargo check --workspace --all-targets, and no line this
window added exceeds the project's 100-column style.
Upgrade
git pull
cargo build --release # ~/.cargo/bin/cargo if it is not on PATH
./target/release/lantern doctorNo database schema change, so flows recorded on 0.1.0–0.1.2 still render —
their reports keep the text they were written with. No new CLI flags, and no
new required variables: the two price variables are optional, and DEEPSEEK_API_KEY
(or the wizard's files) still works exactly as it did.
Attached: lantern-v0.1.3-aarch64-linux-gnu (8,546,696 bytes), built on Debian
13 / glibc 2.41 / aarch64. For anything else, build from source as above.
lantern 0.1.2
lantern 0.1.2
Ten model providers behind one preset table, and a setup wizard that turns
"which endpoint, which model, which key" into three questions asked once.
Providers
| id | endpoint | key variable | default model |
|---|---|---|---|
deepseek |
https://api.deepseek.com |
DEEPSEEK_API_KEY |
deepseek-flash |
openai |
https://api.openai.com/v1 |
OPENAI_API_KEY |
gpt-4o-mini |
anthropic |
https://api.anthropic.com |
ANTHROPIC_API_KEY |
claude-sonnet-4-5 |
gemini |
https://generativelanguage.googleapis.com/v1beta/openai |
GEMINI_API_KEY |
gemini-2.5-flash |
openrouter |
https://openrouter.ai/api/v1 |
OPENROUTER_API_KEY |
deepseek/deepseek-chat |
groq |
https://api.groq.com/openai/v1 |
GROQ_API_KEY |
llama-3.3-70b-versatile |
mistral |
https://api.mistral.ai/v1 |
MISTRAL_API_KEY |
mistral-small-latest |
xai |
https://api.x.ai/v1 |
XAI_API_KEY |
grok-4 |
ollama |
http://127.0.0.1:11434/v1 |
none (local) | first model it lists |
custom |
your own URL | LANTERN_LLM_API_KEY (optional) |
you name it |
A preset carries the endpoint, the key variable and a model shortlist, so
switching providers means naming an id instead of memorising a URL. Order of
precedence is environment, then the wizard's files, then the preset — a preset
only supplies what the operator did not, and a key saved for one provider is
never sent to another.
Nine presets speak chat completions; anthropic speaks the Messages API, which
needed a second client: system turns move to a top-level field, tool results
become tool_result blocks inside one user turn (consecutive turns merge,
because the endpoint requires strict alternation), and tool_use input arrives
as an object rather than a string.
Setup asks three questions
lantern setup now opens with the model half, because that is the part only
you can answer:
- provider — all ten listed with their endpoints, current one marked;
- model — the endpoint's own model list is fetched live, so the menu shows
what that account can actually call; the preset's shortlist is the fallback
when it will not answer, andmlets you type any other name; - key — read with the input hidden, then checked with one real 16-token
completion. A key that cannot reach its endpoint is not worth storing, and
the check costs one call instead of a failed flow later.
The key lands in ~/.config/lantern/credentials at mode 0600 — pinned again
after every write, because an existing file keeps whatever mode it was created
with. Provider, model and endpoint go to config.json beside it, which holds
no secret. Both files sit outside the data root, so neither is ever in the
database, a log, a trace or an artifact, and LANTERN_CONFIG_DIR relocates
them.
With no terminal attached the wizard prints one line and stands down, so a
scripted lantern setup still provisions and never blocks. lantern doctor
now reports provider / model @ endpoint [where the key came from] without
printing the key.
Also in this window
- The development-only judgement client is gone from the product: config, env
vars, doctor row and README rows deleted; the reflector does a plain review
and reportsreviewed N finding(s)(orno findings to review). - The device profile no longer has a SoC vendor in its CPU whitelist — such a
host would have been reported as if the SoC were the CPU. It gets the neutral
core-and-architecture line instead. lantern --versionreports the real number: 0.1.1 shipped a binary that
called itselflantern 0.1.0.
Verified
- 198 tests pass, 1 ignored (the live NVD query), 0 warnings — up from 166 at
0.1.1. - The whole wizard was driven through a real pty: provider menu, model list,
masked key input, a live401from the endpoint with the key redacted in the
message,save anyway, then files on disk —credentialsat0600,
config.jsonfree of secrets. - Two mutation checks: the cross-provider key test and the Anthropic turn
merging test both fail with their fix removed and pass with it. - Precedence spot-checked on the built binary:
LANTERN_LLM_PROVIDER=groq
overrides the saved file and pulls Groq's preset defaults;doctorlabels
credentials filevsGROQ_API_KEY (environment). lantern setup < /dev/nullprints the skip line and provisions anyway.run --dry-run: 0.635 s, report written.lantern doctorexits 0 in 35–37 ms
(5 runs) with 18.8 MB peak RSS and 12 native tools.- The intermediate commit builds on its own: clean-worktree
cargo check,
exit 0.
Upgrade
git pull
cargo build --release # ~/.cargo/bin/cargo if it is not on PATH
./target/release/lantern doctorNothing for existing setups to migrate: DEEPSEEK_API_KEY in the environment
still works exactly as before, since deepseek remains the default preset, and a
fresh clone still boots with no configuration at all. The database schema is
untouched, so flows recorded on 0.1.0 and 0.1.1 still render. No new CLI flags —
lantern setup still takes none.
Attached: lantern-v0.1.2-aarch64-linux-gnu (8,546,696 bytes), built on Debian
13 / glibc 2.41 / aarch64. For anything else, build from source as above.
lantern 0.1.1
lantern 0.1.1
Fixes from the first live run. The pipeline was pointed at a public
documentation host, timed and priced end to end, and it produced one finding,
two warnings and a report whose numbers were invented. Each defect was
reproduced at the root before it was fixed.
Same target, before and after
| first run | after the fixes | |
|---|---|---|
| wall time | 211.4 s | 152.4 s |
| model steps | 11 | 16 |
| findings | 1 | 3 |
| warnings | 2 | 0 |
| plan | fell back to the default | written by the model |
| researcher | failed on call 9 | 6 steps, 1 finding |
| report counters | 4 invocations / 5 steps | 38 invocations / 16 steps |
| cost | $0.01 | ≤ $0.01 |
The four defects
1. The planner answered with nothing. Asked for 500 completion tokens, the
model returned finish_reason: length with 500 reasoning tokens and an empty
string — a reasoning model spends the budget thinking before it answers, and
the empty plan fell back to the default. complete() now asks with headroom
(4×, floor 2 048), retries a blank reply once at double the budget, and
reports an error instead of handing an empty string to the caller.
2. The researcher died on its ninth call. Token-based context trimming
could cut an assistant message and keep its tool reply, which the API rejects
outright ("Messages with role 'tool' must be a response to a preceding message
with 'tool_calls'"). Trimming and summarization now drop replies whose request
was cut away, so every window slice keeps call and reply paired.
3. nikto never scanned. host_nikto passed -Format txt with no output
file, and nikto 2.1.5 aborts before scanning in that case
(Unable to open '' for write, exit 2, 0.4 s). The format flag is gone —
screen output is what the executor captures — and -maxtime now caps the
scan below the adapter's timeout so it reports what it found instead of being
killed mid-run.
4. The report counted rows, not work. "5 model step(s)" was the number of
database rows and "4 tool invocation(s)" counted child processes only, so
in-process tool calls were invisible. The runtime now records real counters on
the flow and the report quotes them, falling back to row counts for flows
recorded before this release.
Verified
- 166 tests pass, 1 ignored (the live NVD query), 0 warnings — up from 158.
- The two context-window tests are mutation-checked: both fail with the fix
removed and pass with it. - The corrected nikto argv was run against a real host: flags parse, the scan
caps itself, findings still print, exit 0. - Binary 8,481,128 bytes (8.09 MB);
lantern doctorexits 0 in 53 ms with
2 MB peak RSS and 12 native tools.
Upgrade
git pull
cargo build --release # ~/.cargo/bin/cargo if it is not on PATH
./target/release/lantern doctorNothing in the CLI, the configuration or the database schema changed: flows
recorded on 0.1.0 still render, using the row-count fallback for their
summary line. For what this project is and how it is installed, see the
0.1.0 notes.
lantern 0.1.0
lantern 0.1.0
First tagged release. lantern is a native, autonomous security assessment
agent for small Linux hosts: one binary, one SQLite file, no services to run.
The pipeline — plan → recon → check → verify → reflect — executes as host
processes on the machine you point it at, through a CLI.
What landed
Real exploitation capability
- Host tools behind the scope gate:
host_nmap,host_nikto,
host_tcpdump,host_sqlmap*,host_hydra*,host_nuclei*,
host_msfconsole*,host_john* — those marked * only under--offensive. lantern setupprovisions the machine in one command: distribution
packages, john built from source (328 formats instead of the handful a
package ships), nuclei with its template set, and the sandbox launcher.
Metasploit is an opt-in repo. With no passwordlesssudoit prints exactly
what is missing rather than failing halfway through.lantern doctorreports device, budget, integrations and every tool path
without touching the network.
A model that remembers, asks, and re-plans
- Two memory namespaces: per-engagement scope keyed exactly like the runtime's
scope, and a global namespace for reusable guides — so a guide written once
reaches every later engagement. Semantic recall through local embeddings,
with an FTS5-only fallback that warns instead of pretending. ask_operatorunder--interactive: the model can ask the person running
the assessment mid-flow, wait up to 300 s, then carry on unanswered. A stale
environment variable can never make a silent run answer itself.- The plan is shared state. Every role reads the current version at prompt
time, the planner's ordered rewrite is adopted when it comes back as a real
list, andplan_patchadds or drops a step once reconnaissance proves the
plan wrong. The amended plan is what gets reported.
Search and compute
web_searchwithmode: vulnerability: keyless NVD 2.0 keyword search
merged ahead of the web results, relevance-filtered to the query tokens,
sorted by CVSS, half the result budget.code_runfor the coder: one Python script, standard library only, behind
bwrap— empty network namespace, read-only filesystem, only the flow's own
directory writable. Computation only; there is no way for it to reach a
target. The script text is stored as an artifact; the binary and its entire
argv are fixed in code.
Safety properties
- Never a shell:
Command::new().argseverywhere, argv arrays only, a binary
allowlist, rlimits (CPU, AS, FSIZE, NOFILE, NPROC), a per-flow working
directory, a timeout, an output cap, and every child audited to SQLite and
JSONL. --offensiveand the scope gate are checked before any active tool body
runs; gating is derived from the registry, not a hand-kept list.- API keys live in the environment and are never written to disk.
- Data root capped at 1.28 GB, logs at 200 MB, artifacts expire after 7 days,
findings retained. The disk floor is never touched.
Verified on this build
- 158 tests pass, 1 ignored (the live NVD query), 0 warnings.
- Binary: 8,481,128 bytes (8.09 MB) — thin LTO, a single codegen unit, panic
on abort, stripped. lantern doctor: exit 0 in 53 ms, 2 MB peak RSS, 12 native tools.- The sandbox is asserted by the suite itself: no connection out, no write
outside the flow directory, and rejected input never spawns anything.
Build and run
git clone https://github.com/ianclemence/lantern && cd lantern
cargo build --release # ~/.cargo/bin/cargo if it is not on PATH
./target/release/lantern setup # provision the host tools, once
./target/release/lantern doctor
export DEEPSEEK_API_KEY=... # environment only, never on disk
./target/release/lantern run --target example.com --scope "example.com"--dry-run walks the entire pipeline without a key or a single network call,
which is the fastest way to see what the report looks like.