Plugin API reference
Complete API reference for inspector-sbomgen Lua plugins. For a guide on writing plugins, see Plugin developer guide. For testing, see Plugin testing guide.
Overview
All runtime-provided functions are accessed through the global sbomgen table (file I/O, regex, logging, constants, etc.). In addition, each plugin defines a small set of top-level global functions (discover, collect, get_scanner_name, subscribe_to_event, and so on) that sbomgen calls at defined points in the plugin lifecycle. These are documented in Plugin Lifecycle Globals.
Inside *_test.lua files, sbomgen additionally exposes a testing global that lets test authors drive the discovery→collection pipeline and make assertions. See Testing API.
Sandbox Restrictions
Plugins run in a sandboxed Lua VM with restricted standard library access. The following Lua standard library modules are available:
| Module | Notes |
|---|---|
base |
Core functions (print, type, tostring, tonumber, pairs, ipairs, pcall, error, select, unpack, rawget, rawset, etc.). dofile, loadfile, and loadstring are removed. |
string |
Full string manipulation (string.match, string.find, string.format, string.gsub, etc.) |
table |
Full table manipulation (table.insert, table.remove, table.sort, table.concat, etc.) |
math |
Full math library (math.floor, math.max, math.min, etc.) |
package |
require() is available but restricted to modules within the plugin's own directory tree. Parent-directory traversal (require("../shared")) is blocked. package.cpath and package.path are cleared. |
The following standard library modules are explicitly disallowed for security and stability:
| Module | Reason |
|---|---|
io |
Direct filesystem access is blocked. All file operations must go through sbomgen.* functions, which route through the artifact interface for consistent behavior across artifact types (directory, container, volume, etc.). |
os |
System-level operations (os.execute, os.remove, os.rename, os.getenv, etc.) are blocked to prevent plugins from modifying the host system. |
debug |
The debug library is blocked to prevent inspection or modification of the Lua VM internals. |
coroutine |
Coroutines are not loaded. |
These modules are not in the VM's allowlist and cannot be accessed by plugins.
Note
Important: All file I/O must go through sbomgen.* functions (e.g., sbomgen.read_file, sbomgen.open_file, sbomgen.get_file_list). Using io.open or any direct filesystem access will raise a runtime error. The sbomgen API ensures plugins interact with the artifact abstraction layer, which provides consistent behavior whether scanning a directory, container image, archive, or volume.
Plugin Lifecycle Globals
A plugin is a Lua file named init.lua that defines certain top-level global functions. These globals are not on the sbomgen table — they are functions the plugin defines for sbomgen to call. The set of valid globals differs between discovery plugins and collection plugins. For every function below, if the plugin omits it, the default shown in the table is used.
Discovery plugins
| Function | Arity | Required | Default (when omitted) | Description |
|---|---|---|---|---|
discover() |
0 | Yes | — | Returns the files this plugin has found. Return a sequential table of path strings (single-event mode) or a table keyed by event-name strings whose values are tables of paths (multi-event mode). |
get_event_name() |
0 | No | "lua:{platform}/{category}/{ecosystem}" |
Returns the event name under which files are published. Must be unique across all discovery plugins. |
get_scanner_name() |
0 | No | ecosystem directory name | Returns the scanner's display name. Must be unique across all discovery plugins. |
get_scanner_description() |
0 | No | "Lua discovery plugin: {ecosystem}" |
Returns a human-readable description. |
get_scanner_groups() |
0 | No | Derived from the category directory (see the developer guide) | Returns a table of scanner group strings. Use sbomgen.groups.* constants. |
get_localhost_scan_paths() |
0 | No | — | Returns a table of file/directory paths to include when scanning a localhost artifact. Only consulted for localhost scans. |
Collection plugins
| Function | Arity | Required | Default (when omitted) | Description |
|---|---|---|---|---|
collect(file_path) |
1 | Yes | — | Called once per file published to the subscribed event. Parse the file and emit findings via sbomgen.push_package(). Returns nothing. |
subscribe_to_event() |
0 | No | "lua:{platform}/{category}/{ecosystem}" |
Returns the event name this collector subscribes to. Should match the corresponding discovery plugin's get_event_name(). |
get_collector_name() |
0 | No | ecosystem directory name | Returns the collector's display name. Must be unique across all collection plugins. |
get_collector_description() |
0 | No | "" (empty) |
Returns a human-readable description. |
File I/O
All file operations must go through the sbomgen.* API. Direct filesystem access via Lua's io library is not available (see Sandbox Restrictions). The sbomgen file I/O functions route through the artifact interface, ensuring your plugin works identically whether scanning a directory on disk, a container image, a compressed archive, or a mounted volume.
sbomgen.get_file_list()
Returns all file paths in the artifact as a table of strings.
-
Returns:
{string, ...}— table of absolute file path strings -
Performance: This function copies every file path in the artifact into the Lua VM as a Lua string. On large artifacts (e.g., a localhost scan with 300K+ files), this alone takes several seconds. Iterating the returned table in Lua with
string.match()adds further overhead — a full scan can take 15+ seconds. The more files in the artifact, the slower your plugin will be.
Note
Prefer these targeted alternatives whenever possible:
| Function | Use when... |
|---|---|
sbomgen.find_files_by_name() |
You know the exact filename(s) to match (e.g., "requirements.txt", "curl") |
sbomgen.find_files_by_name_icase() |
Same as above, but case-insensitive |
sbomgen.find_files_by_suffix() |
You need to match path suffixes (e.g., "/pom.properties", "curlver.h") |
sbomgen.find_files_by_path_regex() |
You need full-path regex matching |
sbomgen.glob_find_files() |
You need glob-style basename matching |
These functions perform matching outside the Lua VM and return only the matched paths, completing in under 1 millisecond even on 300K-file artifacts. Use get_file_list() only when your matching logic cannot be expressed with any of the above.
-- AVOID in discovery plugins when possible: local files = sbomgen.get_file_list() for _, f in ipairs(files) do if string.match(f, "pattern$") then ... end end -- PREFER: local matches = sbomgen.find_files_by_name({"target-file.txt"})
sbomgen.read_file(path)
Reads the entire contents of a file and returns it as a string.
-
Returns:
string, err -
On failure:
nil, error_string
local content, err = sbomgen.read_file("/app/package.json") if err then sbomgen.log_error("read failed: " .. err) return end
sbomgen.open_file(path)
Opens a file for streaming reads. Returns a FileHandle object. Use this for large files where loading the entire content into memory is impractical.
-
Returns:
FileHandle, err
local fh, err = sbomgen.open_file(path) if err then return end local line = fh:read_line() while line do -- process line line = fh:read_line() end fh:close()
sbomgen.glob_find_files(pattern)
Returns files matching a Go filepath.Match glob pattern. The pattern is matched against the base filename.
-
Returns:
{string, ...}, err
local files, err = sbomgen.glob_find_files("*.txt")
Use sbomgen.get_file_list() with string.match for full path pattern matching.
sbomgen.find_files_by_name(names)
Returns files whose basename (last path component) exactly matches one of the given names. The iteration and comparison happen in Go, making this significantly faster than iterating sbomgen.get_file_list() in Lua.
-
Parameters:
names— table of strings (basenames to match) -
Returns:
{string, ...}— matching file paths (no error tuple)
local curl_bins = sbomgen.find_files_by_name({"curl", "curl.exe"}) local headers = sbomgen.find_files_by_name({"curlver.h"})
sbomgen.find_files_by_name_icase(names)
Returns files whose basename matches one of the given names, ignoring case. For example, "version" matches VERSION, Version, and version. Like find_files_by_name, matching happens outside the Lua VM.
-
Parameters:
names— table of strings (basenames to match, case-insensitive) -
Returns:
{string, ...}— matching file paths (no error tuple)
local version_files = sbomgen.find_files_by_name_icase({"version"}) local war_files = sbomgen.find_files_by_name_icase({"jenkins.war"})
sbomgen.find_files_by_suffix(suffixes)
Returns files whose full (forward-slash-normalized) path ends with one of the given suffixes. Like find_files_by_name, matching happens outside the Lua VM.
-
Parameters:
suffixes— table of strings (path suffixes to match) -
Returns:
{string, ...}— matching file paths (no error tuple)
local pom_files = sbomgen.find_files_by_suffix({"/pom.properties"}) local release_headers = sbomgen.find_files_by_suffix({"ap_release.h", "opensslv.h"})
sbomgen.find_files_by_path_regex(patterns)
Returns files whose forward-slash-normalized path matches any of the given Go (RE2) regex patterns. Matching happens outside the Lua VM, which makes this efficient on large file lists.
-
Parameters:
patterns— table of Go regex strings -
Returns:
{string, ...}— matching file paths (no error tuple) -
Raises: a Lua error if any pattern fails to compile
local configs = sbomgen.find_files_by_path_regex({"/etc/.*\\.conf$", "/opt/.*/config\\.json$"})
Performance: find_files_by_* vs get_file_list
For discovery plugins, prefer find_files_by_name, find_files_by_suffix, or find_files_by_path_regex over iterating get_file_list() in Lua. On a localhost scan with 300K files, iterating the file list in Lua with string.match() takes ~15 seconds, while find_files_by_name completes in under 1 millisecond. The difference is that get_file_list() copies every file path into the Lua VM as a string, then Lua interprets the loop and pattern match for each one. The find_files_by_* functions perform the matching outside the Lua VM and return only the matched paths, avoiding both the copy and the per-path interpretation overhead.
Use get_file_list() only when you need custom matching logic that cannot be expressed as a basename, suffix, or regex match.
sbomgen.read_dir(path)
Lists entries in a directory.
-
Returns:
{{name, is_dir}, ...}, err
local entries, err = sbomgen.read_dir("/app/node_modules") if err then return end for _, e in ipairs(entries) do if e.is_dir then sbomgen.log_debug("directory: " .. e.name) end end
sbomgen.file_stat(path)
Returns metadata about a file.
-
Returns:
{is_regular, is_dir, size}, err
local info, err = sbomgen.file_stat(path) if err then return end if info.is_regular and info.size > 0 then -- process file end
sbomgen.read_zip_entry(path, entry_path)
Reads a single entry from a ZIP, JAR, or WAR archive.
-
Returns:
string, err
local manifest, err = sbomgen.read_zip_entry( "/app/lib/example.jar", "META-INF/MANIFEST.MF" )
sbomgen.search_binary(path, regex)
Parses a file as an ELF, PE, or Mach-O binary and searches the default constant/variable section for a Go regex match.
-
Returns:
string|nil, err— the matched string, or nil if no match
local version, err = sbomgen.search_binary(path, "Version:\\s+([\\d.]+)") if version then sbomgen.log_info("found version: " .. version) end
sbomgen.search_binary_all(path, regex [, n])
Parses a file as an ELF, PE, or Mach-O binary and returns all unique first capture group matches from the default constant/variable section. Pass n to limit results.
-
Returns:
{string, ...}|nil, err— table of matched strings, or nil if no matches
local versions, err = sbomgen.search_binary_all(path, "version[= ]+([\\d.]+)", 5) if versions then for _, v in ipairs(versions) do sbomgen.log_info("found: " .. v) end end
sbomgen.search_binary_raw(path, regex)
Searches the entire binary file for the first regex match, not limited to a specific section. Use when section-based search (search_binary) is insufficient — for example, when version strings are in non-standard sections.
-
Returns:
string|nil, err— the matched string, or nil if no match
local version, err = sbomgen.search_binary_raw(path, "ProductVersion[\\x00\\s]+([\\d.]+)")
FileHandle Methods
FileHandle objects are returned by sbomgen.open_file().
fh:read_line()
Reads the next line (without the newline character). Returns nil at EOF.
-
Returns:
string|nil, err
fh:read(n)
Reads up to n bytes. Returns nil at EOF.
-
Returns:
string|nil, err
fh:close()
Closes the file handle. Always close handles when done.
Binary Utilities
sbomgen.sha256(path)
Returns the hex-encoded SHA-256 hash of a file's contents.
-
Returns:
string, err
local hash, err = sbomgen.sha256("/app/bin/server") if hash then sbomgen.log_info("SHA-256: " .. hash) end
sbomgen.contains_bytes(path, patterns)
Checks whether a file contains each of the given byte patterns. Returns a table of booleans in the same order as the input patterns.
-
Returns:
{bool, ...}, err
local results, err = sbomgen.contains_bytes(path, { "\xff Go buildinf:", -- Go build identifier "/rustc/", -- Rust build identifier }) if results then local is_go = results[1] local is_rust = results[2] end
sbomgen.get_pe_version_info(path)
Parses Windows PE version resources from a binary file. Returns a table with version fields, or nil, err if the file is not a PE binary or has no version resource.
-
Returns:
{product_version, file_version, string_table}, err
The product_version and file_version fields come from the PE FixedFileInfo structure, formatted as "major.minor.build.revision". The string_table field is a nested table keyed by locale code (e.g., "040904B0" for US English Unicode). Each locale maps to a table of name/value pairs drawn from the PE StringFileInfo (ProductVersion, ProductName, FileDescription, etc.). A PE binary may expose one or more locales.
local info, err = sbomgen.get_pe_version_info(file_path) if err then return end -- Fixed version fields (always flat) local product_ver = info.product_version -- e.g. "25.1.0.0" local file_ver = info.file_version -- e.g. "25.1.0.0" -- String table — iterate locales, or address a known locale by key for locale, fields in pairs(info.string_table or {}) do sbomgen.log_info(string.format("%s ProductName=%s", locale, fields.ProductName or "")) end -- US English Unicode is the most common locale for PE files local us = (info.string_table or {})["040904B0"] if us then local display_ver = us.ProductVersion -- e.g. "25.01" local name = us.ProductName -- e.g. "7-Zip" end
sbomgen.parse_product_version(path)
Convenience wrapper that returns just the product version string from a PE binary's FixedFileInfo. Equivalent to calling get_pe_version_info(path) and reading product_version.
-
Returns:
string, err
local version, err = sbomgen.parse_product_version(file_path) if version then sbomgen.log_info("product version: " .. version) end
sbomgen.parse_file_version(path)
Convenience wrapper that returns just the file version string from a PE binary's FixedFileInfo. Equivalent to calling get_pe_version_info(path) and reading file_version.
-
Returns:
string, err
local version, err = sbomgen.parse_file_version(file_path) if version then sbomgen.log_info("file version: " .. version) end
Package Output
sbomgen.push_package(pkg)
Pushes a package finding into the SBOM. Only available in collection plugins.
The pkg table supports the following fields:
| Field | Type | Required | Description |
|---|---|---|---|
name |
string | Yes | Package name |
version |
string | No | Resolved version string |
namespace |
string | No | PURL namespace (e.g., "curl", "wordpress/plugin") |
purl_type |
string | Yes | PURL type (e.g., "pypi", "npm", "cargo", "deb", "generic") |
component_type |
string | Yes | CycloneDX component type; use sbomgen.component_types.* constants (e.g., sbomgen.component_types.LIBRARY) |
qualifiers |
table | No | PURL qualifiers as key-value pairs (appear in the package URL) |
properties |
table | No | CycloneDX component properties as key-value pairs (see CycloneDX Properties) |
children |
table | No | Nested child packages, each with the same shape as pkg (required fields are validated recursively) |
sbomgen.push_package({ name = "requests", version = "2.28.1", purl_type = "pypi", component_type = sbomgen.component_types.LIBRARY, qualifiers = { example_qualifier = "example_qualifier_value" }, properties = { -- Use your own namespace; amazon:inspector:* is reserved for Amazon Inspector. ["acme:example:extra_field"] = "example_value", }, })
CycloneDX Properties
CycloneDX properties are key-value metadata attached to a component in the SBOM. They are distinct from PURL qualifiers:
-
qualifiers— PURL qualifiers. These become part of the package URL string (e.g.,pkg:deb/debian/curl@7.88.1?arch=amd64). Some PURL qualifiers carry semantic meaning to Amazon Inspector and influence vulnerability identification. See What is a package URL? for Inspector's per-type conventions. -
properties— CycloneDX component properties. These appear in the SBOM'scomponents[].propertiesarray and do not change how the component is identified.
Reserved namespaces
The amazon:inspector:* family of CycloneDX property namespaces is reserved for Amazon Inspector:
-
amazon:inspector:sbom_generator:*— used by sbomgen and its built-in scanners. -
amazon:inspector:sbom_scanner:*— used by the Amazon Inspector Scan API.
Plugin-defined properties must not use these namespaces. Writing into a reserved namespace can shadow or conflict with values Inspector relies on, and the resulting SBOM may be interpreted incorrectly during vulnerability identification. See Using CycloneDX namespaces with Amazon Inspector for the full list of reserved keys.
Key naming rules
Property keys passed to sbomgen.push_package() are processed as follows:
| Input key | Resulting key in SBOM | Recommended for custom plugins? |
|---|---|---|
Contains : (e.g., acme:my_plugin:field) |
Used verbatim | Yes — place every plugin-defined property in your own namespace |
No : (e.g., field) |
Auto-prefixed to amazon:inspector:sbom_generator:field |
No — this writes into a reserved namespace |
Always include at least one colon in property keys you define. Use a namespace unique to your organization or plugin (for example acme:python-pip:*):
properties = { -- Custom namespace — safe to use (recommended) ["acme:python-pip:manifest_path"] = file_path, ["acme:python-pip:pinned"] = "true", -- Fully-qualified key outside amazon:inspector:* — also fine ["my:custom:namespace:key"] = "value", -- No colon: avoid — ends up as "amazon:inspector:sbom_generator:custom_field" -- custom_field = "value", }
Properties set by sbomgen
Sbomgen may attach properties of its own to every component it emits. These values come from the reserved amazon:inspector:sbom_generator:* namespace and should not be produced by plugins. Observed runtime behavior:
-
source_pathis always added by sbomgen. -
source_file_scannerandsource_package_collectorare added when--enable-debug-propsis enabled.
The full taxonomy of reserved keys is maintained in the Amazon Inspector user guide: Using CycloneDX namespaces with Amazon Inspector.
Property Constants
Built-in property key constants are available via sbomgen.properties. Every constant below resolves to a key inside the reserved amazon:inspector:sbom_generator:* namespace. These constants exist so that sbomgen's built-in scanners emit consistent property keys. They are not extension points for custom plugins — using them in a custom plugin writes into a reserved namespace, which can shadow values Inspector relies on. See Reserved namespaces above.
Custom plugin authors should define properties under their own namespace (for example acme:my_plugin:*) rather than reusing these constants.
| Constant | Resolved Value |
|---|---|
sbomgen.properties.NAMESPACE |
amazon:inspector:sbom_generator: |
sbomgen.properties.VENDOR |
amazon:inspector:sbom_generator:vendor |
sbomgen.properties.FILE_SIZE_BYTES |
amazon:inspector:sbom_generator:file_size_bytes |
sbomgen.properties.KERNEL_COMPONENT |
amazon:inspector:sbom_generator:kernel_component |
sbomgen.properties.RUNNING_KERNEL |
amazon:inspector:sbom_generator:running_kernel |
sbomgen.properties.UNRESOLVED_VERSION |
amazon:inspector:sbom_generator:unresolved_version |
sbomgen.properties.TRANSITIVE_DEPENDENCY |
amazon:inspector:sbom_generator:experimental:transitive_dependency |
sbomgen.properties.GO_REPLACE_DIRECTIVE |
amazon:inspector:sbom_generator:replaced_by |
sbomgen.properties.DUPLICATE_PACKAGE |
amazon:inspector:sbom_generator:is_duplicate_package |
sbomgen.properties.DUPLICATE_PURL |
amazon:inspector:sbom_generator:duplicate_purl |
sbomgen.properties.DOCKERFILE_CHECK |
amazon:inspector:sbom_generator:dockerfile_finding |
sbomgen.properties.CERTIFICATE_FINDING |
amazon:inspector:sbom_generator:certificate_finding |
sbomgen.properties.CERTIFICATE_SUBJECT_NAME |
amazon:inspector:sbom_generator:certificate:subject_name |
sbomgen.properties.CERTIFICATE_ISSUER_NAME |
amazon:inspector:sbom_generator:certificate:issuer_name |
sbomgen.properties.CERTIFICATE_SIGNATURE_ALGORITHM |
amazon:inspector:sbom_generator:certificate:signature_algorithm |
sbomgen.properties.CERTIFICATE_NOT_VALID_BEFORE |
amazon:inspector:sbom_generator:certificate:not_valid_before |
sbomgen.properties.CERTIFICATE_NOT_VALID_AFTER |
amazon:inspector:sbom_generator:certificate:not_valid_after |
sbomgen.properties.WINDOWS_REGISTRY_KEY |
amazon:inspector:sbom_generator:registry_key |
sbomgen.properties.SUBSCRIPTION_ENABLED |
amazon:inspector:sbom_generator:subscription:enabled |
sbomgen.properties.SUBSCRIPTION_NAME |
amazon:inspector:sbom_generator:subscription:name |
sbomgen.properties.SUBSCRIPTION_LOCKED_VERSION |
amazon:inspector:sbom_generator:subscription:locked_version |
sbomgen.properties.OPENSSL_FULL_VERSION |
amazon:inspector:sbom_generator:openssl:full_version |
sbomgen.properties.HARDENED_IMAGE_VENDOR |
amazon:inspector:sbom_generator:hardened_image:vendor |
Scanner Groups
Discovery plugins must declare their scanner groups via get_scanner_groups(). Groups categorize scanners and allow users to selectively enable or disable categories. Constants are available via sbomgen.groups:
| Constant | Value | Description |
|---|---|---|
sbomgen.groups.OS |
"os" |
OS package managers (dpkg, rpm, etc.) |
sbomgen.groups.PROGRAMMING_LANGUAGE |
"programming-language-packages" |
Language package managers (pip, npm, maven, etc.) |
sbomgen.groups.BINARY |
"binary" |
Compiled binary analysis (Go, Rust) |
sbomgen.groups.PACKAGE_COLLECTOR |
"pkg-scanner" |
General package collection |
sbomgen.groups.EXTRA_ECOSYSTEMS |
"extra-ecosystems" |
Additional ecosystems (curl, nginx, etc.) |
sbomgen.groups.CERTIFICATE |
"certificate" |
Certificate scanning |
sbomgen.groups.CUSTOM |
"custom" |
Automatically added to all custom plugins loaded via --plugin-dir |
sbomgen.groups.MACHINE_LEARNING |
"machine-learning" |
Machine learning model detection |
Example:
function get_scanner_groups() return {sbomgen.groups.PROGRAMMING_LANGUAGE, sbomgen.groups.PACKAGE_COLLECTOR} end
Component Type Constants
The component_type field in push_package() must be one of the CycloneDX 1.5 component types. Constants are available via sbomgen.component_types:
| Constant | Value |
|---|---|
sbomgen.component_types.APPLICATION |
"application" |
sbomgen.component_types.FRAMEWORK |
"framework" |
sbomgen.component_types.LIBRARY |
"library" |
sbomgen.component_types.CONTAINER |
"container" |
sbomgen.component_types.PLATFORM |
"platform" |
sbomgen.component_types.OPERATING_SYSTEM |
"operating-system" |
sbomgen.component_types.DEVICE |
"device" |
sbomgen.component_types.DEVICE_DRIVER |
"device-driver" |
sbomgen.component_types.FIRMWARE |
"firmware" |
sbomgen.component_types.FILE |
"file" |
sbomgen.component_types.MACHINE_LEARNING_MODEL |
"machine-learning-model" |
sbomgen.component_types.DATA |
"data" |
Example:
sbomgen.push_package({ name = "requests", version = "2.28.1", purl_type = "pypi", component_type = sbomgen.component_types.LIBRARY, })
Platform Constants
Constants for comparing against sbomgen.get_platform(). Available via sbomgen.platform:
| Constant | Value |
|---|---|
sbomgen.platform.LINUX |
"linux" |
sbomgen.platform.WINDOWS |
"windows" |
sbomgen.platform.DARWIN |
"darwin" |
Example:
if sbomgen.get_platform() == sbomgen.platform.WINDOWS then -- Windows-specific logic end
Artifact Info
sbomgen.get_platform()
Returns the runtime platform string (e.g., "linux", "windows", "darwin").
sbomgen.get_artifact_type()
Returns the type of artifact being scanned (e.g., "directory", "archive").
sbomgen.should_collect_licenses()
Returns true if the user enabled license collection via --collect-licenses.
sbomgen.get_env_vars()
Returns environment variables from the artifact as a table of {key, value} entries.
local env_vars = sbomgen.get_env_vars() for _, env in ipairs(env_vars) do if env.key == "NODE_ENV" then sbomgen.log_info("Node environment: " .. env.value) end end
sbomgen.get_system_drive()
Returns the system drive letter (e.g., "C:") from the artifact's environment. Reads the SystemDrive environment variable, defaulting to "C:" if not set. This is the Lua equivalent of strutils.GetSystemDriverLetter().
local drive = sbomgen.get_system_drive() local program_files = drive .. "/Program Files/"
System Info
These functions return metadata about the artifact's operating system and hardware. Values may be empty strings if the information is not available (e.g., when scanning a directory without OS metadata).
| Function | Returns |
|---|---|
sbomgen.get_os_name() |
OS name (e.g., "Ubuntu", "Alpine Linux") |
sbomgen.get_os_version() |
OS version (e.g., "22.04", "3.18") |
sbomgen.get_os_codename() |
OS codename (e.g., "jammy", "bookworm") |
sbomgen.get_os_id() |
OS identifier (e.g., "ubuntu", "alpine") |
sbomgen.get_kernel_name() |
Kernel name (e.g., "Linux") |
sbomgen.get_kernel_version() |
Kernel version string |
sbomgen.get_cpu_arch() |
CPU architecture (e.g., "x86_64", "aarch64") |
sbomgen.get_hostname() |
Hostname of the system |
Regular Expressions
Lua's built-in patterns lack features like alternation (|), quantifier ranges ({n,}), and lookahead. To close this gap, sbomgen exposes Go's regexp package directly. These functions use Go regex syntax (RE2), not Lua patterns.
sbomgen.regex_find(str, pattern)
Returns the first match of a Go regex pattern, or nil if no match.
-
Returns:
string|nil, err
local version = sbomgen.regex_find(content, "\\d+\\.\\d+\\.\\d+")
sbomgen.regex_match(str, pattern)
Returns capture groups from the first match. Index 1 is the full match, 2+ are capture groups.
-
Returns:
{string, ...}|nil, err
local groups = sbomgen.regex_match(content, "(MySQL|MariaDB) (\\d+)\\.(\\d+)\\.(\\d+)") if groups then local db_type = groups[2] -- "MySQL" or "MariaDB" local major = groups[3] end
sbomgen.regex_find_all(str, pattern [, n])
Returns all non-overlapping matches. Pass n to limit results (default: all).
-
Returns:
{string, ...}|nil, err
local versions = sbomgen.regex_find_all(content, "\\d+\\.\\d+\\.\\d+")
sbomgen.regex_replace(str, pattern, replacement)
Replaces all matches. The replacement string can use $1, $2, etc. for capture group references.
-
Returns:
string, err
local cleaned = sbomgen.regex_replace(raw_version, "(1[6-9]\\d{8,}|buildkitsandbox.*)$", "")
When to use regex vs Lua patterns
Use Lua's built-in string.match/string.find for simple patterns — they're faster and don't require escaping backslashes. Use sbomgen.regex_* when you need:
-
Alternation:
(foo|bar) -
Quantifier ranges:
\d{8,} -
Complex character classes not expressible in Lua patterns
Structured Parsing
Sbomgen exposes lightweight helpers for decoding structured text formats directly into Lua tables.
sbomgen.json_decode(str)
Parses a JSON string into a Lua table.
-
Returns:
table|nil, err
local doc, err = sbomgen.json_decode('{"name":"requests","version":"2.28.1"}') if err then return end sbomgen.log_info(doc.name)
sbomgen.xml_decode(str)
Parses an XML string into a Lua table.
-
Returns:
table|nil, err
XML values use the following shape:
-
_name— element name -
_attr— attribute table, when present -
_text— trimmed text content, when present -
numeric indices
1..n— child elements
local doc, err = sbomgen.xml_decode('<package id="Newtonsoft.Json" version="13.0.3" />') if err then return end sbomgen.log_info(doc._attr.id)
Windows Registry
These functions provide read-only access to the Windows registry. On non-Windows artifacts, registry_open_key returns an error. The registry accessor is initialized lazily on first use and supports both live Windows API access (localhost scans on Windows) and file-based REGF hive parsing (container/volume scans).
sbomgen.registry_open_key(path)
Opens a registry key. Returns a key handle that must be closed with registry_close.
-
Returns:
key, err
local key, err = sbomgen.registry_open_key("SOFTWARE\\Microsoft\\Windows\\CurrentVersion\\Uninstall\\7-Zip") if err then return end -- use key... sbomgen.registry_close(key)
sbomgen.registry_get_string(key, value_name)
Reads a string value from an open registry key.
-
Returns:
string, err
local version, err = sbomgen.registry_get_string(key, "DisplayVersion")
sbomgen.registry_get_integer(key, value_name)
Reads an integer value from an open registry key.
-
Returns:
number, err
sbomgen.registry_get_strings(key, value_name)
Reads a multi-string (REG_MULTI_SZ) value from an open registry key. Returns a table of strings.
-
Returns:
{string, ...}, err
local paths, err = sbomgen.registry_get_strings(key, "DependsOnService") if paths then for _, p in ipairs(paths) do sbomgen.log_info("depends on: " .. p) end end
sbomgen.registry_get_subkeys(key)
Returns all subkey names under an open registry key.
-
Returns:
{string, ...}, err
local subkeys, err = sbomgen.registry_get_subkeys(key) for _, name in ipairs(subkeys) do local subkey, err = sbomgen.registry_open_key(parent_path .. "\\" .. name) -- ... end
sbomgen.registry_close(key)
Closes a registry key handle. Key handles are also closed automatically by the garbage collector, but explicit closing is recommended.
Logging
Log messages are written to sbomgen's console output. Every message emitted by a plugin is automatically prefixed with the plugin's source label and ecosystem, for example:
[custom:python-pip] Parsing requirements.txt
log_info, log_warn, and log_error always print. log_debug only prints when sbomgen is invoked with --verbose.
| Function | Level | Visible by default? |
|---|---|---|
sbomgen.log_debug(message) |
DEBUG | No — requires --verbose |
sbomgen.log_info(message) |
INFO | Yes |
sbomgen.log_warn(message) |
WARN | Yes |
sbomgen.log_error(message) |
ERROR | Yes |
Use string.format for formatted messages:
sbomgen.log_info(string.format("found %d packages in %s", count, file_path))
Debugging Functions
sbomgen.breakpoint(message)
Prints message to stderr and blocks execution until the user presses Enter. If message is omitted, prints a default message.
Use this as a crude debugger by placing breakpoints at key points in your plugin and running with --verbose to see surrounding log output.
sbomgen.log_info("state: " .. some_variable) sbomgen.breakpoint("paused after state dump — press Enter to continue")
Testing API
Functions under the global testing table are only available inside plugin test files (*_test.lua), loaded by inspector-sbomgen plugin test. They are not available at runtime in discovery or collection plugins. The full sbomgen.* API is also available inside test files, but sbomgen.* functions that require an artifact (for example sbomgen.read_file()) only produce meaningful results when called from inside a scan. For a narrative guide, see the Plugin testing guide.
Scan functions
Each scan function creates an artifact of the given kind, runs the current plugin's discovery→collection pipeline against it, and returns the resulting findings. The path argument is resolved relative to the test file's directory.
| Function | Artifact kind |
|---|---|
testing.scan_directory(path) |
Directory |
testing.scan_archive(path) |
Directory (alias of scan_directory) |
testing.scan_localhost(path) |
Localhost |
testing.scan_binary(path) |
Binary |
testing.scan_volume(path) |
Volume |
testing.scan_container(path) |
Container |
All six return a result table with the shape below.
Result shape
Each finding table projects only the fields listed below. In particular, namespace and purl_type are not projected separately — they are incorporated into the full purl string.
local result = testing.scan_directory("_testdata/example") -- result.findings -- array of finding tables -- result.findings[i].name -- string -- result.findings[i].version -- string -- result.findings[i].component_type -- string -- result.findings[i].purl -- string (the full Package URL, or "" if none) -- result.findings[i].properties -- table<string, string> -- result.findings[i].children -- array of finding tables (same shape, recursive)
Assertions
| Function | Signature | Description |
|---|---|---|
testing.assert_equals |
(expected: any, actual: any, message?: string) |
Fails if tostring(expected) ~= tostring(actual). |
testing.assert_not_equals |
(expected: any, actual: any, message?: string) |
Fails if tostring(expected) == tostring(actual). |
testing.assert_true |
(value: any, message?: string) |
Fails if value is false or nil. |
testing.assert_false |
(value: any, message?: string) |
Fails if value is not false and not nil. |
testing.assert_nil |
(value: any, message?: string) |
Fails if value is not nil. |
testing.assert_not_nil |
(value: any, message?: string) |
Fails if value is nil. |
testing.assert_contains |
(haystack: string, needle: string, message?: string) |
Fails if haystack does not contain needle (substring match). |
testing.assert_matches |
(str: string, pattern: string, message?: string) |
Fails if str does not match the given Go (RE2) regex. |
testing.assert_length |
(tbl: table, expected: integer, message?: string) |
Fails if #tbl does not equal expected. |
Control flow
| Function | Signature | Description |
|---|---|---|
testing.fail |
(message: string) |
Fails the current test immediately with the given message. |
testing.skip |
(message: string) |
Skips the current test. The result is reported as skipped, not failed. |
Test discovery
Any global Lua function whose name starts with test_ in a file matching *_test.lua is treated as a test. The test file must sit next to an init.lua at the normal {phase}/{platform}/{category}/{ecosystem}/ depth. Fixture data goes in _testdata/ next to the test file — the runner does not descend into _testdata/ when searching for test files.
Error Handling
API functions that can fail return two values: value, err. On success, err is nil. On failure, the first value is nil and err is an error string.
local content, err = sbomgen.read_file(path) if err then sbomgen.log_error("failed to read " .. path .. ": " .. err) return end -- content is safe to use here
If a plugin raises an unhandled Lua error, sbomgen logs a warning and continues with the next file or plugin. Other plugins are not affected.