File: Secrets\SealedSecretManifestTests.cs
Web Access
Project: src\tests\Aspire.Hosting.Radius.Tests\Aspire.Hosting.Radius.Tests.csproj (Aspire.Hosting.Radius.Tests)
// Licensed to the .NET Foundation under one or more agreements.
// The .NET Foundation licenses this file to you under the MIT license.
 
using Aspire.Hosting.Radius.Secrets;
 
namespace Aspire.Hosting.Radius.Tests.Secrets;
 
public class SealedSecretManifestTests : IDisposable
{
    private readonly string _dir = Directory.CreateTempSubdirectory("sealed-secret-manifest-tests").FullName;
 
    public void Dispose() => Directory.Delete(_dir, recursive: true);
 
    private string Write(string content)
    {
        var path = Path.Combine(_dir, $"{Guid.NewGuid():N}.sealed.yaml");
        File.WriteAllText(path, content);
        return path;
    }
 
    [Fact]
    public void ReadMetadata_ExplicitNamespace_IsMarkedExplicit()
    {
        var path = Write(
            "apiVersion: bitnami.com/v1alpha1\n" +
            "kind: SealedSecret\n" +
            "metadata:\n" +
            "  name: db-creds\n" +
            "  namespace: app\n" +
            "spec:\n" +
            "  encryptedData:\n" +
            "    username: AgBjaXBoZXI=\n");
 
        var metadata = SealedSecretManifest.ReadMetadata("store", path, "env-default");
 
        Assert.Equal("db-creds", metadata.Name);
        Assert.Equal("app", metadata.Namespace);
        Assert.True(metadata.NamespaceWasExplicit);
    }
 
    [Fact]
    public void ReadMetadata_MissingNamespace_DefaultsAndIsMarkedImplicit()
    {
        var path = Write(
            "apiVersion: bitnami.com/v1alpha1\n" +
            "kind: SealedSecret\n" +
            "metadata:\n" +
            "  name: db-creds\n" +
            "spec:\n" +
            "  encryptedData:\n" +
            "    username: AgBjaXBoZXI=\n");
 
        var metadata = SealedSecretManifest.ReadMetadata("store", path, "env-default");
 
        Assert.Equal("db-creds", metadata.Name);
        Assert.Equal("env-default", metadata.Namespace);
        Assert.False(metadata.NamespaceWasExplicit);
    }
 
    [Fact]
    public void ReadMetadata_IgnoresNestedTemplateMetadata()
    {
        // The nested spec.template.metadata block also has name/namespace; the top-level
        // metadata block must win regardless of document ordering.
        var path = Write(
            "apiVersion: bitnami.com/v1alpha1\n" +
            "kind: SealedSecret\n" +
            "metadata:\n" +
            "  name: real-name\n" +
            "  namespace: real-ns\n" +
            "spec:\n" +
            "  encryptedData:\n" +
            "    username: AgBjaXBoZXI=\n" +
            "  template:\n" +
            "    metadata:\n" +
            "      name: decoy-name\n" +
            "      namespace: decoy-ns\n");
 
        var metadata = SealedSecretManifest.ReadMetadata("store", path, "env-default");
 
        Assert.Equal("real-name", metadata.Name);
        Assert.Equal("real-ns", metadata.Namespace);
        Assert.True(metadata.NamespaceWasExplicit);
    }
 
    [Theory]
    [InlineData("")]
    [InlineData("   ")]
    [InlineData("bad\0path")]
    public void ReadMetadata_InvalidPath_Throws_ASPIRERADIUS044(string path)
    {
        // File.ReadAllBytes throws ArgumentException for an empty/whitespace/invalid-character path
        // (not IOException). That must still normalize to ASPIRERADIUS044 like a missing/unreadable file.
        var ex = Assert.Throws<InvalidOperationException>(
            () => SealedSecretManifest.ReadMetadata("store", path, "env-default"));
 
        Assert.Contains("ASPIRERADIUS044", ex.Message);
    }
 
    [Fact]
    public void ReadMetadata_MissingName_Throws_ASPIRERADIUS044()
    {
        var path = Write(
            "apiVersion: bitnami.com/v1alpha1\n" +
            "kind: SealedSecret\n" +
            "metadata:\n" +
            "  namespace: app\n" +
            "spec:\n" +
            "  encryptedData:\n" +
            "    username: AgBjaXBoZXI=\n");
 
        var ex = Assert.Throws<InvalidOperationException>(
            () => SealedSecretManifest.ReadMetadata("store", path, "env-default"));
 
        Assert.Contains("ASPIRERADIUS044", ex.Message);
    }
 
    [Theory]
    [InlineData("Bad_Name")]
    [InlineData("UPPER")]
    [InlineData("name/withslash")]
    [InlineData("-leadinghyphen")]
    public void ReadMetadata_InvalidName_Throws_ASPIRERADIUS044(string name)
    {
        var path = Write(
            "apiVersion: bitnami.com/v1alpha1\n" +
            "kind: SealedSecret\n" +
            "metadata:\n" +
            $"  name: {name}\n" +
            "spec:\n" +
            "  encryptedData:\n" +
            "    username: AgBjaXBoZXI=\n");
 
        var ex = Assert.Throws<InvalidOperationException>(
            () => SealedSecretManifest.ReadMetadata("store", path, "env-default"));
 
        Assert.Contains("ASPIRERADIUS044", ex.Message);
    }
 
    [Fact]
    public void ReadMetadata_InvalidNamespace_Throws_ASPIRERADIUS044()
    {
        // A DNS-1123 label caps at 63 chars, so a 64-char namespace is invalid.
        var longNamespace = new string('a', 64);
        var path = Write(
            "apiVersion: bitnami.com/v1alpha1\n" +
            "kind: SealedSecret\n" +
            "metadata:\n" +
            "  name: db-creds\n" +
            $"  namespace: {longNamespace}\n" +
            "spec:\n" +
            "  encryptedData:\n" +
            "    username: AgBjaXBoZXI=\n");
 
        var ex = Assert.Throws<InvalidOperationException>(
            () => SealedSecretManifest.ReadMetadata("store", path, "env-default"));
 
        Assert.Contains("ASPIRERADIUS044", ex.Message);
    }
 
    [Fact]
    public void ReadMetadata_PlaintextSecret_Throws_ASPIRERADIUS044()
    {
        // A plaintext Kubernetes Secret also carries metadata.name; it must be rejected before its
        // metadata is trusted so cleartext credentials are never copied or applied.
        var path = Write(
            "apiVersion: v1\n" +
            "kind: Secret\n" +
            "metadata:\n" +
            "  name: db-creds\n" +
            "  namespace: app\n" +
            "data:\n" +
            "  username: dXNlcg==\n");
 
        var ex = Assert.Throws<InvalidOperationException>(
            () => SealedSecretManifest.ReadMetadata("store", path, "env-default"));
 
        Assert.Contains("ASPIRERADIUS044", ex.Message);
        Assert.Contains("plaintext", ex.Message);
    }
 
    [Fact]
    public void ReadMetadata_WrongKind_Throws_ASPIRERADIUS044()
    {
        var path = Write(
            "apiVersion: bitnami.com/v1alpha1\n" +
            "kind: ConfigMap\n" +
            "metadata:\n" +
            "  name: db-creds\n");
 
        var ex = Assert.Throws<InvalidOperationException>(
            () => SealedSecretManifest.ReadMetadata("store", path, "env-default"));
 
        Assert.Contains("ASPIRERADIUS044", ex.Message);
    }
 
    [Fact]
    public void ReadMetadata_WrongApiVersion_Throws_ASPIRERADIUS044()
    {
        var path = Write(
            "apiVersion: v1\n" +
            "kind: SealedSecret\n" +
            "metadata:\n" +
            "  name: db-creds\n");
 
        var ex = Assert.Throws<InvalidOperationException>(
            () => SealedSecretManifest.ReadMetadata("store", path, "env-default"));
 
        Assert.Contains("ASPIRERADIUS044", ex.Message);
    }
 
    [Fact]
    public void ReadMetadata_MultiDocument_Throws_ASPIRERADIUS044()
    {
        var path = Write(
            "apiVersion: bitnami.com/v1alpha1\n" +
            "kind: SealedSecret\n" +
            "metadata:\n" +
            "  name: db-creds\n" +
            "---\n" +
            "apiVersion: bitnami.com/v1alpha1\n" +
            "kind: SealedSecret\n" +
            "metadata:\n" +
            "  name: other-creds\n");
 
        var ex = Assert.Throws<InvalidOperationException>(
            () => SealedSecretManifest.ReadMetadata("store", path, "env-default"));
 
        Assert.Contains("ASPIRERADIUS044", ex.Message);
        Assert.Contains("multiple YAML", ex.Message);
    }
 
