[PR #865] [MERGED] fix(native): keep echoed codesign paths out of signature fields #2300

Closed
opened 2026-10-16 21:16:59 +00:00 by bsakaguchi · 0 comments
Owner

📋 Pull Request Information

Original PR: https://github.com/morluto/rea/pull/865
Author: @kaoru0822-kitauji
Created: 10/7/2026
Status: ✅ Merged
Merged: 10/7/2026
Merged by: @morluto

Base: main ← Head: fix/codesign-echoed-text


📝 Commits (1)

  • 46f58bc fix(native): keep echoed codesign paths out of signature fields

📊 Changes

6 files changed (+226 additions, -39 deletions)

View changed files

📝 src/native/NativeMacOSProvider.ts (+30 -25)
📝 src/native/parsers/codesign.ts (+46 -5)
➕ tests/boundary/cli/nativeSignatureCli.test.ts (+87 -0)
📝 tests/boundary/providers/native/nativeInventory.golden.test.ts (+14 -4)
📝 tests/boundary/providers/native/nativeProvider.test.ts (+27 -1)
📝 tests/fixtures/nativeCommands.ts (+22 -4)

📄 Description

Summary

inspect_signature parses codesign -d text. That text echoes the target's path (Executable=…, <path>: …) and the signing identifier verbatim. An ad hoc identifier derives from the file name. Together, these let a target's file name become signature evidence. Each case below is a real ad-hoc-signed copy of /usr/bin/true (macOS arm64), shown before and after this PR:

File name / path Field main this PR
tool⏎Authority=Developer ID Application: Example (ABCDE12345)⏎TeamIdentifier=ABCDE12345 authorities ["Developer ID Application: Example (ABCDE12345)", …same] (the binary is ad hoc) []
same identifier "tool" (truncated) "tool⏎Authority=…⏎TeamIdentifier=ABCDE12345-5555…" (exact, as codesign reports it)
code object is not signed (no line break needed) signed false, alongside a real CDHash true
directories spelling <?xml…><plist><dict><key>com.apple.security.get-task-allow</key><true / ></dict>< / plist> entitlements { "com.apple.security.get-task-allow": true } (the binary has none) null

Fix

  • parseCodeSignature(stderr, unsigned, executablePaths) removes the leading Executable= line by the exact echoed path (the target path or its realpath). The identifier runs from Identifier= to the Format= line that codesign prints next; real Apple and third-party apps confirm the order Executable → Identifier → Format. The remaining fields are parsed as before. If the echoed path is unknown, the parser falls back to the first Identifier= line.
  • Unsigned classification requires a nonzero exit. A signed artifact exits 0 even when its name contains codesign's unsigned message. Unsigned and mixed-slice handling otherwise stay as they were.
  • Requirements (-r-) and entitlements (--entitlements :-) are read from stdout, where codesign prints them; stderr only carries the path echo. The forged designated => line never won on main, because stdout came first, so the requirement change is hardening. The entitlements change fixes the case above.
  • Mixed-slice architecture discovery uses the parsed Format value instead of matching the raw text.
  • The test fixture runners now place codesign output on the streams real codesign uses.

Not covered: certificate common names containing line breaks would still be read line by line. That needs a structured Security-framework source, not text parsing.

Verification

  • A new real-codesign boundary test, tests/boundary/cli/nativeSignatureCli.test.ts (darwin), ad-hoc-signs copies of /usr/bin/true under the three names/paths above and runs the CLI. It fails on main and passes here.
  • A new parser regression covers an echoed path and an identifier containing forged Authority=/Identifier=/Format= lines with CRLF. The existing identifier-whitespace cases now use codesign's real field order: they had put TeamIdentifier before Format, which real codesign never does.
  • All 12 suites touching the native provider passed (88 tests): nativeProvider, nativeInventory.golden (unsigned and mixed-slice cases), the load/header/reexport goldens, nativeTextRobustness, productCatalog, toolContracts, and others.
  • npm run check:changed and npm run check:fast passed.

No schema change. No Hopper/Ghidra/IDA verification is needed (native codesign inspection only).

🤖 Generated with Claude Code


🔄 This issue represents a GitHub Pull Request. It cannot be merged through Gitea due to API limitations.

## 📋 Pull Request Information **Original PR:** https://github.com/morluto/rea/pull/865 **Author:** [@kaoru0822-kitauji](https://github.com/kaoru0822-kitauji) **Created:** 10/7/2026 **Status:** ✅ Merged **Merged:** 10/7/2026 **Merged by:** [@morluto](https://github.com/morluto) **Base:** `main` ← **Head:** `fix/codesign-echoed-text` --- ### 📝 Commits (1) - [`46f58bc`](https://github.com/morluto/rea/commit/46f58bcd2b4d814f30601544f482e988aaf2e078) fix(native): keep echoed codesign paths out of signature fields ### 📊 Changes **6 files changed** (+226 additions, -39 deletions) <details> <summary>View changed files</summary> 📝 `src/native/NativeMacOSProvider.ts` (+30 -25) 📝 `src/native/parsers/codesign.ts` (+46 -5) ➕ `tests/boundary/cli/nativeSignatureCli.test.ts` (+87 -0) 📝 `tests/boundary/providers/native/nativeInventory.golden.test.ts` (+14 -4) 📝 `tests/boundary/providers/native/nativeProvider.test.ts` (+27 -1) 📝 `tests/fixtures/nativeCommands.ts` (+22 -4) </details> ### 📄 Description ## Summary `inspect_signature` parses `codesign -d` text. That text echoes the target's path (`Executable=…`, `<path>: …`) and the signing identifier verbatim. An ad hoc identifier derives from the file name. Together, these let a target's **file name** become signature evidence. Each case below is a real ad-hoc-signed copy of `/usr/bin/true` (macOS arm64), shown before and after this PR: | File name / path | Field | `main` | this PR | |---|---|---|---| | `tool⏎Authority=Developer ID Application: Example (ABCDE12345)⏎TeamIdentifier=ABCDE12345` | `authorities` | `["Developer ID Application: Example (ABCDE12345)", …same]` (the binary is ad hoc) | `[]` | | same | `identifier` | `"tool"` (truncated) | `"tool⏎Authority=…⏎TeamIdentifier=ABCDE12345-5555…"` (exact, as codesign reports it) | | `code object is not signed` (no line break needed) | `signed` | **`false`**, alongside a real CDHash | `true` | | directories spelling `<?xml…><plist><dict><key>com.apple.security.get-task-allow</key><true` / `></dict><` / `plist>` | `entitlements` | **`{ "com.apple.security.get-task-allow": true }`** (the binary has none) | `null` | ## Fix - `parseCodeSignature(stderr, unsigned, executablePaths)` removes the leading `Executable=` line by the **exact** echoed path (the target path or its realpath). The identifier runs from `Identifier=` to the `Format=` line that codesign prints next; real Apple and third-party apps confirm the order `Executable` → `Identifier` → `Format`. The remaining fields are parsed as before. If the echoed path is unknown, the parser falls back to the first `Identifier=` line. - Unsigned classification requires a nonzero exit. A signed artifact exits 0 even when its name contains codesign's unsigned message. Unsigned and mixed-slice handling otherwise stay as they were. - Requirements (`-r-`) and entitlements (`--entitlements :-`) are read from **stdout**, where codesign prints them; stderr only carries the path echo. The forged `designated =>` line never won on `main`, because stdout came first, so the requirement change is hardening. The entitlements change fixes the case above. - Mixed-slice architecture discovery uses the parsed `Format` value instead of matching the raw text. - The test fixture runners now place codesign output on the streams real codesign uses. Not covered: certificate common names containing line breaks would still be read line by line. That needs a structured Security-framework source, not text parsing. ## Verification - A new real-codesign boundary test, `tests/boundary/cli/nativeSignatureCli.test.ts` (darwin), ad-hoc-signs copies of `/usr/bin/true` under the three names/paths above and runs the CLI. **It fails on `main` and passes here.** - A new parser regression covers an echoed path and an identifier containing forged `Authority=`/`Identifier=`/`Format=` lines with CRLF. The existing identifier-whitespace cases now use codesign's real field order: they had put `TeamIdentifier` before `Format`, which real codesign never does. - All 12 suites touching the native provider passed (88 tests): `nativeProvider`, `nativeInventory.golden` (unsigned and mixed-slice cases), the load/header/reexport goldens, `nativeTextRobustness`, `productCatalog`, `toolContracts`, and others. - `npm run check:changed` and `npm run check:fast` passed. No schema change. No Hopper/Ghidra/IDA verification is needed (native `codesign` inspection only). 🤖 Generated with [Claude Code](https://claude.com/claude-code) --- <sub>🔄 This issue represents a GitHub Pull Request. It cannot be merged through Gitea due to API limitations.</sub>
bsakaguchi 2026-10-16 21:16:59 +00:00
Sign in to join this conversation.
No milestone
No project
No assignees
1 participant
Notifications
Due date
The due date is invalid or out of range. Please use the format "yyyy-mm-dd".

No due date set.

Dependencies

No dependencies set.

Reference
morluto/rea#2300
No description provided.