# GCC Modernization Plan

> **Status:** Planning — review and adjust this document before execution begins.
> Each phase should be completed and tested before moving to the next.
> **Last updated:** 2026-02-23 — revised after full codebase security audit.

---

## Stack Recommendation

Stay on ColdFusion/Lucee for the backend — the 2.0 rewrite is functionally complete and the engine is solid. The highest-ROI path is:

1. Fix critical security and reliability issues first
2. Clean up legacy debt
3. Improve developer experience
4. Modernize the frontend (CSS/JS consolidation — no framework migration required)
5. **Long-term optional:** expose a thin REST API layer to enable future frontend decoupling

---

## Current Security Assessment

An audit of the codebase found the following:

- **~700+ SQL injection points** across 200+ files. Only **14 out of 900 files** (~1.5%) use `cfqueryparam`.
- **Zero XSS output encoding** — not a single call to `encodeForHTML()`, `HTMLEditFormat()`, or any encoding function exists in the codebase.
- **Plaintext password storage and comparison** in both the main game login and admin login.
- **Input sanitization functions exist but are never called** — `validateURL()`, `formValidate()`, and `hackCheck()` are defined in `Modules/Functions/Functions.cfm` but only `formValidate()` is called (in 1 file). The inline hack check in `i.cfm` line 53 is **disabled** by a `|| 1 == 2` short-circuit.
- **`scriptProtect` is not enabled** in `Application.cfc`.
- **Dynamic file inclusion** via `url.f` with only a basic character check guard.

---

## Phase 1 — Security (Critical, do first)

These are exploitable right now on a live server with active players.

### 1a. Global input protection — `Application.cfc` (immediate, no per-file changes)

Two changes in `Application.cfc` that apply protection across the entire application without touching any other file:

**1. Enable `scriptProtect`:**
```cfml
this.scriptProtect = "all";
```
Automatically strips `<script>`, `<object>`, `<embed>`, and `<applet>` tags from all `form`, `url`, `cookie`, and `cgi` scopes before any template runs. Not a complete XSS solution but a free first layer.

**2. Add input canonicalization to `onRequestStart()`:**
```cfml
// Canonicalize all user input to prevent encoding bypass attacks
for(var key in form) {
    if(isSimpleValue(form[key])) {
        form[key] = canonicalize(form[key], false, false);
    }
}
for(var key in url) {
    if(isSimpleValue(url[key])) {
        url[key] = canonicalize(url[key], false, false);
    }
}
```
`canonicalize()` decodes all encoding layers (URL encoding, HTML entities, double-encoding, etc.) so attackers cannot bypass downstream filters with encoding tricks.

**These two changes do not fix SQL injection or full XSS, but they raise the bar significantly for zero per-file effort.**

**Status:** [ ] Not started

---

### 1b. Harden login files — SQL injection + password hashing (3 files, highest impact)

The three login files are the single most dangerous attack surface. Each allows full authentication bypass via SQL injection, and all three store/compare passwords in plaintext.

**Files:**

| File | Current vulnerability | Fix required |
|------|----------------------|--------------|
| `p_login.cfm` (line 84) | `where nic='#form.nic#' and server=#form.server#` | Add `cfqueryparam` to login query; hash password comparison |
| `edmin/p_login.cfm` (line 33, 46) | `WHERE nic='#form.nic#'` + plaintext `get.password is form.password` | Add `cfqueryparam`; replace with hashed comparison (bcrypt preferred, SHA-256 + salt minimum) |
| `forum/b_login.cfm` (line 5-7) | `where nic='#form.nic#' and password='#form.password#'` — both fields injectable | Add `cfqueryparam`; hash password comparison |

**Password hashing migration strategy:**
1. Add a `password_hash` column to the `user` and `admin` tables (done — `Z_UpdateDB6_PasswordHash.cfm`)
2. On each successful login, if `password_hash` is empty, hash the verified plaintext password with Argon2id and store it, then clear the plaintext password
3. Once all active users have migrated, drop the plaintext `password` column
4. Uses Lucee's native `GenerateArgon2Hash(password, "Argon2id")` and `Argon2CheckHash(password, hash)` via the Argon2 extension (requires `libargon2` system library)

**Status:** [x] Implemented — login-time migration active in `p_login.cfm`, `edmin/p_login.cfm`, `forum/b_login.cfm`. New signups and password changes hash directly with Argon2id.