    [Fact]
    public void ReadMetadata_MultiDocumentWithCommentedSeparator_Throws_ASPIRERADIUS044()
    {
        // A YAML document-start marker may carry a trailing comment (`--- # ...`); it still separates
        // documents, so a second (possibly plaintext) document must not slip past validation.
        var path = Write(
            "apiVersion: bitnami.com/v1alpha1\n" +
            "kind: SealedSecret\n" +
            "metadata:\n" +
            "  name: db-creds\n" +
            "--- # second document\n" +
            "apiVersion: v1\n" +
            "kind: Secret\n" +
            "metadata:\n" +
            "  name: plaintext-creds\n" +
            "data:\n" +
            "  password: c2VjcmV0\n");
 
        var ex = Assert.Throws<InvalidOperationException>(
            () => SealedSecretManifest.ReadMetadata("store", path, "env-default"));
 
        Assert.Contains("ASPIRERADIUS044", ex.Message);
        Assert.Contains("multiple YAML", ex.Message);
    }
 
    [Fact]
    public void ReadMetadata_LeadingDocumentMarker_IsAllowed()
    {
        // A single leading `---` document-start marker is valid YAML and must not be mistaken for a
        // multi-document manifest.
        var path = Write(
            "---\n" +
            "apiVersion: bitnami.com/v1alpha1\n" +
            "kind: SealedSecret\n" +
            "metadata:\n" +
            "  name: db-creds\n" +
            "  namespace: app\n" +
            "spec:\n" +
            "  encryptedData:\n" +
            "    username: AgBjaXBoZXI=\n");
 
        var metadata = SealedSecretManifest.ReadMetadata("store", path, "env-default");
 
        Assert.Equal("db-creds", metadata.Name);
        Assert.Equal("app", metadata.Namespace);
    }
 
    [Theory]
    [InlineData(
        "apiVersion: bitnami.com/v1alpha1\n" +
        "kind: SealedSecret\n" +
        "kind: Secret\n" +
        "metadata:\n" +
        "  name: db-creds\n" +
        "data:\n" +
        "  password: c2VjcmV0\n")]
    [InlineData(
        "apiVersion: bitnami.com/v1alpha1\n" +
        "apiVersion: v1\n" +
        "kind: SealedSecret\n" +
        "metadata:\n" +
        "  name: db-creds\n")]
    [InlineData(
        "apiVersion: bitnami.com/v1alpha1\n" +
        "kind: SealedSecret\n" +
        "metadata:\n" +
        "  name: db-creds\n" +
        "metadata:\n" +
        "  name: other-creds\n")]
    [InlineData(
        "apiVersion: bitnami.com/v1alpha1\n" +
        "\"kind\": Secret\n" +
        "metadata:\n" +
        "  name: db-creds\n" +
        "data:\n" +
        "  password: c2VjcmV0\n")]
    [InlineData(
        "apiVersion: bitnami.com/v1alpha1\n" +
        "kind : Secret\n" +
        "metadata:\n" +
        "  name: db-creds\n" +
        "data:\n" +
        "  password: c2VjcmV0\n")]
    [InlineData(
        "apiVersion: bitnami.com/v1alpha1\n" +
        "kind: !!str SealedSecret\n" +
        "metadata:\n" +
        "  name: db-creds\n")]
    [InlineData("{apiVersion: bitnami.com/v1alpha1, kind: Secret, metadata: {name: db-creds}, data: {password: c2VjcmV0}}\n")]
    [InlineData("{\"apiVersion\":\"bitnami.com/v1alpha1\",\"kind\":\"Secret\",\"metadata\":{\"name\":\"db-creds\"},\"data\":{\"password\":\"c2VjcmV0\"}}\n")]
    [InlineData(
        "apiVersion: bitnami.com/v1alpha1\n" +
        "kind: SealedSecret\n" +
        "metadata: &metadata\n" +
        "  name: db-creds\n" +
        "spec:\n" +
        "  template:\n" +
        "    metadata:\n" +
        "      <<: *metadata\n")]
    [InlineData(
        "apiVersion: bitnami.com/v1alpha1\n" +
        "kind: SealedSecret\n" +
        "metadata:\n" +
        "  name: db-creds\n" +
        "data:\n" +
        "  password: c2VjcmV0\n")]
    [InlineData(
        "apiVersion: bitnami.com/v1alpha1\n" +
        "kind: SealedSecret\n" +
        "metadata:\n" +
        "  name: db-creds\n" +
        "stringData:\n" +
        "  password: secret\n")]
    [InlineData(
        "apiVersion: bitnami.com/v1alpha1\n" +
        "kind: SealedSecret\n" +
        "metadata:\n" +
        "  name: db-creds\n" +
        "spec:\n" +
        "  template:\n" +
        "    data:\n" +
        "      password: c2VjcmV0\n")]
    [InlineData(
        "apiVersion: bitnami.com/v1alpha1\n" +
        "kind: SealedSecret\n" +
        "metadata:\n" +
        "  name: db-creds\n" +
        "spec:\n" +
        "  template:\n" +
        "    stringData:\n" +
        "      password: secret\n")]
    [InlineData("just a scalar\n")]
    [InlineData("- apiVersion: bitnami.com/v1alpha1\n- kind: SealedSecret\n")]
    [InlineData(
        "apiVersion: bitnami.com/v1alpha1\n" +
        "kind: SealedSecret\n" +
        "metadata:\n" +
        "  name: db-creds\n" +
        "---\n" +
        "apiVersion: v1\n" +
        "kind: Secret\n")]
    [InlineData(
        "apiVersion: bitnami.com/v1alpha1\n" +
        "kind: [\n")]
    public void ReadMetadata_UnsafeOrMalformedYaml_Throws_ASPIRERADIUS044(string content)
    {
        var path = Write(content);
 
        var ex = Assert.Throws<InvalidOperationException>(
            () => SealedSecretManifest.ReadMetadata("store", path, "env-default"));
 
        Assert.Contains("ASPIRERADIUS044", ex.Message);
    }
 
