Security hardening and release readiness
ober
ce2fdaa32667c84a4090962c1b6f6093b01b2473
new file mode 100644 --- /dev/null +++ b/.github/workflows/ci.yml @@ -0,0 +1,40 @@ +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 + +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 ca-certificates curl git ripgrep + + - name: Bootstrap Jerboa + run: | + set -eu + sh support/ensure-jerboa.sh "$JERBOA_VERSION" .jerboa/bin + "$JERBUILD" --jerboa-home + + - name: Install cargo-audit + run: | + set -eu + cargo install cargo-audit --locked + + - 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 @@ -3,6 +3,7 @@ *.o target/ vendor/ +dist/ /*-main.c keys/ secmon-agent new file mode 100644 --- /dev/null +++ b/.jerboa/security.json @@ -0,0 +1,86 @@ +{ + "version": 1, + "repo": "jerboa-secmonlib", + "extends": [ + "jerboa:daemon", + "jerboa:security-monitor", + "jerboa:network-service", + "jerboa:static-binary", + "jerboa:ffi-boundary", + "jerboa:crypto-boundary" + ], + "paths": { + "production": [ + ".jerbuild", + "Makefile", + "bin/**/*.ss", + "lib/**/*.sls", + "support/*.c", + "support/*.sh", + "patches/jerboa-native-rs/**/*.rs", + "patches/jerboa-native-rs/Cargo.toml", + "patches/jerboa-native-rs/Cargo.lock" + ], + "tests": ["tests/**"], + "generated": ["secmon-agent", "support/ffi-symbols.gen", "target/**", "build/**", "dist/**"], + "vendor": ["vendor/**"], + "docs": ["README.md", "SECURITY.md", "docs/**", "*.md"] + }, + "policy": { + "failOn": ["critical", "high"], + "crypto": { + "customCrypto": "deny", + "requireAuditedDependencies": true, + "requireKeySeparation": true + }, + "network": { + "requireTimeouts": true, + "requireConnectionLimits": true, + "requireAuthenticatedTransport": true + }, + "daemon": { + "requirePrivilegeDrop": true, + "requireSandboxPlan": true, + "requireReleaseEvidence": true + }, + "releaseEvidence": { + "targetLoadProof": { + "requiredForProduction": true, + "proofFileEnv": "JSECMONLIB_TARGET_LOAD_PROOF_FILE", + "failClosedEnv": "JSECMONLIB_REQUIRE_TARGET_LOAD_PROOF", + "requiredMarkers": [ + "production_load_status=current-run-recorded", + "agent_runtime_status=release-host-runtime-recorded", + "collector_load_status=release-host-sustained-recorded", + "production_min_seconds_met=1" + ] + }, + "targetSandboxProof": { + "requiredForProduction": true, + "proofFileEnv": "JSECMONLIB_TARGET_SANDBOX_PROOF_FILE", + "failClosedEnv": "JSECMONLIB_REQUIRE_TARGET_SANDBOX_PROOF", + "requiredMarkers": [ + "sandbox_status=target-evidence-recorded", + "privilege_drop_status=target-evidence-recorded", + "negative_access_test_status=target-denied-recorded" + ] + }, + "targetBinarySmokeProof": { + "requiredForProduction": true, + "proofFileEnv": "JSECMONLIB_TARGET_BINARY_SMOKE_PROOF_FILE", + "failClosedEnv": "JSECMONLIB_REQUIRE_TARGET_BINARY_SMOKE_PROOF", + "requiredMarkers": [ + "target_binary_smoke_status=target-evidence-recorded", + "agent_no_secret_startup_status=pass", + "target_platform_status=linux-or-freebsd-recorded" + ] + } + }, + "ffi": { + "requireCollectSafeBlockingCalls": true, + "requireRegisteredStaticSymbols": true, + "requireOwnershipDocumentation": true + } + }, + "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 @@ -16,8 +16,14 @@ JSQLITE_URL ?= https://git.sr.ht/~lisp/jsqlite JSQLITE_SRC ?= $(JSQLITE_REPO)/src LIBDIRS := --libdirs lib:$(JSQLITE_SRC):$(JH)/lib JEXEC := $(JERBUILD) exec $(LIBDIRS) +CARGO_AUDIT ?= $(shell command -v cargo-audit 2>/dev/null || printf '%s/.cargo/bin/cargo-audit' "$$HOME") +DIST_DIR ?= dist/release-evidence +SBOM_DIR ?= dist/sbom +REPRO_DIR ?= dist/reproducibility +SOAK_DIR ?= dist/soak +BINARY_SMOKE_DIR ?= dist/binary-smoke -.PHONY: all help build binary keygen agent collector analyze test install clean ensure-jsqlite +.PHONY: all help build binary keygen agent collector analyze test security audit verify sbom reproducibility-report soak-evidence binary-smoke release-evidence install clean ensure-jsqlite .DEFAULT_GOAL := help all: binary @@ -35,6 +41,12 @@ help: @echo " make collector ARGS=.. Run the event collector" @echo " make analyze ARGS=.. Run the offline analyzer" @echo " make test Run the test suite" + @echo " make verify Run release/security gates" + @echo " make sbom Write SBOM/provenance evidence" + @echo " make reproducibility-report" + @echo " Compare two clean local binary builds" + @echo " make soak-evidence Record load/soak evidence status" + @echo " make release-evidence Write release evidence under dist/" # Standalone native binary via .jerbuild (entry bin/agent.ss -> secmon-agent). # Two passes: the first cargo-builds jerboa-native into @@ -48,10 +60,13 @@ ensure-jsqlite: fi @test -f "$(JSQLITE_SRC)/jsqlite/api.ss" +# The final pass stabilizes the WPO payload after the generated FFI symbol set +# exists, so repeated clean release builds produce the same secmon-agent bytes. binary: ensure-jsqlite @: > support/ffi-symbols.gen $(JERBUILD) build - sh support/gen-ffi-symbols.sh + JERBUILD="$(JERBUILD)" sh support/gen-ffi-symbols.sh + $(JERBUILD) build $(JERBUILD) build build: binary @@ -76,6 +91,65 @@ test: ensure-jsqlite fi @echo "All tests passed." +security: + scripts/security-check.sh + +audit: + @if ! [ -x "$(CARGO_AUDIT)" ]; then \ + echo "cargo-audit is required. Install with: cargo install cargo-audit --locked"; \ + exit 1; \ + fi + cd patches/jerboa-native-rs && "$(CARGO_AUDIT)" audit + +verify: security test audit binary + +sbom: + JERBUILD="$(JERBUILD)" JSECMONLIB_SBOM_DIR="$(SBOM_DIR)" scripts/sbom.sh + +reproducibility-report: + JERBUILD="$(JERBUILD)" JSECMONLIB_REPRO_DIR="$(REPRO_DIR)" scripts/reproducibility-report.sh + +soak-evidence: + JSECMONLIB_SOAK_DIR="$(SOAK_DIR)" scripts/soak-evidence.sh + +binary-smoke: + JSECMONLIB_BINARY_SMOKE_DIR="$(BINARY_SMOKE_DIR)" scripts/binary-smoke.sh + +release-evidence: verify sbom reproducibility-report soak-evidence binary-smoke + rm -rf "$(DIST_DIR)" + 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 patches/jerboa-native-rs/Cargo.toml --locked --format-version 1 > "$(DIST_DIR)/cargo-metadata-native.json" + cd patches/jerboa-native-rs && "$(CARGO_AUDIT)" audit > "$(CURDIR)/$(DIST_DIR)/rustsec-native.txt" + shasum -a 256 secmon-agent > "$(DIST_DIR)/secmon-agent-sha256.txt" + shasum -a 256 \ + .jerbuild \ + Makefile \ + SECURITY.md \ + .jerboa/security.json \ + docs/deployment-security.md \ + docs/release-evidence.md \ + docs/threat-model.md \ + scripts/binary-smoke.sh \ + scripts/reproducibility-report.sh \ + scripts/sbom.sh \ + scripts/security-check.sh \ + scripts/soak-evidence.sh \ + support/static-main.c \ + support/gen-ffi-symbols.sh \ + support/ensure-jerboa.sh \ + > "$(DIST_DIR)/source-inputs-sha256.txt" + cp support/ffi-symbols.gen "$(DIST_DIR)/ffi-symbols.gen" + { if command -v otool >/dev/null 2>&1; then otool -L secmon-agent; elif command -v ldd >/dev/null 2>&1; then ldd secmon-agent; else echo "no dynamic-linkage inspector found"; fi; } > "$(DIST_DIR)/native-linkage.txt" + cp -R "$(SBOM_DIR)" "$(DIST_DIR)/sbom" + cp -R "$(REPRO_DIR)" "$(DIST_DIR)/reproducibility" + cp -R "$(SOAK_DIR)" "$(DIST_DIR)/soak" + cp -R "$(BINARY_SMOKE_DIR)" "$(DIST_DIR)/binary-smoke" + install: binary mkdir -p $(BIN_DIR) install -m 0755 $(BIN) $(BIN_DIR)/$(BIN) --- a/README.md +++ b/README.md @@ -1 +1,8 @@ -# jerboa-secmon +# jerboa-secmonlib + +Static Jerboa security-monitor agent, collector/analyzer source, and native FFI +registration support. + +This repo is not public-production-ready until `make release-evidence` is +captured on the target Linux or FreeBSD release host and the blockers in +`docs/release-evidence.md` are closed. new file mode 100644 --- /dev/null +++ b/SECURITY.md @@ -0,0 +1,37 @@ +# Security Policy + +This repository is part of the Jerboa ecosystem. Treat it as experimental unless the README and release notes explicitly state a stronger support level. + +Production claims require the release gates tracked in `~/Release-plan.md` and `~/mine/jerboa-production-readiness.md` to be complete for this repository. + +## Supported Status + +No public production-support commitment exists yet. Security-sensitive releases must be cut from a clean checkout after: + +- `make verify` passes with the pinned Jerboa toolchain. +- `make release-evidence` has been archived for the release candidate. +- `git status --short` shows only intentional release changes. +- A secret scan is clean for the working tree, and history has been reviewed or intentionally reset before first public release. +- The documented build and test commands pass. +- Dependency and native-code audits are clean, or accepted risks are documented. +- Any FFI, filesystem, network, shell, or credential-handling behavior is documented. +- `docs/threat-model.md` and `docs/deployment-security.md` have been reviewed + for the target release platform. + +## Security Expectations + +- Do not commit secrets, private keys, tokens, production `.env` files, operational hostnames, customer/user data, or private infrastructure details. +- Do not implement cryptography directly. Use audited libraries or delegated components. +- Prefer Rust for byte-level parsing, FFI boundaries, crypto-adjacent integration, and sandbox setup. +- Prefer Jerboa for policy, orchestration, configuration, routing, tests, and high-level behavior. +- Document file I/O, shell execution, network access, FFI ownership/lifetime rules, and credential storage before publication. +- Daemons and hostile-input parsers require threat models, fuzz harnesses, regression corpora, resource limits, sandboxing documentation, and review before public production use. +- Static binaries must register only the FFI symbols provided by the exact Jerboa toolchain and native feature set used for the build. +- Do not claim target-host sandbox enforcement unless the release evidence + includes the service profile and a negative access test for that platform. +- Release CI for production candidates must set the fail-closed target proof + controls for load, sandbox/privilege-drop, and target binary smoke evidence. + +## Reporting + +Before public release, report issues privately to the repository owner. After public release, replace this section with a dedicated advisory contact, supported versions, and disclosure window. --- a/bin/agent.ss +++ b/bin/agent.ss @@ -1,7 +1,7 @@ #!chezscheme ;; secmon-agent: Security monitoring agent (import - (chezscheme) + (scheme) (jerboa prelude clean) (secmon config) (secmon crypto ecies) --- a/bin/analyze.ss +++ b/bin/analyze.ss @@ -1,7 +1,7 @@ #!chezscheme ;; secmon-analyze: Offline event analysis and detection (import - (chezscheme) + (scheme) (jerboa prelude clean) (secmon storage store) (std text json)) --- a/bin/collector.ss +++ b/bin/collector.ss @@ -1,7 +1,7 @@ #!chezscheme ;; secmon-collector: Collect and decrypt events from remote agents (import - (chezscheme) + (scheme) (jerboa prelude clean) (secmon config) (secmon crypto ecies) @@ -30,7 +30,7 @@ (define (connect-to-agent host port) (let ([c-socket (foreign-procedure "socket" (int int int) int)] - [c-connect (foreign-procedure "connect" (int u8* int) int)]) + [c-connect (foreign-procedure __collect_safe "connect" (int u8* int) int)]) (let ([fd (c-socket 2 1 0)]) (when (< fd 0) (error 'connect "socket() failed")) @@ -63,12 +63,15 @@ (flush-output-port port))) (define (recv-msg! port psk-auth) - (guard (e [#t #f]) - (let* ([len-bv (get-bytevector-n port 4)] - [len (unpack-u32-le len-bv 0)]) + (let* ([len-bv (get-bytevector-n port 4)]) + (unless (and (bytevector? len-bv) (= (bytevector-length len-bv) 4)) + (error 'recv-msg! "connection closed before frame length")) + (let ([len (unpack-u32-le len-bv 0)]) (when (> len 10485760) (error 'recv-msg! "message too large")) (let ([encrypted (get-bytevector-n port len)]) + (unless (and (bytevector? encrypted) (= (bytevector-length encrypted) len)) + (error 'recv-msg! "connection closed before frame body")) (psk-decrypt-transport psk-auth encrypted))))) ;; --- Authenticate to agent --- @@ -123,11 +126,10 @@ ;; --- Event decryption --- (define (decrypt-event decryptor stored-ev) - (guard (e [#t #f]) - (let* ([enc-bv (stored-event-encrypted-data stored-ev)] - [enc-payload (bytevector->encrypted-payload enc-bv)] - [plaintext (ecies-decrypt decryptor enc-payload)]) - (bytevector->security-event plaintext)))) + (let* ([enc-bv (stored-event-encrypted-data stored-ev)] + [enc-payload (bytevector->encrypted-payload enc-bv)] + [plaintext (ecies-decrypt decryptor enc-payload)]) + (bytevector->security-event plaintext))) ;; --- Print event --- @@ -369,22 +371,21 @@ (poll-loop)))))))) (define (store-decrypted-event! store stored-ev ev host-str) - (guard (e [#t (void)]) - (let ([data (security-event-data ev)]) - (store-event! store - (security-event-hostname ev) - host-str - (stored-event-seq stored-ev) - (security-event-timestamp-ms ev) - (symbol->string (security-event-event-type ev)) - (symbol->string (security-event-severity ev)) - (if (hashtable? data) (hashtable-ref data "pid" #f) #f) - (if (hashtable? data) (hashtable-ref data "process_name" #f) #f) - (format-event-summary ev) - (json-object->string (security-event->json ev))) - (store-set-last-seq! store host-str - (stored-event-seq stored-ev) - (security-event-hostname ev))))) + (let ([data (security-event-data ev)]) + (store-event! store + (security-event-hostname ev) + host-str + (stored-event-seq stored-ev) + (security-event-timestamp-ms ev) + (symbol->string (security-event-event-type ev)) + (symbol->string (security-event-severity ev)) + (if (hashtable? data) (hashtable-ref data "pid" #f) #f) + (if (hashtable? data) (hashtable-ref data "process_name" #f) #f) + (format-event-summary ev) + (json-object->string (security-event->json ev))) + (store-set-last-seq! store host-str + (stored-event-seq stored-ev) + (security-event-hostname ev)))) (define (format-event-summary ev) (let ([type (security-event-event-type ev)] --- a/bin/keygen.ss +++ b/bin/keygen.ss @@ -1,7 +1,7 @@ #!chezscheme ;; secmon-keygen: Generate ECIES keypair and PSK (import - (chezscheme) + (scheme) (jerboa prelude clean) (secmon crypto keys) (std text hex)) new file mode 100644 --- /dev/null +++ b/docs/deployment-security.md @@ -0,0 +1,68 @@ +# Deployment Security + +`secmon-agent` is not production-ready until this deployment posture is tested +on the target Linux or FreeBSD host and archived with the release evidence. + +## Accounts And Files + +- Run the agent as a dedicated `secmon` user when the selected monitors do not + require root-only host telemetry. +- When root is required, start with the minimum service-manager capability set + and drop privileges before accepting collector connections whenever the + target monitor mix allows it. +- Store `keys/public.key` and `keys/psk.key` readable only by the service user. + Keep `keys/private.key` only on the collector host. +- Keep event databases and release evidence outside web roots and shared temp + directories. + +## Network + +- Bind the agent listener to a management interface or loopback tunnel, not a + public interface. +- Firewall the listener to collector hosts. +- Rotate PSKs when a collector host or monitored host is rebuilt, reassigned, or + suspected compromised. + +## Sandbox Plan + +- Linux releases should prefer a systemd unit with `NoNewPrivileges=true`, + `PrivateTmp=true`, strict `ReadWritePaths` for the event buffer/log location, + and a reviewed `SystemCallFilter` or Landlock/seccomp profile. +- FreeBSD releases should prefer Capsicum/service-jail isolation where the + selected monitors can operate with pre-opened paths and sockets. +- Do not claim kernel-enforced sandboxing from Jerboa std stubs alone. Archive + the service unit, jail profile, Landlock/seccomp/Capsicum evidence, and a + negative test showing denied access to an out-of-policy path. + +## Release Evidence Required + +- `make release-evidence` from a clean checkout with a pinned `JERBUILD`. +- `dist/release-evidence/reproducibility/result.txt` showing + `prewarm_status=present` and matching repeated binary, FFI symbol, + release-input, and cargo metadata status. +- `dist/release-evidence/soak/status.txt` from a release host showing sustained + agent/collector load. Local Darwin evidence is expected to remain blocked. +- `dist/release-evidence/binary-smoke/status.txt` showing target-platform + startup smoke, or an explicit platform block for non-target local evidence. +- Current RustSec, source scanner, secret scan, and external review notes. + +For release candidates, run target proof capture with fail-closed controls: + +```sh +JSECMONLIB_REQUIRE_TARGET_LOAD_PROOF=1 \ +JSECMONLIB_TARGET_LOAD_PROOF_FILE=/path/to/load-proof.txt \ +JSECMONLIB_REQUIRE_TARGET_SANDBOX_PROOF=1 \ +JSECMONLIB_TARGET_SANDBOX_PROOF_FILE=/path/to/sandbox-proof.txt \ +make soak-evidence + +JSECMONLIB_REQUIRE_TARGET_BINARY_SMOKE_PROOF=1 \ +JSECMONLIB_TARGET_BINARY_SMOKE_PROOF_FILE=/path/to/binary-smoke-proof.txt \ +make binary-smoke +``` + +The load proof must identify the target host, command line, binary hash, start +and stop timestamps, duration, collector identity, and resource-limit outcome. +The sandbox proof must include the service profile, privilege-drop result, and a +negative access test against an out-of-policy path. The binary smoke proof must +come from Linux or FreeBSD and show the no-secret startup validation path for +the exact release binary. new file mode 100644 --- /dev/null +++ b/docs/release-evidence.md @@ -0,0 +1,75 @@ +# Release Evidence + +`jerboa-secmonlib` contains the static Jerboa security-monitor agent, collector, +analyzer, ECIES/PSK transport boundary, and native FFI registration path. It is +not production-ready until a release candidate has current evidence for the +full security gate, static binary linkage, dependency audit, and operational +soak/load behavior. + +Run from a clean checkout: + +```sh +make clean +make release-evidence JERBUILD=/path/to/jerbuild +``` + +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. +- Cargo dependency metadata and RustSec audit output for the patched native + Rust crate. +- SHA-256 hash for `secmon-agent`. +- Static-binary source input hashes, generated FFI symbol list, and dynamic + linkage output from `otool` or `ldd`. +- `sbom/` with a source manifest, source hashes, native Cargo metadata, and + release input hashes. +- `reproducibility/` with a recorded WPO prewarm build, two measured local + binary builds, release-input hashes for policy/evidence scripts, and + `result.txt`. +- `binary-smoke/` with target-platform startup status. On non-target platforms, + this records an explicit platform block instead of implying runtime success. +- `soak/` with agent/collector load and sandbox status. Local default evidence + records blocked statuses until a Linux or FreeBSD release-host soak is run. + +Reproducibility is claimed only when `reproducibility/result.txt` records +`prewarm_status=present`, matching `binary_status`, `ffi_symbols_status`, +`release_inputs_status`, `cargo_metadata_status`, and overall `status`. + +Production readiness still requires target-host runtime evidence. The external +review packet must include this file, `SECURITY.md`, `docs/threat-model.md`, +`docs/deployment-security.md`, `.jerboa/security.json`, the generated evidence +directory, scanner logs, sustained agent/collector load evidence, and any +accepted RustSec, crypto, platform sandbox, or native-linkage risks. + +## Target Proof Controls + +Target-host evidence is recorded through explicit proof files. Set the matching +`JSECMONLIB_REQUIRE_*_PROOF=1` variable in release CI to fail closed when the +proof is absent or incomplete. + +- `JSECMONLIB_TARGET_LOAD_PROOF_FILE` with + `JSECMONLIB_REQUIRE_TARGET_LOAD_PROOF=1` records Linux/FreeBSD + agent/collector soak evidence. The proof must contain + `production_load_status=current-run-recorded`, + `agent_runtime_status=release-host-runtime-recorded`, + `collector_load_status=release-host-sustained-recorded`, and + `production_min_seconds_met=1`. +- `JSECMONLIB_TARGET_SANDBOX_PROOF_FILE` with + `JSECMONLIB_REQUIRE_TARGET_SANDBOX_PROOF=1` records target sandbox and + privilege-drop evidence. The proof must contain + `sandbox_status=target-evidence-recorded`, + `privilege_drop_status=target-evidence-recorded`, and + `negative_access_test_status=target-denied-recorded`. +- `JSECMONLIB_TARGET_BINARY_SMOKE_PROOF_FILE` with + `JSECMONLIB_REQUIRE_TARGET_BINARY_SMOKE_PROOF=1` records target binary + startup evidence. The proof must contain + `target_binary_smoke_status=target-evidence-recorded`, + `agent_no_secret_startup_status=pass`, and + `target_platform_status=linux-or-freebsd-recorded`. + +When proof is valid, the scripts copy it into `dist/release-evidence/soak/` or +`dist/release-evidence/binary-smoke/` and write a `.sha256` sidecar. When proof +is required but missing or incomplete, the status is `blocked-target-proof` and +the evidence target fails. new file mode 100644 --- /dev/null +++ b/docs/threat-model.md @@ -0,0 +1,60 @@ +# Threat Model + +`jerboa-secmonlib` is a host security-monitoring agent plus collector/analyzer +tooling. The production boundary is a monitored Linux or FreeBSD host running +`secmon-agent`, a collector that authenticates with a PSK and decrypts events +with the collector private key, and release tooling that builds a static Jerboa +binary with a fixed native FFI symbol set. + +## Assets + +- Collector private key and PSK. +- Agent public key, PSK, and local event buffer. +- Collected event databases, IOC inputs, and analysis output. +- Static binary build inputs, native Rust crate lockfile, FFI symbol list, and + release evidence. + +## Adversaries + +- Local users or malware on a monitored host trying to blind the agent, read + buffered events, inject fake telemetry, or pivot through the agent listener. +- Network attackers trying to poll events without the PSK or replay frames. +- Malformed host artifacts such as logs, process tables, DNS data, and file + metadata intended to crash parsers or exhaust memory. +- Build-chain attackers attempting to change native FFI symbols, cargo + dependencies, generated artifacts, or release evidence after review. + +## Required Controls + +- The agent never receives the collector private key. It can encrypt events but + must not decrypt historical telemetry. +- Collector connections must complete PSK authentication before event access. +- Frame parsing must enforce size limits and exact reads. +- Static binaries must use the exact FFI symbol list generated from the pinned + Jerboa/native toolchain. +- Production deployments must run under a dedicated service account with the + smallest file and network access compatible with the selected monitors. +- Release candidates must include SBOM, repeated-build reproducibility, + binary-smoke, and soak/load status evidence. + +## Current Non-Production Gaps + +- Production-length agent/collector load evidence is still required on a + Linux/FreeBSD release host. +- Kernel sandbox enforcement is not claimed until Landlock, Capsicum, seccomp, + or an equivalent service manager profile is captured in release evidence. +- Privileged telemetry monitors require target-OS review because some checks + need elevated read access to host security state. +- External review is still required for the crypto boundary, native FFI symbol + registration, parser resource limits, and deployment policy. + +## Release Evidence Markers + +target_load_proof_status: documented +target_sandbox_proof_status: documented +target_binary_smoke_proof_status: documented + +Release automation must fail with `blocked-target-proof` when target +agent/collector load, sandbox/privilege-drop, or target binary startup proof is +required but absent or missing the markers documented in +`docs/release-evidence.md`. --- a/patches/jerboa-native-rs/Cargo.lock +++ b/patches/jerboa-native-rs/Cargo.lock @@ -486,7 +486,6 @@ dependencies = [ "regex", "ring", "rustls", - "rustls-pemfile", "rustls-pki-types", "scrypt", "sha2 0.10.9", @@ -893,15 +892,6 @@ dependencies = [ ] [[package]] -name = "rustls-pemfile" -version = "2.2.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "dce314e5fee3f39953d46bb63bb8a46d40c2f8fb7cc5a3b6cab2bde9721d6e50" -dependencies = [ - "rustls-pki-types", -] - -[[package]] name = "rustls-pki-types" version = "1.14.1" source = "registry+https://github.com/rust-lang/crates.io-index" --- a/patches/jerboa-native-rs/Cargo.toml +++ b/patches/jerboa-native-rs/Cargo.toml @@ -20,7 +20,6 @@ rcgen = { version = "0.13", features = ["ring", "pem"] } time = "0.3" rustls = { version = "0.23", default-features = false, features = ["ring", "logging", "std", "tls12"] } rustls-pki-types = "1" -rustls-pemfile = "2" webpki-roots = "0.26" [target.'cfg(target_os = "linux")'.dependencies] new file mode 100755 --- /dev/null +++ b/scripts/binary-smoke.sh @@ -0,0 +1,122 @@ +#!/usr/bin/env bash +set -euo pipefail + +ROOT=$(cd "$(dirname "${BASH_SOURCE[0]}")/.." && pwd) +cd "$ROOT" + +OUT=${JSECMONLIB_BINARY_SMOKE_DIR:-dist/binary-smoke} +REQUIRE=${JSECMONLIB_REQUIRE_BINARY_SMOKE:-0} +TARGET_BINARY_SMOKE_PROOF=${JSECMONLIB_TARGET_BINARY_SMOKE_PROOF_FILE:-} +REQUIRE_TARGET_BINARY_SMOKE_PROOF=${JSECMONLIB_REQUIRE_TARGET_BINARY_SMOKE_PROOF:-0} +PLATFORM=$(uname -s) + +rm -rf "$OUT" +mkdir -p "$OUT" + +target_binary_smoke_proof_status=not-run + +write_status() { + local status=$1 + local startup_status=$2 + local rc=$3 + local proof_status=$4 + local reason=$5 + + { + echo "repo=jerboa-secmonlib" + echo "platform=$PLATFORM" + echo "status=$status" + echo "agent_no_secret_startup_status=$startup_status" + echo "agent_exit_code=$rc" + echo "target_binary_smoke_proof_status=$proof_status" + echo "reason=$reason" + } > "$OUT/status.txt" +} + +has_marker() { + local file=$1 + local marker=$2 + + grep -F -q "$marker" "$file" +} + +require_marker() { + local file=$1 + local marker=$2 + + if ! has_marker "$file" "$marker"; then + printf '[secmonlib-binary-smoke] target binary smoke proof is missing marker: %s\n' "$marker" >&2 + return 1 + fi +} + +record_proof() { + local proof=$1 + local destination=$2 + + cp "$proof" "$destination" + shasum -a 256 "$destination" > "$destination.sha256" +} + +proof_requested=0 +if [ -n "$TARGET_BINARY_SMOKE_PROOF" ] || [ "$REQUIRE_TARGET_BINARY_SMOKE_PROOF" = "1" ]; then + proof_requested=1 +fi + +if [ "$proof_requested" = "1" ]; then + if [ -z "$TARGET_BINARY_SMOKE_PROOF" ] || [ ! -s "$TARGET_BINARY_SMOKE_PROOF" ]; then + write_status \ + "blocked-target-proof" \ + "blocked-target-proof" \ + "not-run" \ + "missing" \ + "set JSECMONLIB_TARGET_BINARY_SMOKE_PROOF_FILE to a non-empty target-host binary smoke proof" + exit 1 + fi + if ! require_marker "$TARGET_BINARY_SMOKE_PROOF" "target_binary_smoke_status=target-evidence-recorded" || + ! require_marker "$TARGET_BINARY_SMOKE_PROOF" "agent_no_secret_startup_status=pass" || + ! require_marker "$TARGET_BINARY_SMOKE_PROOF" "target_platform_status=linux-or-freebsd-recorded"; then + write_status \ + "blocked-target-proof" \ + "blocked-target-proof" \ + "not-run" \ + "incomplete" \ + "target binary smoke proof is present but missing required target-platform markers" + exit 1 + fi + record_proof "$TARGET_BINARY_SMOKE_PROOF" "$OUT/target-binary-smoke-proof.txt" + target_binary_smoke_proof_status=present + write_status \ + "target-proof-recorded-review-required" \ + "target-proof-recorded-review-required" \ + "not-run" \