Skip to content

Cortex 2.0 steps 1–3: audit, foundation fixes, shared investigation context, manual workflow - #43

Draft
PdB333 wants to merge 76 commits into
mainfrom
claude/keen-hopper-k8jjdj
Draft

PdB333 wants to merge 76 commits into
mainfrom
claude/keen-hopper-k8jjdj

Conversation

@PdB333

@PdB333 PdB333 commented Oct 30, 2026 •

Copy link
Copy Markdown
Owner

This is a draft so that Windows CI runs on it. Do not merge it until CI is green and the change has been reviewed.

Step 1: audit and foundations

docs/cortex-2-roadmap.md covers the capability matrix, components that are obsolete or duplicated, the baseline benchmarks and the plan for steps 2–10.

Fixes, each with a test that fails without it:

  • SessionManager races. It had no lock. ThreadSanitizer reported 45 data races and 14 heap-use-after-free on the old code; it reports none now. Accessors now return snapshots.
  • Settings and cortex.ini are now replaced atomically.
  • A damaged project file is quarantined instead of overwritten, and a good .bak is no longer destroyed. A locked file blocks saves. project::LoadStatus() reports the outcome.
  • cortex_baseline_bench measures the scanners, the pointer scanner and Zydis; a Windows run is uploaded as an artifact.

Step 2: one context for the desktop and agents

  • Shared investigation context in the runtime. It holds pid and generation, modules as module+RVA, the person's selections, the desktop's scan results and debugger state, observations with provenance, and tool-call history.
    • MCP tools: context_get and context_events (observe), context_observation_add (state).
    • Selection and sharing are desktop-only, on the private channel.
  • Caller provenance comes from the authenticated envelope, never from tool arguments.
  • Typed events with gap reporting: module.*, context.*, breakpoint.hit, knowledge.updated, evidence.created and agent.action_*.
  • PID reuse. The runtime reports its generation. PayloadClient and the context routes refuse a mismatch. Desktop workspaces key on id + generation, and a value scan that finishes after a target change is discarded.
  • Common result contract (api::response::Operation). scan_new, scan_next and debug_breakpoint_add gain its fields; their existing fields are unchanged.
  • Desktop:
    • an address-menu "Share with investigation context" action;
    • Project › Knowledge and Investigation context tabs;
    • a scan-sharing switch.

Step 3: manual mode

  • Checked patch revert. patch::RevertChecked refuses with patch_target_changed, patch_overlaps_newer or patch_write_failed and leaves the target untouched. Before, Revert wrote the original bytes blindly.
  • Pointer path stability across processes. Validate records a check per process generation, without dropping any path. Each path is stable, candidate or invalidated. The history is saved (file v2; v1 still loads). Desktop: "Validate here" and a Status column.
  • Memory viewer capture comparison: Capture, Compare with capture (differences stay highlighted) and Next change.
  • Demonstration fixture: CortexDemoActor (heap object, exported pointer, single writer function) in the test program. docs/demo-manual.md walks through it.

Tests

Windows CI on the step 1–2 head (842ab76): all 41 checks completed green or skipped. This includes:

  • the GUI suite;
  • security_no_writes_regression.py (x64 and x86);
  • the new context_isolation_e2e.py on two live targets (x64 and x86);
  • native_mcp_stdio_e2e.ps1 with the new context checks;
  • E2E for x64 and x86;
  • the full builds;
  • contracts p1 and p2.

Step 3 (this push), run locally: cross-built with MinGW and run under Wine (not Windows):

Suite Result
manual_demo_e2e.py on live runtimes, x64 and x86 pass; rerun-safe
context_isolation_e2e.py, x64 and x86 pass
Security regression pass
Root CTest, x64 and x86 22/22 each
app_imgui tests 11/11
GUI workspace and window suites pass

The manual demo now also runs in the GUI suite workflow.

Known limits

  • context_observation_add is not reachable from the compact profile: semantic execution only runs state tools that have a rollback contract.
  • Module and breakpoint events are observed at refresh time, not as they happen.
  • Step 3 screenshots need a real Windows desktop session and are left to step 10.

🤖 Generated with Claude Code

https://claude.ai/code/session_01FWEcEYNNNoVPqE4jQBQGT5

claude added 19 commits October 30, 2026 13:18
The desktop frame loop, its background workers, PayloadClient and the
external debugger backend share one SessionManager, which had no lock.
Attach/Detach/Activate/PruneDeadSessions mutated the session map while
other threads read it, ActiveTargetId() returned a reference to mutable
state and ActiveTarget()/ActiveCapabilities() returned pointers into a
session a later detach could free.