    [Fact]
    public void ReadMetadata_PlaintextSecretInTopLevelLastAppliedAnnotation_Throws_ASPIRERADIUS063()
    {
        // `kubectl apply` stashes the full applied object under this annotation. It is NOT encrypted,
        // so a plaintext Secret embedded here would be copied into artifacts and re-applied verbatim.
        var path = Write(
            "apiVersion: bitnami.com/v1alpha1\n" +
            "kind: SealedSecret\n" +
            "metadata:\n" +
            "  name: db-creds\n" +
            "  namespace: app\n" +
            "  annotations:\n" +
            "    kubectl.kubernetes.io/last-applied-configuration: '{\"apiVersion\":\"v1\",\"kind\":\"Secret\",\"metadata\":{\"name\":\"db-creds\"},\"data\":{\"password\":\"c2VjcmV0\"}}'\n" +
            "spec:\n" +
            "  encryptedData:\n" +
            "    password: AgBjaXBoZXI=\n");
 
        var ex = Assert.Throws<InvalidOperationException>(
            () => SealedSecretManifest.ReadMetadata("store", path, "env-default"));
 
        Assert.Contains("ASPIRERADIUS063", ex.Message);
    }
 
    [Fact]
    public void ReadMetadata_PlaintextSecretInTemplateLastAppliedAnnotation_Throws_ASPIRERADIUS063()
    {
        var path = Write(
            "apiVersion: bitnami.com/v1alpha1\n" +
            "kind: SealedSecret\n" +
            "metadata:\n" +
            "  name: db-creds\n" +
            "spec:\n" +
            "  encryptedData:\n" +
            "    password: AgBjaXBoZXI=\n" +
            "  template:\n" +
            "    metadata:\n" +
            "      name: db-creds\n" +
            "      annotations:\n" +
            "        kubectl.kubernetes.io/last-applied-configuration: '{\"kind\":\"Secret\",\"metadata\":{\"name\":\"db-creds\"},\"stringData\":{\"password\":\"secret\"}}'\n");
 
        var ex = Assert.Throws<InvalidOperationException>(
            () => SealedSecretManifest.ReadMetadata("store", path, "env-default"));
 
        Assert.Contains("ASPIRERADIUS063", ex.Message);
    }
 
    [Fact]
    public void ReadMetadata_MalformedLastAppliedAnnotation_FailsClosed_Throws_ASPIRERADIUS063()
    {
        // Present but unparseable content cannot be proven free of cleartext — fail closed.
        var path = Write(
            "apiVersion: bitnami.com/v1alpha1\n" +
            "kind: SealedSecret\n" +
            "metadata:\n" +
            "  name: db-creds\n" +
            "  annotations:\n" +
            "    kubectl.kubernetes.io/last-applied-configuration: 'not-json{'\n" +
            "spec:\n" +
            "  encryptedData:\n" +
            "    password: AgBjaXBoZXI=\n");
 
        var ex = Assert.Throws<InvalidOperationException>(
            () => SealedSecretManifest.ReadMetadata("store", path, "env-default"));
 
        Assert.Contains("ASPIRERADIUS063", ex.Message);
    }
 
