Security hardening and release readiness
ober
6cd962d1f0f4327ec9c3227a6e96d6af85f94b15
new file mode 100644 --- /dev/null +++ b/.github/workflows/ci.yml @@ -0,0 +1,42 @@ +name: CI + +on: + push: + branches: [main, master] + pull_request: + workflow_dispatch: + +permissions: + contents: read + +env: + JERBOA_VERSION: v0.2.3 + JERBUILD: ${{ github.workspace }}/.jerboa/bin/jerbuild + JERBOA_SSL_REPO: https://git.sr.ht/~lisp/jerboa-ssl + SSL_DIR: ${{ github.workspace }}/../jerboa-ssl + +jobs: + verify: + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@v4 + + - name: Install system tools + run: | + set -eu + sudo apt-get update + sudo apt-get install -y --no-install-recommends build-essential git curl ca-certificates libssl-dev + + - name: Bootstrap Jerboa + run: | + set -eu + sh support/ensure-jerboa.sh "$JERBOA_VERSION" .jerboa/bin + "$JERBUILD" --jerboa-home + + - name: Fetch TLS dependency + run: | + set -eu + git clone --depth 1 "$JERBOA_SSL_REPO" "$SSL_DIR" + + - name: Verify + run: make verify new file mode 100644 --- /dev/null +++ b/.github/workflows/security-baseline.yml @@ -0,0 +1,35 @@ +name: Security Baseline + +on: + push: + branches: [main, master] + pull_request: + workflow_dispatch: + +permissions: + contents: read + +jobs: + baseline: + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@v4 + + - name: Required release files + run: | + set -eu + test -f LICENSE + test -f SECURITY.md + test -f .gitignore + find . -maxdepth 1 -iname "README*" -type f | grep -q . + + - name: High-confidence secret scan + run: | + set -eu + pattern="(BEGIN (RSA|OPENSSH|EC|DSA|PRIVATE) KEY|ghp_[A-Za-z0-9_]{20,}|github_pat_[A-Za-z0-9_]{20,}|sk-(ant-api03|proj|svcacct)-[A-Za-z0-9_-]{30,}|AKIA[0-9A-Z]{16})" + matches="$(git grep -n -I -E "$pattern" -- . ":!*.png" ":!*.jpg" ":!*.jpeg" ":!*.gif" ":!*.so" ":!*.dylib" ":!*.o" ":!*.a" ":!*.boot" ":!*.tar.gz" || true)" + if [ -n "$matches" ]; then + echo "$matches" + echo "High-confidence secret pattern found." + exit 1 + fi --- a/.gitignore +++ b/.gitignore @@ -2,3 +2,16 @@ *.o .jerbuild-hashes tests/static/leak +wasm/target/ +fuzz/target/ +fuzz/artifacts/ +fuzz/corpus/*/* +!fuzz/corpus/http_request_line/ +!fuzz/corpus/http_request_line/* +!fuzz/corpus/http_headers/ +!fuzz/corpus/http_headers/* +!fuzz/corpus/http_chunked/ +!fuzz/corpus/http_chunked/* + +# Release evidence generated by make release-evidence +/dist/ --- a/.jerboa/security.json +++ b/.jerboa/security.json @@ -11,11 +11,40 @@ }, "policy": { "failOn": ["critical", "high"], - "network": { "requireTimeouts": true, "requireMaxHeaderBytes": true, "requireMaxBodyBytes": true }, + "network": { "requireTimeouts": true, "requireMaxHeaderBytes": true, "requireMaxBodyBytes": true, "requireLoadSmoke": true }, "daemon": { "requirePrivilegeDropBeforeProduction": true, "requireSandboxPlan": true }, "parser": { "requireMalformedInputCorpus": true, "requireSmugglingTests": true }, "filesystem": { "staticServingRequiresTraversalTests": true }, - "tls": { "requireModernProtocolDefaults": true } + "tls": { "requireModernProtocolDefaults": true }, + "supplyChain": { "sbom": "required", "reproducibilityReport": "required", "dependencyEvidence": ["jerboa-ssl"] }, + "releaseEvidence": { + "httpdLoadProof": { + "proofFileEnv": "JHTTPS_TARGET_LOAD_PROOF_FILE", + "requireEnv": "JHTTPS_REQUIRE_TARGET_LOAD_PROOF", + "requiredMarkers": [ + "production_load_status=current-run-recorded", + "load_status=release-host-sustained-recorded", + "queue_exhaustion_status=release-host-sustained-recorded" + ] + }, + "httpdFuzzProof": { + "proofFileEnv": "JHTTPS_TARGET_FUZZ_PROOF_FILE", + "requireEnv": "JHTTPS_REQUIRE_TARGET_FUZZ_PROOF", + "requiredMarkers": [ + "coverage_fuzz_status=release-host-sustained-recorded", + "targets_completed=3" + ] + }, + "tlsProxyProof": { + "proofFileEnv": "JHTTPS_TARGET_TLS_PROXY_PROOF_FILE", + "requireEnv": "JHTTPS_REQUIRE_TARGET_TLS_PROXY_PROOF", + "requiredMarkers": [ + "tls_proxy_smoke_status=target-evidence-recorded", + "forwarded_header_policy_status=deny-by-default", + "plaintext_listener_policy_status=private-or-behind-tls-proxy" + ] + } + } }, "suppressions": [] } new file mode 100644 --- /dev/null +++ b/LICENSE @@ -0,0 +1,201 @@ + Apache License + Version 2.0, January 2004 + http://www.apache.org/licenses/ + + TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION + + 1. Definitions. + + "License" shall mean the terms and conditions for use, reproduction, + and distribution as defined by Sections 1 through 9 of this document. + + "Licensor" shall mean the copyright owner or entity authorized by + the copyright owner that is granting the License. + + "Legal Entity" shall mean the union of the acting entity and all + other entities that control, are controlled by, or are under common + control with that entity. For the purposes of this definition, + "control" means (i) the power, direct or indirect, to cause the + direction or management of such entity, whether by contract or + otherwise, or (ii) ownership of fifty percent (50%) or more of the + outstanding shares, or (iii) beneficial ownership of such entity. + + "You" (or "Your") shall mean an individual or Legal Entity + exercising permissions granted by this License. + + "Source" form shall mean the preferred form for making modifications, + including but not limited to software source code, documentation + source, and configuration files. + + "Object" form shall mean any form resulting from mechanical + transformation or translation of a Source form, including but + not limited to compiled object code, generated documentation, + and conversions to other media types. + + "Work" shall mean the work of authorship, whether in Source or + Object form, made available under the License, as indicated by a + copyright notice that is included in or attached to the work + (an example is provided in the Appendix below). + + "Derivative Works" shall mean any work, whether in Source or Object + form, that is based on (or derived from) the Work and for which the + editorial revisions, annotations, elaborations, or other modifications + represent, as a whole, an original work of authorship. For the purposes + of this License, Derivative Works shall not include works that remain + separable from, or merely link (or bind by name) to the interfaces of, + the Work and Derivative Works thereof. + + "Contribution" shall mean any work of authorship, including + the original version of the Work and any modifications or additions + to that Work or Derivative Works thereof, that is intentionally + submitted to Licensor for inclusion in the Work by the copyright owner + or by an individual or Legal Entity authorized to submit on behalf of + the copyright owner. For the purposes of this definition, "submitted" + means any form of electronic, verbal, or written communication sent + to the Licensor or its representatives, including but not limited to + communication on electronic mailing lists, source code control systems, + and issue tracking systems that are managed by, or on behalf of, the + Licensor for the purpose of discussing and improving the Work, but + excluding communication that is conspicuously marked or otherwise + designated in writing by the copyright owner as "Not a Contribution." + + "Contributor" shall mean Licensor and any individual or Legal Entity + on behalf of whom a Contribution has been received by Licensor and + subsequently incorporated within the Work. + + 2. Grant of Copyright License. Subject to the terms and conditions of + this License, each Contributor hereby grants to You a perpetual, + worldwide, non-exclusive, no-charge, royalty-free, irrevocable + copyright license to reproduce, prepare Derivative Works of, + publicly display, publicly perform, sublicense, and distribute the + Work and such Derivative Works in Source or Object form. + + 3. Grant of Patent License. Subject to the terms and conditions of + this License, each Contributor hereby grants to You a perpetual, + worldwide, non-exclusive, no-charge, royalty-free, irrevocable + (except as stated in this section) patent license to make, have made, + use, offer to sell, sell, import, and otherwise transfer the Work, + where such license applies only to those patent claims licensable + by such Contributor that are necessarily infringed by their + Contribution(s) alone or by combination of their Contribution(s) + with the Work to which such Contribution(s) was submitted. If You + institute patent litigation against any entity (including a + cross-claim or counterclaim in a lawsuit) alleging that the Work + or a Contribution incorporated within the Work constitutes direct + or contributory patent infringement, then any patent licenses + granted to You under this License for that Work shall terminate + as of the date such litigation is filed. + + 4. Redistribution. You may reproduce and distribute copies of the + Work or Derivative Works thereof in any medium, with or without + modifications, and in Source or Object form, provided that You + meet the following conditions: + + (a) You must give any other recipients of the Work or + Derivative Works a copy of this License; and + + (b) You must cause any modified files to carry prominent notices + stating that You changed the files; and + + (c) You must retain, in the Source form of any Derivative Works + that You distribute, all copyright, patent, trademark, and + attribution notices from the Source form of the Work, + excluding those notices that do not pertain to any part of + the Derivative Works; and + + (d) If the Work includes a "NOTICE" text file as part of its + distribution, then any Derivative Works that You distribute must + include a readable copy of the attribution notices contained + within such NOTICE file, excluding those notices that do not + pertain to any part of the Derivative Works, in at least one + of the following places: within a NOTICE text file distributed + as part of the Derivative Works; within the Source form or + documentation, if provided along with the Derivative Works; or, + within a display generated by the Derivative Works, if and + wherever such third-party notices normally appear. The contents + of the NOTICE file are for informational purposes only and + do not modify the License. You may add Your own attribution + notices within Derivative Works that You distribute, alongside + or as an addendum to the NOTICE text from the Work, provided + that such additional attribution notices cannot be construed + as modifying the License. + + You may add Your own copyright statement to Your modifications and + may provide additional or different license terms and conditions + for use, reproduction, or distribution of Your modifications, or + for any such Derivative Works as a whole, provided Your use, + reproduction, and distribution of the Work otherwise complies with + the conditions stated in this License. + + 5. Submission of Contributions. Unless You explicitly state otherwise, + any Contribution intentionally submitted for inclusion in the Work + by You to the Licensor shall be under the terms and conditions of + this License, without any additional terms or conditions. + Notwithstanding the above, nothing herein shall supersede or modify + the terms of any separate license agreement you may have executed + with Licensor regarding such Contributions. + + 6. Trademarks. This License does not grant permission to use the trade + names, trademarks, service marks, or product names of the Licensor, + except as required for reasonable and customary use in describing the + origin of the Work and reproducing the content of the NOTICE file. + + 7. Disclaimer of Warranty. Unless required by applicable law or + agreed to in writing, Licensor provides the Work (and each + Contributor provides its Contributions) on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or + implied, including, without limitation, any warranties or conditions + of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A + PARTICULAR PURPOSE. You are solely responsible for determining the + appropriateness of using or redistributing the Work and assume any + risks associated with Your exercise of permissions under this License. + + 8. Limitation of Liability. In no event and under no legal theory, + whether in tort (including negligence), contract, or otherwise, + unless required by applicable law (such as deliberate and grossly + negligent acts) or agreed to in writing, shall any Contributor be + liable to You for damages, including any direct, indirect, special, + incidental, or consequential damages of any character arising as a + result of this License or out of the use or inability to use the + Work (including but not limited to damages for loss of goodwill, + work stoppage, computer failure or malfunction, or any and all + other commercial damages or losses), even if such Contributor + has been advised of the possibility of such damages. + + 9. Accepting Warranty or Additional Liability. While redistributing + the Work or Derivative Works thereof, You may choose to offer, + and charge a fee for, acceptance of support, warranty, indemnity, + or other liability obligations and/or rights consistent with this + License. However, in accepting such obligations, You may act only + on Your own behalf and on Your sole responsibility, not on behalf + of any other Contributor, and only if You agree to indemnify, + defend, and hold each Contributor harmless for any liability + incurred by, or claims asserted against, such Contributor by reason + of your accepting any such warranty or additional liability. + + END OF TERMS AND CONDITIONS + + APPENDIX: How to apply the Apache License to your work. + + To apply the Apache License to your work, attach the following + boilerplate notice, with the fields enclosed by brackets "[]" + replaced with your own identifying information. (Don't include + the brackets!) The text should be enclosed in the appropriate + comment syntax for the file format. We also recommend that a + file or class name and description of purpose be included on the + same "printed page" as the copyright notice for easier + identification within third-party archives. + + Copyright [yyyy] [name of copyright owner] + + Licensed under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. + You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. --- a/Makefile +++ b/Makefile @@ -6,6 +6,18 @@ endif SSL_DIR ?= ../jerboa-ssl LIBDIRS = lib:$(SSL_DIR)/lib:$(JH)/lib +NATIVE_DIR := $(JH)/jerboa-native-rs/target/release +RUNTIME_LIBDIRS = $(SSL_DIR):$(NATIVE_DIR) +DIST_DIR ?= dist/release-evidence +SBOM_DIR ?= dist/sbom +REPRO_DIR ?= dist/reproducibility +SOAK_DIR ?= dist/soak +FUZZ_EVIDENCE_DIR ?= dist/fuzz-evidence +TLS_PROXY_EVIDENCE_DIR ?= dist/tls-proxy-evidence +CARGO_AUDIT ?= $(shell command -v cargo-audit 2>/dev/null || printf '%s/.cargo/bin/cargo-audit' "$$HOME") +WASM_DIR ?= wasm +WASM_OUTPUTS = lib/jerboa-https/sandbox/http_parser.wasm +export CARGO_CACHE_AUTO_CLEAN_FREQUENCY ?= never UNAME_S := $(shell uname -s) ifeq ($(UNAME_S),Darwin) @@ -14,7 +26,7 @@ else LD_VAR = LD_LIBRARY_PATH endif -.PHONY: all build transpile test test-https test-httpd security audit fuzz-check verify clean deps +.PHONY: all build transpile wasm wasm-clean test test-https test-httpd security audit fuzz-check load-smoke fuzz-evidence tls-proxy-evidence sbom reproducibility-report soak-evidence verify release-evidence clean deps all: build @@ -24,29 +36,106 @@ deps: transpile: $(JERBUILD) transpile src lib --force -build: deps transpile +wasm: + @$(WASM_DIR)/build.sh + +wasm-clean: + rm -rf $(WASM_DIR)/target $(WASM_OUTPUTS) + +build: deps wasm transpile test: test-https test-httpd test-https: build JERBOA_SSL_LIB=$(SSL_DIR) \ - $(LD_VAR)=$(SSL_DIR) \ + $(LD_VAR)=$(RUNTIME_LIBDIRS) \ $(JERBUILD) exec --libdirs "$(LIBDIRS)" tests/https-test.ss test-httpd: build JERBOA_SSL_LIB=$(SSL_DIR) \ - $(LD_VAR)=$(SSL_DIR) \ + $(LD_VAR)=$(RUNTIME_LIBDIRS) \ $(JERBUILD) exec --libdirs "$(LIBDIRS)" tests/httpd-test.ss security: scripts/daemon-security-check.sh audit: - @echo "No Cargo workspace in this repo; audit native TLS dependencies separately." + @if [ -x "$(CARGO_AUDIT)" ]; then \ + (cd $(WASM_DIR) && "$(CARGO_AUDIT)" audit); \ + else \ + echo "cargo-audit not installed; checking Rust parser/fuzz metadata only"; \ + cargo metadata --manifest-path wasm/Cargo.toml --locked --format-version 1 >/dev/null; \ + cargo metadata --manifest-path fuzz/Cargo.toml --locked --format-version 1 >/dev/null; \ + fi + +fuzz-check: build + cargo run --manifest-path fuzz/Cargo.toml --bin corpus_smoke + @if rustup toolchain list 2>/dev/null | grep -q '^nightly' && \ + { command -v cargo-fuzz >/dev/null 2>&1 || [ -x "$$HOME/.cargo/bin/cargo-fuzz" ]; }; then \ + NIGHTLY_BIN=$$(dirname "$$(rustup which cargo --toolchain nightly)"); \ + PATH="$$NIGHTLY_BIN:$$HOME/.cargo/bin:$$PATH" cargo fuzz build --fuzz-dir fuzz; \ + else \ + echo "cargo-fuzz with nightly Rust unavailable; falling back to cargo check"; \ + cargo check --manifest-path fuzz/Cargo.toml --bins; \ + fi + JERBOA_SSL_LIB=$(SSL_DIR) \ + $(LD_VAR)=$(RUNTIME_LIBDIRS) \ + $(JERBUILD) exec --libdirs "$(LIBDIRS)" tests/httpd-fuzz-test.ss + +load-smoke: build + JERBOA_SSL_LIB=$(SSL_DIR) \ + $(LD_VAR)=$(RUNTIME_LIBDIRS) \ + $(JERBUILD) exec --libdirs "$(LIBDIRS)" tests/httpd-load-smoke.ss + +fuzz-evidence: build + JERBUILD="$(JERBUILD)" SSL_DIR="$(SSL_DIR)" FUZZ_EVIDENCE_DIR="$(FUZZ_EVIDENCE_DIR)" bash scripts/fuzz-evidence.sh + +sbom: build + JERBUILD="$(JERBUILD)" SSL_DIR="$(SSL_DIR)" SBOM_DIR="$(SBOM_DIR)" bash scripts/sbom.sh + +reproducibility-report: + JERBUILD="$(JERBUILD)" SSL_DIR="$(SSL_DIR)" REPRO_DIR="$(REPRO_DIR)" bash scripts/reproducibility-report.sh + +soak-evidence: + JERBUILD="$(JERBUILD)" SSL_DIR="$(SSL_DIR)" SOAK_EVIDENCE_DIR="$(SOAK_DIR)" bash scripts/soak-evidence.sh + +tls-proxy-evidence: + TLS_PROXY_EVIDENCE_DIR="$(TLS_PROXY_EVIDENCE_DIR)" bash scripts/tls-proxy-evidence.sh -fuzz-check: test-httpd +verify: security test fuzz-check load-smoke audit sbom reproducibility-report -verify: security test fuzz-check audit +release-evidence: verify + mkdir -p "$(DIST_DIR)" + git rev-parse HEAD > "$(DIST_DIR)/git-commit.txt" + git status --short > "$(DIST_DIR)/git-status.txt" + uname -a > "$(DIST_DIR)/build-environment.txt" + $(JERBUILD) --version >> "$(DIST_DIR)/build-environment.txt" + $(JERBUILD) --jerboa-home >> "$(DIST_DIR)/build-environment.txt" + cargo metadata --manifest-path wasm/Cargo.toml --locked --format-version 1 > "$(DIST_DIR)/cargo-metadata-wasm.json" + cargo metadata --manifest-path fuzz/Cargo.toml --locked --format-version 1 > "$(DIST_DIR)/cargo-metadata-fuzz.json" + find lib/jerboa-https/sandbox -type f -name '*.wasm' -exec shasum -a 256 {} \; > "$(DIST_DIR)/wasm-sha256.txt" + if [ -x "$(CARGO_AUDIT)" ]; then (cd "$(WASM_DIR)" && "$(CARGO_AUDIT)" audit > "../$(DIST_DIR)/rustsec-wasm.txt"); else printf 'status=blocked-cargo-audit-missing\n' > "$(DIST_DIR)/rustsec-wasm.txt"; fi + find src lib tests -type f \( -name '*.ss' -o -name '*.sls' \) -print | sort > "$(DIST_DIR)/scheme-inputs.txt" + find "$(SSL_DIR)" -maxdepth 2 -type f \( -name '*.ss' -o -name '*.sls' -o -name '*.c' -o -name '*.h' -o -name 'Cargo.lock' -o -name 'Cargo.toml' \) -print | sort > "$(DIST_DIR)/tls-dependency-inputs.txt" + test -f "$(SSL_DIR)/jerboa_ssl_shim.c" && shasum -a 256 "$(SSL_DIR)/jerboa_ssl_shim.c" > "$(DIST_DIR)/tls-shim-sha256.txt" + $(MAKE) soak-evidence + $(MAKE) fuzz-evidence + $(MAKE) tls-proxy-evidence + rm -rf "$(DIST_DIR)/sbom" "$(DIST_DIR)/reproducibility" "$(DIST_DIR)/soak" "$(DIST_DIR)/fuzz-evidence" "$(DIST_DIR)/tls-proxy-evidence" + cp -R "$(SBOM_DIR)" "$(DIST_DIR)/sbom" + cp -R "$(REPRO_DIR)" "$(DIST_DIR)/reproducibility" + cp -R "$(SOAK_DIR)" "$(DIST_DIR)/soak" + cp -R "$(FUZZ_EVIDENCE_DIR)" "$(DIST_DIR)/fuzz-evidence" + cp -R "$(TLS_PROXY_EVIDENCE_DIR)" "$(DIST_DIR)/tls-proxy-evidence" + cat "$(SBOM_DIR)/status.txt" > "$(DIST_DIR)/sbom.log" + cat "$(REPRO_DIR)/result.txt" > "$(DIST_DIR)/reproducibility.log" + cat "$(SOAK_DIR)/status.txt" > "$(DIST_DIR)/soak.log" + cat "$(FUZZ_EVIDENCE_DIR)/status.txt" > "$(DIST_DIR)/fuzz-evidence.log" + cat "$(TLS_PROXY_EVIDENCE_DIR)/status.txt" > "$(DIST_DIR)/tls-proxy-policy.txt" + grep -q '^tls_proxy_policy_status=present$$' "$(DIST_DIR)/tls-proxy-policy.txt" + grep -q '^forwarded_header_policy_status=deny-by-default$$' "$(DIST_DIR)/tls-proxy-policy.txt" + grep -q '^plaintext_listener_policy_status=private-or-behind-tls-proxy$$' "$(DIST_DIR)/tls-proxy-policy.txt" + if [ "$${JHTTPS_RUN_COVERAGE_FUZZ:-0}" = "1" ]; then grep -q '^coverage_fuzz_status=local-smoke-recorded$$' "$(DIST_DIR)/fuzz-evidence/status.txt"; fi clean: rm -rf lib --- a/README.md +++ b/README.md @@ -93,6 +93,8 @@ git clone https://github.com/ober/chez-https.git ~/mine/chez-https - Chunked transfer encoding - 100 Continue handling - Binary response body preservation (critical for gzip/binary downloads) +- Configurable HTTPD worker count, listen backlog, queue capacity, request + timeout, response timeout, header count, and body size limits - S3-compatible header format (`::` separator convention) - URL encoding (RFC 3986) - Automatic SSL initialization @@ -103,6 +105,27 @@ git clone https://github.com/ober/chez-https.git ~/mine/chez-https make test ``` +## Release Evidence + +The HTTPD surface is private until the daemon release gate is complete. Run: + +```bash +make release-evidence +``` + +The release bundle includes SBOM, reproducibility, TLS dependency, HTTPD +fuzz-evidence, and HTTPD soak status under `dist/release-evidence/`. Default +fuzz evidence records fixed adversarial cases, a 2048-case generated +deterministic HTTPD corpus, and a present cargo-fuzz harness for the sandboxed +HTTP parser. Run with `JHTTPS_RUN_COVERAGE_FUZZ=1` to record bounded local +coverage evidence; production release still requires longer release-host fuzz +evidence or a signed exception. Default local soak evidence records load and queue-exhaustion +soak as blocked/not-run. Opt-in local soak evidence records deterministic +slow-client coverage plus bounded load/queue smokes, but production release +still requires target-host sustained load evidence. +HTTPD TLS/proxy policy is documented in `docs/tls-proxy-policy.md` and copied +into `dist/release-evidence/tls-proxy-policy.txt`. + ## Architecture ``` @@ -113,4 +136,7 @@ chez-https (this library) └── OpenSSL (libssl, libcrypto) ``` -`chez-https` is a pure Scheme library with no C code. All native I/O goes through `chez-ssl`. +`chez-https` is Scheme on top of `chez-ssl` for I/O. The HTTPD parser boundary +uses a small Rust/WASM sandbox for request lines, header lines, and chunk sizes +when the Jerboa WASM sandbox is available; `JHTTPS_REQUIRE_WASM_HTTP=1` makes +that boundary fail closed for production deployments. --- a/SECURITY.md +++ b/SECURITY.md @@ -11,16 +11,54 @@ must be cut from a clean checkout after: - `make security` - `make test` - `make fuzz-check` +- `make fuzz-evidence` +- `make load-smoke` - `make audit` - `make verify` +- `make release-evidence` + +Release evidence must include current SBOM/toolchain provenance, local +`jerboa-ssl` dependency inputs, and a repeated clean generated-library build +report. It must also include explicit HTTPD soak status. A reproducibility +`status=mismatch` report or `production_load_status=blocked-not-run` is a +production blocker unless a release owner records an explicit exception. Local +`load_status=local-smoke-recorded` and +`queue_exhaustion_status=local-smoke-recorded` prove only bounded local smoke +coverage. Default `coverage_fuzz_status=blocked-not-run` is also a production +blocker until bounded cargo-fuzz evidence is recorded and longer release-host +fuzzing is reviewed. + +Reviewed target-host proof files may be attached instead of rerunning a target +test in this checkout, but required proof must fail closed. Use +`JHTTPS_REQUIRE_TARGET_LOAD_PROOF=1` with `JHTTPS_TARGET_LOAD_PROOF_FILE`, +`JHTTPS_REQUIRE_TARGET_FUZZ_PROOF=1` with `JHTTPS_TARGET_FUZZ_PROOF_FILE`, and +`JHTTPS_REQUIRE_TARGET_TLS_PROXY_PROOF=1` with +`JHTTPS_TARGET_TLS_PROXY_PROOF_FILE`. Missing, empty, or marker-incomplete proof +records `status=blocked-target-proof` and does not clear production blockers. ## Hardening Expectations -- Request-line, header, chunked transfer, content-length, and traversal cases - need malformed-input coverage before any production claim. +- The HTTPD threat model is tracked in `docs/threat-model.md`. +- TLS termination, reverse-proxy, forwarded-header, certificate, and plaintext + listener policy is tracked in `docs/tls-proxy-policy.md`. +- Request-line, header, chunked transfer, content-length, traversal, and + slow-byte-delivery cases must stay covered by `make fuzz-check`, including + the 2048-case generated deterministic HTTPD corpus and the HTTP parser + fuzz-corpus smoke. +- `make fuzz-evidence` must record the deterministic HTTPD adversarial corpus + result and must not mark the coverage-guided fuzz gate complete unless all + cargo-fuzz HTTP parser targets pass. +- Bounded sequential, concurrent, and queue-recovery behavior must stay covered + by `make load-smoke`. - Static file serving must remain traversal-resistant. -- Timeout, max header, max body, and slow-client behavior are release gates. -- TLS termination and certificate policy must be documented for deployments. +- Timeout, max header, max body, worker, backlog, queue-capacity, and + slow-client behavior are release gates; `request-timeout:`, + `response-timeout:`, `workers:`, `backlog:`, and `queue-capacity:` must be + set explicitly for daemon deployments. +- Plain HTTP listeners must remain private or behind a reviewed TLS proxy. +- Public production deployment still requires queue-exhaustion/load evidence, + TLS dependency evidence from `jerboa-ssl`, SBOM/reproducibility evidence, and + external review. ## Reporting new file mode 100644 --- /dev/null +++ b/docs/fuzzing.md @@ -0,0 +1,57 @@ +# HTTPD Fuzz Evidence + +`jerboa-https` has deterministic HTTPD adversarial coverage and a bounded +cargo-fuzz harness for the sandboxed HTTP parser. The distinction is +release-significant: deterministic coverage proves known request-smuggling, +traversal, slow-client, and parser edge cases stay covered, while cargo-fuzz +evidence exercises the request-line, header, and chunk-size parser boundary +that the Scheme HTTPD uses before routing requests. + +Required release markers: + +- deterministic_fuzz_evidence_status: documented +- deterministic_fuzz_cases_minimum: 2048 +- coverage_fuzz_evidence_status: documented +- coverage_fuzz_targets: http_request_line-http_headers-http_chunked +- coverage_fuzz_runs_minimum: 2048 +- target_fuzz_proof_status: documented + +Run the local evidence bundle with: + +```sh +make fuzz-evidence JERBUILD=/path/to/jerbuild SSL_DIR=/path/to/jerboa-ssl +``` + +The default run writes `dist/fuzz-evidence/status.txt` with: + +- `deterministic_fuzz_status=local-smoke-recorded` when + `tests/httpd-fuzz-test.ss` passes. +- `deterministic_generated_cases=2048` and + `deterministic_generated_failures=0` when the generated HTTPD corpus passes. +- `coverage_guided_harness_status=present`. +- `coverage_fuzz_status=blocked-not-run` unless the coverage run is explicitly + requested. + +Run bounded coverage-guided evidence with: + +```sh +JHTTPS_RUN_COVERAGE_FUZZ=1 JHTTPS_FUZZ_RUNS=2048 make fuzz-evidence +``` + +That command requires nightly Rust and `cargo-fuzz`. It records +`coverage_fuzz_status=local-smoke-recorded` only if all three libFuzzer targets +pass. Short local runs are evidence that the harness exists and catches parser +panics; sustained release-host fuzzing is still required before a production +claim. + +For release-host fuzz proof captured outside the current checkout, attach a +reviewed text proof with `JHTTPS_TARGET_FUZZ_PROOF_FILE` and set +`JHTTPS_REQUIRE_TARGET_FUZZ_PROOF=1`. The proof must include: + +```text +coverage_fuzz_status=release-host-sustained-recorded +targets_completed=3 +``` + +The release evidence target copies the bundle into +`dist/release-evidence/fuzz-evidence/` and writes `fuzz-evidence.log`. new file mode 100644 --- /dev/null +++ b/docs/release-evidence.md @@ -0,0 +1,135 @@ +# Release Evidence + +`jerboa-https` includes client code and an HTTPD surface. The HTTPD surface +stays private until a release candidate has current evidence for the full +daemon gate, dependency inventory, reproducibility, slow-client/load behavior, +TLS dependency posture, and external review. + +Run from a clean checkout with an audited `jerboa-ssl` checkout: + +```sh +make clean +make release-evidence JERBUILD=/path/to/jerbuild SSL_DIR=/path/to/jerboa-ssl +``` + +The `release-evidence` target runs `make verify` first, then writes untracked +artifacts under `dist/release-evidence/`: + +- `git-commit.txt` and `git-status.txt`. +- `build-environment.txt` with OS and Jerboa toolchain identity. +- `sbom/` and `sbom.log` with toolchain identity, source hashes, generated + library hashes, and local `jerboa-ssl` dependency hashes. +- `reproducibility/` and `reproducibility.log` with two clean generated-library + builds, SHA-256 manifests, and any manifest/tree diffs. +- `soak/` and `soak.log` with HTTPD slow-client/load/queue-exhaustion evidence + status. The default local release bundle records `blocked-not-run`. Opt-in + local evidence records deterministic slow-client coverage plus bounded + local load/queue smokes, while `production_load_status=blocked-not-run` + remains until sustained target-host evidence is captured or a reviewed + target proof is attached. +- `fuzz-evidence/` and `fuzz-evidence.log` with the fixed adversarial cases, + the 2048-case generated deterministic HTTPD corpus, and the explicit + coverage-guided fuzz status for the sandboxed HTTP parser. Default evidence + records `coverage_fuzz_status=blocked-not-run`; run with + `JHTTPS_RUN_COVERAGE_FUZZ=1` to capture bounded local cargo-fuzz evidence, + or attach reviewed release-host proof with `JHTTPS_TARGET_FUZZ_PROOF_FILE`. +- `cargo-metadata-wasm.json`, `cargo-metadata-fuzz.json`, `wasm-sha256.txt`, + and `rustsec-wasm.txt` for the HTTP parser sandbox supply chain. +- `scheme-inputs.txt` listing the Scheme source and generated library inputs + used by tests. +- `tls-dependency-inputs.txt` and `tls-shim-sha256.txt` for the local + `jerboa-ssl` dependency. +- `tls-proxy-policy.txt`, generated from `docs/tls-proxy-policy.md`, with TLS + termination, reverse-proxy, forwarded-header, HSTS, certificate, plaintext + listener, production-load policy markers, and optional hashed target-host + TLS/proxy proof. + +For a release candidate, archive those files with the candidate package and +the matching `jerboa-ssl` release evidence. Reproducibility is claimed only +when `dist/release-evidence/reproducibility/result.txt` records `status=match` +or a release owner records an explicit exception. + +The external review packet must include this file, `SECURITY.md`, +`docs/threat-model.md`, `docs/tls-proxy-policy.md`, the HTTPD adversarial +corpus in `tests/httpd-fuzz-test.ss`, `docs/fuzzing.md`, the HTTP parser WASM +and fuzz evidence, slow-client/load evidence, +`dist/release-evidence/tls-proxy-policy.txt`, the `jerboa-ssl` FFI evidence, +and any accepted native OpenSSL risks. + +To refresh the current deterministic adversarial and slow-byte smoke evidence +inside `dist/soak/`, run: + +```sh +JHTTPS_RUN_RELEASE_SOAK=1 make soak-evidence +``` + +That records `status=short-smoke-not-production` when the local smokes pass, +including `load-smoke.log` for bounded sequential, concurrent, and idle +queue-recovery checks. It does not clear the production gate. Production +release still requires sustained request/load evidence captured on the target +release host. + +To attach reviewed target HTTPD load proof without rerunning the load test in +the current checkout, provide a text proof file and make the gate fail closed +if it is missing or marker-incomplete: + +```sh +JHTTPS_TARGET_LOAD_PROOF_FILE=/path/to/httpd-load-proof.txt \ +JHTTPS_REQUIRE_TARGET_LOAD_PROOF=1 \ +make soak-evidence +``` + +The load proof must include these exact lines: + +```text +production_load_status=current-run-recorded +load_status=release-host-sustained-recorded +queue_exhaustion_status=release-host-sustained-recorded +``` + +To refresh only the HTTPD fuzz evidence, run: + +```sh +make fuzz-evidence JERBUILD=/path/to/jerbuild SSL_DIR=/path/to/jerboa-ssl +``` + +That reruns the deterministic adversarial corpus, records +`deterministic_generated_cases=2048`, and records the coverage-guided harness +status. To run bounded local cargo-fuzz evidence too: + +```sh +JHTTPS_RUN_COVERAGE_FUZZ=1 JHTTPS_FUZZ_RUNS=2048 make fuzz-evidence +``` + +To attach reviewed longer release-host fuzz proof, provide a text proof file +and require it: + +```sh +JHTTPS_TARGET_FUZZ_PROOF_FILE=/path/to/httpd-fuzz-proof.txt \ +JHTTPS_REQUIRE_TARGET_FUZZ_PROOF=1 \ +make fuzz-evidence +``` + +The fuzz proof must include: + +```text +coverage_fuzz_status=release-host-sustained-recorded +targets_completed=3 +``` + +To attach reviewed target-host TLS/proxy smoke proof, provide a text proof +file and require it: + +```sh +JHTTPS_TARGET_TLS_PROXY_PROOF_FILE=/path/to/httpd-tls-proxy-proof.txt \ +JHTTPS_REQUIRE_TARGET_TLS_PROXY_PROOF=1 \ +make tls-proxy-evidence +``` + +The TLS/proxy proof must include: + +```text +tls_proxy_smoke_status=target-evidence-recorded +forwarded_header_policy_status=deny-by-default +plaintext_listener_policy_status=private-or-behind-tls-proxy +``` new file mode 100644 --- /dev/null +++ b/docs/threat-model.md @@ -0,0 +1,103 @@ +# jerboa-https HTTPD Threat Model + +Status: the HTTP client library is a review-required dependency; the HTTP/1.1 +server module is experimental/private until the daemon release gates in +`~/Release-plan.md` and `~/mine/jerboa-production-readiness.md` are complete. + +## Scope + +This model covers the HTTPD surface in `src/jerboa-https/httpd.ss`: request +parsing, response generation, static file serving, body handling, and network +service behavior. TLS termination, certificate provisioning, reverse-proxy +configuration, and deployment firewalls are external unless a future server +entrypoint manages them directly. + +## Assets + +- Static file roots and any files reachable from them. +- Service availability and worker queue capacity. +- Request/response logs that may contain paths, headers, query strings, or + credentials. +- TLS private keys if a deployment colocates termination with the service. +- Upstream callers that trust parsed request data. + +## Trust Boundaries + +- Network boundary: every request line, header, body byte, and connection timing + pattern is hostile. +- Filesystem boundary: URL paths must not escape the configured static root. +- Parser boundary: request-line, header-line, and chunk-size parsing prefer the + sandboxed Rust/WASM parser before application code sees the request. The + Scheme parser remains a development fallback unless + `JHTTPS_REQUIRE_WASM_HTTP=1` is set. +- Queue boundary: accepted sockets are handed to bounded worker capacity and + must be closed when admission fails. + +## Threat Actors + +- Remote clients attempting request smuggling, header confusion, slowloris + stalls, body overflows, traversal, or resource exhaustion. +- Local deployers accidentally serving broad filesystem roots or placing + secrets under the static root. +- Intermediaries sending ambiguous `Transfer-Encoding`/`Content-Length` + combinations. + +## Security Objectives + +- Reject ambiguous framing, duplicate `Content-Length`, unsupported transfer + encodings, `Content-Length` plus `Transfer-Encoding`, truncated bodies, and + malformed chunk terminators. +- Enforce maximum request line length, header line length, header count, and + body size before allocation grows unbounded. +- Close file ports, accepted sockets, and client ports on all success and error + paths. +- Keep static file serving traversal-resistant after percent decoding. +- Make slow-client timeout, worker count, worker queue capacity, listen backlog, + and TLS termination policy explicit in deployment guidance. +- Treat forwarded headers as untrusted by default; proxy deployments must strip + client-provided forwarding headers before adding trusted values. + +## Required Controls + +- `make verify` must pass, including `make security`, HTTPD tests, + deterministic HTTPD adversarial corpus tests, slow-byte-delivery smoke tests, + bounded local load/queue-recovery smoke tests, and audit notes. +- `make release-evidence` must include explicit HTTPD soak status. A default + `blocked-not-run` load/queue-exhaustion record is acceptable for private + builds, but it is a production blocker. +- Regression coverage must include request-line parser fuzzing, header parser + fuzzing, duplicate and conflicting length headers, chunked bodies, oversized + bodies, percent-encoding, traversal attempts, malformed methods, slow + clients, and bounded local queue pressure. Sustained queue exhaustion remains + a separate release-host load-test gate. +- Static roots must be explicit and must not default to a sensitive directory. +- Logs must redact authorization headers, cookies, tokens, and query parameters + likely to hold credentials. +- Deployments must follow `docs/tls-proxy-policy.md` for TLS termination, + reverse-proxy behavior, forwarded-header trust, certificate handling, + plaintext listener exposure, HSTS, and release-host load evidence. +- Target-host load, target-host TLS/proxy smoke, and release-host fuzz proof + attachments must fail closed when required. Use + `JHTTPS_TARGET_LOAD_PROOF_FILE`, `JHTTPS_TARGET_TLS_PROXY_PROOF_FILE`, and + `JHTTPS_TARGET_FUZZ_PROOF_FILE` only for reviewed proof files carrying the + exact required status markers. + +## Open Release Gates + +- Run longer release-host coverage-guided fuzzing for the sandboxed + request-line, header, and chunk-size parser targets. Short bounded local + cargo-fuzz evidence is useful smoke coverage, not a production soak. If the + proof is captured outside this checkout, require + `JHTTPS_TARGET_FUZZ_PROOF_FILE` with `JHTTPS_REQUIRE_TARGET_FUZZ_PROOF=1`. +- Add release-host queue-exhaustion/load evidence beyond the deterministic + slow-byte and bounded local load/queue smokes. If the proof is captured + outside this checkout, require `JHTTPS_TARGET_LOAD_PROOF_FILE` with + `JHTTPS_REQUIRE_TARGET_LOAD_PROOF=1`. +- Add target-host TLS/proxy smoke proof with + `JHTTPS_TARGET_TLS_PROXY_PROOF_FILE` and + `JHTTPS_REQUIRE_TARGET_TLS_PROXY_PROOF=1`. +- Keep SBOM/toolchain provenance and repeated generated-library build evidence + current for release artifacts. +- Schedule external review before any public production claim for HTTPD. +- Replace private reporting instructions in `SECURITY.md` with a public + advisory contact before public release. new file mode 100644 --- /dev/null +++ b/docs/tls-proxy-policy.md @@ -0,0 +1,104 @@ +# HTTPD TLS And Proxy Policy + +`jerboa-https` includes an HTTP/1.1 server surface. A production deployment +must make TLS termination, proxy behavior, and forwarded-request trust explicit +before the HTTPD surface is exposed outside a controlled environment. + +## Release Checklist Markers + +- tls_termination_policy_status: documented +- reverse_proxy_policy_status: documented +- forwarded_header_policy_status: deny-by-default +- hsts_policy_status: proxy-or-application-required +- certificate_policy_status: documented +- plaintext_listener_policy_status: private-or-behind-tls-proxy +- production_load_policy_status: release-host-required +- target_tls_proxy_proof_status: documented +- target_load_proof_status: documented + +These markers are checked by `make security` and copied into release evidence. +Changing one of them requires matching implementation, deployment notes, and +refreshed release evidence. + +## Supported Deployment Shapes + +Use one of these reviewed shapes: + +- Terminate TLS in a hardened reverse proxy on the same host or trusted private + network segment, then proxy cleartext HTTP to `jerboa-https` over loopback or + an isolated service network. +- Use `httpd-start-https` only with current `jerboa-ssl` release evidence, + reviewed certificate/key permissions, and target-host TLS smoke evidence. + +Do not expose a plaintext listener to the public Internet. Plain HTTP is +acceptable only for loopback, private integration tests, or a proxy backend +network where TLS is already terminated by a reviewed component. + +## Reverse Proxy Requirements + +When a proxy terminates TLS: + +- Restrict backend access to loopback or a private service network. +- Preserve the original method, path, query, and body without ambiguous + re-encoding. +- Normalize or reject duplicate `Content-Length`, conflicting + `Transfer-Encoding`, oversized headers, and unsupported methods before + forwarding when the proxy supports doing so. +- Set explicit backend connect, read, and write timeouts shorter than the + external client timeout budget. +- Do not rely on `X-Forwarded-*`, `Forwarded`, or similar headers for security + decisions unless an application-specific trusted-proxy parser is added. +- Strip inbound client-provided forwarding headers at the edge before adding + trusted values for logging or application use. + +## TLS And Certificate Requirements + +- Use TLS 1.2 or TLS 1.3 only. +- Disable obsolete ciphers, compression, and renegotiation in the proxy or TLS + terminator. +- Store private keys outside the static-file root and outside release evidence. +- Run certificate renewal as a separate least-privilege process. +- Record certificate ownership, key permissions, renewal path, and TLS test + command output in the release packet. +- Enable HSTS at the proxy or application layer for public HTTPS deployments + after confirming the hostname should be HTTPS-only. + +## Application Listener Requirements + +- Set `workers:`, `backlog:`, `queue-capacity:`, `request-timeout:`, and + `response-timeout:` explicitly for daemon deployments. +- Keep static roots narrow and separate from config, keys, logs, generated + release evidence, and source checkouts. +- Keep authorization headers, cookies, bearer tokens, and query parameters that + may contain credentials out of logs and release artifacts. +- Treat `production_load_status=blocked-not-run` as a release blocker. + +## Production Evidence + +A production release needs: + +- `dist/release-evidence/tls-proxy-policy.txt` with the markers above. +- Target-host TLS or proxy smoke output. +- Proxy or in-process TLS configuration snippets with secrets redacted. +- `jerboa-ssl` release evidence for the TLS dependency used by the candidate. +- Sustained target-host HTTPD load evidence with + `production_load_status=current-run-recorded` or a signed exception. + +For the fail-closed release gate, attach target-host TLS/proxy smoke with +`JHTTPS_TARGET_TLS_PROXY_PROOF_FILE` and set +`JHTTPS_REQUIRE_TARGET_TLS_PROXY_PROOF=1`. The proof must include: + +```text +tls_proxy_smoke_status=target-evidence-recorded +forwarded_header_policy_status=deny-by-default +plaintext_listener_policy_status=private-or-behind-tls-proxy +``` + +Attach sustained target-host load proof with `JHTTPS_TARGET_LOAD_PROOF_FILE` +and set `JHTTPS_REQUIRE_TARGET_LOAD_PROOF=1`. The proof must include: + +```text +production_load_status=current-run-recorded