# Secret-Key Scan — Workspace Security Audit

**Verdict:** ⚠️ **FLAGS FOUND** — 1 real hardcoded secret value (1 distinct secret, 4 tracked files). MIS code itself is **CLEAN** (no vendor secrets in committed MIS source).

**Generated:** Sun, May 24 2026 · 11:02 PM EDT (NY)
**Scope:** READ-ONLY scan for exposed secret VALUES in *committed/tracked* code.
**Working dir:** `C:\Users\ztrei\OneDrive\2. Hook Street\05. 2026 BH\`
**Repos scanned:** `hookstreet-workspace` (root, private), `MIS/` (nested, private), `HookStreet-Business-OS/sheets` (nested, private).
**Method:** `git ls-files` to establish tracked-vs-ignored, then ripgrep for vendor-key prefixes (`sk-ant-`, `AKIA`, `AIza`, `ghp_`, `xox`), credential assignments, private-key headers, Schwab/Twilio/Finnhub patterns, and 32+ char hardcoded strings. Only TRACKED files count as exposures (gitignored = local-only).

---

## HEADLINE

The MIS primary code Sam just copied/changed is **clean** — every Schwab/Finnhub credential is read by NAME from the sheet CONTROL tab or Script Properties; no secret VALUE is hardcoded in any committed MIS file.

The one real exposure is **unrelated to MIS**: the **Obligations PWA shared API key** (`K4vWx…`, 48 chars) is hardcoded in 4 tracked files. It is a self-minted shared secret gating Sam's own private Apps Script Web App (low blast radius — private to his account, no money/PII directly behind it), but it IS a committed secret value and should be rotated + moved to a non-tracked location.

---

## PER-LOCATION FINDINGS

### MIS/ (priority) — ✅ CLEAN
- Tracked code: `src/Code.js`, `src/emailDailySnapshot.js`, `MIS-v1-script/emailDailySnapshot.js`, `scripts/*.js`, `scripts/v11_microservices/Code.gs`, `python/MIS_v8_*.py`.
- Schwab/Finnhub creds are read correctly:
  - `src/Code.js:1147` → `getControlValue_('EARNINGS_API_KEY')` (name only)
  - `src/emailDailySnapshot.js:2583-2587` → `misReadControl_('SCHWAB_CLIENT_SECRET')`, `…('SCHWAB_REFRESH_TOKEN')` (names only)
  - `src/emailDailySnapshot.js:691-694` → seed-defaults array writes EMPTY strings (`['SCHWAB_CLIENT_SECRET', '']`) — placeholder rows, not values.
- `python/` and `MIS/scripts/` Python: zero `os.environ`/`getenv`/hardcoded-secret hits.
- **Note (not a code finding):** MIS audit docs (`MIS_Master_v11_0f_Forensic_Teardown.md`, `notes mis.txt`, `FINAL_STRUCTURE_RECOMMENDATION.md`) repeatedly flag that the LIVE Schwab `CLIENT_ID/CLIENT_SECRET/REFRESH_TOKEN` + Finnhub key sit in PLAINTEXT in the Google Sheet's CONTROL/KEY_VALUE tab. That is a **sheet-side** exposure (anyone with viewer access to the sheet can read them), NOT a committed-code exposure — so it is out of scope for this code scan, but the standing remediation (rotate at developer.schwab.com + Finnhub, migrate to PropertiesService) remains open per those docs. No credential VALUES appear in any tracked file.

### command-inbox/ — ✅ CLEAN (exemplary)
- `Code.js`, `start-here.gs`, `telegram-webhook-worker.js`: every secret (`INBOX_SECRET`, `NOTIFY_SECRET`, `TELEGRAM_BOT_TOKEN`, `ANTHROPIC_API_KEY`, `TWILIO_ACCOUNT_SID/AUTH_TOKEN/FROM`) read via `PropertiesService.getScriptProperties().getProperty(...)`.
- `README.md` documents the pre-2026-05-19 hardcoded secret was rotated out and its backup file (`live-command-inbox-…-20260519.gs`) is **gitignored**. Confirmed not tracked.

### HookStreet-Business-OS/sheets — ✅ CLEAN
- Tracked: `src/Code.js`, `src/v3-obligations.js`, `src/eden-*.js`. No secret-value assignments found.
- `sheets-v2/` is clasp-only (NOT in git per CLAUDE.md) → local-only, out of committed scope.

### levsms/router — ✅ CLEAN
- `Code.js:217-275`: Twilio `ACCOUNT_SID`/`AUTH_TOKEN`/`FROM_NUMBER`/`STATUS_CALLBACK_URL` all via `props.getProperty(...)`. README explicitly says "Never put Twilio tokens in this repo or in a sheet cell."

### tools/, scripts/, root-level — ✅ CLEAN
- `tools/underwriting/hsc_underwriter_v0.py`, `tools/drive/`, `tools/portal/`, `scripts/transcribe_*.py`, `scripts/build_index.py`: zero hardcoded secrets.
- Root `wrangler.toml`, `.github/workflows/deploy-outputs.yml`: no secret values (CI uses GitHub Actions secrets, not inlined).

### outputs/ + archive/ — ⚠️ contains the one flagged secret (see table)
- Root-level `.xlsx` files (HookStreet_Command_Center_*, Monthly Obligations*) not parsed for cell contents — binary; the documented Schwab plaintext lives in the live Google Sheet, not these local copies, and they are not code.

---

## REAL EXPOSED SECRETS (committed)

| File:line | Type | Redacted value (first-4 + len) | Committed? |
|---|---|---|---|
| `outputs/pwa-deploy/index.html:190` | PWA→Apps Script shared API key | `K4vW…` (48 chars) | ✅ TRACKED |
| `outputs/2026-05-07_pwa-obligations-view.html:179` | same key (duplicate) | `K4vW…` (48 chars) | ✅ TRACKED |
| `outputs/2026-05-08_14-54_briefing_session-30-ipad-handoff.html:140` | same key (in a copy-block) | `K4vW…` (48 chars) | ✅ TRACKED |
| `archive/2026-05-12_tilde-folder/~hookstreet-workspace/pwa-api-script/Code.js:21` | same key (server side `API_KEY:` config) | `K4vW…` (48 chars) | ✅ TRACKED |

**One distinct secret, 4 files.** It is the shared token the Obligations PWA sends to Sam's private Apps Script Web App (`…/exec`) to gate reads. The PWA file even comments: "treat as a secret … light blast radius (private to your account)."

**Blast radius:** LOW–MEDIUM. The repo is **private** (`hookstreet-workspace`), so the key is not public on the open internet. Worst case if the repo leaked: someone could query Sam's obligations Web App (read-only money-list data) — no banking write access, no vendor account, no PII beyond bill names/amounts. Still a real committed secret.

**Recommended remediation (not done — read-only audit):**
1. Rotate the key: generate a new random string, set it in the Apps Script **Script Properties** (e.g. `PWA_API_KEY`), and have the script read `getProperty('PWA_API_KEY')` instead of a hardcoded constant.
2. Update the live PWA to fetch/inject the key at deploy time rather than committing it inline (or accept the constant only in a gitignored deploy artifact).
3. Optionally scrub the value from the 4 tracked files (git history will still hold it — full purge needs `git filter-repo`/BFG; given private repo + low blast radius, rotation alone is the pragmatic fix).

---

## SAFE PATTERNS CONFIRMED (secrets correctly externalized)

| Location | Secret(s) | Pattern |
|---|---|---|
| `MIS/src/emailDailySnapshot.js` | SCHWAB_CLIENT_ID / _SECRET / _REFRESH_TOKEN | `misReadControl_('NAME')` — name-only read from sheet CONTROL tab |
| `MIS/src/Code.js:1147` | EARNINGS_API_KEY (Finnhub) | `getControlValue_('EARNINGS_API_KEY')` — name-only |
| `command-inbox/Code.js` + `start-here.gs` | INBOX_SECRET, NOTIFY_SECRET, TELEGRAM_BOT_TOKEN, ANTHROPIC_API_KEY, TWILIO_* | `PropertiesService.getScriptProperties().getProperty(...)` |
| `levsms/router/Code.js` | TWILIO_ACCOUNT_SID / AUTH_TOKEN / FROM_NUMBER | `props.getProperty(...)` |
| `HookStreet-Business-OS/sheets/src/*.js` | (none hardcoded) | n/a — clean |

**Placeholder-only `sk-ant-` mentions (NOT keys):**
- `docs/legacy/SETUP_GUIDE.md:64` — "Add property `ANTHROPIC_API_KEY` = your `sk-ant-...` key" (instruction)
- `archive/graveyard/2026-05-19_command-inbox-templates/02_apps_script_claude_router.gs:16` — "Value: your sk-ant-... key" (instruction)

**No matches for:** `AKIA` (AWS), `AIza` (Google API), `ghp_`/`gho_` (GitHub), `xox` (Slack), `BEGIN … PRIVATE KEY` / `BEGIN RSA`, `Bearer <token>`, Twilio `AC` + 32-hex value, or any `refresh_token = "<value>"` assignment in tracked code.

**Note on `AKfycb…` Web App URLs:** several tracked files contain `https://script.google.com/macros/s/AKfycb…/exec` deployment URLs. These are deployment IDs, **public-by-design** (the endpoint is meant to be reachable); they are gated by the API_KEY above, not themselves secrets. Listed for completeness, not flagged.

---

## Source trail
- **Artifact:** `C:\Users\ztrei\OneDrive\2. Hook Street\05. 2026 BH\outputs\2026-05-24_23-02_audit_secret-key-scan.md`
- **Working dir:** `C:\Users\ztrei\OneDrive\2. Hook Street\05. 2026 BH\`
- **Root repo:** `zee78900/hookstreet-workspace` (private)
- **Scan type:** READ-ONLY. No files modified, no secrets rotated, no values written into this report (all redacted to first-4 + length).
