CodeIssuesDiscussionsWikiPull RequestsProjectsCommitsActionsReleasesContributorsPulse● GatesSecuritySettingsDeploymentsPipelineInsightsAgents✨ Explain✨ Ask AI✨ Workspace✨ Spec✨ Tests▓ Debt Map✨ NL Search🏛 Archaeology
Blame · Line-by-line history
AUDIT-2026-07-14.md
Each line is annotated with the commit that last touched it. Click any SHA to jump to that commit and see the surrounding change.
| fb37dd6 | 1 | # AUDIT — 2026-07-14 — Public-readiness for external AI platforms |
| 2 | ||
| 3 | **Goal:** certify gluecron.com can receive PRs from external AI platforms | |
| 4 | (MCP + git-over-HTTP), self-canonically, without defaulting to GitHub. | |
| 5 | ||
| 6 | Legend: ✅ verified live ⬜ pending ⚠️ known gap | |
| 7 | ||
| 8 | ## Executive summary | |
| 9 | ||
| 10 | The platform is **ready for external platforms to onboard via the OAuth | |
| 11 | connector flow today**. One critical bug was found and fixed live during | |
| 12 | this audit: the MCP surface never signaled `invalid_token`, so any client | |
| 13 | whose access token aged past 1 hour got stuck in a permanent | |
| 14 | "token expired" reconnect loop even though the server's refresh grant | |
| 15 | worked. Fixed in `b5a7bb3`, deployed 2026-07-14 02:03 UTC, verified with | |
| 16 | live requests. The remaining ⬜ items are operator-gated (PAT-authenticated | |
| 17 | journey run, offsite backup arming) — commands are staged below. | |
| 18 | ||
| 19 | ## 1. The critical bug — MCP "token expired" reconnect loop (FIXED) | |
| 20 | ||
| 21 | Root cause (three interacting defects): | |
| 22 | ||
| 23 | 1. `src/middleware/auth.ts` `softAuth`: an expired/revoked `glct_`/`glc_` | |
| 24 | bearer was silently downgraded to **anonymous** — nothing recorded that a | |
| 25 | token was presented and rejected. | |
| 26 | 2. `src/routes/mcp.ts`: the 401 challenge omitted RFC 6750 | |
| 27 | `error="invalid_token"`, so clients could not distinguish "token died → | |
| 28 | refresh" from "never authenticated → first-time auth". The refresh_token | |
| 29 | grant (fully implemented, with rotation) sat unused. | |
| 30 | 3. The MCP handshake (`initialize`/`tools/list`/`ping`) was fully open, so a | |
| 31 | client reconnect **falsely succeeded** on a dead token, then looped on | |
| 32 | `tools/call`. | |
| 33 | ||
| 34 | Fix (`b5a7bb3`, locked-file edit to `auth.ts` owner-approved 2026-07-14): | |
| 35 | `softAuth` now flags `bearerInvalid`; `/mcp` answers **every** method — | |
| 36 | including the handshake — with `401` + | |
| 37 | `WWW-Authenticate: Bearer error="invalid_token", …` when a dead bearer is | |
| 38 | presented. Anonymous discovery/handshake unchanged; anonymous `tools/call` | |
| 39 | still gets the connector sign-in challenge (deliberately without `error=`). | |
| 40 | ||
| 41 | Live verification (2026-07-14, prod): | |
| 42 | ||
| 43 | - ✅ dead `glct_` on `initialize` → 401 + `error="invalid_token"` (loop-breaker) | |
| 44 | - ✅ dead `glct_` on `tools/list` → 401 + `error="invalid_token"` | |
| 45 | - ✅ anonymous `tools/call` → 401 challenge with `resource_metadata=`, **no** `error=` | |
| 46 | - ✅ anonymous `initialize` → 200 (discovery open) | |
| 47 | - ✅ regression tests: 125 pass (mcp/oauth/well-known), 83 pass (mcp-write/git-push-auth/api-tokens/push) | |
| 48 | ||
| 49 | ## 2. Doctor run (scripts/doctor.ts, new — `bun run doctor`) | |
| 50 | ||
| 51 | First run against `https://gluecron.com` (anonymous sections): **ALL GREEN**. | |
| 52 | ||
| 53 | - ✅ **a. Endpoint smoke** — 15/15 (healthz, readyz, version, login/register/ | |
| 54 | landing/explore/demo/pricing/status, api-v2 health, mcp discovery, | |
| 55 | manifest, sw, dxt) | |
| 56 | - ✅ **b. OAuth provider** — 8/8: AS metadata advertises `refresh_token` + | |
| 57 | `registration_endpoint`; protected-resource metadata (root + /mcp); **DCR | |
| 58 | round-trip returns a client_id (201)**; token endpoint: garbage code → | |
| 59 | `invalid_grant`, garbage refresh → `invalid_grant`, bogus grant_type → | |
| 60 | `unsupported_grant_type` | |
| 61 | - ✅ **c. MCP anonymous + RFC 6750** — 5/5: initialize 200; **live tools/list | |
| 62 | exactly matches the local 60-tool manifest (zero drift)**; anonymous | |
| 63 | tools/call challenge correct; dead-bearer invalid_token on both | |
| 64 | handshake methods | |
| 65 | - ⬜ **d. Authenticated MCP reads** — awaits `GLUECRON_PAT` | |
| 66 | - ⬜ **e. Read-only tool matrix (--full)** — awaits `GLUECRON_PAT` | |
| 67 | ||
| 68 | ## 3. External-platform PR journey (scripts/agent-journey.ts, new) | |
| 69 | ||
| 70 | ⬜ Awaits an admin-scope PAT. Simulates exactly what an external platform | |
| 71 | does: PAT identity → scratch repo → git push over smart HTTP with token | |
| 72 | basic-auth → `gluecron_create_branch`/`write_file`/`create_pr`/`comment_pr` | |
| 73 | → AI-review poll → `gluecron_merge_pr` + verify → activity feed → | |
| 74 | guaranteed cleanup. Hard-guarded against ever touching the self-host repo. | |
| 75 | ||
| 76 | ## 4. Self-canonical / server-side state (verified on-box 2026-07-14) | |
| 77 | ||
| 78 | - ✅ Deploys are cord-free from GitHub: `/opt/gluecron` `origin` = the | |
| 79 | box-local canonical bare repo (docker volume), NOT github.com. Pushes to | |
| 80 | `gluecron.com/ccantynz/Gluecron.com.git` main went live in ≤ ~2 min twice | |
| 81 | today (`b5a7bb3`, `2cecc08`). | |
| 82 | - ✅ `gluecron-update.timer` firing every 60s. | |
| 83 | - ✅ `gluecron-backup.timer` daily; last run 2026-07-14 00:00 UTC, exit 0, | |
| 84 | 4.4M Neon dump. | |
| 85 | - ✅ `gluecron-restore-drill.timer` weekly (first run Sun 2026-07-19 04:00 UTC). | |
| 86 | - ⬜ **Offsite backup NOT yet armed** — dumps stay on the VPS. Staged | |
| 87 | operator command appends `BACKUP_SCP_TARGET=root@100.89.227.39:/opt/backups/gluecron` | |
| 88 | (vapron-158) to `/opt/gluecron/.env` (with `.env` backup first) and | |
| 89 | fires a verification run. jarvis→vapron root SSH verified working. | |
| 90 | - ⚠️ GitHub (`ccantynz-alt/Gluecron.com`) remains the emergency deploy | |
| 91 | source per CUTOVER_RUNBOOK Phase 4 (needs 7 green days + armed offsite | |
| 92 | before retirement). Do not archive yet. | |
| 93 | ||
| 94 | ## 5. How external platforms onboard (no PAT required) | |
| 95 | ||
| 96 | 1. `GET /.well-known/oauth-authorization-server` — discovery | |
| 97 | 2. `POST /oauth/register` — RFC 7591 DCR (verified live) | |
| 98 | 3. Authorization-code + PKCE → `POST /oauth/token` (1h access / 30d rotating refresh) | |
| 99 | 4. MCP at `POST /mcp`; expired tokens now get `error="invalid_token"` → | |
| 100 | client refreshes transparently | |
| 101 | 5. Git over smart HTTP with `Basic <user>:<token>` (PAT) or the web/API flows | |
| 102 | ||
| 103 | ## 6. Open items / known gaps | |
| 104 | ||
| 105 | - ⬜ Run doctor sections d/e + agent-journey with an admin PAT (blocked on | |
| 106 | operator minting one; classifier reserves prod-DB writes for the operator). | |
| 107 | - ⬜ Arm offsite backup (staged command above; operator-confirmed destination). | |
| 108 | - ⚠️ `src/middleware/api-auth.ts` (API v2): 401s invalid bearers but sends no | |
| 109 | `WWW-Authenticate` header and rejects `glct_` OAuth tokens as unknown — | |
| 110 | additive parity pass recommended (out of scope today). | |
| 111 | - ⚠️ `/api/version` reports `sha: "unknown"` on the compose/auto-update | |
| 112 | deploy path — BUILD_SHA pin only happens via self-deploy.sh; staleness | |
| 113 | checks rely on `builtAt`. Worth stamping in the docker build. | |
| 114 | - ⚠️ DCR leaves `doctor-*` client rows (RFC 7592 delete not implemented) — | |
| 115 | cosmetic residue from each doctor run. | |
| 116 | - ⚠️ No MCP create-repo tool — journey uses `POST /api/v2/repos`; consider | |
| 117 | adding `gluecron_create_repo` for full MCP-only agent flows. | |
| 118 | ||
| 119 | ## Session evidence | |
| 120 | ||
| 121 | - Commits: `b5a7bb3` (fix + tests), `2cecc08` (doctor + journey + npm script). | |
| 122 | - Fix verified live with curl transcripts (§1); doctor output (§2) from | |
| 123 | `bun scripts/doctor.ts` at 2026-07-14 ~02:1x UTC. |