HIGH:
- H1: nlohmann::json::exception wrapped as ProtocolError at 5 sites in
client.cpp via with_protocol_guard helper. Preserves the documented
clawdforge::Error catch-all base contract; nlohmann types never leak
into the message (e.what() only).
- H2: libcurl MAXREDIRS=5, REDIR_PROTOCOLS_STR="http,https"
(CURLOPT_REDIR_PROTOCOLS bitmask fallback for libcurl < 7.85.0),
UNRESTRICTED_AUTH=0L. Defense-in-depth on top of libcurl's automatic
bearer strip on cross-host redirects (>=7.64.0).
MEDIUM:
- M1: upload_file resolves the path via std::filesystem::canonical up
front. Closes broken-symlink, symlink-loop, and TOCTOU-on-target
classes without a doc burden on callers.
- M2: README "Linking" section documents the public-ABI nlohmann_json
implication. v0.2 wrapper deferred.
- M3: README "Threat model" section documents the parse-depth concern
on the result field of /run replies. Runtime guard skipped for v0.1
per audit recommendation (low yield, complexity).
LOW:
- L1: cxx_std_20 → cxx_std_17 in CMakeLists.txt (no C++20-only
features in the library source; broader downstream reach). Examples
and tests still build via designated initializers (g++ accepts these
in C++17 mode).
- L2: RunResult struct doc clarifies that missing ok/duration_ms
decode to defaults — opt-out forward-compat.
- L3: Client class doc clarifies that moved-from instances must not
have any non-special-member methods invoked (UB), with explicit
callout on base_url() returning an internal reference.
Test-only:
- cpp-httplib 0.15.3 → 0.20.1. Optional backends (OpenSSL / zlib /
brotli / zstd) forced off to keep the dep graph minimal. Test-only,
never on the consumer wire path. README "Test deps" section added
for transparency.
Tests added (12 → 23 cases, 70 → 106 assertions):
- protocol_error on malformed response for healthz, run, upload_file,
create_token, list_tokens (H1 regression)
- redirect_clamp_test (H2 regression — TransportError after 5+ hops)
- redirect_protocol_clamp (H2 regression — ftp:// Location rejected)
- upload_file_canonicalize: symlink→file works, broken symlink
rejected, symlink loop rejected, directory rejected (M1 regression)
Verified:
- cmake --build build clean (-Wall -Wextra -Wpedantic -Wshadow
-Wconversion -Wsign-conversion -Wold-style-cast -Werror)
- ctest --output-on-failure all green (Release)
- ASan + UBSan: 23/23 cases, 106/106 assertions, zero diagnostics
Audit: memory/clawdforge-audits/cpp-bae34a7.md
Modern C++20 SDK targeting CMake 3.20+. Library is RAII / move-only,
backed by a libcurl easy handle per Client. Public surface is throwing;
exception hierarchy under clawdforge::Error covers AuthError, APIError
(carries status_code + body), TransportError, and ProtocolError.
Dependencies: libcurl + nlohmann/json (FetchContent or find_package).
Tests use cpp-httplib's in-process server + doctest. 12 test cases /
70 assertions cover healthz, run with JSON / text / 502 / files,
multipart upload, full token CRUD, transport failure, URL normalization,
and bad-input rejection. Clean under -Wall -Wextra -Wpedantic -Werror,
ASan + UBSan clean (no leaks, no UB).
upload_file streams via curl_mime_filedata — no in-memory buffering.
Install path produces clawdforge::clawdforge target consumable via
target_link_libraries; FetchContent path mirrors the existing Rust /
Go SDK ergonomics. MIT licensed.