diff --git a/.github/workflows/build.yml b/.github/workflows/build.yml index 0dfb6cb..4e13b1f 100644 --- a/.github/workflows/build.yml +++ b/.github/workflows/build.yml @@ -13,6 +13,9 @@ on: - main workflow_dispatch: +permissions: + contents: read + jobs: espressif-targets: name: Espressif target ${{ matrix.device.soc }} @@ -56,7 +59,7 @@ jobs: - name: Check lints and format if: ${{ contains(fromJson('["esp32c6"]'), matrix.device.soc) }} run: | - cargo +${{ matrix.device.toolchain }} clippy --release --features ${{ matrix.device.soc }} --target riscv32imac-unknown-none-elf -p ssh-stamp-esp32 --bin ssh-stamp-esp32 --no-default-features -- -W clippy::mem_forget -W clippy::await_holding_lock -W clippy::large_futures -W clippy::pedantic -D warnings + cargo +${{ matrix.device.toolchain }} clippy --release --features ${{ matrix.device.soc }} --target riscv32imac-unknown-none-elf -p ssh-stamp-esp32 --bin ssh-stamp-esp32 --no-default-features -- -D warnings cargo +${{ matrix.device.toolchain }} fmt -- --check packer: name: OTA Packer diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml index 89eae11..d37bfb0 100644 --- a/.github/workflows/release.yml +++ b/.github/workflows/release.yml @@ -32,12 +32,14 @@ on: required: true permissions: - contents: write + contents: read jobs: generate-sbom: name: Generate SBOM runs-on: ubuntu-latest + permissions: + contents: read steps: - name: Checkout repository uses: actions/checkout@v6 @@ -55,6 +57,8 @@ jobs: build-firmware: name: Build ${{ matrix.target.soc }} runs-on: ubuntu-latest + permissions: + contents: read strategy: fail-fast: false matrix: diff --git a/.github/workflows/tests.yml b/.github/workflows/tests.yml index 1c7a25d..aaf1da0 100644 --- a/.github/workflows/tests.yml +++ b/.github/workflows/tests.yml @@ -13,6 +13,9 @@ on: - main workflow_dispatch: +permissions: + contents: read + jobs: packer: name: OTA tests diff --git a/.pre-commit-config.yaml b/.pre-commit-config.yaml index 0becd99..9acf932 100644 --- a/.pre-commit-config.yaml +++ b/.pre-commit-config.yaml @@ -11,3 +11,9 @@ repos: entry: cargo fmt --all -- --check --color always language: system pass_filenames: false + - id: clippy + name: clippy + description: Run clippy with project lint rules + entry: cargo clippy --workspace -- -D warnings + language: system + pass_filenames: false diff --git a/Cargo.toml b/Cargo.toml index 94dce4a..a6fe1ec 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -18,6 +18,15 @@ license = "GPL-3.0-or-later" [workspace] members = ["ssh-stamp-hal", "ssh-stamp-esp32", "ota"] +[workspace.lints.clippy] +mem_forget = "warn" +await_holding_lock = "warn" +large_futures = "warn" +pedantic = "warn" + +[lints] +workspace = true + [workspace.dependencies] # Async runtime embassy-sync = "0.8" diff --git a/SECURITY.md b/SECURITY.md new file mode 100644 index 0000000..39cb653 --- /dev/null +++ b/SECURITY.md @@ -0,0 +1,154 @@ + + +# Security Policy + +This policy is designed to comply with the EU Cyber Resilience Act (CRA, +Regulation 2024/2847) and the NIS2 Directive (Directive 2022/2555), in +anticipation of ssh-stamp being embedded in commercial products placed on +the EU market. + +## Product Information + +- **Product name:** ssh-stamp +- **Developer:** Roman Valls Guimera — brainstorm@nopcode.org +- **License:** GPL-3.0-or-later +- **Product category:** Embedded firmware (SSH server for microcontrollers) +- **Intended use:** Network-attached SSH-to-UART bridge for IoT/embedded + devices + +## Coordinated Vulnerability Disclosure + +### Reporting a vulnerability + +Report security vulnerabilities by emailing **brainstorm@nopcode.org**. + +Do **not** open a public GitHub issue for security-sensitive bugs. + +### What to include + +- A description of the vulnerability and its potential impact. +- Steps to reproduce or a proof-of-concept. +- Any affected versions or hardware targets you have tested. +- CVSS v3.1 base score or severity estimate, if available. +- Suggested fix or mitigation, if you have one. + +### Response timeline + +| Phase | SLA | +|------------------------------------|-----------------------------| +| Acknowledgement of report | 3 business days | +| Initial triage & severity assess. | 7 business days | +| Critical-severity fix shipped | 14 calendar days | +| High-severity fix shipped | 30 calendar days | +| Medium/Low-severity fix shipped | Next scheduled release | +| Public disclosure (coordinated) | 90 calendar days max | + +Severity is assessed using CVSS v3.1. "Shipped" means a tagged release +on the `main` branch with the fix merged and binary artifacts published. + +### Disclosure policy + +We follow **coordinated vulnerability disclosure**. Reporters are asked +to allow the full SLA window before public disclosure. If a fix has not +been shipped within the SLA, the reporter may disclose publicly. We +commit to keeping reporters informed of progress throughout. + +We will **not** pursue legal action against researchers who act in good +faith and follow this policy. + +## Incident Response and Regulatory Reporting + +### CRA-compliant incident handling + +In accordance with CRA Art. 13(3) and Art. 14, when an actively +exploited vulnerability is discovered in a released version of ssh-stamp: + +1. **Triage:** Determine if the vulnerability is being actively + exploited or poses a serious risk. +2. **Patch:** Develop and release a fix per the SLA above. +3. **Notify downstream manufacturers:** If ssh-stamp is embedded in a + commercial product, the integrating manufacturer is responsible for + propagating the fix to end users and, where required, filing + notifications with the relevant national CSIRT per NIS2 Art. 23. +4. **SBOM update:** Publish an updated SBOM reflecting any dependency + changes introduced by the fix. + +### NIS2 obligations + +ssh-stamp itself is not an operator of essential/important services under +NIS2. However, manufacturers integrating ssh-stamp into products that fall +under NIS2 must: + +- Report significant incidents to their national CSIRT within **24 + hours** (early warning) and **72 hours** (incident notification), per + NIS2 Art. 23. +- Maintain documentation of the vulnerability handling measures applied, + per NIS2 Art. 21(2)(d). + +This policy and its artifact trail (advisories, commits, SBOMs) are +intended to satisfy the upstream portion of those obligations. + +## Supported Versions and Product Lifetime + +| Version line | Security fixes | End of security support | +|--------------|-----------------------|--------------------------| +| Latest release on `main` | Full support | 5 years from initial release, or until a successor version is designated, whichever is later | +| Prior major version | Best-effort | 1 year after successor release | + +The **expected product lifetime** for CRA Art. 10(2)(f) purposes is **5 +years** from the date of first release of each major version. + +## Software Bill of Materials (SBOM) + +In accordance with CRA Art. 13(2), an SBOM is generated for each release +and published alongside binary artifacts. The SBOM is produced in +SPDX format (`sbom.spdx.json`) and covers all direct and transitive Rust +dependencies. SPDX is the format supported by `sbom-cve-check` for +continuous vulnerability monitoring. + +Integrating manufacturers may use the SBOM to comply with CRA Art. +13(2)(c) (vulnerability monitoring of components). + +## Security Risk Assessment + +ssh-stamp follows a risk assessment consistent with NIS2 Art. 21(2)(d). +The key risk categories and mitigations are: + +| Risk category | Threat | Mitigation | +|---------------|--------|------------| +| Authentication | Brute-force or credential compromise | SSH key-only auth via `sunset`; no password auth | +| Network exposure | Unauthorized remote access | WiFi AP / Ethernet port is the only attack surface; no open inbound ports beyond SSH (port 22) | +| Firmware integrity | Malicious OTA update | OTA images are validated before flashing (see `ota/README.md`) | +| Supply chain | Vulnerable upstream dependency | SBOM published per release; `cargo audit` in CI | +| Data confidentiality | UART traffic interception | SSH encryption protects UART-to-SSH bridge traffic | +| Availability | DoS via network | No explicit rate-limiting; relies on MCU resource constraints as a natural throttle | + +This assessment is reviewed at each major release. + +## Audit Trail + +All vulnerability reports, triage decisions, and fix commits are recorded +in: + +- The project's **GitHub Security Advisories** (private until disclosure). +- **Git history** — fix commits reference the advisory and CVE, if + assigned. +- Release notes document all security-relevant changes. + +This log satisfies CRA Art. 13(3) documentation requirements and +supports NIS2 Art. 21(2)(d) evidence of risk management measures. + +## Scope + +This policy covers the ssh-stamp codebase, including the core library +(`ssh-stamp`), the HAL trait crate (`ssh-stamp-hal`), and all platform +port crates (`ssh-stamp-esp32`, etc...). + +For vulnerabilities in third-party dependencies (e.g. `sunset`, +`embassy-net`, `esp-hal`), we monitor through `cargo audit` and will +issue advisories when affected versions are in use, per CRA Art. 13(2)(c). +Upstream vulnerabilities should also be reported directly to their +respective maintainers. diff --git a/ota/Cargo.toml b/ota/Cargo.toml index 3b62de0..1d99c17 100644 --- a/ota/Cargo.toml +++ b/ota/Cargo.toml @@ -25,6 +25,9 @@ rustc-hash.workspace = true [target.'cfg(not(target_os = "none"))'.dependencies] clap = "4.5" +[lints] +workspace = true + [[bin]] name = "packer" path = "src/bin/packer.rs" diff --git a/ssh-stamp-esp32/Cargo.toml b/ssh-stamp-esp32/Cargo.toml index 4e107df..d506f30 100644 --- a/ssh-stamp-esp32/Cargo.toml +++ b/ssh-stamp-esp32/Cargo.toml @@ -60,3 +60,6 @@ esp32c3 = ["esp-hal/esp32c3", "esp-radio/esp32c3", "esp-storage/esp32c3", "esp-b esp32c6 = ["esp-hal/esp32c6", "esp-radio/esp32c6", "esp-storage/esp32c6", "esp-bootloader-esp-idf/esp32c6", "esp-alloc/esp32c6", "esp-backtrace/esp32c6", "esp-rtos/esp32c6", "esp-println/esp32c6"] esp32s2 = ["esp-hal/esp32s2", "esp-radio/esp32s2", "esp-storage/esp32s2", "esp-bootloader-esp-idf/esp32s2", "esp-alloc/esp32s2", "esp-backtrace/esp32s2", "esp-rtos/esp32s2", "esp-println/esp32s2"] esp32s3 = ["esp-hal/esp32s3", "esp-radio/esp32s3", "esp-storage/esp32s3", "esp-bootloader-esp-idf/esp32s3", "esp-alloc/esp32s3", "esp-backtrace/esp32s3", "esp-rtos/esp32s3", "esp-println/esp32s3"] + +[lints] +workspace = true diff --git a/ssh-stamp-hal/Cargo.toml b/ssh-stamp-hal/Cargo.toml index ee416ae..053572f 100644 --- a/ssh-stamp-hal/Cargo.toml +++ b/ssh-stamp-hal/Cargo.toml @@ -19,3 +19,6 @@ heapless = { workspace = true } [features] default = [] sftp-ota = [] + +[lints] +workspace = true