Skip to content

Cortex 2.0 - #43

Merged
PdB333 merged 85 commits into
mainfrom
claude/keen-hopper-k8jjdj
Nov 6, 2026
Merged

PdB333 merged 85 commits into
mainfrom
claude/keen-hopper-k8jjdj

Conversation

@PdB333

@PdB333 PdB333 commented Oct 30, 2026 •

Copy link
Copy Markdown
Owner

Cortex 2.0: roadmap steps 1 to 10. The release notes are the ## [v2.0.0] section of CHANGELOG.md; the plan, what each step validated and its limits are in docs/cortex-2-roadmap.md and docs/limitations.md.

Highlights:

  • Two ways to work. The desktop and the command line for a person; cortex.exe mcp for an AI agent, read-only unless the operator adds --allow-writes, with every call shown in the desktop's AI Activity.
  • Files as well as processes. Static analysis of PE and native code, .NET, Electron ASAR, JavaScript with source maps, and ZIP/JAR, without running anything.
  • External engines and live providers. Ghidra, ILSpy and Vineflower as separate processes; log breakpoints for Node.js, Python, .NET and Java.
  • Evidence and supervised investigations, with an AI Investigation workspace.
  • A desktop that does not assume a process: a Home page, Files and AI layouts, Open a file and recent files, a grouped View menu, and a keyboard path for attach and scan.
  • Quality. Fuzzing, robustness tests, live baselines, a comparison with Ghidra, and a desktop acceptance run on a real Windows session.

Tests

On bcf26e8, every workflow is green: Contracts and unit tests (fuzzing included), the ImGui GUI suite (with the analysis engines), Lightweight ImGui UI, Full Windows Build, Cortex E2E Windows, Publish Release (the portable archive is built and validated), Unified Cortex Host, UI Migration Benchmarks, and Desktop acceptance (18 checks on a real Windows 11 desktop: icon, file dialog, guide, attach and scan, MCP read-only and with --allow-writes, clean close).

The merge commit carries Release v2.0.0, so publish-release.yml publishes the release on main, as approved by the maintainer.

🤖 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 27 commits November 4, 2026 22:10
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
- core/version.h is the one place the version lives (2.0.0): cortex.exe
  --version, the Help menu and MCP serverInfo read it. --help no longer
  says 'migration preview' and lists all engines and live debuggers.
- publish-release.yml targets v2.0.0. It still publishes only on a push
  to main whose commit says 'Release v2.0.0' and refuses to overwrite.
  New archive checks: release documents present, every README link and
  image resolves inside the archive, README_INSTALL names v2.0.0, --help
  lists the 2.0 commands, provider list works without engines, no test
  leftovers. CONTRIBUTING, SECURITY and agent/ are now in the archive.
- README_INSTALL.txt rewritten for 2.0; CHANGELOG 'Unreleased' becomes
  '[v2.0.0] - not yet released' with highlights (the workflow's notes).
- docs/releasing.md (checklist before approval), roadmap step 10 status,
  docs no longer present v1.0 as current.
Checked under Wine: --version 2.0.0, --help, provider list, MCP
serverInfo 2.0.0, protocol/schema/authority tests.

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 opened on the memory scanner and every entry point asked for a
process, although files and AI agents need none.

- Home: three ways in (analyze a file with recent files, inspect a
  running program, work with an AI agent with the MCP configuration to
  copy). Cortex starts there; attaching from it opens the Memory layout.
- File > Open a file... (Ctrl+O), File > Recent files and a header
  button. The recent list is kept only by the interactive desktop, so
  test modes leave the person's settings alone.
- Home, Files and AI layouts; the View menu grouped by task; Sessions,
  Runtime and Diagnostics under Tools > Advanced; Help opens the guides.
- Panels that need a process say so and offer to select one or analyze
  a file. Without a process the bottom panel shows only AI Activity and
  no runtime controls. The status line names the panel in use.
- Artifacts: the capability table says what to do instead of naming MCP
  tools; the stale 'step 8' notes of the ASAR and JS providers are fixed.
- The header shows the Cortex mark (drawn, no texture) and disabled
  controls are dimmer.

The GUI workspace suite checks the start page, the Files and AI paths
and that every workspace has a View group.

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

CI fuzzing found a record file whose verification.trials held a number:
the read path called value() on it and the exception left the store. A
relation that is not an object, or without an object 'to', did the same
through operator[], and a trial with a null outcome broke the Markdown
export.

WellFormed now checks every field the read paths take, down to array
elements, and the export reads a missing outcome as such. The three
inputs are in tests/fuzz/regressions/fuzz_stores; each threw before the
change and replays cleanly after it.

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

tests/desktop_acceptance.py drives the shipped cortex.exe on the runner's
desktop with SendInput: the icon resource and window icons, Ctrl+O and the
system file dialog (the file joins the recent files), Help: Getting started
opening the guide with the associated program, attach from the palette and
the process picker, a first scan typed into the value field, an MCP client
read-only then with --allow-writes, and a clean close. Screenshots go to
the artifact and to the log as base64 PNG. New workflow:
desktop-acceptance.yml.

For the keyboard path, which is also the person's:
- the process picker focuses its search field, and Enter attaches the
  selected row or the first match;
- after an attach from the picker, the value field of the scanner takes
  the keyboard, kept for a few frames since the closing popup hands focus
  back to its parent first;
- the palette has Help: entries for the four guides.

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

The first run on a real Windows 11 desktop passed 15 of 17 checks (icon,
window, file dialog, MCP read-only and with --allow-writes, clean close).
The path was typed before the dialog took input and lost its first
character, and the dialog left open blocked the guide step. The file name
is now set in the dialog's File name box and Open is clicked; a dialog
left open is closed before the next step. Focusing the window no longer
sends Alt, which opened the menu bar.

The screenshots also showed the start page at 1024x768: the three cards
stacked, each 250 px tall, put the AI card below the fold. Cards now sit
side by side from a smaller width, and stacked cards fit their content.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01FWEcEYNNNoVPqE4jQBQGT5
Its second run on a Windows 11 runner passed all 18 checks: icon of the
executable and window, the system file dialog and recent files, the guide
opened in Notepad by the shell, attach from the picker and a first scan
(6 results for 100), an MCP client refused read-only and applied with
--allow-writes while the desktop showed it connected, and a clean close.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01FWEcEYNNNoVPqE4jQBQGT5
…ebugger [skip ci]

manual-find-what-writes.gif starts on the Home page, attaches by typing
the target's name and Enter, and scans by typing into the focused value
field. agent-mcp-live.gif shows the bottom panel's AI agents line and AI
Activity. static-inspect.gif is the command line, unchanged.

Recording it showed the header's first line overflowing once a debugger
was attached: the Open a file... button pushed Writes allowed and Detach
to a second line, moving the panels down. The button is shown while no
process is attached; File > Open a file... and Ctrl+O always work.

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–3: audit, foundation fixes, shared investigation context, manual workflow Cortex 2.0 Nov 6, 2026
@PdB333
PdB333 marked this pull request as ready for review November 6, 2026 22:58
@PdB333
PdB333 merged commit 6dd0f38 into main Nov 6, 2026
45 checks passed
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