Skip to content

chore(deps): modernize YAML parser dependencies #2018

Description

@elezar

chore(deps): modernize YAML parser dependencies

Problem Statement

OpenShell currently depends directly on serde_yml, whose latest release is marked deprecated and points users to noyalib. The workspace also still has transitive serde_yaml / unsafe-libyaml exposure through regorus's YAML feature and the current kube client stack. We should modernize YAML parsing dependencies without changing policy semantics, provider profile behavior, or fail-closed sandbox policy loading.

Technical Context

OpenShell uses YAML in several independent areas: sandbox policies, supervisor OPA data loading, provider profiles, router config, CLI YAML output, and prover input files. The main security-sensitive flow parses policy YAML into typed data or JSON before it reaches regorus; OpenShell does not rely on regorus YAML builtins for normal policy evaluation.

serde_yml is not an ideal long-term dependency because the latest published release is explicitly marked deprecated/unmaintained and is only a compatibility shim over noyalib. Keeping serde_yml as the public dependency name would leave OpenShell anchored to an abandoned crate API even if the implementation is forwarded underneath. If noyalib is selected, OpenShell should prefer depending on it directly or through a deliberate short-term Cargo rename; if kube 4 is selected, serde-saphyr may be preferable because it avoids carrying a second YAML parser.

Two changes look small and directly relevant: replace the direct serde_yml dependency with a maintained parser, and remove the unused yaml feature from regorus. serde-saphyr should be evaluated as a first-class replacement candidate, especially if the kube stack is upgraded to kube 4 because kube-client then already brings serde-saphyr into the dependency graph. A kube-client upgrade also removes a serde_yaml path, but that is a separate major dependency migration with source API changes.

Affected Components

