Skip to content

One edit to a shared payload was seven HIGH incidents, and the rung that explained it was lost to every git timeout - #61

Open
opencdlee-dotcom wants to merge 4 commits into
agent/fable-precision/integrationfrom
agent/precision-s7/integration
Open

opencdlee-dotcom wants to merge 4 commits into
agent/fable-precision/integrationfrom
agent/precision-s7/integration

Conversation

@opencdlee-dotcom

Copy link
Copy Markdown
Owner

Precision plan step S7 (Phase 3): the operator's own LaunchAgents. Based on agent/fable-precision/integration.

What the live store showed

gap measured cause
1. one payload edit, seven HIGH incidents #383, #392–#397: com.aikit.* → run.py bytes 4a1646366adf -> 25ee9a4593c2 a change to a shared payload was keyed per plist; only program bytes had the one-case-per-program fix
2. custody: null on those findings 193 of 217 recorded scans answered local-commit (MEDIUM); the 24 that recorded null/HIGH took 11–190 s per plist against 0–1 s when git answered git probe timeouts (10–15 s caps). Seven plists asked git about the same file seven times a scan, and each non-answer flipped the case back to HIGH and wrote a new event
2b. #319 ~/.local/bin/improver receipt for a588f96d11b4 exists, custody never asked extension-less payload fails _intent_worthy, and the receipt binds the source path the agent wrote, not the installed copy
3. NEW item, no producer the current code already resolves one producer class through the subject (6 of the 7 cited incidents carry it on their newest evidence and on subject_json); the empty ones are pre-2026-08-29 events, which predate subjects and the runner-subcommand fix (script_target None) history is not recoverable; the flat evidence now carries both hashes

Change

  • check_persistence: a change confined to a payload's bytes (same program, argv, env, payload path; never attack-defined) is collected and emitted as one finding per (payload, new sha): fingerprint persistence:payload-update:<payload>:<sha>, case persistence:payload-update:<payload>, the labels in the detail, the plist paths in referrer_paths. Mirrors the OS-program case.
  • _custody_payload: the payload is graded once per scan. Intent receipt, then git, with no hook-mode prefilter. When git gives no answer, the bytes are asked whether they were already explained: by the custody ledger (a rung this payload earned before, now remembered) or by an intent receipt for the same sha at another path (_intent_receipt). Either carries as copy-of-graded, the weakest rung. An actual git answer (untracked, remote-foreign) is never overridden.
  • _accept_into_baseline: one benign-positive on a payload case promotes every job it names (only jobs the recomputed diff still reproduces).
  • Migration persistence_payload_case_20260923: folds open per-plist incidents whose newest evidence is payload-only into the payload case (evidence-matched, created_at < now, adjudicated rows untouched). It folds 0 on the live store today, because #383/#392–#397 have already aged out.
  • NEW finding: flat program_sha + target_sha, target_sha on the subject.
  • Also touched: the per-job payload custody lookup now skips attack-defined jobs, as its comment says. Before, a DYLD-injected job with a committed payload recorded custody=self-committed on an attack-defined finding.
  • Also touched: tests/conftest.py now refuses and reports any _custody_remember aimed at the real ~/.aegis/custody.jsonl. FamiliesAreTheAdjudicationSurface re-diffs the real LaunchAgents, and during this branch's first suite run it wrote a real local-commit row for run.py. That row was removed by hand. The guard also stops a pre-existing leak: TestWritEnforcementIsActuallyWired reached the real ledger 45 times through _grade_binary.

