{"id":51140649,"url":"https://github.com/piranhap/linuxir-agent","last_synced_at":"2026-06-25T22:30:23.933Z","repository":{"id":363211528,"uuid":"1262322249","full_name":"piranhap/linuxir-agent","owner":"piranhap","description":"Multi-agent Linux DFIR triage for the SANS FIND EVIL! challenge, with architectural evidence-integrity guardrails.","archived":false,"fork":false,"pushed_at":"2026-06-16T01:15:12.000Z","size":412,"stargazers_count":0,"open_issues_count":0,"forks_count":0,"subscribers_count":0,"default_branch":"main","last_synced_at":"2026-06-16T02:20:49.948Z","etag":null,"topics":[],"latest_commit_sha":null,"homepage":null,"language":"Python","has_issues":true,"has_wiki":null,"has_pages":null,"mirror_url":null,"source_name":null,"license":"mit","status":null,"scm":"git","pull_requests_enabled":true,"icon_url":"https://github.com/piranhap.png","metadata":{"files":{"readme":"README.md","changelog":null,"contributing":null,"funding":null,"license":null,"code_of_conduct":null,"threat_model":null,"audit":null,"citation":null,"codeowners":null,"security":null,"support":null,"governance":null,"roadmap":null,"authors":null,"dei":null,"publiccode":null,"codemeta":null,"zenodo":null,"notice":null,"maintainers":null,"copyright":null,"agents":null,"dco":null,"cla":null}},"created_at":"2026-06-07T21:19:55.000Z","updated_at":"2026-06-16T01:15:17.000Z","dependencies_parsed_at":null,"dependency_job_id":null,"html_url":"https://github.com/piranhap/linuxir-agent","commit_stats":null,"previous_names":["piranhap/linuxir-agent"],"tags_count":0,"template":false,"template_full_name":null,"purl":"pkg:github/piranhap/linuxir-agent","repository_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/piranhap%2Flinuxir-agent","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/piranhap%2Flinuxir-agent/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/piranhap%2Flinuxir-agent/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/piranhap%2Flinuxir-agent/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/piranhap","download_url":"https://codeload.github.com/piranhap/linuxir-agent/tar.gz/refs/heads/main","sbom_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/piranhap%2Flinuxir-agent/sbom","scorecard":null,"host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":286080680,"owners_count":34795436,"icon_url":"https://github.com/github.png","version":null,"created_at":"2022-05-30T11:31:42.601Z","updated_at":"2026-05-26T15:22:16.424Z","status":"online","status_checked_at":"2026-06-25T02:00:05.521Z","response_time":101,"last_error":null,"robots_txt_status":"success","robots_txt_updated_at":"2025-07-24T06:49:26.215Z","robots_txt_url":"https://github.com/robots.txt","online":true,"can_crawl_api":true,"host_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub","repositories_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories","repository_names_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repository_names","owners_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners"}},"keywords":[],"created_at":"2026-06-25T22:30:23.297Z","updated_at":"2026-06-25T22:30:23.923Z","avatar_url":"https://github.com/piranhap.png","language":"Python","funding_links":[],"categories":[],"sub_categories":[],"readme":"# LinuxIR Agent\n\nA multi-agent Linux DFIR (digital forensics \u0026 incident response) triage system for the\nSANS **\"FIND EVIL!\"** challenge. An analyst opens a browser, gives plain-language case\ncontext and evidence paths, and watches specialized agents investigate a mounted evidence\ntree in parallel — finding persistence, reconstructing the intrusion timeline, analyzing\nmemory and network captures (when the tools are present), enriching indicators with threat\nintel, and answering the 12 mandatory IR questions — producing a cross-referenced report in\nan Obsidian vault with honest, audited confidence levels.\n\nIts defining property is that **evidence-integrity guardrails are architectural, not\nprompt-based**. A Python `ConstraintEnforcer` vets every tool call *before any subprocess\nor filesystem write runs*. The model never gets the chance to spoliate evidence — the\nrestriction is code at the dispatch layer, not an instruction the model could ignore,\njailbreak, or hallucinate past. See [`docs/architecture.svg`](docs/architecture.svg) for the\ncomponent diagram with trust-zone labels.\n\n```\n web GUI ─▶ Coordinator ─▶ disk / log / memory / network specialists   (parallel, own gateway)\n                  │ iterate (--max-iterations)   │ every tool_use\n                  │                               ▼\n                  │                    ToolGateway.dispatch()   ◀── the one chokepoint\n                  │                               │\n                  │            ConstraintEnforcer → AuditLogger → adapter (real binary / fallback)\n                  ▼                               ▼\n   auditor (verify vs cited output) ─▶ IR expert (intel + MITRE + re-analysis) ─▶\n   persona builder ─▶ reporter (12 IR answers) ─▶ Obsidian vault + JSONL audit\n```\n\n## What it does (capabilities)\n\n- **Web intake GUI** — browser form → case-state note in an Obsidian vault (`linuxir serve`).\n- **Read-only tool surface** — persistence (cron/systemd/ssh-keys/setuid/rc/ld.so.preload/\n  passwd/bash-history/wtmp), logs (auth/lastb/syslog/timeline/gaps), memory (volatility3 +\n  kernel-banner), network (pcap summary/beaconing/dns/http/exfil/creds/tor), threat-intel.\n- **Deterministic self-correction** — failed/empty/unavailable tool results yield a logged\n  recovery hint fed back to the model (vol3 retry · empty-result pivot · contradiction\n  reconciliation).\n- **Orchestrator** — parallel specialists (each in its own gateway), inter-agent messages\n  logged to `agent-messages.jsonl`, `--max-iterations` with graceful partial reports.\n- **Auditor** — drops findings unsupported by their cited tool output (anti-hallucination).\n- **IR expert** — local-first threat-intel enrichment + MITRE mapping; can request one\n  bounded re-analysis (closing the self-learning loop).\n- **Reporter** — the 12 mandatory IR answers (each evidence-cited), plus attacker profile,\n  timeline, IOC/TTP, and recommendations.\n\n## Why a hand-rolled tool loop\n\nThe agents use the Anthropic SDK with an explicit `create → tool_use → dispatch →\ntool_result` loop (`linuxir/agents/loop.py`) rather than a higher-level tool runner.\nThat is deliberate: it forces **every** tool call through `ToolGateway.dispatch`\n(`linuxir/gateway.py`), where `ConstraintEnforcer.check` (`linuxir/guardrails/constraints.py`)\nruns first. There is no code path from a model tool request to a subprocess that bypasses it.\n\nThe enforcer blocks a call when any of these hold:\n1. the tool **name** denotes mutation (`write_`, `delete_`, `rm_`, `chmod_`, `truncate_`, …);\n2. the tool is **not in the read-only registry**;\n3. a path argument resolves (via `realpath`, so `..` is neutralized) **outside evidence scope**;\n4. the `bash_readonly` escape hatch uses a non-allowlisted binary, a redirect (`\u003e`/`\u003e\u003e`),\n   an in-place edit, or a destructive flag;\n5. an **output flag** (`--output-file`, `-o`, `of=`) appears on a read-only tool.\n\n## Proof: the spoliation test (the headline claim)\n\nReproduces the report's ten write/delete/modify attempts and asserts **10/10 blocked,\n10/10 raised as exceptions, 10/10 logged** to `audit/spoliation-attempts.jsonl`:\n\n```bash\nuv run python -m linuxir.guardrails.spoliation_test\nuv run pytest tests/test_spoliation.py -q\n```\n\n## Setup\n\n```bash\nuv sync --extra dev --extra web   # core + pytest + FastAPI/uvicorn (the web GUI)\n```\n\nOptional forensic binaries (the system runs without them — adapters fall back gracefully):\n`volatility3` (`pip install volatility3`), `tshark`, `sleuthkit`, `last`/`lastb`/`utmpdump`,\n`geoiplookup`.\n\n## Web GUI\n\n```bash\nuv run linuxir serve              # http://127.0.0.1:8080\n```\n\nOpen the page, enter client/context + evidence paths, submit → a `case-state.md` note lands\nin the Obsidian vault (via the Local REST API if configured, else a local-file fallback).\nEndpoints: `GET /`, `POST /case/new`, `GET /case/{id}/status`, `GET /cases`, `GET /healthz`.\n\n## Run\n\nThree auth modes, selected with `--auth` / `--offline`:\n\n| Mode | Flag | Cost | Needs |\n|---|---|---|---|\n| **Subscription** (default) | `--auth subscription` | **$0 per-token** (uses your Claude Pro/Max plan limits) | `claude` CLI + `CLAUDE_CODE_OAUTH_TOKEN` |\n| Offline demo | `--offline` | $0, no network | nothing |\n| Billed API | `--auth api` | paid per token | `ANTHROPIC_API_KEY` |\n\n**Offline demo** — full pipeline, scripted client against the bundled evidence fixture\n(great for proving the flow with zero setup):\n\n```bash\nuv run linuxir analyze --case cases/sample-case.yaml --offline\n```\n\n**Subscription ($0) — the hackathon path.** Runs on the **Claude Agent SDK** authenticated\nby your Pro/Max subscription, so there is **no API key and no per-token billing** (just your\nplan's usage limits). The forensic tools run as an in-process MCP server, built-in\nBash/Read/Write/Edit are disabled, and the `ConstraintEnforcer` still gates every call.\n\n```bash\nuv run linuxir analyze --case cases/sample-case.yaml                 # --auth subscription is the default\nuv run linuxir analyze --case cases/sample-case.yaml --model opus --effort high\n```\n\n**Billed API** — raw Messages API with a hand-rolled gated loop:\n\n```bash\nexport ANTHROPIC_API_KEY=sk-ant-...\nuv run linuxir analyze --case cases/sample-case.yaml --auth api\n```\n\n### Setting up the $0 subscription path on a VM (e.g. the SANS DFIR VM in GNOME Boxes)\n\nThe Python Agent SDK shells out to the **Claude Code CLI**, so the VM needs it plus a\nsubscription OAuth token. Browser login can't happen on a headless VM, so mint the token on\nyour normal machine and copy it over:\n\n```bash\n# 1. On a machine WITH a browser (your laptop), logged into Claude Pro/Max:\nnpm install -g @anthropic-ai/claude-code // you can also use the curl command on claude's site\nclaude setup-token          # opens a browser → prints sk-ant-oat01-...  (valid ~1 year)\n\n# 2. On the SANS VM:\nnpm install -g @anthropic-ai/claude-code          # needs Node 18+\nexport CLAUDE_CODE_OAUTH_TOKEN=sk-ant-oat01-...    # the token from step 1\nunset ANTHROPIC_API_KEY                            # IMPORTANT: it silently overrides the token \u003c- important if you DO NOT want to get charged for API costs.\nuv sync --extra dev                                # or: pip install -e .\nuv run linuxir analyze --case cases/sample-case.yaml\n```\n\nNotes:\n- `ANTHROPIC_API_KEY` takes precedence over the OAuth token and would bill you — the CLI\n  unsets it for you when `--auth subscription`, but keep it out of your shell to be safe.\n- Subscription auth is licensed for **personal use** — run it yourself for the competition;\n  don't ship it as a multi-user service on subscription credentials.\n- Point `evidence_scope` in the case file at the mounted evidence (read-only).\n\nOutput lands in the case `workspace`:\n- `vault/report.md` + `vault/analysis-\u003cagent\u003e.md` — Obsidian-style notes (cross-linked).\n- `audit/tool-calls.jsonl` — every tool call (allowed/blocked), with its `hypothesis` and\n  `outcome`, plus findings and phase events.\n- `audit/spoliation-attempts.jsonl` — blocked evidence-mutation attempts.\n- `Corrections/self-learning-log.md` — distilled self-corrections (dropped findings, etc.).\n\n## A case file\n\n```yaml\ncase_id: demo-001\nevidence_scope:          # READ-ONLY roots; paths resolve relative to this file\n  - ../tests/fixtures/evidence\nworkspace: ../out/demo-001   # writable: vault, audit, Corrections\n```\n\nMemory images (`*.lime`/`*.raw`/…) and pcaps (`*.pcap`/…) found inside the evidence scope\nautomatically activate the memory and network agents.\n\n## How findings stay honest\n\n- **Hypothesis before execution:** every tool call carries a required `hypothesis` field —\n  what the agent expects to find — recorded to the audit log *before* the tool runs and\n  compared against the `outcome`, so surprises surface instead of being rationalized.\n- Each finding **must cite the verbatim tool output** it rests on (`source_tool_output`).\n- A separate **auditor pass (Haiku)** judges every finding against that cited output, not\n  against the agent's prose, and **drops** anything it can't substantiate — caught before\n  the final report. (The demo plants a \"meterpreter\" claim with no supporting evidence to\n  show this working.)\n- LOW-confidence or elevated-risk findings are flagged `requires_human_review`.\n- The report includes a transparency section listing what the auditor dropped and why.\n\n## Layout\n\n```\nlinuxir/\n  guardrails/constraints.py   ConstraintEnforcer + SpoliationViolation  (the safety core)\n  guardrails/spoliation_test.py   10-attack harness\n  gateway.py                  ToolGateway.dispatch — the chokepoint (+ self-correction)\n  selfcorrect.py              deterministic recovery hints (vol3 / empty-pivot / reconcile)\n  adapters/                   base.run_binary + disk / logs / memory / network / intel / geoip\n  tools.py                    read-only tool schemas → gateway handlers\n  agents/                     loop, base, coordinator, auditor, linux_ir_expert,\n                              persona_builder, reporter, {disk,log,memory,network}_agent\n  agentsdk_runtime.py         $0 subscription runtime (Claude Agent SDK + in-process MCP)\n  web/                        FastAPI intake GUI (server.py + static/index.html)\n  obsidian.py casestore.py    vault writer (REST + local fallback) + case intake/state\n  findings.py audit.py report.py corrections.py config.py llm.py demo.py cli.py\nknowledge/                    linux-techniques · mitre-attack · known-hashes · threat-intel-sources\ncases/sample-case.yaml\ndocs/                         architecture.svg · accuracy-report.md · evidence-dataset.md\ntests/                        spoliation · adapters · pipeline · subscription · web · persistence ·\n                              logs/memory · network · self-correction · hypothesis · orchestrator ·\n                              intel · expert · reporter  =  113 tests\n```\n\nSee [`docs/accuracy-report.md`](docs/accuracy-report.md) (spoliation + two real-evidence runs) and\n[`docs/evidence-dataset.md`](docs/evidence-dataset.md) (what was tested against).\n\nThe same gateway, enforcer, tools, prompts, auditor, correlation, and reports are shared by\nboth transports — only how the model is reached differs (raw Messages API loop vs the Agent\nSDK driving in-process MCP tools).\n","project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fpiranhap%2Flinuxir-agent","html_url":"https://awesome.ecosyste.ms/projects/github.com%2Fpiranhap%2Flinuxir-agent","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fpiranhap%2Flinuxir-agent/lists"}