Component Key Files Role
Workspace dependencies Cargo.toml Declares direct serde_yml, kube, kube-runtime, and k8s-openapi versions.
Policy parser crates/openshell-policy/src/lib.rs Parses sandbox policy YAML into proto and serializes proto back to YAML.
Supervisor policy engine crates/openshell-supervisor-network/src/opa.rs Parses YAML policy data into JSON, preprocesses it, and feeds JSON into regorus.
Provider profiles crates/openshell-providers/src/profiles.rs Parses and exports provider profile YAML.
CLI output crates/openshell-cli/src/output.rs Emits YAML output for list/detail commands.
Router config crates/openshell-router/src/config.rs Loads router YAML config files.
Prover inputs crates/openshell-prover/src/*.rs Parses policy, credentials, registry, and accepted-risk YAML files.
Kubernetes driver/server crates/openshell-driver-kubernetes/, crates/openshell-server/ Uses kube 0.90, whose kube-client depends on deprecated serde_yaml.

Technical Investigation

Architecture Overview

openshell-policy is the canonical policy YAML boundary. It parses YAML strings through serde_yml::from_str and serializes through serde_yml::to_string. The sandbox then loads policy YAML from /etc/openshell/policy.yaml or the legacy path, validates it, and falls back to restrictive defaults on invalid policy.

The supervisor network policy path uses regorus as the embedded Rego evaluator, but OpenShell preprocesses YAML itself. OpaEngine::from_files and OpaEngine::from_strings parse YAML policy data into serde_json::Value, normalize and validate it, then call regorus::Engine::add_data_json. The proto runtime path skips YAML entirely by converting typed policy data to JSON before loading regorus.

Provider profiles, router config, CLI output, and prover inputs are separate YAML call sites. They mostly use from_str, to_string, serde_yml::Error, and serde_yml::Value.

Code References

Location Description
Cargo.toml:73 Workspace direct dependency is serde_yml = "0.0.12".
Cargo.toml:111 Workspace uses kube = "0.90" and kube-runtime = "0.90".
crates/openshell-supervisor-network/Cargo.toml:28 Enables regorus feature set ["std", "arc", "glob", "yaml"]; yaml brings in serde_yaml.
crates/openshell-policy/src/lib.rs:545 Parses sandbox policy YAML through serde_yml::from_str.
crates/openshell-policy/src/lib.rs:557 Serializes sandbox policy YAML through serde_yml::to_string.
crates/openshell-supervisor-network/src/opa.rs:140 Loads Rego policy files and YAML data files.
crates/openshell-supervisor-network/src/opa.rs:194 Runtime proto path converts policy to JSON and loads regorus with add_data_json.
crates/openshell-supervisor-network/src/opa.rs:720 Parses YAML policy data into serde_json::Value before preprocessing.
crates/openshell-providers/src/profiles.rs:39 Provider profile parse error is currently typed as serde_yml::Error.
crates/openshell-providers/src/profiles.rs:931 Provider profile YAML parse/export calls.
crates/openshell-router/src/config.rs:88 Router config YAML parsing.
crates/openshell-prover/src/policy.rs:63 Prover uses serde_yml::Value for ignored policy sections.
crates/openshell-cli/src/output.rs:71 CLI YAML output uses serde_yml::to_string.

Current Dependency Paths

cargo tree -i serde_yml on main shows direct use by:

  • openshell-cli
  • openshell-policy
  • openshell-prover
  • openshell-providers
  • openshell-router
  • openshell-supervisor-network

cargo tree -i serde_yaml on main shows deprecated serde_yaml v0.9.34+deprecated through:

  • kube-client v0.90.0, via kube and kube-runtime
  • regorus v0.9.1, via the enabled yaml feature

unsafe-libyaml v0.2.11 is only under serde_yaml.

Feasibility

Replacing direct serde_yml is medium-low complexity if using noyalib in compatibility mode. A temporary compile check passed with this workspace dependency shape:

serde_yml = { package = "noyalib", version = "0.0.8", default-features = false, features = ["std", "compat-serde-yaml"] }

The check covered the direct users:

mise exec -- cargo check -p openshell-policy -p openshell-prover -p openshell-providers -p openshell-router -p openshell-supervisor-network -p openshell-cli

Removing the regorus yaml feature is low complexity. No OpenShell Rego policy or Rust call site uses yaml.* builtins, and OpenShell already feeds JSON into regorus. A temporary compile check of openshell-supervisor-network passed after removing only that feature.

Using serde-saphyr as the direct replacement may be a better long-term outcome if kube 4 is adopted. That would avoid carrying both serde-saphyr through kube and noyalib for OpenShell's own YAML parsing. It likely requires more code churn than the noyalib compatibility alias because OpenShell currently references serde_yml::Error and serde_yml::Value, so it needs a behavior and API compatibility pass before selection.

A temporary compatibility check showed that serde-saphyr can replace the direct serde_yml package with small source changes. The experiment used this workspace dependency alias:

serde_yml = { package = "serde-saphyr", version = "0.0.28" }

The required source changes were:

  • Replace the prover's ignored serde_yml::Value fields with serde::de::IgnoredAny for landlock and process, avoiding a parser-specific dynamic value type.
  • Add a provider-profile YAML serialization error variant for serde_yml::ser::Error, because serde-saphyr has distinct deserialize and serialize error types.
  • Serialize provider-profile slices as serde_yml::to_string(&profiles) because serde-saphyr::to_string requires a sized type parameter.

Focused checks passed in the temp worktree:

mise exec -- cargo check -p openshell-policy -p openshell-prover -p openshell-providers -p openshell-router -p openshell-supervisor-network -p openshell-cli
mise exec -- cargo test -p openshell-policy -p openshell-providers -p openshell-router -p openshell-prover
mise exec -- cargo test -p openshell-supervisor-network opa
mise exec -- cargo test -p openshell-cli output

After the alias, cargo tree -i serde_yml and cargo tree -i libyml no longer matched any packages, and cargo tree -i serde-saphyr showed the existing direct YAML users now routed through serde-saphyr. Deprecated serde_yaml still remained through kube-client 0.90 and regorus's yaml feature, so those remain separate cleanup steps.

Upgrading kube should be split into a separate issue. A temporary kube 4 check showed it removes the kube-client serde_yaml path and uses serde-saphyr, but requires coordinated dependency and source changes: k8s-openapi must move with kube, watcher event variants changed, and Kubernetes timestamp types now use jiff.

Alternative Approaches Considered

noyalib

Best near-term candidate. It is the migration path named by the deprecated serde_yml release, is pure Rust, and can be used with a Cargo package rename to keep current serde_yml:: call sites initially.

serde-saphyr

Pure-Rust Serde YAML library and used by newer kube-client. This is the strongest consolidation candidate if OpenShell upgrades to kube 4 because it avoids introducing another parser while kube already depends on serde-saphyr. A temporary compile and focused-test pass shows it is feasible with small code changes, but it still needs final review of YAML edge-case behavior, output formatting, and whether the temporary Cargo alias should become a direct serde_saphyr:: migration or an internal YAML adapter.

saneyaml

Pure-Rust, Serde-compatible, and config-focused. It may fit the domain, but it is young and should be tested against OpenShell policy/profile fixtures before adoption.

yaml_serde

Maintained by the YAML Organization with a familiar API, but it depends on libyaml-rs. This may address unmaintained-crate concerns, but not libyaml-style parser concerns.

serde_norway, serde_yaml_ng, serde_yaml_neo

Not preferred for this concern. They still rely on unsafe-libyaml variants.

Proposed Approach

Treat direct YAML parser replacement and regorus feature cleanup as one dependency-modernization issue. Evaluate both noyalib compatibility mode and serde-saphyr against the existing YAML round-trip, provider profile, router config, prover, and supervisor policy tests. Prefer serde-saphyr if kube 4 is accepted and behavior parity is good, because it would standardize OpenShell on the same maintained parser already used by kube-client; otherwise use noyalib compatibility mode to minimize code churn. Remove regorus's unused yaml feature in the same change if tests confirm no Rego YAML builtins are required.

Handle the kube-client serde_yaml path in a separate kube upgrade issue because that migration changes kube APIs and Kubernetes type versions.

Scope Assessment

  • Complexity: Medium
  • Confidence: Medium-high for direct serde_yml replacement and regorus feature cleanup; medium for behavior parity until fixture tests are run.
  • Estimated files to change: 2-8 for the direct dependency cleanup, depending on whether the implementation uses a Cargo rename or explicit module rename.
  • Issue type: chore

Risks & Open Questions

  • Should OpenShell prioritize compatibility mode with noyalib, or standardize on serde-saphyr if kube 4 brings it into the dependency graph?
  • If serde-saphyr is chosen, should OpenShell rename call sites directly or introduce a small internal YAML adapter module to isolate parser APIs?
  • Does OpenShell need to preserve exact YAML output formatting, ordering, quoting, and error messages for CLI/provider/profile workflows?
  • What behavior should be expected for duplicate keys, anchors/aliases, merge keys, tags, non-string map keys, numeric coercion, and multi-document YAML?
  • Invalid sandbox policy YAML must continue to fail closed to restrictive defaults.
  • If the parser changes error text, tests that assert specific error messages may need to be updated carefully.
  • Kube should remain out of scope unless the issue is expanded into a dependency epic.

Test Considerations

  • Run focused cargo checks for all direct parser users:
mise exec -- cargo check -p openshell-policy -p openshell-prover -p openshell-providers -p openshell-router -p openshell-supervisor-network -p openshell-cli
  • Run policy parser tests in crates/openshell-policy/src/lib.rs, especially YAML parse and round-trip coverage.
  • Run supervisor-network OPA policy preprocessing tests in crates/openshell-supervisor-network/src/opa.rs.
  • Run provider profile YAML import/export tests in crates/openshell-providers/src/profiles.rs and CLI provider integration tests that assert YAML output.
  • Run router config parse-error tests in crates/openshell-router/src/config.rs.
  • Run sandbox policy-load tests that confirm invalid YAML falls back safely.
  • Add targeted parser behavior tests if the selected replacement differs for duplicate keys, anchors, merge keys, or multi-document YAML.

Documentation Impact

Architecture docs describe policy YAML and runtime flow, but not parser implementation. A compatible dependency swap probably does not require architecture or published docs changes. Update docs/reference/policy-schema.mdx only if accepted YAML syntax or serialization semantics change.

docs/reference/gateway-config.mdx is not affected unless a later kube upgrade changes gateway TOML fields, driver-specific config options, config defaults, or Helm rendering of gateway.toml.

LSM Impact

No Linux Security Module impact is expected for parser dependency changes. The existing OPA proto path includes /proc/<pid>/root symlink resolution and remains LSM-sensitive, but this spike does not change that behavior.


Created by spike investigation. Use build-from-issue to plan and implement after human review.

Metadata

Metadata

Assignees

No one assigned

    Type

    No type
    No fields configured for issues without a type.

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions