procfetch
| What it does | Enumerates running processes with SHA-1 hashes of executables |
| Version | 1.0.0 |
| Kind | Collector · read-only · gathered (crossplatform.process.fetch) |
| Platforms | Windows ✅ · macOS ✅ · Linux ✅ |
| Actions | procfetch_fetch (definition crossplatform.process.fetch) |
| Security | securable Inventory · operation Read · risk Low · dispatch ReadOnly · approval gate None |
| Roles | execute: endpoint-admin, endpoint-operator · author: content-author |
How it works
procfetch_fetch is the plugin’s only action. Each leg enumerates every process the OS exposes, resolves the executable image path, and SHA-1-hashes the file at that path, streaming one pipe-delimited row per process via write_output() as it goes rather than collecting the whole list first. Hashes are cached by path within a single run so a binary loaded by many processes (svchost.exe, cfprefsd, MTLCompilerService) is only read and hashed once. The three legs are native, in-process enumeration — /proc on Linux, libproc on macOS, CreateToolhelp32Snapshot on Windows — with OpenSSL EVP (POSIX) or BCrypt (Windows) doing the hashing; no subprocess is spawned anywhere in the plugin. It is deliberately not a general process inventory: processes (crossplatform.process.list/.query) already lists PID and name only, cheaply and without touching disk; procfetch exists specifically to add the executable path and a file hash, which costs one file read and one hash per unique binary.
flowchart LR
OP[Operator / workflow] --> SRV[Server<br/>authz: Inventory.Read]
SRV -- gRPC mTLS --> HOST[Agent plugin host] --> EX[procfetch.execute]
EX --> WIN[Windows leg<br/>CreateToolhelp32Snapshot + BCrypt SHA-1]
EX --> MAC[macOS leg<br/>libproc proc_listpids/proc_pidpath + OpenSSL EVP SHA-1]
EX --> LIN[Linux leg<br/>/proc enumeration + OpenSSL EVP SHA-1]
WIN & MAC & LIN --> ROWS[rows, no typed status] --> RS[(ResponseStore)] --> API[REST /api/responses]
OS capability
| Action | Windows | macOS | Linux |
|---|---|---|---|
procfetch_fetch | ✅ supported · rung 1 · CreateToolhelp32Snapshot + BCrypt SHA-1 | ✅ supported · rung 1 · libproc (proc_listpids/proc_pidpath) + OpenSSL EVP SHA-1 | ✅ supported · rung 1 · /proc enumeration + OpenSSL EVP SHA-1 |
Privileges and prerequisites
| OS | Runs as | Extra grant needed | Measured | If the read is refused |
|---|---|---|---|---|
| Windows | SYSTEM (measured; no row in docs/agent-privilege-model.md for this plugin) | None observed at SYSTEM. Even as SYSTEM, OpenProcess(PROCESS_QUERY_LIMITED_INFORMATION, ...) is refused for a handful of protected/system processes — the sample’s [System Process], System, Secure System, Registry rows carry no path or hash. | 2026-09-07, bare-metal, SYSTEM | that row’s path and sha1 fields are empty; no error is surfaced and enumeration continues (procfetch_plugin.cpp:251-264) |
| macOS | unprivileged (agent daemon) | None observed unprivileged — proc_listpids/proc_pidpath resolved paths and hashes for processes owned by root and other users in the sample (e.g. postgres, XprotectService) without elevation. | 2026-09-07, macOS 26.6.2, euid 501 (jsmith) | proc_pidpath failing falls back to proc_name() for a short name only, leaving path and sha1 empty (procfetch_plugin.cpp:216-223) — not observed in this capture |
| Linux | root (captured in a container; not measured unprivileged) | resolve_exe reads the /proc/<pid>/exe symlink, which the kernel restricts to the same UID or a reader holding CAP_SYS_PTRACE; an unprivileged, non-matching-UID read is expected to fail | 2026-09-06, Debian 13 (trixie) aarch64 container, euid 0 | std::filesystem::read_symlink throws filesystem_error, caught and turned into an empty path, so path and sha1 are empty and the row still emits (procfetch_plugin.cpp:134-140) |
No external binaries, no subprocesses, no network access — every leg is a native OS call plus an in-process hash (agents/plugins/procfetch/src/procfetch_plugin.cpp, no popen/CreateProcess/socket use anywhere in the file).
Data contract
Inputs
The action takes no parameters.
Outputs
Pipe-delimited rows, one per process, in the fixed field order pid|name|path|sha1. name and path are pipe-escaped (a literal | becomes \|) before being written, so the delimiter itself is never ambiguous. path and sha1 are empty strings, not -, when the OS would not hand back an image path or the file could not be opened/hashed — there is no placeholder row for “no processes”: the row is only ever missing fields, never absent.
crossplatform.process.fetch — pid|name|path|sha1
| Field | Type | Values | Available | Example | Description |
|---|---|---|---|---|---|
pid | int64 | - | Windows, Linux, macOS | 1052 | The process ID assigned by the OS. Values: integer. |
name | string | - | Windows, Linux, macOS | svchost.exe | The process’s short or executable name, as reported by the OS. Values: free text. |
path | string | - | Windows, Linux, macOS | /usr/bin/dash | Absolute path to the process’s executable image on disk; empty when the OS refuses or fails to resolve it (a protected process, a permission-denied read, or a pseudo-process with no backing binary). Values: absolute path or empty. |
sha1 | string | - | Windows, Linux, macOS | e5355187cbd952a5bc9dfca430d10383f0f8750c | Lowercase hex SHA-1 of the executable file’s contents, cached per path so a binary shared by several processes is hashed once; empty when the file could not be opened or its path could not be resolved. Values: 40-char lowercase hex or empty. |
Result status
This plugin does not set a typed result status; the agent records UNDECLARED and the sample shows UNDECLARED / UNKNOWN /.
Where the data goes
- Instruction result only. Rows travel over the agent’s mTLS gRPC channel as the command response and land in the ResponseStore, queryable at
/api/responses/{id}. The classic dashboard also reaches this action through the legacyPOST /api/procfetch/fetchroute (server.cpp:14943), which forwards into the same dispatch chokepoint as the DSL path (changelog.d/1.9-dispatch-chokepoint.security.md) rather than a separate mechanism. - Not consumed by daily-sync inventory, the Echo warehouse, DEX, or metrics — grepping the server for
procfetchfinds only the dashboard route, the legacy REST forwarder, the capability declaration, and the static result-table column list (result_parsing.hpp:49). - Sensitivity.
namerows name installed applications on the host (process/executable names — an installed-software inventory by another route, e.g.XprotectService,Code Helper (Plugin));pathfrequently embeds the owning account’s home directory on macOS/Linux (e.g./Users/jsmith/...in the macOS sample), identifying a specific person, whilesha1alone identifies neither a device nor a person. - Siblings:
crossplatform.process.list/crossplatform.process.query(theprocessesplugin) — PID and name only, no path or hash, cheaper to run. - MCP / REST. Discover:
discover_plugins(summary) →yuzu://plugin-docs(this page as data) →discover_instructions/get_definition("crossplatform.process.fetch"). Run:execute_instruction {definition_id, parameters}. Read:/api/responses/{id}.
Sample output
Windows — captured: windows Microsoft Windows NT 10.0.26200.0 x64 · bare-metal · 2026-09-07 · SYSTEM · leg-hash 514b81c2df7e
== action=procfetch_fetch
0|[System Process]||
4|System||
332|Secure System||
376|Registry||
1052|smss.exe|C:\Windows\System32\smss.exe|2c84c834b6326b8fc02cc3262d9035e16fc27847
1504|csrss.exe|C:\Windows\System32\csrss.exe|17397f105f7d51112261ecc501fe99b8fcf4de67
1596|wininit.exe|C:\Windows\System32\wininit.exe|54fea18cc8aaefa2759701df35a4d4487a594593
1604|csrss.exe|C:\Windows\System32\csrss.exe|17397f105f7d51112261ecc501fe99b8fcf4de67
1676|services.exe|C:\Windows\System32\services.exe|56aea1cb2ceada37df439990113529c2c67a61c8
1716|winlogon.exe|C:\Windows\System32\winlogon.exe|c2c0e4e27796545d94c5ef879ee429c608a42dbf
1724|LsaIso.exe|C:\Windows\System32\LsaIso.exe|839d5e8e57687cbb0d21420e386f73c6742e8588
1740|lsass.exe|C:\Windows\System32\lsass.exe|258a9984be57a5000517fd76abff440f5562a315
… 12 of 184 rows shown
[result_status] UNDECLARED / UNKNOWN
macOS — captured: macos macOS 26.6.2 arm64 · bare-metal · 2026-09-07 · euid 501 (jsmith) · leg-hash 514b81c2df7e
== action=procfetch_fetch
10151|plugin_capture|/private/tmp/claude-501/-Users-jsmith-yuzu-dev/00000000-0000-0000-0000-000000000000/scratchpad/d2/plugin_capture|87ea43e2b7d3dd2eb6e7db6a20041b1f77d20272
10143|mdworker_shared|/System/Library/Frameworks/CoreServices.framework/Versions/A/Frameworks/Metadata.framework/Versions/A/Support/mdworker_shared|19ca6c856d80cfc6b83800c821b3129f40b135c7
10125|Code Helper (Plugin)|/Applications/Visual Studio Code.app/Contents/Frameworks/Code Helper (Plugin).app/Contents/MacOS/Code Helper (Plugin)|0478d377b6ffa6659d823d3c72d25dc9b8816bd6
10121|mdworker_shared|/System/Library/Frameworks/CoreServices.framework/Versions/A/Frameworks/Metadata.framework/Versions/A/Support/mdworker_shared|19ca6c856d80cfc6b83800c821b3129f40b135c7
10120|mdworker_shared|/System/Library/Frameworks/CoreServices.framework/Versions/A/Frameworks/Metadata.framework/Versions/A/Support/mdworker_shared|19ca6c856d80cfc6b83800c821b3129f40b135c7
10119|mdworker_shared|/System/Library/Frameworks/CoreServices.framework/Versions/A/Frameworks/Metadata.framework/Versions/A/Support/mdworker_shared|19ca6c856d80cfc6b83800c821b3129f40b135c7
10118|Code Helper (Plugin)|/Applications/Visual Studio Code.app/Contents/Frameworks/Code Helper (Plugin).app/Contents/MacOS/Code Helper (Plugin)|0478d377b6ffa6659d823d3c72d25dc9b8816bd6
9945|biomesyncd|/usr/libexec/biomesyncd|231b0daf0f1dd48345aade47d3adfbbb5a75844f
9654|head|/usr/bin/head|a78fc55bfe98bda12ebd3c8190d12b3f09159407
9653|ssh|/usr/bin/ssh|8ccefa7581a171018a6cc737649ef7854e75d4c0
9647|zsh|/bin/zsh|95f7d2b76c248d1dc8c84c37e3e5cddc93246b6c
9621|spotlightknowledged|/System/Library/Frameworks/CoreSpotlight.framework/spotlightknowledged|8d9cd67b92a13d9f23b7c546737edce879c47436
… 12 of 818 rows shown
[result_status] UNDECLARED / UNKNOWN
Linux — captured: linux Debian GNU/Linux 13 (trixie) aarch64 · container · 2026-09-06 · euid 0 · leg-hash 514b81c2df7e
== action=procfetch_fetch
1|sh|/usr/bin/dash|e5355187cbd952a5bc9dfca430d10383f0f8750c
9|plugin-capture|/src/builddir/tools/plugin-capture/plugin-capture|c93a85ef1e07f11e88bcea0d85f828adc1069521
[result_status] UNDECLARED / UNKNOWN
Caveats and known gaps
- Windows hashing opens a UTF-8 path via the ANSI
CreateFileA.get_process_image_pathconverts the wide image path to UTF-8 viafrom_wide(procfetch_plugin.cpp:263), thensha1_of_filepasses that UTF-8 string straight toCreateFileA(procfetch_plugin.cpp:267), which interprets it as the ANSI code page, not UTF-8. A process whose executable path contains non-ASCII characters will fail to open, silently leavingsha1empty with no error surfaced. - This plugin never sets a typed result status. Both captured samples read
UNDECLARED / UNKNOWN /regardless of whether any row’s path/hash resolution failed — a caller cannot distinguish “clean run” from “every row’s hash silently failed” without inspecting the rows themselves. - Protected Windows processes report name only.
OpenProcessis refused even running as SYSTEM for a handful of processes ([System Process],System,Secure System,Registryin the sample); those rows carry emptypathandsha1with no row-level error indicator. - SHA-1, not SHA-256. The metadata description positions this action for “security auditing” and “IOC matching” (
content/definitions/procfetch.yaml:16-17), but the hash is SHA-1 on every leg (EVP_sha1()/BCRYPT_SHA1_ALGORITHM), which is broken for collision resistance; it remains adequate for matching against a known-hash IOC list (a preimage property SHA-1 still holds) but not for detecting a deliberately-collided binary. - Linux exe resolution needs same-UID or
CAP_SYS_PTRACE.resolve_exereads/proc/<pid>/exe(procfetch_plugin.cpp:134-140); the capture was taken as root in a two-process container, so an unprivileged multi-user host was not exercised — expectpath/sha1to go empty for other users’ processes there.
Source and tests
- Plugin:
agents/plugins/procfetch/src/procfetch_plugin.cpp - Definitions:
content/definitions/procfetch.yaml - Capability rows:
server/core/src/capability_decls/plugin_action_catalogue_b.hpp - Tests: none found by name
- Privilege row:
docs/agent-privilege-model.md(no row yet)