Every member now takes one mutex; the catalog's process open runs outside
it. ActiveTargetId() returns a copy and ActiveTarget()/ActiveCapabilities()
return std::optional snapshots. Existing callers compile unchanged.

The session test now checks that a snapshot outlives a detach and runs
four readers against a writer that attaches, detaches, activates and
prunes. Under ThreadSanitizer the previous implementation reports 45 data
races and 14 heap-use-after-free on it; this one reports none.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01FWEcEYNNNoVPqE4jQBQGT5
SettingsStore::Save and the cortex.ini sync rewrote the live files with
std::ios::trunc, so a crash or a full disk mid-write left a truncated
file: settings fell back to defaults and cortex.ini could lose
project_directory or the diagnostics keys.

services::WriteFileAtomically writes <file>.tmp, flushes it and replaces
the file (MoveFileExW with REPLACE_EXISTING | WRITE_THROUGH on Windows,
rename elsewhere), removing the temporary on failure. Both writers use it.

cortex_settings_store_tests covers the round trip, .ini key and comment
preservation, stale temporaries from a crashed write and a failed replace
leaving the previous file intact.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01FWEcEYNNNoVPqE4jQBQGT5
When <process>.json did not parse, Init fell back to .bak or to defaults
and the next saves rotated the damaged bytes away. With a good .bak, the
first save deleted that backup and moved the damaged primary into its
place.

A damaged primary or backup is now copied to <file>.damaged-<FILETIME>
first, and the damaged primary is removed so the next save keeps the good
backup. If an unreadable primary cannot be copied (locked, access denied),
saves fail for the session instead of replacing it. LoadJsonFile no longer
leaves a half-parsed document in its output. project::LoadStatus() reports
the source, the quarantined copies, saves_blocked and the schema version;
the runtime logs it at start-up.

cortex_project_store_{backup,damaged,fresh,locked} cover each case.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01FWEcEYNNNoVPqE4jQBQGT5
cortex_baseline_bench measures Cortex's own algorithms on a deterministic
in-memory fixture (256 MiB pseudo-random heap, a module, 1000 planted
values, a pointer-dense heap with a known chain, an 8 MiB instruction
stream): value scanner first/next/unknown/AOB scans, the pointer scanner,
session reads and Zydis decode+format for x86 and x64. Every benchmark
also checks its result; --quick is the CTest smoke. It does not measure
ReadProcessMemory, the MCP round trip or UI frame time.

The GUI release job records a Windows run as an artifact, and the Ubuntu
contracts job runs the session manager test under ThreadSanitizer.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01FWEcEYNNNoVPqE4jQBQGT5
docs/cortex-2-roadmap.md records the capability matrix of the current
code (state, location, UI/MCP exposure, tests, limits, duplications),
obsolete components kept for now, the baseline benchmark run, the step 1
fixes with their evidence, what was and was not verified, and the plan
for steps 2 to 10. The reference benchmark JSON is under docs/benchmarks.

Also corrects disasm.h: the disassembler decodes the runtime's own
bitness, not only x86-32.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01FWEcEYNNNoVPqE4jQBQGT5
The event ring kept 2048 untyped events and gave a consumer no way to
learn that events were evicted before it read them. events::types names
the runtime's event types, and events::Read returns the oldest held id
and how many ids after the caller's last one were already dropped
(missed, gap). /ui/events reports the same fields alongside its existing
ones.

cortex_events_tests covers contiguous ids, eviction and gap arithmetic.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01FWEcEYNNNoVPqE4jQBQGT5
The desktop and MCP clients had no common state: what the person found
in the UI was invisible to an agent and the reverse, except through the
project file. Each runtime now keeps one investigation context
(core/context): pid and process generation, modules with module+RVA
locations, the person's address selections, observations with
provenance and a bounded history of tool calls.

- Routes GET /context, GET /context/events, POST /context/observation
  and POST /context/select. The route semantics live in context_api so
  they are tested without a target.
- MCP tools context_get and context_events (observe), reachable from the
  compact profile through capture_runtime_state, and
  context_observation_add (state tier, classified and fixture-locked).
  /context/select is desktop-only and absent from the manifest.
- Caller provenance (context/caller.h) is set by the pipe dispatcher from
  the authenticated envelope, never from tool arguments, and grants no
  authority.
- Every primitive call, direct or semantic, lands in the context history;
  an agent's state-changing calls publish agent.action_* events with
  argument names only. Knowledge writes publish knowledge.updated and
  evidence.created.
- api::response::Operation is the common result contract the context
  routes return.
