EyeON is a CLI tool that allows users to collect software metadata from their machines for threat and inventory analysis. It can be used to quickly generate manifests of installed software or potential firmware patches. For deeper analysis and reporting workflows, see the companion project pEyeON-Analytics.
For normal field use, you do not need to clone this repository. Download the two wrapper scripts, pull the published container image, run a batch parse, and then summarize the newest batch.
curl -O https://raw.githubusercontent.com/LLNL/pEyeON/main/eyeon-parse.sh
curl -O https://raw.githubusercontent.com/LLNL/pEyeON/main/eyeon-batch-summary.sh
chmod +x eyeon-parse.sh eyeon-batch-summary.sh
docker pull ghcr.io/llnl/peyeon:latest
mkdir -p "$HOME/data/eyeon"
./eyeon-parse.sh TESTSITE /path/to/software "$HOME/data/eyeon"
./eyeon-batch-summary.shNotes:
TESTSITEis your site or utility identifier and becomes part of the batch directory name./path/to/softwareshould point to the directory you want to scan.- If you omit the dataset path,
eyeon-parse.shusesdatasets.dataset_pathfromEyeOnData.tomlwhen present, otherwise$HOME/data/eyeon.
For deeper analysis of collected metadata, see the companion project pEyeON-Analytics.
Validation is important when installing new software. Existing tools use a hash/signature check to validate that the software has not been tampered. Knowing that the software works as intended saves a lot of time and energy, but just performing these hash/signature checks doesn't provide all the information needed to understand supply chain threats.
EyeON provides an automated, consistent process across users to scan software files used for operational technologies. Its findings can be used to generate reports that track software patterns, shedding light on supply chain risks. This tool's main capabilities are focused on increasing the visibility of OT software landscape.
Eyeon can also be run in linux or WSL.
The simplest install can be done with pip:
pip install peyeonHowever, this does not install several key dependencies, namely libmagic, ssdeep, and tlsh. A better way to install is via the container or install scripts on the github page.
The container images include the main extraction dependencies such as ssdeep, libmagic, tlsh, and detect-it-easy.
For most users, the recommended container workflow is eyeon-parse.sh plus eyeon-batch-summary.sh from the quickstart above. The direct docker run and podman run examples below are mainly useful for development, debugging, and image validation.
The primary container image is published to GHCR as a multi-arch image. The same tag works on both amd64 and arm64 hosts, and Docker will pull the matching architecture automatically.
For normal users and production workflows, use the released latest tag from main.
docker pull ghcr.io/llnl/peyeon:latest
docker run --rm ghcr.io/llnl/peyeon:latest eyeon --helpFor branch validation and other development work, use a branch-scoped or commit-scoped dev tag from the same package instead of latest:
docker pull ghcr.io/llnl/peyeon:dev-<branch>
docker run --rm ghcr.io/llnl/peyeon:dev-<branch> eyeon --help
docker pull ghcr.io/llnl/peyeon:dev-<sha>
docker run --rm ghcr.io/llnl/peyeon:dev-<sha> eyeon --helpdocker build -f builds/Dockerfile -t peyeon .
docker run --rm -it -v "$(pwd):/workdir:Z" peyeon /bin/bashpodman build -t peyeon -f builds/podman.Dockerfile .
podman run --rm -it -v "$(pwd):/workdir:rw" peyeon /bin/bashAlternatively, to install on a clean Ubuntu or RHEL8/9 VM:
Ubuntu installs are split into root and user steps. Run the system dependency install once, then run the user environment setup whenever you want to recreate or refresh the virtual environment from the current checkout.
sudo bash builds/install-ubuntu-root.sh
bash builds/install-ubuntu-user.shThe compatibility wrapper builds/install-ubuntu.sh will run the root script when invoked with sudo, and the user script otherwise.
If you previously ran an older Ubuntu install flow entirely with sudo, you may need to clean up Surfactant's temp state once:
sudo rm -f /tmp/.surfactant_extracted_dirs.jsonOn an Apple Silicon Mac, a native Ubuntu VM can be created with Multipass:
multipass launch 24.04 --name eyeon-arm --cpus 4 --memory 8G --disk 40G
multipass mount ~/git/LLNL/pEyeON eyeon-arm:/workspace/pEyeON
multipass shell eyeon-arm
cd /workspace/pEyeON
sudo bash builds/install-ubuntu-root.sh
bash builds/install-ubuntu-user.shVerify the CLI inside the VM:
source eye/bin/activate
eyeon --helpwget https://raw.githubusercontent.com/LLNL/pEyeON/refs/heads/main/builds/install-rhel.sh
chmod +x install-rhel.sh && ./install-rhel.shTo request other options for install, please create an issue on our GitHub page.
This section starts with the recommended batch workflow for normal users. Lower-level eyeon CLI usage follows after that for development and direct library/CLI use.
eyeon-parse.sh is the primary container wrapper for batch parsing. It treats the container as compute only:
- the source directory is mounted read-only at
/source - the dataset root is mounted read-write at
/workdir - parse output is written directly back to the host under the dataset root
The wrapper creates a timestamped output directory named <timestamp>_<UTIL_CD> under the dataset path and then runs eyeon parse inside the container.
Option form:
./eyeon-parse.sh --util-cd UTIL_CD --dir SOURCE --dataset-path DATASET_PATH --threads 8Positional form:
./eyeon-parse.sh UTIL_CD SOURCE [DATASET_PATH] [THREADS]Examples:
./eyeon-parse.sh TESTSITE ./samples /data/eyeon
./eyeon-parse.sh TESTSITE ./samples /data/eyeon 16
./eyeon-parse.sh TESTSITE ./samples 16THREADS defaults to 8.
If DATASET_PATH is not provided, the wrapper uses datasets.dataset_path from EyeOnData.toml. If that is also unset, it falls back to $HOME/data/eyeon.
eyeon-batch-summary.sh prints a short summary for the newest parse batch directory, including total file count, top-level JSON count, and counts by metadata type.
./eyeon-batch-summary.sh
./eyeon-batch-summary.sh /data/eyeon
./eyeon-batch-summary.sh /data/eyeon/20260417T120000Z_TESTSITETo summarize multiple explicit batch directories in one call, pass them all on the command line. This is useful with shell globs:
./eyeon-batch-summary.sh /data/eyeon/*TESTSITE*By default the wrapper uses the published production image:
ghcr.io/llnl/peyeon:latestTo test a branch image without touching latest, override EYEON_IMAGE:
EYEON_IMAGE=ghcr.io/llnl/peyeon:dev-<branch> ./eyeon-parse.sh TESTSITE ./samples /data/eyeon
EYEON_IMAGE=ghcr.io/llnl/peyeon:dev-<sha> ./eyeon-parse.sh TESTSITE ./samples /data/eyeoneyeon-parse.sh supports both Docker and Podman.
- set
EYEON_CONTAINER_RUNTIME=dockerorEYEON_CONTAINER_RUNTIME=podman - or pass
--runtime docker/--runtime podman - if neither is set, the wrapper auto-selects the runtime only when exactly one of them is installed
- if both are installed, the wrapper stops and asks you to choose explicitly
Examples:
./eyeon-parse.sh --runtime docker TESTSITE ./samples /data/eyeon
./eyeon-parse.sh --runtime podman TESTSITE ./samples /data/eyeon
EYEON_CONTAINER_RUNTIME=podman ./eyeon-parse.sh TESTSITE ./samples /data/eyeonFor Podman, the wrapper relies on Podman's default runtime behavior instead of passing explicit UID/GID overrides.
When run as a normal user, the wrapper passes your current UID and GID into the container so output files remain owned by you on the host.
When run as root, the wrapper requires you to choose the output owner explicitly unless you intentionally want root-owned output files.
Run as root but write files as a named user:
EYEON_OWNER=alice ./eyeon-parse.sh TESTSITE ./samples /data/eyeonRun as root with explicit numeric ownership:
EYEON_UID=12345 EYEON_GID=12345 ./eyeon-parse.sh TESTSITE ./samples /data/eyeonIntentionally allow root-owned outputs:
EYEON_PASSTHROUGH_ROOT=1 ./eyeon-parse.sh TESTSITE ./samples /data/eyeon| Runtime | Host mode | Wrapper behavior | In-container write identity | Resulting host file ownership |
|---|---|---|---|---|
| Docker | Normal user | Passes caller UID/GID into container | entrypoint.sh remaps with gosu to caller UID/GID |
Caller UID/GID |
| Docker | Root shell with EYEON_OWNER or EYEON_UID/EYEON_GID |
Resolves target owner, chowns new output dir, passes target UID/GID |
entrypoint.sh remaps with gosu to requested UID/GID |
Requested UID/GID |
| Docker | Root shell with EYEON_PASSTHROUGH_ROOT=1 |
Passes root through intentionally | Root | Root |
| Podman | Normal user | Does not pass UID/GID overrides | Podman default rootless mapping | Caller UID/GID |
| Podman | Root shell | Root-run Podman path is not the primary tested mode | Runtime-dependent | Treat as admin/debug use only |
Notes:
- Docker uses explicit UID/GID handoff from the wrapper into the container.
- Podman currently works best by relying on Podman's default runtime behavior rather than forcing UID/GID overrides.
- In root-run Docker mode, the wrapper must know which non-root host owner should receive the output files.
- The wrapper creates the timestamped output directory on the host before launching the container. In root-run Docker mode it also
chowns that directory to the requested target owner before execution.
Set DEBUG=1 or pass --debug to turn the wrapper into an interactive debugging session.
DEBUG=1 ./eyeon-parse.sh TESTSITE ./samples /data/eyeon
./eyeon-parse.sh --debug TESTSITE ./samples /data/eyeonDebug mode does the following:
- prints the resolved wrapper environment values
- prints the full
docker runcommand before launch - passes
DEBUG=1into the container - prints entrypoint and runtime UID/GID information inside the container
- shows metadata for
/source,/workdir, and/tmp - opens an interactive
bashshell instead of immediately runningeyeon parse
Inside the debug shell, the intended parse command is written to /tmp/eyeon-debug-command.sh so it can be inspected or run directly:
cat /tmp/eyeon-debug-command.sh
/tmp/eyeon-debug-command.shThese commands are still available when you install the Python package directly or when working interactively inside the container.
- Displays all arguments
eyeon --help- Displays observe arguments
eyeon observe --help- Displays parse arguments
eyeon parse --helpEyeON consists of two parts: an observe call and a parse call. observe.py works on a single file to return a suite of identifying metrics, whereas parse.py expects a folder. Both can be run either from a library import or a CLI command.
CLI command:
eyeon observe demo.ipynbLibrary usage:
obs = eyeon.observe.Observe("demo.ipynb")The observation will create a JSON file containing identifying information such as hashes, modify date, certificate info, and other metadata.
Example JSON file:
{
"bytecount": 9381,
"filename": "demo.ipynb",
"signatures": {"valid": "N/A"},
"imphash": "N/A",
"magic": "JSON text data",
"modtime": "2023-11-03 20:21:20",
"observation_ts": "2024-01-17 09:16:48",
"permissions": "0o100644",
"md5": "34e11a35c91d57ac249ff1300055a816",
"sha1": "9388f99f2c05e6e36b279dc2453ebea4bdc83242",
"sha256": "fa95b3820d4ee30a635982bf9b02a467e738deaebd0db1ff6a262623d762f60d",
"ssdeep": "96:Ui7ooWT+sPmRBeco20zV32G0r/R4jUkv57nPBSujJfcMZC606/StUbm/lGMipUQy:U/pdratRqJ3ZHStx4UA+I1jS"
}parse.py calls observe recursively, returning an observation for each file in a directory.
obs = eyeon.parse.Parse(args.dir)The Eyeon tool has the ability to verify against a provided sha1, md5, or sha256 hash. This can be leveraged as a stand alone function or with observe command to record the result in the output. If no algorithm is specified with -a, --algorithm it will default to md5.
eyeon checksum -a [md5,sha1,sha256] <file> <provided_checksum>For convenience you can parse, compress, and upload your results to box in a single command:
eyeon parse <dir> --uploadTo set up box and upload results, see Uploading Results section below
Examples Stand Alone Check
eyeon checksum -a sha256 tests/binaries/Wintap.exe bdd73b73b50350a55e27f64f022db0f62dd28a0f1d123f3468d3f0958c5fcc39Eyeon Observe
eyeon observe tests/binaries/Wintap.exe -a sha256 -c bdd73b73b50350a55e27f64f022db0f62dd28a0f1d123f3468d3f0958c5fcc39Recorded Result in Eyeon Output
"checksum_data": {
"algorithm": "sha256",
"expected": "bdd73b73b50350a55e27f64f022db0f62dd28a0f1d123f3468d3f0958c5fcc39",
"actual": "bdd73b73b50350a55e27f64f022db0f62dd28a0f1d123f3468d3f0958c5fcc39",
"verified": true
}If you want to run jupyter from the container, launch the image with the notebook port exposed and a bind mount for your working directory:
docker run --rm -it -p 8888:8888 -v "$(pwd):/workdir:Z" ghcr.io/llnl/peyeon:latest /bin/bash
jupyter notebook --ip=0.0.0.0 --no-browserThen open the demo.ipynb notebook for a quick demonstration.
Database-backed analysis and dashboard workflows now live in pEyeON-Analytics. This repository focuses on collection, parsing, and optional Box upload of EyeON results.
The Eyeon tool leverages the Box platform for data uploads and storage. All data handled by Eyeon is voluntarily submitted by users and securely stored in your Box account. If you wish to share the results of the eyeon tool with us please contact eyeon@llnl.gov to get setup.
To use Eyeon with Box, first configure your local Box settings and then generate a box_tokens.json file from a browser-friendly environment.
Steps:
-
Install EyeON in an environment where you can open a web browser.
-
Run the Box authentication command:
eyeon box-authThis will guide you through authenticating with Box in your browser and store your local tokens for later CLI use.
Once authentication is complete and your box_tokens.json file is generated, you can start the Eyeon Docker container and use the commands listed below.
eyeon box-listDisplays all items in your connected Box folder.
eyeon box-delete <file-or-id>Deletes the specified file from your connected Box folder.
eyeon box-upload <archive>Uploads the specified archive (zip, tar, tar.gz) to your Box folder.
There will be a second part to this project, which will be to develop a cloud application that anonymizes and summarizes the findings to enable OT security analysis.
SPDX-License-Identifier: MIT
