Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
34 commits
Select commit Hold shift + click to select a range
805dddb
docs: start milestone v0.2 Embedded CA & CLI Polish
roobie Mar 8, 2026
1eec413
docs: complete v0.2 project research
roobie Mar 8, 2026
d7b8799
docs: define milestone v0.2 requirements
roobie Mar 8, 2026
e867ff9
docs: create milestone v0.2 roadmap (2 phases)
roobie Mar 8, 2026
93c02d3
docs(04): capture phase context
roobie Mar 8, 2026
6728e5b
docs(state): record phase 4 context session
roobie Mar 8, 2026
80a45f1
docs(04): research phase domain
roobie Mar 8, 2026
8c1652e
docs(04): add validation strategy
roobie Mar 8, 2026
4b87dcd
docs(04): create phase plan
roobie Mar 8, 2026
aded32d
feat(04-01): vendor CA bundle, update C shim cert count, add CaBundle…
roobie Mar 8, 2026
f457746
feat(04-01): wire @embedFile for CA bundle in build.zig
roobie Mar 8, 2026
bdc8d9a
docs(04-01): complete CA bundle plumbing plan
roobie Mar 8, 2026
98ace78
feat(04-02): wire embedded CAs into TLS connect with validation and l…
roobie Mar 8, 2026
b4c0680
docs(04-02): complete embedded CA wiring plan
roobie Mar 8, 2026
1139d18
test(04): add brat test harness and embedded CA integration tests
roobie Mar 8, 2026
f8481eb
docs(phase-04): complete phase execution
roobie Mar 8, 2026
af70b6a
docs(05): capture phase context
roobie Mar 8, 2026
de133a2
docs(state): record phase 5 context session
roobie Mar 8, 2026
1d04510
docs(05): research phase domain
roobie Mar 8, 2026
8eb618f
docs(phase-05): add validation strategy
roobie Mar 8, 2026
52a2d28
docs(05-cli-polish): create phase plan
roobie Mar 8, 2026
4cc1640
fix(05): revise plans based on checker feedback
roobie Mar 8, 2026
d72dfef
feat(05-01): refactor response headers from HashMap to ordered array
roobie Mar 8, 2026
a0430fe
feat(05-01): add CLI flag parsing for -I, -i, --fail-with-body with m…
roobie Mar 8, 2026
7df7af2
docs(05-01): complete header refactor and CLI flag parsing plan
roobie Mar 8, 2026
786ce65
feat(05-02): wire -I, -i, --fail, --fail-with-body into fetch pipeline
roobie Mar 8, 2026
8748991
feat(05-02): update help text with -I, -i, --fail-with-body flags
roobie Mar 8, 2026
5a8e1d3
docs(05-02): complete wire CLI flags into runtime plan
roobie Mar 8, 2026
8fa8ca3
test(05): add brat integration tests for CLI flags and mise test task
roobie Mar 8, 2026
c87b30b
docs(phase-05): complete phase execution
roobie Mar 8, 2026
ab143e0
docs(v0.2): milestone audit — all requirements satisfied, minor tech …
roobie Mar 8, 2026
3fee44c
chore: complete v0.2 milestone
roobie Mar 8, 2026
b721daf
chore(v0.2): clean up archived files
roobie Mar 8, 2026
eee9953
chore(v0.2): archive phase directories to milestones/
roobie Mar 8, 2026
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
3 changes: 3 additions & 0 deletions .gitmodules
Original file line number Diff line number Diff line change
Expand Up @@ -4,3 +4,6 @@
[submodule "vendor/boringssl"]
path = vendor/boringssl
url = https://github.com/google/boringssl.git
[submodule "test/brat"]
path = test/brat
url = https://codeberg.org/sstephenson/brat.git
52 changes: 52 additions & 0 deletions .planning/MILESTONES.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,52 @@
# Project Milestones: zfetch

## v0.2 Embedded CA & CLI Polish (Shipped: 2026-03-08)

**Delivered:** Self-contained HTTPS binary with embedded Mozilla CAs and complete curl-compatible CLI flags for headers, HEAD requests, and failure control.

**Phases completed:** 4-5 (4 plans, 8 tasks)

