This repository is an in-process command-line argument parser written in C23.
It parses attacker-controlled argv strings into caller-owned variables,
supports typed conversion, list accumulation, choice matching, and recursive
subcommands, and prints usage/help text from caller-supplied configuration
metadata.
clags is not a sandbox, not an authorization engine, and not a replacement
for application-level validation. Its job is to parse syntax and enforce basic
type constraints inside the caller's process. Callers remain responsible for
trust decisions, semantic validation, privilege separation, resource limits,
and logging policy.
argcandargvpassed toclags_parse()are untrusted.- Values consumed by the built-in verifiers (
bool, integer, floating-point, size, time, choice, path, file, dir, and subcommand inputs) remain untrusted until the embedding application validates their semantics. - Filesystem paths accepted through
Clags_Path,Clags_File, andClags_Dirare untrusted caller input.clagsonly performsstat()-based existence/type checks. clags_config_tdefinitions, custom verifiers, callback flags, log handlers, and allocator macro overrides (CLAGS_CALLOC,CLAGS_REALLOC,CLAGS_FREE) are part of the trusted computing base.- Names and descriptions used by
clags_usage()are trusted application metadata and are printed verbatim.
- Process memory safety during parsing, list growth, string duplication, and usage formatting.
- Integrity of caller-owned output variables and
clags_list_tstorage. - Deterministic rejection of malformed numeric values, unsupported units, unknown flags/options, invalid choice selections, invalid subcommand layouts, and failed path/file/dir checks.
- Overflow-safe size calculations for internal dynamic allocations.
- Allocation and buffer growth math uses checked addition/multiplication via
<stdckdint.h>when available, compiler overflow builtins otherwise, and a manual fallback when neither exists. - Recursive subcommand parsing is capped at
CLAGS_MAX_PARSE_DEPTH(default64), and parent/child config cycles are rejected before descent. - Unsigned parsers reject sign-prefixed inputs; integer, double, and time
parsers reject empty values, trailing junk,
ERANGE, NaN, and infinity. - Size and time parsers validate supported suffixes and reject values that
would overflow
uint64_t-backed storage. - List parsing validates
clags_list_t.item_sizeagainst the declaredvalue_typebefore writing. - Optional string duplication is tracked per-config and can be released through
clags_config_free_allocs()orclags_config_free(). - The library performs no file writes, network I/O, shell execution, or environment-variable parsing.
- Build definitions enable
-std=c23or-std=c2x,-Wall -Wextra -Wpedantic -Werror, stack protector,FORTIFY_SOURCE, and PIE. Debug test targets add ASan, UBSan, and LSan flags.
- Maximum recursive subcommand depth is
64unlessCLAGS_MAX_PARSE_DEPTHis overridden. - Initial dynamic growth capacity is
8unlessCLAGS_LIST_INIT_CAPACITYis overridden. - Usage/help alignment defaults to column
36unlessCLAGS_USAGE_ALIGNMENTis overridden. duplicate_stringsdefaults tofalse, so string-like outputs borrow pointers intoargvunless explicitly enabled.--always disables option/flag parsing for the rest of the parse unlessallow_option_parsing_toggleis enabled, in which case a later--can re-enable it.ignore_prefixandlist_terminatorare optional syntax extensions defined entirely by the caller.Clags_Path,Clags_File, andClags_Dirrely onstat()at parse time only.
These are implementation properties, not isolation guarantees. Hostile inputs can still consume CPU, stack depth, and heap memory up to the limits available to the embedding process.
clags_parse()mutatesclags_config_t(name,parent,invalid,allocs, anderror). Do not parse concurrently against the same config or config tree without external synchronization.- When
duplicate_stringsis disabled, parsedstring,path,file, anddiroutputs are borrowedargvpointers. They become invalid once the caller releases or mutates the underlying argument storage. Clags_Path,Clags_File, andClags_Dirdo not canonicalize paths and are subject to TOCTOU races between parse-timestat()and later open/use by the caller.- Custom verifiers, callback flags, log handlers, and allocator overrides
execute with the caller's privileges and can reintroduce memory-safety or
logic bugs outside
clags's control. - The default log handler prints raw argument values and configuration errors to
stdout/stderr. Do not rely on it when arguments may contain secrets or sensitive file paths. - The library validates syntax and basic type constraints only. Semantic policy remains the caller's responsibility: allowed ranges, path policy, authorization, cross-argument constraints, and business rules all belong in the application.
- Internal allocation failures and invariant violations use
clags_assert()and abort the process. This favors fail-fast behavior over silent corruption, but hostile inputs can still cause denial of service through resource exhaustion. clags_config_free()frees list buffers and duplicated strings for one config only; it does not recurse into child configs automatically.- An in-tree libFuzzer harness is available at
testing/fuzz_parse.cand can be built withjust fuzz-buildwhen a libFuzzer-capable compiler (for exampleclang) is available.
Current validation confirmed in this checkout:
just testzig build test
Additional validation paths present in the repository:
just tests-debugbuilds the test binary with ASan, UBSan, and LSan enabled.just fuzz-buildbuilds the in-tree libFuzzer harness with ASan and UBSan whenFUZZ_CCpoints to a compatible compiler.just test-debugexists, but it does not complete in this environment because LeakSanitizer cannot run under the current ptrace restrictions.
- Treat every parsed value as untrusted until your application validates semantics and authorization.
- Enable
duplicate_stringswhen parsed values must outliveargvstorage or when you want explicit ownership boundaries. - Re-validate paths at open/use time and apply least-privilege filesystem
policy around any
Clags_Path,Clags_File, orClags_Dirinputs. - Use a custom log handler or raise
min_log_levelwhen argument values may contain secrets. - Free duplicated strings and list storage with
clags_config_free()or the narrower free helpers on every parse path that enables allocations. - Add fuzzing and sanitizer-backed CI if the parser will handle hostile or high-volume command-line input. The repository ships a starter harness, but continuous fuzzing coverage is still a caller/integrator responsibility.