backend

Response Obfuscation Architecture

This document describes the current response obfuscation behavior for protected v1 Nexus routes.

Updated April 27, 2026

Response Obfuscation Architecture

This document describes the current response obfuscation behavior for protected v1 Nexus routes.

The canonical term for this subsystem is now response obfuscation. Older references to encryption describe the same route family historically, but the live implementation is the obfuscation flow documented here.

Use ../README.md for project context and ../API.md for the HTTP contract.

Overview

Nexus no longer uses the older application-layer X25519/AES session encryption flow for protected v1 data responses. The current system uses a lightweight, session-bound obfuscation layer intended to deter casual scraping and direct readability of response bodies.

Properties of the current model:

  • keeps transport security on HTTPS
  • obfuscates selected 2xx success payloads only
  • leaves errors as readable JSON envelopes
  • binds the obfuscation token to the authenticated JWT session_id
  • does not claim strong confidentiality against a determined client operator

Current obfuscated routes:

  • /v1/bookDetails
  • /v1/verses
  • /v1/chapterSummary
  • /v1/vedavaani
  • /v1/vedavaani/verseAssistant

Current plain authenticated JSON routes:

  • /v1/session
  • /v1/account

High-Level Flow

  1. Client authenticates and receives a JWT that includes session_id
  2. Client calls POST /v1/session
  3. Server creates an obfuscation session and returns:
    • obfuscationToken
    • version
    • expiresAt
  4. Client sends X-Obfuscation-Token on protected v1 data routes
  5. Middleware validates the token and obfuscates only successful responses
  6. Client reverses the wrapper locally
  7. Client calls DELETE /v1/session on logout or token invalidation

Token Model

The obfuscationToken is signed server-side and encodes:

  • session_id
  • version
  • exp
  • random nonce

Server validation checks:

  • token signature
  • token expiry
  • supported token version
  • session_id match against the authenticated JWT
  • active in-memory obfuscation session presence

Response Transform

For successful protected responses only:

  1. Minify the response envelope keys
    • success -> s
    • data -> d
    • cached -> c
    • pagination -> p
  2. Minify route-specific payload keys using the versioned alias map
  3. Serialize compact JSON
  4. Gzip-compress the bytes
  5. Apply the reversible nonce-seeded byte transform
  6. Base64url-encode the result

Headers on obfuscated success responses:

Content-Type: application/octet-stream
X-Obfuscated: 1
X-Obfuscation-Version: v1

Errors are never obfuscated.

Client Integration Notes

Development/debug mode can bypass obfuscation on protected success responses by sending:

x-debug-raw-response: true

That header is intended for local development and testing only. It is honored only when DENO_ENV is not production; production continues to obfuscate successful protected responses even if the header is sent.

Client decode sequence:

  1. Base64url decode the response body
  2. Extract nonce from the obfuscation token payload
  3. Reverse the nonce-seeded byte transform
  4. Gunzip
  5. UTF-8 decode
  6. JSON decode

Exact Byte Transform

The current v1 backend does not use XOR. The transform uses:

  • an offset derived from the first nonce byte
  • a rotating read position into the compressed byte array
  • an additive mask that uses both the nonce byte and the current index

Server-side transform:

offset = nonceBytes[0] % input.length;

for (let i = 0; i < input.length; i++) {
  const sourceIndex = (i + offset) % input.length;
  const salt = nonceBytes[(i + offset) % nonceBytes.length];
  output[i] = (input[sourceIndex] + salt + i) % 256;
}

Client-side reverse transform:

offset = nonceBytes[0] % input.length;

for (let i = 0; i < input.length; i++) {
  const salt = nonceBytes[(i + offset) % nonceBytes.length];
  rotated[i] = (input[i] - salt - i + 512) % 256;
}

for (let i = 0; i < input.length; i++) {
  const targetIndex = (i + offset) % input.length;
  output[targetIndex] = rotated[i];
}

Implementation Reference

Key files:

  • nexus/src/services/obfuscation.service.ts
  • nexus/src/middleware/obfuscation.ts
  • nexus/src/controllers/v1/keyExchange.controller.ts
  • nexus/src/routes/v1/index.ts

Responsibilities:

  • obfuscation.service.ts
    • issues and validates obfuscation tokens
    • stores active sessions in memory
    • minifies payload keys
    • applies and reverses the byte transform for tests
  • obfuscation.ts
    • enforces X-Obfuscation-Token
    • skips obfuscation for non-2xx responses
    • honors x-debug-raw-response
  • keyExchange.controller.ts
    • POST /v1/session creates a session
    • DELETE /v1/session invalidates the active session

Operational Notes

  • The session store is in-memory and short-lived.
  • Re-run POST /v1/session after JWT refresh when session_id changes.
  • Missing, expired, tampered, or mismatched tokens should return readable JSON errors so client recovery is straightforward.
  • This mechanism should be paired with auth, rate limits, and abuse monitoring for actual scraping resistance.

Testing Expectations

When this flow changes, verify:

  • POST /v1/session issues a signed token for a valid JWT
  • tampered, expired, or mismatched tokens fail with readable JSON errors
  • protected routes return obfuscated success payloads
  • protected route errors remain plain JSON
  • DELETE /v1/session invalidates the current token
  • raw-response debug mode still returns plain JSON
  • deobfuscation fixtures round-trip to the original envelope

Related Docs