- Every context route takes expected_generation and refuses a stale one
  with stale_target_generation before recording anything.

cortex_investigation_context_tests (also under ThreadSanitizer in CI) and
a live-runtime step in native_mcp_stdio_e2e.ps1 cover it.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01FWEcEYNNNoVPqE4jQBQGT5
…target

PayloadClient verified only the PID a runtime reported. If the target
exited and its PID was reused by another process carrying a runtime, the
desktop or cortex.exe mcp could keep talking to the wrong process.
status now reports the runtime's process generation (creation time) and
VerifyTarget refuses a mismatch with payload_target_generation_mismatch.
Runtimes that report no generation are accepted on the PID alone, and 0
on either side means unknown, as in SessionManager.

SetDesktopChannel marks the desktop's envelopes so the runtime attributes
the records they create to the person; it grants no authority.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01FWEcEYNNNoVPqE4jQBQGT5
- InvestigationContextModel reads the runtime's investigation context
  and publishes the person's selection over the private desktop channel.
  It never loads the runtime and pins the active target's generation.
- Address menu: "Share with investigation context".
- Project workspace: a Knowledge tab listing project_knowledge_put records
  as stored (status as asserted, revision, evidence count, byte check)
  and an Investigation context tab with entries, provenance and recent
  tool calls; both refresh on knowledge.updated, evidence.created,
  context.* and agent.action_* events.
- AppState marks the desktop's runtime envelopes as the desktop's.

cortex_app_models_tests drives the model against the real context route
semantics: the selection is what an agent reads as module+RVA, an agent's
observation is what the desktop shows with its session, and a stale
generation is refused.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01FWEcEYNNNoVPqE4jQBQGT5
docs/mcp.md documents the context tools, provenance, the result contract,
generation checks, event types and limits. The roadmap records what step
2 delivered, how each criterion was validated and what remains open.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01FWEcEYNNNoVPqE4jQBQGT5
…untime

- POST /context/share (desktop channel only): the desktop shares the
  state the runtime cannot see by itself, its value-scanner results and
  its external debugger's breakpoints and paused threads. The newest share
  of each kind is kept, bounded (256 rows, 64 KiB), validated, with every
  address given its module+RVA location; context_get returns it as
  shared_state and context.state_shared is published.
- breakpoint.hit: InvestigationContext::UpdateBreakpointHits publishes
  counter increases for the runtime's VEH breakpoints (polled by each
  context route) and the desktop debugger (each share). Nothing is
  published from the exception path; the first poll is the baseline and a
  breakpoint added later counts from zero.
- response::Annotate adds the common contract fields to scan_new,
  scan_next and debug_breakpoint_add without changing any field they
  already return; the breakpoint names its rollback.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01FWEcEYNNNoVPqE4jQBQGT5
The Memory, Project, RE and Trace workspaces detected a target change by
id only, and a reused PID keeps the id, so the previous process's scan,
knowledge or traces stayed on screen for the new one. ui::TargetKey (id
plus generation) replaces the id. A value scan, which runs while the UI
stays live, records the target it started on: a result that finishes
after the target changed is discarded. A pointer scan from a previous
process is kept (its paths are module-relative) but labelled for rescan.

Each finished scan is shared with the investigation context when the
runtime is connected (switch in the memory workspace, on by default), and
the frame loop shares the debugger's breakpoints, hit counters and paused
threads at most once a second when they change.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01FWEcEYNNNoVPqE4jQBQGT5
tests/context_isolation_e2e.py injects two test targets, attaches both to
one cortex.exe mcp and checks own pid and generation, no cross-talk,
stale generations refused by the host and by the runtime with nothing
recorded, agent.action_* events without argument values, history
provenance, detach/reattach and one target killed. The GUI suite workflow
runs it for x64 and x86 after the write-authority regression.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01FWEcEYNNNoVPqE4jQBQGT5
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01FWEcEYNNNoVPqE4jQBQGT5
patch::Revert wrote the original bytes back blindly: it did not check the
patched bytes were still in place, ignored newer patches covering the same
bytes (whose own originals are this patch's bytes) and ignored a failed
write. patch::RevertChecked refuses with patch_target_changed (bytes
rewritten since, or memory gone), patch_overlaps_newer (revert the newer
patch first) or patch_write_failed, and leaves the target and the
registry untouched. DELETE /patch/{id} returns the code (409, 404 or 500)
and batch_run's patch_revert reports it. There is deliberately no force
option. Revert() keeps its signature and now reports the refusal; the
action journal's rollbacks use it.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01FWEcEYNNNoVPqE4jQBQGT5
Pointer scanner: Validate records, per process generation, whether each
path led to the value's address there, without dropping any (unlike
Rescan). Status is stable once the target was reached in two distinct
known generations, invalidated after a single miss, candidate otherwise;
a generation of 0 never proves a second process. The scan records its own
generation and the file format v2 keeps the history (v1 still loads). The
desktop pointer table gains a Status column and Validate here.