---

### 1c. SQL injection — `cfqueryparam` everywhere (systematic, file-by-file)

**The single largest task.** ~700+ injection points across 200+ files. There is no global fix for SQL injection — each `<cfquery>` with raw `#variable#` interpolation must be individually parameterized.

Current pattern (found across nearly all `f_com_*.cfm`, `edmin/*.cfm`, `forum/*.cfm`):
```cfml
WHERE userid = #session.userid# AND name = '#form.name#'
```

Required pattern:
```cfml
WHERE userid = <cfqueryparam value="#session.userid#" cfsqltype="cf_sql_integer">
AND   name   = <cfqueryparam value="#form.name#"      cfsqltype="cf_sql_varchar">
```

**Work in waves — each wave can be deployed independently:**

| Wave | Scope | Files | Risk level |
|------|-------|-------|------------|
| 1 | Login files (covered in 1b above) | 3 | Critical — authentication bypass |
| 2 | `p_signup.cfm`, `l/p_signup.cfm` — registration | 2 | Critical — account creation injection |
| 3 | All `f_com_*.cfm` player action files | ~75 | High — player-facing, every session |
| 4 | All `s_*.cfm` shared utility files | ~63 | High — called by many features |
| 5 | All `edmin/f_admin_*.cfm` admin files | ~100 | Medium — behind admin auth |
| 6 | `Z_*.cfm` background processes, `forum2/`, remaining files | ~60+ | Lower — not directly user-accessible or behind auth |

**Status:** [ ] Not started

---

### 1d. Dynamic file inclusion — whitelist `url.f`

`i_f_800.cfm` (lines 92-98) and `Application.cfc` (lines 233-241) build include paths directly from `url.f`:
```cfml
<cfinclude template="Modules\Pages\#url.f#.cfm">
<cfinclude template="f_#url.f#.cfm">
```

