1
0
Fork
You've already forked filefacts
0
expose characteristics and metadata for a wide variety of filetypes
  • Rust 99.6%
  • Makefile 0.4%
2026年07月13日 21:31:07 +02:00
.tuna complete the migration 2026年05月23日 20:23:27 -04:00
docs fetch package.json refs 2026年06月17日 14:26:42 -04:00
scripts add precommit hook 2026年06月17日 14:26:42 -04:00
src crx: Improve signed-unverified detection 2026年07月13日 21:31:07 +02:00
tests Add support for PE CLR metadata 2026年07月11日 17:01:00 +02:00
vendor/tree-sitter-clojure add batch AST 2026年06月01日 21:42:05 -04:00
.gitignore Initial commit 2026年05月19日 13:58:36 +02:00
Cargo.lock update deps 2026年07月12日 20:40:17 +02:00
Cargo.toml implement an auto-clearing cache 2026年07月12日 19:41:49 +02:00
LICENSE Initial commit 2026年05月19日 13:58:36 +02:00
Makefile Add more registry, and make fix 2026年06月23日 17:01:49 -04:00
README.md update docs 2026年07月07日 08:53:54 +02:00

filefacts

A Rust library that reads a file and tells you what is in it — built for the people who turn files into feature vectors. Malware classifiers, triage systems, dataset builders: anything that needs a dense, honest description of a file as model input. Extracted from cleave, it is the extraction layer that sits underneath Atomdrift's own models — and it is designed to sit underneath yours. (If you only want to understand a single file, it does that too.)

The pitch to an ML security researcher is one word: reproducible. Same bytes in, same facts out, every time — no subprocesses, no file/objdump/openssl/ tar shelling out, no locale or tool-version drift leaking into your features. A vector you extract during a training run is the same vector you extract when you re-triage that sample in an incident six months later. That property is hard to get from a pile of CLIs glued together, and it is the whole point here.

Give it bytes. filefacts identifies the format, parses it once, and returns a ParsedFile of lazy, cached views — read what you want, the rest costs nothing:

letfacts=filefacts::open(&bytes)?;letentropy=facts.metrics();// computed on demand
letstrings=facts.text();// the other views never ran

filefacts <path> writes the same structure as JSON, one object per file, ready to fold straight into a feature pipeline.

The output

View What it carries
fileid Format identification — type, container, compression. Free; no view computed.
values Residual structural fields, navigable as a JSON tree.
text Byte-scan strings (ASCII + UTF-16LE) with offsets.
literals Parser-extracted language string literals.
comments Comments recovered from source and documents.
metrics Derived numeric features — entropy, sizes, counts.
sections Binary section / segment listings.
symbols Unified named entities — imports, exports, functions, calls, members, identifiers — tagged by kind.
archive_members Entries inside archives, recursively.
source_ast tree-sitter AST facts for source files.
errors Recoverable extractor diagnostics — never a panic.

Every fact carries a byte-range span pointing at the evidence it was derived from, so a downstream model (or a human) can trace any feature back to the exact region of the file that produced it. The output schema is versioned (SCHEMA_VERSION): field additions are non-breaking; renames or semantic changes bump it, so a pipeline can pin a schema and trust it across releases.

The signals that discriminate

filefacts surfaces the features that actually separate benign files from the things they imitate — not a metadata dump:

  • Entropy, windowed. Byte and per-section entropy, plus a windowed scan that surfaces a concealed high-entropy region (peak value + offset) even when the file's overall entropy looks unremarkable — the classic "small encrypted blob hidden in a large benign file" tell.
  • Structure over trust. Section layout, import and symbol tables, signature validity (Authenticode verified in-process with pure-Rust crypto), and extension/content mismatches.
  • Supply-chain identity. Registry and package metadata parsed into structured fields, including a security_hold flag and a version_removed metric for packages pulled from their registry.
  • Stego surface. Per-channel entropy, edge density, and histogram flatness for images.

What it parses

  • Executables — PE, ELF (with DWARF), Mach-O, Java class, Python bytecode, WebAssembly, Android DEX.
  • Archives & packages — zip, tar (+ gz/bz2/xz/zst), 7z, rar, deb, rpm, pkg, cab, CHM, DMG, CRX, XPI, WHL, JAR, VSIX, npm, gem, nupkg, Rust crate, Python sdist, OCI container images.
  • Documents — PDF, RTF, OOXML, OLE2 (legacy Office, MSI, MSG), LNK, plist, AppleScript. Authenticode is verified in-process.
  • Images — JPEG, PNG, with the stego metrics above.
  • Structured — JSON, YAML, TOML, XML, SVG, pickle, Dockerfile, Makefile, systemd units, .desktop, GitHub Actions, lockfiles, and package manifests.
  • Source — tree-sitter ASTs for 20+ languages: JavaScript, TypeScript, Python, Go, Rust, Java, C, C#, Bash, Batch, PowerShell, PHP, Ruby, Lua, Scala, Swift, Objective-C, Kotlin, Clojure, Elixir, Groovy, Zig, plus Cypher/CQL and Python template files.

Install

make install # build + install via cargo
brew tap atomdrift/tap # one-time, macOS / Linux
brew install filefacts

make install builds the release binary and copies it to the first writeable location on your PATH. As a library, add filefacts = "1.2" to Cargo.toml.

filefacts suspect.bin # JSON facts for one file
filefacts /tmp/samples # recurse a directory

Performance

In-process and single-pass by design: one walk per file, no forking. Views are cached on disk as zstd-compressed bincode keyed by SHA-256, so a second pass over the same corpus is nearly free — handy when you re-extract features after tweaking a downstream model. The cache self-cleans, pruning to 90% of capacity under pressure rather than aging entries out on a timer. On source and script files with no sign of XOR logic, the XOR seed search is skipped entirely.


Source and issues live on Codeberg. Apache 2.0.