Skip to content

Consolidate all branches onto main, fix pre-existing failures, and implement the missing planned modules - #4

Merged
lizTheDeveloper merged 9 commits into
mainfrom
claude/merge-and-implement-modules-7a5wtb
Aug 5, 2026
Merged

Consolidate all branches onto main, fix pre-existing failures, and implement the missing planned modules#4
lizTheDeveloper merged 9 commits into
mainfrom
claude/merge-and-implement-modules-7a5wtb

Conversation

@lizTheDeveloper

@lizTheDeveloper lizTheDeveloper commented Aug 5, 2026

Copy link
Copy Markdown
Owner

Consolidates the three diverged branches onto main, greens a suite that had 64 failures, and implements the six planned modules the roadmap named but never built.

Full suite: 3503 passed, 0 failed (from 64 failed / 3341 passed). Registry discovers 278 modules. rescue/security/integrity_manifest.json verifies clean.

Branch consolidation

main could not merge any of the three branches: they have completely unrelated histories, no common ancestor at all. main's own root commit is a mid-stream feature commit, so it looks like a re-rooted snapshot rather than a continuation.

Rather than force a ~400-file conflict through --allow-unrelated-histories, I compared the trees:

Branch Files missing from main
wip-checkpoint-2026-07-16 0 — fully subsumed
feat/core-framework 0 — fully subsumed
claude/bloatware-spyware-remediation-0mbzkb 43, in 3 commits

Only the third carried unique content, so those 3 commits are cherry-picked here (cryptojacking IOC pack, stalkerware_scan, LAN inventory / ARP-spoof / router-security modules, and the home_network_intrusion and identity_theft_recovery profiles and guides). git diff against every branch tip now reports zero missing files, and the merge introduced zero regressions.

This makes PR #3 redundant — its content is fully contained here, and its base branch is one of the branches being retired.

Audit findings

Several of these contradicted docs/ROADMAP_STATUS.md, which is corrected in a dated section rather than edited in place, so what was previously believed stays legible.

Python 3.11 support was broken, not partially broken. pyproject.toml declares >=3.11, but:

  • 3 files did not parse on 3.11 (PEP 701 nested-quote f-strings; a backslash inside an f-string expression). Two were shipped modules, so module discovery broke. The bloatware branch had already fixed these independently — one concrete reason the consolidation mattered.
  • 30 call sites across 16 modules passed follow_symlinks= to pathlib.Path.is_file()/is_dir(). That keyword arrived in 3.13, so every one raised TypeError at runtime. Those modules did not work on two of the three supported versions.

rescue.fsbounds now exposes is_file_nofollow / is_dir_nofollow, which get the same no-follow semantics from os.lstat everywhere. fsbounds' own follow_symlinks arguments are deliberately untouched — those are os.DirEntry from os.scandir, where the keyword has always been valid.

The integrity manifest was invalid on main — 8 tampered, 5 added files, so startup verification failed and every launch printed a tamper warning. ROADMAP_STATUS.md recorded this as verifying clean; the regeneration was real but happened on the other lineage, and main's commits then added remediation.py, serialize.py, threat_map.py and two TUI screens without regenerating. Fixed.

There is no CI. No .github/ directory exists. A matrix that actually ran the suite on 3.11 would have caught the largest defect in the tree.

Verified healthy, for balance: auto_apply = True appears in zero modules, so auto mode genuinely is read-only as P0#5 intends; no NotImplementedError stubs or placeholder TODOs; the only skipped tests are correctly conditional on gpg; 272 pre-existing modules discover cleanly with no duplicate names; and both setup.py and rescue.spec glob the content directories, so new modules ship without packaging changes.

The 64 failures were environment coupling, not flakiness

Every one was a test that silently depended on the machine it ran on, so the suite only passed on one developer's macOS box:

  • 7 + 1 + 1notification_center_check, kext_audit, appleid_security_check each check a real path under ~/Library or /Library and return early if absent, so check() short-circuited before the mocked subprocess was reached. appleid additionally needed a real plist on disk, since patching plistlib.load still left open() to fail.
  • 2 — a time bomb. win_safe_mode_check used absolute dates chosen to be "5 days ago" when written; real time moved past them and the fixture aged into a >30-day uptime, tripping the warning it asserts is absent. Fixtures are now relative.
  • 1disk_permissions_repair asserted ownership against a hardcoded uid 501, so it only passed as a typical macOS user. It now derives the uid from the process, with /usr/local given a separate non-root uid: "correctly owned" means the running user for a home directory but explicitly not root for /usr/local, which one uid cannot express when running as root.
  • 3CliRunner(mix_stderr=...), removed in click 8.2.
  • 1update/test_verify signed a tag without pinning gpg.format, so on a host configured for SSH commit signing it produced an SSH signature while asserting on the GPG path. No trust check was weakened to green this: git verify-tag detects signature type on its own, the verification code was correct, and only the test changed.
  • ~45 — the follow_symlinks breakage above.

The fix pattern is now a convention: a module's traversal roots are class attributes so tests can point them at a fixture tree.

Desktop IPC hardening

ipcMain.handle('open-setting', …) passed any renderer-supplied string straight to shell.openExternal, which hands the URL to the OS handler.

The renderer escapes all scan output, so there is no live injection path today. But the URLs it opens come from permissions-content.json — precisely the kind of data file the signed-content-update mechanism exists to replace. Left unrestricted, the channel turns "attacker influences shipped content" into "attacker launches local files and registered protocol handlers", a bad trade for a tool people run on machines they already suspect are compromised.

The walkthrough only opens OS settings panes, so this is an allowlist of scheme prefixes rather than sanitisation — allowlists have no parser differentials, and matching is startsWith so an allowed scheme embedded later in a file:// URL does not qualify it. Also sets sandbox: true, denies window.open, and blocks navigation. The predicate lives in desktop/setting-urls.js so it is testable without booting Electron, and package.json gains the test script the desktop app was missing.

Planned modules

Resolved against the roadmap, not guessed. Profiles, guides and the threat map reference no missing modules, and every module named in docs/superpowers/plans/ exists. Two sources yielded real gaps:

Module Source Why it exists
password_manager_check P0#8 Named, never built
twofa_audit P0#8 Named, never built
session_revocation_scan P0#8 Named, never built
code_signature_audit P2 "Trust and reputation verification" No signature validation existed
security_baseline_diff P2 "Baselines and differential scans" No baseline capability existed
evidence_bundle P2 "Evidence collection and forensic handoff" Nothing preserved evidence before repair

P0#8's three were recorded as "done" because the profile was made valid by deleting the references — the capability was never built.

All six are read-only, guidance-only (ActionKind.GUIDANCE, no auto_apply), read no secret of any kind, and bound their filesystem and command work. Three design points worth review attention:

  • twofa_audit refuses to overclaim. A local tool cannot know whether 2FA is on at your provider; confirming it means signing in. So it reports what it observes and marks account status NOT CHECKED, and absence of evidence is reported as unknown, never as "two-factor is off". There is a test asserting it can never collapse to False.
  • security_baseline_diff is deliberately asymmetric. New persistence/port/extension warns; removals do not. Protection going on → off is CRITICAL; off → on is silent. Symmetric reporting buries the four changes that matter under forty that do not. Every finding states the trust-on-first-use limit, and a protection that merely became unqueryable is never reported as disabled — both tested.
  • evidence_bundle.check() writes nothing. Creating files is a real effect and belongs behind an explicit human decision. Its bundle is redacted by construction, hashes every item, records what it omitted and why, and has no network path. A test asserts no collection command can read a credential store.

digital_security_reset now includes these with evidence_bundle first, and its description no longer claims the password-manager/2FA/session steps are entirely human-led — the device-side half is real now, while the provider-side work stays human-led.