The current guard in `i_f_800.cfm` (line 2-4) only checks for `/`, `..`, and `\` — a character-level filter that may be bypassable with encoding. `Functions.cfm` has a `validateURL()` function that strips non-alphanumeric chars, but **it is never called anywhere in the codebase**.

Fix: build an explicit allowlist of all valid `url.f` values (by scanning for `f_*.cfm` files and `Modules/Pages/*.cfm` files) and reject anything not on it before any routing or include occurs. Place the check in `onRequestStart()` so it runs before any template.

**Status:** [ ] Not started

---

### 1e. XSS — output encoding

There are **zero instances** of `encodeForHTML()`, `encodeForHTMLAttribute()`, `HTMLEditFormat()`, or any output encoding function in the entire codebase. Every user-supplied value — player nicknames, chat messages, forum posts, form inputs — is rendered as raw HTML.

This means any player can inject JavaScript that executes for every other player who views the content (stored XSS).

**Lucee provides context-specific encoding functions:**

| Function | Use when outputting into... | Example |
|----------|---------------------------|---------|
| `encodeForHTML()` | HTML body text | `<td>#encodeForHTML(name)#</td>` |
| `encodeForHTMLAttribute()` | HTML attribute values | `value="#encodeForHTMLAttribute(val)#"` |
| `encodeForJavaScript()` | JavaScript string literals | `var x = '#encodeForJavaScript(val)#'` |
| `encodeForURL()` | URL parameters | `?name=#encodeForURL(val)#` |
| `encodeForCSS()` | CSS property values | `color: #encodeForCSS(val)#` |

The legacy `HTMLEditFormat()` still works but only escapes `<`, `>`, `&`, `"`. The `encodeFor*` family is more thorough and context-aware.

**Priority order for encoding:** Focus on any template that outputs user-generated content visible to other players:
1. Chat/shoutbox output in `i_f_800.cfm` (line 259 — `#name#` and `#post#` rendered raw)
2. Private messages (`f_pm.cfm`)
3. Sector messages (`f_com_msgsector*.cfm`)
4. Forum posts (`forum2/`)
5. Player profile / intel pages (`f_com_intel.cfm`, `f_rank.cfm`)
6. All remaining templates

**Status:** [ ] Not started

---

### 1f. Credentials out of source control

`Application.cfc` lines 194-206 have hardcoded PayPal client ID and secret (both sandbox and production):
```cfml
APPLICATION.PayPal_ClientID = "AdzH1SZl-Erou...";
APPLICATION.PayPal_Secret   = "EMU3LUYluXVi6...";
```

Move all secrets to a `.env` file or Lucee server environment variables. Add `.env` to `.gitignore`. The encrypted DB passwords (`encrypted:6cea...`) in `Application.cfc` are handled by Lucee's encryption scheme and can stay in-repo.

**Status:** [ ] Not started

---

### 1g. Remove hardcoded user bypass logic

`p_login.cfm` lines 89-93 contain hardcoded logic for specific user IDs:
```cfml
<cfif checkuser.id == 302041><cfset CF22 = 51091118439458><cfset UIP = '174.134.131.147'></cfif>
<cfif checkuser.id == 388580><cfset CF22 = 265913861215821><cfset UIP = '107.77.224.185'></cfif>
```

These are security backdoors — specific users get special login treatment tied to hardcoded IP addresses. Remove these and handle any necessary exceptions through the admin panel or database flags.

**Status:** [ ] Not started

---

### 1h. Fix `onError` information disclosure

`Application.cfc` lines 257-274 output raw error messages, full file paths, line numbers, and stack traces directly to the browser:
```cfml
Error.TagContext[i]['template']   // Full server file path
Error.TagContext[i]['line']       // Line number
Error.TagContext[i]['Raw_Trace']  // Full stack trace
```

This tells attackers exactly what technology, file structure, and code paths exist. Replace with a generic user-facing error page that logs full details server-side only.

**Status:** [ ] Not started

---

### 1i. Replace `evaluate()` calls

`i_f_800.cfm` uses `evaluate()` to dynamically build variable names:
```cfml
#evaluate("application.gc_leftnav_#left(url.f,2)#")#           <!-- line 70 -->
evaluate("application.pm_#session.userid#_last")                <!-- line 104 -->
evaluate("application.server#session.server#_turnmin")          <!-- line 118 -->
```

Replace with struct key access:
```cfml
application["gc_leftnav_#left(url.f,2)#"]
application["pm_#session.userid#_last"]
application["server#session.server#_turnmin"]
```

Struct bracket notation is functionally identical but cannot execute arbitrary code.

**Status:** [ ] Not started

---

## Phase 2 — Reliability

### 2a. Restore error handling in `i.cfm`

The main entry point's `<cftry>/<cfcatch>` is commented out (lines 12 and 135-137):
```cfml
<!--- <cftry> --->
  ... entire page logic ...
<!---
<cfcatch type="Any"><cfset url.PageError = 1><cfdump var=#cfcatch#></cfcatch>
</cftry> --->
```

Uncomment the try/catch, **remove the `<cfdump>`** (information disclosure), and route the catch to the centralized logger built in Phase 2c. Display a generic error page to the user.

**Status:** [ ] Not started

---

### 2b. Background jobs must not silently fail

`Z_Hourly_Processes.cfm` wraps entire sections in `<cfcatch>` blocks that do nothing:
```cfml
<cftry>
  <!-- database update code -->
<cfcatch type="any">
</cfcatch>
</cftry>
```

Every empty catch block in `Z_Hourly_Processes.cfm`, `z_endturn.cfm`, and `Z_Upkeep_Calc.cfm` must at minimum log the error. Silently swallowed failures in background jobs are how turn processing or upkeep bugs go undetected for days.

**Status:** [ ] Not started

---

### 2c. Centralized structured logging

Replace the current email-only approach (`s_error2.cfm` -> `cfmail`) with a logging CFC that:
- Writes to a `logs/` directory with daily rotation
- Sends email only for `CRITICAL`/`ERROR` severity
- Accepts severity levels: `DEBUG`, `INFO`, `WARN`, `ERROR`, `CRITICAL`

Hook `Application.cfc`'s `onError()` handler into it. All empty `cfcatch` blocks from Phase 2b should call this logger instead.

**Status:** [ ] Not started

---

### 2d. Health check endpoint

Add `health.cfm` at the root. It should:
- Run a lightweight DB query against `gcc` (e.g. `SELECT 1`)
- Return JSON: `{ "status": "ok", "db": "ok", "version": "2.77" }`
- Return HTTP 500 on any failure

Enables uptime monitoring via any external service (UptimeRobot, Better Stack, etc.).

**Status:** [ ] Not started

---

## Phase 3 — Legacy Debt Cleanup

### 3a. Delete confirmed dead files

The following files have no inbound references and are safe to delete:

| File | Reason |
|------|--------|
| `p_login_old.cfm` | Superseded by `p_login.cfm` |
| `s_com_attack_finish_bak.cfm` | Backup file |
| `f_com_colupgrade-bak.cfm` | Backup file |
| `f_com_test1.cfm` | Test file |
| `pop_test.cfm` | Test file |
| `Modules/Pages/com_project2-.cfm` | Malformed name, no references |
| `Modules/Pages/com_project2---.cfm` | Malformed name, no references |
| `forum/` (entire directory) | Superseded by `forum2/` |

> **Note:** Confirm each deletion with a project-wide search before removing.

**Status:** [ ] Not started

---

### 3b. Remove dead server slot code

Only Real-Time (slot 1) and Turn-Based (slot 2) exist. Code for slots 3-5:

- `i.cfm` line 105: server 5 routing block (`cfif session.server is 5`) — remove
- `s_com_market_use.cfm` line 352 — server 4 reference — remove
- `s_com_market_use_ren.cfm` line 357 — server 4 reference — remove
- `Application.cfc`: Change `this.ServerMax = 5` to `this.ServerMax = 2` and add named constants for the two live servers

**Status:** [ ] Not started

---

### 3c. Annotate legacy datasources

`ucc`, `ucc_log`, `gcs`, `gcs_log`, `ds_uc`, `ds_do` are defined/referenced but not used for any active game logic. Until they can be fully removed:

- Add `// LEGACY — do not use` comments to each datasource definition in `Application.cfc`
- Add the same comment at each usage point in `edmin/i.cfm`, `s_loadsystem.cfm`, `s_loadvar.cfm`, `hef.cfm`
- Track remaining references so they can be cleaned up in a later pass
- Note: `Application.cfc` has **duplicate datasource definitions** (lines 37-91 and 97-179) — consolidate to one set

**Status:** [ ] Not started

---

### 3d. Clean up dead security code in `Functions.cfm` and `i.cfm`

- `Functions.cfm`: `validateURL()` is defined but never called. `hackCheck()` is defined but never called. Either wire them up properly or remove them to avoid false confidence.
- `i.cfm` line 53: The inline hack detection block is **disabled** by `|| 1 == 2` at the end of the condition. Either remove the dead code or fix and re-enable it.
- If Phase 1a-1c are complete, these blacklist-based checks become redundant — parameterized queries and output encoding are the real fix. Remove the dead code.

**Status:** [ ] Not started

---

## Phase 4 — Developer Experience

### 4a. Consistent authentication guard

Authenticated include files currently mix `ihasrunflag` and `session.userid` checks inconsistently — some check both, some only one, some neither. Define one standard guard and apply it uniformly:

```cfml
<cfif parameterExists(ihasrunflag) IS "NO" OR parameterExists(session.userid) IS "NO">
  <cfabort>
</cfif>
```

Apply to all `f_com_*.cfm`, `s_*.cfm`, and `Modules/` includes. Consider extracting this into a custom tag (`<cf_auth_guard>`) so there is one canonical implementation.

**Status:** [ ] Not started

---

### 4b. Expand documentation

Build on `project_overview.md`. Add per-subsystem docs as each area is touched. Suggested documents:

- `docs/turn-system.md` — how turns are generated, Z_ processes, timing
- `docs/combat.md` — attack flow, f_com_attack*.cfm, s_com_attack*.cfm
- `docs/economy.md` — market, projects, income, upkeep
- `docs/admin.md` — edmin/ structure and capabilities
- `docs/database-schema.md` — key tables in `gcc`

Each doc should be written as the corresponding code is touched, not all at once.

**Status:** [ ] Not started

---

### 4c. Git hygiene

- Add `.gitignore` covering: `.env`, `WEB-INF/`, `logs/`, `*.log`, `.DS_Store`, `Thumbs.db`
- Audit what is currently committed (PayPal secrets, encrypted DB passwords) and decide what stays vs. moves to environment config
- Consider a `CONTRIBUTING.md` with the file naming conventions and the standard auth guard pattern

**Status:** [ ] Not started

---

## Phase 5 — Player UX / Frontend (lower priority)

### 5a. Resolve the jQuery version split

The authenticated game layout (`i_f_800.cfm`) loads **jQuery 1.11.1** (released 2014). Public pages load **jQuery 3.4.0**. These should be the same version.

Steps:
1. Upgrade `i_f_800.cfm` to jQuery 3.x
2. Test TableSorter — the `jquery.tablesorter` version in use may need upgrading to the maintained fork (`mottie/tablesorter`) which supports jQuery 3
3. Audit `JS/Mods.js` and `i_js.js` for jQuery 1.x-only APIs (`.live()`, `.die()`, `.size()`, etc.)

**Status:** [ ] Not started

---

### 5b. Purge deprecated HTML

Systematic pass across all templates to remove:

- `<font>` tags — replace with CSS classes (found in `p_login.cfm` and others)
- `bgcolor`, `align`, `nowrap`, `width` as HTML attributes on `<table>`/`<td>` — replace with CSS
- Inline layout styles (`style="width: 35%"` on tables, etc.) — move to classes
- `<center>` tags if any remain

**Status:** [ ] Not started

---

### 5c. CSS consolidation (future)

59 CSS files with no clear ownership or load order is unmaintainable. Target structure:

| File | Purpose |
|------|---------|
| `CSS/base.css` | Resets, CSS custom properties, typography |
| `CSS/layout.css` | Grid, wrappers, page structure |
| `CSS/components.css` | Buttons, cards, tables, forms, modals |
| `CSS/game.css` | Game-specific UI (resource bars, ship displays, etc.) |
| `CSS/public.css` | Unauthenticated pages only |
| `CSS/admin.css` | `edmin/` only |

Files to retire: `RenStyle.css` (fixed-width legacy), duplicate rules across `Style.css` / `main.css`, per-page CSS files that can be absorbed.

**Status:** [ ] Not started

---

### 5d. Mobile/responsive pass on game UI (future)

`i_f_800.cfm` uses Bootstrap 4 grid for the outer shell, but inner game content still uses table-based layouts and fixed widths (`width: 1024px` in `RenStyle.css`). Focus the first pass on the highest-traffic pages:

1. Colony overview
2. Ship building
3. Attack/combat
4. Research

Goal: pages should be usable (not perfect) on a 390px wide mobile screen.

**Status:** [ ] Not started

---

## Execution Order

```mermaid
flowchart TD
    P1a["1a: Global input protection (Application.cfc)"] --> P1b["1b: Harden 3 login files"]
    P1b --> P1g["1g: Remove hardcoded user bypasses"]
    P1g --> P1h["1h: Fix onError info disclosure"]
    P1h --> P1i["1i: Replace evaluate() calls"]
    P1i --> P1d["1d: url.f whitelist"]
    P1d --> P1f["1f: Credentials to env"]
    P1f --> P1c["1c: cfqueryparam wave 2-3 (signup, f_com_*)"]

    P1c --> P1e["1e: XSS output encoding (high-traffic templates)"]
    P1e --> P1c2["1c continued: cfqueryparam waves 4-6"]

    P1c2 --> P2a["2a: Restore i.cfm error handling"]
    P2a --> P2b["2b: Fix silent background job failures"]
    P2b --> P2c["2c: Centralized logging CFC"]
    P2c --> P2d["2d: health.cfm endpoint"]

    P2d --> P3a["3a: Delete dead files"]
    P3a --> P3b["3b: Remove server slot 3-5 code"]
    P3b --> P3c["3c: Annotate legacy datasources"]
    P3c --> P3d["3d: Clean up dead security code"]

    P3d --> P4a["4a: Consistent auth guard"]
    P4a --> P4b["4b: Expand docs"]
    P4b --> P4c["4c: Git hygiene"]

    P4c --> P5a["5a: jQuery upgrade"]
    P5a --> P5b["5b: Purge deprecated HTML"]
    P5b --> P5c["5c: CSS consolidation"]
    P5c --> P5d["5d: Mobile responsive pass"]

    style P1a fill:#ff4444,color:#fff
    style P1b fill:#ff4444,color:#fff
    style P1g fill:#ff4444,color:#fff
    style P1h fill:#ff4444,color:#fff
    style P1i fill:#ff6644,color:#fff
    style P1d fill:#ff6644,color:#fff
    style P1f fill:#ff6644,color:#fff
    style P1c fill:#ff8844,color:#fff
    style P1e fill:#ff8844,color:#fff
    style P1c2 fill:#ff8844,color:#fff
```
