filesystem
| What it does | Filesystem operations — exists, list_dir, file_hash, search_dir, text ops (admin-only) |
| Version | 0.4.0 |
| Kind | Action · mutating · gathered (device.filesystem.exists, device.filesystem.list_dir, device.filesystem.file_hash, device.filesystem.read, device.filesystem.create_temp, device.filesystem.create_temp_dir, device.filesystem.get_acl, device.filesystem.get_signature, device.filesystem.find_by_hash, device.filesystem.get_version_info, device.filesystem.search_dir, device.filesystem.search, device.filesystem.replace, device.filesystem.write_content, device.filesystem.append, device.filesystem.delete_lines, workflow.version_compliance_check) |
| Platforms | Windows ✅ · macOS ✅ · Linux ✅ |
| Actions | append (definition device.filesystem.append) · create_temp (definition device.filesystem.create_temp) · create_temp_dir (definition device.filesystem.create_temp_dir) · delete_lines (definition device.filesystem.delete_lines) · exists (definition device.filesystem.exists) · file_hash (definition device.filesystem.file_hash) · find_by_hash (definition device.filesystem.find_by_hash) · get_acl (definition device.filesystem.get_acl) · get_signature (definition device.filesystem.get_signature) · get_version_info (definition device.filesystem.get_version_info, workflow.version_compliance_check) · list_dir (definition device.filesystem.list_dir) · read (definition device.filesystem.read) · replace (definition device.filesystem.replace, workflow.config_search_and_replace) · search (definition device.filesystem.search) · search_dir (definition device.filesystem.search_dir) · write_content (definition device.filesystem.write_content) |
| Security | exists: securable FileRetrieval · operation Read · risk Low · dispatch ReadOnly · approval gate None; list_dir: securable FileRetrieval · operation Read · risk Low · dispatch ReadOnly · approval gate None; file_hash: securable FileRetrieval · operation Read · risk Low · dispatch ReadOnly · approval gate None; create_temp: securable FileRetrieval · operation Write · risk Medium · dispatch Mutating · approval gate AdminOrApproval; create_temp_dir: securable FileRetrieval · operation Write · risk Medium · dispatch Mutating · approval gate AdminOrApproval; read: securable FileRetrieval · operation Read · risk Low · dispatch ReadOnly · approval gate None; get_acl: securable FileRetrieval · operation Read · risk Low · dispatch ReadOnly · approval gate None; get_signature: securable FileRetrieval · operation Read · risk Low · dispatch ReadOnly · approval gate None; find_by_hash: securable FileRetrieval · operation Read · risk Low · dispatch ReadOnly · approval gate AdminOrApproval; search_dir: securable FileRetrieval · operation Read · risk Low · dispatch ReadOnly · approval gate None; get_version_info: securable FileRetrieval · operation Read · risk Low · dispatch ReadOnly · approval gate None; search: securable FileRetrieval · operation Read · risk Low · dispatch ReadOnly · approval gate None; replace: securable FileRetrieval · operation Write · risk Medium · dispatch Mutating · approval gate AdminOrApproval; write_content: securable FileRetrieval · operation Write · risk Medium · dispatch Mutating · approval gate AdminOrApproval; append: securable FileRetrieval · operation Write · risk Medium · dispatch Mutating · approval gate AdminOrApproval; delete_lines: securable FileRetrieval · operation Delete · risk High · dispatch Destructive · approval gate AdminOrApproval |
| Roles | execute: exists: endpoint-admin, endpoint-operator; list_dir: endpoint-admin, endpoint-operator; file_hash: endpoint-admin, endpoint-operator; read: endpoint-admin, endpoint-operator; create_temp: endpoint-admin; create_temp_dir: endpoint-admin; get_acl: endpoint-admin, endpoint-operator; get_signature: endpoint-admin, endpoint-operator; find_by_hash: endpoint-admin; get_version_info: endpoint-admin, endpoint-operator; search_dir: endpoint-admin, endpoint-operator; search: endpoint-admin, endpoint-operator; replace: endpoint-admin; write_content: endpoint-admin; append: endpoint-admin; delete_lines: endpoint-admin; replace: endpoint-admin; get_version_info: endpoint-admin, endpoint-operator · author: content-author |
How it works
The filesystem plugin reads, inspects and edits files on the device the agent runs on. Its 16 actions fall into four groups: look (exists, list_dir, read, search, search_dir), identify (file_hash, find_by_hash, get_signature, get_version_info, get_acl), create (create_temp, create_temp_dir) and change (write_content, replace, append, delete_lines). Most actions turn the caller’s path into its canonical form, following symlinks, and, if the caller also passes base_dir, refuse a path that resolves outside that directory. The exceptions: find_by_hash and create_temp take no base_dir, and exists and a new-file write_content canonicalise only the parent directory when the target does not exist yet. Reads never alter the target. Changes to an existing file (replace, delete_lines, and write_content with overwrite=true) are written to a new sibling file and renamed over the target, so a reader sees either the old or the new content. append, write_content creating a new file, and the temp-file actions open the target directly. It is deliberately not a file manager: there is no whole-file delete, rename or copy, and no ACL write; get_acl only reads.
How the main actions work:
- Hashing one file.
file_hashhashes in process on Windows (BCrypt). On macOS and Linux it runsshasumorsha256sum/sha1sumby absolute path, with no shell, through the bounded subprocess runner (compute_hash_unix, 300 second deadline). - Finding a file by hash.
find_by_hashhashes in process on every OS and starts no subprocess (filesystem_find_by_hash.hpp). It walks the tree under fixed bounds (20 seconds, 50,000 entries visited, 4 GiB hashed, 100 matches), does not follow a symlink it meets below the search root (on Windows and on older Linux only the final component is opened no-follow, Caveat 2), reads nothing but a regular file, and refuses a root on a network mount (on macOS and Linux also a network mount below it) before touching it. What it skipped is counted in one trailingtruncated|...row and, when that matters, aCONSTRAINEDresult status; the diagram below and Result status say how to read it. - Signature and version.
get_signatureandget_version_infoexist on Windows and macOS only. Windows usesWinVerifyTrustandGetFileVersionInfoWin process; macOS runs/usr/bin/codesignand/usr/bin/plutilthrough the same runner, re-validating the path immediately before the exec. On Linux both answererror|with a non-zero return code. - Search.
search,search_dirandreplacereject regexes with nested quantifiers (has_nested_quantifiers) to avoid catastrophic backtracking, and limit patterns to 256 characters.search_dirmatches entry names only, as a glob (glob_match) or a regex, never file contents. - Atomic writes. The temporary file is created exclusively with a random name, written, flushed to disk with
fsyncon macOS and Linux (F_FULLFSYNCfirst on macOS; the Windows leg does not fsync), then renamed into place (atomic_write_file). A new file gets the usual0666minus umask; an existing file keeps its permission bits on POSIX, but its owner, group, ACLs and hard links are not carried over because the rename replaces the file. A leftover file at the temp path fails the write rather than being followed. get_acl. Each OS reads its native ACL (see the table below) and prints it in the sameace|<type>|<principal>|<access>|<flags>row shape.
Only find_by_hash sets a typed result status, and only when it could not examine part of the tree or ran without its network-mount guard; every other outcome is carried in the rows and return code (see Result status).
flowchart LR
OP["Operator or workflow"] --> SRV["Server: authz FileRetrieval Read, Write or Delete, gate None or AdminOrApproval"]
SRV -- "gRPC mTLS" --> HOST["Agent plugin host"] --> EX["filesystem.execute"]
EX --> VAL["validate_path: canonical path, optional base_dir"]
VAL --> WIN["Windows leg: std::filesystem, BCrypt, WinVerifyTrust, version info, Win32 ACL"]
VAL --> MAC["macOS leg: std::filesystem, shasum for file_hash, in-process SHA-256 for find_by_hash, codesign, plutil, ACL"]
VAL --> LIN["Linux leg: std::filesystem, sha256sum and sha1sum for file_hash, in-process SHA-256 for find_by_hash, xattr ACL"]
WIN --> ROWS["Pipe rows; a typed result status from find_by_hash only"]
MAC --> ROWS
LIN --> ROWS
ROWS --> RS[("ResponseStore, 90 day default retention")] --> API["REST /api/responses, MCP execute_instruction"]
What you can decide with it
| Question | Action | What you get and its limits |
|---|---|---|
| Is there a file or directory here, and how big? | exists, list_dir | exists reports true or false, the type and size. list_dir returns one entry row per item and stops at 1000 entries with a warning| row. |
| What does this text file say? | read, search | read returns 100 lines per call by default (limit, at most 10000) from a 1-based offset and refuses a file whose first 512 bytes contain a NUL byte; search returns 100 matching lines by default (max_matches, at most 10000). Both refuse a file over 100 MB. |
| Where is a directory or file named like this? | search_dir | Name matches under a root: depth 5 by default (max_depth, at most 20), 100 results by default (max_results, at most 1000). It does not look inside files. |
| Is this the file I expect, or does a known file exist anywhere here? | file_hash, find_by_hash | SHA-256 or SHA-1 of one file (up to 10 GB). find_by_hash sweeps a directory tree for a SHA-256 within a fixed budget and says what it did not examine; see Caveat 2 for what a “no match” means. |
| Is it signed, and which version is it? | get_signature, get_version_info | Windows and macOS only, with narrow meanings (Caveat 5). Linux has no equivalent and answers an error. |
| Who can touch it? | get_acl | Per-OS decode below. |
| Make a change | write_content, replace, append, delete_lines, create_temp, create_temp_dir | See the safe workflow below. All need an administrator or an approval. |
Safe workflow for changes. There is no backup and no undo: a change overwrites the file, and the old content is gone unless you kept a copy elsewhere. Work in this order: confirm the target with exists and read (or search to find the line); use replace with dry_run=true to see how many replacements it would make, since the other change actions have no preview; apply the change; verify by read, search or file_hash. Target rules: the path must already exist, except write_content with create=true, whose parent directory must exist. write_content refuses to touch an existing file unless overwrite=true, and refuses to create one unless create=true. replace, append and delete_lines need a regular file, and the text actions treat the file as lines (delete_lines rewrites the file so it ends with a newline, unless no lines remain). With regex=true, replace expands $1-style references in the replacement only when max_replacements is 0; with a limit it inserts the replacement text literally, so the same replacement string can behave differently with and without a limit.
get_acl by OS.
| OS | Source | What you see | Not shown |
|---|---|---|---|
| Windows | GetNamedSecurityInfoW owner and DACL | sddl, control flags, one ace row per entry (allow, deny, conditional and object types; principal as DOMAIN\name or SID; hex access mask; inherit flags) | The SACL (needs SeSecurityPrivilege); condition expressions are ignored. A NULL DACL (everyone allowed) prints no ace rows. |
| Linux | system.posix_acl_access and, for a directory, system.posix_acl_default | owner, group, permissions, mode, acl|none|extended, one ace row per entry | An ACL over 64 KiB is an error. Only the first 64 distinct ids are resolved to names; the rest print as numbers. |
| macOS | acl_get_file(ACL_TYPE_EXTENDED) | owner, group, permissions, mode, acl row, one ace row per entry in stored order | No extended ACL prints acl|none; a file system that cannot return ACLs prints acl|unsupported. An unresolved principal prints as its UUID. |
On Linux and macOS the whole ACL is read before any row is written, so a failure is one error| row. On Windows an error part-way through the entries can follow rows already written.
Diagrams
Write path
sequenceDiagram
participant S as Server
participant P as filesystem plugin
participant D as Disk
S->>P: replace with path, search, replacement, optional base_dir
P->>D: canonicalize path, check base_dir, regular file, size cap
D-->>P: file bytes read into memory
P->>P: match and substitute in memory
alt dry_run is true or nothing matched
P-->>S: replacements_made and sizes, file untouched
else changes to write
P->>D: create sibling temp exclusively, write, fsync, rename over target
P-->>S: replacements_made and sizes
end
replace, delete_lines and write_content on an existing file all follow the atomic path above. append and write_content on a new file skip the temp-and-rename step and write straight into the target, so an interrupted write can leave a partial file. The size cap is checked before anything is read, and the result is built entirely in memory.
find_by_hash
flowchart TD
S["find_by_hash with directory, sha256, max_depth"] --> MT["Read the network-mount list first: Linux /proc/self/mountinfo, macOS getmntinfo_r_np, Windows the root's own spelling and drive type"]
MT --> RM{"Is the directory as given on a network mount, a UNC path or a non-fixed drive?"}
RM -->|"yes"| RX["Refused before anything touches it: skipped_network_mount, CONSTRAINED"]
RM -->|"no"| V["validate_path: canonical directory; then max_depth is clamped to 1-10"]
V --> RC{"Canonical root under a listed network mount (macOS, Linux), or not listable?"}
RC -->|"yes"| RR["rc 0: skipped_network_mount=1 or skipped_unreadable=1, CONSTRAINED"]
RR --> OUT
RC -->|"no"| NX["Next entry"]
NX --> B{"20 seconds or 50,000 entries reached, or the agent shutting down?"}
B -->|"yes"| STOP["Stop with reason deadline, file_cap or cancelled"]
B -->|"no"| K{"What is the entry, judged without following it?"}
K -->|"under a network mount (macOS, Linux)"| SN["skipped_network_mount, not entered"]
K -->|"symlink or junction"| SL["skipped_symlink, never followed"]
K -->|"FIFO, device or socket"| SR["skipped_not_regular, never opened"]
K -->|"directory"| D{"Can it be listed? On Linux: not a pseudo-filesystem such as /proc"}
D -->|"no"| SU["skipped_unreadable"]
D -->|"yes, or at max_depth: not opened, not counted"| NX
K -->|"regular file"| O["Open no-follow; regular file and size decided on that descriptor or handle"]
O -->|"over 10 GB, or larger than the bytes left in the budget"| SO["skipped_oversize; the second also makes the reason byte_cap"]
O -->|"cannot open or read, cloud placeholder, unknown reparse tag"| SU
O -->|"a link at open time"| SL
O -->|"not a regular file at open time"| SR
O -->|"ok"| H["Stream 64 KiB chunks through SHA-256, budget checked per chunk"]
H -->|"equals the target"| M["match row; at 100 matches stop with max_results if entries remain"]
H -->|"differs"| NX
H -->|"a bound trips mid-file: byte_cap, deadline or cancelled"| STOP
SN --> NX
SL --> NX
SR --> NX
SU --> NX
SO --> NX
M --> NX
NX -->|"none left"| OUT
STOP --> OUT["Closing rows: network_mount_guard unavailable when the mount table could not be read, one truncated row when anything was skipped or the walk stopped, then matches_found"]
The mount check is a string comparison that runs before any call touching a path, because an open or listing on a dead hard-mounted server would hold one of the agent’s shared command workers with no deadline. Every skip the walk sees lands in a counter of the trailing truncated|<reason>|visited=N|hashed=N|skipped_unreadable=N|skipped_symlink=N|skipped_not_regular=N|skipped_oversize=N|skipped_network_mount=N row, written only when something was skipped or the walk stopped early (reason is none when no bound tripped). hashed= counts bytes admitted to the hasher, including a file abandoned part-way (no match is reported for it). Empty files are hashed. Stopping at 100 matches counts as a bound only when entries remained, so exactly 100 matches in a fully walked tree is clean. A search root that cannot be listed is not an error| line either: it answers return code 0 with skipped_unreadable=1 and CONSTRAINED. Caveat 2 lists the races that can still skip a directory uncounted.
How the “never followed” holds per OS. macOS opens every candidate with O_NOFOLLOW_ANY (no symlink in any path component) and turns the thread’s dataless-file materialisation off for the walk, so an iCloud or File Provider item that is not downloaded fails to open and is counted unreadable instead of being fetched. Linux 5.6 or later opens with openat2(RESOLVE_NO_SYMLINKS); on an older kernel, or where a seccomp profile denies openat2, it falls back to O_NOFOLLOW on the final component only, for the rest of the process’s life once openat2 has been refused (an architecture with no known openat2 number always uses the fallback). Windows opens the final component with FILE_FLAG_OPEN_REPARSE_POINT and classifies the handle before any read: a symlink, junction or other name-surrogate reparse point is a link, a compressed (WOF) or deduplicated file is hashed through a handle reopened without the reparse flag, and a cloud placeholder, an offline file or any other reparse tag is counted unreadable and never recalled. On Windows a mounted folder below the root is a reparse point and is skipped as a junction, and a \\?\ root reads as a UNC path and is refused. A relative directory is judged by its absolute spelling (the working directory joined lexically), so pass absolute paths.
OS capability
| Action | Windows | macOS | Linux |
|---|---|---|---|
append | ✅ supported · rung 1 · std::ofstream | ✅ supported · rung 1 · std::ofstream | ✅ supported · rung 1 · std::ofstream |
create_temp | ✅ supported · rung 1 · yuzu_create_temp_file | ✅ supported · rung 1 · yuzu_create_temp_file | ✅ supported · rung 1 · yuzu_create_temp_file |
create_temp_dir | ✅ supported · rung 1 · yuzu_create_temp_dir | ✅ supported · rung 1 · yuzu_create_temp_dir | ✅ supported · rung 1 · yuzu_create_temp_dir |
delete_lines | ✅ supported · rung 1 · atomic_write_file | ✅ supported · rung 1 · atomic_write_file | ✅ supported · rung 1 · atomic_write_file |
exists | ✅ supported · rung 1 · std::filesystem | ✅ supported · rung 1 · std::filesystem | ✅ supported · rung 1 · std::filesystem |
file_hash | ✅ supported · rung 1 · bcrypt | ✅ supported · rung 2 · subprocess_runner:shasum | ✅ supported · rung 2 · subprocess_runner:sha256sum/sha1sum |
find_by_hash | ✅ supported · rung 1 · std::filesystem+bcrypt | ✅ supported · rung 1 · std::filesystem+sha256_nofollow_fd | ✅ supported · rung 1 · std::filesystem+sha256_nofollow_fd |
get_acl | ✅ supported · rung 1 · win32_acl | ✅ supported · rung 1 · posix_stat+acl_get_file(ACL_TYPE_EXTENDED) | ✅ supported · rung 1 · posix_stat+getxattr(system.posix_acl_*) |
get_signature | ✅ supported · rung 1 · wintrust | ✅ supported · rung 2 · subprocess_runner:codesign | ⛔ unsupported |
get_version_info | ✅ supported · rung 1 · win32_version_info | ✅ supported · rung 2 · subprocess_runner:plutil | ⛔ unsupported |
list_dir | ✅ supported · rung 1 · std::filesystem | ✅ supported · rung 1 · std::filesystem | ✅ supported · rung 1 · std::filesystem |
read | ✅ supported · rung 1 · std::ifstream | ✅ supported · rung 1 · std::ifstream | ✅ supported · rung 1 · std::ifstream |
replace | ✅ supported · rung 1 · atomic_write_file | ✅ supported · rung 1 · atomic_write_file | ✅ supported · rung 1 · atomic_write_file |
search | ✅ supported · rung 1 · std::ifstream+std::regex | ✅ supported · rung 1 · std::ifstream+std::regex | ✅ supported · rung 1 · std::ifstream+std::regex |
search_dir | ✅ supported · rung 1 · std::filesystem+std::regex | ✅ supported · rung 1 · std::filesystem+std::regex | ✅ supported · rung 1 · std::filesystem+std::regex |
write_content | ✅ supported · rung 1 · atomic_write_file | ✅ supported · rung 1 · atomic_write_file | ✅ supported · rung 1 · atomic_write_file |
Privileges and prerequisites
| OS | Runs as | Extra grant needed | Measured | If the read is refused |
|---|---|---|---|---|
| Windows | LocalSystem today (docs/agent-privilege-model.md, tracked #1442; the target is the virtual service account NT SERVICE\YuzuAgent) | None for any read action. write_content, replace, append and delete_lines against a path the running account cannot already write need that account to be an Administrators member; the default install grants nothing | 2026-10-04, bare metal, as LocalSystem (elevated) | a single error|<message> line and return code 1 (for example error|GetNamedSecurityInfo failed (error N)); no typed status is set |
| macOS | root today: the shipped LaunchDaemon has no UserName key (docs/agent-privilege-model.md) | None for any read action. The plugin never calls sudo; the agent runs as root today, so a write succeeds on any path the OS lets root write. Under the planned unprivileged account it would succeed only where that account can already write | 2026-10-04, bare metal, unprivileged at euid 501; the capture ran through LocalDispatcher, not the root daemon | same error|<message> and return code 1 |
| Linux | yuzu-agent unprivileged system account on the packaged unit (docs/agent-privilege-model.md) | None for any read action. The plugin never calls sudo: a write succeeds only on a path the yuzu-agent account can already write, and the OS refuses it on a system path | 2026-10-04, container, as euid 0 (root), so not the least-privilege yuzu-agent account | same error|<message> and return code 1 |
Subprocesses: shasum (macOS) and sha256sum/sha1sum (Linux) for file_hash only (compute_hash_unix; find_by_hash starts none); /usr/bin/codesign for macOS get_signature (do_get_signature); /usr/bin/plutil for macOS get_version_info (do_get_version_info). Every one is invoked by absolute path with no shell through yuzu::agent::run_bounded_subprocess. No network access on any OS.
Who may run what is decided by the compiled capability row, not by the header’s role line. The plugin description says “admin-only”, but the read actions run for endpoint-operator as well as endpoint-admin; the write actions, create_temp, create_temp_dir and find_by_hash carry the AdminOrApproval gate. permissions.executeRoles in the definition YAML is documentation only and is not an access-control guarantee.
Data contract
Inputs
| Definition | Parameter | Type | Required | Default | Constraints | Description |
|---|---|---|---|---|---|---|
device.filesystem.append | path | string | yes | - | maxLength 4096 | The existing file to append content to. |
device.filesystem.append | content | string | no | - | - | The text content to append to the file. |
device.filesystem.append | newline | string | no | true | enum: true, false | If “true” (default), adds a newline before appended content when the file does not end with a newline character. Prevents content from being joined to the last existing line. |
device.filesystem.append | base_dir | string | no | - | maxLength 4096 | Optional. Restricts access to files within this directory. |
device.filesystem.create_temp | prefix | string | no | yuzu- | maxLength 64 | Prefix for the temp file name. |
device.filesystem.create_temp | suffix | string | no | .tmp | maxLength 32 | Suffix/extension for the temp file name. |
device.filesystem.create_temp | directory | string | no | - | maxLength 4096 | Optional. Create the temp file in this directory instead of the system default temp directory. |
device.filesystem.create_temp | persist | string | no | true | enum: true, false | If “true” (default), the file is kept. If “false”, the file is deleted after creation (useful for generating unique paths). |
device.filesystem.create_temp_dir | prefix | string | no | yuzu- | maxLength 64 | Prefix for the temp directory name. |
device.filesystem.create_temp_dir | directory | string | no | - | maxLength 4096 | Optional. Create the temp directory inside this parent instead of the system default temp directory. |
device.filesystem.create_temp_dir | persist | string | no | true | enum: true, false | If “true” (default), the directory is kept. If “false”, deleted after creation. |
device.filesystem.delete_lines | path | string | yes | - | maxLength 4096 | The text file to delete lines from. |
device.filesystem.delete_lines | start_line | int32 | yes | - | minimum 1 | The first line to delete (1-based, inclusive). |
device.filesystem.delete_lines | end_line | int32 | yes | - | minimum 1 | The last line to delete (1-based, inclusive). Clamped to total line count if it exceeds file length. |
device.filesystem.delete_lines | base_dir | string | no | - | maxLength 4096 | Optional. Restricts access to files within this directory. |
device.filesystem.exists | path | string | yes | - | maxLength 4096 | The filesystem path to check. |
device.filesystem.exists | base_dir | string | no | - | maxLength 4096 | Optional. If set, the path must resolve within this directory. Prevents directory traversal outside the allowed subtree. |
device.filesystem.file_hash | path | string | yes | - | maxLength 4096 | The file to hash. |
device.filesystem.file_hash | algorithm | string | no | sha256 | enum: sha256, sha1 | The hash algorithm to use. |
device.filesystem.file_hash | base_dir | string | no | - | maxLength 4096 | Optional. Restricts access to files within this directory. |
device.filesystem.find_by_hash | directory | string | yes | - | maxLength 4096 | The root directory to search. |
device.filesystem.find_by_hash | sha256 | string | yes | - | minLength 64 · maxLength 64 | The SHA256 hex digest to search for (case-insensitive). |
device.filesystem.find_by_hash | max_depth | int32 | no | 3 | minimum 1 · maximum 10 | Maximum directory recursion depth. Default: 3, max: 10. Out-of-range values are rejected with a 400 by POST /api/instructions/{id}/execute. |
device.filesystem.get_acl | path | string | yes | - | maxLength 4096 | The file or directory to inspect. |
device.filesystem.get_acl | base_dir | string | no | - | maxLength 4096 | Optional. Restricts access to paths within this directory. |
device.filesystem.get_signature | path | string | yes | - | maxLength 4096 | The executable, DLL, or app bundle to verify. |
device.filesystem.get_signature | base_dir | string | no | - | maxLength 4096 | Optional. Restricts access to files within this directory. |
device.filesystem.get_version_info | path | string | yes | - | maxLength 4096 | The executable, DLL, or app bundle to inspect. On macOS this may be a .app bundle directory or an Info.plist file directly. |
device.filesystem.get_version_info | base_dir | string | no | - | maxLength 4096 | Optional. Restricts access to files within this directory. |
device.filesystem.list_dir | path | string | yes | - | maxLength 4096 | The directory to list. |
device.filesystem.list_dir | base_dir | string | no | - | maxLength 4096 | Optional. Restricts access to paths within this directory. |
device.filesystem.read | path | string | yes | - | maxLength 4096 | The text file to read. |
device.filesystem.read | offset | int32 | no | 1 | minimum 1 | 1-based line number to start reading from. Default: 1. |
device.filesystem.read | limit | int32 | no | 100 | minimum 1 · maximum 10000 | Maximum number of lines to return. Default: 100, max: 10000. Out-of-range values are rejected with a 400 by POST /api/instructions/{id}/execute. |
device.filesystem.read | base_dir | string | no | - | maxLength 4096 | Optional. Restricts access to files within this directory. |
device.filesystem.replace | path | string | yes | - | maxLength 4096 | The file to perform find/replace on. |
device.filesystem.replace | search | string | yes | - | maxLength 256 | The literal string or regex to search for. |
device.filesystem.replace | replacement | string | no | - | The text to replace matches with. Empty string removes matches. | |
device.filesystem.replace | regex | string | no | false | enum: true, false | If “true”, interpret search as an ECMAScript regex. Default: “false”. |
device.filesystem.replace | case_sensitive | string | no | true | enum: true, false | If “true” (default), match is case-sensitive. |
device.filesystem.replace | dry_run | string | no | false | enum: true, false | If “true”, count replacements without modifying the file. Default: “false”. |
device.filesystem.replace | max_replacements | int32 | no | 0 | minimum 0 | Maximum number of replacements to perform. 0 = unlimited. Default: 0. |
device.filesystem.replace | base_dir | string | no | - | maxLength 4096 | Optional. Restricts access to files within this directory. |
device.filesystem.search | path | string | yes | - | maxLength 4096 | The text file to search. |
device.filesystem.search | pattern | string | yes | - | maxLength 256 | The literal string or regex to search for in each line. |
device.filesystem.search | regex | string | no | false | enum: true, false | If “true”, interpret the pattern as an ECMAScript regex. Patterns with nested quantifiers are rejected to prevent catastrophic backtracking. Default: “false”. |
device.filesystem.search | case_sensitive | string | no | true | enum: true, false | If “true” (default), match is case-sensitive. If “false”, performs case-insensitive matching. |
device.filesystem.search | max_matches | int32 | no | 100 | minimum 1 · maximum 10000 | Maximum number of matching lines to return. Default: 100, max: 10000. Out-of-range values are rejected with a 400 by POST /api/instructions/{id}/execute. |
device.filesystem.search | base_dir | string | no | - | maxLength 4096 | Optional. Restricts access to files within this directory. |
device.filesystem.search_dir | root | string | yes | - | maxLength 4096 | The root directory to search from. |
device.filesystem.search_dir | pattern | string | yes | - | maxLength 256 | Glob pattern (default) or regex to match against entry names. Examples: “.log”, “config”, “^backup-\d+$” (with regex=true). |
device.filesystem.search_dir | regex | string | no | false | enum: true, false | If “true”, interpret the pattern as an ECMAScript regex instead of a glob. Default: “false”. |
device.filesystem.search_dir | match_type | string | no | directories | enum: directories, files, both | What to match: “directories” (default), “files”, or “both”. |
device.filesystem.search_dir | max_depth | int32 | no | 5 | minimum 1 · maximum 20 | Maximum directory recursion depth. Default: 5, max: 20. Out-of-range values are rejected with a 400 by POST /api/instructions/{id}/execute. |
device.filesystem.search_dir | max_results | int32 | no | 100 | minimum 1 · maximum 1000 | Maximum number of matching entries to return. Default: 100, max: 1000. Out-of-range values are rejected with a 400 by POST /api/instructions/{id}/execute. |
device.filesystem.search_dir | base_dir | string | no | - | maxLength 4096 | Optional. Restricts the search root to paths within this directory. |
device.filesystem.write_content | path | string | yes | - | maxLength 4096 | The file path to write to. If the file does not exist, it will be created if create=true. |
device.filesystem.write_content | content | string | no | - | - | The text content to write to the file. |
device.filesystem.write_content | create | string | no | false | enum: true, false | If “true”, create the file if it does not exist. Default: “false”. |
device.filesystem.write_content | overwrite | string | no | false | enum: true, false | If “true”, overwrite an existing file. Default: “false”. |
device.filesystem.write_content | base_dir | string | no | - | maxLength 4096 | Optional. Restricts access to files within this directory. |
workflow.config_search_and_replace | path | string | yes | - | maxLength 4096 | Full path to the configuration file to update. Example: “/etc/myapp/config.ini” or “C:\ProgramData\MyApp\config.ini” |
workflow.config_search_and_replace | search | string | yes | - | maxLength 256 | The current configuration value to search for. Matched literally (not regex) unless regex=true. |
workflow.config_search_and_replace | replacement | string | yes | - | - | The new configuration value to replace matches with. |
workflow.config_search_and_replace | regex | string | no | false | enum: true, false | If “true”, interpret search as a regex pattern. Default: “false”. |
workflow.config_search_and_replace | case_sensitive | string | no | true | enum: true, false | If “true” (default), match is case-sensitive. |
workflow.config_search_and_replace | dry_run | string | no | false | enum: true, false | If “true”, count replacements without modifying the file. Default: “false”. |
workflow.config_search_and_replace | max_replacements | int32 | no | 0 | minimum 0 | Maximum number of replacements to perform. 0 = unlimited. Default: 0. |
workflow.config_search_and_replace | base_dir | string | no | - | maxLength 4096 | Optional. Restricts access to files within this directory. |
workflow.version_compliance_check | path | string | yes | - | maxLength 4096 | Full path to the Windows executable to check. Example: “C:\Program Files\MyApp\myapp.exe” |
workflow.version_compliance_check | minimum_version | string | no | - | maxLength 64 | The minimum acceptable product version string (e.g. “2.5.0.0”). Used by downstream PolicyFragment conditions for compliance evaluation. The check itself returns the raw version; the comparison happens in the policy layer using version_gte. |
workflow.version_compliance_check | base_dir | string | no | - | maxLength 4096 | Optional. Restricts access to files within this directory. |
Outputs
Every action writes pipe-delimited lines. A repeating row (list_dir’s entry rows, get_acl’s ace rows on every OS, search_dir’s result rows, find_by_hash’s match rows, read’s and search’s line rows) starts with a literal word naming its kind, then its fields; a single-value field is its own key|value line. There is no placeholder row for “found nothing”: a zero-hit action reports only its trailing count (total_matches|0, matches_found|0). find_by_hash adds up to two rows between its match rows and its count: network_mount_guard|unavailable when the mount table could not be read, then one truncated|... row when it skipped anything or stopped early; only match rows have the three fields a column matcher reads. A match path containing CR or LF shows a space in its place, | is escaped, and a trailing backslash becomes /. Failure ends with an error|<message> line and a non-zero return code, never a placeholder success row (see Result status), with two exceptions: get_signature reports its verdict in signature_status (including unknown and error_0x...) and get_version_info on macOS reports version_status|not_available, both with return code 0. An unknown action name writes unknown action: <name> with return code 1. For get_acl the ace row is ace|<type>|<principal>|<access>|<flags>, with - for an empty field and <access> as permission text on Linux and macOS but a hex access mask on Windows; acl|<none|extended|unsupported>|<acl flags or -> (Linux, macOS) and control|<flags or -> (Windows) are single-value rows.
Two places where the emitted rows and the declared columns differ, so match on the emitted names. delete_lines emits lines_deleted, total_lines_before and total_lines_after, while its definition declares lines_before and lines_after. append’s total_size is not the size after the append (see Caveat 3).
device.filesystem.append — status|bytes_appended|total_size
| Field | Type | Values | Available | Example | Description |
|---|---|---|---|---|---|
status | string | ok | Windows, Linux, macOS | ok | Outcome of the append; the only value the plugin emits on success. |
bytes_appended | int64 | - | Windows, Linux, macOS | 21 | Number of bytes appended, including an inserted newline separator when newline=true and the file did not already end with one. Values: integer (bytes). |
total_size | int64 | - | Windows, Linux, macOS | 40 | Total file size in bytes after the append. Values: integer (bytes). |
device.filesystem.create_temp — path|persist
| Field | Type | Values | Available | Example | Description |
|---|---|---|---|---|---|
path | string | - | Windows, Linux, macOS | D:\yuzu-dev\tmp\yuzu-964a901bfe197402d93f51a7653b0697.tmp | Full path to the created temporary file. Values: free text (path). |
persist | bool | - | Windows, Linux, macOS | true | Echoes the persist parameter — whether the file was kept. |
device.filesystem.create_temp_dir — path|persist
| Field | Type | Values | Available | Example | Description |
|---|---|---|---|---|---|
path | string | - | Windows, Linux, macOS | D:\yuzu-dev\tmp\yuzu-3849a33da8fc555d407ac0f2c67356f9 | Full path to the created temporary directory. Values: free text (path). |
persist | bool | - | Windows, Linux, macOS | true | Echoes the persist parameter — whether the directory was kept. |
device.filesystem.delete_lines — lines_deleted|lines_before|lines_after
| Field | Type | Values | Available | Example | Description |
|---|---|---|---|---|---|
lines_deleted | int32 | - | Windows, Linux, macOS | 1 | Count of lines removed (end_line - start_line + 1, after end_line is clamped to the file’s length). Values: integer. |
lines_before | int32 | - | Windows, Linux, macOS | 2 | Declared here as lines_before, but the plugin’s actual wire field is total_lines_before (filesystem_plugin.cpp:1998) — the file’s total line count before the deletion. The emitted key does not match this column’s declared name; see the filesystem plugin README Caveats. Values: integer. |
lines_after | int32 | - | Windows, Linux, macOS | 1 | Declared here as lines_after, but the plugin’s actual wire field is total_lines_after (filesystem_plugin.cpp:1999) — the file’s total line count after the deletion. Same name mismatch as lines_before. Values: integer. |
device.filesystem.exists — exists|type|size
| Field | Type | Values | Available | Example | Description |
|---|---|---|---|---|---|
exists | bool | - | Windows, Linux, macOS | true | Whether the path exists on the endpoint. |
type | string | file directory other | Windows, Linux, macOS | file | The kind of filesystem entry found. Only emitted when exists is true. |
size | int64 | - | Windows, Linux, macOS | 1405 | Size in bytes for a file entry; 0 for a directory or an “other” entry. Only emitted when exists is true. Values: integer (bytes). |
device.filesystem.file_hash — hash|algorithm|size
| Field | Type | Values | Available | Example | Description |
|---|---|---|---|---|---|
hash | string | - | Windows, Linux, macOS | 9321feab332edbba521c7ea3eb978d9844cb4f62a4730dab9cf60fb79649037d | Hex-encoded digest of the file’s contents. Values: free text (hex digest). |
algorithm | string | sha256 sha1 | Windows, Linux, macOS | sha256 | The hash algorithm actually used. |
size | int64 | - | Windows, Linux, macOS | 1405 | File size in bytes at the time of hashing. Values: integer (bytes). |
device.filesystem.find_by_hash — row_kind|path|size
| Field | Type | Values | Available | Example | Description |
|---|---|---|---|---|---|
row_kind | string | match truncated matches_found network_mount_guard | Windows, Linux, macOS | match | Row shape discriminator (the wire row’s leading tag). Values: match (one per file whose SHA-256 equals the target), truncated (at most one, only when something was skipped or the walk stopped early), matches_found (exactly one, always last), network_mount_guard (at most one, after any match rows and ahead of the other two, only when the mount table could not be read in full). Only match rows are data rows to column matchers; the other kinds have a different field count and never match. |
path | string | - | Windows, Linux, macOS | C:\Windows\System32\drivers\etc\hosts | match rows: full path of a file whose SHA-256 matches the target hash. (A truncated row carries its stop reason in this position — deadline, file_cap, byte_cap, cancelled, max_results, or none when no bound tripped — and a matches_found row the count; neither is a data row to a column matcher.) |
size | int64 | - | Windows, Linux, macOS | 1405 | match rows: the matching file’s size in bytes. (A truncated row carries its counters here and in the fields after it, visited=N|hashed=N|skipped_unreadable=N|skipped_symlink=N|skipped_not_regular=N|skipped_oversize=N|skipped_network_mount=N, and matches_found|N is a two-field summary; neither is a data row to a column matcher.) |
device.filesystem.get_acl — sddl|ace_type|account|access_mask|owner|group|permissions|mode|ace_perms|ace_flags|acl|acl_flags|control
| Field | Type | Values | Available | Example | Description |
|---|---|---|---|---|---|
sddl | string | - | Windows | O:BAD:AI(A;;FA;;;SY)(A;ID;FA;;;SY)(A;ID;FA;;;BA) | Windows-only. SDDL string encoding the owner and DACL. Absent on POSIX. Values: free text (SDDL). |
ace_type | string | allow deny allow_conditional deny_conditional allow_object deny_object other user group mask | Windows, Linux, macOS | allow | One access-control-entry row’s kind; the plugin emits one ace row per entry on every OS. Windows - allow, deny, allow_conditional, deny_conditional, allow_object, deny_object, other (any other ACE type). macOS - allow or deny. Linux - user, group, mask or other (the POSIX ACL tag). |
account | string | - | Windows, Linux, macOS | NT AUTHORITY\SYSTEM | The principal: DOMAIN\account or SID on Windows; |
access_mask | string | - | Windows | 0x001f01ff | Windows-only. The ACE’s access mask, hex-formatted. Values: hex integer, or - for an ACE type the plugin does not decode. |
owner | string | - | Linux, macOS | root | POSIX-only. The file’s owning user name, or the numeric uid when it cannot be resolved. Values: free text. |
group | string | - | Linux, macOS | wheel | POSIX-only. The file’s owning group name, or the numeric gid when it cannot be resolved. Values: free text. |
permissions | string | - | Linux, macOS | rw-r--r-- | POSIX-only. rwx permission string for owner/group/other. Values: 9-character rwx string. |
mode | string | - | Linux, macOS | 0644 | POSIX-only. Octal permission bits. Values: 4-digit octal. |
ace_perms | string | - | Linux, macOS | r-x | POSIX-only. The ACE’s permissions: r/w/x triple on Linux, comma-separated ACL permission names on macOS (- when none). Values: free text. |
ace_flags | string | object_inherit container_inherit no_propagate inherit_only inherited file_inherit directory_inherit limit_inherit only_inherit default - | Windows, Linux, macOS | - | The ACE’s flags, comma-joined, or - when none. Windows - object_inherit, container_inherit, no_propagate, inherit_only, inherited. macOS - inherited, file_inherit, directory_inherit, limit_inherit, only_inherit. Linux - default for an entry of the directory default ACL. |
acl | string | none extended unsupported | Linux, macOS | extended | POSIX-only. Whether the path carries an extended ACL: none, extended, or unsupported (the filesystem cannot carry an ACL, not that none is set). Single-value row. |
acl_flags | string | no_inherit defer_inherit - | macOS | - | macOS-only. ACL-level flags, comma-joined, or - when none. Single-value field of the acl row. |
control | string | protected auto_inherited - | Windows | protected | Windows-only. DACL control flags from the security descriptor, comma-joined, or - when none. Single-value row. |
device.filesystem.get_signature — signature_status
| Field | Type | Values | Available | Example | Description |
|---|---|---|---|---|---|
signature_status | string | - | Windows, macOS | unsigned | Signature verdict. On Windows: valid/unsigned/distrusted/untrusted (Authenticode chain trust). On macOS: valid/unsigned/invalid/unknown — here valid means the code signature’s seal is intact (INTEGRITY only: unmodified since signing), NOT that the code is trusted or notarized; an ad-hoc/self-signed binary reports valid. Values: windows: valid, unsigned, distrusted, untrusted, security_settings_blocked, error_0x |
device.filesystem.get_version_info — file_version|product_version|company_name|file_description|internal_name|original_filename|product_name|legal_copyright|version_status
| Field | Type | Values | Available | Example | Description |
|---|---|---|---|---|---|
file_version | string | - | Windows, macOS | 6.2.26100.9278 | Windows: PE FileVersion (four-part). macOS: CFBundleVersion, the build number. Absent when not carried by the target. Values: free text (version string). |
product_version | string | - | Windows, macOS | 10.0.26100.9278 | Windows: PE ProductVersion (four-part). macOS: CFBundleShortVersionString, the marketing version. Absent when not carried by the target. Values: free text (version string). |
company_name | string | - | Windows | Microsoft Corporation | Windows-only. PE CompanyName string resource, when present. Values: free text. |
file_description | string | - | Windows | Notepad | Windows-only. PE FileDescription string resource, when present. Values: free text. |
internal_name | string | - | Windows | Notepad | Windows-only. PE InternalName string resource, when present. Values: free text. |
original_filename | string | - | Windows | NOTEPAD.EXE.MUI | Windows-only. PE OriginalFilename string resource, when present. Values: free text. |
product_name | string | - | Windows | Microsoft® Windows® Operating System | Windows-only. PE ProductName string resource, when present. Values: free text. |
legal_copyright | string | - | Windows | © Microsoft Corporation. All rights reserved. | Windows-only. PE LegalCopyright string resource, when present. Values: free text. |
version_status | string | not_available | macOS | not_available | macOS-only honest-empty marker: emitted as not_available when the target has no Info.plist or no version keys within it; absent otherwise (Windows never emits this field). |
device.filesystem.list_dir — entry_name|entry_type|entry_size
| Field | Type | Values | Available | Example | Description |
|---|---|---|---|---|---|
entry_name | string | - | Windows, Linux, macOS | hosts | Filename of one directory entry (not a full path). Values: free text. |
entry_type | string | file directory symlink other | Windows, Linux, macOS | file | The entry’s kind. |
entry_size | int64 | - | Windows, Linux, macOS | 1405 | Size in bytes; 0 for directories and symlinks. Values: integer (bytes). |
device.filesystem.read — line_number|content|total_lines|file_size
| Field | Type | Values | Available | Example | Description |
|---|---|---|---|---|---|
line_number | int32 | - | Windows, Linux, macOS | 1 | 1-based line number of this returned line. Values: integer. |
content | string | - | Windows, Linux, macOS | # Copyright (c) 1993-2009 Microsoft Corp. | The line’s text, with a trailing CRLF’s carriage return stripped. Values: free text. |
total_lines | int32 | - | Windows, Linux, macOS | 37 | Total number of lines in the file, counted past the returned window. Values: integer. |
file_size | int64 | - | Windows, Linux, macOS | 1405 | File size in bytes. Values: integer (bytes). |
device.filesystem.replace — replacements_made|file_size_before|file_size_after|dry_run
| Field | Type | Values | Available | Example | Description |
|---|---|---|---|---|---|
replacements_made | int32 | - | Windows, Linux, macOS | 2 | Count of replacements performed (or that would be performed, when dry_run=true). Values: integer. |
file_size_before | int64 | - | Windows, Linux, macOS | 42 | File size in bytes before the replace. Values: integer (bytes). |
file_size_after | int64 | - | Windows, Linux, macOS | 18 | File size in bytes after the replace. Values: integer (bytes). |
dry_run | bool | - | Windows, Linux, macOS | not observed in any capture (every capture ran with dry_run=false) | Present and true only when dry_run=true was requested; the plugin omits this line entirely otherwise. Values: true (field absent when dry_run=false). |
device.filesystem.search — line_number|content|total_matches
| Field | Type | Values | Available | Example | Description |
|---|---|---|---|---|---|
line_number | int32 | - | Windows, Linux, macOS | 19 | 1-based line number of a matching line. Values: integer. |
content | string | - | Windows, Linux, macOS | # localhost name resolution is handled within DNS itself. | Full text of the matching line, with a trailing carriage return stripped. Values: free text. |
total_matches | int32 | - | Windows, Linux, macOS | 3 | Count of matching lines returned in this result, capped by max_matches. Values: integer. |
device.filesystem.search_dir — path|entry_type|total_matches
| Field | Type | Values | Available | Example | Description |
|---|---|---|---|---|---|
path | string | - | Windows, Linux, macOS | not observed in any capture (every platform's capture returned total_matches|0 with no result rows) | Absolute path of a matched file or directory. |
entry_type | string | file directory | Windows, Linux, macOS | not observed in any capture (every platform's capture returned total_matches|0 with no result rows) | Whether the matched entry is a file or a directory, as classified by match_type. |
total_matches | int32 | - | Windows, Linux, macOS | 0 | Count of matching entries returned in this result, capped by max_results. Values: integer. |
device.filesystem.write_content — status|bytes_written|path
| Field | Type | Values | Available | Example | Description |
|---|---|---|---|---|---|
status | string | ok | Windows, Linux, macOS | ok | Outcome of the write; the only value the plugin emits on success. |
bytes_written | int64 | - | Windows, Linux, macOS | 20 | Number of bytes written to the file. Values: integer (bytes). |
path | string | - | Windows, Linux, macOS | C:\Windows\Temp\yuzu_capture_bs7ucomo\yuzu_capture_tmp.txt | Canonicalized path that was written. |
workflow.config_search_and_replace — replacements_made|file_size_before|file_size_after
| Field | Type | Values | Available | Example | Description |
|---|---|---|---|---|---|
replacements_made | int32 | - | all | - | - |
file_size_before | int64 | - | all | - | - |
file_size_after | int64 | - | all | - | - |
workflow.version_compliance_check — file_version|product_version|company_name|product_name|file_description
| Field | Type | Values | Available | Example | Description |
|---|---|---|---|---|---|
file_version | string | - | all | - | - |
product_version | string | - | all | - | - |
company_name | string | - | all | - | - |
product_name | string | - | all | - | - |
file_description | string | - | all | - | - |
Result status
| Status | Completeness | Provenance | When |
|---|---|---|---|
UNDECLARED | UNKNOWN | (empty) | every action other than find_by_hash, on every OS; and a find_by_hash run that hit no bound, read its mount table in full and skipped nothing, or skipped only by design (a symlink, a FIFO or device, a file over 10 GB: its truncated row still counts them). A refusal (bad path, missing parameter, wrong file type, tool failure) is one error|<message> output line with return code 1, so a caller must parse that line, not a status field, to detect failure |
CONSTRAINED | PARTIAL | filesystem:deadline, filesystem:file_cap, filesystem:byte_cap, filesystem:cancelled, filesystem:max_results, filesystem:mount_table_unreadable, filesystem:skipped_unreadable or filesystem:skipped_network_mount (the first that applies, in that order) | find_by_hash only, return code 0: a bound tripped, the mount table could not be read in full so the network-mount guard was off, an entry or directory could not be read, or a network mount was refused |
Reading a find_by_hash result. The status number is stored with the response and, when not zero, rides on the agent-transition event of the execution event stream (GET /api/v1/events), but the REST and MCP response rows and the CSV export do not carry it, and the completeness and provenance reach only the agent log (the wire carries the status enum alone, plugin_result_status in agent.proto). The rows are the signal you can see: the search is incomplete when the truncated reason is not none, when skipped_unreadable or skipped_network_mount is above zero, or when a network_mount_guard|unavailable row is present. matches_found|0 beside either row means “not found in the part examined”, never “not present”. A result-set column matcher takes only rows of the declared width, so it sees only match rows and cannot pick out the devices a search covered only in part: read the output rows for that. The bounds are fixed, not parameters: search a large tree one subdirectory at a time, and set stagger_seconds on a fleet-wide dispatch so every agent does not hash at once.
The truncated row’s counters:
skipped_unreadable: an entry, directory or root that could not be opened or listed (permissions, vanished, a Linux pseudo-filesystem directory, a macOS dataless item, a Windows cloud placeholder, offline file or unknown reparse tag), an entry whose type could not be read, or an enumeration error (which also ends the walk). Makes the resultCONSTRAINED.skipped_network_mount: a root on a network mount, a UNC share or a non-fixed Windows drive, or (macOS and Linux only) a subtree on a network mount, refused before anything under it was read. Makes the resultCONSTRAINED.skipped_symlink: a symlink or junction, including one the no-follow open met, never followed. By design; does not constrain.skipped_not_regular: a FIFO, device or socket, or a non-file found at open time, never read. Does not constrain.skipped_oversize: a file over 10 GB (by design, does not constrain), or one larger than the bytes left in the 4 GiB budget (the walk goes on, the reason becomesbyte_cap, and the result isCONSTRAINED).visited,hashed: entries seen; bytes admitted to the hasher.
The output never names a skipped path: when a counter is above zero, narrow it down by searching subdirectories.
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 (90-day default retention), queryable at
/api/responses/{id}. - Not consumed by daily-sync inventory, the Echo warehouse, DEX or metrics: no sync source, Echo or DEX consumer reads
device.filesystem.*results, only the generic result parser does. Nothing runs on a schedule; the plugin executes only when an operator or workflow dispatches one of its 16 definitions. - Sensitivity.
get_aclrows carry local account names (ownerandgroupon Linux and macOS, and the principal of everyacerow on all three OSes), potentially real usernames.get_version_infoandget_signaturerows name specific installed software (company_name,product_name,file_description), an installed-software inventory by another route.read,searchandwrite_contentcarry whatever the operator pointed them at, which can include credentials or personal data in file contents or usernames in paths. - Siblings:
filesystem_posture.mounts,.quotasand.snapshotsare a separate, similarly named, read-only plugin (mount, quota and snapshot inventory only; no read, hash or write actions); do not confuse the two. Two shipped workflow definitions chain this plugin’s actions:workflow.config_search_and_replace(int2_chaining_examples.yaml) searches a config file then dispatchesreplace, andworkflow.version_compliance_checkdispatchesget_version_infoand compares the result against a minimum version. - MCP / REST. Discover:
discover_plugins(summary), thenyuzu://plugin-docs(this page as data), thendiscover_instructionsorget_definition("device.filesystem.file_hash"). Run:execute_instruction {definition_id, parameters}. Read:/api/responses/{id}.
Sample output
Windows — captured: windows Windows 10.0.26200 x86_64 · bare-metal · 2026-10-04 · LocalSystem (elevated) · leg-hash 802f5467fb09
== action=exists path=C:\Windows\System32\drivers\etc\hosts
exists|true
type|file
size|1405
[result_status] UNDECLARED / UNKNOWN
== action=list_dir path=C:\Windows\System32\drivers\etc
entry|hosts|file|1405
entry|hosts.ics|file|444
entry|lmhosts.sam|file|3683
entry|networks|file|407
entry|protocol|file|1358
entry|services|file|17635
[result_status] UNDECLARED / UNKNOWN
== action=file_hash path=C:\Windows\System32\drivers\etc\hosts
hash|9321feab332edbba521c7ea3eb978d9844cb4f62a4730dab9cf60fb79649037d
algorithm|sha256
size|1405
[result_status] UNDECLARED / UNKNOWN
== action=create_temp
path|C:\WINDOWS\TEMP\yuzu-391d5e4c88ea2e641a93b5dd96849117.tmp
persist|true
[result_status] UNDECLARED / UNKNOWN
== action=create_temp_dir
path|C:\WINDOWS\TEMP\yuzu-41a05a87a8d5aae48423ac53334d0429
persist|true
[result_status] UNDECLARED / UNKNOWN
== action=read path=C:\Windows\System32\drivers\etc\hosts
line|1|# Copyright (c) 1993-2009 Microsoft Corp.
line|2|#
line|3|# This is a sample HOSTS file used by Microsoft TCP/IP for Windows.
line|4|#
line|5|# This file contains the mappings of IP addresses to host names. Each
line|6|# entry should be kept on an individual line. The IP address should
line|7|# be placed in the first column followed by the corresponding host name.
line|8|# The IP address and the host name should be separated by at least one
line|9|# space.
line|10|#
line|11|# Additionally, comments (such as these) may be inserted on individual
line|12|# lines or following the machine name denoted by a '#' symbol.
… 12 of 39 rows shown
[result_status] UNDECLARED / UNKNOWN
== action=get_acl path=C:\WINDOWS\TEMP\yuzu_acl_599f97e1\cond.txt
sddl|O:BAD:PAI(XA;;FA;;;WD;(Member_of {SID(BA)}))
control|protected,auto_inherited
ace|allow_conditional|\Everyone|0x001f01ff|-
[result_status] UNDECLARED / UNKNOWN
== action=get_signature path=C:\Windows\System32\notepad.exe
signature_status|unsigned
[result_status] UNDECLARED / UNKNOWN
== action=find_by_hash directory=C:\Windows\System32\drivers\etc sha256=031da282a9436540be78beab34801f32c1429491e881838b0ddf4f8c16e122cc
match|C:\Windows\System32\drivers\etc\hosts|1224
matches_found|1
[result_status] UNDECLARED / UNKNOWN
== action=search_dir root=C:\Windows\System32\drivers\etc pattern=*hosts*
total_matches|0
[result_status] UNDECLARED / UNKNOWN
== action=get_version_info path=C:\Windows\System32\notepad.exe
file_version|6.2.26100.9278
product_version|10.0.26100.9278
company_name|Microsoft Corporation
file_description|Notepad
internal_name|Notepad
original_filename|NOTEPAD.EXE.MUI
product_name|Microsoft® Windows® Operating System
legal_copyright|© Microsoft Corporation. All rights reserved.
[result_status] UNDECLARED / UNKNOWN
== action=search path=C:\Windows\System32\drivers\etc\hosts pattern=localhost
match|19|# localhost name resolution is handled within DNS itself.
match|20|# 127.0.0.1 localhost
match|21|# ::1 localhost
total_matches|3
[result_status] UNDECLARED / UNKNOWN
== action=replace path=C:\WINDOWS\TEMP\yuzu_capture_599f97e1\yuzu_capture_tmp.txt search=yuzu-capture replace=yuzu-replaced
replacements_made|1
file_size_before|24
file_size_after|12
[result_status] UNDECLARED / UNKNOWN
== action=write_content path=C:\WINDOWS\TEMP\yuzu_capture_599f97e1\yuzu_capture_tmp.txt content="yuzu-capture written" overwrite=true
status|ok
bytes_written|20
path|C:\Windows\Temp\yuzu_capture_599f97e1\yuzu_capture_tmp.txt
[result_status] UNDECLARED / UNKNOWN
== action=append path=C:\WINDOWS\TEMP\yuzu_capture_599f97e1\yuzu_capture_tmp.txt content="yuzu-capture appended"
status|ok
bytes_appended|22
total_size|20
[result_status] UNDECLARED / UNKNOWN
== action=delete_lines path=C:\WINDOWS\TEMP\yuzu_capture_599f97e1\yuzu_capture_tmp.txt start_line=1 end_line=1
lines_deleted|1
total_lines_before|2
total_lines_after|1
[result_status] UNDECLARED / UNKNOWN
macOS — captured: macos macOS 26.6.2 arm64 · bare-metal · 2026-10-04 · euid 501 (jsmith) · leg-hash 802f5467fb09
== action=exists path=/etc/hosts
exists|true
type|file
size|213
[result_status] UNDECLARED / UNKNOWN
== action=list_dir path=/etc
entry|manpaths|file|36
entry|rc.common|file|1560
entry|auto_master|file|195
entry|csh.login|file|121
entry|syslog.conf|file|96
entry|krb5.keytab|file|1946
entry|sudoers.d|directory|0
entry|ssl|directory|0
entry|csh.logout|file|39
entry|aliases.db|file|16384
entry|bashrc_Apple_Terminal|file|9293
entry|yuzu|directory|0
… 12 of 76 rows shown
[result_status] UNDECLARED / UNKNOWN
== action=file_hash path=/etc/hosts
hash|c7dd0e2ed261ce76d76f852596c5b54026b9a894fa481381ffd399b556c0e2da
algorithm|sha256
size|213
[result_status] UNDECLARED / UNKNOWN
== action=create_temp
path|/var/folders/hq/lc3t_rsx2blfc8rhys6kzh4r0000gn/T//yuzu-oqRhjd.tmp
persist|true
[result_status] UNDECLARED / UNKNOWN
== action=create_temp_dir
path|/var/folders/hq/lc3t_rsx2blfc8rhys6kzh4r0000gn/T//yuzu-sPr64l
persist|true
[result_status] UNDECLARED / UNKNOWN
== action=read path=/etc/hosts
line|1|##
line|2|# Host Database
line|3|#
line|4|# localhost is used to configure the loopback interface
line|5|# when the system is booting. Do not change this entry.
line|6|##
line|7|127.0.0.1 localhost
line|8|255.255.255.255 broadcasthost
line|9|::1 localhost
total_lines|9
file_size|213
[result_status] UNDECLARED / UNKNOWN
== action=get_acl path=/tmp/yuzu_test_acl_Rw0R2f/acl_file
owner|jsmith
group|wheel
permissions|rw-r--r--
mode|0644
acl|extended|-
ace|deny|group:staff|delete|-
ace|allow|user:_www|read,write|file_inherit
[result_status] UNDECLARED / UNKNOWN
== action=get_signature path=/bin/ls
signature_status|valid
[result_status] UNDECLARED / UNKNOWN
== action=find_by_hash directory=/etc sha256=c7dd0e2ed261ce76d76f852596c5b54026b9a894fa481381ffd399b556c0e2da
match|/private/etc/hosts|213
truncated|none|visited=261|hashed=2997701|skipped_unreadable=17|skipped_symlink=4|skipped_not_regular=0|skipped_oversize=0|skipped_network_mount=0
matches_found|1
[result_status] CONSTRAINED / PARTIAL / filesystem:skipped_unreadable
== action=search_dir root=/etc pattern=*hosts*
total_matches|0
[result_status] UNDECLARED / UNKNOWN
== action=get_version_info path=/bin/ls
version_status|not_available
[result_status] UNDECLARED / UNKNOWN
== action=search path=/etc/hosts pattern=localhost
match|4|# localhost is used to configure the loopback interface
match|7|127.0.0.1 localhost
match|9|::1 localhost
total_matches|3
[result_status] UNDECLARED / UNKNOWN
== action=replace path=/var/folders/hq/lc3t_rsx2blfc8rhys6kzh4r0000gn/T//yuzu_capture_MaI1z1B3/yuzu_capture_tmp.txt search=yuzu-capture replace=yuzu-replaced
replacements_made|2
file_size_before|37
file_size_after|13
[result_status] UNDECLARED / UNKNOWN
== action=write_content path=/var/folders/hq/lc3t_rsx2blfc8rhys6kzh4r0000gn/T//yuzu_capture_MaI1z1B3/yuzu_capture_tmp.txt content="yuzu-capture written" overwrite=true
status|ok
bytes_written|20
path|/private/var/folders/hq/lc3t_rsx2blfc8rhys6kzh4r0000gn/T/yuzu_capture_MaI1z1B3/yuzu_capture_tmp.txt
[result_status] UNDECLARED / UNKNOWN
== action=append path=/var/folders/hq/lc3t_rsx2blfc8rhys6kzh4r0000gn/T//yuzu_capture_MaI1z1B3/yuzu_capture_tmp.txt content="yuzu-capture appended"
status|ok
bytes_appended|22
total_size|20
[result_status] UNDECLARED / UNKNOWN
== action=delete_lines path=/var/folders/hq/lc3t_rsx2blfc8rhys6kzh4r0000gn/T//yuzu_capture_MaI1z1B3/yuzu_capture_tmp.txt start_line=1 end_line=1
lines_deleted|1
total_lines_before|2
total_lines_after|1
[result_status] UNDECLARED / UNKNOWN
Linux — captured: linux Debian GNU/Linux 13 (trixie) aarch64 · container · 2026-10-04 · euid 0 · leg-hash 802f5467fb09
== action=exists path=/etc/hosts
exists|true
type|file
size|177
[result_status] UNDECLARED / UNKNOWN
== action=list_dir path=/etc
entry|nsswitch.conf|file|494
entry|update-motd.d|directory|0
entry|dpkg|directory|0
entry|skel|directory|0
entry|rc1.d|directory|0
entry|xattr.conf|file|681
entry|fstab|file|37
entry|logrotate.d|directory|0
entry|libaudit.conf|file|191
entry|os-release|file|286
entry|pam.d|directory|0
entry|issue.net|file|20
… 12 of 82 rows shown
[result_status] UNDECLARED / UNKNOWN
== action=file_hash path=/etc/hosts
hash|5ed49ae76ca451e9f063a9b28d11f6e8784a851e983664cb495206b75453c412
algorithm|sha256
size|177
[result_status] UNDECLARED / UNKNOWN
== action=create_temp
path|/tmp/yuzu-GqGF5B.tmp
persist|true
[result_status] UNDECLARED / UNKNOWN
== action=create_temp_dir
path|/tmp/yuzu-CtDWtd
persist|true
[result_status] UNDECLARED / UNKNOWN
== action=read path=/etc/hosts
line|1|127.0.0.1 localhost
line|2|::1 localhost ip6-localhost ip6-loopback
line|3|fe00:: ip6-localnet
line|4|ff00:: ip6-mcastprefix
line|5|ff02::1 ip6-allnodes
line|6|ff02::2 ip6-allrouters
line|7|172.17.0.3 yuzu-capture-host
total_lines|7
file_size|177
[result_status] UNDECLARED / UNKNOWN
== action=get_acl path=/tmp/yuzu_test_acl/f
owner|root
group|root
permissions|rw-rwxr--
mode|0674
acl|extended|-
ace|user|-|rw-|-
ace|user|1001|rw-|-
ace|group|-|r--|-
ace|group|1002|r-x|-
ace|mask|-|rwx|-
ace|other|-|r--|-
[result_status] UNDECLARED / UNKNOWN
== action=get_signature path=/bin/ls
error|code signature verification is not supported on this platform
[result_status] UNDECLARED / UNKNOWN
[rc] 1
== action=find_by_hash directory=/etc sha256=ed9ab7f958d4c01aa81e80a108e2c69db299b3e7cd6577b286905f1ee4c47b4c
match|/etc/hosts|413
truncated|none|visited=1676|hashed=1922709|skipped_unreadable=0|skipped_symlink=720|skipped_not_regular=0|skipped_oversize=0|skipped_network_mount=0
matches_found|1
[result_status] UNDECLARED / UNKNOWN
== action=search_dir root=/etc pattern=*hosts*
total_matches|0
[result_status] UNDECLARED / UNKNOWN
== action=get_version_info path=/bin/ls
error|version info extraction is not supported on this platform
[result_status] UNDECLARED / UNKNOWN
[rc] 1
== action=search path=/etc/hosts pattern=localhost
match|1|127.0.0.1 localhost
match|2|::1 localhost ip6-localhost ip6-loopback
total_matches|2
[result_status] UNDECLARED / UNKNOWN
== action=replace path=/tmp/yuzu_capture_vySwPCm6/yuzu_capture_tmp.txt search=yuzu-capture replace=yuzu-replaced
replacements_made|2
file_size_before|37
file_size_after|13
[result_status] UNDECLARED / UNKNOWN
== action=write_content path=/tmp/yuzu_capture_vySwPCm6/yuzu_capture_tmp.txt content="yuzu-capture written" overwrite=true
status|ok
bytes_written|20
path|/tmp/yuzu_capture_vySwPCm6/yuzu_capture_tmp.txt
[result_status] UNDECLARED / UNKNOWN
== action=append path=/tmp/yuzu_capture_vySwPCm6/yuzu_capture_tmp.txt content="yuzu-capture appended"
status|ok
bytes_appended|22
total_size|20
[result_status] UNDECLARED / UNKNOWN
== action=delete_lines path=/tmp/yuzu_capture_vySwPCm6/yuzu_capture_tmp.txt start_line=1 end_line=1
lines_deleted|1
total_lines_before|2
total_lines_after|1
[result_status] UNDECLARED / UNKNOWN
Caveats and known gaps
- Size and count limits apply, and most stop without a marker.
read,search,replaceanddelete_linesrefuse a file over 100 MB, andwrite_contentrefuses content over 100 MB, each with anerror|row;file_hashrefuses a file over 10 GB;appendhas no size check.list_dirstops at 1000 entries and says so with awarning|row, butsearch_dir(default 100 results, at most 1000) andsearch(default 100 lines, at most 10000) simply stop at their cap, so a count equal to the cap in force (the default unless you raised it) means there may be more. find_by_hashis a bounded sweep, not proof of absence. Whichever of 20 seconds, 50,000 entries visited or 100 matches comes first ends the walk, at most 4 GiB is hashed (a file larger than what is left is skipped and the reason becomesbyte_cap), it goes at most 10 levels deep (max_depth, default 3; the agent reads a value below 1 as 1, above 10 as 10 and one with no leading integer as 3), and these bounds are fixed in the plugin. What it skipped is counted in thetruncatedrow, butmatches_found|0still means only “not found in the part examined” (Result status). The deadline is checked between system calls, so a single blocked read or directory listing is not interrupted, and only the agent’s shutdown cancels a running search, never a single command (#4875). Residuals: on Windows, and on Linux withoutopenat2(a kernel before 5.6, or a seccomp profile that denies it), only the final path component is opened no-follow (#6348); a directory whose permissions change after the readability check is skipped without being counted (a symlink swapped in between that check’saccess()and its no-follow directory open is refused there and counted unreadable); on macOS a directory swapped for a symlink between its classification and its descent is listed (names only: each child then fails its own no-follow open) and can block on a dead mount. The mount guard is a deny-list of filesystem names read once at the start, so a stacked filesystem, a barefusemount and a mount made mid-walk are not caught, and a root reached through a symlink, or spelt in a different letter case on macOS, reachesvalidate_pathbefore the canonical re-check can refuse it (#4875). Windows hands the walk no mount list: only the root’s own spelling and drive type are judged, so a root that is itself a link to a share is not refused. When the mount table cannot be read in full (an unreadable or over-16 MiB/proc/self/mountinfo, a failedgetmntinfo_r_np) the walk runs unguarded, logs once and reportsnetwork_mount_guard|unavailableandCONSTRAINED. A Windows drive letter that does not exist is answereddirectory is not accessible, not as a mount. An agent older than this release still runs the unbounded, symlink-following walk.appendreports a staletotal_size. The size is read while the written bytes are still buffered, so on the captured 20-byte file it reportedtotal_size|20after appending 22 bytes; treat it as the size before the append and useexistsorreadto get the real size. Because the same check runs before the buffer is flushed, a write failure at flush time (for example a full disk) can still printstatus|ok.base_diris the caller’s own narrowing, and path checks leave a small race. It is an optional parameter on each request, not an agent-side policy, so a request that omits it is not restricted, andfind_by_hashandcreate_tempdo not take it at all. The re-validation just beforecodesignandplutilnarrows but cannot close the window in which a writer insidebase_dirswaps the path for a symlink, sobase_dirmust not be writable by lower-privileged principals.- Signature and version answers are narrow. On Windows
get_signatureverifies only an embedded Authenticode signature and does not check revocation, so a file signed only through a Windows catalog is expected to reportunsigned; the captured sample showsnotepad.exereportingunsigned, which is consistent with that (the cause is inferred, not shown). On macOSvalidmeans the code signature’s seal is intact, not that the code is trusted or notarized, and a timed-out or truncatedcodesignorplutilrun is reported asunknownornot_availablerather than as a failure.
Source and tests
- Plugin:
agents/plugins/filesystem/src/filesystem_acl_parsers.hpp·agents/plugins/filesystem/src/filesystem_find_by_hash.hpp·agents/plugins/filesystem/src/filesystem_macos_sig.hpp·agents/plugins/filesystem/src/filesystem_plugin.cpp - Definitions:
content/definitions/filesystem.yaml·content/definitions/t2_capabilities.yaml·content/definitions/t2_chaining_examples.yaml - Capability rows:
server/core/src/capability_decls/plugin_action_catalogue_a.hpp - Tests:
tests/test_filesystem_definition.py·tests/unit/test_filesystem_acl_parsers.cpp·tests/unit/test_filesystem_actions.cpp·tests/unit/test_filesystem_find_by_hash.cpp·tests/unit/test_filesystem_find_by_hash_dispatcher.cpp·tests/unit/test_filesystem_get_acl_dispatcher.cpp·tests/unit/test_filesystem_local_dispatcher.cpp·tests/unit/test_filesystem_macos_sig.cpp·tests/unit/test_filesystem_posture_local_dispatcher.cpp·tests/unit/test_filesystem_posture_parsers.cpp·tests/unit/test_filesystem_read.cpp - Privilege row:
docs/agent-privilege-model.md