Test plan

  • Full suite before and after each stage, with counts reported rather than summarised: 3503 passed, 0 failed.
  • grep -rnE '\.(is_file|is_dir)\(follow_symlinks' modules/ returns nothing.
  • Every new module confirmed discoverable via discover_modules; tests/test_all_shipped_content.py, tests/test_remediation_validation.py and tests/test_module_code_consistency.py all pass. That last one caught a real defect during development — codes built with an f-string instead of code="..." literals — which is fixed.
  • python3 scripts/generate_integrity_manifest.py then verify_package_integrity → clean.
  • node --test in desktop/ → 7 passing.

Follow-ups not in this PR

  • Retire wip-checkpoint-2026-07-16, feat/core-framework and claude/bloatware-spyware-remediation-0mbzkb after this merges, and close the now-redundant PR Cryptojacking, home-network intrusion, stalkerware, and identity theft recovery #3.
  • Add CI. This is the highest-value remaining gap — a 3.11 matrix would have caught the largest defect here.
  • P0#7 command-runner migration: 756 subprocess.run calls in modules, 0 through rescue.command.run, 435 without timeouts. The roadmap counted 744/395 — both have grown.
  • emits_codes migration: 169 of 272 pre-existing modules declare none.

Both migrations are large mechanical sweeps that deserve their own PRs.

claude added 9 commits August 5, 2026 09:11
…rage

Adds the detection and remediation content for a household whose Wi-Fi has
been broken into, and for the cryptojacking that tends to arrive with it.

New modules:
- crypto_miner_persistence (macOS/Windows/Linux) finds what restarts a miner
  after it is killed: launch items, cron, systemd units, Run keys, scheduled
  tasks, shell profiles, and dropped miner configs. Wallet addresses and
  stratum arguments are treated as high-confidence evidence; a product name
  alone is not.
- win_crypto_miner_detect gives Windows the live-miner detection that
  previously existed only for macOS: process command lines, established
  connections to mining-pool ports, and high CPU as a low-confidence lead.
- browser_cryptojacking_check covers in-browser mining, which never shows up
  in a process list: extension code, startup pages, and hosts-file entries.
  Blackholed mining domains are recognised as protection, not as a threat.
- lan_device_inventory lists the devices actually visible on the network,
  with OUI vendor labels, and says plainly that a quiet device can be missing.
- arp_spoof_check looks for traffic interception: the gateway's address
  answering for other hosts, duplicate addresses, and multiple default routes.
- router_security_audit checks the gateway's exposed admin surface and UPnP,
  and carries the full router reclaim procedure.
- stalkerware_scan separates covert monitoring software from workplace and
  parental products and from dual-use remote access, and leads its guidance
  with safety planning rather than removal instructions.

Supporting content: shared cryptojacking IOC data, a shared neighbour-table
helper, expanded known-bloatware datasets (adware, scareware, bundled miners,
OEM trials), and the home_network_intrusion profile and five-phase guide,
ordered so the network is reclaimed before the devices are cleaned.

rescue.runtime gains load_content_module() so modules can share a helper by
path, which works in a source checkout, a pip install, and a frozen bundle.

Every fix() here is guidance only, marked ActionKind.GUIDANCE, and every
external command and filesystem scan is bounded by a timeout or a cap.

Also fixes three pre-existing Python 3.12-only f-strings that made
win_malware_indicators and win_bsod_analysis fail to load, and
test_module_win_scheduled_tasks_security fail to collect, on the declared
minimum Python 3.11.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01AsDpjoDi59AXxhoy8TriUQ
Adds a six-phase guide that doubles as a recovery checklist — `rescue guide
identity_theft_recovery` prints the outstanding steps and `--complete <n>`
marks them off, so progress survives across the weeks this actually takes.

The phases are ordered the way recovery works rather than the way it feels:

0. The first hour — start a recovery log, write down what is known, and deal
   with money that has already moved (reporting windows for unauthorised
   debit charges are short).
1. Confirm the device is not the leak — the only automatable phase, because
   changing passwords on a machine with a keylogger hands them straight back.
   Also covers email forwarding rules and carrier port-out locks, which both
   survive a password change.
