Class Gdpr
data fields a
registry declared x-gdpr-sensitive. babelqueue-registry only DECLARES and AUDITS
sensitivity (and offers a one-way mask for safe logging); this class ENFORCES it on the wire — a
producer encrypts each marked leaf before publish, a consumer decrypts it after decode.
The contract mirrors the Go reference so every SDK round-trips byte-for-byte:
- The envelope stays frozen (GR-1).
protect(java.util.Map<java.lang.String, java.lang.Object>, java.util.Map<java.lang.String, java.lang.Object>, com.babelqueue.gdpr.Cipher)mutates only values insidedata: a sensitive leaf's value becomes a ciphertext String. It never adds, renames, removes or retypes an envelope field;meta.schema_versionstays1;trace_idis untouched (GR-4).dataremains pure JSON (GR-3) — a JSON string is still pure JSON, so any SDK can carry the envelope even without the key (it just cannot read the protected fields). Which fields are sensitive lives in the schema, not the message, so nothing about the frame changes. - Zero heavy dependencies (GR-7). The crypto is a caller-provided
Cipherinterface (KMS/Vault/HSM/tokenisation); the bundledAesGcmCipheris JDK-only. The core pulls no crypto/KMS dependency.
The sensitive paths come from the SAME per-URN schema the produce/consume validation path
already loads (via a SchemaProvider) — the x-gdpr-sensitive marks ride on it
(SensitivePaths.of(java.util.Map<java.lang.String, java.lang.Object>)). protect(java.util.Map<java.lang.String, java.lang.Object>, java.util.Map<java.lang.String, java.lang.Object>, com.babelqueue.gdpr.Cipher)/unprotect(java.util.Map<java.lang.String, java.lang.Object>, java.util.Map<java.lang.String, java.lang.Object>, com.babelqueue.gdpr.Cipher) are standalone helpers the caller
invokes, so the feature is strictly opt-in: a producer/consumer that never calls them behaves
exactly as before.
Typical wiring (producer), after building data and before encode/publish:
Map<String, Object> schema = provider.schemaFor(envelope.job());
if (schema != null) {
SchemaValidation.validate(provider, envelope.job(), envelope.data()); // validate cleartext
Gdpr.protect(envelope.data(), schema, cipher); // encrypt marked leaves
}
String body = EnvelopeCodec.encode(envelope); // ciphertext rides in data
and the inverse on the consumer, after decode and before the handler reads data:
Map<String, Object> schema = provider.schemaFor(in.job());
if (schema != null) {
Gdpr.unprotect(in.data(), schema, cipher); // decrypt marked leaves
}
Validate cleartext BEFORE protect(java.util.Map<java.lang.String, java.lang.Object>, java.util.Map<java.lang.String, java.lang.Object>, com.babelqueue.gdpr.Cipher) / AFTER unprotect(java.util.Map<java.lang.String, java.lang.Object>, java.util.Map<java.lang.String, java.lang.Object>, com.babelqueue.gdpr.Cipher) — a schema that
constrains a sensitive field (minLength, enum, …) would reject the ciphertext
string otherwise.
-
Method Summary
Modifier and TypeMethodDescriptionstatic voidEncrypts, in place, every value indatalocated at a path the schema markedx-gdpr-sensitive— the producer-side step, run after buildingdataand before encode/publish.static voidThe consumer-side inverse ofprotect(java.util.Map<java.lang.String, java.lang.Object>, java.util.Map<java.lang.String, java.lang.Object>, com.babelqueue.gdpr.Cipher): decrypts, in place, every value indataat anx-gdpr-sensitivepath, restoring the original JSON value byte-for-byte.
-
Method Details
-
protect
Encrypts, in place, every value indatalocated at a path the schema markedx-gdpr-sensitive— the producer-side step, run after buildingdataand before encode/publish. Each marked leaf's value is canonically JSON-encoded and replaced byCipher.encrypt(byte[])'s ciphertext String; the envelope frame, non-sensitive fields, and key order are untouched (GR-1).A marked path that is absent from
datais skipped (not an error) — schemas evolve and a message need not carry every optional field.data/schema/cipherbeingnullis a no-op. A container mark (a whole object/array marked sensitive) is supported: the entire sub-value is encoded and encrypted as one ciphertext string.On any cipher failure it throws and leaves
datain a partially-protected state; a caller should treat a thrownprotectas fatal for that message (do not publish it).- Parameters:
data- the message data, mutated in placeschema- the decoded JSON Schema carrying thex-gdpr-sensitivemarkscipher- the caller's encryption primitive
-
unprotect
The consumer-side inverse ofprotect(java.util.Map<java.lang.String, java.lang.Object>, java.util.Map<java.lang.String, java.lang.Object>, com.babelqueue.gdpr.Cipher): decrypts, in place, every value indataat anx-gdpr-sensitivepath, restoring the original JSON value byte-for-byte. Run it after decode and before the handler readsdata.An absent path is skipped. A leaf that is NOT a string — e.g. it was never protected, or this is a re-run after a successful
unprotect— is left as-is, so re-invokingunprotecton already-cleartext data is safe (idempotent for non-string leaves). A string the cipher cannot open (wrong key, tampered, or not a ciphertext) throws aDecryptException— the consumer should fail the message (retry / dead-letter) rather than process unreadable PII.- Parameters:
data- the message data, mutated in placeschema- the decoded JSON Schema carrying thex-gdpr-sensitivemarkscipher- the caller's encryption primitive- Throws:
DecryptException- when a protected field cannot be restored
-