Memory viewer: Capture keeps the 64 KB window; with Compare with capture
every byte that differs stays highlighted, with a changed-bytes count and
Next change. services::DiffRuns ignores pages unreadable in either
capture. The viewer resets on a reused PID (TargetKey) like the other
workspaces.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01FWEcEYNNNoVPqE4jQBQGT5
The test program gains a heap-allocated CortexDemoActor, reached from the
exported g_cortex_demo_actor and written only by CortexDemoApplyDamage
(the D key, or a <manifest>.damage file for scripts). The manifest lists
its addresses and the global's RVA.

tests/manual_demo_e2e.py runs the step 3 workflow on it over MCP: scan for
the value, narrow it after the action, watch it, catch the write with a
hardware breakpoint, check the instruction lies in CortexDemoApplyDamage
and is the store ending at the trap address, read the function's CFG,
save a pointer path that resolves to the scanned address, a named address
and a knowledge record with the hit as evidence, then clean up. The GUI
suite runs it for x64 and x86.

Also corrects the disasm tool description (it is not x86-32 only).

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01FWEcEYNNNoVPqE4jQBQGT5
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01FWEcEYNNNoVPqE4jQBQGT5
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01FWEcEYNNNoVPqE4jQBQGT5
@PdB333 PdB333 changed the title Cortex 2.0 steps 1–2: audit, foundation fixes, shared investigation context Cortex 2.0 steps 1–3: audit, foundation fixes, shared investigation context, manual workflow Oct 30, 2026
claude added 10 commits October 30, 2026 15:33
Adds the requirement to analyze JavaScript/TypeScript, Electron, .NET,
Python, Unity/IL2CPP, Java and WebAssembly, and later APK, ELF and
Mach-O. Step 4 becomes static analysis on an artifact-provider
architecture: the offline PE model is its first provider, followed by
JS/Electron and .NET static providers. Decompilation and live debugging
(ILSpy, Vineflower/CFR, CDP, the CLR, JDWP, debugpy) are added to step 8,
evidence and cross-runtime workflows to steps 5-7, and fuzzing and live
E2E to step 9. Section 8 records the principles (one provider system;
static, decompile and live capabilities validated separately; the native
engine unchanged; security unchanged), the priority order and the
validation fixtures.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01FWEcEYNNNoVPqE4jQBQGT5
On Windows CI (MSYS2 GCC) CortexDemoApplyDamage compiles to
sub [rcx+0x04], edx instead of a load and a mov store, so the check that
required a mov failed on x64 and x86. The check now requires the
instruction ending at the trap address to write the health field (+0x04)
with mov, sub or add.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01FWEcEYNNNoVPqE4jQBQGT5
core/artifacts: a provider registry over bounded byte sources. Every
result (schema cortex.artifact.v1) carries the identity (name, size,
SHA-256), the provider and its version, and separate static, decompile
and live capability statuses.

- PE: headers, sections (long names via the COFF string table), imports,
  exports, relocations, CodeView, TLS, exceptions, CLR header,
  certificate, overlay; bounded listings, fuzzed.
- ASAR: header, file tree, package.json, readable and unreadable entries.

Access: cortex.exe inspect, and the read-only MCP host tools
cortex_artifact_inspect/_children/_read, confined to the operator's
--artifact-root directories (canonical paths, 64 KiB reads).

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01FWEcEYNNNoVPqE4jQBQGT5
Inspects a file through the same providers as cortex.exe inspect: the
capabilities table, a bounded structure tree, container entries (nested
with '!') and Record in investigation context, which hands the artifact's
SHA-256 to agents as a desktop observation. The GUI workspace suite
inspects cortex.exe itself; CI runs the artifact E2E on x64 and x86.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01FWEcEYNNNoVPqE4jQBQGT5
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01FWEcEYNNNoVPqE4jQBQGT5
A real small Electron app (CommonJS main, preload with contextBridge, ES
modules, TypeScript renderer, lexer traps). make_electron_fixture.mjs
bundles the renderer with esbuild 0.25.12, packs the app with
@electron/asar 4.3.1 and records Node's node:module SourceMap answers for
every column of the bundle.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01FWEcEYNNNoVPqE4jQBQGT5
- js_lexer: strings, nested templates, regex vs division, comments,
  sourceMappingURL; UTF-16 columns; errors are warnings.