2. Freeze — all three credit bureaus plus Innovis, NCTUE, and ChexSystems,
   which is where phone contracts and bank accounts get opened.
3. Report — IdentityTheft.gov first, because the FTC identity theft report is
   what unlocks the §605B block and the seven-year extended alert. Then
   police, IRS/state tax, SSA, CFPB, and the channels specific to SIM swap,
   medical, criminal, child, and deceased-relative identity theft.
4. Dispute — the distinction between a dispute (30 days, contestable) and an
   identity theft block (four business days, backed by the FTC report), plus
   the follow-up schedule for accounts that reappear via a new collector.
5. Monitor and rebuild — the long tail, and what recurrence looks like.

The profile pairs the guide with the twelve existing modules that answer the
one technical question here: is this device leaking credentials. It claims no
automation for anything that happens at a bank or a government agency.

US-first, with equivalents for the UK, Canada, Australia, and the EU in
phase 2, and free case-managed support services named in phase 5.

Also regenerates the integrity manifest for the runtime.py change in the
previous commit, which was making every launch print a tamper warning.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01AsDpjoDi59AXxhoy8TriUQ
Both guides had helplines scattered inline across steps, which is where they
are least findable — somebody who needs a number six weeks from now is not
going to re-read phase 3 step 4 to find it. Each guide now ends with a
dedicated reference phase.

identity_theft_recovery phase 6 covers free case-managed help (Identity Theft
Resource Center, the AARP helpline, the DOJ elder fraud hotline, IDCARE, the
Access Now digital security helpline), the official reporting channels, all
six credit bureaus and databases that accept freezes, abuse-specific support
including coerced debt, free legal aid, and non-US equivalents.

home_network_intrusion phase 5 covers abuse support first — because when the
intruder is someone known, the safety plan changes the order of the technical
work — then free incident response, where to report an intrusion, a pointer to
the identity theft guide when accounts are involved, and how to tell whether
a router is worth keeping.

Both open with the same warning: search results for support numbers are bought
by scam call centres, and someone mid-recovery is exactly who they want. Type
the domain, use the number on the card, and nothing legitimate is ever paid
for in gift cards. That warning is worthless below the list, so it is step 1.

stalkerware_scan's safety guidance gains the same country-by-country numbers,
plus a note not to look them up from the monitored device — browsing history
is one of the things that gets reported.

A test asserts the scam-helpline warning stays first in the resources phase,
and the step-title check caught one heading too vague to work as a checklist
item.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01AsDpjoDi59AXxhoy8TriUQ
The 'open-setting' channel passed any renderer-supplied string straight to
shell.openExternal, which hands the URL to the OS handler. The renderer escapes
all scan output today, so there is no live injection path — but the URLs it opens
come from permissions-content.json, a data file the content-update mechanism is
designed to replace. An unrestricted channel therefore turns "attacker influences
shipped content" into "attacker launches local files and registered protocol
handlers", which is a poor trade for a tool people run on machines they already
suspect are compromised.

The walkthrough only ever needs OS settings panes, so the check is an allowlist
of scheme prefixes rather than sanitisation — allowlists do not have parser
differentials. Matching is startsWith, so a permitted scheme embedded later in a
file:// URL does not qualify it.

Also sets sandbox: true, denies window.open, and blocks navigation: the renderer
only ever loads the bundled local page and has no reason to do any of the three.

The predicate lives in its own module so it is testable without booting Electron,
and package.json gains the "test" script the desktop app was missing.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_013g8dGcNgzJ1vZCw6kguGEu
pyproject declares requires-python = ">=3.11", but 30 call sites across 16
modules called Path.is_file(follow_symlinks=False) / Path.is_dir(...). That
keyword argument reached pathlib in 3.13, so on 3.11 and 3.12 every one of these
raises TypeError at runtime — these modules did not work at all on two of the
three supported versions, and roughly 45 tests were failing because of it.

rescue.fsbounds gains is_file_nofollow / is_dir_nofollow, which get the same
no-follow semantics from os.lstat on every supported version and return False
rather than raising on a missing or unreadable path, matching how Path.is_file
swallows OSError. All 30 sites now go through them.