    [Fact]
    public void ReadMetadata_NonScalarLastAppliedAnnotation_FailsClosed_Throws_ASPIRERADIUS063()
    {
        // A present annotation whose value is a YAML mapping (not the expected JSON string scalar)
        // cannot be verified free of cleartext, so fail closed rather than skip it.
        var path = Write(
            "apiVersion: bitnami.com/v1alpha1\n" +
            "kind: SealedSecret\n" +
            "metadata:\n" +
            "  name: db-creds\n" +
            "  annotations:\n" +
            "    kubectl.kubernetes.io/last-applied-configuration:\n" +
            "      kind: Secret\n" +
            "      data:\n" +
            "        password: c2VjcmV0\n" +
            "spec:\n" +
            "  encryptedData:\n" +
            "    password: AgBjaXBoZXI=\n");
 
        var ex = Assert.Throws<InvalidOperationException>(
            () => SealedSecretManifest.ReadMetadata("store", path, "env-default"));
 
        Assert.Contains("ASPIRERADIUS063", ex.Message);
    }
 
    [Fact]
    public void ReadMetadata_SecretWithNonObjectDataInAnnotation_FailsClosed_Throws_ASPIRERADIUS063()
    {
        // A `kind: Secret` whose `data` is a non-empty string (malformed but present) still carries
        // potential cleartext and must fail closed.
        var path = Write(
            "apiVersion: bitnami.com/v1alpha1\n" +
            "kind: SealedSecret\n" +
            "metadata:\n" +
            "  name: db-creds\n" +
            "  annotations:\n" +
            "    kubectl.kubernetes.io/last-applied-configuration: '{\"kind\":\"Secret\",\"metadata\":{\"name\":\"db-creds\"},\"data\":\"c2VjcmV0\"}'\n" +
            "spec:\n" +
            "  encryptedData:\n" +
            "    password: AgBjaXBoZXI=\n");
 
        var ex = Assert.Throws<InvalidOperationException>(
            () => SealedSecretManifest.ReadMetadata("store", path, "env-default"));
 
        Assert.Contains("ASPIRERADIUS063", ex.Message);
    }
 
    [Theory]
    [InlineData("bitnami.com/")]
    [InlineData("bitnami.com/v1beta1")]
    [InlineData("bitnami.com/v1alpha1x")]
    public void ReadMetadata_MalformedOrUnsupportedApiVersion_Throws_ASPIRERADIUS044(string apiVersion)
    {
        // A `bitnami.com/*` prefix check used to accept the malformed value `bitnami.com/` and
        // arbitrary unsupported versions; only the exact supported group/version is accepted so such
        // manifests fail fast instead of at cluster apply time.
        var path = Write(
            $"apiVersion: {apiVersion}\n" +
            "kind: SealedSecret\n" +
            "metadata:\n" +
            "  name: db-creds\n");
 
        var ex = Assert.Throws<InvalidOperationException>(
            () => SealedSecretManifest.ReadMetadata("store", path, "env-default"));
 
        Assert.Contains("ASPIRERADIUS044", ex.Message);
    }
 
    [Fact]
    public void ReadMetadata_EmbeddedSealedSecretWithPlaintextTemplateData_Throws_ASPIRERADIUS063()
    {
        // An embedded SealedSecret keeps its sealed payload under spec.encryptedData, but a crafted
        // annotation can still carry cleartext under spec.template.data — the same field rejected on
        // the outer manifest. That must fail closed rather than be copied verbatim into artifacts.
        var path = Write(
            "apiVersion: bitnami.com/v1alpha1\n" +
            "kind: SealedSecret\n" +
            "metadata:\n" +
            "  name: db-creds\n" +
            "  namespace: app\n" +
            "  annotations:\n" +
            "    kubectl.kubernetes.io/last-applied-configuration: '{\"apiVersion\":\"bitnami.com/v1alpha1\",\"kind\":\"SealedSecret\",\"metadata\":{\"name\":\"db-creds\"},\"spec\":{\"encryptedData\":{\"password\":\"AgBjaXBoZXI=\"},\"template\":{\"data\":{\"password\":\"c2VjcmV0\"}}}}'\n" +
            "spec:\n" +
            "  encryptedData:\n" +
            "    password: AgBjaXBoZXI=\n");
 
        var ex = Assert.Throws<InvalidOperationException>(
            () => SealedSecretManifest.ReadMetadata("store", path, "env-default"));
 
        Assert.Contains("ASPIRERADIUS063", ex.Message);
    }
 
