Files
reverse-engineering-anything/src/contracts/native/binaryDiagnosticsToolContracts.ts
T
f21fda0fc0 feat(elf): report a Linux kernel image's version banner and embedded build config (#9573)
* feat(elf): report a Linux kernel image's version banner and embedded build config

inspect_kernel_build_config and inspect-kernel-build-config read one
local vmlinux, raw arm64 Image, gzip bzImage or zImage, or configs.ko
and report its Linux version banner and the IKCONFIG .config embedded
between IKCFG_ST and IKCFG_ED, without executing it. A config is present
only when a gzip member ends exactly at IKCFG_ED, passes its CRC-32 and
ISIZE checks and parses as .config text; framing that fails is reported
malformed, never absent. bzImage gzip payloads are inflated within a
budget and other codecs are named as unsupported, using codec detection
now shared with the kernel module parser. The config search, inflate
sizes and option counts are bounded, scans honor cancellation, and
target-derived text is marked untrusted.

* fix(elf): reject a gzip member whose header checksum fails

When FHCRC is set, compare the low 16 bits of the header CRC-32 before
inflating, so a damaged header no longer yields a present config or an
inflated bzImage payload.

* fix(elf): detect the bzImage codec inside the declared payload

Codec detection now reads the same payload_offset/payload_length slice that
is inflated, so bytes past a short payload cannot name its codec.

* fix(elf): report a framed config that does not decode as malformed

A damaged or cut-off deflate stream between IKCFG_ST and IKCFG_ED was
skipped and could end as an absent config with complete coverage. It is
now malformed with config_gzip_invalid, and a later valid config still wins.

* fix(elf): scan config lines in place and accept long string values

Config text is read one line at a time instead of split into an array, and
a quoted value is validated before it is decoded once. The 4096-character
string cap is removed: Kconfig has no such limit and max_config_bytes
already bounds every line, so a long CONFIG_EXTRA_FIRMWARE list is no
longer reported as malformed.

* fix(elf): let a live cancellation reach the kernel config parser

The parser ran synchronously, so an abort raised during parsing could not
change the signal before it returned. Inflation now runs on the zlib thread
pool and each scan yields to the event loop once per MiB, so the existing
cancellation checks observe a real abort.

* refactor(elf): carry the refused kernel config budget as a typed fact

Budget failures are now KernelBuildConfigBudgetFailure with a knob field,
and the provider builds its recovery advice from that field instead of
searching the diagnostic message for an option name.

* docs(elf): move the kernel build config guide under docs/guides/native

The documentation guide assigns analyst workflows to docs/guides/<domain>/.
Move the guide there, place its navigation and directory entries in the
Native binaries group, and update links from the docs index, CLI guide,
spec and authored skill. The public /kernel-build-config route is unchanged.

* fix(elf): yield while checking one long config line

A config made of one long quoted value never reached the per-line yield, so
the control-byte check, value validation and SHA-256 ran in one synchronous
block. Each now works one MiB window per event-loop turn, and the line
patterns match only a bounded name prefix instead of the whole value.

* fix(elf): return paged config entries that do not pin the decoded text

Entry names and values were V8 slices of the whole decoded config, so a
one-entry page kept the full text alive. Entries in the returned page are
now standalone copies, so retained memory follows the page size.

* fix(elf): yield while kernel marker searches find nothing

IKCFG_ST, IKCFG_ED and banner searches used one Buffer.indexOf over the
rest of the image, so a large image with no marker never yielded. They now
search overlapping one MiB windows and yield after each window without a
match.

* fix(elf): preserve long kernel config names

* fix(elf): frame kernel configs by gzip member boundaries

---------

Co-authored-by: Matt Van Horn <455140+mvanhorn@users.noreply.github.com>
Co-authored-by: morluto <76467478+morluto@users.noreply.github.com>
2026-11-09 21:41:56 +08:00

359 lines
24 KiB
TypeScript

