# Variable Classification — PUBLIC, SENSITIVE, and SERVER Tiers > REP's three-tier security model for environment variables. PUBLIC for plaintext, SENSITIVE for AES-256 encrypted, SERVER for gateway-only. Classified by naming convention. Source: https://rep-protocol.dev/concepts/variable-classification/ REP classifies every environment variable into one of three security tiers based on its prefix. This is the foundational design decision — it forces an explicit security choice for every variable. ## The three tiers | Prefix | Tier | Behaviour | SDK Access | |---|---|---|---| | `REP_PUBLIC_*` | **PUBLIC** | Plaintext JSON in page source | `rep.get('KEY')` (sync) | | `REP_SENSITIVE_*` | **SENSITIVE** | AES-256-GCM encrypted blob | `await rep.getSecure('KEY')` | | `REP_SERVER_*` | **SERVER** | Never sent to client | N/A | | `REP_GATEWAY_*` | *(config)* | Gateway configuration, not app vars | N/A | Variables without a `REP_` prefix are ignored by the gateway entirely. This prevents accidental exposure of system variables like `PATH`, `HOME`, or database credentials. ## Classification rules 1. Classification is determined **exclusively by prefix**. There is no override mechanism. 2. The gateway will not inject any variable without a `REP_` prefix. 3. Prefixes are stripped before injection: `REP_PUBLIC_API_URL` becomes `API_URL` in the payload. 4. Names must be unique after stripping. If `REP_PUBLIC_API_URL` and `REP_SENSITIVE_API_URL` both exist, the gateway refuses to start. ## Decision tree Use this guide to classify each variable: ``` Does the value contain credentials, tokens, or keys? ├── Yes → Does it need to reach the browser at all? │ ├── No → REP_SERVER_* │ └── Yes → REP_SENSITIVE_* └── No → Is it configuration the app needs at runtime? ├── Yes → REP_PUBLIC_* └── No → REP_SERVER_* or don't use REP_ prefix ``` ## Common misclassifications | Variable | Wrong Tier | Correct Tier | Reason | |---|---|---|---| | `DATABASE_URL` | PUBLIC | **SERVER** | Database credentials must never reach the browser | | `STRIPE_SECRET_KEY` | SENSITIVE | **SERVER** | Secret keys should never be decryptable client-side | | `STRIPE_PUBLISHABLE_KEY` | SERVER | **PUBLIC** | Publishable keys are designed to be public | | `API_URL` | SENSITIVE | **PUBLIC** | API endpoints are visible in network requests anyway | | `JWT_SECRET` | SENSITIVE | **SERVER** | Signing secrets must stay server-side | | `OAUTH_CLIENT_ID` | SERVER | **SENSITIVE** | Client IDs are needed browser-side but not public | | `ANALYTICS_WRITE_KEY` | PUBLIC | **SENSITIVE** | Write keys allow data ingestion — encrypt them | | `FEATURE_FLAGS` | SENSITIVE | **PUBLIC** | Feature flag names have no security value | | `SENTRY_DSN` | PUBLIC | **SENSITIVE** | DSNs contain project-specific ingestion endpoints | | `APP_VERSION` | SENSITIVE | **PUBLIC** | Version strings are informational, not secret | | `INTERNAL_SERVICE_TOKEN` | SENSITIVE | **SERVER** | Service-to-service tokens must not reach the browser | | `CDN_BASE_URL` | SENSITIVE | **PUBLIC** | CDN URLs are visible in every asset request | > **Tip** > > When in doubt, start with the most restrictive tier (SERVER) and relax only when you can articulate why the browser needs the value. ## Automatic secret detection (guardrails) At startup, the gateway scans all `REP_PUBLIC_*` values for patterns indicating potential misclassification: | Detection | Description | |---|---| | **Shannon entropy > 4.5** | Random-looking strings typical of API keys and tokens | | **Known key formats** | AWS keys (`AKIA...`), JWTs (`eyJ...`), GitHub tokens (`ghp_...`), Stripe keys (`sk_live_...`), private keys (`-----BEGIN`) | | **Length anomalies** | Strings > 64 characters that may be encoded secrets | A variable declared `type: csv` in the [manifest](/guides/manifest/) is scanned one element at a time (split on commas, whitespace trimmed) rather than as one string. A long list of short feature flags therefore doesn't trip the length check under `--strict`. A long or secret-shaped token inside the list is still flagged, and the warning names its position (`csv element 3: …` in the message, `csv_element` in the log). Every other value is scanned whole, and so is every value in the dev plugins, which [don't read the manifest](/guides/feature-flags/#dev-plugins). When a potential misclassification is detected: - The gateway logs a **WARNING** with the variable name (never the value) - In `--strict` mode, the gateway refuses to start - The gateway never silently changes classification — you must fix the prefix > **Caution** > > Guardrails use heuristics, not certainty. A low-entropy secret (like a short password) may not trigger detection. Use `--strict` mode in production and review your `.rep.yaml` manifest in code review. ## Manifest validation When a `.rep.yaml` manifest is present, the gateway can validate at startup that: - All required variables are present in the environment - Variable values match declared types (url, number, enum, etc.) - Variables are in the correct tier See the [Manifest guide](/guides/manifest/) for details.