risingwavelabs/risingwave · error · SecretError

decode utf8 error: {0}

Error message

decode utf8 error: {0}

What it means

SecretError::DecodeUtf8Error wraps std::string::FromUtf8Error and is raised when secret bytes retrieved from storage are not valid UTF-8. Secret values are handled as Rust Strings internally, so non-UTF-8 bytes (e.g. binary data stored directly as the secret) cannot be decoded.

Source

Thrown at src/common/secret/src/error.rs:28

// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.

pub use anyhow::anyhow;
use thiserror::Error;
use thiserror_ext::Construct;

use super::SecretId;

pub type SecretResult<T> = Result<T, SecretError>;

#[derive(Error, Debug, Construct)]
pub enum SecretError {
    #[error("secret not found: {0}")]
    ItemNotFound(SecretId),

    #[error("decode utf8 error: {0}")]
    DecodeUtf8Error(#[from] std::string::FromUtf8Error),

    #[error("I/O error: {0}")]
    IoError(#[from] std::io::Error),

    #[error("unspecified secret ref type: {0}")]
    UnspecifiedRefType(SecretId),

    #[error("failed to encrypt or decrypt the secret")]
    AesError,

    #[error("ser/de proto message error: {0}")]
    ProtoError(#[from] bincode::Error),

    #[error(transparent)]
    Internal(#[from] anyhow::Error),
}

View on GitHub (pinned to 6469eb736d)

Solutions

  1. Store textual/base64-encoded content in the secret instead of raw binary bytes.
  2. Base64-encode binary material before storing, and decode it after retrieval.
  3. Inspect the secret value's encoding; re-create the secret with UTF-8 content.
  4. Fix the upstream secret provider so it returns UTF-8 encoded values.

Example fix

// before
let bytes: Vec<u8> = load_der_key();
manager.create(id, bytes)?; // fails on read: not UTF-8

// after
use base64::Engine;
let encoded = base64::engine::general_purpose::STANDARD.encode(load_der_key());
manager.create(id, encoded)?; // store base64 text; decode after read
Defensive patterns

Strategy: validation

Validate before calling

// Validate secret bytes are UTF-8 before storing:
fn ensure_utf8(bytes: &[u8]) -> Result<&str, std::str::Utf8Error> {
    std::str::from_utf8(bytes)
}

Type guard

fn is_utf8_secret(bytes: &[u8]) -> bool {
    std::str::from_utf8(bytes).is_ok()
}

Try / catch

let secret = match manager.get(id).await {
    Ok(s) => s,
    Err(SecretError::DecodeUtf8Error(e)) => {
        // re-create the secret with base64-encoded content
        return Err(anyhow!("secret bytes not UTF-8 ({e}); re-create with base64"));
    }
    Err(e) => return Err(e.into()),
};

Prevention

When it happens

Trigger: Storing raw binary (non-UTF-8) bytes as a secret value and then reading it back via the secret manager, where conversion `String::from_utf8` fails and is converted with #[from] into SecretError::DecodeUtf8Error.

Common situations: Writing binary keys or certificates (DER, PKCS#12) directly as secret content instead of PEM/base64; a misconfigured external secret backend returning bytes in an unexpected encoding (e.g. UTF-16); corrupted secret entries in the backing store.

Understand the failure class

Background: Type mismatch errors: IllegalArgumentException, TypeError and type guards across 150 open-source libraries — this error's family across 150 libraries.

Related errors


AI-assisted analysis of risingwavelabs/risingwave@6469eb736d (2026-09-11). Data as JSON: /api/errors/2b1e4960ea308953. Report an issue: GitHub.