import {
inspectDevicetreeInputSchema,
devicetreeSchema,
} from "../../domain/firmware/devicetree.js";
import {
devicetreeSummaryViewResultSchema,
binaryLayoutSummaryViewResultSchema,
nativeExportsSummaryViewResultSchema,
} from "../../domain/analysisView/analysisView.js";
import {
inspectPeLoadConfigInputSchema,
peLoadConfigSchema,
} from "../../domain/native/peLoadConfig.js";
import {
binaryHardeningSchema,
inspectBinaryHardeningInputSchema,
} from "../../domain/native/binaryHardening.js";
import { inspectByteEntropyInputSchema } from "../../domain/native/byteEntropy.js";
import { byteEntropyOutputSchema } from "../toolOutputSchemaGroups.js";
import {
inspectRecordedCrashInputSchema,
recordedCrashSchema,
} from "../../domain/native/recordedCrash.js";
import {
inspectBinaryLayoutInputSchema,
binaryLayoutSchema,
} from "../../domain/native/binaryLayout.js";
import {
analysisDetailOutputSchemaOf,
analysisDetailSchema,
} from "../analysisDetail.js";
import {
inspectNativeExportsInputSchema,
nativeExportsSchema,
} from "../../domain/native/nativeExports.js";
import type { ToolContract } from "../toolContractTypes.js";
import { toolContractMetadata } from "../toolEffects.js";
import { evidenceResultOf } from "../toolOutputSchemaPrimitives.js";
import {
inspectPeResourcesInputSchema,
peResourcesSchema,
} from "../../domain/native/peResources.js";
import {
inspectPeCertificatesInputSchema,
peCertificatesSchema,
} from "../../domain/native/peCertificates.js";
import {
inspectPeImportsInputSchema,
peImportsSchema,
} from "../../domain/native/peImports.js";
import {
inspectElfNotesInputSchema,
elfNotesSchema,
} from "../../domain/native/elfNotes.js";
import {
inspectAppleIpsInputSchema,
appleIpsSchema,
} from "../../domain/native/appleIps.js";
import {
inspectKernelModuleInputSchema,
kernelModuleSchema,
} from "../../domain/native/kernelModule.js";
import {
inspectKernelBuildConfigInputSchema,
kernelBuildConfigSchema,
} from "../../domain/native/kernelBuildConfig.js";
import {
inspectWindowsShortcutInputSchema,
windowsShortcutSchema,
} from "../../domain/native/windowsShortcut.js";
/** Caller-selected delivery of completed binary layout Evidence. */
export const inspectBinaryLayoutRequestSchema =
inspectBinaryLayoutInputSchema.extend({ detail: analysisDetailSchema });
export const inspectNativeExportsRequestSchema =
inspectNativeExportsInputSchema.extend({ detail: analysisDetailSchema });
/** Public byte-entropy input retains the producer schema's closed-object policy. */
export const inspectByteEntropyRequestSchema =
inspectByteEntropyInputSchema.extend({ detail: analysisDetailSchema });
/** Offline binary diagnostics are independent of macOS process/UI providers. */
export const BINARY_DIAGNOSTICS_TOOL_CONTRACTS = [
{
name: "inspect_devicetree",
...toolContractMetadata("inspect_devicetree"),
kind: "native-provider",
description:
"Inspect an explicit local flattened devicetree blob (FDT v16/v17) without execution or external tools. Returns validated header, memory reservations, full node/property tree with bounded raw hex, derived standard strings, cells and parent-sized reg tuples, aliases and selected single-cell phandle references with unresolved targets. Binding-specific arrays, overlays and runtime hardware remain unknown. Malformed structure or exhausted named budgets fails without partial success. Complete detail returns all Evidence inline; summary retains it for inspect_analysis_view kind devicetree, collections nodes, properties (requires exact path), reservations and references, with offset/limit pagination.",
inputSchema: inspectDevicetreeInputSchema.extend({
detail: analysisDetailSchema,
}),
outputSchema: analysisDetailOutputSchemaOf(
devicetreeSchema,
devicetreeSummaryViewResultSchema,
),
examples: [
{
title: "Inspect a firmware hardware description",
input: { path: "/artifacts/board.dtb", detail: "summary" },
},
],
},
{
name: "inspect_pe_load_config",
...toolContractMetadata("inspect_pe_load_config"),
kind: "native-provider",
description:
"Inspect PE32/PE32+ TLS callbacks and sized load configuration without executing the image. Returns callback VA/RVA/file offsets and sections, security cookie, SafeSEH (PE32), CFG flags and table pointers/counts, EH continuation and XFG fields. Flag-derived mitigation claims are labeled inferred. Absent directories are null; malformed structures and exhausted budgets fail without partial success. Reads a stable local regular file without a disassembler.",
inputSchema: inspectPeLoadConfigInputSchema,
outputSchema: evidenceResultOf(peLoadConfigSchema),
examples: [
{
title: "Inspect PE TLS callbacks and load configuration",
input: { path: "/artifacts/program" },
},
],
},
{
name: "inspect_binary_hardening",
...toolContractMetadata("inspect_binary_hardening"),
kind: "native-provider",
description:
"Inspect compile-time and link-time hardening of one explicit local ELF, PE, or Mach-O file, including one image per universal Mach-O slice. Reports PIE or ASLR, NX, RELRO, stack-protector signs, FORTIFY signs, control-flow flags and stripped state, each with the header field or import behind it. States are enabled, disabled, partial, unknown, or not_applicable. A missing import in a static or stripped image is unknown, not disabled. Authenticode and Mach-O code-signature rows report table or load-command presence, not validity. Mach-O hardened runtime stays unknown and names inspect_signature. This inspection states that it does not execute the image and does not assess exploitability. Reads a stable regular file. A malformed header returns no partial report, except one bad slice in a universal Mach-O. A symbol table, symbol name, or GNU property note over its budget leaves those rows unknown and keeps the header rows. A dynamic-table budget still fails the ELF report.",
inputSchema: inspectBinaryHardeningInputSchema,
outputSchema: evidenceResultOf(binaryHardeningSchema),
examples: [
{
title: "Inspect binary hardening",
input: { path: "/artifacts/program" },
},
],
},
{
name: "inspect_elf_notes",
...toolContractMetadata("inspect_elf_notes"),
kind: "native-provider",
description:
"Inspect one explicit local ELF32/ELF64 image's PT_NOTE program headers and SHT_NOTE sections without executing it or opening a disassembler. Returns note-table file ranges, owners, types, descriptor SHA-256 and hex previews, plus GNU build-id, ABI-tag and gold-version fields and Linux CORE process, mapping, signal, and auxv notes when those descriptors match. A missing table is observed absence of those declarations, not proof the image never carried notes in another container. Reads a stable regular file; malformed ELF headers, truncated notes and exhausted budgets return no partial success. Portable on supported Node hosts; no debug package or catalog is required.",
inputSchema: inspectElfNotesInputSchema,
outputSchema: evidenceResultOf(elfNotesSchema),
examples: [
{
title: "Inspect ELF GNU build-id and ABI-tag notes",
input: { path: "/artifacts/program" },
},
],
},
{
name: "inspect_apple_ips",
...toolContractMetadata("inspect_apple_ips"),
kind: "native-provider",
description:
"Inspect one explicit local Apple .ips crash report without executing a target, launching atos, or opening a disassembler. Parses the first-line metadata object and, when bug_type is the string 309, the crash body: process identity, exception and termination facts, threads, usedImages, lossless integer registers, and deterministic classification from documented SIGKILL, EXC_GUARD, pointer-authentication and dyld strings. Other bug_type values, including 288 stackshots, return no crash result. A missing or truncated body fails without partial success. With join_path, every frame whose image UUID and architecture match a slice LC_UUID reports the slice static __TEXT vmaddr plus its image offset as a static address; symbolication and dSYM symbol lookup remain unobserved. Portable on supported Node hosts; no Xcode or pwntools required.",
inputSchema: inspectAppleIpsInputSchema,
outputSchema: evidenceResultOf(appleIpsSchema),
examples: [
{
title: "Inspect an Apple .ips crash report",
input: { path: "/artifacts/app.ips" },
},
{
title: "Join report frames to the crashed Mach-O",
input: {
path: "/artifacts/app.ips",
join_path: "/artifacts/App.app/Contents/MacOS/App",
},
},
],
},
{
name: "inspect_kernel_module",
...toolContractMetadata("inspect_kernel_module"),
kind: "native-provider",
description:
"Inspect one explicit local ELF32/ELF64 ET_REL Linux kernel module without executing it or opening a disassembler. Returns ordered .modinfo key/value entries (duplicates preserved), __versions symbol CRCs, presence of .gnu.linkonce.this_module and __ksymtab sections, observed .init.text and .exit.text sizes, and an appended module signature trailer with its module_signature fields plus a PKCS#7 byte range and SHA-256. Does not execute the module, decode struct module, or validate certificates. A missing section is observed absence when section names are known, not proof the module never carried that metadata. Other ELF types are not applicable. gzip, xz, and zstd containers are unsupported and name external_decompression as the next probe; neither this tool nor extract_artifact decodes those codecs, so decompress with an external utility and inspect the result. Reads a stable regular file; malformed ELF headers and exhausted entry budgets return no partial success. Portable on supported Node hosts.",
inputSchema: inspectKernelModuleInputSchema,
outputSchema: evidenceResultOf(kernelModuleSchema),
examples: [
{
title:
"Inspect Linux kernel module license, vermagic, and dependencies",
input: { path: "/artifacts/demo.ko" },
},
],
},
{
name: "inspect_kernel_build_config",
...toolContractMetadata("inspect_kernel_build_config"),
kind: "native-provider",
description:
"Inspect one explicit local Linux kernel image (vmlinux ELF, raw arm64 Image, x86 bzImage/zImage, or configs.ko) without executing it or opening a disassembler. Returns the `Linux version` banner split into version, builder, and compiler, and the embedded CONFIG_IKCONFIG build config: option count, SHA-256 of the inflated text, and paged CONFIG_* entries with value kind (y, m, string, number, not_set), optionally filtered by exact name. A config is accepted only when IKCFG_ST is immediately followed by a gzip member that inflates to config text and is immediately followed by IKCFG_ED, so an ordinary IKCFG_ST string is not a config. A bzImage gzip payload located through the setup header is inflated with a bounded output budget and searched; xz, lz4, lzma, zstd, bzip2, and lzo payloads are named as unsupported with external_decompression as the next probe. Truncated payloads and gzip members are incomplete, not absent. Reads a stable regular file; exhausted budgets return no partial success. Portable on supported Node hosts.",
inputSchema: inspectKernelBuildConfigInputSchema,
outputSchema: evidenceResultOf(kernelBuildConfigSchema),
examples: [
{
title: "Read a kernel image's version banner and embedded build config",
input: { path: "/artifacts/vmlinux" },
},
{
title: "Check one security option in an embedded kernel config",
input: {
path: "/artifacts/bzImage",
name: "CONFIG_SECURITY_SELINUX",
},
},
],
},
{
name: "inspect_byte_entropy",
...toolContractMetadata("inspect_byte_entropy"),
kind: "native-provider",
description:
"Inspect Shannon byte entropy for one explicit local file offline, without execution, extraction or external tools. Returns file-wide SHA-256 and entropy, fixed-block entropy for an optional offset/length window, and adjacent high (>=7.2) and low (<=1.0) runs with exact file offsets. Block size is a power of two from 256 to 65536 (default 4096). Complete output can become very large because it includes one row per selected block, and summary detail still retains that full list. Use a shorter length or a larger block_size; the selected window is limited to 32,768 output blocks. Entropy cannot distinguish compression from encryption. Summary detail returns file entropy plus the ten longest runs; retrieve parent Evidence for the full series. The entire file is read even for a window.",
inputSchema: inspectByteEntropyRequestSchema,
outputSchema: byteEntropyOutputSchema,
examples: [
{
title: "Profile firmware bytes before extraction",
input: { path: "/artifacts/firmware.bin" },
},
{
title: "Summarize a selected byte window",
input: {
path: "/artifacts/firmware.bin",
offset: 4096,
length: 65536,
block_size: 4096,
detail: "summary",
},
},
],
},
{
name: "inspect_pe_certificates",
...toolContractMetadata("inspect_pe_certificates"),
kind: "native-provider",
description:
"Inspect one explicit local PE32/PE32+ image's IMAGE_DIRECTORY_ENTRY_SECURITY table without executing it, verifying signatures or opening a disassembler. Returns WIN_CERTIFICATE file ranges, types and payload SHA-256, plus structurally decoded PKCS#7 signedData digest algorithms, embedded X.509 serial/issuer/subject/validity fields and signer identifiers, including a subject key identifier, when type 2 is present. A missing directory is observed absence of that table, not proof the image is unsigned in every catalog or overlay sense. Authenticode digest coverage, chain validity and timestamp trust remain unobserved. Reads a stable regular file; malformed PE headers, truncated WIN_CERTIFICATE entries, X.509 certificates missing subjectPublicKeyInfo or signatureValue, and non-signedData PKCS#7 return no partial success. Portable on supported Node hosts; no Windows SDK or engine installation required.",
inputSchema: inspectPeCertificatesInputSchema,
outputSchema: evidenceResultOf(peCertificatesSchema),
examples: [
{
title: "Inspect PE Authenticode certificate identities",
input: { path: "/artifacts/application.exe" },
},
],
},
{
name: "inspect_pe_imports",
...toolContractMetadata("inspect_pe_imports"),
kind: "native-provider",
description:
"Inspect one explicit local PE32/PE32+ image's IMAGE_DIRECTORY_ENTRY_IMPORT and delay-load tables without executing it or opening a disassembler. Returns DLL names, named and ordinal functions, ILT/IAT RVAs, delay-load descriptors, and the pefile import hash of the standard import table, withheld with a reason when that table is partial or pefile would hash a different row sequence. A missing import directory is observed absence of that table, not proof the image never imported APIs through another table or overlay. Bound snapshots, loaded IAT contents and export-forward resolution remain unobserved. Reads a stable regular file; malformed PE headers and exhausted DLL or function budgets return no partial success. Unreadable names or thunks and descriptors missing a required table are reported as partial coverage with issues. Portable on supported Node hosts; no Windows SDK or engine installation required.",
inputSchema: inspectPeImportsInputSchema,
outputSchema: evidenceResultOf(peImportsSchema),
examples: [
{
title: "Inspect PE import and delay-load identities",
input: { path: "/artifacts/application.exe" },
},
],
},
{
name: "inspect_windows_shortcut",
...toolContractMetadata("inspect_windows_shortcut"),
kind: "native-provider",
description:
"Inspect one explicit local Windows Shell Link (.lnk) file without resolving or executing the target, using COM, or opening a network path. Returns ShellLinkHeader flags, FILETIMEs, ShowCommand and hotkey; LinkTargetIDList item count with documented root/drive/file-entry names and raw hashes for unknown items; LinkInfo volume serial, label, local base path and network share names; StringData name, relative path, working directory, arguments and icon location; and ExtraData blocks including EnvironmentVariable, IconEnvironment, Tracker droid/MAC facts labelled derived, KnownFolder, SpecialFolder, ConsoleFE code page, and PropertyStore raw hashes. Unknown ExtraData signatures are listed. Derived observations cover argument presence and length, target extension, minimized ShowCommand, and an icon location that differs from the target. Reads a stable regular file; malformed HeaderSize or CLSID, exhausted budgets, and odd Unicode lengths return typed failures or partial coverage with the failing offset, never a complete partial inventory. Portable on supported Node hosts; no Windows SDK or engine installation required.",
inputSchema: inspectWindowsShortcutInputSchema,
outputSchema: evidenceResultOf(windowsShortcutSchema),
examples: [
{
title: "Inspect a Windows Shell Link shortcut",
input: { path: "/artifacts/shortcut.lnk" },
},
],
},
{
name: "inspect_pe_resources",
...toolContractMetadata("inspect_pe_resources"),
kind: "native-provider",
description:
"Inspect one explicit local PE32/PE32+ image's complete bounded type/name/language resource tree without executing it or opening a disassembler. Returns numeric IDs versus original UTF-16 names, directory identity paths including empty branches, directory/data-entry file locations, payload RVA/file ranges, complete SHA-256 hashes and static RT_GROUP_ICON image-resource candidates. Language fallback, image decoding and actual shell/window loading remain unobserved. Reads a stable regular file; malformed or ambiguous mappings, non-identical overlapping ranges and exhausted budgets return no partial success. Portable on supported Node hosts; no Windows SDK or engine installation required.",
inputSchema: inspectPeResourcesInputSchema,
outputSchema: evidenceResultOf(peResourcesSchema),
examples: [
{
title: "Inspect PE resource identities and icon relationships",
input: { path: "/artifacts/application.exe" },
},
],
},
{
name: "inspect_native_exports",
...toolContractMetadata("inspect_native_exports"),
kind: "native-provider",
description:
"Inspect one explicit local PE or ELF image's export table without executing it or opening an engine: PE export-directory names, ordinals, addresses and forwarders, or ELF defined default/protected global, weak and unique .dynsym symbols with type, binding and symbol version. Ordinal-only, forwarded and hidden-version exports stay distinct. Portable on any supported host. Coverage is partial when table entries are unreadable and unsupported when ELF dynamic symbols cannot be enumerated from the available section metadata; an empty complete inventory means the image declares no exports. Mach-O exports use inspect_macho. Omitted/complete detail preserves inline delivery. Select detail summary to retain complete Evidence and return identity, counts and source coverage. Query inspect_analysis_view kind native-exports, collection exports or issues, with exact names/original indices, fields and matching-row pagination. Names return every version or alias; source_index is scoped to the parent Evidence. ELF name projections keep raw-byte and encoding qualifications.",
inputSchema: inspectNativeExportsRequestSchema,
outputSchema: analysisDetailOutputSchemaOf(
nativeExportsSchema,
nativeExportsSummaryViewResultSchema,
),
examples: [
{
title: "List a native library's exports",
input: { path: "/artifacts/native/libexample.so" },
},
],
},
{
name: "inspect_binary_layout",
...toolContractMetadata("inspect_binary_layout"),
kind: "native-provider",
description:
"Inspect an explicit local binary without launching it as a host process. Returns file-backed section/segment ranges, linked addresses, original symbol/REL/RELA table identities, complete encoded RELR tables and upstream-derived offsets with unknown per-word locations/addends, raw name bytes, dependency/interpreter names and static mitigation inferences inline with artifact SHA-256 Evidence. Profile: EXEC/DYN/REL ELF64 x86-64, ELF64 little-endian AArch64, ELF32 i386, ELF32 little-endian ARM and ELF32 little- or big-endian MIPS on Linux x64 via caller-supplied Python with unchanged pwntools 4.15.0, pyelftools 0.33 and Unicorn 2.1.2. Runtime addresses/library paths, overall relocation inventory completeness and complete derived GOT/PLT coverage remain unknown. Zero executable entry values mean absence; relocatable entries are not applicable. Upstream can emulate PLT instructions with Unicorn for derived static maps. Uses bounded complete output and owned temporary files/processes; malformed, unsupported or oversized objects return no partial success. Select detail summary to retain the complete layout Evidence in this session and receive only its summary view, then inspect sections, symbols, mitigations or linkage with inspect_analysis_view. pwntools_timeout_ms is each owned pwntools command's deadline (omitted means 30000 ms, range 1..2147483647, next call). cpuSeconds 30 and the output, diagnostic, and address-space byte allowances have no runtime override.",
inputSchema: inspectBinaryLayoutRequestSchema,
outputSchema: analysisDetailOutputSchemaOf(
binaryLayoutSchema,
binaryLayoutSummaryViewResultSchema,
),
examples: [
{
title: "Inspect ELF linked and file layout",
input: { path: "/artifacts/application.elf" },
},
{
title: "Retain the complete layout and return its summary view",
input: { path: "/artifacts/application.elf", detail: "summary" },
},
],
},
{
name: "inspect_recorded_crash",
...toolContractMetadata("inspect_recorded_crash"),
kind: "native-provider",
description:
"Inspect a supplied Linux ELF64 amd64 or AArch64 little-endian core without launching its target or attaching to any live PID. Returns all recorded segments, raw note owners/descriptors and source ranges, every Linux CORE PRSTATUS thread/register and SIGINFO fields inline with SHA-256 Evidence. Historical PID is recorded metadata; current process/file identity, note coverage and signal-thread associations stay unknown. Optionally add derived core-only GDB/pwndbg mapping candidates through explicit BYO configuration; zero map flags mean unknown permissions. Uses the same stable snapshot for both stages, bounded complete output and owned cleanup. Linux x64 host profile: unchanged pwntools 4.15.0/pyelftools 0.33/Unicorn 2.1.2; optional unchanged pwndbg 2026.09.15. No implicit executable/library pairing, GDB command language, host configuration changes or dependency installation. pwntools_timeout_ms is each owned pwntools command's deadline (omitted means 30000 ms, range 1..2147483647, next call). cpuSeconds 30 and the output, diagnostic, and address-space byte allowances have no runtime override.",
inputSchema: inspectRecordedCrashInputSchema,
outputSchema: evidenceResultOf(recordedCrashSchema),
examples: [
{
title: "Inspect recorded registers and signal evidence",
input: { path: "/artifacts/crash.core" },
},
{
title: "Add core-only debugger mapping candidates",
input: {
path: "/artifacts/crash.core",
include_debugger_context: true,
},
},
],
},
] as const satisfies readonly ToolContract[];