Skip to content
Back to skills

Orbit I18n Runtime

ASecurity

Runtime i18n correctness audit — catches the non-Latin data corruption bugs that gettext-style audits miss. Scans every wp_json_encode / json_encode call in paths that store post_meta, options, REST output, or LLM/external-API request bodies; flags any missing JSON_UNESCAPED_UNICODE | JSON_UNESCAPED_SLASHES flag. Also verifies Content-Type charset=utf-8 on REST responses. Use when the user says "i18n runtime", "Unicode encoding", "non-Latin characters", "Turkish broken", "CJK garbage", "data ...

  • 2 stars
  • 0 votes
  • 0 copies
  • 1 view
  • Added September 9, 2026
testinggophpbashcode-reviewapidatabase

Works with

  • api

Security analysis

A100/100

Scanned September 9, 2026

npx -y skills add adityaarsharma/orbit --skill orbit-i18n-runtime --agent claude-code

Installs into .claude/skills of the current project.

Are you the author of Orbit I18n Runtime?

Add the live security badge to your README. It updates with every re-scan.

Security grade badge for Orbit I18n Runtime
[![Security: A — Skills Directory](https://www.skillsdirectory.com/api/skills/adityaarsharma-orbit-i18n-runtime/badge)](https://www.skillsdirectory.com/skills/adityaarsharma-orbit-i18n-runtime)

More formats (shields.io, HTML) on the badges page. Keep it an A: scan every change in CI with Pro.

Download with Pro
SKILL.md
---
name: orbit-i18n-runtime
description: Runtime i18n correctness audit — catches the non-Latin data corruption bugs that gettext-style audits miss. Scans every wp_json_encode / json_encode call in paths that store post_meta, options, REST output, or LLM/external-API request bodies; flags any missing JSON_UNESCAPED_UNICODE | JSON_UNESCAPED_SLASHES flag. Also verifies Content-Type charset=utf-8 on REST responses. Use when the user says "i18n runtime", "Unicode encoding", "non-Latin characters", "Turkish broken", "CJK garbage", "data corruption on save", or before any release that ships to non-English locales.
---

# 🪐 orbit-i18n-runtime — Runtime data i18n correctness

`orbit-i18n` is gettext-only — it checks UI strings get wrapped in `__()`. This skill checks the **other half of i18n**: when the plugin writes Turkish, CJK, Arabic, Hindi, Cyrillic, or any non-Latin data into post_meta / options / REST responses / outgoing API bodies, does it survive the round-trip without corruption?

**Why this skill exists:** example-plugin shipped v1.0.x storing non-Latin content as `\uXXXX` escape sequences via default `wp_json_encode()`. WordPress's sanitization filters dropped the backslashes; "Yatırım" became visible garbage "Yu0131lu0131" in the database. The fix required patching 4 modules + 4 LLM provider classes + a 1-shot migration to repair existing data. `orbit-i18n` did not catch it because it was looking at `__()` wrapping, not JSON encoding flags. Plugin-agnostic — applies to any WP plugin that stores or transmits user-supplied content.

---

## What this skill checks

### 1. `wp_json_encode()` / `json_encode()` in storage paths

Scan every call site. If the encoded value flows into:
- `update_post_meta()`, `add_post_meta()`, `update_metadata()`, `update_user_meta()`, `update_term_meta()`
- `update_option()`, `update_site_option()`, `set_transient()`
- A REST response body (`rest_ensure_response`, `WP_REST_Response`)
- A `wp_remote_post()` / `wp_remote_request()` body posted to any external HTTP endpoint

Then it MUST include both flags:
```php
wp_json_encode( $data, JSON_UNESCAPED_UNICODE | JSON_UNESCAPED_SLASHES );
```

#### ❌ Bad
```php
update_post_meta( $post_id, '_my_summary', wp_json_encode( $summary ) );
// "Yatırım" → "Yatırım" in DB → backslash-stripped → "Yu0131ru0131m" garbage
```

#### ✅ Good
```php
update_post_meta(
    $post_id,
    '_my_summary',
    wp_json_encode( $summary, JSON_UNESCAPED_UNICODE | JSON_UNESCAPED_SLASHES )
);
```

### 2. LLM / external-API outgoing bodies

Every `wp_remote_post( $url, [ 'body' => ... ] )` where the body is JSON-encoded must use the flags. Without them:
- Wire bytes inflate ~6× for CJK / Arabic / Hindi content
- Token billing inflates proportionally
- Some upstream providers reject `\uXXXX` sequences in specific fields

#### Detection grep
```bash
# Files using wp_json_encode without UNESCAPED_UNICODE on the same line or next line
grep -rn 'wp_json_encode\|json_encode' --include='*.php' . \
  | grep -v 'JSON_UNESCAPED_UNICODE' \
  | grep -v 'tests/'
```
Every hit is a candidate. Inspect each: is the encoded value persisted or sent externally? If yes → flag.

### 3. REST response `Content-Type` charset

Custom REST routes that ship translated content MUST set `charset=utf-8` explicitly. Default WP responses do this, but custom output paths via `wp_die()`, `echo`, or direct `header()` calls often forget.

#### ❌ Bad
```php
header( 'Content-Type: text/markdown' );
echo $markdown;  // browser guesses encoding → mangles non-Latin
```

#### ✅ Good
```php
header( 'Content-Type: text/markdown; charset=utf-8' );
echo $markdown;
```

#### Detection grep
```bash
grep -rn "header(.*Content-Type" --include='*.php' . \
  | grep -v 'charset=utf-8' \
  | grep -v 'tests/'
```

### 4. Round-trip safety: writer/reader flag parity

If the writer uses `JSON_UNESCAPED_UNICODE`, the reader's `json_decode()` works on either format — no change needed. BUT: if you mix writers (some flagged, some not) on the same meta key, you get garbled data depending on which writer last wrote.

**Audit pattern:**
1. Find every plugin-namespaced meta key written via JSON encoding.
2. List every call site for that key.
3. All writers must use the same flag set.

### 5. Migration / repair routine for legacy data

If the plugin previously shipped without the flags, an upgrade routine must re-encode existing rows:

```php
add_action( 'plugins_loaded', function() {
    if ( get_option( 'myplugin_unicode_meta_migrated_v_X_Y_Z' ) ) {
        return;
    }
    // Query postmeta for rows containing \uXXXX patterns, json_decode + wp_json_encode
    // with new flags, $wpdb->update. Batch-cap at 2000 rows per request.
    // Set the option flag when row count < 2000.
} );
```

The skill flags the ABSENCE of this routine when the plugin's version history shows a pre-flag → post-flag transition.

### 6. Magic-quotes / `wp_unslash` interaction

After reading `$_POST` content destined for JSON encoding, `wp_unslash()` is mandatory BEFORE encoding. Without it, backslashes from slash-escaping survive into the encoded payload and combine with `\uXXXX` sequences to produce double-corrupted output.

```php
// ❌
$data = $_POST['data'];
update_post_meta( $id, '_my_data', wp_json_encode( $data ) );

// ✅
$data = wp_unslash( $_POST['data'] );
update_post_meta(
    $id, '_my_data',
    wp_json_encode( $data, JSON_UNESCAPED_UNICODE | JSON_UNESCAPED_SLASHES )
);
```

---

## Report format

```markdown
# i18n Runtime Audit — [Plugin]

## Summary
- wp_json_encode calls missing UNESCAPED_UNICODE: <N>
  - Persisted (post_meta/options): <X>     ← Critical
  - External API bodies:           <Y>     ← High
  - REST responses:                <Z>     ← High
  - Read-only / logs:              <W>     ← Low
- REST routes missing charset=utf-8: <N>
- Mixed writer/reader flag sets:     <N>
- Missing legacy-repair migration:    <Y/N>
- $_POST → wp_json_encode without wp_unslash: <N>

## Critical (block release)

### Persisted non-Latin corruption risk
**File:** includes/class-foo.php:305
**Code:**
```php
update_post_meta( $post_id, '_foo_data', wp_json_encode( $data ) );
```
**Fix:** add `JSON_UNESCAPED_UNICODE | JSON_UNESCAPED_SLASHES` to the second arg.
**Why:** stored \uXXXX → WP filter strips backslashes → "Yatırım" becomes "Yu0131ru0131m"

[Continue for every finding]
```

---

## Plug-in agnostic

This skill never references example-plugin or any specific plugin. It scans for **patterns**, not bugs. If a future plugin (example-plugin, example-plugin, example-plugin, third-party) ships any of the call shapes above without the flags, this skill flags it.

---

## Cross-references

- **`orbit-i18n`** — covers gettext wrapping. Run both. Different surfaces.
- **`orbit-i18n-js-parity`** — JS↔PHP label mismatches (different bug class).
- **`orbit-compat-polylang` / `orbit-compat-wpml`** — language-aware custom endpoints (different bug class — see §7 in each).
- **§10.6 in orbit-code-reviewer** — `$_POST` reads without `wp_unslash()` (related, broader scope).
- **CTO brain `orbit/00-cto`** — promote new corruption patterns here when discovered.

---

## Severity

- Persisted-meta corruption: **Critical** (silent data loss; multi-version migration to fix)
- External API body without flag: **High** (token inflation + potential upstream rejection)
- REST response without charset: **High** (consumer-side display corruption)
- Mixed flag sets across writers: **High** (last-writer-wins data corruption)
- Missing legacy-repair migration: **High** (existing installs stay broken after fix)
- `$_POST` without `wp_unslash` before encode: **High**

Block release on any Critical or High that isn't an explicit accepted risk in `orbit/00-cto`.

Attribution

Is this your skill, or is something wrong with this listing? Request removal or report an issue. Author removals are honored within 72 hours.

Comments

Loading comments…