- js_analysis: ESM/CommonJS/dynamic dependencies, exports, Electron IPC
  uses and BrowserWindow webPreferences (aliases followed), bundler
  signatures, minification heuristic.
- source_map: VLQ decoding and lookup with Node's semantics; inline maps.
- ASAR: per-file integrity check, header SHA-256, module graph and a
  static Electron view (main/preload/renderer files, IPC channels paired
  by handler and caller, contextBridge keys).
- MCP cortex_artifact_source_position and inspect --position; the map a
  script names stays inside the artifact roots or its container.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01FWEcEYNNNoVPqE4jQBQGT5
The GUI workspace suite inspects a script with an inline map and an IPC
call, draws the new tabs and maps a position. The artifact E2E covers the
Electron fixture over MCP and the CLI; CI packs a fresh archive with
@electron/asar first.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01FWEcEYNNNoVPqE4jQBQGT5
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01FWEcEYNNNoVPqE4jQBQGT5
Cortex.Fixture is a small C# library built by the .NET SDK (enum,
interface, nested class, switch, try/catch/finally, generics, P/Invoke,
embedded resource). Oracle/Program.cs records the expected metadata,
signatures and CIL using System.Reflection.Metadata and the runtime's
OpCodes table. make_dotnet_fixture.py also writes the dotnet pack package
and Python zipfile cases.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01FWEcEYNNNoVPqE4jQBQGT5
claude added 30 commits November 2, 2026 17:21
… work folder

CI (analysis-engines) passed a relative path; Ghidra and ilspycmd run in
the job's temporary folder and could not find it. The service now stores
the absolute path; a test covers it. The provider tests no longer abort
on a failed ILSpy job.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01FWEcEYNNNoVPqE4jQBQGT5
…valuation)

- core/live: loopback-only HTTP and RFC 6455 WebSocket client (handshake key
  checked) and a CDP session: targets, scripts, source, console, log
  breakpoints that capture the stack and locals (getters not run) and
  resume at once, removed after max_hits; foreign pauses (debugger
  statements) recorded and resumed. No Runtime.evaluate, no conditions.
- MCP cortex_live_* (ports allowed with --inspect-port; breakpoints need
  mutation_permission and --allow-writes), cortex.exe live, desktop
  AI Investigation > Live (CDP) tab.
- Tests: cortex_cdp_tests against a real node --inspect (native and
  Winsock under Wine), tests/live_e2e.py, GUI suite, CI steps.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01FWEcEYNNNoVPqE4jQBQGT5
On Windows a killed JVM's files stay locked briefly; CI (x86 target)
found the cancelled Ghidra project folder left behind. Retry for up to
10 s and report a folder that still could not be removed.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01FWEcEYNNNoVPqE4jQBQGT5
- core/providers/vineflower_provider.cpp: java -jar vineflower.jar -s on a
  .jar/.class, sources returned as files; version from the --help banner.
  --vineflower / CORTEX_VINEFLOWER, java from JAVA_HOME or PATH.
- Java fixture (tests/fixtures/java, built with javac --release 17):
  enum switch, lambda, generics, try/catch/finally, nested class.
- Tests: provider test (sources and signatures present, cache, refused
  options); analysis_e2e.py recompiles the decompiled sources with javac and
  compares javap -p of every class; CI pins Vineflower 1.11.1 by SHA-1.
- MCP/CLI/desktop: provider vineflower, Engines tab for .jar files.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01FWEcEYNNNoVPqE4jQBQGT5
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01FWEcEYNNNoVPqE4jQBQGT5
ELF fixture (tests/fixtures/elf, gcc -O1 PIE, with nm's symbols as the
expected answer): every nm function is a Ghidra function at the same RVA
and the decompiled CortexDemoApplyDamage writes +4/+8/+0xc. The provider
tests take "-" to skip an argument.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01FWEcEYNNNoVPqE4jQBQGT5
Hashes the files of the selected target's loaded modules (only those of
the job's file size) and returns base+RVA for the module whose file has
the job's SHA-256; nothing is mapped otherwise. Unit test of the matching;
analysis_e2e maps CortexDemoApplyDamage in the running test target and
compares with the address the process reports.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01FWEcEYNNNoVPqE4jQBQGT5
- core/live/dap.{h,cpp}: DAP client over loopback TCP sharing the new
  LiveSession interface with CDP (core/live/live_service.{h,cpp}). Log
  breakpoints only (setBreakpoints lines; no condition, logMessage or
  evaluate); at a hit the stack and the top frame's locals are recorded
  and the thread continues; removed after max_hits; foreign stops
  recorded and continued; disconnect with terminateDebuggee=false.
  Source() reads only files the debugger reported in a stack.
