Skip to content

Security: dynamic-config-rs/dynamic-config-python

Security

SECURITY.md

Security

Reporting a vulnerability

Please report privately, through GitHub's advisory form, rather than in a public issue.

Include what you would want if you were on the other side: what an attacker can do, what they need to start with, and — if you have one — a reproduction. A first response should take a few days; if it takes longer than a week, assume the message went astray and ping the issue tracker without details.

What this crate is responsible for

A configuration library sits in an awkward place: it reads files a program trusts, holds values a program must not leak, and — with the companion crates — talks to services over a network. These are the properties it tries to keep, and the ones it explicitly does not.

It tries to keep

Secrets stay out of diagnostics. #[config(secret)] redacts a field in Debug; reload diffs, check() reports, unknown-key suggestions and error messages all report paths and types, never values, so nothing routes around the redaction. A report of a value appearing in a log or an error message is a vulnerability. the engine repository's tests/security.rs asserts each of these, and its CI runs it as a job of its own.

The parsing surfaces are fuzzed. The unit parsers, Value path lookup and the rule that decides whether a path touches a secret run under libFuzzer (the engine repository's fuzz/, built on every CI run and run on a weekly schedule). A crash or a redaction rule that answers differently for the same path is a finding: report it through the process above, with the reproducing input attached.

Files it writes are private from the moment they exist. save and the last-known-good cache create their file 0600 on Unix — created that way, not chmodded afterwards — through a temporary path opened with create_new, so a symlink planted at that path is refused rather than followed.

A remote store cannot crash the process. Everything a server sends is treated as untrusted input: lease durations that would overflow a clock, bodies that are not JSON, values that are not UTF-8. A panic reachable from a server's response is a vulnerability.

Credentials are not logged. A Debug on a source prints its address and its key, never a token, a password or a key file's contents.

It explicitly does not

Keep secrets out of process memory. A resolved configuration holds every value, because that is what configuration is — a program that can use a password can read it. The age feature zeroizes the decrypted file once parsed, which is defence in depth, not a claim about memory.

Encrypt what it writes. save and the cache write plaintext. The cache's three modes exist so that is a choice rather than a surprise; see the book.

Validate that a config file is trusted. If an attacker can write your config file, they can configure your program. That is the file's permissions to enforce, not this crate's.

Sandbox a Decryptor or a RemoteSource you implement. Those run your code with your privileges.

Supported versions

Version Supported
0.3.x ✅ the latest patch
≤ 0.2 — end of life

Security fixes land on the latest patch of the line above and nothing is backported before 1.0: when a release ships, every prior patch of its line is end-of-life the same day. Older toolchains resolve older published versions through cargo's MSRV-aware resolver and are explicitly unsupported. After 1.0, the current and previous minor lines. The full promise lives in the engine book's Compatibility Contract.

Threat model, stated plainly

This crate assumes:

  • The config files are trusted. Whoever can write them can configure the program, which is the point of a config file. File permissions are the control, not this crate.
  • The environment is trusted. Same reasoning: anything that can set APP_DB_HOST is already inside the process's world.
  • A remote store is not trusted. It is across a network, may be compromised, and may simply be buggy. Everything it sends is parsed defensively; nothing it sends can panic the process or reach an arithmetic overflow.
  • A Decryptor or RemoteSource you write is trusted. It runs your code.
  • Log output is not private. Everything this crate writes to a log or an error may end up in a system that many people can read, which is why diagnostics carry paths and types rather than values.

How the properties are kept honest

Prose is not a guarantee, so each claim above has something behind it that CI runs on every change:

Claim Enforced by
Secrets stay out of diagnostics the engine repository's tests/security.rs, run as its own CI job there
Files are created private, symlinks refused write::permissions tests, same job
A store cannot panic the process checked_add on every server-supplied duration, plus a hostile-document test
No unsafe code #![forbid(unsafe_code)] in every crate, and a CI job that checks the attribute is still there
No unmaintained or vulnerable dependency cargo deny, on every push and weekly on a schedule
No surprise dependency cargo deny sources and licences, plus dependency review on pull requests

Exceptions in deny.toml are listed one at a time with a reason. There is no blanket "ignore dev-dependencies": something that runs on a contributor's machine or in CI still matters.

Dependencies

The core crate's non-optional dependency list is deliberately short — figment, serde, arc-swap — and every network client, format parser and crypto stack is behind a feature or in a companion crate, so a build carries only what it asked for. A dynamic-config with default features pulls in no cryptography, no HTTP client and no runtime.

cargo deny runs on every push and weekly on a schedule, because an advisory is published on somebody else's timetable: a dependency that was clean on Monday is not necessarily clean on Friday, and nothing about this repository has to change for that to happen.

GitHub's Dependabot alerts are a second feed with different coverage — GHSA advisories that RustSec has not picked up, and vice versa — so passing cargo deny does not close the security tab. The standing rule: every open alert is triaged before a release ships. Triaged means one of two outcomes, each visible afterwards: the lockfile moves to a patched version (with --precise when the MSRV-fallback resolver refuses the jump — the same mechanism deny.toml documents for time), or the alert is dismissed with a written reason stating why the vulnerable path is not reachable and what would reopen the question. An alert that is neither fixed nor argued away blocks the release.

What a report gets you

A fix, credit unless you would rather not have it, and a note in the changelog under Security describing what was wrong in enough detail to tell whether you were affected. If a fix has to be coordinated with a downstream project, that is worth saying in the first message.

There aren't any published security advisories