File: Secrets\SealedSecretManifest.cs
Web Access
Project: src\src\Aspire.Hosting.Radius\Aspire.Hosting.Radius.csproj (Aspire.Hosting.Radius)
// Licensed to the .NET Foundation under one or more agreements.
// The .NET Foundation licenses this file to you under the MIT license.
 
using System.Buffers.Text;
using System.Text;
using System.Text.Json;
using YamlDotNet.Core;
using YamlDotNet.Core.Events;
using YamlDotNet.RepresentationModel;
 
namespace Aspire.Hosting.Radius.Secrets;
 
/// <summary>
/// Reads a committed, encrypted Bitnami <c>SealedSecret</c> manifest and returns both the
/// identifying metadata and the exact bytes that were validated.
/// </summary>
internal readonly record struct ValidatedManifest(
    SealedSecretManifest.Metadata Metadata,
    string SourcePath,
    ReadOnlyMemory<byte> Content);
 
/// <summary>
/// Minimal reader for a committed, encrypted Bitnami <c>SealedSecret</c> manifest. Extracts
/// the <c>metadata.name</c> / <c>metadata.namespace</c> that identify the <c>Secret</c> the
/// controller will materialize (used for the <c>secretStores.resource</c> reference and the
/// deploy-time materialization poll). The manifest content is already encrypted; only its
/// metadata is read. A missing or unreadable manifest fails with <c>ASPIRERADIUS044</c>.
/// </summary>
internal static class SealedSecretManifest
{
    // The only Bitnami SealedSecret group/version this integration accepts. Validating the exact
    // value keeps manifest validation fail-fast; an unsupported version would fail at apply time.
    // https://github.com/bitnami-labs/sealed-secrets
    private const string SupportedApiVersion = "bitnami.com/v1alpha1";
 
    private static readonly UTF8Encoding s_utf8 = new(encoderShouldEmitUTF8Identifier: false, throwOnInvalidBytes: true);
 
    /// <summary>
    /// The result of reading a <c>SealedSecret</c> manifest's identifying metadata.
    /// <see cref="NamespaceWasExplicit"/> distinguishes a namespace read from the manifest from
    /// one defaulted to the owning environment: only when defaulted may the deploy step add
    /// <c>-n</c> to <c>kubectl apply</c> (passing <c>-n</c> when the object already declares a
    /// different namespace makes <c>kubectl apply</c> fail).
    /// </summary>
    internal readonly record struct Metadata(string Namespace, string Name, bool NamespaceWasExplicit);
 
    /// <summary>
    /// Reads, validates, and returns the manifest metadata plus the exact validated bytes.
    /// </summary>
    /// <exception cref="InvalidOperationException">
    /// The manifest is missing, unreadable, malformed, has no <c>metadata.name</c>, has no non-empty
    /// <c>spec.encryptedData</c> mapping of standard-base64 sealed values, or is not a single
    /// encrypted Bitnami <c>SealedSecret</c> document (<c>ASPIRERADIUS044</c>); or it embeds
    /// a plaintext <c>Secret</c> in a <c>last-applied-configuration</c> annotation
    /// (<c>ASPIRERADIUS063</c>).
    /// </exception>
    internal static ValidatedManifest ReadValidated(
        string storeName, string manifestPath, string defaultNamespace)
    {
        byte[] content;
        try
        {
            content = File.ReadAllBytes(manifestPath);
        }
        // File.ReadAllBytes surfaces an unreadable path as several exception types: IO/permission
        // failures (IOException — includes FileNotFoundException/DirectoryNotFoundException/
        // PathTooLongException — and UnauthorizedAccessException/NotSupportedException) as well as
        // argument failures for an empty/whitespace/invalid-character path (ArgumentException, which
        // covers ArgumentNullException). Normalize them all to ASPIRERADIUS044 so every "unreadable
        // manifest" failure matches the XML-doc/README contract instead of leaking a raw exception.
        catch (Exception ex) when (ex is IOException or PathTooLongException or UnauthorizedAccessException or NotSupportedException or ArgumentException)
        {
            throw new InvalidOperationException(
                $"Secret store '{storeName}' references a SealedSecret manifest at '{manifestPath}' that " +
                $"is missing or unreadable ({ex.GetType().Name}). Diagnostic: ASPIRERADIUS044.", ex);
        }
 
        string text;
        try
        {
            text = s_utf8.GetString(content);
        }
        catch (DecoderFallbackException ex)
        {
            throw new InvalidOperationException(
                $"Secret store '{storeName}' references a SealedSecret manifest at '{manifestPath}' that " +
                "is not valid UTF-8 YAML. Diagnostic: ASPIRERADIUS044.", ex);
        }
 
        var metadata = ReadMetadataFromYaml(storeName, manifestPath, defaultNamespace, text);
        return new ValidatedManifest(metadata, manifestPath, content);
    }
 