- MCP cortex_live_attach protocol=dap adapter=debugpy|netcoredbg
  (netcoredbg attach to a pid needs --allow-writes + mutation_permission),
  cortex.exe live --protocol dap, desktop Live tab protocol selector.
- Fixtures: tests/fixtures/python-live/app.py, tests/fixtures/dotnet-live.
- cortex_dap_tests against real debugpy 1.8.22 and netcoredbg 3.1.2
  (natively; Windows build checked under Wine); CI pins both.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01FWEcEYNNNoVPqE4jQBQGT5
- core/live/jdwp.{h,cpp}: JDWP over loopback TCP. Breakpoints by loaded
  class and line with suspend policy EVENT_THREAD; at a hit the stack
  (FrameCount then Frames), the top frame's line and locals
  (VariableTable, StackFrame.GetValues; strings via StringReference.Value,
  objects by type only) are recorded and the thread resumed; cleared after
  max_hits; VirtualMachine.Dispose on detach. No method invocation.
- cortex_live_attach protocol=jdwp, cortex.exe live --protocol jdwp,
  desktop Live tab JDWP option.
- cortex_dap_tests: Java fixture (tests/fixtures/java-live) on a real JVM;
  CI runs it with CORTEX_REQUIRE_JDWP=1.
- Docs: JDWP section, roadmap step 8 status (delivered), changelog, MCP
  and third-party notes.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01FWEcEYNNNoVPqE4jQBQGT5
…P step fails

analysis-engines on Windows: debugpy and JDWP pass, netcoredbg's server
refused connections for 30 s. The test now prints the adapter's own
output and exit code when attaching fails and runs it from its folder;
the Ghidra/ILSpy/Vineflower MCP E2E runs regardless so one run reports
every result (the job still fails on any red step).

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01FWEcEYNNNoVPqE4jQBQGT5
On a busy Windows runner node had not opened its inspector after the
fixed 1.5 s wait (inspector_unreachable). The suite now retries the
attach for up to 20 s.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01FWEcEYNNNoVPqE4jQBQGT5
On Windows CI netcoredbg exited with code 0 and no output: its server mode
ends after one client session, so a first failed attach made every retry
see connect_refused. The test now retries only while the adapter is not
listening and prints the first other error; the DAP client includes the
adapter's error body in dap_attach_failed. Documented.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01FWEcEYNNNoVPqE4jQBQGT5
Windows CI: netcoredbg accepted the connection but answered initialize
after more than the 10 s timeout (its first start loads its managed part),
so the session ended and the one-session server exited. Initialize,
configurationDone and attach now wait up to 60 s, and
dap_initialize_failed carries the reason (timeout, adapter message).

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01FWEcEYNNNoVPqE4jQBQGT5
CI now gets past initialize (60 s) but netcoredbg never answers attach
on Windows (timeout), while the same attach works on Linux. The test runs
netcoredbg with --engineLogging and prints that log when attaching fails;
dap_attach_failed also carries the adapter's output events.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01FWEcEYNNNoVPqE4jQBQGT5
Windows CI: netcoredbg's server accepted the connection and then left
initialize unanswered for 60 s (engine log empty); under Wine the same
binary answers in 0.1 s with its output going to a file. The process
runner gains ProcessSpec::outputFile (stdout/stderr to a file instead of
a pipe); the test uses it for netcoredbg, adds --log=file and prints its
output and log files when attaching fails.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01FWEcEYNNNoVPqE4jQBQGT5
…mode finding

CI engine log and a Wine reproduction with an independent client show the
Windows netcoredbg 3.1.2 --server mode answering initialize and then
reading no further request. The test stays in CI (red on that step);
documented, with stdio mode as a possible way round.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01FWEcEYNNNoVPqE4jQBQGT5
The Windows netcoredbg 3.1.2 --server mode reads no request after
initialize. Over Win32 pipes the same binary answers initialize, attach
and configurationDone (checked under Wine with a Windows harness).

- core/providers: ChildProcess (stdin/stdout pipes, job object / process
  group, stderr to a file or discarded).
- core/live/dap: transport over TCP or an adapter's pipes;
  DapSession::AttachStdio; LiveService::AttachNetcoredbgStdio with the
  operator's path (--netcoredbg, CORTEX_NETCOREDBG).
