Repository navigation
Conversation
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
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
… 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
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
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
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.mdcovers 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:
SessionManagerraces. 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.cortex.iniare now replaced atomically..bakis no longer destroyed. A locked file blocks saves.project::LoadStatus()reports the outcome.cortex_baseline_benchmeasures the scanners, the pointer scanner and Zydis; a Windows run is uploaded as an artifact.Step 2: one context for the desktop and agents
module+RVA, the person's selections, the desktop's scan results and debugger state, observations with provenance, and tool-call history.context_getandcontext_events(observe),context_observation_add(state).module.*,context.*,breakpoint.hit,knowledge.updated,evidence.createdandagent.action_*.PayloadClientand the context routes refuse a mismatch. Desktop workspaces key on id + generation, and a value scan that finishes after a target change is discarded.api::response::Operation).scan_new,scan_nextanddebug_breakpoint_addgain its fields; their existing fields are unchanged.Step 3: manual mode
patch::RevertCheckedrefuses withpatch_target_changed,patch_overlaps_newerorpatch_write_failedand leaves the target untouched. Before,Revertwrote the original bytes blindly.Validaterecords 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.CortexDemoActor(heap object, exported pointer, single writer function) in the test program.docs/demo-manual.mdwalks through it.Tests
Windows CI on the step 1–2 head (
842ab76): all 41 checks completed green or skipped. This includes:security_no_writes_regression.py(x64 and x86);context_isolation_e2e.pyon two live targets (x64 and x86);native_mcp_stdio_e2e.ps1with the new context checks;Step 3 (this push), run locally: cross-built with MinGW and run under Wine (not Windows):
manual_demo_e2e.pyon live runtimes, x64 and x86context_isolation_e2e.py, x64 and x86app_imguitestsThe manual demo now also runs in the GUI suite workflow.
Known limits
context_observation_addis not reachable from the compact profile: semantic execution only runs state tools that have a rollback contract.🤖 Generated with Claude Code
https://claude.ai/code/session_01FWEcEYNNNoVPqE4jQBQGT5