Effect on the recorded corpus (read-only, scripted from events)

  • run.py: 7 per-plist incidents → 1 case; HIGH/no-custody scans 24 → 1 (the first sighting, before git had ever answered).
  • improver (#319): 15 HIGH scans → MEDIUM copy-of-graded (receipt at the source path).
  • authman healthcheck.sh (#286/#365/#366): no receipt, not in a repo, no carry. It stays HIGH, as it should.

backtest replay --days 30 --reobserve

Paired run on the same store snapshot, base 0d7d3b6 vs this branch: identical (persistence 54 interrupt / 27 digest / 5638 seen; noise re-opened 127/214; open 132; assay 9/21). The only difference is the assay's random token. The harness replays persistence findings as recorded; S0b adds re-observation, so it cannot see this change. The morning baseline (persistence 54, 126/213, open 131) drifted because the corpus moved: #534 was closed re-graded in the live store in between.

Tests

tests/test_persistence_payload_case.py, 28 cases, green under SIM_BODY=win|linux|mac. Full suite result is in the builder report.

🤖 Generated with Claude Code

opencdlee-dotcom and others added 3 commits September 23, 2026 15:07
…hat explained it was lost to every git timeout

Precision plan step S7 (Phase 3): the operator's own LaunchAgents.

Gap 1: one payload edit, seven cases. #383 and #392-#397 were one edit to
~/Ai/Universe/tools/aikit/schedule/run.py (4a1646366adf -> 25ee9a4593c2),
one incident per com.aikit.* plist that runs it. check_persistence now
collects a change confined to a payload's bytes (same program, argv, env and
payload path; never an attack-defined job) and emits ONE finding per
(payload, new sha): case persistence:payload-update:<payload>, the referring
labels in the detail and their plist paths in referrer_paths. Same shape as
the OS-program case. _accept_into_baseline promotes every job a verdicted
payload case names, so one benign-positive ends the re-assertion for all of
them. Store migration persistence_payload_case_20260923 folds open per-plist
incidents whose newest evidence is payload-only into that case (evidence
matched, created_at < now, adjudicated rows untouched).

Gap 2: why custody read null. The rung did reach the payload.
_custody(run.py, sha) answered local-commit (MEDIUM) on 193 of the 217 scans
that recorded the change. The 24 scans that recorded custody null and HIGH
were the scans where git did not answer: 11-190 s per plist there, against
0-1 s when it answered (probe timeouts are 10-15 s). Seven plists asked git
about the same file seven times a scan. Each non-answer flipped the case
back to HIGH and wrote a new event. The payload is now graded once per scan,
by _custody_payload. It asks the same intent-then-git questions. When git
gives no answer at all, it asks whether the bytes were already explained:
by the custody ledger (a rung this payload earned on an earlier scan, now
remembered) or by an intent receipt for the same sha at another path. Either
one carries as copy-of-graded, the weakest rung. On the recorded corpus that
takes run.py from 24 HIGH scans to 1 (the first sighting, before any answer
existed).

#319 (~/.local/bin/improver bytes -> a588f96d11b4) was never asked. An
extension-less payload fails _intent_worthy, and its receipt binds the source
path the agent wrote (.../VSCode Projects/improver/improver.py), not the
installed copy. _custody_payload drops that hook-mode prefilter, and
_intent_receipt finds the receipt by sha. Result: copy-of-graded, MEDIUM.

Also touched in the same branch: the per-job payload custody lookup now
honours attack_defined, as its comment already promised. Before this, a
DYLD-injected job whose payload was committed recorded
custody=self-committed on an attack-defined finding. The severity was
untouched, but the chain and routing tiers read that field.

Gap 3: producer fields. On current code the NEW finding already resolves a
producer class through its subject. The seven cited incidents lack one only
in their pre-2026-08-29 evidence, which predates subjects and the runner
subcommand fix (script_target None) and cannot be recovered. The NEW finding
now also carries program_sha and target_sha flat, and target_sha on the
subject, so recorded evidence names both halves of what the job executes.

Also touched: tests/conftest.py. The new ledger writer turned an existing
test leak into a write. FamiliesAreTheAdjudicationSurface re-diffs the REAL
LaunchAgents through _accept_into_baseline, and during this branch's suite
run it appended a local-commit row for run.py to the operator's
~/.aegis/custody.jsonl. That row has been removed by hand. The suite now
refuses and reports any _custody_remember aimed at the real ledger, the same
way it handles the notary anchor. The guard also stops a pre-existing leak:
TestWritEnforcementIsActuallyWired reached the real ledger 45 times through
_grade_binary.

Tests: tests/test_persistence_payload_case.py (28).

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
…adder never read them

`_package_receipt` covered Homebrew, VS Code, pipx, uv's interpreters, winget,
Chocolatey and the distro managers. Two installers on a developer's machine
leave a receipt just as checkable, and their binaries were the process
sensor's residue: an ad-hoc `~/.local/bin/uv` (#344, #514) and the Playwright
browser cache (#346, #433-#435).

- `_cargo_dist_receipt`: reads `<config>/<d>/<d>-receipt.json` (nothing else),
  and answers when the path resolves to `<install_prefix>/<binary>` for a
  binary the receipt lists. Roots taken from the uv 0.11.6 installers
  (cargo-dist 0.31.0): `${XDG_CONFIG_HOME:-$HOME/.config}` on posix,
  `%XDG_CONFIG_HOME%` else `%LOCALAPPDATA%` on Windows; hierarchical and
  cargo-home layouts install under `bin/`. A `binaries` entry with a separator
  is refused. Parsed once per scan, cleared by `_reset_custody_probes`.
- `_playwright_receipt`: a file under a `<browser>-<rev>` directory directly
  beneath a Playwright cache root that holds `INSTALLATION_COMPLETE`, the
  marker the download worker writes after extracting. Roots as Playwright's
  registry resolves them; an absolute `PLAYWRIGHT_BROWSERS_PATH` is honoured,
  "0" and relative values are not guessed.

Both answer only `package-managed`, the vouched tier. They are
same-uid-forgeable, exactly like the Homebrew receipt already trusted: one
step, never to LOW, risk weight 0.25, and `_grade_binary` never consults them
for attack-defined evidence.

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
Precision plan S7 follow-up. Seven com.aikit.* LaunchAgents (#287 #288 #295
#296 #300 #302 #307: `uv run .../schedule/run.py <job>`) and xbar (#399)
reached the incident tier because a NEW persistence item carried no custody
rung at all. The CHANGED branch had a ladder and NEW had none. The seven
aikit incidents each interrupted HIGH on every sighting.

check_persistence now grades a NEW item by what it EXECUTES
(_custody_executes):

  * the program goes through the binary ladder (_grade_binary: vouch,
    package receipt -- now including cargo-dist and Playwright from the
    merged receipts branch -- build output, carried copies);
  * the payload script, when there is one, goes through _custody_payload;
  * the item's rung is the WEAKER of the two (_weaker_rung: the rung the
    risk tier weighs heavier, then the one that leaves the higher
    severity). A strong program running an unexplained script explains
    nothing, and an explained script run by an unexplained program explains
    nothing either. No payload means the program's rung alone;
  * attack-defined jobs (hostile argv, loader-injection env) get no rung,
    as everywhere;
  * _persistence_exec_extra states the argv rule: the item gets no rung if
    the argv can run anything beyond program + payload. That covers a URL
    anywhere, a stripped launcher wrapper, an interpreter with no payload
    file (`python -m`, `npx pkg`), and anything before the payload except
    the runner's one declared subcommand (`uv run --with evil`,
    `--directory D python -m m`). For a non-interpreter it covers a path
    argument, or any argument to a launcher such as `open -b`. Arguments
    after the payload are the payload's own input. The rule fails closed.
  * The finding records `custody` and is demoted by it. Program and payload
    answers are memoized per call, so seven jobs sharing uv and run.py ask
    once.

Direct measurement on this body today (live snapshot, ledger writer
disabled, store read-only): all seven aikit items grade uv as
package-managed (cargo-dist:astral-sh/uv@0.11.6) and run.py as
local-commit. The weaker rung is local-commit, so HIGH -> MEDIUM (digest).
That covers 321 recorded NEW-item HIGH events across the seven incidents.
xbar gets no rung on this base: publisher-signed ships with S5, and there
is no receipt for /Applications/xbar.app. Its NEW item already reads LOW
with today's trust (developer-id); the 271 HIGH events on #399 were
recorded while the trust was misread as `unsigned`. Weak rungs do not stop
a finding from joining a chain (S3's _custody_explained), so chains like
#296 can still form.

tests/test_custody_roster.py: check_persistence custody debt ratcheted 2 -> 1.

Merged origin/agent/precision-p2b/receipts (PR #63) first, as briefed.

Tests: 15 new cases in tests/test_persistence_payload_case.py
(NewItemIsGradedByWhatItRuns); the publisher-signed case is skipped until
S5's rung exists on the branch.

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
@opencdlee-dotcom

Copy link
Copy Markdown
Owner Author

Follow-up pushed (2e5bb9f, after merging PR #63's receipts branch): a NEW persistence item is now graded by what it executes. The program goes through _grade_binary and the payload through _custody_payload, and the item takes the weaker of the two rungs. Attack-defined jobs get no rung. _persistence_exec_extra sets the argv rule: nothing may run beyond program + payload. Measured today, read-only: the seven com.aikit.* items get uv package-managed + run.py local-commit, so they go HIGH -> MEDIUM (digest). xbar (#399) gets no rung on this base, because publisher-signed ships with S5; its NEW item is already LOW with today's trust. Full suite: 2219 passed, 7 skipped, 30 xfailed.

🤖 Generated with Claude Code

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant