Skip to content
Open
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
1 change: 1 addition & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -26,6 +26,7 @@ Makefile.in
/config.log
/config.status
/codebud
/codebud-sec
/codebud-*.tar.gz
/codebud-*.tar.gz.sig
/codebud-*.tar.gz.sha256
Expand Down
10 changes: 7 additions & 3 deletions Makefile.am
Original file line number Diff line number Diff line change
Expand Up @@ -11,13 +11,17 @@ SUBDIRS = src docs
# ─── The codebud command ─────────────────────────────────────────────────────
# A small POSIX sh launcher for the configured Python and package directory.

bin_SCRIPTS = codebud
CLEANFILES = codebud
bin_SCRIPTS = codebud codebud-sec
CLEANFILES = codebud codebud-sec

codebud: $(srcdir)/src/launcher.in Makefile
# codebud runs the codebud package, codebud-sec the security layer.
codebud codebud-sec: $(srcdir)/src/launcher.in Makefile
$(AM_V_GEN)rm -f $@ $@.tmp && \
module=`echo $@ | sed 's/-/_/'` && \
$(SED) -e 's|@PYTHON[@]|$(PYTHON)|g' \
-e 's|@pythondir[@]|$(pythondir)|g' \
-e "s|@MODULE[@]|$$module|g" \
-e 's|@COMMAND[@]|$@|g' \
$(srcdir)/src/launcher.in > $@.tmp && \
chmod +x $@.tmp && mv $@.tmp $@

Expand Down
15 changes: 13 additions & 2 deletions NEWS
Original file line number Diff line number Diff line change
Expand Up @@ -85,6 +85,17 @@ a safety policy you can read and change.
exploitation tools, no files sent to a target. A profile requires an
engagement with engagement = true.

codebud-sec, the security layer's command: codebud-sec chat|run|plan
--engagement FILE runs codebud inside an engagement -- its scope guard
and network fence active, every step recorded before it runs and after
it ends in an encrypted, hash-chained evidence log in codebud's data
directory, out of the steps' reach; a later session must use the same
engagement file. codebud-sec evidence add|verify|show|close keeps the
log; codebud-sec pin is codebud integrity pin. Scanning steps run
behind a network fence (pasta and nftables, optional): only their
targets are reachable, no DNS, nothing of the machine itself. A new
manual chapter, "Security Work".

A profile can ask for observer isolation (isolation = "observer"):
command steps see the host's processes through a read-only /proc,
for live triage, but still cannot signal them or reach the network.
Expand All @@ -107,5 +118,5 @@ a safety policy you can read and change.
Alpha: the command line, configuration and data formats may still
change before 1.0. Isolation needs bubblewrap and user namespaces
(GNU/Linux); elsewhere steps run unisolated with a warning. The
security layer (codebud_sec: engagement file, evidence log) ships but
is not used yet.
security layer (codebud_sec) is for authorized work only and is used
through codebud-sec; its network fence needs pasta (passt) and nft.
23 changes: 21 additions & 2 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -85,6 +85,24 @@ codebud integrity pin # record what is installed n
codebud integrity verify # what changed since (exit 1 if anything)
```

## Security work

For authorized security work only -- incident response on hosts you own,
penetration tests you are contracted to run -- `codebud-sec` runs codebud
inside an engagement: a TOML file with the client, the in-scope and
excluded targets and the time window. Every network target a step names is
checked against it (out of scope is refused), scanning steps run where the
network itself lets through only their targets, and every step goes into
an encrypted, hash-chained evidence log.

```bash
codebud-sec chat --engagement acme.toml # security-pentest profile
codebud-sec evidence add disk.E01 --engagement acme.toml
codebud-sec evidence verify --engagement acme.toml # record the head hash elsewhere
```

See "Security Work" in the manual.

## Project search

Index a project once and every request gets its most relevant code as
Expand Down Expand Up @@ -151,14 +169,15 @@ make pdf # → docs/codebud.pdf

Topics covered: Introduction · Installation · Model Management ·
LLM Execution · Performance Benchmarking · Usage · Configuration ·
Architecture · Tools Reference · Safety & Sandbox · Development ·
Architecture · Tools Reference · Safety & Sandbox · Privacy · Security Work ·
Development ·
Troubleshooting · Glossary.

## Project layout

```
src/codebud/ The package: agent, CLI (cli/), tools, default policy
src/codebud_sec/ Security layer (engagement file, evidence log); not used yet
src/codebud_sec/ Security layer: codebud-sec, engagements, scope, evidence log
docs/ GNU Texinfo manual
scripts/ Developer scripts (benchmark, search and plan evals)
tests/ Test suite (pytest)
Expand Down
1 change: 1 addition & 0 deletions docs/Makefile.am
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,7 @@ codebud_TEXINFOS = \
chapters/model-management.texi \
chapters/performance-benchmarking.texi \
chapters/privacy.texi \
chapters/security.texi \
chapters/safety-and-sandbox.texi \
chapters/tools-reference.texi \
chapters/troubleshooting.texi \
Expand Down
2 changes: 2 additions & 0 deletions docs/chapters/architecture.texi
Original file line number Diff line number Diff line change
Expand Up @@ -637,6 +637,8 @@ src/codebud/ the package
└── host_network.py host_network (observer profiles only)

