Security audit finding EXP2-SEC-03. `path_validation.rs` guarded against traversal, UNC prefixes, drive letters and symlink escapes, but unlike the catalog validators in lanspread-db it did not reject Windows device names (CON, NUL, COM1..9, LPT1..9), components with a trailing dot or space, reserved characters (`<>:"|?*`), or control characters. On Windows, opening `NUL.txt` talks to a device and `file.txt.` is silently rewritten to `file.txt`, so such names must never reach the filesystem. The catalog component validator is now exported from lanspread-db as `validate_portable_component` and applied to every normal component in `validate_relative_path`. The only current caller is Stream Install's staging-path resolution, whose inputs are already canonical catalog paths, so this changes nothing for valid archives; it removes a divergence between two validators that are supposed to agree. Test plan: `just test` (new cases cover device names in any position, trailing dot/space, a reserved character and a control character, and confirm `console.txt` and `com10.txt` stay valid). Claude-Session: https://claude.ai/code/session_017C3Nbgwpdm3YNwZhhFLHwg
52 lines
1.5 KiB
Rust
52 lines
1.5 KiB
Rust
//! Trusted catalog content manifests.
|
|
//!
|
|
//! Manifest JSON is a reproducible transport for catalog-publisher output. The
|
|
//! content identity is derived from the versioned binary transcript in
|
|
//! [`encoding`], never from JSON formatting. Sealed manifests deliberately do
|
|
//! not implement [`serde::Deserialize`]; untrusted bytes must pass through the
|
|
//! bounded canonical loader [`CatalogContentManifest::from_json_slice`].
|
|
|
|
#![allow(clippy::missing_errors_doc)]
|
|
|
|
mod bundle;
|
|
mod digest;
|
|
mod encoding;
|
|
mod index;
|
|
mod model;
|
|
mod path;
|
|
mod store;
|
|
|
|
pub use bundle::CatalogBundle;
|
|
pub use digest::{Blake3Digest, ContentId};
|
|
pub use index::{
|
|
CATALOG_CONTENT_INDEX_NAME,
|
|
CATALOG_CONTENT_INDEX_SCHEMA_VERSION,
|
|
CatalogContentIdentity,
|
|
CatalogContentIndex,
|
|
CatalogContentIndexEntry,
|
|
MAX_CATALOG_CONTENT_INDEX_BYTES,
|
|
};
|
|
pub use model::{
|
|
CATALOG_CHUNK_SIZE,
|
|
CATALOG_CONTENT_MANIFEST_SCHEMA_VERSION,
|
|
CatalogContentManifest,
|
|
CatalogContentManifestBody,
|
|
CatalogEntryKind,
|
|
CatalogExtractedEntry,
|
|
CatalogFileEntry,
|
|
MAX_CATALOG_COMPONENT_BYTES,
|
|
MAX_CATALOG_ENTRIES,
|
|
MAX_CATALOG_FILE_BYTES,
|
|
MAX_CATALOG_MANIFEST_BYTES,
|
|
MAX_CATALOG_PATH_BYTES,
|
|
MAX_CATALOG_TOTAL_BYTES,
|
|
};
|
|
pub use path::{CanonicalCatalogPath, validate_portable_component};
|
|
pub use store::{
|
|
CATALOG_PUBLICATION_MARKER_NAME,
|
|
CatalogManifestStore,
|
|
reject_incomplete_catalog_publication,
|
|
write_canonical_content_index_atomic,
|
|
write_canonical_manifest_atomic,
|
|
};
|