    [Fact]
    public void ReadMetadata_EmbeddedSealedSecretWithPlaintextTemplateStringData_Throws_ASPIRERADIUS063()
    {
        var path = Write(
            "apiVersion: bitnami.com/v1alpha1\n" +
            "kind: SealedSecret\n" +
            "metadata:\n" +
            "  name: db-creds\n" +
            "  namespace: app\n" +
            "  annotations:\n" +
            "    kubectl.kubernetes.io/last-applied-configuration: '{\"kind\":\"SealedSecret\",\"spec\":{\"template\":{\"stringData\":{\"password\":\"secret\"}}}}'\n" +
            "spec:\n" +
            "  encryptedData:\n" +
            "    password: AgBjaXBoZXI=\n");
 
        var ex = Assert.Throws<InvalidOperationException>(
            () => SealedSecretManifest.ReadMetadata("store", path, "env-default"));
 
        Assert.Contains("ASPIRERADIUS063", ex.Message);
    }
 
    [Fact]
    public void ReadMetadata_EmbeddedSealedSecretWithEmptyTemplateData_IsAllowed()
    {
        // A SealedSecret whose template carries an empty data map has no cleartext and stays allowed.
        var path = Write(
            "apiVersion: bitnami.com/v1alpha1\n" +
            "kind: SealedSecret\n" +
            "metadata:\n" +
            "  name: db-creds\n" +
            "  namespace: app\n" +
            "  annotations:\n" +
            "    kubectl.kubernetes.io/last-applied-configuration: '{\"kind\":\"SealedSecret\",\"spec\":{\"encryptedData\":{\"password\":\"AgBjaXBoZXI=\"},\"template\":{\"data\":{}}}}'\n" +
            "spec:\n" +
            "  encryptedData:\n" +
            "    password: AgBjaXBoZXI=\n");
 
        var metadata = SealedSecretManifest.ReadMetadata("store", path, "env-default");
 
        Assert.Equal("db-creds", metadata.Name);
        Assert.Equal("app", metadata.Namespace);
    }
 
    [Fact]
    public void ReadMetadata_SealedSecretInLastAppliedAnnotation_IsAllowed()
    {
        // An embedded *SealedSecret* carries no cleartext, so the annotation is fine.
        var path = Write(
            "apiVersion: bitnami.com/v1alpha1\n" +
            "kind: SealedSecret\n" +
            "metadata:\n" +
            "  name: db-creds\n" +
            "  namespace: app\n" +
            "  annotations:\n" +
            "    kubectl.kubernetes.io/last-applied-configuration: '{\"apiVersion\":\"bitnami.com/v1alpha1\",\"kind\":\"SealedSecret\",\"metadata\":{\"name\":\"db-creds\"},\"spec\":{\"encryptedData\":{\"password\":\"AgBjaXBoZXI=\"}}}'\n" +
            "spec:\n" +
            "  encryptedData:\n" +
            "    password: AgBjaXBoZXI=\n");
 
        var metadata = SealedSecretManifest.ReadMetadata("store", path, "env-default");
 
        Assert.Equal("db-creds", metadata.Name);
        Assert.Equal("app", metadata.Namespace);
    }
 
    [Fact]
    public void ReadMetadata_EmptySecretInLastAppliedAnnotation_IsAllowed()
    {
        // A `kind: Secret` annotation with no data/stringData carries no cleartext, so it is not a leak.
        var path = Write(
            "apiVersion: bitnami.com/v1alpha1\n" +
            "kind: SealedSecret\n" +
            "metadata:\n" +
            "  name: db-creds\n" +
            "  namespace: app\n" +
            "  annotations:\n" +
            "    kubectl.kubernetes.io/last-applied-configuration: '{\"apiVersion\":\"v1\",\"kind\":\"Secret\",\"metadata\":{\"name\":\"db-creds\"}}'\n" +
            "spec:\n" +
            "  encryptedData:\n" +
            "    password: AgBjaXBoZXI=\n");
 
        var metadata = SealedSecretManifest.ReadMetadata("store", path, "env-default");
 
        Assert.Equal("db-creds", metadata.Name);
        Assert.Equal("app", metadata.Namespace);
    }
 
    [Fact]
    public void ReadMetadata_DuplicateKindSmugglesPlaintextSecret_FailsClosed_Throws_ASPIRERADIUS063()
    {
        // System.Text.Json keeps duplicate property names and a single TryGetProperty("kind") lookup
        // observes only ONE of them. A crafted payload can lead with `kind: Secret` + data and append a
        // second `kind: SealedSecret` so a naive lookup clears it. Duplicate identity keys make the
        // object ambiguous and must fail closed.
        var path = Write(
            "apiVersion: bitnami.com/v1alpha1\n" +
            "kind: SealedSecret\n" +
            "metadata:\n" +
            "  name: db-creds\n" +
            "  annotations:\n" +
            "    kubectl.kubernetes.io/last-applied-configuration: '{\"kind\":\"Secret\",\"data\":{\"password\":\"c2VjcmV0\"},\"kind\":\"SealedSecret\"}'\n" +
            "spec:\n" +
            "  encryptedData:\n" +
            "    password: AgBjaXBoZXI=\n");
 
        var ex = Assert.Throws<InvalidOperationException>(
            () => SealedSecretManifest.ReadMetadata("store", path, "env-default"));
 
        Assert.Contains("ASPIRERADIUS063", ex.Message);
    }
 
