fmt
fmt safety boundaries: format authority, argument lifetimes and output encoding
Distinguish untrusted text from a runtime format program, preserve borrowed data lifetimes, and avoid mistaking type safety or bounded writes for complete output security.
What you will learn
- Keep data separate from format authority
- Audit what survives an asynchronous boundary
- Treat the destination as a separate protocol
Before you start
- Basic C++ values, references, strings and build targets
- Ability to distinguish a documented expectation from a measured result
Trace concrete fmt behavior and choose an integration with explicit output, lifetime and verification boundaries.
Key takeaways
- User text and user-authored runtime templates grant different control.
- Borrowed argument data must outlive deferred formatting.
- Type safety does not replace resource limits, output encoding or redaction.
Keep data separate from format authority
When user-controlled text is only a value, place it behind a fixed field such as fmt::format("{}", text). Allowing a caller to supply a string wrapped in fmt::runtime gives that caller formatting control instead: fields, presentation and sizes become part of the input contract. That may be intentional in a template editor, but it requires explicit restrictions and failure handling.
A runtime formatting error is not the only resource concern. Valid but large widths, large strings and custom formatter work can still consume time or memory. Bound the values and template features accepted by your application before formatting. format_to_n limits destination writes; its untruncated counting behavior does not establish a complete execution budget.
Audit what survives an asynchronous boundary
The public argument-view API does not own the referenced objects. A queue that stores fmt::format_args for later use can outlive its backing argument store or strings. Dynamic argument storage copies some types but keeps string-view referents and explicit reference wrappers borrowed. A convenient container name is not a substitute for an ownership audit.
A source-grounded counterexample inserts a character array as a copied value, an explicit reference and a string view, then changes its first character. The copied output remains abc while the borrowed forms reflect Xbc. The safe lesson is not that borrowing is forbidden; it is that the owner and lifetime must be part of the queue’s contract.
Treat the destination as a separate protocol
fmt produces formatted text; it is not an HTML sanitizer, SQL parameter binder or secret-redaction policy. A debug presentation may improve readability, but it does not establish the escaping rules of an unrelated output context. Apply the destination’s correct encoding or structured API after deciding what data is permitted to leave the application.
For fixed buffers, retain an explicit written length and decide how truncation should be signaled. UTF-8 byte truncation and missing null termination need their own handling. The repository’s tests and fuzzing references are useful quality evidence, but this editorial review is not a comprehensive security audit or proof that your custom formatter and build configuration are safe.
Implementation steps
- 1
Keep ordinary untrusted text behind a fixed literal format.
- 2
Restrict runtime template size and features when templates are a real requirement.
- 3
Audit argument ownership across queues and callbacks.
- 4
Use the destination’s encoding rules and explicit truncation handling.
Copy-ready example
#include <fmt/args.h>
#include <cassert>
#include <functional>
int main() {
char text[] = "abc";
fmt::dynamic_format_arg_store<fmt::format_context> store;
store.push_back(text);
store.push_back(std::cref(text));
store.push_back(fmt::string_view(text));
text[0] = 'X';
assert(fmt::vformat("{}|{}|{}", store) == "abc|Xbc|Xbc");
}Frequently asked questions
Does fmt automatically make formatted HTML safe?
No. Formatting and output-context escaping are different responsibilities; use a suitable HTML encoding or templating boundary.
Does a dynamic argument store make an asynchronous log record fully owning?
Not automatically. Review string views, explicit references and custom-type storage requirements before deferring the operation.
Sources
- doc/api.mdSource checked 2026-09-08
- doc/syntax.mdSource checked 2026-09-08
- include/fmt/core.hSource checked 2026-09-08
- include/fmt/args.hSource checked 2026-09-08
- test/format-test.ccSource checked 2026-09-08
- test/args-test.ccSource checked 2026-09-08