rescue/fsbounds.py's own follow_symlinks arguments are deliberately untouched:
those are os.DirEntry objects from os.scandir, where the keyword has been valid
all along.

user_profile_size additionally needed a testability seam. Its tests mocked only
subprocess and then walked the real /Users, so they asserted against whatever
happened to be on the host — passing on a developer's Mac and failing anywhere
else, which is how the follow_symlinks breakage stayed hidden. The traversal
roots are now attributes a test can point at a fixture tree, and the tests build
one, covering the skip rules, a missing /Users, and a failing du. They no longer
touch the host filesystem.

Also drops CliRunner(mix_stderr=False) in test_cli_scan_json: click >= 8.2
removed the parameter and now always captures stdout and stderr separately,
which is precisely what these tests want to assert.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_013g8dGcNgzJ1vZCw6kguGEu
The remaining failures were not flaky. Each was a test that silently depended on
the machine it ran on, so the suite only passed on one developer's macOS box:

- notification_center_check (7), kext_audit (1), appleid_security_check (1):
  each module checks a real path under ~/Library or /Library and returns early
  if it is absent, so on any non-macOS host check() short-circuited before the
  mocked subprocess was ever reached. The traversal roots are now attributes a
  test can point at a fixture; appleid additionally needed a real plist on disk,
  since patching plistlib.load still left open() to fail.

- win_safe_mode_check (2): a time bomb. The uptime fixtures were absolute dates
  chosen to be "5 days ago" when written; real time moved past them and the
  5-day fixture aged into a >30-day uptime, tripping the high-uptime warning it
  asserts is absent. Fixtures are now computed relative to now.

- disk_permissions_repair (1): asserted ownership against a hardcoded uid 501,
  so it only passed as a typical macOS user and failed as root. It now derives
  the uid from the process, with /usr/local given a separate non-root uid —
  "correctly owned" means the running user for a home directory but explicitly
  *not* root for /usr/local, which one uid cannot express when running as root.

- update/test_verify (1): the test signs a tag with `git tag -s` without pinning
  gpg.format. git honours the ambient setting, so on a machine configured for
  SSH commit signing it produced an SSH signature while the test asserted on the
  GPG path. Pinned to openpgp. The verification code itself is correct here —
  git verify-tag detects the signature type on its own — so only the test
  changed; no trust check was weakened to get this green.

kext_audit's `find` call also gained the timeout it was missing.

Full suite: 3405 passed, 0 failed (was 64 failed, 3341 passed).

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_013g8dGcNgzJ1vZCw6kguGEu
docs/ROADMAP.md P0#8 recorded that digital_security_reset referenced
password_manager_check, twofa_audit, and session_revocation_scan while none was
registered. The recorded fix deleted the references and relabelled the steps
human-led, so the capability was never actually built. These are the real
modules.

All three are read-only and guidance-only (ActionKind.GUIDANCE, no auto_apply),
and none of them reads a secret: no vault, keychain item, cookie database, token
value, or SSH key material. Their filesystem roots are class attributes so the
tests run off a fixture tree rather than the host.

password_manager_check — detects installed managers (macOS app bundles, Windows
uninstall registry) and browser profiles that would otherwise be holding the
passwords. Browser storage is flagged only when no dedicated manager is present,
since that is the posture worth changing. Prefix matching stops at the first
hit so KeePassXC is not also reported as KeePass.

twofa_audit — the honest one. A local tool cannot tell whether 2FA is enabled on
your accounts; that lives at the provider and confirming it means signing in.
So it reports what it can observe (authenticator apps, locally recorded platform
2FA) and states account status as NOT CHECKED. Crucially, absence of evidence is
reported as unknown, never as "two-factor is off" — an unverified security
verdict presented as fact is worse than an admitted gap, and there is a test
asserting it can never collapse to False.

