Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
208 changes: 207 additions & 1 deletion ARCHITECTURE.md

Large diffs are not rendered by default.

86 changes: 86 additions & 0 deletions BATTLE-LOG.md
Original file line number Diff line number Diff line change
@@ -1,3 +1,89 @@
# Aegis — Battle-Test Log (2026-08-21, `/battle-test` siege — 6 fixes, delegated 4-lens hunt)

Full repository siege under the fable-mode gates. Aegis has manually invoked
quarantine, neutralize, kill, freeze and irreversible destroy paths, so every
test used inert fixtures, temporary state, mocked process control, or the
repository's real-state pytest guard. No live scan, response action, install,
service change, notification, deployment, or network call ran.

## Outcome

**6 genuine defects fixed**: one malformed-input correctness bug, one silent
sensor-coverage loss, two measured repeated-I/O defects, one missing human
authorization boundary, and one authorization-audit omission found by the
mandatory Builder/Reviewer duel. Seven permanent regression tests were added.

Final captured gates:

- `pytest tests/ -q` → **968 passed, 4 skipped, 11 subtests passed** in 357.06s
(baseline: 961 passed, 4 skipped, 11 subtests).
- `selftest.py` → **7/7 passed**.
- Python compile, `bash -n`, ShellCheck, and `git diff --check` → exit 0.
- Completeness pre-flight → **0 pass-only / ellipsis stubs**.
- Mandatory patch duel after the last fix → **two consecutive dry rounds**:
5 passed + 7 subtests, then 47 passed + 7 subtests.
- Battle-log self-check → **PASS**: six finding rows, final/baseline test counts,
both dry rounds, and the mutation/cross-OS limits are all published above or
below; the numeric claims match the captured command output from this run.

## Findings fixed

| # | Sev | Defect | Reproducer / measured baseline | Fix |
|---|-----|--------|--------------------------------|-----|
| F1 | MEDIUM | `_beacon_parts` accepted malformed IP literals and ports outside 1–65535, allowing invalid endpoint evidence into rotating-beacon tolerance state. | Invalid IPv4 `999.999.999.999`, malformed IPv6 `12345::1` / `:`, and ports 0 / 65536 all parsed before the fix. The intent-derived `ipaddress` oracle rejected them and killed the permissive-parser mutation. | Validate with stdlib `ipaddress.ip_address()` and enforce port 1–65535 before generalizing. |
| F2 | HIGH | On macOS, failure of the second `ps` call (full argv) silently substituted the executable path while leaving process coverage healthy. Behavioral/session-theft sensors then lost their load-bearing input without a DEGRADED health record. | Inert split-`ps` fixture returned a valid executable row plus failed argv query; `_PROC_ARGV_PARTIAL` did not exist and health stayed complete. | Retain useful executable rows, reset a per-scan partial flag, and publish `process.argv` DEGRADED when argv enumeration fails. |
| F3 | LOW/PERF | Writ enforcement loaded `writs.json` once before its loop and again for every finding. | 100 findings → **101 reads**. | Split the pure snapshot check from the file-loading wrapper; `_apply_writ` now evaluates one loaded snapshot → **1 read**. |
| F4 | LOW/PERF | Intel grading called `_intel_sets()` once per candidate hash; even cache hits stat both feed files. | Two matching records → 2 set lookups; the 100-record reproducer implied 200 avoidable stats. | Load the hash set once in `check_intel` and pass it through the grading closure → **1 lookup per pass**. |
| F5 | HIGH | The CLI claimed response was human-reviewed and run by hand, but direct `quarantine`, `restore`, `destroy --yes`, `kill`, `freeze`, `thaw`, and `neutralize` dispatches had no interactive authorization boundary. A same-uid script could invoke them. | A refusal stub still reached every mutating `cmd_*` function. | Gate only the external dispatcher with the existing interactive challenge, bound to the exact verb and argument. Internal functions remain composable and sandbox-testable; `destroy --yes` remains an additional irreversible confirmation. |
| F6 | MEDIUM | F5's first fix discarded whether approval was out-of-band or merely `tty-only`, violating the architecture rule that the weaker channel must be recorded rather than overstated. | Inert duel captured a successful terminal action with no authorization-channel evidence. | Write a durable audit-before-mutation `response-auth` event for approval and refusal, including verb, argument, result, and exact channel. An unavailable audit log now fails an approval closed. |