    [Fact]
    public void ReadMetadata_MissingKindWithData_FailsClosed_Throws_ASPIRERADIUS063()
    {
        // Without a `kind` we cannot positively rule out a Secret, so any embedded data/stringData
        // is treated as a potential cleartext leak rather than assumed safe.
        var path = Write(
            "apiVersion: bitnami.com/v1alpha1\n" +
            "kind: SealedSecret\n" +
            "metadata:\n" +
            "  name: db-creds\n" +
            "  annotations:\n" +
            "    kubectl.kubernetes.io/last-applied-configuration: '{\"metadata\":{\"name\":\"db-creds\"},\"data\":{\"password\":\"c2VjcmV0\"}}'\n" +
            "spec:\n" +
            "  encryptedData:\n" +
            "    password: AgBjaXBoZXI=\n");
 
        var ex = Assert.Throws<InvalidOperationException>(
            () => SealedSecretManifest.ReadMetadata("store", path, "env-default"));
 
        Assert.Contains("ASPIRERADIUS063", ex.Message);
    }
 
    [Fact]
    public void ReadMetadata_NonStringKindWithData_FailsClosed_Throws_ASPIRERADIUS063()
    {
        // A non-string `kind` (here an object) is not positively a non-Secret kind, so an accompanying
        // non-empty data payload must fail closed rather than being cleared.
        var path = Write(
            "apiVersion: bitnami.com/v1alpha1\n" +
            "kind: SealedSecret\n" +
            "metadata:\n" +
            "  name: db-creds\n" +
            "  annotations:\n" +
            "    kubectl.kubernetes.io/last-applied-configuration: '{\"kind\":{\"nested\":\"SealedSecret\"},\"data\":{\"password\":\"c2VjcmV0\"}}'\n" +
            "spec:\n" +
            "  encryptedData:\n" +
            "    password: AgBjaXBoZXI=\n");
 
        var ex = Assert.Throws<InvalidOperationException>(
            () => SealedSecretManifest.ReadMetadata("store", path, "env-default"));
 
        Assert.Contains("ASPIRERADIUS063", ex.Message);
    }
 
    [Fact]
    public void ReadMetadata_DuplicateDataKeysInAnnotation_FailsClosed_Throws_ASPIRERADIUS063()
    {
        // Duplicate payload keys are likewise ambiguous (only one is observable), so fail closed even
        // when one of them appears empty.
        var path = Write(
            "apiVersion: bitnami.com/v1alpha1\n" +
            "kind: SealedSecret\n" +
            "metadata:\n" +
            "  name: db-creds\n" +
            "  annotations:\n" +
            "    kubectl.kubernetes.io/last-applied-configuration: '{\"kind\":\"Secret\",\"data\":{},\"data\":{\"password\":\"c2VjcmV0\"}}'\n" +
            "spec:\n" +
            "  encryptedData:\n" +
            "    password: AgBjaXBoZXI=\n");
 
        var ex = Assert.Throws<InvalidOperationException>(
            () => SealedSecretManifest.ReadMetadata("store", path, "env-default"));
 
        Assert.Contains("ASPIRERADIUS063", ex.Message);
    }
 