session_revocation_scan — the post-compromise question: what is still logged in.
Counts browser profiles (each holds its own cookies), device-configured
accounts, and authorised SSH keys, all of which survive a password change. The
guidance puts the password change *before* the sign-out, because revoking first
just lets whoever has the password back in, and that is the step people get
backwards. A test asserts key material never reaches a finding.

Registry now discovers 275 modules; catalog and remediation-code gates pass.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_013g8dGcNgzJ1vZCw6kguGEu
docs/ROADMAP.md P2 lists rescue techniques "missing as coherent, evidence-driven
rescue techniques rather than another collection of isolated checks". Three of
those rows were module-shaped and had no coverage at all.

code_signature_audit — the answer to P2's own complaint that "file-name and
keyword checks create false positives". Verifies signatures via codesign/spctl
and Get-AuthenticodeSignature. Severity is chosen to stay defensible: CRITICAL
only for a signature that is present and broken, because a binary modified after
signing has no innocent reading; WARNING for unsigned software in a system-wide
location; nothing at all for unsigned software in the user's own ~/Applications,
which would be pure noise. Verification is slow, so the scan is capped — and the
INFO finding states the cap and whether it was hit, because a result that
silently examined 40 of 300 apps while implying a full audit is worse than none.

security_baseline_diff — P2's "a single snapshot cannot identify what changed".
Records persistence, listening ports, browser extensions and protection states,
then reports only additions and only protections that went on -> off. The
asymmetry is deliberate: reporting removals and re-enablements symmetrically
buries the four changes that matter under forty that do not. Two properties are
tested rather than assumed — every finding states the trust-on-first-use limit
(a machine already compromised at baseline has that baked in), and a protection
that merely became unqueryable is never reported as disabled.

evidence_bundle — P2's "repair can destroy the information needed to understand
a compromise". Runs at priority 90 so it is considered before the modules that
propose fixes. check() deliberately writes nothing; creating files is a real
effect and belongs behind an explicit human decision. The bundle it can write is
redacted by construction, hashes every item, and records what it omitted and why
so a gap is visible to the recipient rather than looking like an absence of
evidence. It has no network path. A test asserts no collection command can read
a credential store.

digital_security_reset now includes these plus the three account-recovery
modules, with evidence_bundle first. Its description no longer claims the
password-manager, 2FA and session steps are entirely human-led, because the
device-side half of each is now real — while being explicit that changing
passwords, enabling 2FA and revoking sessions still happens at the provider.

Also regenerates rescue/security/integrity_manifest.json, which was stale on
main: verification reported 8 tampered and 5 added files, so every launch
printed a tamper warning. It verifies clean now.

Registry discovers 278 modules. Full suite: 3503 passed, 0 failed.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_013g8dGcNgzJ1vZCw6kguGEu
Three claims in the status document did not hold on main, and a status doc that
says "verified" next to something untrue is worse than no status doc:

- P0#4 self-integrity was recorded as verifying clean. On main it reported 8
  tampered and 5 added files, so every launch printed a tamper warning. The
  regeneration was real but happened on a different lineage.
- P0#8 was recorded as done. The profile was made valid by deleting the three
  module references, not by building the modules.
- The baseline was recorded as 3094 passing. It was 64 failed / 3341 passed,
  with one file that did not parse on the declared minimum Python.

The corrections are appended as a dated section rather than edited in place, so
what was previously believed stays legible.

Also records the current numbers for the two large outstanding migrations, both
of which have grown since the roadmap was written (756 subprocess.run sites with
0 through the bounded runner, 435 without timeouts; 169 modules with no
emits_codes), and the absence of CI as the gap that let the Python 3.11
breakage ship.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_013g8dGcNgzJ1vZCw6kguGEu
@lizTheDeveloper
lizTheDeveloper marked this pull request as ready for review August 5, 2026 22:30
@lizTheDeveloper
lizTheDeveloper merged commit 4996295 into main Aug 5, 2026
@lizTheDeveloper
lizTheDeveloper deleted the claude/merge-and-implement-modules-7a5wtb branch August 5, 2026 22:31
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.

2 participants