Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
5 changes: 4 additions & 1 deletion .github/workflows/build.yml
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,9 @@ on:
- main
workflow_dispatch:

permissions:
contents: read

jobs:
espressif-targets:
name: Espressif target ${{ matrix.device.soc }}
Expand Down Expand Up @@ -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
Expand Down
6 changes: 5 additions & 1 deletion .github/workflows/release.yml
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand All @@ -55,6 +57,8 @@ jobs:
build-firmware:
name: Build ${{ matrix.target.soc }}
runs-on: ubuntu-latest
permissions:
contents: read
strategy:
fail-fast: false
matrix:
Expand Down
3 changes: 3 additions & 0 deletions .github/workflows/tests.yml
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,9 @@ on:
- main
workflow_dispatch:

permissions:
contents: read

jobs:
packer:
name: OTA tests
Expand Down
6 changes: 6 additions & 0 deletions .pre-commit-config.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -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
9 changes: 9 additions & 0 deletions Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -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"
Expand Down
154 changes: 154 additions & 0 deletions SECURITY.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,154 @@
<!--
SPDX-FileCopyrightText: 2026 Roman Valls Guimera <brainstorm@nopcode.org>
SPDX-License-Identifier: GPL-3.0-or-later
-->

# 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.
3 changes: 3 additions & 0 deletions ota/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -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"
3 changes: 3 additions & 0 deletions ssh-stamp-esp32/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -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
3 changes: 3 additions & 0 deletions ssh-stamp-hal/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -19,3 +19,6 @@ heapless = { workspace = true }
[features]
default = []
sftp-ota = []

[lints]
workspace = true
Loading