**Key accomplishments:**
- Vendored 144-cert Mozilla CA bundle — zfetch works over HTTPS with zero filesystem dependencies
- Embedded CAs via @embedFile with cert count validation (CaBundleCorrupt on corrupt bundles)
- Ordered response header storage preserving HTTP wire format
- -I (HEAD) and -i (include headers) flags with wire-format output
- --fail body suppression via NullSink and --fail-with-body for error-with-output
- 15 brat integration tests covering CA and CLI flag behavior

**Stats:**
- 11 files created/modified
- 3,682 lines of Zig
- 2 phases, 4 plans, 8 tasks
- 1 day (2026-03-08)

**Git range:** `feat(04-01)` → `test(05)`

**Known tech debt:**
- Dead code: TlsOptions.ca_bundle_path field in src/tls/tls.zig (unused after @embedFile design)

**What's next:** TBD — next milestone planning

---

## v0.1 MVP (Shipped: 2026-03-07)

**Delivered:** Minimal, security-hardened HTTP client with 14 curl-compatible CLI flags and clean library API.

**Phases completed:** 1-3 (6 plans)

**Key accomplishments:**
- CRLF injection prevention, cross-host credential stripping, memory leak fixes
- 14 curl-compatible CLI flags with full argument parsing
- Clean public library API via @import("zfetch") with BoringSSL wiring
- Build profiles (ReleaseSmall/ReleaseSafe) documented with size measurements

**Stats:**
- 6 plans, ~22 min total execution
- 3 phases from security hardening through release packaging

**Git range:** `feat(01-01)` → `feat(03-02)`

**What's next:** v0.2 Embedded CA & CLI Polish

---
62 changes: 33 additions & 29 deletions .planning/PROJECT.md
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@

## What This Is

A minimal, security-focused HTTP client for restricted environments. Both a CLI tool and a Zig library for making GET and POST requests over HTTP/1.1 with TLS. Think `curl` but with 1% of the knobs — designed for containers, CI pipelines, hardened servers, and embedded/IoT devices where a reduced attack surface matters.
A minimal, security-focused HTTP client for restricted environments. Both a CLI tool and a Zig library for making GET and POST requests over HTTP/1.1 with TLS. Think `curl` but with 1% of the knobs — designed for containers, CI pipelines, hardened servers, and embedded/IoT devices where a reduced attack surface matters. Ships as a fully self-contained static binary with embedded CA certificates — no filesystem dependencies for HTTPS.

## Core Value

Expand All @@ -12,29 +12,25 @@ A single static binary that can fetch URLs over HTTPS with strict security defau

### Validated

- HTTP/1.1 GET requests over TCP — existing
- HTTPS via vendored BoringSSL with TLS verification — existing
- URL parsing with strict validation — existing
- DNS resolution and non-blocking TCP connect with per-address timeout — existing
- Three-tier timeout model (connect, idle, overall) — existing
- Streaming response body via sink pattern (zero-copy to stdout) — existing
- Chunked, fixed-length, and EOF-delimited body framing — existing
- Redirect following with configurable limit — existing
- Unified error set with curl-style exit codes — existing
- Verbose/debug logging to stderr — existing
- Strict protocol limits (header size, body size, URL length) — existing
- Library API: `Client.fetch(url, sink)` with `Options` struct — existing
- CRLF header injection prevention — v0.1
- Cross-host redirect credential stripping — v0.1
- ResponseHead memory leak fixes — v0.1
- Idle timeout enforcement — v0.1
- CLI argument parsing (14 curl-compatible flags) — v0.1
- Clean public library API via `@import("zfetch")` — v0.1
- Zig package with `build.zig.zon` v0.1.0 — v0.1
- Build profiles (ReleaseSmall/ReleaseSafe) documented — v0.1
- Embedded Mozilla CA root certificates at compile time — v0.2
- Both embedded and system CA certificates loaded — v0.2
- Embedded CA cert count validation on load — v0.2
- `-I` flag (HEAD request, print response headers only) — v0.2
- `-i` flag (include response headers before body) — v0.2
- `--fail-with-body` flag (exit non-zero, output body) — v0.2
- `--fail` suppresses body output on HTTP errors — v0.2

### Active

- [ ] CLI argument parsing: `-H`, `-d`, `-L`, `-s`, `--verbose`, method selection
- [ ] POST with string body (`-d 'data'`) and stdin piping
- [ ] Silent mode (suppress non-body output)
- [ ] Custom request headers via CLI
- [ ] Clean, documented public library API suitable for `@import("zfetch")`
- [ ] Zig package consumable as a dependency via `build.zig.zon`
- [ ] Multiple build profiles (ReleaseSmall for containers, ReleaseSafe for servers)
- [ ] macOS best-effort support (POSIX overlap with Linux)
(No active milestone — planning next)