    /// <summary>
    /// Reads the <c>metadata.name</c> (required) and <c>metadata.namespace</c> (optional,
    /// defaulting to <paramref name="defaultNamespace"/>) from the manifest at
    /// <paramref name="manifestPath"/>.
    /// </summary>
    /// <exception cref="InvalidOperationException">
    /// The manifest is missing, unreadable, has no <c>metadata.name</c>, has no non-empty
    /// <c>spec.encryptedData</c> mapping of standard-base64 sealed values, or is not a single
    /// encrypted Bitnami <c>SealedSecret</c> document (<c>ASPIRERADIUS044</c>); or it embeds a
    /// plaintext <c>Secret</c> in a <c>last-applied-configuration</c> annotation
    /// (<c>ASPIRERADIUS063</c>).
    /// </exception>
    internal static Metadata ReadMetadata(
        string storeName, string manifestPath, string defaultNamespace) =>
        ReadValidated(storeName, manifestPath, defaultNamespace).Metadata;
 
    private static Metadata ReadMetadataFromYaml(
        string storeName, string manifestPath, string defaultNamespace, string text)
    {
        try
        {
            ValidateStructure(storeName, manifestPath, text);
 
            var stream = new YamlStream();
            stream.Load(new StringReader(text));
 
            if (stream.Documents.Count != 1)
            {
                throw CreateInvalidManifestException(
                    storeName,
                    manifestPath,
                    "contains multiple YAML documents. Provide a single encrypted Bitnami SealedSecret document.");
            }
 
            if (stream.Documents[0].RootNode is not YamlMappingNode root)
            {
                throw CreateInvalidManifestException(
                    storeName,
                    manifestPath,
                    "does not have a YAML mapping as its root. Provide a single encrypted Bitnami SealedSecret object.");
            }
 
            return ReadMetadataFromRoot(storeName, manifestPath, defaultNamespace, root);
        }
        catch (YamlException ex)
        {
            throw CreateInvalidManifestException(
                storeName,
                manifestPath,
                "is malformed YAML or uses unsupported YAML features.",
                ex);
        }
    }
 
