Skip to content

Define policy once, enforce it everywhere.

safe-pkgs loads typed config from global and project scopes, merges values deterministically, and sanitizes invalid thresholds back to secure defaults.

Global + project overlay Typed config Safe defaults

Load order

1. Global config

SAFE_PKGS_CONFIG_GLOBAL_PATH if set, otherwise ~/.config/safe-pkgs/config.toml.

2. Project override

SAFE_PKGS_CONFIG_PROJECT_PATH if set, otherwise ./.safe-pkgs.toml.

Project values overlay global values.

Full schema

Key Type Default Behavior
min_version_age_days integer 7 Versions newer than this raise risk. <= 0 is reset to default.
min_weekly_downloads integer 50 Packages below this threshold raise risk.
max_risk enum medium low \| medium \| high \| critical. Above this threshold means deny.
allowlist.packages string[] [] Package entries that should be explicitly allowed.
denylist.packages string[] [] Package entries that should be explicitly denied.
denylist.publishers string[] [] Publisher identities to deny.
staleness.warn_major_versions_behind integer 2 Major-version gap warning threshold. 0 resets to default.
staleness.warn_minor_versions_behind integer 3 Minor-version gap warning threshold. 0 resets to default.
staleness.warn_age_days integer 365 Warn if release age exceeds this value. <= 0 resets to default.
staleness.ignore_for string[] [] Package/version patterns excluded from staleness warnings.
checks.disable string[] [] Globally disable selected checks (version_age, staleness, popularity, install_script, typosquat, advisory).
checks.registry.<key>.disable string[] [] Disable checks only for a specific registry key (for example npm or cargo).
cache.ttl_minutes integer 30 Cache TTL in minutes. 0 resets to default.
lockfile.eval_concurrency integer 5 Number of packages evaluated in parallel during lockfile audits. Lower values reduce API burst load. 0 resets to default.
lockfile.inter_batch_delay_ms integer 100 Milliseconds to wait before spawning each replacement evaluation task after one completes. The initial batch is spawned immediately. Helps avoid rate limiting by spacing requests over time. Set to 0 for no delay.
custom_rules array(table) [] User-defined rule set evaluated alongside built-in checks. Invalid rules fail config load.

Merge rules

Scalar fields

Later sources overwrite earlier values (for example max_risk and numeric thresholds).

List fields

Lists are appended with de-duplication, so global and project entries combine cleanly (including checks.disable and per-registry disable lists).

Invalid values

Non-positive values for positive thresholds are reset to defaults to avoid unsafe settings.

Rate limiting defaults

The lockfile configuration defaults are intentionally conservative to minimize the risk of triggering registry API rate limits during large dependency audits.

Conservative by design

Default settings (eval_concurrency = 5, inter_batch_delay_ms = 100) prioritize reliability over speed. These values work well for most users and registries without hitting rate limits.

Default behavior: - 5 concurrent evaluations reduces peak API load by 50% compared to higher concurrency - 100ms inter-batch delay distributes requests over time instead of bursting - For a 100-package lockfile: ~261 total API calls spread across ~20 seconds (vs. ~10 seconds with no rate limiting)

When to adjust:

Scenario Recommended Settings Rationale
Default (most users) eval_concurrency = 5
inter_batch_delay_ms = 100
Balanced speed and rate limit safety
Strict rate limits eval_concurrency = 3
inter_batch_delay_ms = 200
Further reduced burst load for restrictive APIs
Generous rate limits eval_concurrency = 10
inter_batch_delay_ms = 0
Faster audits when rate limits are not a concern
CI/CD pipelines eval_concurrency = 3-5
inter_batch_delay_ms = 100-200
Conservative to avoid build failures

Avoid aggressive settings in shared environments

High concurrency with no delay can trigger 429 (Too Many Requests) errors, especially in CI/CD where multiple builds may run concurrently. Start with defaults and increase only if you confirm rate limits are not an issue.

Example

min_version_age_days = 7
min_weekly_downloads = 50
max_risk = "medium"

[cache]
ttl_minutes = 30

[lockfile]
eval_concurrency = 5        # Number of packages evaluated in parallel
inter_batch_delay_ms = 100  # Delay between spawning evaluation tasks (helps with rate limiting)

[[custom_rules]]
id = "deny-very-new-low-downloads"
severity = "high"
reason = "package is too new and has low adoption"
registries = ["npm"]
match = "all"
conditions = [
  { field = "version_age_days", op = "lt", value = 7 },
  { field = "weekly_downloads", op = "lt", value = 100 }
]

[staleness]
warn_major_versions_behind = 2
warn_minor_versions_behind = 3
warn_age_days = 365
ignore_for = ["legacy-pkg@1.x"]

[checks]
disable = ["typosquat"]

[checks.registry.npm]
disable = ["install_script"]

[allowlist]
packages = ["my-internal-pkg"]

[denylist]
packages = ["event-stream@3.3.6"]
publishers = ["suspicious-user-123"]

Apply changes

Config is loaded at process start. Restart safe-pkgs serve after edits.

Custom rule fields and operators

custom_rules[].conditions[].field supports: - registry - package_name - requested_version - latest_version - resolved_version - version_age_days - version_deprecated - has_install_scripts - install_script_count - publisher_count - publishers - weekly_downloads - advisory_count - advisory_ids

custom_rules[].conditions[].op supports: - eq, ne - gt, gte, lt, lte - contains - starts_with, ends_with - in - exists