### Out of Scope

Expand All @@ -51,11 +47,14 @@ A single static binary that can fetch URLs over HTTPS with strict security defau

- Zig 0.15.2 project, zero external Zig dependencies
- BoringSSL vendored as git submodule, built via CMake/Ninja
- 144-cert Mozilla CA bundle embedded at compile time via @embedFile
- Layered architecture: URL -> Net/Dial -> TLS -> HTTP -> CLI
- Single-threaded, synchronous design
- All state per-request (no global state, no connection reuse)
- Fail-closed security: strict validation, TLS verify on by default
- Existing test infrastructure: unit tests in source files, TLS integration tests with ephemeral server
- Fail-closed security: strict validation, TLS verify on by default, embedded CAs always loaded
- Test infrastructure: 67 unit tests in source files, 15 brat shell integration tests
- 3,682 lines of Zig, ~219KB embedded CA bundle
- Shipped v0.1 (2026-03-07) and v0.2 (2026-03-08)

## Constraints

Expand All @@ -69,11 +68,16 @@ A single static binary that can fetch URLs over HTTPS with strict security defau

| Decision | Rationale | Outcome |
|----------|-----------|---------|
| Vendored BoringSSL over system TLS | Reproducible builds, no system dependency, auditable | -- Pending |
| Sink-based streaming over buffering | Zero-copy output, constant memory usage | -- Pending |
| Single error set over per-layer errors | Simple API surface, curl-style exit codes | -- Pending |
| HTTP/1.1 only | Covers target use cases, avoids protocol complexity | -- Pending |
| No connection reuse | Stateless design matches single-fetch CLI use case | -- Pending |
| Vendored BoringSSL over system TLS | Reproducible builds, no system dependency, auditable | Good |
| Sink-based streaming over buffering | Zero-copy output, constant memory usage | Good |
| Single error set over per-layer errors | Simple API surface, curl-style exit codes | Good |
| HTTP/1.1 only | Covers target use cases, avoids protocol complexity | Good |
| No connection reuse | Stateless design matches single-fetch CLI use case | Good |
| Fixed-size header array (64 max) | Avoids heap allocation in parser, preserves insertion order | Good |
| @embedFile for CA bundle | Zero filesystem dependency, fail-closed, compile-time guarantee | Good |
| Embedded CAs always loaded (even with -k) | Fail-closed design — -k only skips verification, not loading | Good |
| NullSink comptime polymorphism for --fail | Clean body suppression without buffering, leverages Zig's comptime | Good |
| Exit code 77 for CaBundleCorrupt | Matches curl CURLE_SSL_CACERT_BADFILE convention | Good |

---
*Last updated: 2026-03-06 after initialization*
*Last updated: 2026-03-08 after v0.2 milestone completion*
89 changes: 89 additions & 0 deletions .planning/RETROSPECTIVE.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,89 @@
# Project Retrospective

*A living document updated after each milestone. Lessons feed forward into future planning.*

## Milestone: v0.2 — Embedded CA & CLI Polish

**Shipped:** 2026-03-08
**Phases:** 2 | **Plans:** 4

### What Was Built
- Embedded 144-cert Mozilla CA bundle for zero-filesystem HTTPS
- Wire-format response header output (-I, -i flags)
- --fail body suppression via comptime NullSink pattern
- --fail-with-body for error-with-output workflow
- 15 brat shell integration tests

### What Worked
- Coarse granularity (2 phases, 4 plans) kept overhead low for a focused milestone
- Phase 4 before Phase 5 was correct — all subsequent testing used embedded certs
- Brat test framework (added in Phase 04) provided clean integration testing for Phase 05
- Wave-based execution with dependency ordering (05-01 before 05-02) worked smoothly

### What Was Inefficient
- ROADMAP.md plan checkboxes not updated during execution (still showed `[ ]` for completed plans)
- Verification flagged 5 "human_needed" items that brat tests could have caught — writing brat tests earlier would have avoided manual verification
- TlsOptions.ca_bundle_path dead code left behind — small debt from design pivot