    private static Metadata ReadMetadataFromRoot(
        string storeName, string manifestPath, string defaultNamespace, YamlMappingNode root)
    {
        var kind = ReadScalar(root, "kind");
        var apiVersion = ReadScalar(root, "apiVersion");
 
        if (string.Equals(kind, "Secret", StringComparison.Ordinal))
        {
            throw new InvalidOperationException(
                $"Secret store '{storeName}' references a plaintext Kubernetes Secret manifest at '{manifestPath}'. " +
                "Only an encrypted Bitnami SealedSecret is accepted so cleartext credentials are never copied into " +
                "artifacts or applied to the cluster. Seal it with kubeseal first. Diagnostic: ASPIRERADIUS044.");
        }
 
        // Require the exact supported group/version rather than any `bitnami.com/*` prefix: a
        // malformed value like `bitnami.com/` or an unsupported future version would otherwise pass
        // validation here and only fail when applied to the cluster.
        if (!string.Equals(kind, "SealedSecret", StringComparison.Ordinal) ||
            !string.Equals(apiVersion, SupportedApiVersion, StringComparison.Ordinal))
        {
            throw new InvalidOperationException(
                $"Secret store '{storeName}' references a manifest at '{manifestPath}' that is not an encrypted " +
                $"Bitnami SealedSecret (expected 'kind: SealedSecret' and 'apiVersion: {SupportedApiVersion}'; found " +
                $"kind '{kind ?? "<none>"}', apiVersion '{apiVersion ?? "<none>"}'). Diagnostic: ASPIRERADIUS044.");
        }
 
        if (TryGetNode(root, "data", out _) || TryGetNode(root, "stringData", out _))
        {
            throw CreateInvalidManifestException(
                storeName,
                manifestPath,
                "contains top-level plaintext Kubernetes Secret fields ('data' or 'stringData'). Seal those values under spec.encryptedData instead.");
        }
 
        if (TryGetNode(root, "spec", out var specNode) &&
            specNode is YamlMappingNode spec &&
            TryGetNode(spec, "template", out var templateNode) &&
            templateNode is YamlMappingNode template &&
            (ContainsPlaintextTemplateData(template, "data") || ContainsPlaintextTemplateData(template, "stringData")))
        {
            throw CreateInvalidManifestException(
                storeName,
                manifestPath,
                "contains plaintext-capable spec.template.data or spec.template.stringData values. Seal secret material under spec.encryptedData instead.");
        }
 
        // A `kubectl.kubernetes.io/last-applied-configuration` annotation records the full JSON of a
        // previously-applied object. Unlike spec.encryptedData it is NOT encrypted, so a plaintext
        // Secret embedded there (top-level metadata, or the templated Secret's metadata) would be
        // copied verbatim into publish artifacts and re-applied — defeating sealing. Reject it.
        RejectPlaintextLastAppliedAnnotation(storeName, manifestPath, root);
 
        // Runs after the leak gates above so a manifest that both leaks cleartext and has a malformed
        // payload still reports the more specific ASPIRERADIUS063/plaintext diagnostic.
        ValidateEncryptedData(storeName, manifestPath, root);
 
        if (!TryGetNode(root, "metadata", out var metadataNode) || metadataNode is not YamlMappingNode metadata)
        {
            throw CreateInvalidManifestException(
                storeName,
                manifestPath,
                "has no metadata mapping with metadata.name.");
        }
 
        var name = ReadScalar(metadata, "name");
        if (string.IsNullOrWhiteSpace(name))
        {
            throw new InvalidOperationException(
                $"Secret store '{storeName}' references a SealedSecret manifest at '{manifestPath}' that has " +
                "no metadata.name. Diagnostic: ASPIRERADIUS044.");
        }
 
        var ns = ReadScalar(metadata, "namespace");
        var namespaceWasExplicit = !string.IsNullOrWhiteSpace(ns);
 
        // The resulting Secret is applied with `kubectl apply`, which enforces Kubernetes naming: the
        // name must be a DNS-1123 subdomain and any explicit namespace a DNS-1123 label. Validate here
        // so a bad manifest (e.g. 'Bad_Name', 'a/b', an overlong namespace) fails at publish rather
        // than producing an artifact that is guaranteed to be rejected at deploy — mirroring the
        // fail-fast validation on WithExistingSecret references.
        if (!KubernetesName.IsDns1123Subdomain(name!))
        {
            throw new InvalidOperationException(
                $"Secret store '{storeName}' references a SealedSecret manifest at '{manifestPath}' whose metadata.name " +
                $"'{name}' is not a valid Kubernetes name (must be a DNS-1123 subdomain). Diagnostic: ASPIRERADIUS044.");
        }
 
        if (namespaceWasExplicit && !KubernetesName.IsDns1123Label(ns!))
        {
            throw new InvalidOperationException(
                $"Secret store '{storeName}' references a SealedSecret manifest at '{manifestPath}' whose metadata.namespace " +
                $"'{ns}' is not a valid Kubernetes namespace (must be a DNS-1123 label). Diagnostic: ASPIRERADIUS044.");
        }
 
        return new Metadata(namespaceWasExplicit ? ns! : defaultNamespace, name!, namespaceWasExplicit);
    }
 
    // Requires the sealed payload to actually be present and well-formed:
    //
    //   spec:
    //     encryptedData:
    //       username: AgBy3i4OJSWK+PiTySYZZA9rO43cGDEq...   # standard base64
    //
    // Without this gate a manifest with a missing, scalar, empty, or plaintext-looking
    // spec.encryptedData passes publish validation and is copied verbatim into the artifact, even
    // though this path promises a structurally valid encrypted manifest.
    //
    // This is intentionally stricter than the upstream CRD, which permits an empty encryptedData and
    // template-only objects: this integration already rejects plaintext spec.template.data, so a
    // SealedSecret with no sealed payload can never populate a store here.
    // https://github.com/bitnami-labs/sealed-secrets
    private static void ValidateEncryptedData(string storeName, string manifestPath, YamlMappingNode root)
    {
        if (!TryGetNode(root, "spec", out var specNode) || specNode is not YamlMappingNode spec)
        {
            throw CreateInvalidManifestException(
                storeName,
                manifestPath,
                "has no spec mapping with sealed values under spec.encryptedData. Seal the secret with kubeseal.");
        }
 
        if (!TryGetNode(spec, "encryptedData", out var encryptedDataNode) ||
            encryptedDataNode is not YamlMappingNode encryptedData ||
            encryptedData.Children.Count == 0)
        {
            throw CreateInvalidManifestException(
                storeName,
                manifestPath,
                "has no non-empty spec.encryptedData mapping of sealed values. Seal the secret with kubeseal.");
        }
 
        foreach (var (keyNode, valueNode) in encryptedData.Children)
        {
            // ValidateStructure already rejects non-scalar mapping keys, so the key is a scalar here;
            // read it defensively for the diagnostic message only.
            var key = (keyNode as YamlScalarNode)?.Value ?? "<non-scalar>";
 
            // kubeseal writes each value with Go's base64.StdEncoding and the controller decodes it the
            // same way, so anything that is not a standard-base64 scalar cannot be a sealed value. This
            // does not prove the value is ciphertext (plaintext can be valid base64), but it fails
            // closed on the shapes the CRD never produces: nested mappings/sequences, empty/whitespace
            // values, and most implicit non-string scalars (`123`, `~`). It cannot reject a short plain
            // scalar that happens to be valid base64 (`true`), and a minimum length is deliberately not
            // enforced because the ciphertext length is not a documented part of the format.
            // https://github.com/bitnami-labs/sealed-secrets/blob/main/pkg/apis/sealedsecrets/v1alpha1/sealedsecret_expansion.go
            if (valueNode is not YamlScalarNode { Value: { Length: > 0 } value } ||
                !IsStandardBase64(value))
            {
                throw CreateInvalidManifestException(
                    storeName,
                    manifestPath,
                    $"has a spec.encryptedData entry '{key}' that is not a non-empty standard-base64 encoded sealed " +
                    "value. Seal the secret with kubeseal instead of writing the value by hand.");
            }
        }
    }
 
    private static bool IsStandardBase64(string value) =>
        // Base64.IsValid validates without allocating a decode buffer (the ciphertext can be large).
        // It ignores whitespace, so require a non-zero decoded length to reject a whitespace-only value.
        Base64.IsValid(value, out var decodedLength) && decodedLength > 0;
 
    private static bool ContainsPlaintextTemplateData(YamlMappingNode template, string field) =>
        TryGetNode(template, field, out var value) && HasContent(value);
 
    // The annotation key `kubectl apply` uses to stash the last-applied object JSON.
    private const string LastAppliedConfigurationAnnotation = "kubectl.kubernetes.io/last-applied-configuration";
 
    // Rejects a plaintext Kubernetes Secret embedded in a last-applied-configuration annotation on
    // either the SealedSecret itself (metadata.annotations) or the Secret it templates
    // (spec.template.metadata.annotations). An embedded *SealedSecret* carries no cleartext and is
    // fine; only a bare `kind: Secret` with data/stringData is a leak. Fails closed when the
    // annotation is present but cannot be parsed/verified.
    private static void RejectPlaintextLastAppliedAnnotation(
        string storeName, string manifestPath, YamlMappingNode root)
    {
        CheckLastAppliedAnnotation(storeName, manifestPath, root);
 
        if (TryGetNode(root, "spec", out var specNode) &&
            specNode is YamlMappingNode spec &&
            TryGetNode(spec, "template", out var templateNode) &&
            templateNode is YamlMappingNode template)
        {
            CheckLastAppliedAnnotation(storeName, manifestPath, template);
        }
    }
 
    private static void CheckLastAppliedAnnotation(
        string storeName, string manifestPath, YamlMappingNode owner)
    {
        if (!TryGetNode(owner, "metadata", out var metadataNode) || metadataNode is not YamlMappingNode metadata ||
            !TryGetNode(metadata, "annotations", out var annotationsNode) || annotationsNode is not YamlMappingNode annotations ||
            !TryGetNode(annotations, LastAppliedConfigurationAnnotation, out var valueNode))
        {
            return;
        }
 
        // The annotation is present. A legitimate value is always a JSON string scalar (Kubernetes
        // annotation values are `map[string]string`). Anything else — a YAML mapping/sequence, or a
        // null/empty scalar where we expected JSON — cannot be verified free of cleartext, so fail
        // closed rather than skip it.
        if (valueNode is not YamlScalarNode { Value: { } lastApplied } || EmbedsPlaintextSecret(lastApplied))
        {
            throw new InvalidOperationException(
                $"Secret store '{storeName}' references a SealedSecret manifest at '{manifestPath}' whose " +
                $"'{LastAppliedConfigurationAnnotation}' annotation embeds a plaintext Kubernetes Secret " +
                "(kind 'Secret' with 'data'/'stringData'), or content that cannot be verified as sealed. Such " +
                "annotations are copied verbatim into publish artifacts and applied to the cluster, so the " +
                "cleartext would leak. Re-seal from a clean manifest without the annotation. " +
                "Diagnostic: ASPIRERADIUS063.");
        }
    }
 
    // Example annotation value (a single JSON string):
    //   {"apiVersion":"v1","kind":"Secret","metadata":{...},"data":{"password":"cGFzcw=="}}
    // Returns true when that JSON is a plaintext Secret carrying data/stringData, or when it cannot
    // be parsed as the expected object (fail closed). An embedded SealedSecret returns false.
    private static bool EmbedsPlaintextSecret(string lastAppliedJson)
    {
        try
        {
            using var document = JsonDocument.Parse(lastAppliedJson);
            var root = document.RootElement;
            if (root.ValueKind != JsonValueKind.Object)
            {
                return true;
            }
 
            // System.Text.Json preserves duplicate property names, and TryGetProperty silently
            // observes only ONE of them, so a crafted payload could smuggle cleartext past the gate.
            // For example, the following is a plaintext Secret, but TryGetProperty("kind") would
            // return the trailing "SealedSecret" and wrongly clear it:
            //   {"kind":"Secret","data":{"password":"cGFzcw=="},"kind":"SealedSecret"}
            // Enumerate every property so duplicate/missing/non-string identity or payload keys all
            // fail closed, rather than trusting a single lookup.
            JsonElement? kind = null;
            JsonElement data = default;
            JsonElement stringData = default;
            var kindCount = 0;
            var dataCount = 0;
            var stringDataCount = 0;
 
            foreach (var property in root.EnumerateObject())
            {
                switch (property.Name)
                {
                    case "kind":
                        kindCount++;
                        kind = property.Value;
                        break;
                    case "data":
                        dataCount++;
                        data = property.Value;
                        break;
                    case "stringData":
                        stringDataCount++;
                        stringData = property.Value;
                        break;
                }
            }
 
            // Duplicated identity or payload keys make the object ambiguous; it cannot be verified.
            if (kindCount > 1 || dataCount > 1 || stringDataCount > 1)
            {
                return true;
            }
 
            // A well-formed non-Secret resource (the expected `kind: SealedSecret`, whose payload
            // lives in `spec.encryptedData`) normally carries no cleartext. But an embedded
            // SealedSecret can still smuggle plaintext under `spec.template.data` /
            // `spec.template.stringData` (the same fields rejected on the outer manifest) or top-level
            // data/stringData, and the annotation is copied verbatim into artifacts. Validate those
            // recursively so a SealedSecret that carries any such cleartext fails closed. Other
            // non-Secret kinds keep their prior allow behavior; only `kind: Secret` (or a
            // missing/non-string kind we cannot rule out) is treated as a Secret below.
            if (kind is { ValueKind: JsonValueKind.String } kindElement &&
                !string.Equals(kindElement.GetString(), "Secret", StringComparison.Ordinal))
            {
                if (string.Equals(kindElement.GetString(), "SealedSecret", StringComparison.Ordinal) &&
                    (((dataCount == 1 && HasNonEmptyValue(data)) || (stringDataCount == 1 && HasNonEmptyValue(stringData))) ||
                     SealedSecretTemplateHasPlaintext(root)))
                {
                    return true;
                }
 
                return false;
            }
 
            return (dataCount == 1 && HasNonEmptyValue(data)) || (stringDataCount == 1 && HasNonEmptyValue(stringData));
        }
        catch (JsonException)
        {
            return true;
        }
    }
 
    // Inspects an embedded SealedSecret's spec.template for plaintext data/stringData. The template
    // mirrors the Secret that will be produced; a well-formed SealedSecret seals values under
    // spec.encryptedData and leaves the template free of cleartext. Fails closed (true) on a
    // duplicated spec/template key or any non-empty template data/stringData.
    private static bool SealedSecretTemplateHasPlaintext(JsonElement root)
    {
        if (!TryGetSingleObjectProperty(root, "spec", out var spec, out var specDuplicated))
        {
            return specDuplicated;
        }
 
        if (!TryGetSingleObjectProperty(spec, "template", out var template, out var templateDuplicated))
        {
            return templateDuplicated;
        }
 
        return TemplateFieldHasPlaintext(template, "data") || TemplateFieldHasPlaintext(template, "stringData");
    }
 
    private static bool TemplateFieldHasPlaintext(JsonElement template, string field)
    {
        JsonElement value = default;
        var count = 0;
        foreach (var property in template.EnumerateObject())
        {
            if (string.Equals(property.Name, field, StringComparison.Ordinal))
            {
                count++;
                value = property.Value;
            }
        }
 
        // A duplicated field is ambiguous and cannot be verified, so fail closed.
        if (count > 1)
        {
            return true;
        }
 
        return count == 1 && HasNonEmptyValue(value);
    }
 
    // Returns the single object-valued property named <paramref name="name"/>. Sets
    // <paramref name="duplicated"/> when the property appears more than once so the caller can fail
    // closed; an absent property, or a value that is not a JSON object, yields false without a
    // duplicate signal (there is no verifiable nested object to inspect).
    private static bool TryGetSingleObjectProperty(JsonElement obj, string name, out JsonElement value, out bool duplicated)
    {
        value = default;
        var count = 0;
        foreach (var property in obj.EnumerateObject())
        {
            if (string.Equals(property.Name, name, StringComparison.Ordinal))
            {
                count++;
                value = property.Value;
            }
        }
 
        duplicated = count > 1;
        return count == 1 && value.ValueKind == JsonValueKind.Object;
    }
 
    // For a `kind: Secret`, ANY non-empty representation of data/stringData is treated as a potential
    // cleartext leak — not only a non-empty JSON object. A well-formed Secret uses an object map, but
    // a hand-crafted/malformed annotation could carry cleartext as a string or array; those cannot be
    // proven safe, so they fail closed. Null, empty-object, empty-string, and empty-array
    // representations carry no data and are allowed.
    private static bool HasNonEmptyValue(JsonElement element)
    {
        return element.ValueKind switch
        {
            JsonValueKind.Object => HasAnyChild(element),
            JsonValueKind.Array => element.GetArrayLength() > 0,
            JsonValueKind.String => element.GetString() is { Length: > 0 },
            JsonValueKind.Null or JsonValueKind.Undefined => false,
            _ => true,
        };
    }
 
    private static bool HasAnyChild(JsonElement obj)
    {
        foreach (var _ in obj.EnumerateObject())
        {
            return true;
        }
 
        return false;
    }
 
    private static bool HasContent(YamlNode node) =>
        node switch
        {
            YamlScalarNode scalar => !string.IsNullOrEmpty(scalar.Value),
            YamlMappingNode mapping => mapping.Children.Count > 0,
            YamlSequenceNode sequence => sequence.Children.Count > 0,
            _ => true
        };
 
    private static string? ReadScalar(YamlMappingNode mapping, string key) =>
        TryGetNode(mapping, key, out var node) && node is YamlScalarNode scalar ? scalar.Value : null;
 
    private static bool TryGetNode(YamlMappingNode mapping, string key, out YamlNode node)
    {
        foreach (var (candidateKey, value) in mapping.Children)
        {
            if (candidateKey is YamlScalarNode scalarKey &&
                string.Equals(scalarKey.Value, key, StringComparison.Ordinal))
            {
                node = value;
                return true;
            }
        }
 
        node = null!;
        return false;
    }
 
    private static void ValidateStructure(string storeName, string manifestPath, string text)
    {
        var parser = new Parser(new StringReader(text));
        var stack = new Stack<MappingFrame>();
 
        while (parser.MoveNext())
        {
            var yamlEvent = parser.Current;
 
            if (yamlEvent is AnchorAlias)
            {
                throw CreateInvalidManifestException(
                    storeName,
                    manifestPath,
                    "uses YAML aliases. Provide a self-contained SealedSecret manifest without anchors, aliases, or merge keys.");
            }
 
            if (yamlEvent is NodeEvent nodeEvent)
            {
                if (!nodeEvent.Anchor.IsEmpty || HasExplicitTag(nodeEvent))
                {
                    throw CreateInvalidManifestException(
                        storeName,
                        manifestPath,
                        "uses YAML anchors or explicit tags. Provide a plain SealedSecret manifest without anchors, aliases, merge keys, or tags.");
                }
 
                RegisterNodeWithParent(storeName, manifestPath, yamlEvent, stack);
            }
 
            if (yamlEvent is MappingStart)
            {
                stack.Push(new MappingFrame());
            }
            else if (yamlEvent is MappingEnd)
            {
                // A well-formed stream always pairs MappingStart/MappingEnd, but guard the pop so a
                // malformed or out-of-sync event stream fails as ASPIRERADIUS044 rather than escaping
                // ValidateStructure as a raw InvalidOperationException from Stack.Pop() on an empty stack.
                if (stack.Count == 0)
                {
                    throw CreateInvalidManifestException(
                        storeName,
                        manifestPath,
                        "is malformed YAML with an unbalanced mapping. Provide a single well-formed encrypted Bitnami SealedSecret document.");
                }
 
                stack.Pop();
            }
            else if (yamlEvent is SequenceStart)
            {
                stack.Push(MappingFrame.s_sequence);
            }
            else if (yamlEvent is SequenceEnd)
            {
                if (stack.Count == 0)
                {
                    throw CreateInvalidManifestException(
                        storeName,
                        manifestPath,
                        "is malformed YAML with an unbalanced sequence. Provide a single well-formed encrypted Bitnami SealedSecret document.");
                }
 
                stack.Pop();
            }
        }
    }
 
    private static bool HasExplicitTag(NodeEvent nodeEvent) =>
        !nodeEvent.Tag.IsEmpty &&
        !string.Equals(nodeEvent.Tag.Value, "!", StringComparison.Ordinal);
 
    private static void RegisterNodeWithParent(
        string storeName, string manifestPath, ParsingEvent yamlEvent, Stack<MappingFrame> stack)
    {
        if (stack.Count == 0)
        {
            return;
        }
 
        var frame = stack.Peek();
        if (frame.IsSequence)
        {
            return;
        }
 
        if (frame.ExpectsKey)
        {
            if (yamlEvent is not Scalar scalar)
            {
                throw CreateInvalidManifestException(
                    storeName,
                    manifestPath,
                    "uses a non-scalar YAML mapping key. Provide a plain SealedSecret manifest with scalar keys.");
            }
 
            var key = scalar.Value;
            if (string.Equals(key, "<<", StringComparison.Ordinal))
            {
                throw CreateInvalidManifestException(
                    storeName,
                    manifestPath,
                    "uses YAML merge keys. Provide a self-contained SealedSecret manifest without anchors, aliases, or merge keys.");
            }
 
            // YAML allows duplicate keys, and some high-level readers keep the last value. For a
            // security gate that rejects plaintext-capable fields, last-wins semantics would let a
            // document advertise `kind: SealedSecret` first and then override it with `kind: Secret`.
            if (!frame.Keys.Add(key))
            {
                throw CreateInvalidManifestException(
                    storeName,
                    manifestPath,
                    $"contains a duplicate YAML mapping key '{key}'. Provide a single unambiguous SealedSecret manifest.");
            }
        }
 
        frame.ExpectsKey = !frame.ExpectsKey;
    }
 
    private static InvalidOperationException CreateInvalidManifestException(
        string storeName, string manifestPath, string reason, Exception? innerException = null) =>
        new(
            $"Secret store '{storeName}' references a SealedSecret manifest at '{manifestPath}' that {reason} " +
            "Diagnostic: ASPIRERADIUS044.",
            innerException);
 
    private sealed class MappingFrame
    {
        internal static readonly MappingFrame s_sequence = new(isSequence: true);
 
        private MappingFrame(bool isSequence)
        {
            IsSequence = isSequence;
        }
 
        internal MappingFrame()
        {
        }
 
        internal bool IsSequence { get; }
 
        internal bool ExpectsKey { get; set; } = true;
 
        internal HashSet<string> Keys { get; } = new(StringComparer.Ordinal);
    }
}