    [Theory]
    // No spec at all.
    [InlineData(
        "apiVersion: bitnami.com/v1alpha1\n" +
        "kind: SealedSecret\n" +
        "metadata:\n" +
        "  name: db-creds\n")]
    // spec is a scalar rather than a mapping.
    [InlineData(
        "apiVersion: bitnami.com/v1alpha1\n" +
        "kind: SealedSecret\n" +
        "metadata:\n" +
        "  name: db-creds\n" +
        "spec: nonsense\n")]
    // spec has no encryptedData.
    [InlineData(
        "apiVersion: bitnami.com/v1alpha1\n" +
        "kind: SealedSecret\n" +
        "metadata:\n" +
        "  name: db-creds\n" +
        "spec:\n" +
        "  template:\n" +
        "    metadata:\n" +
        "      name: db-creds\n")]
    // encryptedData is a scalar.
    [InlineData(
        "apiVersion: bitnami.com/v1alpha1\n" +
        "kind: SealedSecret\n" +
        "metadata:\n" +
        "  name: db-creds\n" +
        "spec:\n" +
        "  encryptedData: AgBjaXBoZXI=\n")]
    // encryptedData is a sequence.
    [InlineData(
        "apiVersion: bitnami.com/v1alpha1\n" +
        "kind: SealedSecret\n" +
        "metadata:\n" +
        "  name: db-creds\n" +
        "spec:\n" +
        "  encryptedData:\n" +
        "    - AgBjaXBoZXI=\n")]
    // encryptedData is an empty mapping: nothing is sealed, so the store can never be populated.
    [InlineData(
        "apiVersion: bitnami.com/v1alpha1\n" +
        "kind: SealedSecret\n" +
        "metadata:\n" +
        "  name: db-creds\n" +
        "spec:\n" +
        "  encryptedData: {}\n")]
    public void ReadMetadata_MissingOrMalformedEncryptedData_Throws_ASPIRERADIUS044(string yaml)
    {
        var path = Write(yaml);
 
        var ex = Assert.Throws<InvalidOperationException>(
            () => SealedSecretManifest.ReadMetadata("store", path, "env-default"));
 
        Assert.Contains("ASPIRERADIUS044", ex.Message);
        Assert.Contains("spec.encryptedData", ex.Message);
    }
 
    [Theory]
    // Empty and whitespace-only values carry no sealed material.
    [InlineData("password: ''")]
    [InlineData("password: '   '")]
    // A key with no value parses as a null scalar.
    [InlineData("password:")]
    [InlineData("password: ~")]
    // Implicit non-string scalars are not values the CRD ever produces. (A plain scalar such as
    // `true` is intentionally NOT covered: it is indistinguishable from valid standard base64, and
    // requiring a minimum length would encode an undocumented assumption about the Bitnami wire format.)
    [InlineData("password: 123")]
    // kubeseal writes standard base64; a hand-written plaintext value is rejected.
    [InlineData("password: hunter2!")]
    [InlineData("password: not base64")]
    // Nested structures instead of a sealed scalar.
    [InlineData("password:\n      inner: AgBjaXBoZXI=")]
    [InlineData("password:\n      - AgBjaXBoZXI=")]
    public void ReadMetadata_InvalidEncryptedDataValue_Throws_ASPIRERADIUS044(string entry)
    {
        var path = Write(
            "apiVersion: bitnami.com/v1alpha1\n" +
            "kind: SealedSecret\n" +
            "metadata:\n" +
            "  name: db-creds\n" +
            "spec:\n" +
            "  encryptedData:\n" +
            $"    {entry}\n");
 
        var ex = Assert.Throws<InvalidOperationException>(
            () => SealedSecretManifest.ReadMetadata("store", path, "env-default"));
 
        Assert.Contains("ASPIRERADIUS044", ex.Message);
        Assert.Contains("spec.encryptedData", ex.Message);
    }
 
    [Fact]
    public void ReadMetadata_MultipleSealedValues_IsAllowed()
    {
        // Every entry is validated, not just the first one.
        var path = Write(
            "apiVersion: bitnami.com/v1alpha1\n" +
            "kind: SealedSecret\n" +
            "metadata:\n" +
            "  name: db-creds\n" +
            "spec:\n" +
            "  encryptedData:\n" +
            "    username: AgBjaXBoZXI=\n" +
            "    password: AgBvdGhlcmNpcGhlcg==\n");
 
        var metadata = SealedSecretManifest.ReadMetadata("store", path, "env-default");
 
        Assert.Equal("db-creds", metadata.Name);
    }
 
    [Fact]
    public void ReadMetadata_PlaintextEncryptedData_TakesPrecedenceOverLastAppliedLeak()
    {
        // A manifest that both leaks cleartext through the annotation and has a malformed payload must
        // still report the more specific ASPIRERADIUS063 leak diagnostic.
        var path = Write(
            "apiVersion: bitnami.com/v1alpha1\n" +
            "kind: SealedSecret\n" +
            "metadata:\n" +
            "  name: db-creds\n" +
            "  annotations:\n" +
            "    kubectl.kubernetes.io/last-applied-configuration: '{\"kind\":\"Secret\",\"data\":{\"password\":\"c2VjcmV0\"}}'\n" +
            "spec:\n" +
            "  encryptedData: {}\n");
 
        var ex = Assert.Throws<InvalidOperationException>(
            () => SealedSecretManifest.ReadMetadata("store", path, "env-default"));
 
        Assert.Contains("ASPIRERADIUS063", ex.Message);
    }
}