Output configuration fields marked isEncrypted are stored in plain text when installed from a content pack

Author: patrickmannCreated Sep 1, 2026Updated Sep 14, 2026
Labelsbugsecuritytriaged

Expected Behavior

MessageOutput configuration fields declared with isEncrypted=true should be encrypted before being persisted, no matter which code path creates the output. Content pack installation should behave like the REST API.

Current Behavior

OutputFacade.decode passes the raw configuration map straight into outputService.create, with no conversion of encrypted fields:

java
final CreateOutputRequest createOutputRequest = CreateOutputRequest.create(
        outputEntity.title().asString(parameters),
        outputEntity.type().asString(parameters),
        toValueMap(outputEntity.configuration(), parameters),
        null // Outputs are assigned to streams in StreamFacade
);

InputFacade.decode does the equivalent conversion, routing the configuration through ConfigurationWrapper so the specialized deserializer turns plain strings into EncryptedValue objects:

java
// Incoming encrypted fields from a content pack will be plain strings in the input configuration at this
// point.
// We use the object mapper to convert them into proper EncryptedValue objects. This works, because classes
// implementing the WithInputConfiguration interface will be converted using a specialized deserializer.
final var configuration = objectMapper.convertValue(
        new ConfigurationWrapper(type, rawConfiguration), ConfigurationWrapper.class).configuration();

Result for outputs: the value lands in MongoDB as a plain String. Configuration only routes EncryptedValue instances into its encrypted map, so Configuration.getEncryptedValue(key) returns unset and the running output gets no secret.

Two concrete consequences:

  1. A content pack that parameterizes the field stores the operator supplied secret in plain text in the outputs collection.
  2. A content pack exported from a live system carries the placeholder that ValueReference.of(EncryptedValue) substitutes (<Encrypted value was replaced with this text for content pack export. ...>). Installing it stores that literal placeholder string as the field value rather than leaving the field unset.

The export side itself is fine. ValueReference.of(EncryptedValue) already prevents secrets from leaking into an exported content pack.

Steps to Reproduce

  1. Build a plugin output with a TextField declared isEncrypted=true, as in #26626.
  2. Create a content pack containing that output, with the encrypted field exposed as a parameter.
  3. Install the content pack and supply a value for the parameter.
  4. Inspect the document in the outputs collection. The field holds the plain string, not { "encrypted_value": "...", "salt": "..." }.

Context

Follow-up to #26626 and PR #27104, which fixed the REST API create and update paths (OutputResource) and the load path (OutputServiceImpl). The content pack path was left out of scope there.

No output shipped by core or enterprise declares isEncrypted today, so this only affects third-party plugin outputs. It is the same class of bug as #26626 and the fix is to mirror what InputFacade.decode already does.

Related

  • #26626
  • #27104

Source: Graylog2/graylog2-server