π Getting startedο
Before installing anything, pick your mode based on two questions, then install only the prerequisites for the cell you land in, you will never need all of them.
What do you want to do? Use Assets Guardian to run audits, or develop it (contribute, write plugins)?
Where do you want it to run? Directly on your host (Python + uv), or inside a container (Docker)?
π On your host (Python + uv) |
π³ In a container |
|
|---|---|---|
Use it |
Standalone install: installs a global |
Docker image: build once, run audits in an isolated container with your |
Develop it |
Local dev: full dev environment ( |
Dev Container: a fully provisioned, reproducible VS Code environment. The entire toolchain is set up for you inside a Docker container. |
Prerequisites per mode
Mode |
Requirements |
|---|---|
π Standalone install |
Python 3.13 and uv (replacing standard |
π³ Docker image |
A container engine only: Docker. No local Python setup needed, applies to both the use and develop Docker variants. |
π Local dev |
Same as Standalone install. |
π» Dev Container |
VS Code (or any Dev Containers-compatible IDE) + the Dev Containers extension, and a container engine (Docker or compatible). |
π‘ Tip: The two axes are independent: Docker is not only for production. The same engine powers three modes: the
useimage (audits in isolation), thedevelopimage (validate your changes in the exact production container), and the Dev Container (full VS Code environment, workspace live-mounted).π‘ Tip:
makeis optional in every mode, it provides shortcuts for common commands regardless of where the tool runs.
π¦ Installationο
π Clone the repositoryο
All modes start from the source:
# Over SSH: recommended if you have an SSH key configured
git clone git@github.com:apizee/assets-guardian.git
cd assets-guardian/
π‘ Tip: All the commands in the sections below assume you are running them from the root of this cloned directory.
π Standalone installο
Installationο
make install
Without make:
uv tool install .
Verify:
assets-guardian --version
The assets-guardian command is now available globally on your PATH.
π‘ Tip: If your shell canβt find the command, run
uv tool update-shelland restart your terminal.
Upgradeο
make upgrade
Without make:
uv tool install . --reinstall
Verify:
assets-guardian --version
Uninstallο
make uninstall
Without make:
uv tool uninstall assets-guardian
Verify:
assets-guardian
π Local devο
make setup
Without make:
uv sync --all-groups
uv run pre-commit install
Verify:
uv run assets-guardian --version
π³ Docker image (use)ο
make docker-build
Without make:
docker build -t assets-guardian .
The image is built once. See Running for mounting volumes and passing credentials.
π» Dev Containerο
Open the repository folder in VS Code. When prompted, click Reopen in Container, or open the Command Palette (Ctrl+Shift+P) and run Dev Containers: Reopen in Container.
Command |
Description |
|---|---|
|
Opens the current folder in a Dev Container |
|
Opens the current folder in WSL (if you prefer to use WSL rather than the Dev Container) |
|
Return to the local environment |
|
Rebuilds the container (after modifying the |
|
Rebuilds the container without Docker cache, to start from scratch |
|
Displays the container creation logs (useful for debugging a failing build) |
π³ Docker image (develop)ο
make docker-build
Without make:
docker build -t assets-guardian .
The image is built once. See Running for mounting volumes and passing credentials.
π‘ Tip: Rebuild the image after each change to dependencies or the Dockerfile, source code changes are reflected live via the volume mount.
βοΈ Setup configurationsο
No matter which mode you installed (π Standalone install, π Local dev, π³ Docker or π» Dev Container), the Assets Guardian configuration is the same and shared across all of them.
π Configuration Filesο
You need a config/ directory with all files required:
config/
βββ config.yml # Central hub & plugin activation
βββ template.config.yml # A file for validation purpose before running
βββ rules_config.yml # IAM policies & rule registry
βββ employees.json # HR database (source of truth about identity)
βββ excel_config.json # Excel report styling
βββ pdf_config.json # PDF report styling
π‘ Tip: You can use the
config/from the repository as based for your configuration, donβt forget to remove thetemplate.prefix.β οΈ Warning: You need to conserve the
template.config.ymlfor validation purpose before running.
π File Detailsο
File |
Description |
Key Notes |
|---|---|---|
|
Central hub for env profiles, global logs, and paths. |
Plugins: Only activated if explicitly defined here. |
|
Validation schema for |
Do not delete or rename: required at startup to validate the main config. |
|
IAM policies registry (MFA, inactivity thresholds). |
Strict execution: Rules are evaluated only if explicitly defined or inherited under a plugin section. |
|
List of active employees and access profiles. |
Used to detect shadow accounts. Must be populated manually. |
|
Visual and structural guidelines for Excel reports. |
Configures fields validation and timezones. |
|
Visual and structural guidelines for PDF reports. |
Configures brand colors, fonts, and checkboxes. |
π‘ Tip: Remember, you can simply copy the templates to get started:
cp config/template.* config/.
π The config.yml fileο
The central configuration hub. Every run reads this file first.
env: "dev" # Active environment label (dev, test or prod)
author:
fullname: "First name LAST NAME" # Displayed in report headers
email: "...@example.com" # Displayed in report headers
notification_email: # Recipients of the audit report by email
- "...@example.com"
logging:
console_level: "INFO" # Verbosity printed to stdout (DEBUG, INFO, WARNING, ERROR)
file_level: "DEBUG" # Verbosity written to the log file
file-basename: "assets-guardian" # Log file name prefix
max-size: 10 # Max log file size in MB before rotation
max-files: 3 # Number of rotated log files to keep
path: "local:logs" # Directory where log files are written (local only)
paths:
excel: "local:outputs/assets_guardian.xlsx" # Output Excel report
pdf: "local:outputs/audit_report.pdf" # Output PDF report
rules: "local:config/rules_config.yml" # Path to the rules registry
excel_config: "local:config/excel_config.json" # Excel report styling rules
pdf_config: "local:config/pdf_config.json" # PDF report styling rules
employees: "local:config/employees.json" # Path to the HR database
cache:
batch_size: 64 # Number of items fetched per API call
cache_dir: ".assets-guardian_cache" # Directory for local API response cache
The env parameterο
Controls the execution mode of the application. Three values are accepted: dev, test, and prod.
env: "dev"
Cache behaviour: after a sync run, temporary API-response cache files are only deleted when env is prod. In dev and test they are kept on disk so subsequent runs skip already-fetched data, which significantly reduces collection time during development.
π‘ Tip: You can override this value at runtime without editing the file by setting the
ENVenvironment variable (e.g.ENV=prod assets-guardian sync). The environment variable always takes priority over the value inconfig.yml.
The notification_email sectionο
The list of recipients that receive the PDF audit report by email at the end of an audit run. The sender is author.email, and delivery goes through Microsoft Graph, so it requires a configured microsoft365 instance.
notification_email:
- "security-team@company.com"
- "ciso@company.com"
Value |
Behaviour |
|---|---|
One or more addresses |
The report is emailed to every address once the audit completes |
Empty or omitted |
No email is sent. |
β οΈ Warning: Every entry must be a syntactically valid address. A malformed address makes the
checkcommand fail, andsync/auditrefuse to start.
The logging sectionο
Controls what gets written to the console and to the rotating log file.
logging:
console_level: "INFO"
file_level: "DEBUG"
file-basename: "assets-guardian"
max-size: 10 # MB
max-files: 3
path: "local:logs"
Field |
Default |
Description |
|---|---|---|
|
|
Minimum level printed to stdout |
|
|
Minimum level written to the log file |
|
|
Prefix for the log file name (e.g. |
|
|
Maximum log file size in MB before rotation |
|
|
Number of rotated log files to keep |
|
|
Directory where log files are written. Accepts a relative ( |
Valid levels (from least to most verbose): CRITICAL β ERROR β WARNING β INFO β DEBUG.
β οΈ Warning:
pathmust be local. A rotating file handler cannot write to a remote location, so aremote:value is reported as an error byassets-guardian check, and logging silently falls back tolocal:logswith a warning.π‘ Tip: The directory is created at startup if it does not exist. Make sure the account running Assets Guardian can write there, an absolute path such as
/var/log/assets-guardianusually needs to be created and owned beforehand.β οΈ Warning: In Docker, keep
pathconsistent with the mounted volume. The run commands in Running mountlogs/to/app/logs, which matches thelocal:logsdefault. Pointpathsomewhere else without mounting it and the logs are written inside the container: they are lost when it exits, with no error reported.π‘ Tip: The
-v/--verboseCLI flag forcesconsole_leveltoDEBUG, and-q/--quietforces it toCRITICAL, regardless of what is set here. The log file level is never affected by CLI flags, the file always follows the key:file_levelinconfig.ymlfile.
The paths sectionο
Locations of every file that Assets Guardian reads or writes. Every value is prefixed: local: resolves relative to the working directory (or accepts an absolute path), remote: points to a SharePoint document library.
paths:
excel: "local:outputs/assets_guardian.xlsx"
pdf: "local:outputs/audit_report.pdf"
rules: "local:config/rules_config.yml"
excel_config: "local:config/excel_config.json"
pdf_config: "local:config/pdf_config.json"
excel_config: "local:config/excel_config.json"
pdf_config: "local:config/pdf_config.json"
employees: "local:config/employees.json"
Field |
Default |
|
Description |
|---|---|---|---|
|
|
β |
Output Excel report, also read back as the audit baseline and access matrix |
|
|
β |
Output PDF report |
|
|
β |
Input IAM rules registry |
|
|
β |
Excel report styling rules |
|
|
β |
PDF report styling rules |
|
|
β |
Input HR database |
Using a remote: path. The format is remote:<instance>:<path in the document library>, where <instance> is the label of a configured microsoft365 instance:
paths:
excel: "remote:main:Security/IAM/assets_guardian.xlsx"
Files are downloaded to the local cache before being read, and re-downloaded whenever the SharePoint version changes. Generated reports are uploaded once produced.
Date-stamped filenames. Write the literal word DATE in the filename of excel or pdf and it is replaced by the current date, formatted YYYY_MM_DD:
paths:
excel: "local:outputs/assets_guardian_DATE.xlsx" # -> assets_guardian_2026_07_30.xlsx
pdf: "local:outputs/audit_report_DATE.pdf" # -> audit_report_2026_07_30.pdf
Without the DATE keyword the filename is used as-is, and each run overwrites the previous file. This is opt-in, and only the filename is substituted, never the directories above it.
β οΈ Warning:
auditdoes not only write the dated file, it also reads back the Excel workbook to load the audit baseline and the access matrices, and it recomputes todayβs name to find it. Asyncrun on one day followed by anauditon the next therefore looks for a file that does not exist: the audit falls back to an empty baseline and an empty matrix, logging only a warning. Every access then appears unauthorized and comparison rules detect nothing. WithDATEinpaths.excel, runsyncandauditon the same day.π‘ Tip: The date is computed in UTC, not local time. Late in the evening in a UTC+n timezone the generated name may already have rolled over to the next day.
β οΈ Warning: A
remote:path requires a workingmicrosoft365section inconfig.yml, with theSites.ReadWrite.AllGraph permission granted. Without it,assets-guardian checkfails with βOutput path is remote but no microsoft365 integration is configuredβ.β οΈ Warning:
excel_configandpdf_configare restricted tolocal:. They are read before any remote client exists, soCheckEnginerejects a remote location for these two.π‘ Tip: Each path can also be set via a dedicated environment variable (
PATH_EXCEL,PATH_PDF,PATH_RULES,PATH_EXCEL_CONFIG,PATH_PDF_CONFIG,PATH_EMPLOYEES), which takes priority over the config file. This is useful to redirect outputs in a CI pipeline without editing the file.
Excel workbook integrityο
Every sync protects the generated workbook in two independent ways:
Read-only sheets. Every auto-generated sheet (anything that is not a ββ¦ Matrixβ sheet) is locked within Excelβs UI, discouraging accidental edits. ββ¦ Matrixβ sheets are deliberately left unlocked, since filling them in by hand for future audits is their intended purpose.
Tamper detection. A SHA-256 checksum of each sheetβs content (auto-generated and Matrix sheets alike) is stored in the workbookβs custom properties on every write. The next
syncrecomputes it from the file it finds and logs a warning naming every sheet that changed since the last write, whether or not it was locked - along with who last saved the file and when, when that information is available.
π‘ Tip: The sheet lock is a UI-level deterrent (
openpyxlsheet protection), not encryption. It is never verified or unlocked by the tool itself, so the password behind it does not need to be known by anyone - it exists purely to make casual edits in Excel harder. If you need to remove it (e.g. to fix an auto-generated sheet by hand), the password isplaceholder(Excel: Review > Unprotect Sheet), matching the_PROTECTION_PASSWORDconstant incore/reporting/excel/writer.py.β οΈ Warning: The tamper-detection warning never blocks
sync, it only logs. Editing a ββ¦ Matrixβ sheet by hand - the expected workflow - will trigger it on the very nextsync, just like an edit to an auto-generated sheet would; there is no way to tell the two apart from the checksum alone.π‘ Tip: The warning also names the fileβs last author and save date, read from its standard
lastModifiedBy/modifiedproperties. These are filled in by whatever application last saved the file (e.g. Excelβs File > Options > General > User name) - self-reported, not cryptographically verified, and left blank or generic by some non-Microsoft editors (e.g. OnlyOffice on Linux). Treat it as a helpful hint, not proof.
Plugin sectionsο
Add one block per plugin instance you want to audit:
gitlab:
prod: # Arbitrary environment label
url: "https://gitlab.company.com/api/v4"
credentials:
personnal_access_token: "${GITLAB_PROD_TOKEN}"
test: # Arbitrary environment label
url: "https://gitlab-test.company.com/api/v4"
credentials:
personnal_access_token: "${GITLAB_TEST_TOKEN}"
dolibarr:
my_instance: # Arbitrary environment label
url: "https://dolibarr.company.com/api/index.php"
credentials:
dolapikey: "${DOLIBARR_TOKEN}"
microsoft365:
main: # Arbitrary environment label
credentials:
tenant_id: "${M365_TENANT_ID}"
application_id: "${M365_APPLICATION_ID}"
client_secret: "${M365_CLIENT_SECRET}"
Most plugins reach their source over plain HTTP and therefore need a url. Microsoft 365 is the exception: it goes through the official Graph SDK, which resolves the endpoint itself, so the section takes no url key and only carries credentials.
β οΈ Warning: A plugin section must be present (and not commented out) for the corresponding plugin to run. Plugins with no section are silently skipped.
π‘ Tip: Each plugin ships with a
CREDENTIALS.mdfile that explains the required credentials fields, how to generate them on the target platform, and the minimum permissions Assets Guardian needs.
π Environment variablesο
You may have noticed placeholders like ${M365_APPLICATION_ID} or ${M365_CLIENT_SECRET} in the configuration above. These are environment variable references, they keep sensitive credentials out of config.yml and out of version control. You can supply them via a .env file or directly as OS-level environment variables.
Assets Guardian accepts environment variables from any standard source, use whichever fits your setup:
.envfile (quickest for local use): copy the provided template and fill in only the variables you need.cp .env.template .env # then edit .env, comment out anything you don't use...
Shell / OS environment: export variables directly in your terminal or shell profile.
export GITLAB_MAIN_TOKEN=glpat-xxxxxxxxxxxxxxxxxxxx
Docker: pass them via
--env-fileor-eat runtime, no.envfile required on the host.docker run --env-file .env assets-guardian ... # or individually: docker run -e GITLAB_MAIN_TOKEN=... assets-guardian ...
CI/CD secrets: inject them as pipeline environment variables (GitHub Actions secrets, GitLab CI variables, etc.), nothing to store on disk.
Injecting environment variables into config.yml (recommended)ο
The recommended way to handle secrets is to never write them in plain text inside config.yml. Instead, reference your environment variables using the ${VAR_NAME} syntax directly in the YAML file:
gitlab:
prod:
credentials:
personnal_access_token: "${GITLAB_PROD_TOKEN}"
At startup, Assets Guardian reads your .env file and expands all ${...} placeholders before parsing the YAML. Your secrets stay out of the file, the only thing committed to version control is the reference, not the value.
π‘ Tip: This approach makes it trivial to switch between environments (dev, test, prod) by swapping the
.envfile, without ever touchingconfig.yml.
How get_config_value resolves configurationο
Internally, Assets Guardian uses get_config_value (loader.py) whenever it reads a value from the configuration. This function applies a strict priority chain:
flowchart TD
subgraph LOAD["β Loading: load_yaml_config()"]
SYS["π₯οΈ System env vars\n(shell / CI / Docker)"]
ENV_FILE[".env file"]
ENV_FILE -->|"setdefault\nnever overwrites"| MERGED["os.environ\n(merged env)"]
SYS -->|always present| MERGED
MERGED -->|"os.path.expandvars()\nresolves \${VAR} in YAML"| PARSED["config.yml\nparsed dict"]
end
subgraph RESOLVE["β‘ Resolution: get_config_value()"]
Q1{"env_override?\ndefault: True"} -->|Yes| Q2{"Env var\nset?"}
Q2 -->|Yes| R1["β
env value"]
Q2 -->|No| Q3{"Key in\nYAML dict?"}
Q1 -->|No| Q3
Q3 -->|Yes| R2["β
YAML value"]
Q3 -->|No| Q4{"Default\nprovided?"}
Q4 -->|Yes| R3["β
default"]
Q4 -->|No| R4["β KeyError"]
end
LOAD --> RESOLVE
The environment variable name is derived automatically from the YAML key path by uppercasing it and replacing : and - with _. For example:
YAML key path |
Derived env variable |
|---|---|
|
|
|
|
This means you can override any configuration value at runtime by setting the corresponding environment variable, without modifying config.yml at all. This is particularly useful in CI/CD pipelines or containerised deployments where injecting env vars is more practical than managing files.
π‘ Tip: The
${VAR}interpolation inconfig.ymland the env-variable override inget_config_valueare two independent mechanisms that complement each other. The interpolation makes secrets explicit and readable in the file, the override priority lets CI infrastructure inject values without a config file at all.
π The template.config.yml fileο
A reference copy of config.yml with placeholder values. Assets Guardian validates your config.yml against this file at startup.
Do not delete or rename it. It is not a backup, it is required at runtime. Keep it alongside config.yml at all times.
π‘ Tip: When you add a new plugin section or a new field to
config.yml, mirror the change intemplate.config.ymlwith an appropriate placeholder value, otherwise validation will fail.
π The rules_config.yml fileο
The IAM policy registry. It declares which rules are active for each plugin and configures their thresholds.
Rules are grouped using a YAML anchor (define: &default_rules) so they can be inherited by any plugin with <<: *default_rules. A rule is only evaluated if it appears under the pluginβs section, even if the rule is implemented in the pluginβs code, it will be silently ignored at runtime if it has no entry here.
Each rule entry takes a description and a severity. All additional fields (thresholds, IPs, flags, etc.) are passed directly to the rule as free-form parameters, what is accepted depends entirely on the ruleβs own implementation.
severity drives how the finding is ranked and coloured in the PDF report. It is read from this file by every rule: if the key is missing or left empty, the rule logs a warning and falls back to the default listed below, the audit still runs.
Built-in default rules:
Rule ID |
Description |
Default severity |
Extra configurable fields |
|---|---|---|---|
|
Account without MFA enabled |
|
- |
|
Inactivity threshold exceeded |
per tier |
|
|
Account with excessive permissions |
|
- |
|
Connection from an unusual location |
|
|
Built-in identity naming-convention rules: CTRL_HUMAN_*, CTRL_SERVICE_* and CTRL_GENERIC_* check identity attributes (name, username, email, β¦) against the companyβs identity-creation naming convention, one rule per attribute and identity type (human, non-human/service, generic).
Rule ID |
Description |
Default severity |
Extra configurable fields |
|---|---|---|---|
|
Last name entirely in uppercase |
|
- |
|
First name properly capitalized per component |
|
- |
|
Full name is βfirst lastβ (+ |
|
- |
|
Username format (+ |
|
- |
|
Email format (+ |
|
|
|
Creation date recorded |
|
- |
|
Job title set |
|
- |
|
Full name suffixed |
|
- |
|
Username format (lowercase, hyphen-separated) |
|
- |
|
Creation date recorded |
|
- |
|
Description set |
|
- |
|
Never marked as third-party |
|
- |
|
Username format (lowercase, hyphen-separated) |
|
- |
|
Creator recorded |
|
- |
|
Creation date recorded |
|
- |
|
Description set |
|
- |
β οΈ Coverage depends on what each source actually provides. A rule only fires when the source populates the field it checks β it never guesses. Notably: GitLab only exposes a single merged
namefield (no separate first/last name), so everyCTRL_HUMAN_*rule based on them silently skips GitLab identities. Third-party (-ext/.ext/' (EXT)') checks rely onis_external, which only GitLab populates, from its own βexternal userβ access flag rather than a genuine βthird-party companyβ indicator.CTRL_GENERIC_*rules currently never fire on real data: no plugin assigns thegenericidentity type yet.
Severity levels (from lowest to highest): INFO β WARNING β DANGER β CRITICAL
DEFAULT-002 is tiered. Instead of a single severity, it takes a list of thresholds, each pairing a number of inactive days with the severity to raise. The rule keeps the highest tier reached: with the defaults below, an account inactive for 200 days is reported as DANGER, and one inactive for 400 days as CRITICAL. Tiers are sorted automatically, so declaration order does not matter, and a tier missing its days is skipped with a warning.
Example:
define: &default_rules
DEFAULT-001:
description: "Account without MFA enabled."
severity: "DANGER"
DEFAULT-002:
description: "Inactivity threshold exceeded for an account."
inactivity_threshold_days:
- severity: "WARNING"
days: 90
- severity: "DANGER"
days: 180
- severity: "CRITICAL"
days: 365
DEFAULT-003:
description: "Account with excessive permissions."
severity: "DANGER"
DEFAULT-004:
description: "Connection from an unusual location."
severity: "WARNING"
company_network_ip: "192.168.1.2" # Your corporate network IP/range
gitlab:
<<: *default_rules # Inherit all default rules
COMPLIANCE-001: # Plugin-specific rule
description: "Gitlab mailbox not matching any employee in employees.json."
severity: "WARNING"
dolibarr:
<<: *default_rules
COMPLIANCE-001: # Same ID as GitLab's, but a separate implementation
description: "Dolibarr mailbox not listed in employees.json."
severity: "WARNING"
DOLIBARR-005:
description: "Lists disabled user accounts in Dolibarr."
severity: "INFO"
microsoft365:
<<: *default_rules
COMPLIANCE-002:
description: "Microsoft365 mailbox not listed in employees.json."
severity: "WARNING"
β οΈ Warning: Removing a rule from a plugin section disables it entirely for that plugin, even if it is defined in
&default_rules.
Rule IDs are namespaced per source, so gitlabβs COMPLIANCE-001 and dolibarrβs COMPLIANCE-001 are two independent rules that happen to share a number. Always read a rule ID together with the plugin section it sits in.
π‘ Tip: The snippet above is trimmed for readability, each plugin also ships matrix (
MATRIX-XXX) and comparison (COMPARE-XXX) rules.config/template.rules_config.ymllists every rule available for every shipped plugin and is the file to copy from.
π The employees.json fileο
The HR source of truth. Assets Guardian cross-references every account found during an audit against this list to detect shadow accounts (accounts that belong to no known employee).
It is a JSON array of employee objects:
[
{
"first_name": "John",
"last_name": "DOE",
"email": ["john.doe@company.com", "jdoe@company.com"],
"username": ["jdoe", "john.doe"],
"profiles": "R&D, Support"
}
]
Field |
Description |
|---|---|
|
Employeeβs first name |
|
Employeeβs last name (usually uppercase by convention) |
|
Professional email address(es), single value or list. This is the identifier every rule matches on: shadow-account detection compares collected accounts against these addresses, and matrix rules resolve an employeeβs profiles through them. Listing several addresses is how one employee holding multiple accounts is recognised |
|
Login handle(s) used across audited platforms, single value or list. Reported in the Excel employees sheet, and used to resolve profiles only as a fallback, when the entry has no |
|
Comma-separated list of job profiles / departments, used to detect privilege mismatches with matrix |
β οΈ Warning: This file must be maintained manually. Any account found on an audited platform that cannot be matched to an entry here will be flagged as a potential shadow account.
π The excel_config.json fileο
Controls cell validation rules and conditional formatting applied to the generic sheets of the generated Excel report (e.g. βAccess Review Scope Listβ). Plugin-specific sheets are not configured here, each plugin ships its own embedded JSON that governs its own columns and is not meant to be edited by the user.
Structure:
{
"Sheet Name": [
{
"column_name": "Column Header",
"rules": [ ... ]
}
]
}
Two rule_type values are supported:
list_validation: restricts a cell to a dropdown of allowed values.
Field |
Required |
Description |
|---|---|---|
|
β |
|
|
β |
Exact column header as it appears in the sheet |
|
β |
Always |
|
β |
Array of allowed string values |
|
- |
Allow blank cells (default: |
conditional_format: highlights cells based on their value.
Field |
Required |
Description |
|---|---|---|
|
β |
|
|
β |
Exact column header as it appears in the sheet |
|
β |
Color name to apply (see palette below) |
|
- |
|
|
- |
Comparison value: string, number, boolean, or array of values to match against |
Available colors:
Name |
Background |
Font |
|---|---|---|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
Full example:
{
"Access Review Scope List": [
{
"column_name": "Access Review Method",
"rules": [
{
"rule_type": "list_validation",
"validate": "list",
"source": ["Automated (Assets Guardian)", "Partial Automated", "Manual"],
"ignore_blank": true
},
{
"rule_type": "conditional_format",
"format": "green",
"criteria": "==",
"value": "Automated (Assets Guardian)"
},
{
"rule_type": "conditional_format",
"format": "yellow",
"criteria": "==",
"value": ["Partial Automated", "Manual"]
}
]
},
{
"column_name": "Status",
"rules": [
{
"rule_type": "conditional_format",
"format": "red",
"criteria": "is_empty"
}
]
}
]
}
π‘ Tip: A column entry can carry both a
list_validationand one or moreconditional_formatrules simultaneously, they are applied independently.
π The pdf_config.json fileο
Controls the visual appearance of the generated PDF audit report. The file has three top-level sections: settings, colors, and fonts. All sections are optional, only the values you want to override need to be specified, the rest fall back to built-in defaults.
Structure:
{
"settings": { ... },
"colors": { "<role>": { "r": 0, "g": 0, "b": 0 }, ... },
"fonts": { "<role>": { "family": "...", "style": "...", "size": 0 }, ... }
}
settings
Field |
Default |
Description |
|---|---|---|
|
|
Render an empty checkbox next to each finding (useful for manual review sign-off) |
|
|
Checkbox size in mm |
|
|
Timezone used to format all timestamps in the report (any valid IANA name, e.g. |
colors
Each entry maps a color role to an RGB triplet {"r": 0β255, "g": 0β255, "b": 0β255}.
Key |
Default |
Used for |
|---|---|---|
|
|
Severity label color for CRITICAL findings |
|
|
Severity label color for DANGER findings |
|
|
Severity label color for WARNING findings |
|
|
Severity label color for INFO findings |
|
|
Background color for table headers (used by plugin builders) |
|
|
Fallback text color when no severity color matches |
fonts
Each entry maps a font role to a font definition {"family": "...", "style": "...", "size": N}.
Font style follows FPDF conventions: "B" bold, "I" italic, "BI" bold-italic, "" regular.
Key |
Default |
Used for |
|---|---|---|
|
|
Cover page main title |
|
|
Cover page subtitle |
|
|
Section headings and summary labels |
|
|
Finding descriptions and body text |
|
|
Severity group label inside finding sections, if omitted, inherits the previous font |
Full example (all defaults shown):
{
"settings": {
"show_checkboxes": true,
"checkbox_size": 3.5,
"timezone": "Europe/Paris"
},
"colors": {
"CRITICAL": { "r": 255, "g": 0, "b": 0 },
"DANGER": { "r": 255, "g": 69, "b": 0 },
"WARNING": { "r": 255, "g": 165, "b": 0 },
"INFO": { "r": 0, "g": 122, "b": 255 },
"header_bg": { "r": 208, "g": 208, "b": 208 },
"text": { "r": 0, "g": 0, "b": 0 }
},
"fonts": {
"title": { "family": "helvetica", "style": "B", "size": 16 },
"subtitle": { "family": "helvetica", "style": "B", "size": 14 },
"heading": { "family": "helvetica", "style": "B", "size": 12 },
"body": { "family": "helvetica", "style": "", "size": 10 },
"severity": { "family": "helvetica", "style": "B", "size": 18 }
}
}
π‘ Tip: You only need to include the keys you want to change. For example, to only adjust the timezone and make CRITICAL findings red-pink, a minimal config is sufficient, everything else keeps its default value.
βΆοΈ Runningο
π‘οΈ Assets Guardian commandsο
Assets Guardian exposes three primary commands:
check: Runs a global checkup of configuration files, output volume permissions, and Assets Guardianβremote instance connectivity for all enabled plugins.sync: Connects to all configured platform instances, retrieves their data, and creates/updates the corresponding Excel (.xlsx) database accordingly.audit: Evaluates the configured security policies, executes comparison checks over time across all instances, and generates the detailed PDF audit report.
A fourth, advanced command is aimed at power users:
script <name>: Executes a custom Python script dropped at the root of thescripts/directory, with full access to the application context (see the Power-user scripts section below).
These flags can be applied globally to any command. Note that they must be placed before the subcommand (e.g., uv run assets-guardian --verbose <sync>).
Option / Flag |
Description |
Default / Behavior |
|---|---|---|
|
Custom path to the application configuration file. |
|
|
Forces console logs to |
|
|
Mutes non-critical logs. Only |
|
|
Simulation mode. Executes logic without applying real side effects. |
|
|
Disables all interactive prompts (perfect for automation/CI). |
|
|
Displays the help menu with all available options and exits. |
- |
|
Displays the version of the tool and exits. |
- |
π§ͺ Power-user scriptsο
β οΈ Warning: This is an advanced, deliberately unpolished feature aimed at tinkerers: scripts are arbitrary Python executed without guardrails, unlike the rest of Assets Guardian.
The script command runs a custom Python file dropped at the root of the scripts/ directory (resolved from the current working directory, like logs/):
assets-guardian script my_automation # runs scripts/my_automation.py (the .py suffix is optional)
Each script must expose a run(ctx) function. The received Context gives full access to the loaded configuration (ctx.app_config), the initialized logging system, and the plugin registries, client providers and collectors are already discovered and registered when the script runs, so it can talk to every configured integration:
import logging
from assets_guardian.core.domain.models.context import Context
logger = logging.getLogger(__name__)
def run(ctx: Context) -> None:
"""Entry point called by 'assets-guardian script <name>'."""
logger.info("Running in '%s' environment.", ctx.app_config.env)
See scripts/example.py in the repository for a complete, runnable example that instantiates a client per configured integration instance and checks its connectivity.
βΆοΈ Running by modeο
The exact invocation pattern depends on which mode you installed. Replace <command> with check, sync, audit, or script <name>. Global options (--verbose, --dry-run, etc.) always go before the subcommand.
π Standalone (with assets-guardian)ο
The assets-guardian command is on your PATH and can be invoked directly from any audit folder.
assets-guardian [options] <command>
Examples:
assets-guardian check
assets-guardian sync
assets-guardian --verbose audit
π Local dev (with uv)ο
Prefix every command with uv run to run from the projectβs virtual environment.
uv run assets-guardian [options] <command>
Examples:
uv run assets-guardian check
uv run assets-guardian sync
uv run assets-guardian --verbose audit
π‘ Tip: Makefile shortcuts:
make local-check,make local-sync,make local-audit.
π³ Docker image (use)ο
Mount the four required directories and pass credentials via an env file. The Docker imageβs entrypoint is assets-guardian, so options and subcommands are appended directly.
docker run --rm \
-v $(pwd)/logs:/app/logs \
-v $(pwd)/outputs:/app/outputs \
-v $(pwd)/config:/app/config:ro \
-v $(pwd)/.assets-guardian_cache:/app/.assets-guardian_cache \
--env-file .env \
assets-guardian [options] <command>
π‘ Tip: Makefile shortcuts:
make docker-check,make docker-sync,make docker-audit.
π» Dev Container (VS Code)ο
Once the Dev Container is open in VS Code, the full toolchain is available, all three invocation modes work:
Via uv run (preferred):
uv run assets-guardian [options] <command>
Directly on PATH:
assets-guardian [options] <command>
Via Docker: (docker-in-docker is available in the Dev Container):
docker run --rm \
-v $(pwd)/logs:/app/logs \
-v $(pwd)/outputs:/app/outputs \
-v $(pwd)/config:/app/config:ro \
-v $(pwd)/.assets-guardian_cache:/app/.assets-guardian_cache \
--env-file .env \
assets-guardian [options] <command>
π‘ Tip: Makefile shortcuts (
make local-check,make docker-check, etc.) also work as-is.
π³ Docker image (develop)ο
Same as the use variant, with an additional src/ bind-mount so that source code changes are reflected in the running container without rebuilding the image (the package is installed as editable inside the container).
docker run --rm \
-v $(pwd)/src:/app/src \
-v $(pwd)/logs:/app/logs \
-v $(pwd)/outputs:/app/outputs \
-v $(pwd)/config:/app/config:ro \
-v $(pwd)/.assets-guardian_cache:/app/.assets-guardian_cache \
--env-file .env \
assets-guardian [options] <command>
β οΈ Warning: Only source code changes under
src/are picked up live. Any change to dependencies (pyproject.toml,uv.lock) or theDockerfileitself requires a full rebuild (make docker-build).