Find what you missed before you ship.
Ghost is an open-source, local-first application security CLI. Review Python and JavaScript/TypeScript, test configured cross-user access locally, and verify a supported Python repair in an isolated Git worktree before you choose whether to apply it.
Observation is explicit: ghost watch records file changes, and ghost run
records commands you launch through Ghost. Ghost does not intercept your shell
history or silently collect terminal output. Saved reviews stay in your local
.ghost/ directory.
Python 3.12+ · macOS / Linux · CLI + interactive REPL · No API key needed for security checks · MIT licensed
Documentation · Landing page · Quick security workflow · Install · Run a demo · Screenshots · Commands · Safety · Development · Release notes · Releases
Prepared for Graph Hacks: FalkorDB security triage adds a new synthetic relationship demo to Ghost's existing CLI. See the submission brief and pending event checks.
Project status: Ghost is an early-stage security tool with deliberately bounded checks. Its scan results are leads to investigate, not a guarantee that an application is secure. Review current limitations before relying on it in a release process.
# While you develop — optional, explicit capture
ghost watch # file changes; leave running in another terminal
ghost run "python -m pytest -q" # record this command and its output
# Before you push
ghost review # guided scan + brief; asks about LLM help and repair approval
ghost scope # inspect Git-visible scan candidates and blind spots
ghost sandboxes # inspect leftover experiment worktrees safely
ghost find
ghost brief # summarize saved evidence and next commands
ghost findings --id <finding-id>
# Optional: prove a cross-user access failure on a configured local route
ghost auth --init # edit private .ghost/auth.json for your app
ghost auth --check # validate the local contract without running app code
ghost find --auth # send owner and other-user GET requests
ghost auth --prepare-candidate # edit private .ghost/candidate.py or candidate.cjs
ghost auth --candidate # test the proposal in fresh isolated worktrees
# Test a supported Python repair, then review the approval prompt
ghost solve <finding-id> --tests "python -m pytest -q"
ghost solution # inspect saved proof and patch
ghost find # fresh review after applying a changeThe same commands work in ghost repl, including /review in the current Git
repository. find also works without a watch session. find and brief put
priority candidates in compact rows and group repeated lower-priority rules;
findings --id retains the full inspection view.
Ghost records only commands run through ghost run or REPL run. It does not
watch every terminal, intercept shell history, or silently collect arbitrary logs.
Recent session context shows changed-path and failed-command counts from the last
200 events. Failures are context, not proof of a vulnerability; failures and
debug retain the existing runtime-debugging workflow.
cd /path/to/your/git-project
ghost connect --help # choose a provider; reuse its existing connection
ghost review # asks whether to share source with that provider
# Explicit commands, when you already know the workflow:
ghost find --llm
ghost brief
ghost findings --id FINDING_ID
ghost solve FINDING_ID --llm --tests "python -m pytest -q"
ghost solution --jsonReplace FINDING_ID with a static finding ID or an ai-... advisory ID from
the latest scan. Configure your provider with ghost connect first; the same
connections support remote providers and local Ollama. Local static checks
remain the default and need no model. ghost review --no-llm keeps the guided
workflow local; /review offers the same prompts inside the REPL.
The guided review scans the Git repository containing the current directory,
prints its path and a brief, and asks before preparing a repair. It then shows
the concrete diff and test evidence and asks again before changing your source.
Both repair confirmations default to No. With redirected input/output, it only
scans and saves a brief; --llm explicitly enables source sharing but does not
approve a repair. An explicit solve --apply approves application after the
gates pass, including for an LLM proposal; omit it for interactive approval.
find --llm sends one bounded source bundle to your configured provider: at
most 20 eligible scanner-reviewed files and 64 KB of source, preferring static
finding locations. Larger files and files beyond the budget are omitted.
It saves up to ten LLM advisories, separately from suspected static findings,
with selected-file and omission counts. Empty model results do not establish
security. Provider errors, stale source, privacy blocks or malformed replies
make the LLM review incomplete (exit 2); static findings remain saved. Exit 1
also covers LLM advisories needing review. JSON adds llm_review without changing
the static evidence fields; old audit records remain readable.
solve --llm sends only the selected finding metadata and its current application
source file (up to 64 KB). It accepts one bounded, exact text replacement in that
file. It does not accept model commands or allow editing test files, credential
paths, creating/deleting files, or multi-file patches. Existing tests must pass
before and after in an OS-confined disposable worktree, collect at least one
test with no skips, retain the same passing count, and leave reviewed files
unchanged except for the proposal. A complete static rescan must remove the
target static rule when applicable and introduce no new HIGH/MEDIUM rule/path
pairs. Changed source or HEAD blocks application.
Successful model proposals are TESTED, never security-verified: passing tests and a disappearing rule cannot prove exploitability, repair correctness, or test coverage. A model-only advisory has no scanner rule to independently clear. Review compatibility, the diff and security behavior before approval.
You must select an existing command with --tests, or enter it explicitly at
the guided review prompt. Ghost never guesses the runner, executes model-suggested
commands, installs dependencies, or enables network access for repair tests.
| Repair target | Supported test command | Evidence and limits |
|---|---|---|
Python .py |
python -m pytest ..., pytest ..., or python -m unittest ... |
Ghost's installed Python interpreter; recognized passing summary and unchanged nonzero passing count. Individual test identities and assertion coverage are not measured. Existing skip/failure rejection is preserved. |
Plain JavaScript .js, .mjs, .cjs with solve --llm |
node --test test/parser.test.cjs with installed Node 20.10+ |
Exactly one explicit Git-visible, repository-relative JS test file, with flat, uniquely named node:test cases. Complete TAP plan/results/summary, nonzero count, no failed/cancelled/skipped/TODO cases, and identical ordered case identities and counts before/after. |
TypeScript .ts, .tsx, .mts, .cts; JSX .jsx |
Unsupported | Blocked before requesting a model patch. Node's TypeScript stripping does not qualify as a supported TypeScript setup. |
| Jest, Vitest, npm/npx scripts, custom loaders/preloads, test discovery/globs, multiple files, nested Node tests/suites | Unsupported for repair verification | Select the supported direct runner or review and repair manually. A passing Python suite cannot validate a JavaScript/TypeScript target. |
For example, after configuring a provider and running ghost find:
ghost solve <finding-id> --llm --tests 'node --test test/parser.test.cjs'
ghost solution --jsonNode arguments are validated without a shell and normalized to a resolved Node
executable with --test --test-reporter=tap --test-concurrency=1 and the selected
file. Those two fixed options may also be supplied explicitly; other Node options
are rejected. Missing/old Node blocks the proposal with manual setup guidance.
The selected file must be regular, readable and at most 512,000 bytes; existing
snapshot limits of 1,000 Git-visible files and 16,000,000 bytes also apply.
Use flat node:test registrations: a script that merely exits successfully is
rejected, even when Node reports the file itself as a passing result. ESM and
CommonJS are supported as plain JavaScript, without TypeScript compilation.
Python test dependencies must already be available in Ghost's Python environment;
Node dependencies must already be available in the reviewed project/runtime.
The saved solution and terminal output identify the selected command, normalized
arguments, runner/version, baseline and patched results, and test evidence.
ghost solution --json includes per-command execution flags, snapshot integrity,
summary counters and hashed Node case identities; unestablished failure/skip
counters are null, and incomplete reports never qualify. It does not save raw test logs
or test names. Older solution records remain readable. Truncated output, timeouts,
incomplete reports, changed test identities/counts, and reviewed file mutations
block application. Tests run in the existing OS-confined disposable worktree with
network denied, bounded time and output. Inherited NODE_OPTIONS, NODE_PATH,
NODE_V8_COVERAGE and NODE_TEST_CONTEXT are removed to avoid injecting a harness.
These are test execution checks, not code coverage instrumentation. Matching counts and names cannot prove that assertions stayed equivalent, that the affected behavior was exercised, or that a vulnerability was repaired. Conditional tests can preserve names while changing behavior, and trusted project code can fabricate report text. The real JavaScript regression fixture demonstrates its own known before/after behavior with a deterministic fake model provider; it is not evidence of live model quality or a general security proof. Review the proposed diff and the relevance of your selected tests before approving application.
Credential-path and recognizable-secret checks reject the whole request rather
than sending redacted patch inputs. These checks are heuristic: inspect selected
source before opting in, and consider a local provider for private code. Project
tests execute trusted project code in confinement. Advice and approved patches
stay in .ghost/; an explicitly selected remote provider receives source under
its own data policy. No model keys or live provider calls are needed for Ghost's
automated tests.
- Python: Bandit checks risky patterns including evaluation, deserialization, shell execution, disabled TLS verification, weak hashes, and possible hardcoded credentials.
- JavaScript/TypeScript: Semgrep runs four bundled Ghost
rules for
eval, dynamicFunctionconstruction, Node shell execution, andrejectUnauthorized: false. The shell rule covers named/aliased/default/namespace ESM imports and CommonJS bindings forchild_processandnode:child_process:exec/execSync, plusspawn/spawnSync/execFile/execFileSyncwith a literalshell: trueor nonempty string option. This coverage remains bounded: computed methods, dynamic imports, indirect aliases and variable shell options are not exhaustively traced. Static shell use does not establish command injection. - Evidence: locations, rule/CWE IDs, scanner confidence, file hashes, engine versions, scan completeness, and session context are saved in local SQLite. Static matches remain suspected, including after a separate repair succeeds.
Default static scans run offline against disposable source snapshots under OS confinement.
They do not import project code, call an LLM, download rules, or contact a live
application. The optional --auth check does execute local project code in
a confined worktree. Repository scanner configuration and inline nosec/nosemgrep
suppressions are ignored. Semgrep metrics and version checks are disabled.
Source excerpts and potentially credential-bearing scanner messages are omitted
from saved findings. Paths and hashes remain in local reports.
Scope: tracked and non-ignored untracked .py, .js, .jsx, .ts, .tsx, .mjs,
.cjs, .mts, and .cts files, excluding standard build/dependency directories
and credential paths. Per engine: 1,000 files, 512 KB per file, 16 MB total source,
1 MB report output, default 120-second timeout (--timeout, up to 600). Bandit's
raw JSON report has a separate 8 MB bound inside the sandbox; Ghost removes
source excerpts and messages before transporting its finding metadata. Hitting
either report bound makes the review incomplete. Git inventory
operations are outside that scanner timeout. Inventory and hashes are checked
again before completion. Reported parse failures, unreadable selected files,
skipped coverage, timeouts, or disabled confinement cannot produce a clean result.
ghost scope shows Git-visible Python and JS/TS candidates, known unreviewed
source, excluded, unreadable and over-budget paths, and deleted tracked paths. It reads
candidate source safely without importing it, and does not run a scanner or save
an audit. Terminal output shows up to 20 paths per group; --json lists all of
them. Gitignored files are not enumerated. Use ghost find for actual scan
coverage and findings; a scope inventory is not a security verdict.
find --json exports the complete record. Exit 0 means no findings in the
completed, declared scope; 1 means static candidates or confirmed configured access failures need review; 2 means
incomplete/blocked scanning or no supported source. Other languages remain outside
coverage even if selected checks complete. audit retains the Python-only scan.
findings reads the latest saved scan, including incomplete runs, and displays
at most 20 cards by default. Use --severity high --limit 10 to focus the cards,
--id for one or --json for all. Filtering retains the full audit's coverage,
severity totals and authorization results; an empty filter does not mean a clean
scan. Filters cannot be combined with --id or --json. IDs change when their source
hash changes. No result certifies an application safe to deploy.
ghost freshness # compare recorded hashes with current source
ghost freshness --audit AUDIT_ID --jsonThis opt-in check distinguishes changed, missing, unreadable and unsafe source;
it also flags unknown hashes, budget gaps, moved HEAD and supported files absent
from the saved snapshot. It does not run a scanner or change saved evidence.
Matching source is not a security verdict. See source freshness
for bounds, JSON states and exit codes. Run ghost find for current scan evidence.
After several reviews, use audits to find an earlier snapshot. History separates
scan completeness, suspected static findings and executed local access results.
Times in the history view are UTC. It includes incomplete scans; a later empty
or incomplete report does not erase earlier evidence.
ghost audits --limit 5
ghost audits --limit 5 --offset 5 # next page; terminal history shows the total
ghost findings --audit AUDIT_ID
ghost findings --audit AUDIT_ID --id FINDING_ID
ghost findings --audit AUDIT_ID --severity high --limit 5
ghost findings --audit AUDIT_ID --json
ghost audits --jsonCopy real IDs from your output, replacing AUDIT_ID and FINDING_ID. Audit IDs
accept unique prefixes within the current repository. Unknown, empty or
ambiguous IDs fail explicitly. --json exports full saved records; finding
filters are terminal views and cannot be combined with it. The same commands
work in the REPL; /audits is available in the slash picker.
Browsing runs no scanner, project code or model request. It does not recheck
current source or change the latest audit used by solve or ask --context.
Repairs use the latest audit only: rerun ghost find before requesting a repair
based on old evidence. Listing and valid inspection exit 0 even when the saved
review has findings or incomplete coverage; inspect its recorded status, or use
a fresh ghost find for a current check.
ghost find # save the first review
# Edit your project, then run ghost find again.
ghost compare # latest review versus the previous saved review
ghost compare --base BASE_ID --audit TARGET_ID
ghost compare --base BASE_ID --json
ghost compare --limit 5 # at most five terminal rows per category
ghost compare --group-by file --limit 5
ghost compare --group-by rule --path src/parser.py --rule B307
ghost compare --change new --severity HIGH --confidence HIGH
ghost compare --change repeated --limit 5 --offset 5Use real audit IDs or unique prefixes from ghost audits --json. --base alone
compares against the latest review; --audit requires --base. The same options
work through /compare in the REPL. JSON includes all comparison rows, regardless
of the terminal limit. Comparing does not rescan, run project code, contact a
model or modify source, and does not change which audit solve uses.
--change selects new, repeated, no-longer-reported or not-compared locations.
--path, --rule, --severity and --confidence narrow the terminal display
and combine. Severity/confidence use the target finding, or the base for base-only
rows. --offset pages matching rows after filtering. --group-by
groups each category by file or rule, with counts and one representative
location per group; --limit bounds groups in that mode. Full category totals,
coverage warnings and exit codes remain based on the complete comparison.
Each shown location offers commands for its exact base and/or target finding.
Use --json without display filters, grouping or a nonzero comparison offset
to export every location. History JSON remains an array of full records for the
selected history page. See review comparison and history.
The report separates new in target, reported at the same location, no longer reported, and not compared. Matching uses rule, path and line, preserving duplicate counts rather than content-dependent finding IDs. Moving code may produce a new and a missing location. A repeated location may represent different code. A disappearing finding is not evidence of a verified fix.
Both audits need completed, confined scans, recorded scanner identity/version/
scope and consistent SHA-256 source inventories. Mismatched or incomplete scans
are incomparable. Changed inventories or excluded/unsupported counts produce a
partial comparison; findings in paths absent from the target inventory are not
treated as no longer reported. New reviews record SHA-256 fingerprints of staged
scanner configuration, command flags, Python version/platform and selected
Ghost adapters, selection and execution policy code. Different fingerprints
make reviews incomparable; older reviews without them are partial (exit 2).
ghost findings --json includes full fingerprints and per-engine identities.
These fingerprints do not authenticate scanner binaries, transitive packages,
plugins or every runtime setting, and matching them is not proof of safety.
Authorization and candidate repair proofs are not compared; inspect each saved
review through ghost findings --audit <id>.
Exit 0 means a comparable report has no new locations or severity increases;
existing suspected risks may remain. Exit 1 flags new locations or higher
reported static severity. Exit 2 means partial/incomparable coverage, a
changed severity involving UNDEFINED, or an invalid selection. Unassigned
severity is not ranked below LOW for escalation decisions. These exits describe
the saved comparison, not current source or deployment approval. Rerun find
and project tests after making changes.
Repeated rows now show whether their saved whole-file hashes changed; the inventory summary counts changed, unchanged, added and removed source paths. Both review contexts and baseline access counts remain visible even when a comparison is blocked or filtered. Duplicate locations retain equal severity/confidence pairs first so a removed duplicate does not invent a severity change in a surviving equal-level candidate. These are saved report observations; current source and persistent bug identity are not established.
The new optional FalkorDB demo ranks synthetic findings using package/service, internet-exposure, ownership and incident relationships. FalkorDB computes the priority; Ghost explains the relationship paths and exports the executed Cypher.
For the complete demo without Docker or a separately managed server:
# macOS only, once:
brew install libomp
uv run --locked --extra graph-embedded ghost demo --graph --embeddedThis optional FalkorDBLite mode starts a native server on a private Unix socket, runs the live three-stage graph story, then stops the process and removes its temporary files. Supported native wheels are required; see the guide below. For a persistent graph server and individual triage questions instead:
docker compose -f examples/graph_triage/compose.yaml up -d --wait
uv sync --locked --extra graph
uv run --locked --extra graph ghost demo --graph
uv run --locked --extra graph ghost triage --load-demo
uv run --locked --extra graph ghost triage "Which finding should we fix first, and why?"The sample recommends F-002 at 115 points because its affected package serves a public API linked to an open incident; the owner is Platform Engineering. It remains a suspected finding. Existing review/repair flows are unchanged, and default installs need no FalkorDB. See the Graph Hacks guide for schema, queries, JSON output, tests and the exact new-work/reuse boundary.
ghost auth --init creates a private .ghost/auth.json example. Add .ghost/
to your project's .gitignore as described in project setup.
Set the local app module or handler, a resource path, fake headers for its
owner and another user, and a synthetic protected_marker present in the
owner's response. Run ghost auth --check to validate the contract and local
app source without importing or running project code. ghost auth --check --json
provides the same limited preflight for scripts. It does not prove the app can
start or that access is safe. ghost find --auth runs local GET requests for
the owner and another user in a disposable Git worktree. It records the case
name, path, HTTP statuses,
verdict and two booleans indicating whether the marker appeared. Response
bodies and the marker are not saved in the audit. If the other user receives
the protected marker, Ghost reports a confirmed failure against that contract,
even if the response says HTTP 403. A denial requires both an expected
401/403/404 and no marker. If the owner does not receive the marker, or the
other user gets an unexpected response without it, the check is inconclusive
and exits 2. Existing contracts need protected_marker added to every case;
ghost auth --init shows the current format.
Ghost repeats the configured requests in the opposite actor and case order in a fresh worktree. If statuses or protected-content observations change, that case is inconclusive and the report explains why. The same check runs on a proposed fix before Ghost calls it verified. This catches order-sensitive local handlers; it does not prove independence from all application state.
Two adapters are supported: Python ASGI apps (module:app, with project packages
available through --auth-python PATH) and a local CommonJS request handler
(file.cjs:handle). This is a local contract, not a crawler or general Express
adapter. It does not start a server, execute lifespan hooks, test live URLs, or
discover routes automatically. The CommonJS handler should return {status, body}
with a string, Buffer or JSON-serializable body. Node.js is needed for that adapter.
To test a proposed fix, run ghost auth --prepare-candidate, edit the private
.ghost/candidate.py or .ghost/candidate.cjs, and run ghost auth --candidate
(equivalent to ghost find --auth --candidate). Ghost repeats the vulnerable
baseline and runs the proposed file in separate fresh worktrees. A candidate
is verified only if a confirmed baseline is blocked in every configured case
while owners still get their expected status and protected content. The real
app remains unchanged; the two order checks for both baseline and candidate use
four disposable worktrees in total. This may take longer than an ordinary scan.
The report records the proposed file's SHA-256 hash, and the command still exits
1 while that vulnerable app is present. Review and apply
the change yourself, then rerun ghost find --auth on the updated checkout.
See runnable Python and JS examples.
Only use synthetic accounts in this configuration. Keep .ghost/ ignored by Git
using the project setup above. Ghost's local OS sandbox is not a strong containment
boundary for malicious project code. Review the app before executing its
authorization contract. A passing case proves only the configured route and actors; it cannot certify the
application safe to deploy.
Without --llm, solve has one conservative verified recipe: a standalone Python function
whose body is return eval(value) and whose intended API is literal parsing
(Bandit B307). Complex modules, executable annotations/defaults, shadowed names,
and other findings receive an explicit unsupported result. Other application
source can use the optional LLM proposal workflow below; it does not provide
the same security proof as this recipe.
The repair must pass all of these gates:
- Finding matches the current file hash; OS confinement is active.
- Existing project tests pass and collect at least one test, without skipped cases.
- A trusted helper-level probe demonstrates that the original parser evaluates a harmless function call. It also checks three legitimate literal inputs.
- A minimal
ast.literal_evalpatch in a worktree rejects the function call and preserves those literal inputs. - The same project test command passes with the same count; project files remain as expected; a Python rescan removes the target finding.
- The developer checkout is unchanged since verification. Only then is application
offered. Interactive approval defaults to No; noninteractive runs leave files
untouched unless the user explicitly supplies
--apply.
After each test run, helper probe and rescan, Ghost checks the reviewed file
inventory and bytes against its sandbox snapshot. Added, deleted, changed or
unreadable project files block verification, even when tests exit 0. The proposed
parser edit is the only permitted source change. Each completed check records
snapshot_unchanged in ghost solution --json; a failed check is shown in the
terminal. Generated fixtures/build output must stay outside the reviewed scope
(for example in an already ignored directory). Gitignored and Ghost-excluded
paths are outside this check; it is not a complete filesystem integrity monitor.
Use --tests "python -m unittest discover -v" or --tests "python -m pytest -q".
Ghost uses its installed Python interpreter; install the project's test dependencies
in that environment. The command runs twice, with two bounded security probes and
one rescan (five processes, at most 120 seconds each). Git and cleanup can add time. Repair validation also limits the project snapshot
to 1,000 regular files, 512 KB each and 16 MB total; larger/unsafe snapshots are
blocked. These limits are checked after worktree creation.
solution --json exports the latest persisted repair, including blocked/failed
attempts. Exit 0 means a verified repair is available (or was applied), not that the
whole project is secure; blocked/failed repairs exit 2.
Limits of this proof: it establishes helper behavior, not remote reachability
or attacker-controlled input. Replacing eval intentionally rejects expressions;
review whether that matches your API. The three probe inputs are recorded in the
trusted probe implementation; no project regression-test file is added yet.
literal_eval is not a resource-exhaustion defense. Project tests are trusted code;
counts and summaries do not establish their honesty or coverage. Automatic
authorization discovery, broad tenant isolation, dependency CVEs, deployment configuration, and general automatic
repairs remain launch priorities. Ghost does not promise to find every vulnerability.
With Homebrew installed, this repository can serve as a custom tap:
brew tap devesh36/ghost https://github.com/Devesh36/ghost.git && brew install --HEAD devesh36/ghost/ghostOr use the one-command installer, which selects Homebrew or an existing uv:
curl -fsSL https://raw.githubusercontent.com/Devesh36/ghost/main/install.sh | bashThis installs the development version from main. The Homebrew path
installs Git, Python 3.12, uv and Linux Bubblewrap; the uv path needs Git and
OS confinement already available. Without either package manager, the installer
shows the prerequisite link and exits. See installation details.
Then, inside your Git project:
ghost doctor
ghost find
ghost briefRequires macOS/Linux and an initial Git commit. Add .ghost/ to the project's
.gitignore. No API key is needed for security checks. Python repairs need
your project test dependencies in Ghost's Python environment.
If uv, Git and OS confinement are already installed:
uv tool install --python 3.12 git+https://github.com/Devesh36/ghost.git
uv tool update-shellFor editable development, clone the repository:
git clone https://github.com/Devesh36/ghost.git
cd ghost
uv tool install --editable . --python 3.12
uv tool update-shellOpen a new terminal if uv updated your PATH, then try:
ghost --help
ghost demo --securityPrefer pip? Install in a virtual environment.
From the cloned checkout:
python3.12 -m venv .venv
source .venv/bin/activate
python -m pip install -e .
ghost demoActivate .venv in each new terminal, or call .venv/bin/ghost directly. Installing into a virtual environment alone does not put ghost on your normal shell's PATH.
ghost demo --security
ghost demo --security --keepThis runs from any directory with no API key. Ghost creates a temporary repository
with a Python parser, a TypeScript helper, and passing unittest tests. It finds
both evaluation risks, reproduces the Python behavior, verifies and applies a
Python repair only to the sample, then rescans. The TypeScript finding remains
visible because verified JS/TS repair recipes are not supported yet. Every displayed result comes
from a real command; failed verification stops the demo.
--keep retains the sample and evidence for inspection. Without it the sample is
removed. The original pricing-regression walkthrough remains available as
ghost demo (or ghost demo --keep) and exercises the agentic debug pipeline.
Real terminal output from a disposable sample repository, with presentation frames added. These are executed sample findings and repair checks, not mock UI. The default Ghost theme is shown; other themes are available.
ghost brief highlights the most urgent saved static candidates, scope and
evidence limits. Use ghost brief --markdown for a review handoff. See the
brief guide for export examples and sharing boundaries.
This separate two-file sample contains two static leads. Local access was not tested; a saved brief does not recheck the current checkout.
Start ghost repl inside your repository. The welcome screen introduces the daily
flow: record changes and commands, review source, then verify a supported repair.
Watching is opt-in through watch; commands are recorded through run.
Open the workspace capture at full resolution
Type / to browse commands, or /sol to filter them. Use Up/Down to select,
Enter to insert the command, then Enter again to submit it. The menu includes
security review, access checks, model connections, themes and session commands.
View the plain terminal capture
ghost find scanned this sample with Bandit and the bundled Semgrep rules. It
found evaluation risks in parser.py and client.ts. The report shows coverage,
source locations, rule IDs and evidence state. Both are suspected static
findings; this screen does not establish application exploitability.
View the plain terminal capture
The supported literal-parser repair reproduced function-call evaluation, rejected it after the patch, preserved three legitimate literal inputs, passed the three sample project tests and completed a Python rescan. Verification ran in an isolated Git worktree. This capture shows the patch and approval prompt; application was subsequently declined, leaving the sample source unchanged. Verified JS/TS repair recipes are not supported yet; optional LLM proposals have separate testing and approval gates.
View the plain terminal capture
Try your own disposable walkthrough with ghost demo --security. That demo
explicitly applies its verified Python repair only to the generated sample.
Choose the repository by opening Ghost from that repository's directory. Install Ghost once using Get started; you do not need to copy Ghost's source into the project you want to review.
For example, if your app lives in ~/Dev/my-app:
cd ~/Dev/my-app
git rev-parse --show-toplevel # confirm which repository Ghost will use
git rev-parse --verify HEAD # the repository needs at least one commit
ghost doctor # check local dependencies and confinementReplace ~/Dev/my-app with your own path. A new project needs its first Git
commit before Ghost can start a session. Launching from a subdirectory still
selects the containing Git repository: scans and recorded commands use its root,
including in a monorepo. scope shows which files will be considered.
Add this line to that project's .gitignore before using Ghost:
.ghost/Ghost creates .ghost/ at the repository root for its database, session history,
reports, configuration and temporary worktrees. Each repository gets its own
local evidence. Ghost does not add the ignore rule for you.
You can run a review directly, without opening a REPL or starting a watcher:
ghost scope # inspect coverage and exclusions
ghost find # run Python and JS/TS security checks
ghost findings # read the saved results and finding IDs
ghost brief # see the highest-priority saved candidates and next steps
ghost brief --markdown > review.md # export a bounded handoff; review metadata before sharingNo AI connection is required for these checks. A finding needs review; a clean result covers only the supported checks and reported scope. See what find checks today for coverage and exit codes.
For a Python project, activate its existing environment before running its tests.
For example, if it uses .venv:
source .venv/bin/activate
ghost replFor JS/TS, or when no environment needs activation, just run ghost repl.
The welcome screen shows the selected project. Inside the REPL, omit the ghost
prefix. Type / to browse commands, or use this daily workflow:
watch
run python -m pytest -q
timeline --limit 20
find
findings
unwatch
exit
Choose the test command your project actually uses. For a JavaScript/TypeScript
project, replace the Python test line with run npm test. In a normal shell,
the equivalents are ghost run "python -m pytest -q" and ghost run "npm test".
If your environment exposes python3 instead of python, use that executable
name in the run command.
watch observes edits while you use your editor; run captures only the commands
you explicitly run through Ghost. The REPL's watcher stays in the background;
standalone ghost watch occupies its terminal until Ctrl-C.
Copy a real ID from ghost findings, then replace FINDING_ID below:
ghost findings --id FINDING_ID
ghost solve FINDING_ID --tests "python -m pytest -q"
ghost solution # inspect the saved patch and test evidencesolve currently supports the limited Python literal-parser repair described in
Python repairs first; other findings require manual
review. Ghost tests a supported fix in an isolated worktree and asks before
applying it. Answer N to keep your project untouched and inspect the saved
solution. After applying or making your own fix, run ghost find and your project
tests again.
For ghost run, activate the project environment so its test command resolves
correctly. Python repairs use the interpreter where Ghost itself is installed.
Its environment must contain your project's test dependencies. If you want Ghost
and your Python project to share an existing Python 3.12+ virtual environment,
activate it and install Ghost there:
cd ~/Dev/my-app
source .venv/bin/activate
python -m pip install -e ~/Dev/ghost # path to your cloned Ghost checkoutIf Ghost was installed only into its own virtual environment, use its full path
from your app directory, for example ~/Dev/ghost/.venv/bin/ghost repl.
See Get started for a user-tool installation that makes ghost
available across repositories.
ghost connect codex --check
ghost ask --context "Explain my latest findings and suggest the next step"
ghost ask --finding <finding-id> "Explain this risk and how to verify a fix"Other connection options include Claude Code login,
Claude API, OpenAI and local providers. Chat recommends steps; you run the
commands yourself. --context shares a bounded summary of the saved audit.
--finding shares one selected finding from that latest audit, using its full ID
or a unique prefix from ghost findings --json.
To review another project, exit the REPL, cd to that project's repository and
start ghost repl there. The original repository's .ghost/ history stays put;
your terminal theme is a shared user preference.
The welcome screen puts the project, branch, and next local review step together.
The footer shows your workspace while input is empty and a command description
as you type. The animated mascot and serif wordmark are available through logo.
Type guide to choose a path:
ghost ❯ guide daily # Watch edits, record tests, understand failures
ghost ❯ guide review # Check scope, find risks, inspect evidence and access
ghost ❯ guide repair # Verify a supported repair, review it, approve and rescan
Each guide explains the benefit and the command for every step. It only displays
examples; it never starts a watcher, scan, model request or repair. Replace the
example test runner with your project's command and <id> with a real finding
ID. Standalone ghost guide works outside a repository too. Try
demo --security inside the REPL for a disposable, executable walkthrough.
Type commands without the ghost prefix:
ghost ❯ watch
ghost [watching] ❯ run python -m pytest -q
ghost [watching] ❯ failures --output
ghost [watching] ❯ timeline --limit 20
ghost [watching] ❯ find
ghost [watching] ❯ findings
ghost [watching] ❯ solve <id> --tests "python -m pytest -q"
ghost [watching] ❯ solution
watch records edits in the background while you work in your editor. run captures the command's output and exit status. debug investigates the latest recorded failure and shows its evidence, patch, and verification results before asking:
Apply verified patch to working tree? [y/N]
The default answer is no. debug --apply supplies explicit approval on the command line. If files change during the investigation, Ghost refuses to apply a stale patch.
Type / to open the command picker. Use Up/Down to move through every command
and its description; type /sol to filter to solve and solution. Enter inserts
the selected command without executing it, so you can add arguments. Press Enter
again to run it, or Esc to close the picker and restore what you typed. Tab also
completes ordinary command names. Up/Down outside the picker navigates this
session's in-memory input history; nothing is written to shell-history files.
After a local command, type -- to browse its options or press Tab after a
space. For example, /find --t offers --timeout; /findings --group-by offers
file and rule. guide lists workflows, and help lists command-specific
help. The footer explains the selected option. Enter inserts the option or
choice; another Enter submits the completed line. Completion reads command
definitions without scanning files or contacting a model. Shell commands after
run and conversational arguments stay outside option completion.
You can also submit /guide review or /scope directly. Pipes, TERM=dumb and
NO_COLOR use a plain prompt; submitting / there prints the command list.
Run ghost or ghost home for a workspace overview with one suggested next
step. In the REPL, home returns to it, and copied commands such as
ghost findings --id <id> work directly. Local review commands come first;
AI connections are optional. Saved review summaries do not recheck current source.
Ghost's identity pairs an ivory serif wordmark with a soft mint mascot and lavender accents. The terminal logo draws the serif letterforms with cell pixels, so it works without downloading or installing a font. The SVG product wordmark uses Georgia, with Times New Roman and the system serif as fallbacks.
Body text and code use the font selected in your terminal's appearance settings.
CLI output cannot select a different font for individual paragraphs. Keep a
monospace body font so commands, tables and borders align. AI replies format
paragraphs, headings, lists and code blocks; terminal controls are escaped and
Markdown links remain inert text. NO_COLOR and reduced-motion settings still work.
helpshows grouped commands;help runshows command options.- Narrow terminals show a compact command map; use
help <command>for full options. Mistyped commands suggest a close match without echoing arguments. demoruns the guided example without switching your current project or session.unwatchstops the background watcher and saves pending events.logoreplays the pixel ghost animation;clearredraws the welcome screen.- Tab completes command names; ↑ / ↓ recalls input when readline is available.
- Ctrl-C cancels a command or input. Ctrl-D,
exit, orquitleaves the REPL.
Input history stays in memory. Set GHOST_NO_ANIMATION=1 for reduced motion.
Set NO_COLOR=1 for plain terminal output without styling codes; Ghost still
uses the real terminal width. Animation also turns off for NO_COLOR, redirected
output, and basic terminals. Investigation spinners run only while actual work
is in progress.
The watcher, status and timeline screens display repository names, file paths
and recorded commands literally, including terminal control characters in
unusual filenames or command output.
# Terminal 1
ghost watch
# Terminal 2: after changing code
ghost run "python -m pytest -q"
ghost timeline
ghost debugghost run creates a session automatically when needed. Only commands executed through Ghost are recorded.
| Command | What it does |
|---|---|
ghost / ghost home |
Show workspace context and the next review step without starting a scan or session. |
ghost review [--llm|--no-llm] [--tests COMMAND] |
Scan the current repo, brief risks, ask about LLM help and confirm before repairs. |
ghost find [--llm] [--json] [--timeout 120] |
Review Python + JS/TS security; optionally add bounded LLM advisories. |
ghost scope [--limit 20] [--json] |
List Git-visible scan candidates, exclusions and blind spots without scanning. |
ghost sandboxes [--limit 20] [--json] |
Inspect experiment worktree registrations and local leftovers; reads no source and never deletes anything. |
ghost find --auth [--auth-python PATH] [--candidate] |
Add configured local owner/other-user proof; optionally test a proposed fix. |
ghost auth --init |
Create a private example authorization contract. |
ghost auth --check [--json] |
Validate the contract and app source without executing project code. |
ghost auth --prepare-candidate |
Copy the configured app into a private proposed-fix file. |
ghost auth --candidate |
Compare original and proposed access behavior in separate worktrees. |
ghost solve <id> --tests COMMAND [--llm] [--apply] |
Verify a supported Python recipe or test an LLM proposal, then request approval. Select Python pytest/unittest or, for plain JS with --llm, node --test test/parser.test.cjs. TypeScript/Jest/Vitest repairs are unsupported. |
ghost solution [--json] |
Inspect the latest security repair, proof and patch. |
ghost audit [--json] [--timeout 120] |
Offline Python security review with explicit coverage and failure status. |
ghost audits [--limit 20] [--offset 0] [--json] |
Page through saved security reviews, newest first, without rescanning. |
ghost compare [--base <id>] [--audit <id>] [--json] |
Compare saved static report locations; flag new locations, severity increases and coverage gaps. |
ghost triage [QUESTION] [--load-demo] [--port 6379] [--json] |
Optional FalkorDB Graph Hacks demo: rank synthetic findings and show graph evidence paths. |
ghost demo --graph [--embedded] [--no-pause] [--port 6379] [--json] |
Present the live FalkorDB ranking, remove an incident link, then restore it. Optional native mode needs no Docker. Pauses for recording and cleans up. |
ghost findings [--audit <id>] [--severity high] [--confidence high] [--path <path>] [--rule B307] [--group-by file|rule] [--limit 20] [--id <id>] [--json] |
Filter or group saved candidates; open an ID for investigation and repair guidance. |
ghost freshness [--audit <id>] [--limit 20] [--json] |
Inspect recorded source hashes, HEAD, and unreviewed supported paths without rescanning or changing saved evidence. |
ghost brief [--audit <id>] [--limit 5] [--markdown] |
Summarize saved evidence and next steps; export a bounded Markdown handoff. |
ghost demo --security [--keep] |
Try mixed-stack findings and a verified Python repair in a temporary sample. |
ghost doctor [--strict] [--json] |
Check prerequisites and storage paths; execute a sandbox write/network probe. |
ghost repl |
Open the interactive prompt with background watching. |
ghost guide [daily|review|repair] |
Read practical workflows and examples without running anything. |
ghost theme [name] [--preview <name>] [--json] |
Browse, preview and save terminal palettes without requiring a repository. |
ghost connect [provider] [--model <id>] [--check] [--json] |
Save nonsecret AI settings, inspect them, or test a real connection. |
ghost chat ["<prompt>"] |
Chat without a watcher; request bounded local workflows. |
ghost ask [--advice-only] [--context] [--finding <id>] "<question>" |
Ask or request a workflow; explicitly share saved metadata for advice. |
ghost fix "<change>" [--path FILE] [--tests COMMAND] [--llm] [--apply] |
Test one requested source change and obtain approval before application. |
ghost watch |
Start a session and watch file changes until Ctrl-C. |
ghost run <command> |
Execute a command and capture stdout, stderr, timing, and exit status. |
ghost retry [--dry-run] [--timeout 120] |
Preview or rerun the latest session's last failed command. |
ghost debug [--apply] |
Investigate the latest failure; offer a verified patch. |
ghost sessions [--limit 20] [--json] |
Browse saved sessions, newest first, with IDs for history inspection. |
ghost status |
Show the latest session, branch, base commit, and event counts. |
ghost timeline --limit 20 |
Inspect recent edits, commands, and failures. |
ghost failures --output |
Read failed commands and the tail of their captured output. |
ghost diff |
Show tracked changes against HEAD and list untracked files. |
ghost investigations [--session <id>] [--json] |
Browse saved investigations in a session, newest start time first. |
ghost report --id <id> |
Read a specific investigation using its full ID or a unique prefix. |
ghost report |
Read the latest session's saved investigation. |
ghost report --json |
Export that investigation as JSON, or null if none exists. |
Use ghost run --timeout 30 "python -m unittest -v" to limit a command to 30 seconds. Quoted paths and arguments are supported; shell operators such as pipes and redirects are blocked. A saved report describes its recorded run and does not reverify your current files.
Live run and retry output escapes terminal control and direction characters,
including ANSI sequences, so a project's output cannot clear Ghost's screen or
create terminal hyperlinks. Newlines and tabs remain readable. Live output is
limited to 64 KB per stream and prints a truncation notice; bounded captured
stdout/stderr are still saved as raw evidence in the local .ghost/ghost.db.
ghost run "python -m pytest -q"
# Edit your code, then inspect the command Ghost will repeat:
ghost retry --dry-run
ghost retry --timeout 30retry selects the newest completed command with a nonzero exit code in the
latest session. It revalidates the saved command, runs it in the current repository
with the current environment, streams output, and records a new result linked to
the source session and failure timestamp. A later successful run does not erase
that session's last failure. Older sessions are never searched automatically.
If the source session has ended, execution creates a new session; preview does not.
Like ghost run, this is a developer-requested command in your working tree;
it is not an isolated debugging experiment. The default timeout is 120 seconds,
not the original run's timeout. --dry-run previews and validates without running
the command or recording run events. Exit codes are the command's exit status,
124 on timeout, 1 when there is no failure to retry, or 2 for invalid/blocked
requests and launch errors. The REPL supports retry, retry --dry-run, and
help retry with the same behavior.
ghost sessions
ghost status --session <id>
ghost timeline --session <id> --limit 30
ghost failures --session <id> --output
ghost report --session <id> --jsonUse a full session ID or a unique prefix from the list. --session also has a -s shorthand; without it, these commands inspect the latest session. Ambiguous prefixes produce an error and can be expanded using full IDs from ghost sessions --json.
The same commands work inside ghost repl without the ghost prefix. help sessions shows options. Inspecting history does not switch the session used by watch, run, or debug. An open session has no recorded end time; this does not establish that its watcher is still running.
The session browser uses a table on wide terminals and cards on narrow ones, honors no-color output, and does not animate. JSON output is an array of session records with full IDs (or [] when no sessions exist).
Only one ghost debug or ghost solve investigation may run in a checkout at a time, including calls from the REPL. A second attempt exits with code 2 and instructions to wait or cancel the active run in its terminal. The lock stays held through worker cancellation, worktree cleanup, patch approval/application, and final report persistence. Other checkouts can investigate independently; observation and history commands remain available.
Ghost uses a nonblocking OS advisory lock in .ghost/investigation.lock. The empty lock file remains after completion; its presence does not mean an investigation is active. The OS releases the lock when its holder exits, including a crash. Do not delete or rename the lock file while Ghost is running. This coordinates cooperating Ghost processes; it does not lock your editor or replace source-change checks. Crash recovery for abandoned reports and worktrees is still pending.
ghost investigations --limit 10
ghost investigations --session <session-id>
ghost report --id <investigation-id>
ghost report --id <investigation-id> --jsoninvestigations lists the latest session by default. report --id searches all sessions in the current repository; add --session to restrict it. IDs can be unique prefixes. Invalid or ambiguous IDs fail explicitly. Without --id, report retains its latest-investigation behavior.
Both commands work inside the REPL. The history screen separates the recorded run state, patch verification/application state, and root cause. Updating an older investigation no longer makes it appear newest. Browsing does not execute experiments or apply a patch. investigations --json returns full records (including saved evidence and patches), or [] when none exist. Terminal rendering escapes control characters in saved report text; JSON preserves the stored evidence.
Run ghost doctor before your first investigation. It checks Python, Git,
optional model configuration, repository context, local storage paths and the
actual OS sandbox boundaries. It does not contact your model provider or scan
your project. Sandbox diagnostics execute a bounded probe in a temporary sample;
if isolation is disabled, Ghost reports a warning instead of claiming a passed
probe.
ghost doctor
ghost doctor --strict
ghost doctor --strict --jsonBy default, failed checks exit 1; warnings are shown with exit 0.
--strict also exits 1 for warnings. Informational results remain exit 0,
including missing optional AI configuration or running outside a committed Git
repository. A passing doctor result is a prerequisite diagnostic, not deployment
approval. JSON retains the checks list, adds each check's nullable next_step,
and reports the selected strict policy and resulting exit_code.
Storage diagnostics inspect the .ghost directory, logs, worktrees, database,
config and SQLite sidecar paths without opening SQLite, initializing storage or
changing permissions. Links, unexpected file types, shared file hardlinks and
foreign ownership fail the check. Incomplete initialization or unexpected POSIX
modes produce warnings. Inspect saved data before correcting unsafe paths;
explicit Ghost startup tightens accessible owned storage to 0700 directories
and 0600 files. Database contents, integrity, custom ACLs and every other runtime
file are outside this check.
Inside the REPL, use /doctor --strict or /doctor --json; help doctor shows the
same options. Wide terminals show a diagnostic table, while narrow terminals
keep each result, detail and next step visible in stacked sections. Output honors
your theme and no-color settings and has no animation.
Investigations default to a 600-second time budget, 24 experiment/verification commands, and a 120-second cap per command. Adjust the first two with:
ghost debug --time-budget 300 --max-commands 12An exhausted budget stops the investigation and leaves its evidence in ghost report. Model reasoning calls also obey the remaining investigation budget. Ctrl-C cancels pending model I/O, asks command workers to stop, and waits for provider/worktree cleanup before releasing the repository lock. Cleanup and synchronous Git/filesystem operations may extend past the time budget. Timeouts and signal-terminated experiments are inconclusive evidence, never proof of a root cause. Reports record final state, limits, and command usage.
Debugging commands capture up to 64,000 bytes per output stream by default. If
stdout or stderr exceeds that limit, the command continues under its existing
timeout, but its evidence is inconclusive. A zero exit code with truncated output
cannot verify a patch or authorize application, including with debug --apply.
Verification stops at the first failed or incomplete command. Reduce command
verbosity and rerun ghost debug to collect complete evidence.
Experiments and verification records include OS confinement and output-limit
metadata. report, investigation history, the debugger and demo use the same
verification gate; successful exit codes alone are insufficient. Older saved
records remain readable, but missing output-completeness metadata cannot establish
verification. An already recorded application stays labeled applied; that
label records the action and does not reverify today's source. Reports retain
short output summaries even when full capture succeeded; these summaries are
separate from the subprocess capture-limit check.
The model reasoning boundary supplies a 60-second request deadline for custom providers without limits, or uses their validated ProviderLimits.request_timeout (up to 300 seconds). Built-in CLI adapters retain their 120-second request limit; the investigation's remaining budget can stop them sooner. A timed-out hypothesis request falls back to deterministic investigation. A timed-out patch request cannot produce a verified patch, including when a provider returns an answer during cancellation. Providers must yield to asyncio and cooperate with cancellation; arbitrary blocking code or cleanup that never finishes cannot be forcibly stopped in the Python process.
The primary security workflow:
explicit watch/run context + current source snapshot
|
ghost find
/ \
Python / Bandit JS/TS / bundled Semgrep rules
\ /
scoped, suspected findings -> SQLite
|
ghost solve <id>
|
supported recipe + fresh source check
|
isolated worktree + OS confinement
|
passing baseline -> reproduce -> patch -> verify -> rescan
|
source check + explicit user approval
Optional `ghost find --auth` path:
private owner/other-user contract
|
baseline app in fresh worktrees -> compare both request orders
|
proposed file in fresh worktrees -> owner succeeds + other denied in both orders
|
scoped verdict + candidate hash -> SQLite (real checkout unchanged)
The runtime debugging engine remains available through ghost debug:
FILE EDITS + GIT STATE + COMMAND RESULTS
│
▼
SQLite session timeline
│
frozen source snapshot
│
┌────────────────┼────────────────┐
▼ ▼ ▼
CODE GIT RUNTIME
investigator investigator investigator
└────────────────┼────────────────┘
▼
3–5 testable hypotheses
│
▼
isolated worktree experiments
├─ reproduce the failure
├─ reverse a suspect file
├─ compare a clean baseline
└─ repeat to probe intermittency
│
▼
evidence-based judgment
│
▼
minimal patch → executable tests
│
▼
report → explicit approval
The investigators gather bounded evidence through explicit, validated tools. They examine changed code, call sites, Git history, stack traces, and recorded failures concurrently with asyncio.
For uncommitted changes, Ghost compares the captured state with HEAD. For committed regressions, it uses the session's starting commit or the previous commit. Every experiment starts from the same frozen snapshot or its chosen Git baseline.
High confidence requires executable evidence: the failure reproduces, exactly one changed-file reversal passes, and the comparison experiments finish without supporting a competing hypothesis. Inconclusive evidence stops automatic patch generation.
A proposed patch runs through the reproduction command, directly affected and broader tests where discoverable, and configured lint/type checks where available. Model statements never count as proof that a fix works.
Ghost uses the same layered organization as OpenSRE, scoped to a local CLI:
surfaces/ CLI, interactive shell, shared terminal UI, entrypoint
bootstrap/ Session and provider composition
core/ Agent harness, domain types, LLMs, tool dispatch, verification
infrastructure/ Collectors, database, repository operations, safety and sandboxes
config/ Shared defaults and terminal theme
tests/ Unit, adversarial, and real isolated integration tests
examples/ Sample repository generator
assets/ Logo, icon and terminal screenshots
docs/ Architecture and launch-readiness tracker
See Architecture for package responsibilities and enforced
import boundaries. The command remains ghost. After updating an older editable
install, rerun pip install -e '.[dev]' (or uv tool install --force --editable .
for a uv tool installation) to refresh its entrypoint. Saved .ghost/ data is
unchanged.
Ghost includes seven palettes: Ghost, Dracula, Nord, Catppuccin, Amber, Paper (light), and Mono (grayscale).
Actual REPL output in a local sample; terminal frames added for presentation.
ghost theme # browse themes and your current choice
ghost theme --preview dracula # sample interface; nothing is saved
ghost theme catppuccin # switch and save your preference
ghost theme ghost # restore the original palette
ghost theme --json # active/saved settings for scriptsInside ghost repl, type /theme to open the theme picker. Browse with Up/Down,
press Enter to insert a name, then Enter again to save it. The prompt and menus
update immediately; new logo, reply and status output uses the selected colors. Your session and conversation stay open. Use clear in the
REPL to redraw the welcome screen. theme --preview paper temporarily renders
a sample and restores the current appearance, without starting a scan or contacting a model.
Preferences are local to your user and work outside Git repositories. Only the
palette name is stored in $XDG_CONFIG_HOME/ghost/theme.json, or
~/.config/ghost/theme.json when XDG is unset. The file is written atomically with
owner-only permissions; links and special files are refused. GHOST_THEME=nord
overrides the saved palette at startup without writing it. An explicit theme
command changes the current process, while that environment override still wins
on the next launch.
Paper gives Ghost's printed content and input menu a light background; it does
not change your terminal's background or font settings. NO_COLOR=1 keeps output
completely plain, and GHOST_NO_ANIMATION=1 disables decorative motion in every
theme. Raw output from project commands is not recolored. Preview samples are
illustrative interface content, not findings or verification results.
Chat without opening the REPL or starting its watcher:
ghost chat # interactive conversation in the current repository
ghost chat "scan this project" # one local workflow; no AI connection needed
ghost ask "show my findings" # read saved findings locally
ghost fix "Accept JSON without evaluating expressions" --path parser.cjs \
--tests "node --test test/parser.test.cjs" --llmThe same prompts work as ordinary prose inside ghost repl. Explicit scan/read
requests run host-owned Ghost commands. For less direct requests, the connected
model can offer one typed workflow: scan, brief, findings, scope, status, diff,
solution, doctor, review or fix. Say “do that for me” to run a pending proposal,
or “cancel” to discard it. One-shot offers are not persisted; run the printed
Ghost command or open interactive ghost chat for follow-ups. Offers expire after
five minutes, are scoped to the repository and are cleared by a new request or forget. Model-written commands,
flags, Markdown and shell text are never executed. There is no arbitrary shell,
package installation, deployment or network-enabling chat action.
ghost fix "requested change" selects one existing application file and an
explicit test command. In a terminal it asks for missing selections, asks whether
to share the request and selected source with the configured LLM, tests one
replacement in an isolated worktree, displays its diff/evidence, then asks before
application. With redirected input, supply --path, --tests and --llm;
without --apply it saves the tested proposal and leaves source unchanged.
--apply is explicit approval, effective only after all gates pass. Requested
changes are recorded separately from discovered security findings.
| Requested change | Test runner | Limits |
|---|---|---|
Python .py |
Explicit pytest or unittest | Complete passing baseline and patched results; compare passing counts. |
Plain JavaScript .js, .mjs, .cjs |
Node 20.10+ node --test <one JS file> |
Flat unique TAP tests; same ordered identities/counts, no skipped/TODO/cancelled tests. |
| TypeScript, JSX/TSX, Jest, Vitest, npm scripts | Unsupported | Blocked before source sharing; no inferred alternative runner. |
This increment changes one source file; it cannot create files, edit tests or fix a currently failing baseline suite. Matching passing tests do not establish that a natural-language request was fulfilled or security behavior was repaired. Review the diff and test reachability. Source freshness, snapshot integrity, static rescan, OS confinement, deadlines and output limits still gate application.
connect selects the AI provider. Conversations keep four turns in memory,
bounded to 24 KB; forget clears history and pending actions. Conversations are
not saved to SQLite. Local workflows and the capability guide work offline;
other questions need a connection. Chat itself does not silently share saved
findings, source or logs. A missing shared summary means evidence was not
shared, not that there are no findings. ask --advice-only disables actions;
ask --context and ask --finding <id> are also advisory-only. ask --context opts
into sharing the latest saved audit's bounded metadata: scope, counts, up to 20
finding IDs/rules/paths/locations/severities and up to 20 authorization verdicts.
ask --finding <id> explicitly selects one finding, including findings beyond
that summary's first 20. It shares that finding and audit coverage counts, with
other finding details and authorization verdicts omitted. Combining --context
and --finding retains the single-finding selection. Unknown or ambiguous IDs
fail before loading the provider; use ghost findings --json to select a full ID.
Selection uses only the latest saved audit and does not rescan, switch history or
read project source. help ask and ghost ask --help show both options.
Serialized audit context is limited to 24 KiB of ASCII JSON, including escaped
Unicode/control characters. Long audit ID/time/scope fields are shortened and
named in truncated_fields; complete finding rows that exceed the remaining
budget are omitted. A selected finding that cannot fit fails explicitly instead
of sending a partial path or ID. Included/omitted counts are shown before the
request and supplied to the model. Omitted findings are not evidence that a
repository is safe. This is the audit-summary limit, separate from conversation,
question and provider request limits.
Both options exclude source code, command output, scanner messages, headers and
private contract markers.
Paths and the words you type may still be sensitive; the privacy heuristic below
is not complete secret detection. A later follow-up can include metadata already
quoted in the model's answer until you use forget.
In an interactive REPL, type /connect or select it from /. A provider list
opens with Codex, Claude Code login, Claude API, OpenAI, OpenRouter, Ollama and
compatible APIs. Up/Down browses; Enter inserts a choice; another Enter saves it.
Esc closes the picker. Selecting a provider does not send a request. The setup
card explains missing login, key or model settings; --check makes a real request.
Pipes and NO_COLOR show the same choices as a plain list through connect.
Codex uses your installed CLI login; no extra API key is required:
codex login # if not already signed in
ghost connect codex --check # sends a real connection-test request
ghost replClaude has two separate connection options. Use your Claude Code login:
claude auth login # sign in outside Ghost
ghost connect claude-code --check # installed Claude Code; no API key required
ghost replBoth CLI providers accept an optional --model <id> and otherwise use their CLI
default. Ghost reuses authentication managed by the installed CLI; it does not
copy login tokens into the repository. Update the CLI if it lacks the required
safety flags. Selecting claude-code excludes API-key and alternative-backend
environment variables so they cannot silently override your login choice.
Or connect Claude through the Anthropic API with claude. OpenAI uses its native
API too. Choose a model available to your account:
export ANTHROPIC_API_KEY="your-api-key"
ghost connect claude --model "your-claude-model-id" --check
export OPENAI_API_KEY="your-api-key"
ghost connect openai --model "your-openai-model-id" --check
ghost ask "What can you do?"
ghost connect # inspect effective settings without a requestOpenRouter, local Ollama and other OpenAI-compatible services are also supported:
export OPENROUTER_API_KEY="your-api-key"
ghost connect openrouter --model "provider/model-id"
ghost connect ollama --model "your-installed-model"
export MY_PROVIDER_KEY="your-api-key"
ghost connect compatible --model "your-model" \
--base-url "https://your-provider.example/v1" --key-env MY_PROVIDER_KEYKeys stay in environment variables. .ghost/llm.json stores only the selected
provider, model, endpoint and key-variable name with owner-only permissions. Start
the REPL after exporting keys; an already running process cannot see later shell
exports. Ghost reads environment variables and does not automatically load
.env. GHOST_PROVIDER, GHOST_MODEL, GHOST_BASE_URL, GHOST_KEY_ENV and the
legacy GHOST_API_KEY override saved settings. If unset, the previous three-variable
OpenAI-compatible setup remains supported:
export GHOST_API_KEY="your-api-key"
export GHOST_BASE_URL="https://your-provider.example/v1"
export GHOST_MODEL="your-model"No API model is hard-coded. Codex and Claude Code can use their CLI default or an explicit --model.
The LLMProvider protocol exposes generate and tool_call; all adapters are also
available to the existing debugging investigators. Security scanners remain
deterministic and do not call a model. Ghost works offline for causal file
identification and small one-hunk reversals; a model can refine hypotheses and
propose smaller debugging edits.
The shared HTTP transport enforces a 60-second total request deadline and 1 MiB
request/response limits. It refuses redirects and compressed responses. Adapters
require complete, nonempty text: stop for chat completions, end_turn for Claude,
and completed for OpenAI Responses (store: false). JSON tool replies are
validated by the caller. Errors omit response bodies and endpoint URLs. Remote
configured endpoints require HTTPS; loopback endpoints may use HTTP.
Codex runs exec in an empty disposable directory with read-only sandboxing,
ephemeral sessions, user configuration/rules disabled and a restricted inherited
environment. It reuses CLI authentication, requires a completed JSON event stream,
and has a 120-second total deadline and 1 MiB limit per output stream. Timeout,
cancellation and oversized output terminate its process group. It is a trusted
external agent binary: read-only sandboxing does not guarantee that it cannot
read other accessible files or use its own read tools. The conversation prompt
requests no tool use; Ghost never dispatches its answer as commands. Use a current
CLI supporting these flags. Its service retention is governed by that account.
Claude Code uses print mode and a complete successful JSON result. It runs from an empty disposable directory with built-in tools, slash skills, MCP servers, Chrome access and customization loading disabled; hooks are disabled explicitly. Settings sources are empty, safe mode is required and session persistence is off. Admin-managed policy still applies. This is a trusted external CLI, not an OS sandbox; the installed binary still has access to its account and home directory. It shares Codex's bounded transport, 120-second deadline and process-group cleanup. Neither CLI adapter executes commands from chat answers. Claude subscription/API availability and service retention depend on your account.
Embedded callers can customize ProviderLimits; CLI defaults are fixed. Chat
also enforces a provider-independent deadline, an 8 KB question limit and 64 KB
answer limit. The recognized-credential check runs before sending requests.
.env.example lists the settings. Debugging with a configured
provider may send selected code, diffs and failure context to that endpoint. The
guided demo always runs without a model provider. API references:
OpenAI Responses,
Claude Messages,
Codex noninteractive execution,
Claude Code CLI flags.
Ghost's file-read, file-list, and code-search tools exclude common credential paths: .env and .env.* (including examples), .npmrc, .pypirc, .netrc, private-key extensions/names, common cloud credential files, and .ssh, .aws, .azure, .gnupg, and gcloud directories. Model patch proposals for these paths are refused, including deleted files.
Before built-in investigators call any provider, Ghost checks system text, evidence, and the requested schema for recognized credentials. It checks selected secret-named environment values of at least eight characters (including their JSON-escaped forms), private-key headers, selected API-token formats, and quoted credential assignments. The built-in HTTP provider also checks its explicitly configured API key against message content. Detection blocks the whole request and records a content-free reason. Deterministic investigation can continue; no redacted source is used to generate a patch.
This is a conservative heuristic, not a complete secret scanner: false positives are possible, and short, transformed, encoded, or unrecognized secrets can escape detection. Direct calls to a third-party provider outside Ghost's agent boundary are not covered. Local command output, the SQLite history, raw Git tools, and sandbox snapshots are not scrubbed by this change. Choose a trusted model endpoint and review sensitive repositories before enabling model calls.
Ghost stores session records, events, and investigations under the target repository:
.ghost/
├── ghost.db
├── config.toml
├── logs/
└── worktrees/
Add .ghost/ to your project's .gitignore. Observation does not edit repository files. Configure extra watcher exclusions in .ghost/config.toml:
ignore = ["generated", "tmp/*.py"]Common dependency, build, virtual environment, Git, and Ghost directories are excluded by default.
File observation boundaries: watch also excludes the common credential
paths listed above, including .env.example. Extra ignore patterns cannot
enable those paths. File reads reject symlinks in any path component, shared
hardlinks and special files such as FIFOs. Unsafe, unreadable, unstable or
oversized files are skipped rather than recorded as deletions. On supported
POSIX platforms, directory-relative no-follow opens and bounded reads capture
at most 2,000,000 bytes per source file. Platforms without those primitives
fail closed for source reads; their watcher cannot supply file history.
Hashes preserve the captured bytes, including binary files and line endings.
Diff context compares that same capture with a pinned regular HEAD blob;
it includes current contents of staged files. Both inputs must be UTF-8 text
without NUL bytes and no larger than 16,000 bytes. Longer/binary changes retain
hashes but omit diff text; displayed diff text is capped at 16,000 characters.
The Git baseline also has the 2 MB limit. Session startup records Git metadata
without collecting full diffs. These rules do not redact arbitrary secrets
inside ordinary source, explicit run output, raw Git tools or old saved
history. This is not containment against a same-user attacker relocating
opened directories or corrupting Git storage.
Local history permissions: Ghost sets POSIX mode 0700 on .ghost, logs
and worktrees, and 0600 on its database/configuration and existing SQLite
sidecars. Existing owner-owned storage is tightened without resetting records
or configuration. Persistence rejects storage symlinks, hardlinked files, special
files and foreign-owned entries. Every database operation rechecks these paths;
replacing the database/directory requires reopening Ghost. SQLite transactions
commit or roll back, and their connections close when the operation ends.
On startup, unsafe or inaccessible storage exits with an actionable message. Corrupt or
locked databases are retained; back up .ghost before inspecting or recovering
them. Ghost does not automatically delete or rebuild a damaged database.
These are POSIX permission/path checks, not encryption or secret redaction.
Custom ACLs are not inspected, and malicious concurrent path replacement by a
process running as the same user is not fully prevented. Raw command history can
still contain sensitive data; review it before sharing backups.
- Experiments run in disposable Git worktrees. Ghost detects source changes before applying a verified patch.
- Single-file patch writes are staged. Ghost validates every replacement before writing, syncs a temporary file beside the target, and installs it with an atomic rename. Existing permission bits and uniform CRLF line endings are preserved. New files are created with owner-only permissions and cannot overwrite an existing file. Patch targets and their parent paths cannot be symlinks; hardlinked and special files are rejected. Multi-file batches are rejected until transaction recovery is available.
- Agent operations are logged. Tool inputs and repository paths are validated; commands have timeouts and bounded captured output.
- Process confinement limits writes and networking. macOS uses
sandbox-exec; Linux usesbwrapwith a read-only root, writable worktree, and separate network namespace. - Python experiments use fresh bytecode caches. CPython agent commands use a private cache prefix and disable bytecode writes, including when
-Iignores environment variables. Existing project caches stay untouched; same-size, same-timestamp edits are tested from source. - Commands are screened. Direct destructive operations, privilege escalation, shell operators, and agent package-install/network commands are blocked.
- Agent Git reads ignore external helpers. Agent
git diff,showandlogdisable external diff and text conversion, and agent processes drop inheritedGIT_*overrides so ambient Git settings cannot redirect a read to another checkout. - Applying a project fix requires approval. Debugging never commits or pushes. The demo creates one baseline commit inside its own generated repository.
The process sandbox allows reads needed by runtimes and installed dependencies. ghost run executes your chosen project command in the real repository, so use it with code and commands you trust.
If no supported OS sandbox is available, Ghost refuses agent execution. GHOST_DISABLE_OS_SANDBOX=1 explicitly opts into worktree-only isolation, which does not enforce the OS write or network restrictions. Security find, audit, auth, and solve refuse this opt-out.
For a repeatable development environment, install uv 0.12.23 (the version pinned in CI) and sync the checked-in lockfile:
uv sync --locked --extra dev
uv run --locked --extra dev python -m pytest -qWhen dependency metadata changes, run uv lock, review the uv.lock diff, and
rerun the suite with --locked before committing.
If you use pip instead, the project metadata still supports a standard editable install; that path resolves the declared version ranges at install time:
python3.12 -m venv .venv
source .venv/bin/activate
python -m pip install -e '.[dev]'
pytest -qThe suite covers persistence, file watching, command capture, tool validation, dangerous-command rejection, sandbox isolation, causal judgment, verification, approval, the REPL, terminal fallbacks, and the guided demo. Integration tests create intentionally broken Git repositories and run real commands; no API key is required. Tests involving experiment execution need the supported OS sandbox.
Brand assets: wordmark · icon.
Launch hardening is in progress. See the readiness tracker for completed work, executable checks, and remaining blockers.
Ghost is an early security-focused MVP: broad Python static checks, four JS/TS checks, configured local authorization proof, and one constrained Python repair recipe. The regression debugger remains available.
- Git comparison is bounded to HEAD, a session baseline, or the parent commit; there is no history bisect yet.
- A passing file reversal implicates a file and may not isolate a single edit. Complex or interacting changes can remain inconclusive.
- External services, ignored dependencies/configuration, and nondeterministic environments may prevent reproduction in a worktree.
- Model-generated patches still require local executable verification; a useful patch is not guaranteed.
- Patch installation supports one file at a time (multiple replacement hunks are allowed). It checks for editor changes immediately before installation, but this is not an atomic compare-and-swap with other writers. Crash/power-loss recovery, directory-entry durability, and preservation of ACLs, extended attributes, and ownership are not yet implemented. Interrupted staging may leave a
.ghost-patch-*.tmpfile beside the target; Ghost excludes these files from watcher events. - The current interface is a CLI and REPL. There is no editor extension, dashboard, shell-history interception, cloud account, or automatic PR workflow.
When the evidence is insufficient, Ghost reports that result and leaves the project's code unchanged.
Ghost is open source under the MIT License. You can use, modify and redistribute Ghost, including commercially, provided you retain the copyright and license notices. It is provided without warranty; see the license for the complete terms.
Third-party dependencies and external model tools retain their own licenses and terms. Ghost's MIT license applies to this project's code and documentation; it does not relicense those dependencies or grant access to model services.
Contributions must follow the source provenance policy. Ghost requires independent implementations and prohibits copying or adapting Apache-licensed project material into its source. Separately installed dependencies, including Bandit and watchdog, retain their own licenses. The policy records the checks performed and their limits; it is not a guarantee of universal originality.




