mirror of
https://github.com/ruvnet/RuView
synced 2026-07-24 17:43:20 +00:00
5287497a4a
* fix(homecore-migrate): redact secret value from malformed secrets.yaml error (secret-leak)
`read_secrets` wrapped serde_yaml's parse error into `MigrateError::YamlParse {
source }`. serde_yaml's message for a typed-tag coercion failure embeds the
offending scalar verbatim, e.g. `invalid value: string "<the-secret-value>"`.
That error propagates out of `read_secrets`, is `?`-returned by the
`InspectSecrets` CLI path in main.rs, and printed to stderr by anyhow — leaking
a secret value despite the CLI's deliberate `<redacted>` design.
Fix: secrets.yaml parse failures now map to a new redacting variant
`MigrateError::SecretsParse { path, line, column }` that carries only the file
path and a coarse location (from `serde_yaml::Error::location()`), never the
scalar content. Other (non-secret) YAML files keep `YamlParse`.
Pinned by `secrets::tests::malformed_secrets_error_never_contains_secret_value`
(asserts the rendered error AND its full #[source] chain never contain the
secret value; fails on the old `YamlParse` path) plus
`malformed_secrets_error_reports_location` (still fail-closed + locatable).
ADR-165 secret-handling rule: a secret value must never appear in output.
Co-Authored-By: claude-flow <ruv@ruv.net>
* docs(homecore-migrate): record secret-leak fix in ADR-165 + CHANGELOG
Note the secrets.yaml error-redaction fix and the review's clean dimensions
(read-only source / no traversal / no panic / fail-closed versioning / no
injection) in ADR-165 §2.4, bump the test-evidence count 19→21 in §2.6, and add
an [Unreleased] Security entry to CHANGELOG.
Co-Authored-By: claude-flow <ruv@ruv.net>
97 lines
3.7 KiB
Rust
97 lines
3.7 KiB
Rust
//! homecore-migrate — Migration tooling from Python Home Assistant.
|
|
//!
|
|
//! Implements [ADR-165](../../docs/adr/ADR-165-homecore-migrate-from-home-assistant.md)
|
|
//! (HOMECORE-MIGRATE; ADR-126 §4 series map labels the role "ADR-134 HOMECORE-MIGRATE",
|
|
//! but on-disk ADR-134 is CIR — the migrate decision was renumbered to ADR-165. See ADR-164).
|
|
//!
|
|
//! ## P1 scope
|
|
//!
|
|
//! - [`storage`] — `HaStorageDir`, `HaStorageEnvelope`; `read_envelope(path)`
|
|
//! - [`storage_format`] — versioned format parsers (`v13`); unknown minor_version → hard error
|
|
//! - [`entity_registry`] — `core.entity_registry` → `Vec<homecore::EntityEntry>`
|
|
//! - [`device_registry`] — `core.device_registry` → `Vec<DeviceImport>` (P1 stub)
|
|
//! - [`config_entries`] — `core.config_entries` diagnostic (count + domain list; P2 converts)
|
|
//! - [`secrets`] — `secrets.yaml` → `HashMap<String, String>`
|
|
//! - [`automations`] — `automations.yaml` count + ID list (P2 converts)
|
|
//! - [`cli`] — `clap`-derived subcommand types shared between `src/main.rs` and tests
|
|
//!
|
|
//! ## What is NOT here yet (deferred to P2+)
|
|
//!
|
|
//! - Conversion of `config_entries` to HOMECORE plugin manifests
|
|
//! - Conversion of `automations.yaml` to `homecore-automation` YAML
|
|
//! - Side-by-side runtime mode (requires `homecore-recorder`, ADR-132)
|
|
//! - `!secret` reference resolution in non-secrets YAML files
|
|
|
|
pub mod automations;
|
|
pub mod cli;
|
|
pub mod config_entries;
|
|
pub mod device_registry;
|
|
pub mod entity_registry;
|
|
pub mod secrets;
|
|
pub mod storage;
|
|
pub mod storage_format;
|
|
|
|
/// Crate-level error type. Each module exposes `MigrateError` variants.
|
|
#[derive(Debug, thiserror::Error)]
|
|
pub enum MigrateError {
|
|
#[error("I/O error reading {path}: {source}")]
|
|
Io {
|
|
path: String,
|
|
#[source]
|
|
source: std::io::Error,
|
|
},
|
|
|
|
#[error("JSON parse error in {path}: {source}")]
|
|
JsonParse {
|
|
path: String,
|
|
#[source]
|
|
source: serde_json::Error,
|
|
},
|
|
|
|
#[error("YAML parse error in {path}: {source}")]
|
|
YamlParse {
|
|
path: String,
|
|
#[source]
|
|
source: serde_yaml::Error,
|
|
},
|
|
|
|
/// Parse failure in a SECRET-bearing file (`secrets.yaml`).
|
|
///
|
|
/// Unlike [`MigrateError::YamlParse`], this variant deliberately does NOT
|
|
/// embed the underlying `serde_yaml::Error` message — that message can quote
|
|
/// the offending scalar verbatim (e.g. a typed-tag coercion error renders
|
|
/// `invalid value: string "<the-secret-value>"`), which would leak a secret
|
|
/// into stderr/logs. We carry only the file path plus a coarse line/column
|
|
/// so the user can locate the problem without the value being printed.
|
|
/// (ADR-165 secret-handling rule: a secret value must never appear in output.)
|
|
#[error(
|
|
"secrets.yaml parse error in {path} (line {line}, column {column}): \
|
|
malformed YAML (value content redacted)"
|
|
)]
|
|
SecretsParse {
|
|
path: String,
|
|
line: usize,
|
|
column: usize,
|
|
},
|
|
|
|
/// Fired when the outer `{version, minor_version}` envelope version is
|
|
/// known but the `minor_version` is not supported by any compiled parser.
|
|
/// Per ADR-165 §6 Q5: hard error on unknown minor_version.
|
|
#[error(
|
|
"unsupported schema version in {file}: \
|
|
version={version} minor_version={minor_version}. \
|
|
Upgrade homecore-migrate or downgrade HA to a supported release."
|
|
)]
|
|
UnsupportedSchemaVersion {
|
|
file: String,
|
|
version: u32,
|
|
minor_version: u32,
|
|
},
|
|
|
|
#[error("missing required field '{field}' in {context}")]
|
|
MissingField { field: String, context: String },
|
|
|
|
#[error("entity_id parse error: {0}")]
|
|
EntityId(#[from] homecore::EntityIdError),
|
|
}
|