src/codebud_sec/ the security layer (authorized security work)
├── cli.py the codebud-sec command: sessions inside an
│ engagement, evidence add/verify/show/close, pin
├── engagement.py engagement file: authorized scope
├── scope.py scope guard: every network target read from the
│ command line and checked (DNS tools, whois,
Expand Down
2 changes: 2 additions & 0 deletions docs/chapters/development.texi
Original file line number Diff line number Diff line change
Expand Up @@ -55,6 +55,8 @@ tests/
├── test_chat_input.py / menu, toolbar, Shift-Tab (prompt_toolkit)
├── test_cli.py subcommands, doctor, display, chat REPL,
│ history, sessions, modes, run --yes
├── test_codebud_sec.py codebud-sec: guard, fence and recorder on
│ for a session only; evidence commands; pin
├── test_confine.py file tools: project-only writes, no .git/
│ .codebud/, no secret reads, symlinks, display
├── test_core.py AgentCore: models, /compact, context usage
Expand Down
6 changes: 5 additions & 1 deletion docs/chapters/safety-and-sandbox.texi
Original file line number Diff line number Diff line change
Expand Up @@ -230,7 +230,11 @@ is registered this way, so the core never imports that layer. A check
can say what it @emph{provides} (@code{add_step_check(check,
provides="engagement")}), and a profile with @code{engagement = true}
refuses command and web search steps while no registered check provides
an engagement (@pxref{Profiles}).
an engagement (@pxref{Profiles}). A layer can also register a step
@emph{recorder} (@code{add_step_recorder()}): the executor tells it about
every step before it runs, after it ends and when it is refused, and a
step whose start cannot be recorded does not run --- the security
layer's evidence log is kept this way.

@code{describe_rules()} returns a compact summary of the three policy
lists, which is embedded in the planner prompt so the model can avoid
Expand Down
185 changes: 185 additions & 0 deletions docs/chapters/security.texi
Original file line number Diff line number Diff line change
@@ -0,0 +1,185 @@
@node Security Work
@chapter Security Work
@cindex security work
@cindex codebud-sec

Codebud's security layer (@file{codebud_sec}, part of the Codebud
distribution) is for @emph{authorized} security work: incident response
on hosts you own or are authorized to examine, and penetration tests you
are contracted to run. It adds an engagement file that states what was
authorized, a command, @command{codebud-sec}, that keeps every step to
it, and an evidence log that records every step. Nothing here grants
access to anything; it only states, and helps keep to, what was already
authorized. Use it only within a written authorization (rules of
engagement, a signed scope, an incident ticket).

@menu
* Security Work at a Glance:: What codebud-sec adds to codebud.
* The Engagement File:: Client, scope, window, fence.
* Running codebud-sec:: Sessions, evidence, pinning.
* The Evidence Log:: What is recorded and how to check it.
* What Security Work Does Not Cover:: The limits, plainly.
@end menu


@node Security Work at a Glance
@section Security Work at a Glance

