06 / Articles

Write-up format

published / 4 min read /chwrld

processwriting

This is an internal document, published openly. When every post invents its own shape, readers have to re-learn the structure each time — and, more importantly, it becomes easy to skip the redaction pass.

New posts live at src/content/articles/<lang>/<slug>.md. The Korean and English versions must share the same slug or the language switcher has nothing to point at.

Frontmatter

The schema lives in src/content.config.ts and is .strict(). An undefined key fails the build immediately. That is deliberate: a typo’d key that gets silently ignored is worse than a broken build.

The example below is fictional, for shape only. The values are not a real report.

---
title: "Internal API prefix bypassed gateway authentication"
description: "The router special-cased a single public prefix, so a similarly named internal namespace was proxied without authentication. Reproduction and impact, as of the patched build."
pubDate: 2026-09-19
updatedDate: 2026-09-22
lang: en
tags: ["web", "authorization", "code-audit"]
author: chwrld
severity: high
target: "Example Gateway 1.4.2"
cve: ["CVE-2026-00000"]
bounty: "$750"
draft: false
---

Field by field:

FieldRequiredRules
titleyesUsed for the card and the <title>. Name the flaw, not the vendor
descriptionyesMeta description, RSS summary and card excerpt in one. A single paragraph, no line breaks. One or two sentences
pubDateyes2026-09-19 — unquoted, so YAML parses it as a date
updatedDatenoOnly for meaningful revisions. Typo fixes do not count
langyesko or en, and it must match the directory the file sits in
tagsnoLowercase kebab-case. These become URLs (/en/articles/tags/web/)
authornoDefaults to chwrld. Match a handle in src/data/team.ts
draftnotrue keeps it out of the production index, RSS and sitemap. The dev server shows it with a DRAFT badge
severitynocritical / high / medium / low / info. Omit on non-vulnerability posts
targetnoProduct and version. A target without a version is meaningless six months later
cvenoAn array, and only identifiers actually assigned. Requested-but-unassigned does not go here
bountynoExactly as the vendor stated it. Do not estimate or convert currency
heroImagenoAbsolute path from public/ (/og/gateway.png)

Do not add a slug key. The filename is the only source of a slug.

draft versus the _ prefix

Both hide a post, for different reasons.

  • draft: true — the page is still built; it is only absent from listings. Anyone with the URL can read it, which makes it the right tool for sharing a review link.
  • Naming the file _wip-foo.md — it is never loaded into the collection at all, and does not have to satisfy the schema. Use this for drafts that do not even have frontmatter yet.

A draft containing details of an unpatched issue belongs behind the _ prefix, not behind draft: true. Drafts are reachable by URL.

Body

The layout renders the <h1> from title. Body content starts at ##.

Code blocks

Always tag the language. Shiki needs it, and without it you get an undifferentiated grey block.

```http
GET /internalapi/v1/namespaces/default/config HTTP/1.1
Host: gateway.example
Authorization: REDACTED
```

Use http for requests and responses, bash for shell, yaml / json for config. For patch diffs and decompiler output, diff gets you coloured + and - lines.

Trim long output. Rather than pasting a 200-line stack trace, keep the frames that matter and mark the cut with [...]. Trimming without marking it leaves whoever reproduces the bug wondering what they are missing.

Images

  • Store them under public/articles/<slug>/ and reference them as /articles/<slug>/foo.png.
  • Alt text is mandatory. Describe what the reader is supposed to notice, not the word “screenshot”.
  • Prefer a code block over a screenshot wherever possible. Text is searchable, copyable and diffable; a PNG is none of those.
  • When a screenshot is genuinely necessary, capture the region, not the whole browser window. Full windows come with other tab titles, bookmarks and notifications attached.

Tables

Tables are for comparison — behaviour across versions, responses across parameters — not for procedures. Numbered lists read better for steps. Keep it to four columns; beyond that it scrolls sideways on a phone.

Quotes

Cite the source when quoting an advisory or a commit message.

> The quoted text.
> <cite>Vendor advisory SA-2026-001</cite>

Pre-publication checklist

This list is checked by someone other than the author. Finding your own mistakes in your own prose does not work reliably.

Credentials and tokens

  • No live session cookies, bearer tokens or API keys in the prose, code blocks or screenshots
  • Anything removed is replaced with REDACTED or an obvious dummy (eyJhbG...TRUNCATED)
  • Nothing is only partially masked — a JWT with the first eight characters covered is still a working token
  • No passwords for accounts used during testing
  • Vendor-internal hostnames and IPs are confirmed harmless to publish

Third-party data

  • No other user’s email, phone number, name or order reference in the example responses
  • Nothing personal in screenshot margins, tab titles or autocomplete dropdowns
  • Internal identifiers (UUIDs, account IDs) do not resolve to a real user

Payload scope

  • The proof of concept stops at demonstrating the flaw exists
  • It is not a finished script that takes over an account or destroys data when run as-is
  • No automation is attached — no mass scanners, no account enumeration tooling

Clearance

  • The vendor has shipped a patch
  • The vendor agreed to publication, or an advisory is already out
  • No program or platform terms restrict disclosure
  • No still-unpatched variant from the same codebase has leaked into the text

That last item is the genuinely dangerous one. A second flaw found during variant analysis has a way of ending up inside the first post’s casual sentence about how “this pattern appears elsewhere too”. Unpatched variants stay unmentioned until their own report closes.


The reasoning behind clearance decisions is in the responsible disclosure policy.