- cortex_live_attach adapter=netcoredbg without a port uses stdio mode
  (still needs --allow-writes + mutation_permission); cortex.exe live
  --netcoredbg; desktop port 0.
- cortex_dap_tests: stdio mode on every system, --server mode on
  non-Windows systems only. Docs updated.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01FWEcEYNNNoVPqE4jQBQGT5
…mplates, limitations

- README reorganized: three GIFs recorded from the real app (under Wine
  with Xvfb) against the test target and fixtures: manual find-what-writes,
  a scripted MCP client with the desktop's AI Activity, and static
  inspection of an Electron asar and a .NET assembly. An architecture
  diagram (SVG), a table of what can be analyzed with each row's
  validation, the safety model, command line and an FAQ.
- New CONTRIBUTING.md, SECURITY.md, issue and pull request templates,
  docs/limitations.md (the documented limits in one page).
- docs index and CHANGELOG updated (including netcoredbg stdio mode); a
  stale 'live providers not implemented' line in analysis-providers.md
  fixed; the .NET file provider's live note points to netcoredbg.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01FWEcEYNNNoVPqE4jQBQGT5
Run 145 (analysis-engines): netcoredbg-win64 3.1.2 in stdio mode attached
to the .NET live fixture and recorded its hits with locals.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01FWEcEYNNNoVPqE4jQBQGT5
- docs/images/brand/cortex-logo.png (512) and cortex-logo-1024.png: the
  new C-and-probe logo with its generated dark background removed and a
  thin dark outline on the white probe so it reads on light backgrounds.
- cortex-banner-dark.png (rounded corners) and cortex-banner-light.png
  (transparent, dark text, rebuilt from the clean logo); the README picks
  one with prefers-color-scheme.
- resources/windows/cortex.ico regenerated from the logo at the same nine
  sizes (16 to 256).

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01FWEcEYNNNoVPqE4jQBQGT5
A side-by-side section: the person drives the desktop and the command
line; an AI client drives cortex.exe mcp, with the person choosing write
authority, watching AI Activity, sharing selections and approving steps.
The quick start now has desktop, command-line and MCP parts. The note
about the unreleased 2.0 is removed.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01FWEcEYNNNoVPqE4jQBQGT5
… targets

request_id, pagination, mcp_protocol, mcp_pipe_protocol, mcp_schema,
mcp_query_schema, mcp_bridge_policy, mcp_authority (with the risk
fixture), semantic_tools (with routes_status.cpp), action_transaction and
call_scheduler (Windows) are built by CMake under BUILD_TESTING, linked
statically on MinGW, and run by every ctest. contracts.yml drops the
inline c++ steps its p1/p2 jobs repeated after ctest. 11/11 pass under
Wine for x64 and x86.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01FWEcEYNNNoVPqE4jQBQGT5
tests/fuzz is a separate CMake project built with clang on Linux:
- fuzz_artifact: the provider registry as cortex.exe inspect runs it
  (detection, Inspect, children two levels deep: PE, .NET, ZIP/JAR with
  DEFLATE, ASAR, JS/TS, source maps);
- fuzz_pe_code: ParsePe, CLR metadata and CIL, and the native-code index
  (Zydis): functions, strings, xrefs, CFG, fingerprints;
- fuzz_text: JS lexer and analysis, source maps, data URLs, DEFLATE.
Seeds come from the repository's fixtures (make_seeds.py) plus two tiny
PEs built from tests/fuzz/seeds/pe/tiny.c. CORTEX_FUZZ_REPLAY=ON builds the
harnesses as plain executables that ctest uses to replay kept crash
inputs (regressions/). CI job fuzz-parsers runs each harness for 60 s.

Local runs (5 min each): 50,876 / 11,403 / 627,977 inputs, no crash, no
sanitizer report. Not covered: the live protocol decoders and the JSON
project files (documented).

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01FWEcEYNNNoVPqE4jQBQGT5
Fixes:
- cortex.exe mcp exited (std::terminate on a request thread) when a local
  tool got a wrongly typed argument ({"job_id": null}), when tools/call
  params was not an object, and when a notification's method was not a
  string. Local tools now answer invalid_arguments, each request has a
  last-resort internal_error reply, and malformed notifications are
  ignored; the server keeps serving.
- A .NET TypeSpec signature naming itself recursed until the stack
  overflowed (found by fuzz_artifact and fuzz_pe_code). TypeSpec nesting
  (8) and the work per top-level signature (4096 steps) are bounded. The
  two crash inputs are kept in tests/fuzz/regressions: they fail without
  the fix and pass with it.