### Patterns Established
- Brat shell tests for CLI integration testing (`test/*.brat`)
- NullSink comptime pattern for body suppression
- writeHeadersToFile helper for Zig 0.15 File API compatibility
- Dedicated module test steps in build.zig for named module test discovery

### Key Lessons
1. Write integration tests alongside implementation, not after verification flags them
2. Zig 0.15 named module tests are NOT discovered transitively — always add dedicated test steps
3. Zig 0.15 File.writer() requires a buffer — use bufPrint+writeAll instead

### Cost Observations
- Model mix: orchestrator (opus), executors (sonnet), verifier (sonnet)
- Sessions: 1 session for full milestone (execute + audit + complete)
- Notable: 4 plans executed in ~19 min total, including verification

---

## Milestone: v0.1 — MVP

**Shipped:** 2026-03-07
**Phases:** 3 | **Plans:** 6

### What Was Built
- Security hardening (CRLF injection, credential stripping, memory leaks, idle timeout)
- 14 curl-compatible CLI flags with argument parsing
- Clean library API with build profiles

### What Worked
- TDD approach for CLI argument parsing produced comprehensive test coverage
- Security-first phase ordering (hardening before features)
- Small, focused plans (1-2 tasks each) kept execution fast

### What Was Inefficient
- No integration tests until v0.2 — unit tests alone couldn't cover E2E CLI behavior

### Key Lessons
1. Security hardening first prevents widening attack surface with new features
2. Fixed-size arrays avoid heap allocation complexity in parsers

---

## Cross-Milestone Trends

### Process Evolution

| Milestone | Phases | Plans | Key Change |
|-----------|--------|-------|------------|
| v0.1 | 3 | 6 | Established TDD, security-first ordering |
| v0.2 | 2 | 4 | Added brat integration tests, wave-based execution |

### Cumulative Quality

| Milestone | Unit Tests | Integration Tests | Test Steps |
|-----------|-----------|-------------------|------------|
| v0.1 | 27 | 0 | 1 |
| v0.2 | 67 | 15 | 9 |

### Top Lessons (Verified Across Milestones)

1. Write tests at the same level as the feature — unit tests for library, integration tests for CLI
2. Zig 0.15 has API quirks (File.writer, named module test isolation) — always verify API availability
76 changes: 21 additions & 55 deletions .planning/ROADMAP.md
Original file line number Diff line number Diff line change
@@ -1,8 +1,9 @@
# Roadmap: zfetch

## Overview
## Milestones

zfetch has a working HTTP/HTTPS library layer with vendored BoringSSL, streaming responses, redirects, and timeouts. What remains for v0.1 is fixing known security bugs in the library, completing the CLI flag surface, and packaging the library for external consumers. Three phases: harden the foundation, build the CLI, then polish for release.
- v0.1 MVP - Phases 1-3 (shipped 2026-03-07)
- v0.2 Embedded CA & CLI Polish - Phases 4-5 (shipped 2026-03-08)

## Phases

Expand All @@ -12,64 +13,29 @@ zfetch has a working HTTP/HTTPS library layer with vendored BoringSSL, streaming

Decimal phases appear between their surrounding integers in numeric order.

- [x] **Phase 1: Security Hardening** - Fix credential leakage, injection, memory leaks, and idle timeout before exposing new attack surface (completed 2026-03-06)
- [ ] **Phase 2: CLI Completion** - Implement all argument parsing and flags to make zfetch a usable curl replacement
- [x] **Phase 3: Library and Release** - Clean public API, build profiles, and packaging for library consumers and distribution (completed 2026-03-07)
<details>
<summary>v0.1 MVP (Phases 1-3) - SHIPPED 2026-03-07</summary>

## Phase Details
- [x] **Phase 1: Security Hardening** - Fix credential leakage, injection, memory leaks, and idle timeout (completed 2026-03-06)
- [x] **Phase 2: CLI Completion** - Implement all argument parsing and flags for curl-compatible CLI (completed 2026-03-07)
- [x] **Phase 3: Library and Release** - Clean public API, build profiles, and packaging (completed 2026-03-07)

