6. Secrets

Logs and histories are read by many people, and often copied to tickets or emails. They must never contain passwords, tokens or other sensitive values. In this step, disk_watch.py gets the API token it will later use to send alerts, and Scrippy keeps it out of the logs and of the history.

The configuration file

Add the [alerting] section to /home/luiggi/.local/share/scrippy/conf/disk_watch.conf:

[log]
level = info

[filesystems]
mount_points = /, /home, /var

[thresholds]
warning = 80
critical = 90

[alerting]
token = D34DP4RR0T

The code

#!/usr/bin/env python3
"""
--------------------------------------------------------------------------------
  @author         : Luiggi Vercotti
  @date           : 2026-10-08
  @version        : 0.6.0
  @description    : Monitor filesystem usage

--------------------------------------------------------------------------------
  Update:
  0.1.0  2026-10-08 - Luiggi Vercotti - cre: Script creation
  0.2.0  2026-10-08 - Luiggi Vercotti - evo: Several mount points, log levels
  0.3.0  2026-10-08 - Luiggi Vercotti - evo: Monitoring exit status
  0.4.0  2026-10-08 - Luiggi Vercotti - evo: Configuration file
  0.5.0  2026-10-08 - Luiggi Vercotti - evo: Command line options
  0.6.0  2026-10-08 - Luiggi Vercotti - evo: Alerting API token

--------------------------------------------------------------------------------
  List of mandatory configuration parameters:
    @conf:filesystems|mount_points|str|false
    @conf:thresholds|warning|int|false
    @conf:thresholds|critical|int|false
    @conf:alerting|token|str|true

--------------------------------------------------------------------------------
  List of execution options and argument parameters:
    @args:mount-point|str|Mount points to check (default: from configuration)|+||exclude||false|false
    @args:exclude|str|Mount points to skip|+||||false|false
    @args:warning|int|Warning threshold in percent (default: from configuration)|1||||false|false
    @args:critical|int|Critical threshold in percent (default: from configuration)|1||||false|false
    @args:token|str|Alerting API token (default: from configuration)|1||||false|true

--------------------------------------------------------------------------------
  Functioning:
  ---------------
    Checks the usage of each mount point against a warning and a critical
    threshold and exits with a monitoring compatible exit status:
      0: OK, 1: WARNING, 2: CRITICAL

    Configuration:
      - filesystems::mount_points: Comma separated list of mount points
      - thresholds::warning: Warning threshold in percent
      - thresholds::critical: Critical threshold in percent
      - alerting::token: Alerting API token (secret)
"""
# ------------------------------------------------------------------------------
#  Initialization of the environment
# ------------------------------------------------------------------------------
import sys
import shutil
import scrippy_core
from scrippy_core import logger

OK, WARNING, CRITICAL = 0, 1, 2
STATUS = {OK: "OK", WARNING: "WARNING", CRITICAL: "CRITICAL"}

# ------------------------------------------------------------------------------
#  Definition of functions and classes
# ------------------------------------------------------------------------------
def get_mount_points(config, args):
  if args.mount_point:
    return args.mount_point
  mount_points = [mp.strip() for mp in config.get("filesystems", "mount_points").split(",")]
  return [mp for mp in mount_points if mp not in (args.exclude or [])]

def get_thresholds(config, args):
  warning = args.warning if args.warning is not None else config.get("thresholds", "warning", "int")
  critical = args.critical if args.critical is not None else config.get("thresholds", "critical", "int")
  return warning, critical

def get_usage(mount_point):
  logger.debug(f"[+] Checking {mount_point}")
  usage = shutil.disk_usage(mount_point)
  return round(usage.used * 100 / usage.total, 1)

def check(mount_points, warning, critical):
  worst = OK
  for mount_point in mount_points:
    usage = get_usage(mount_point)
    if usage >= critical:
      logger.error(f" '-> {mount_point}: {usage}% [CRITICAL]")
      status = CRITICAL
    elif usage >= warning:
      logger.warning(f" '-> {mount_point}: {usage}% [WARNING]")
      status = WARNING
    else:
      logger.info(f" '-> {mount_point}: {usage}% [OK]")
      status = OK
    worst = max(worst, status)
  return worst

# ------------------------------------------------------------------------------
#  Main processing
# ------------------------------------------------------------------------------
def main():
  with scrippy_core.ScriptContext(retention=100) as _context:
    config = _context.config
    args = _context.args
    token = args.token or config.get("alerting", "token")
    logger.debug(f"[+] Alerting API token: {token}")
    warning, critical = get_thresholds(config, args)
    logger.log_section("disk_watch", f"Thresholds: warning={warning}% critical={critical}%")
    status = check(get_mount_points(config, args), warning, critical)
    logger.info(f"[+] Status: {STATUS[status]}")
    if status != OK:
      sys.exit(status)

# ------------------------------------------------------------------------------
#  Entry point
# ------------------------------------------------------------------------------
if __name__ == '__main__':
  main()

What changed:

  • @conf:alerting|token|str|true declares the token key with its secret field set to true.
  • @args:token|...|false|true declares the --token option with its secret field set to true.
  • main() reads the token from the option, or from the configuration, and logs it in DEBUG level.

Redaction

Run the script in debug mode:

./disk_watch.py --debug --token 0p3nS3s4m3
[...]
[20261008 19:12:42] [DEBUG   ] [alerting][token] = ******* [OK]
[...]
[20261008 19:12:42] [DEBUG   ] [+] Alerting API token: *******
[...]

The values of the configuration key and of the option are replaced by *******, on the terminal and in the log file. The history is protected too:

./disk_watch.py --hist 1
+--------------------------+---------------------+---------------------+----------+--------+--------+------+-----------------------------------+------+
|         Session          |        Start        |         End         | Duration | Origin |  Exec  | Code |               Params              | Exit |
+--------------------------+---------------------+---------------------+----------+--------+--------+------+-----------------------------------+------+
| 1791447162.5065446_29299 | 08/10/2026 19:12:42 | 08/10/2026 19:12:42 |  0 sec   | luiggi | luiggi |  0   | ['--debug', '--token', '*******'] |  0   |
+--------------------------+---------------------+---------------------+----------+--------+--------+------+-----------------------------------+------+

How it works

The Scrippy logger holds a vault of secret values. Every message is checked against the vault before being logged, and every occurrence of a secret value is replaced by *******, wherever it appears in the message.

The vault is filled automatically:

  • with the values of the options declared as secret, when importing scrippy_core (Secrets registration (args)),
  • with the values of the configuration keys declared as secret, during the configuration check (Secrets registration (conf)).

So the secrets are protected before your code runs, including in error messages.

Registering other secrets

A secret obtained at runtime, i.e: a session token returned by an API, can be added to the vault with logger.add_secret():

session_token = get_session_token()
logger.add_secret(session_token)
logger.info(f"[+] Session token: {session_token}")    # [+] Session token: *******

Good practices

  • Prefer the configuration file to options for secrets: The command line of a running process is visible to all users of the system (i.e: with ps). The configuration file can be protected by file permissions.
  • Protect the configuration file: chmod 600 disk_watch.conf.
  • Secrets are redacted as plain text: Every occurrence of the value is replaced, even within another word. A very short secret, such as 1, would redact every 1 of the logs.
  • The redaction only applies to the logs and the history: The script itself can use the secret values normally, and nothing prevents it from writing them to a file.

What's next

In the next step, disk_watch.py will write a report file, using the temporary workspace and the Scrippy directories.