Commit Graph
36 Commits
Author SHA1 Message Date
morluto 2721650884 fix(native): bind signing facts and preserve native output semantics 2026-10-24 02:45:14 +08:00
morluto aa357d6d91 refactor(macho): separate dyld import and export parsing
Keep mode-specific inputs and parsed row types at their respective parser boundaries.
2026-10-23 19:26:17 +08:00
morluto 5d0d243a45 fix(macho)!: preserve ambiguous chained import names
Keep the exact producer expression and report unknown symbol identity when chained-import text cannot distinguish a binding addend from a literal name suffix.

Fixes #6648

BREAKING CHANGE: inspect_macho import names can be null when identity is ambiguous. Use raw_name for the observed expression and name_status to distinguish it from an exact symbol name.
2026-10-23 19:25:29 +08:00
HawkForge 2425493e10 fix(native): keep export names ending in a carriage return (#3457)
nm -j and dyld_info -exports end each row with LF. Splitting on \r?\n and
trimming rows treated a carriage return at the end of a symbol name as part
of the line terminator, so the distinct exports "trailing\r" and
"trailing" merged into one entry with the wrong address. Split rows on LF
only and keep trailing text in export rows.

Fixes #3390
2026-10-23 02:33:56 +08:00
23196daa11 fix(apple): read LC_LAZY_LOAD_DYLIB_INFO dependency payloads (#5650)
* fix(apple): read LC_LAZY_LOAD_DYLIB_INFO dependency payloads

Parse the linkedit install name for lazy-load info commands, resolve those
edges without traversing them, and stop inspect-macho from treating an otool
dependency list as exhaustive when that payload is omitted.

Fixes #5551
Fixes #5649

Co-authored-by: morluto <morluto@users.noreply.github.com>

* fix(apple): bound lazy-load payload reads and image offsets

Reuse identical LC_LAZY_LOAD_DYLIB_INFO ranges, cap their combined size,
and reject flag or chain-start offsets outside the slice VM range.

Co-authored-by: morluto <morluto@users.noreply.github.com>

* fix(apple): bound lazy-load string scans and contained reads

Reject payload strings that start in the 24-byte header, validate each
file-offset string once, and reuse payload bytes already read for a
containing range.

Co-authored-by: morluto <morluto@users.noreply.github.com>

* fix(apple): reject cached lazy-load strings outside a payload

Remember each cached string's terminating offset and require that NUL to
fall inside the payload being decoded.

Co-authored-by: morluto <morluto@users.noreply.github.com>

* fix(apple): bound lazy-load payload range lookups

Count containment checks across earlier payload reads and stop at the
existing 16 MiB work limit.

Co-authored-by: morluto <morluto@users.noreply.github.com>

* fix(apple): count every lazy-info string reference

Charge repeated and overlapping LC_LAZY_LOAD_DYLIB_INFO scans against the
16 MiB validation budget, and stop a scan once that budget is exhausted.

Co-authored-by: morluto <morluto@users.noreply.github.com>

* fix(apple): cap retained lazy-info dependency results

Stop after 1024 LC_LAZY_LOAD_DYLIB_INFO commands so a load-command region
full of identical commands cannot become a million traced edges.

Co-authored-by: morluto <morluto@users.noreply.github.com>

* fix(apple): reject a lazy-load path NUL in the final byte

dyld stops the load-path scan before the payload's last byte, so a
terminator in that byte leaves the image malformed.

Co-authored-by: morluto <morluto@users.noreply.github.com>

* fix(apple): keep may-be-missing lazy loads optional

A missing LC_LAZY_LOAD_DYLIB_INFO with the may-be-missing bit is weak, so the lazy finding says dyld continues on first use instead of failing.

Co-authored-by: morluto <morluto@users.noreply.github.com>

* fix(apple): cap lazy-info symbol string references

A payload of distinct one-byte symbol strings stayed under the byte budgets while caching one entry per offset. A slice now stops at 65536 symbol references.

Co-authored-by: morluto <morluto@users.noreply.github.com>

---------

Co-authored-by: Cursor Agent <cursoragent@cursor.com>
Co-authored-by: morluto <morluto@users.noreply.github.com>
2026-10-21 03:33:07 +08:00
morluto 2b263a7fe9 fix(native): retain Mach-O facts when dyld rejects missing UUID
Admit only the corroborated missing-UUID validation diagnostic from header-stripped dyld stdout. Preserve readable load commands and nm exports with unknown dyld coverage, and keep unrelated process and tool failures typed.

Closes #5606
2026-10-21 01:12:51 +08:00
morluto cf3288d8cf fix(native): preserve Mach-O container framing across inspection 2026-10-20 17:16:44 +08:00
morlutoandHenglie 2f6b9ecdc6 fix(native): preserve slice identity when selecting UUID rows
Carry the selected slice name and the existing tool owner's dwarfdump spelling separately. Compare observed row labels exactly, preserving explicit ABI versions and uncertainty for missing rows.

Correct the production-boundary fixture and retain wrong-version and mixed-row controls. Real Apple multi-version selection behavior remains unverified.

Refs: #4195, #4288

Co-authored-by: Henglie <ebhenglie@gmail.com>
2026-10-16 08:26:18 +08:00
Kaoru0822andClaude Opus 5.5 2b2a5d0f26 fix(native): inspect arm64e.x1 slices by their exact name (#3500)
inspect_macho passed the CPU-family spelling (`arm64e`) for every dotted lipo
slice name to otool, nm, dwarfdump and vtool. That is right for the
pointer-auth ABI version suffix (`arm64e.v1`), but `arm64e.x1` is a distinct
subtype: on a universal x86_64 + arm64e.x1 executable otool failed with
"does not contain architecture: arm64e" and the whole inspection failed.

Strip only the `.v<N>` ABI version suffix and pass other variant names
exactly. vtool has no spelling for a variant subtype, so skip it for such
slices and report that build metadata comes from otool only.

Fixes #3389

Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-14 17:10:58 -07:00
Kaoru0822andClaude Opus 5.5 e5791229f3 perf(native): skip llvm-nm's dyld-info pass when the symbol table names every export (#3200)
* perf(native): skip llvm-nm's dyld-info pass when the symbol table names every export

inspect_macho ran `nm -gjU`, whose default dyld-info pass takes seconds on
large apps (Keynote arm64 5.8 s, x86_64 10.2 s) yet under -gU can only add
export-trie names that dyld_info -exports already reports. Run
`nm -gjU --no-dyldinfo` and rerun the default pass only when dyld_info
reports an export the symbol table lacks (e.g. strip -T), so its exact
names still disambiguate dyld_info rows.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>

* fix(native): fall back to nm's dyld-info pass for unreadable export rows

Account for every dyld_info export line instead of only the rows the
parser returns, so an unfamiliar row format reruns the default nm pass.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>

---------

Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-13 23:21:03 -07:00
Kaoru0822andClaude Opus 5.5 3f5b703379 fix(native): derive Mach-O word size and byte order from the selected slice (#3163)
* fix(native): derive Mach-O word size and byte order from the selected slice

inspect_macho read endian and word_size from `file -b` for the whole
file. macOS `file` never prints a Mach-O byte order, so endian was null
for every target. For a universal binary, word_size matched "64-bit"
anywhere in the summary instead of describing the selected slice, and it
was null for a thin 32-bit slice.

Both now come from the selected slice's otool header: the magic gives
the word size and the CPU family gives the byte order (x86 and ARM
little, PowerPC big, other families unknown). The file capture is no
longer needed by inspect_macho, so it is no longer run there.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>

* test(native): select a 32-bit slice beside a 64-bit slice through inspect_macho

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>

---------

Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-13 22:44:41 -07:00
1d5f95dc4a fix(native): parse renamed dyld_info re-exports (#2364)
* fix(native): parse renamed dyld_info re-exports

dyld_info prints a re-export whose name differs from the source library's
symbol as `[re-export] _foo_renamed (_foo from libfoo)`. The export parser
only accepted `(from libfoo)`, so inspect_macho reported a nonexistent
export named `_foo_renamed (_foo from libfoo)` with no source, and the nm
row for `_foo_renamed` survived the name-based merge as a second export
with reexport null.

Accept the optional source symbol, trying the plain `(from lib)` form
first so a library name containing ` from ` keeps its text. The row now
reports `_foo_renamed` re-exported from libfoo and replaces the nm
duplicate. The export schema has no field for the source symbol name, so
exports coverage records a limitation when a renamed re-export is present.
The golden fixture is a dyld_info capture of a synthetic dylib linked with
-Wl,-alias,_foo,_foo_renamed.

* fix(native): split dyld re-export suffix without nested quantifiers

Replace the (?:(.+?)\s+)??from lazy-optional group with a linear string
scan so malformed lines cannot backtrack and a library path containing
" from " still keeps its whole text via the plain-form-first check.

* test(native): cover nested re-export library names

* fix(native): retain reexports missing from nm inventory

---------

Co-authored-by: morluto <76467478+morluto@users.noreply.github.com>
Co-authored-by: morluto <17176810+morluto@users.noreply.github.com>
2026-10-14 01:06:55 +08:00
FengCi 59d33a2733 fix(native): keep lipo path echoes out of FAT records (#2605)
* fix(native): keep lipo path echoes out of FAT records

Parse FAT records only after separating the exact echoed operand path. Pass the same lipo operand at all three consumers and cover multiline paths through parser, slice-reader and compiled CLI regressions.

* fix(native): use parsed lipo framing for container offsets

* test(native): use explicit FAT fixtures for CLI regressions
2026-10-13 21:16:49 +08:00
morluto c02fb0ea80 fix(native): rank Mach-O slices by parsed CPU subtype 2026-10-13 16:43:12 +08:00
96fc84f1fb fix(native): select one slice when universal Mach-O slices share a CPU type (#2040)
* fix(native): select one slice when universal Mach-O slices share a CPU type

On macOS 27 most system binaries are universal files whose slices share
CPU_TYPE_ARM64: arm64e + arm64e.x1 (Calculator, TextEdit), x86_64 + arm64e +
arm64e.x1 (/bin/ls), or arm64 + arm64e.x1 (ShortcutDroplet). Target admission
names slices by CPU family, so these targets have availableArchitectures
["arm64"] while the file holds two arm64 slices.

inspect_macho read that family count as a slice count and ran otool, nm,
dyld_info, dwarfdump and vtool without -arch. ShortcutDroplet failed with
"Malformed otool load-command output" (40 load commands against ncmds 20).
Calculator reported both slices' imports (4138 instead of 2069), nm's
"(for architecture ...)" header lines as exports, and no selected slice.
selectArchitecture now also filters when lipo lists more than one slice. Its
preference is unchanged: the family name, then arm64e, then the first
arm64e.<variant>.

inspect_native_dispatch_metadata matched FAT slices by CPU type only and
refused /bin/ls and TextEdit as "Ambiguous FAT architecture slice".
selectMachoSlice now ranks slices of the requested CPU type by base subtype in
the same order: arm64 (0, then 1) or x86_64 (3), then arm64e (2), then other
variants such as arm64e.x1 in FAT table order. It reports ambiguity only when
the top-ranked slices share a subtype.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>

* fix(native): share Mach-O slice preference across inspections

* chore: preserve upstream process capture test during merge

---------

Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>
Co-authored-by: morluto <76467478+morluto@users.noreply.github.com>
2026-10-13 16:36:23 +08:00
Kaoru0822andClaude Opus 5.5 2c7240cf9d perf(native): run independent Mach-O slice commands concurrently (#2466)
inspect_macho launched otool, nm, two dyld_info reads, dwarfdump and
vtool one at a time. Each launch also resolves the tool through
`xcrun --find`, so a warm call spent about 350 ms in eight serial
supervised launches. The slice commands only read the selected
artifact, so launch them together. Every launch still settles before
the result, and declared order still fixes provenance and which
failure is reported.

Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-12 21:12:05 -07:00
morluto 6fb3a345fe fix(analysis): preserve producer identity and partial evidence
Reconcile Mach-O UUID observations without hiding malformed or conflicting sources. Require source-map identity digests to be supported by consistent observations. Keep truncated Android manifests as partial raw evidence, validate WABT section framing, and reject missing JEB inventories or error-marked success replies.

Consolidate native UUID parsing and simplify capacity and malformed-input fixtures. Focused source and public workflow checks pass, including native arm64e inspection and real WABT framing probes; a successful real JEB GUI project opening remains unverified.
2026-10-12 12:37:00 +08:00
morluto 8bd5ba2d23 fix(native): clarify Mach-O offset origins and reject partial ELF tables
Expose the selected Mach-O slice and its container offset, derive that identity once, and map entrypoint and nonempty segment Evidence to container coordinates. Reject relocation sections whose byte length is not divisible by the entry size instead of silently accepting a floored count.

Validation: native parser and selected-architecture boundaries, actual public CLI and stdio MCP on a compiled universal Mach-O, and a pyelftools 0.33 malformed-table probe. Full Linux pwntools bridge verification was unavailable on this host.

Refs #1973
2026-10-12 06:29:19 +08:00
morluto 108b5f9425 fix(native): reject ambiguous otool load-command framing
Validate Mach command counts, byte totals, scalar uniqueness, section counts, and build-tool counts before claiming exhaustive evidence. Preserve raw output in typed parsing failures and distinguish dylib commands from the dynamic loader. Correct inconsistent handcrafted fixtures and label their provenance.

Validation: 38 native tests, combined checks, and temporary-directory hygiene. Multiline dylib-name ambiguity was reproduced with the host Apple otool; regression workflows use captured output and injected command runners.
2026-10-12 05:24:29 +08:00
Itoje Dollar e4ac7bbfc2 fix: order remaining outputs independently of host locale (#1867)
* fix: order remaining outputs independently of host locale

String.prototype.localeCompare without a pinned collation follows the
host's ICU default locale, so the same input could be ordered differently
on different machines. 614fb768 moved canonical domain projections to
compareUnicodeCodePoints; provider, application, catalog, and setup
orderings still used locale collation.

Order every remaining source site by Unicode code point, including Mach-O
symbols, firmware extraction paths, ASAR entries (previously pinned to
"en" collation), evidence ID sets, browser and Electron target, script,
and resource inventories, reconstruction obligations, capability and
provider listings, and setup environment details.

The current tool and prompt catalog names order identically under both
comparators, so the catalog identity digest is unchanged.

* fix(artifacts): order ASAR entries by their portable path

@electron/asar lists host-spelled paths, so code-point order over the raw
spelling still differed by host: "a\b" sorts after "a0" on Windows while
"a/b" sorts before it on POSIX. Pair each listing with its portable
archive path once, order by that path, and keep the host spelling for
@electron/asar calls and adapter keys.
2026-10-12 01:10:01 +08:00
morlutoandCursor Agent b4db35f092 fix: bind identity and coverage to the producing rule (#1513)
* fix: bind identity and coverage to the producing rule

Logical paths, container families, examined bytes, path-segment conventions, destination collisions, file-id leases, Mach-O CPU selection, and firmware cleanup now follow the observation that owns each rule.

* fix: fail unmatched Mach-O slices and keep firmware roots independent

A fat target whose architecture lipo did not list now stops before slice tools run. Chained fixups still count unread LC_DYLD_INFO rebase bytes, and a firmware cleanup failure no longer blocks the next workspace.

* test: cover path identity and runtime families at the projection boundary

NFC joins and segment conventions are now asserted by the path resolver, the Apple projection, and a real APK inventory instead of helper unit tests.

* test: expect unread dyld rebase bytes in the Apple dispatch fixture

The legacy LC_DYLD_INFO fixture keeps a rebase range this reader does not decode, so the public CLI and MCP check now requires that partial coverage beside the bind opcodes.

---------

Co-authored-by: Cursor Agent <cursoragent@cursor.com>
2026-10-10 17:14:47 +08:00
Kaoru0822andClaude Opus 5.5 6375ccaf04 perf(native): record macOS command provenance once in its Evidence (#1430)
inspect_macho, inspect_signature, inspect_plist, list_architectures and
demangle_swift already return their command provenance inside the
normalized result. The provider also copied the same array into
raw_result, so every response carried it twice. raw_result now stays
null, as docs/mcp-contracts.md describes for results without a distinct
provider representation.

Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-09 21:35:50 -07:00
morluto ad59531a96 refactor: remove forwarding exports and redundant aliases 2026-10-09 22:46:09 +08:00
Ysopwnable ea4db4c9f7 refactor(native): group analyst semantics workflows and contracts (#973) 2026-10-07 22:40:48 +08:00
Iwakurakaoru0822andClaude Opus 5.5 37fe105ba1 fix(native): keep echoed Mach-O paths out of load commands and symbols
otool and dyld_info print the operand path before a slice's records,
and the Mach-O parsers read that header as output. A file name holding
`Load command` blocks or `(from lib)` lines therefore added a forged
LC_MAIN entrypoint, corrupted the Mach header fields, and injected
imports and exports into `inspect_macho`.

Strip the exact echoed operand header (`<path>:`, `<path> (architecture
<arch>):`, `<path> [<arch>]:`) before parsing. Output without that
header, such as replayed captures, is parsed unchanged.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-06 23:40:35 -07:00
3af3811ab0 fix: integrate 24 reviewed browser/native/semantics corrections (#815)
* fix(reference): resolve emitted extensions to TypeScript source

Signed-off-by: Rudy Celekli <rudy@gradiahq.com>

* test(reference): retain legitimate declaration import counterparts

Signed-off-by: Rudy Celekli <rudy@gradiahq.com>

* fix(mcp): recognize implicit form elicitation capability

Signed-off-by: RudyCelekli <47457359+rudycelekli@users.noreply.github.com>

* fix(ghidra): reject contradictory NativeAOT reports

* fix(native): preserve thin architectures across echoed filename lines

Signed-off-by: Rudy Celekli <47457359+rudycelekli@users.noreply.github.com>

* fix(native): preserve app bundle executable filename whitespace

Signed-off-by: Rudy Celekli <47457359+rudycelekli@users.noreply.github.com>

* fix(doctor): validate Node-wrapped client policy

Signed-off-by: RudyCelekli <47457359+rudycelekli@users.noreply.github.com>

* fix(reference): preserve observed entry failure reasons

Signed-off-by: Rudy Celekli <rudy@gradiahq.com>

* fix(artifacts): verify opaque bytes before reusing inventory identities

Signed-off-by: Rudy Celekli <rudy@gradiahq.com>

* fix(browser): resolve DOM destinations against document base URL

Signed-off-by: Rudy Celekli <rudy@gradiahq.com>

* fix: recover serialized native NIB view hierarchy

Signed-off-by: RudyCelekli <47457359+rudycelekli@users.noreply.github.com>

* ci: exercise native compiled NIB hierarchy regressions

Signed-off-by: RudyCelekli <47457359+rudycelekli@users.noreply.github.com>

* fix(deps): remediate reachable archive-parsing advisories

Two high-severity advisories are reachable from untrusted target content:

- @xmldom/xmldom 0.9.10 (via plist) parses XML plist bytes from analyzed
  app bundles in the keyed-archive reader. Refreshed to 0.9.12.
- brace-expansion 5.0.7 (via @electron/asar -> minimatch) is reached while
  analyzing untrusted ASAR archives. Refreshed to 5.0.12.

Both are in-range patch updates (plist declares ^0.9.10, minimatch declares
^5.0.5), so no source or API change is required and no direct dependency
declaration moves. Lockfile-only change.

The remaining smol-toml advisory is not included: it parses operator-owned
client configuration rather than untrusted target content, and its fix moves
outside the declared range and changes leading-BOM handling.

* fix(semantics): link wrapped arrow Promise returns

* fix(reconstruction): preserve unknown graph limitations

* fix(native): preserve exact code signing identifiers

Signed-off-by: Rudy Celekli <47457359+rudycelekli@users.noreply.github.com>

* fix(native): recognize only whole otool command headers

Signed-off-by: Rudy Celekli <47457359+rudycelekli@users.noreply.github.com>

* fix(native): retain nm-proven literal export names

Signed-off-by: Rudy Celekli <47457359+rudycelekli@users.noreply.github.com>

* fix(javascript): project decoded file URL paths

Signed-off-by: Rudy Celekli <47457359+rudycelekli@users.noreply.github.com>

* fix: preserve explicit source-relative URL observation spelling

Signed-off-by: Rudy Celekli <47457359+rudycelekli@users.noreply.github.com>

* fix(native): project Mach-O evidence into container file offsets

Signed-off-by: Rudy Celekli <47457359+rudycelekli@users.noreply.github.com>

* fix(browser): normalize referrer policy fallback lists

Signed-off-by: Rudy Celekli <47457359+rudycelekli@users.noreply.github.com>

---------

Signed-off-by: Rudy Celekli <rudy@gradiahq.com>
Signed-off-by: RudyCelekli <47457359+rudycelekli@users.noreply.github.com>
Signed-off-by: Rudy Celekli <47457359+rudycelekli@users.noreply.github.com>
Co-authored-by: Rudy Celekli <rudy@gradiahq.com>
Co-authored-by: RudyCelekli <47457359+rudycelekli@users.noreply.github.com>
Co-authored-by: Kaoru0822 <aisakamimaki@gmail.com>
Co-authored-by: DeryFerd <deryferdikao125@gmail.com>
2026-10-07 10:33:10 +08:00
Kaoru0822 fd3282a87e fix(native): select concrete arm64e Mach-O slices 2026-10-06 01:25:11 -07:00
morluto 735c10059c refactor(domain): split error taxonomy into thematic modules (#636)
Move each error family out of domain/errors.ts into its own module
(base, core, provider selection/adapter, browser observation,
artifact, evidence, unknown registry, hopper, configuration) and
relocate AnalysisErrorProjection to analysisErrorProjection.
Migrate all 162 importers to the owning modules and delete
errors.ts. No re-export shims; behavior is unchanged.
2026-10-06 05:57:38 +08:00
Rudy Mizrahi Celekli 2b7bb3bb6a fix(native): inspect the selected universal Mach-O architecture (#601)
Signed-off-by: Rudy Celekli <47457359+rudycelekli@users.noreply.github.com>
2026-10-06 02:08:33 +08:00
morluto d40502aa3b fix(native): match compound otool keys exactly and badge unknown thread entrypoints (#534) 2026-10-05 15:43:20 +08:00
morluto 0f77ada55e fix(native): retain lazy dylibs, normalize version-min builds, limit thread entrypoints (#532) 2026-10-05 15:23:08 +08:00
Rudy Mizrahi Celekliandmorluto a8195ba2cc fix(native): capture Mach-O header metadata alongside load commands (#525)
* fix(native): capture Mach-O header metadata alongside load commands

Signed-off-by: Rudy Celekli <47457359+rudycelekli@users.noreply.github.com>

* style(test): format native Mach-O header regression

Signed-off-by: Rudy Celekli <47457359+rudycelekli@users.noreply.github.com>

* fix(native): drop dead bare otool override in header test

---------

Signed-off-by: Rudy Celekli <47457359+rudycelekli@users.noreply.github.com>
Co-authored-by: morluto <76467478+morluto@users.noreply.github.com>
2026-10-05 14:50:58 +08:00
morluto 405732a7f5 fix: correct native inventories and bridge error classification (#503)
* fix(hopper): preserve normalized inventory addresses

* test(hopper): isolate real inventory verification

* fix(native): preserve otool addresses and section alignment

* fix: preserve address spaces in call path evidence

* fix(hopper): distinguish request validation from provider faults

* fix(native): retain tool inventory facts and reject failed signatures
2026-10-04 17:25:09 +08:00
morluto 2f08c7c932 refactor(tools): remove artificial analysis ceilings 2026-09-29 16:29:23 +08:00
N0zoM1z0 d39f98d06d fix(native): harden command capture edge cases 2026-07-14 16:53:47 +08:00
morluto 4ff4c11a00 Add evidence-backed investigation workflows 2026-07-12 21:12:18 +00:00