tests/robustness_e2e.py (42 checks, x64 and x86 under Wine, new CI step):
every tool called with wrongly typed arguments and malformed JSON-RPC
(answered, server and target alive); a target killed during a request
(failure reported at once); a debuggee killed with a hardware breakpoint;
a new target on the same server; engines absent or failing mid-job (jobs
end as failed with the engine's output). Its client has one reader
thread so a late reply is never taken for a later one.

GUI attached suite: the access-finder check waits for the breakpoint to
be applied, then for a write, on time budgets instead of a fixed frame
count, and its errors say which phase failed (it was flaky on loaded
runners). It still requires a recorded write.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01FWEcEYNNNoVPqE4jQBQGT5
analysis-engines, x86-fixture, windows-debug-ui and windows-release-full
install MSYS2's ccache, restore a per-job cache (keyed by commit, restored
from the latest) and configure CMake with ccache as the compiler launcher.
ccache hashes the preprocessed source and the compiler (compilercheck
content), so a stale cache cannot change a build. Each build step prints
ccache -s so the hit rate is visible. The first run fills the caches.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01FWEcEYNNNoVPqE4jQBQGT5
A killed runtime cannot delete its cortex.mcp.<pid>.token. With a reused
PID that token named a pipe that did not exist, so clients and tests
thought the new runtime was ready. Now:
- the payload client treats a token written before its process was
  created as absent and deletes it;
- cortex.exe mcp removes, at startup, the tokens of processes that are
  gone or that predate the process with that PID (bundle root and
  runtime/x64, runtime/x86), leaving live runtimes' tokens alone;
- cortex inject removes such a token for its target before injecting.
robustness_e2e.py checks all three cases under Wine (48 checks).

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01FWEcEYNNNoVPqE4jQBQGT5
…found

Live providers (CDP, DAP, JDWP):
- Framing moved to core/live/protocol_frames (WebSocket messages, DAP
  Content-Length, JDWP packets), used by the sessions and the fuzzer.
  A Content-Length past 64 MiB or not a number now ends the session.
- Every read of debugger data goes through core/live/json_safe.h: typed
  accessors that never throw (a {"type": 5} from an adapter used to
  throw on a reader thread and end cortex.exe mcp).
- Clip cuts on a UTF-8 boundary and returns valid UTF-8 (JDWP strings
  too), so records always serialize. Fuzzing found Clip exceeding its
  bound when repair happened after the cut; it now cleans first.
- An exception on a session's reader or event thread ends that session
  with protocol_error instead of the process.
Native ASan/UBSan runs against real Node, debugpy, netcoredbg (stdio and
server) and JDWP pass.

Stores: fuzz_stores found that an evidence or investigation file edited
on disk (a record without subject, a step without status) reached
unchecked indexing (an assertion, undefined behaviour in release).
Records are checked against their structure when read and a damaged one
is skipped. Unit tests pass; the crash inputs are kept as regressions.

tests/fuzz: fuzz_protocols and fuzz_stores, seeds, CI runs all five
harnesses. Campaigns after the fixes: 753,656 and 1,174,921 inputs, no
finding.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01FWEcEYNNNoVPqE4jQBQGT5
… a scan leak

Measurements (CI artifacts, Windows runner):
- tests/live_baseline.py: exact and unknown scans on the live test
  target, MCP round-trip p50/p95 of memory_read, disasm and tools/list;
  run for x64 and x86 in the GUI suite (cortex-live-baseline-windows).
- analysis_e2e.py --compare-out: Cortex's native function catalog
  against Ghidra's on the same bytes (counts, starts in both / only one,
  exported functions found, time) -> cortex-vs-ghidra-functions. Only
  the exported functions must agree.

Fixes found by these runs:
- Scan sessions lived in the target forever; repeated unknown scans grew
  it and slowed each next scan (Wine: 305, 1640, 2949 ms...). At most
  four are kept, evicted oldest first before a new scan. Unit test.
- robustness_e2e.py killed the target in 2 of 4 runs; Cortex's crash
  report put it in context::CallerInfo::~CallerInfo: a thread_local with
  a destructor in the MinGW DLL, destroyed after its TLS was freed at
  thread exit. The per-thread slot is now trivially destructible.
  robustness_e2e.py now names the tool after which a process died.

Checked: Windows x64 build, core, investigation context, authority and
semantic tests under Wine. The repeated robustness runs that would
confirm the crash fix were interrupted; CI runs them for x64 and x86.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01FWEcEYNNNoVPqE4jQBQGT5
Live baselines, Cortex vs Ghidra function comparison and ccache build
times from run 155; open items listed.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01FWEcEYNNNoVPqE4jQBQGT5
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