@multitable @columnfractions .32 .68
@headitem Piece @tab What it does
@item Security profiles
@tab @code{security-ir} (incident response: read-only triage and
forensics, no network) and @code{security-pentest} (reconnaissance and
scanning of in-scope hosts); @pxref{Profiles}.
@item Engagement file
@tab A TOML file naming the client, the in-scope and excluded targets and
the time window.
@item Scope guard
@tab Reads every host a network step will contact from its command line
and refuses the step when one is out of scope, excluded, or the window
is closed (lookups out of scope ask instead). A command it cannot read
is refused.
@item Network fence
@tab Runs scanning steps where the network itself lets through only
their targets (@pxref{Isolated Steps}).
@item Evidence log
@tab An encrypted, hash-chained, append-only record of every step, before
it runs and after it ends, refused steps too.
@item Integrity
@tab Both profiles refuse to start without a pinned manifest of the
models and programs they run (@pxref{Integrity Checks}).
@end multitable

The security profiles need an encryption key (@command{codebud key
init}) and a pinned manifest (@command{codebud-sec pin}).
@code{security-pentest} runs no command until an engagement is open, so
it is used through @command{codebud-sec}.


@node The Engagement File
@section The Engagement File
@cindex engagement file

@example
client = "ACME Ltd"
ticket = "PT-2026-042"
operator = "A. Analyst"
in_scope = ["10.20.0.0/16", "app.acme.example", "2001:db8:42::/48"]
out_of_scope = ["10.20.5.0/24", "admin.app.acme.example"]
starts = 2026-11-16T08:00:00Z
ends = 2026-11-20T18:00:00Z
fence_scanning = true
notes = "Production: no denial of service; scans 08:00-18:00 only."
@end example

@table @code
@item in_scope
Networks (CIDR), addresses and names. A name covers its subdomains
(@code{app.acme.example} covers @code{api.app.acme.example}). Required.
@item out_of_scope
Excluded networks, addresses and names; they win over @code{in_scope}.
A network is in scope only when all of it is.
@item starts, ends
The window the work is authorized for (UTC unless an offset is given).
Outside it, active steps are refused.
@item fence_scanning
@code{true} (the default) runs scanning steps behind the network fence;
@code{false} gives raw scans (nmap SYN, UDP and OS scans, masscan) back,
with the scope guard as their only check.
@item client, ticket, operator, notes
Recorded in the evidence log.
@end table

A name is never matched against an address, and the guard never
resolves a name to decide scope; the fence resolves the names a step
uses just before it runs.


@node Running codebud-sec
@section Running codebud-sec

@example
codebud-sec chat --engagement acme.toml
codebud-sec run --engagement acme.toml "scan the web tier for open ports"
codebud-sec plan --engagement acme.toml --profile security-ir "..."
@end example

@noindent
@command{chat}, @command{run} and @command{plan} open the engagement and
then run codebud itself, with the @code{security-pentest} profile unless
@option{--profile} names another. Any other option is passed on to
codebud (@option{--resume}, @option{-m}, @dots{}). For the whole session
the scope guard and the network fence are active and every step is
recorded; when it ends they are taken down. Outside the window,
@command{codebud-sec} says so at the start and active steps are refused.

@example
codebud-sec evidence add disk.E01 rules-of-engagement.pdf \
-m "image received from the client" --engagement acme.toml
codebud-sec evidence verify [--files] --engagement acme.toml
codebud-sec evidence show --engagement acme.toml
codebud-sec evidence close --engagement acme.toml
codebud-sec pin
@end example

@noindent
@command{evidence add} records files with their SHA-256 (and a note).
@command{verify} checks the hash chain, and with @option{--files} that
the recorded files are unchanged. @command{show} lists the entries.
@command{close} ends the log: nothing can be added after it.
@command{pin} is @command{codebud integrity pin}.

The log is kept in Codebud's data directory,
@file{~/.local/share/codebud/evidence/<engagement>-<id>.jsonl}, one per
engagement file: steps never see that directory, so no step can change
the log. @option{--evidence LOG} names another place --- never one
inside the project, which steps may write. When a later session or
@command{evidence add} reopens a log, its whole chain is checked (the
passphrase is asked for that), and the engagement file must be the one
the log was opened with: a changed scope needs a new log.


@node The Evidence Log
@section The Evidence Log
@cindex evidence log