Every regression was observed RED before production code changed and GREEN
afterward. The focused post-fix set was 7 tests; adjacent regression
neighborhoods were also exercised in both dry duel rounds.

## Four-lens evidence and stop gate

- **Correctness:** F1 reproduced and mutation-validated; no other candidate
survived minimization.
- **Architecture / efficiency:** F2–F4 reproduced. The automated lexical scan
produced 252 noisy flags; data-flow triage rejected all of them rather than
inflating the finding count.
- **Security:** `watchdog` was unavailable, so the required inline sink and
trust-boundary review ran. It found F5. No hardcoded live secrets, unsafe
deserialization, executable `eval`/`exec`, or `shell=True` sink survived.
- **Adversarial duel:** the initial pass found F6. After its fix, two bounded
consecutive rounds were dry, including fail-closed audit-write checks across
all seven mutating dispatchers and exact argument/channel binding.

The composite stop gate was met on dry rounds, saturated affected regression
neighborhoods, and a fully green suite—not on the siege six-round hard cap.
There is **no repository-wide mutation harness**, so no numeric mutation score
is claimed; F1 used a targeted killed mutation and every fix has captured
fail-before evidence.

## End-state checklist

- [x] Bugs found → fixed and regression-pinned (F1, F2, F5, F6).
- [x] Logic / efficiency errors found → fixed and measured (F3, F4).
- [x] Edge cases found → permanent tests added.
- [x] No unimplemented pass-only / ellipsis stubs remain.
- [x] Security lens run (inline because the watchdog binary was unavailable).
- [x] Mandatory adversarial duel went dry twice after the final fix.

## Residual risk

- Windows and Linux kernel-specific live harnesses did not run on this macOS
host; their CI coverage remains the cross-platform evidence.
- A same-uid attacker can allocate a pseudo-terminal and read an in-terminal
challenge when no separate GUI/notification channel exists. Aegis now records
that weaker condition honestly as `tty-only`; it is not equivalent to an
out-of-band human channel.
- The existing PID-reuse interval between process identity verification and
signaling remains a review candidate; this run did not produce a safe,
reproducible proof, so it was not represented as fixed or cleared.

---

# Aegis — Battle-Test Log (2026-08-12, `/battle-test` siege — 6 fixes, delegated 4-lens hunt)