### Phase 1: Security Hardening
**Goal**: The library layer is secure against known vulnerability classes before new features widen the attack surface
**Depends on**: Nothing (first phase)
**Requirements**: SEC-01, SEC-02, SEC-03, SEC-04
**Success Criteria** (what must be TRUE):
1. A redirect from host-a.com to host-b.com does not forward Authorization, Cookie, or Proxy-Authorization headers
2. Supplying a header value containing CR or LF characters via the library API results in an error, not a sent request
3. Following a chain of redirects does not leak memory (ResponseHead and duplicated headers are freed)
4. A connection that receives no data for longer than the idle timeout is terminated (not left hanging)
**Plans:** 2/2 plans complete
</details>

Plans:
- [ ] 01-01-PLAN.md — CRLF injection prevention (SEC-02) and response header memory leak fixes (SEC-03)
- [ ] 01-02-PLAN.md — Cross-host redirect header stripping (SEC-01) and idle timeout enforcement (SEC-04)
<details>
<summary>v0.2 Embedded CA & CLI Polish (Phases 4-5) - SHIPPED 2026-03-08</summary>

### Phase 2: CLI Completion
**Goal**: Users can invoke zfetch from the command line with curl-compatible flags for all common HTTP operations
**Depends on**: Phase 1
**Requirements**: CLI-01, CLI-02, CLI-03, CLI-04, CLI-05, CLI-06, CLI-07, CLI-08, CLI-09, CLI-10, CLI-11, CLI-12, CLI-13, CLI-14
**Success Criteria** (what must be TRUE):
1. User can POST data with `zfetch -d 'payload' URL` and the request uses POST method with the provided body
2. User can add custom headers, select HTTP method, write output to file, and control verbosity/silence via documented flags (-H, -X, -o, -s, -v)
3. User can run `zfetch --help` and see a summary of all flags with usage examples, and `zfetch --version` prints a version string
4. Running `zfetch --unknown-flag URL` exits with a non-zero code and an error message identifying the bad flag
5. User can disable TLS verification with `-k`, enforce failure on HTTP errors with `--fail`, and set timeouts with `--connect-timeout` and `--max-time`
**Plans:** 1/2 plans executed
- [x] **Phase 4: Embedded CA Bundle** - Compile Mozilla CA roots into the binary for zero-filesystem HTTPS (completed 2026-03-08)
- [x] **Phase 5: CLI Polish** - HEAD requests, response header output, and fail flag improvements (completed 2026-03-08)

Plans:
- [ ] 02-01-PLAN.md — CliArgs struct with TDD: argument parser and Method enum extension (CLI-01 through CLI-14 parsing)
- [ ] 02-02-PLAN.md — Wire CliArgs into main.zig: runtime behaviors, output, help/version, smoke tests

### Phase 3: Library and Release
**Goal**: zfetch is consumable as a Zig library dependency and ships with documented build profiles
**Depends on**: Phase 2
**Requirements**: LIB-01, LIB-02, LIB-03, BLD-01, BLD-02
**Success Criteria** (what must be TRUE):
1. An external Zig project can add zfetch via `build.zig.zon`, `@import("zfetch")`, and call `Client.fetch()` without accessing internal modules
2. The library module links BoringSSL correctly when consumed as a dependency (not just when building the CLI binary)
3. `zig build -Doptimize=ReleaseSmall` and `zig build -Doptimize=ReleaseSafe` both produce working binaries, with measured and documented sizes
**Plans:** 2/2 plans complete

Plans:
- [ ] 03-01-PLAN.md — Clean public API, wire BoringSSL to library module, update build.zig.zon, add LICENSE
- [ ] 03-02-PLAN.md — Build profiles measurement and README documentation
</details>

## Progress

**Execution Order:**
Phases execute in numeric order: 1 -> 2 -> 3

| Phase | Plans Complete | Status | Completed |
|-------|----------------|--------|-----------|
| 1. Security Hardening | 2/2 | Complete | 2026-03-06 |
| 2. CLI Completion | 1/2 | In Progress| |
| 3. Library and Release | 2/2 | Complete | 2026-03-07 |
| Phase | Milestone | Plans Complete | Status | Completed |
|-------|-----------|----------------|--------|-----------|
| 1. Security Hardening | v0.1 | 2/2 | Complete | 2026-03-06 |
| 2. CLI Completion | v0.1 | 2/2 | Complete | 2026-03-07 |
| 3. Library and Release | v0.1 | 2/2 | Complete | 2026-03-07 |
| 4. Embedded CA Bundle | v0.2 | 2/2 | Complete | 2026-03-08 |
| 5. CLI Polish | v0.2 | 2/2 | Complete | 2026-03-08 |
Loading