# 11 — Database credentials (REDACTED)

> ⚠️ **All passwords in this document are redacted.** The actual strings live in `httpdocs/auth/classes/sdba/dbconnect.php` and must be rotated. The structure below is safe to publish internally; the live file is not.

## Connection string structure

`httpdocs/auth/classes/sdba/dbconnect.php` defines one PHP associative array per logical database. The arrays are consumed by SDBA's `::params()` helper when the caller asks for a specific data domain.

```php
<?php
// dbconnect.php — STRUCTURE ONLY — actual credentials redacted
$searchads_params = [
    'dbname' => 'searchmercials',
    'dbuser' => 'ostv',
    'dbpass' => '<REDACTED>',
    'dbhost' => '127.0.0.1',
];

$youportal_params = [
    'dbname' => 'youportal_aroundme_new',
    'dbuser' => 'ostv',
    'dbpass' => '<REDACTED>',
    'dbhost' => '127.0.0.1',
];

$beta_params = [
    'dbname' => 'beta_db',
    'dbuser' => 'ostv',
    'dbpass' => '<REDACTED>',
    'dbhost' => '127.0.0.1',
];

$domain_site_params = [
    'dbname' => 'domain_site',
    'dbuser' => 'ostv',
    'dbpass' => '<REDACTED>',
    'dbhost' => '127.0.0.1',
];
```

## Observed facts

- **Single user** (`ostv`) used for every database — a shared account with broad privileges
- **Same password** across all four databases (based on the exploration sub-agent's observations)
- **All local** (`127.0.0.1`) — no remote DB access
- **Plain-text** in a version-controlled file — present in every historical git revision
- **No rotation policy** — the password has been in the file since at least the 2024-06-03 "backup" commit

## Risk summary (from [08-security-audit.md](08-security-audit.md))

- Anyone with repo access or a stale clone has DBA-equivalent access
- Rotating the password requires touching `dbconnect.php` across the running host; without a deploy automation, rotation is friction-prone and therefore deferred indefinitely
- If the repo is or ever was public, the passwords must be considered permanently compromised

## Remediation plan

### Step 1 — rotate the MySQL user
```sql
ALTER USER 'ostv'@'localhost' IDENTIFIED BY '<new-random-password>';
FLUSH PRIVILEGES;
```

### Step 2 — move credentials to a `.env` outside the webroot
```bash
# /etc/searchmercials/env (chmod 640, owned by root:www-data)
DB_HOST=127.0.0.1
DB_USER=ostv
DB_PASS=<new-random-password>

SEARCHMERCIALS_DB=searchmercials
YOUPORTAL_DB=youportal_aroundme_new
BETA_DB=beta_db
DOMAIN_SITE_DB=domain_site
```

### Step 3 — load env in dbconnect.php
```php
<?php
// Replacement dbconnect.php
$envFile = '/etc/searchmercials/env';
if (!is_readable($envFile)) {
    throw new RuntimeException("DB env file missing or unreadable: $envFile");
}
$env = parse_ini_file($envFile);

$base = [
    'dbuser' => $env['DB_USER'],
    'dbpass' => $env['DB_PASS'],
    'dbhost' => $env['DB_HOST'],
];

$searchads_params    = $base + ['dbname' => $env['SEARCHMERCIALS_DB']];
$youportal_params    = $base + ['dbname' => $env['YOUPORTAL_DB']];
$beta_params         = $base + ['dbname' => $env['BETA_DB']];
$domain_site_params  = $base + ['dbname' => $env['DOMAIN_SITE_DB']];
```

### Step 4 — scrub git history
```bash
# Preferred: git-filter-repo (install via pipx install git-filter-repo)
git filter-repo --path httpdocs/auth/classes/sdba/dbconnect.php --invert-paths

# Force-push to all remotes — coordinate with every engineer who has a clone
```

If this repo is public, assume every password ever committed is already harvested. Rotating is the only defense.

### Step 5 — add to `.gitignore`
```
httpdocs/auth/classes/sdba/dbconnect.php
```

### Step 6 — add a tripwire
```bash
# Nightly cron: detect re-commit of dbconnect.php with real passwords
git log -p -- httpdocs/auth/classes/sdba/dbconnect.php \
  | grep -i "dbpass" \
  | grep -v '<REDACTED>' \
  && mail -s "dbconnect.php credential leak" security@example.com
```

## Other credential surfaces to audit

- **Apache vhost configs** (outside this repo) — may reference MySQL socket paths, cert paths
- **PHP-FPM pool configs** — environment variables
- **`receive-payment.php`** — likely stores PayPal/Authorize.net merchant credentials; audit separately
- **MaxMind licence key** (if using GeoIP2 downloads) — should also live in `.env`
- **SSL cert private keys** — already protected by filesystem mode on `/etc/ssl-wildcard/`; verify `0400 root:root`