Full `/battle-test` run under fable-mode gates. **Tier: siege** (blast-radius ×
Expand Down
42 changes: 40 additions & 2 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -165,6 +165,27 @@ lookup rather than an investigation. And corroboration is scored, not just
counted: two *different* sensors implicating one entity outranks the same number
of hits from one sensor.

**And the output is kept readable, because attention is the real budget.** A
detector that files more alerts than you can read has not made you safer — it
has taught you to ignore it, and that lesson applies to every future alert too.
So: a new install spends its first two weeks **learning** (`aegis.py learn` —
everything recorded and correlated, but non-CRITICAL signals open pre-closed
instead of interrupting, because every ordinary thing a detector has not yet
seen is new by construction); an exec entry is identified by **the command it
runs**, never its position in a config file, so inserting one hook cannot
re-alert the twenty below it; an incident that stops producing evidence **ages
out as ambient** after a week and reopens the moment it recurs; Aegis's own
upgrade is **attested by `install`** and graded down the ordinary custody
ladder, rather than the tool's loudest recurring alert being itself. `report`
leads with one verdict line and what changed since the last scan; `report
--full` still has everything.

That report is a summariser, so it **checks its own headline against the
findings it summarizes on every run and publishes the result** — a
contradiction is printed at the top, above the evidence. It has already earned
that: on its first run against live data it caught a green "nothing new"
printed over two open CRITICAL chains.

---

## Install / use
Expand Down Expand Up @@ -197,7 +218,9 @@ python3 aegis.py update-check # is ~/.aegis/aegis.py stale behind thi
python3 aegis.py scan # run once, print report, establish baseline
python3 aegis.py status # fast hardening posture + XProtect definition age
python3 aegis.py doctor # coverage/permission/sensor-health diagnostics
python3 aegis.py report # reprint the latest report
python3 aegis.py report # reprint the brief report (verdict + what's new)
python3 aegis.py report --full # every finding, grouped by category
python3 aegis.py learn # learning-period status (start|extend|done)
python3 aegis.py baseline # accept current state as known-good (resets diff)
python3 aegis.py incidents # active incidents, evidence count, and state
python3 aegis.py incident ID # evidence, KNOWN BENIGN CAUSES for the sensors
Expand All @@ -211,6 +234,21 @@ python3 aegis.py replay [days] # backtest the CURRENT correlation logic against
# recorded history (default 30d). READ-ONLY:
# opens no incident, sends no notification —
# run it after changing detection logic
python3 aegis.py intent record PATH [tool] # attest an agent config/script you
# just authored: MAC'd {ts,path,sha256,tool}
# line in ~/.aegis/intent.jsonl. A delegate-
# surface change matching a valid record
# grades LOW (self-attested) instead of HIGH
python3 aegis.py intent hook TOOL # harness post-write hook mode: reads the
# tool-call JSON on stdin, attests the written
# file; prints nothing, always exits 0
python3 aegis.py intent list # recent attestations + MAC validity
python3 aegis.py signers pin FILE # pin a device roster (principal ssh-key
# lines) — a commit arriving from another of
# YOUR machines whose SSH signature verifies
# against the PINNED roster grades LOW
# (fleet-signed) instead of poisoned-repo HIGH
python3 aegis.py signers status # show the pinned roster
python3 aegis.py allow PATH # stop alerting on findings matching PATH
python3 aegis.py vt PATH|SHA # OPT-IN VirusTotal reputation (BYO key; sends only
# the hash, never the file; scan stays local-only)
Expand Down Expand Up @@ -395,7 +433,7 @@ scheduled task is future work, and the gap is stated rather than implied.

| Command / sensor | What it does | Notes |
|---|---|---|
| **agent-surface** *(sensor)* | Baselines and diffs the AI-agent trust surface: MCP server registrations, tool-hook configs, and instruction files. Alerts on a **new exec entry**, a **changed resolved target**, or a **new semantic imperative**. | Coverage here was previously **zero** (no matches for `mcp`, `claude_desktop`, `.envrc`, `tasks.json`, `git/hooks` anywhere in the file) while this channel appears in a documented majority of 2026 agent-delivered campaigns. |
| **agent-surface** *(sensor)* | Baselines and diffs the AI-agent trust surface: MCP server registrations, tool-hook configs, and instruction files. Alerts on a **new exec entry**, a **changed resolved target**, or a **new semantic imperative** — each structural change graded by **chain of custody** (a signed intent-ledger record or a commit *created on this machine* by the repo's own identity grades LOW; a same-team re-signed target grades MEDIUM; anything the machine cannot claim stays HIGH, and attack-defined content never downgrades). | Coverage here was previously **zero** (no matches for `mcp`, `claude_desktop`, `.envrc`, `tasks.json`, `git/hooks` anywhere in the file) while this channel appears in a documented majority of 2026 agent-delivered campaigns. |
| **session-theft** *(sensor)* | Flags a browser driven against **its own live profile** — `--remote-debugging-port`, `--load-extension`, `--user-data-dir` aimed at the real profile. | Cookie-store coverage was also **zero**: the only three `cookie` matches in 11,805 lines were redaction regexes. |
| **session-binding** *(sensor)* | Reports App-Bound Encryption / DBSC posture once at baseline, then only on change. A binding **removal** is HIGH. | Designed to convert into a bound-session counter when macOS DBSC ships, rather than be deleted. |
| `cauterize [incident] [done N]` | The dependency-ordered revocation plan derived from this disk. | Reads **no secret bytes** — presence and `stat()` only. |
Expand Down
Loading
Loading