The log is a JSON Lines file, one entry per line, each holding the hash
of the one before; editing, removing or reordering an entry breaks the
chain. The first entry names the engagement file with its SHA-256, so
the log says exactly what was authorized. Each session adds a
@code{start} and an @code{end} note; each step a @code{step} entry before
it runs (the tool, its arguments, the description) and one after it ends
(its exit status and output); a refused step a @code{warning} with the
reason. A step whose start cannot be recorded does not run. Output or
arguments longer than 64 KiB are kept as their SHA-256, size and
beginning.

The log is encrypted at rest with your key: each entry is sealed, and
only its sequence number, kind and the two hashes stay readable, so that
appending needs no passphrase. Reading and a full @command{verify} ask
for it.

@cindex head hash
What the chain proves is relative to its head: anyone who can write the
file could rewrite it consistently. @command{verify} prints the head
hash --- record it outside the log (the case notes, a ticket, a signed
message), and the log then shows any change made before that point.


@node What Security Work Does Not Cover
@section What Security Work Does Not Cover

@itemize
@item Authorization. The engagement file states it; it does not grant
it, and Codebud cannot tell whether it is true.
@item Names. The client's DNS decides what an in-scope name resolves to,
and an address shared with other sites (a CDN) is reachable while a step
that names it runs.
@item Lookups. @command{dig}, @command{host} and @command{whois} are not
fenced; out of scope they ask rather than refuse.
@item Raw scans. With @code{fence_scanning = false}, scanning relies on
the scope guard reading the command line.
@item The machine itself. The evidence log and the integrity checks
detect changes made through Codebud and to the files they cover; a
compromised operating system can defeat both (RFC 3227, section 5).
@end itemize
2 changes: 2 additions & 0 deletions docs/codebud.texi
Original file line number Diff line number Diff line change
Expand Up @@ -57,6 +57,7 @@ This manual is divided into the following chapters:
* Tools Reference:: Every built-in tool documented.
* Safety and Sandbox:: How Codebud limits what the LLM can do.
* Privacy:: What stays on your machine, and how to check.
* Security Work:: Engagements, codebud-sec and the evidence log.
* Development:: Adding tools, running tests, iterating on prompts.
* Troubleshooting:: Common failures and how to fix them.
* Glossary:: Key terms defined.
Expand All @@ -74,6 +75,7 @@ This manual is divided into the following chapters:
@include chapters/tools-reference.texi
@include chapters/safety-and-sandbox.texi
@include chapters/privacy.texi
@include chapters/security.texi
@include chapters/development.texi
@include chapters/troubleshooting.texi
@include chapters/glossary.texi
Expand Down
1 change: 1 addition & 0 deletions pyproject.toml
Original file line number Diff line number Diff line change
Expand Up @@ -61,6 +61,7 @@ dev = [

[project.scripts]
codebud = "codebud.cli.main:main"
codebud-sec = "codebud_sec.cli:main"

[project.urls]
Homepage = "https://github.com/mkhellat/codebud"
Expand Down
2 changes: 2 additions & 0 deletions src/Makefile.am
Original file line number Diff line number Diff line change
Expand Up @@ -61,6 +61,8 @@ nobase_python_PYTHON = \
codebud/tools/tool_registry.py \
codebud/tools/web_search.py \
codebud_sec/__init__.py \
codebud_sec/__main__.py \
codebud_sec/cli.py \
codebud_sec/engagement.py \
codebud_sec/evidence.py \
codebud_sec/fence.py \
Expand Down
8 changes: 8 additions & 0 deletions src/codebud/cli/errors.py
Original file line number Diff line number Diff line change
Expand Up @@ -103,6 +103,14 @@
"The active agent mode does not allow this tool.",
"Switch the mode: /mode ask in chat, or --mode ask.",
),
# Scope before policy: an engagement's refusal is reported as the policy's.
(
"engagement scope",
"A plan step reaches outside the open engagement, or its targets cannot be "
"read; the reason says which.",
"Rephrase the request so each step names only in-scope targets and meets the "
"reason given. The scope is the engagement file's: no allow-list can widen it.",
),
(
"safety policy",
"A plan step uses a command or argument the safety policy does not allow.",
Expand Down
Loading
Loading