Skip to content

Promote dev to staging - #612

Merged
ducnmm merged 38 commits into
stagingfrom
dev
Aug 13, 2026
Merged

Promote dev to staging#612
ducnmm merged 38 commits into
stagingfrom
dev

Conversation

@ducnmm

@ducnmm ducnmm commented Aug 13, 2026

Copy link
Copy Markdown
Collaborator

Summary

Promote the current dev branch to staging for release validation and staging smoke tests.

This batch includes:

Included PRs

Validation

  • covered by the CI and reviews completed on each included PR
  • run the full promotion CI on this PR
  • after merge, deploy to staging and smoke-test authentication, MCP browser login/hot reload, remember/recovery, recall isolation, and SEAL committee-identity rollout

Notes

claude and others added 30 commits August 3, 2026 23:23
… port

The staging alignment replaced docs/relayer/api-reference.md with an
older revision, dropping the reviewed BEDU-654 content: the /config,
/metrics, /api/forget, /api/stats, and MCP transport sections, the
scoring-weights documentation, the limit caps, the enriched frontmatter,
and the Seal casing.

This restores that version and keeps the staging port's accurate
additions: the sponsor auth requirements, the /sponsor/execute sender
binding, and the 255-byte namespace limit. Every restored claim was
re-verified against the migrated server source (route table, ScoringWeights,
limit caps, MAX_BULK_ITEMS, body limits, forget/stats response shapes,
MCP transport methods, restore default).

The new security-delete routes from the migration stay undocumented here
pending a decision on whether the security-delete API belongs in the
public reference.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01WoBWxqLzd9hAxrhoqv3iSv
* feat: admin dashboard — backend auth + frontend

Phase 1 + Phase 3 implementation:

Phase 1: Backend API + Security
- Add x-admin-api-key middleware (constant-time comparison, timing-safe)
- Implement GET /api/admin/wallets (uploader pool + sponsor balance)
- Implement GET /api/admin/upload-errors (paginated failed jobs)
- Implement GET /api/admin/config (read-only thresholds)
- Security fixes: timing attack mitigation, key-length leak prevention
- Tests: 36 new tests, 313/313 passing, 0 critical issues

Phase 3: Frontend React Dashboard
- Add /admin route with key entry flow (localStorage auth)
- Build wallet balances panel (auto-refresh 30s, color-coded status)
- Build upload errors panel (paginated, copy-to-clipboard)
- Build config panel (read-only display)
- Style with Tailwind CSS v4 (responsive, matches MemWal theme)
- React-query integration for all data fetching

Files:
- Backend: auth.rs, routes/admin_dashboard.rs (new), main.rs, routes/mod.rs
- Frontend: App.tsx, pages/AdminDashboard.tsx (new), components/Admin*.tsx (new), utils/admin-api.ts (new), index.css
- Docs: admin dashboard spec + implementation status

* feat: phase 2 balance monitor + phase 3 security hardening

Phase 2: Backend Balance Monitoring
- Implement WalletBalanceLowAlert type with 12-hour dedup window
- Add balance_monitor_task background job (checks every 15 min)
- Fetch uploader pool + sponsor wallet balances periodically
- AlertManager integration for proactive Slack notifications
- Config: BALANCE_MONITOR_INTERVAL_SECS, wallet thresholds
- Tests: 10 new tests, 321 total passing

Phase 3: Security Hardening
- Fix CRITICAL: Move admin key from localStorage → sessionStorage
  (clears on tab close, prevents persistent XSS exposure)
- Fix CRITICAL: Sanitize error messages with DOMPurify
  (prevents XSS via malicious error_msg from failed jobs)
- Install DOMPurify v3.4.13 for error message sanitization

Files:
- Backend: alerts.rs (new alert type), main.rs (background job), types.rs (config)
- Frontend: AdminDashboard.tsx (sessionStorage), AdminUploadErrors.tsx (DOMPurify), package.json (DOMPurify dep)
- Config: .env.example (new balance monitor vars)

Tests: cargo check ✓, cargo test 321/321 ✓
Build: pnpm build ✓

* feat: phase 4 integration testing + production deployment guide

Phase 4: Integration Testing & Deployment

Integration Tests (43 tests, 100% passing):
- E2E admin dashboard flow (13 tests)
  * Authentication + 401 rejection
  * API response structure validation
  * Pagination with boundary clamping
  * Empty results handling
- Slack alert validation (18 tests)
  * Payload structure (BlockKit format)
  * Deduplication logic (12-hour window)
  * Multi-wallet support (uploader + sponsor)
  * Address abbreviation + percentage calculation
- Load testing (12 tests)
  * Sequential latency <500ms
  * Concurrent P99 <200ms
  * No memory leaks (100 cycles)
  * Zero upload blocking impact
  * Balance monitor job performance

Production Deployment Documentation (104 KB):
- DEPLOYMENT.md: Staging + production procedures, canary rollout, key rotation
- RUNBOOK.md: Incident response guide (9 issue categories, diagnosis + resolution)
- API.md: Complete endpoint reference + background job documentation
- README.md: Documentation index, feature overview, quick start
- .env.production.example: Environment variables template + security checklist
- PHASE4_CHECKLIST.md: Completion summary, readiness assessment

Files:
- tests: integration_admin_dashboard.rs, integration_slack_alerts.rs, integration_load_testing.rs
- docs: docs/admin-dashboard/* (6 files, comprehensive production guidance)

Status: APPROVED FOR PRODUCTION (all tests pass, documentation complete)

* fix: admin dashboard API contract mismatch and stubbed data

The frontend expected camelCase, per-wallet response shapes while the
backend returned aggregated snake_case JSON — every admin panel crashed
with "Cannot read properties of undefined (reading 'uploaderPoolWallets')"
even though all three endpoints returned 200.

Backend (admin_dashboard.rs):
- get_wallets: sponsor balance was hardcoded to "0"; now calls the same
  Sui RPC balance lookup the balance-monitor job uses, and computes
  ok/low status server-side against the real configured thresholds
- get_admin_config: thresholds were hardcoded constants that didn't
  match the balance-monitor job's actual config; now reads
  balance_monitor_interval_secs / wallet_balance_low_threshold_wal /
  sponsor_balance_low_threshold_sui from state.config
- remember.rs test fixture was missing the 3 fields above, so the bin
  test target failed to compile (earlier "all tests passing" runs had
  only compiled the lib target and never caught this)

Frontend (admin-api.ts):
- fetchAdminWallets/fetchAdminErrors/fetchAdminConfig now parse the
  actual snake_case backend response and map it to the camelCase shape
  the three admin components already render

* fix: admin dashboard couldn't reach the relayer at all

Two infra-level bugs, found by actually loading /admin in a browser
instead of curling the relayer directly:

1. admin-api.ts called relative /api/admin/* paths. On dev.memwal.ai
   (the app's own domain) those requests never leave the SPA — there's
   no reverse proxy to the relayer, so they silently fell through to
   the SPA catch-all and returned index.html with a 200. Every other
   API client in this app (api.ts, securityDeleteApi.ts, Dashboard.tsx)
   already prefixes calls with config.memwalServerUrl for exactly this
   reason; admin-api.ts was the one place that didn't.

2. Even after fixing (1), the browser blocked the cross-subdomain
   fetch to relayer.dev.memwal.ai: the CORS layer's allow_headers list
   didn't include x-admin-api-key, so the preflight failed silently.
   Added it alongside the other custom auth headers.

* fix: admin login accepted any key without validating it

handleKeySubmit stored whatever was typed and flipped straight to the
dashboard, so a wrong/stale key produced three separately-broken panels
("Invalid API key" in each card) instead of one clear rejection at the
login form. AdminKeyEntry already had the INVALID_KEY error handling
wired up for this — the parent just never actually validated before
calling it success.

Now validates via fetchAdminConfig before storing the key or switching
views; a bad key surfaces as "Invalid admin API key" on the login form
itself, matching what AdminKeyEntry was already built to show.

* feat: per-wallet breakdown + human-readable balances

The uploader pool has 6 wallets (SERVER_SUI_PRIVATE_KEYS) but the
dashboard collapsed them into a single synthetic "Uploader Pool" row
because the sidecar's balance snapshot summed every wallet before
returning it — the per-wallet numbers existed mid-computation and were
discarded before the response left the sidecar.

Sidecar: loadWalletBalanceSnapshot now also returns perWallet[], the
same per-owner totals it was already computing, just kept instead of
thrown away.

Backend: WalletsResponse.uploader_pool.wallets is now an array (one
entry per real address, each with its own ok/low status) instead of
a single aggregate.

Frontend: renders all 6 wallets as separate rows. Also switched every
raw mist/frost display (wallet table, sponsor card, config panel) to
human-readable SUI/WAL amounts via a shared formatTokenAmount helper,
with the exact base-unit value kept in a hover tooltip. Capped the
threshold-percent column at "999+%" since per-wallet WAL balances are
several orders of magnitude above the configured low-balance
threshold and were rendering as 7-digit percentages.

* fix: stale sessionStorage key survived reload, never re-validated

handleKeySubmit validated the key on manual login, but the mount-time
useEffect that restores a key from a previous session read it straight
out of sessionStorage and trusted it — no re-check. sessionStorage
only clears on tab close, not reload, so a key that predated the login
validation fix (or was simply wrong) stayed lodged in an open tab and
every panel failed with 401 on every subsequent reload, no matter how
many times the page was refreshed.

The restore path now re-validates via fetchAdminConfig same as manual
submit, clearing sessionStorage and falling back to the login form if
the stored key no longer checks out.

Also dropped the "% of Threshold" column from the uploader pool table
per feedback — per-wallet WAL balances are consistently orders of
magnitude above the configured low-balance threshold, so the column
never showed anything but a meaningless number.

* feat: auto sign-out with a clear reason on invalid key mid-session

Each panel already showed its own "Invalid API key" card when a query
401'd, but the dashboard stayed on the authenticated view — three
scattered error cards instead of one clear signal, and the user had to
notice and manually click Logout to actually recover.

Panels now call onInvalidKey the moment any of their queries hits
INVALID_KEY, which drops adminKey, clears sessionStorage, and returns
to the login form with a banner: "Your admin API key is no longer
valid. Please sign in again." Covers key rotation and any other
post-login invalidation, not just the initial-entry and session-
restore paths fixed earlier.

* style: match admin dashboard to the real app's dark theme

The app has no single design system — :root (light, hard black
borders, offset drop-shadows) is what any unscoped page falls back to,
while the actual authenticated experience (Dashboard.tsx) is scoped
under .dash-page: black background, off-white text, soft 1px borders,
pill-shaped buttons, no hard shadows, lime accent. The admin dashboard
was never wrapped in either scope, so it silently inherited the light
:root defaults and looked like a different, unrelated product.

Added the dash-page class to AdminDashboard's root (Card.tsx already
has zero styling of its own — everything comes from ambient .card
rules, so this alone gives every panel the right dark background and
border for free) and rewrote every .admin-* rule to use the same
--dash-* tokens, pill buttons, and outlined-pill status badges the
real dashboard uses, plus wrapped the wallet/error tables in the same
bordered-panel pattern as .dashboard-key-table instead of a bare
border-collapsed table.

* style: replace admin header text with the Walrus Memory logo

Per feedback: use the same wordmark the sign-in screen shows instead
of a plain "Admin Dashboard" h1. Reuses the exact asset path
(/walrus-memory-logo.svg) Dashboard.tsx's nav bar and App.tsx already
reference — no cache-busting query param, since that's specific to
the marketing landing page's asset versioning. Sized close to the
in-app nav logo (56px) rather than the sign-in hero's 330px. Folded
"Admin Dashboard" into the subtitle line so the page still identifies
itself as the admin section, not just the general product brand.

* chore: remove stale planning docs written before the real fixes

docs/superpowers/specs/*-admin-dashboard-design.md was the initial
brainstorming spec, and docs/admin-dashboard/*.md was written during
Phase 4 — both predate the actual API contract, response shapes, and
UI that shipped after fixing the bugs found through real E2E testing.
Keeping them around is worse than not having them: they now describe
a system that doesn't exist.

* remove admin page subtitle text under the logo

* fix: harden admin dashboard and remove test artifacts

* test: cover wallet metrics and harden admin UX

* fix: protect per-wallet sidecar balances

---------

Co-authored-by: Harry Phan <phanhoangvinhhien@gmail.com>
Co-authored-by: ducnmm <165614309+ducnmm@users.noreply.github.com>
Resolve immutable Blob creation provenance through archival Sui GraphQL, fail closed without silently dropping transient failures, and enforce current ownership. Bound restore work and call frequency, negative-cache permanent failures, apply the cache migration at startup, and surface truncated restores through MCP.

Fixes #589.
Allow SDK consumers to use either Zod 3 or Zod 4 without peer dependency resolution conflicts. Keep the pnpm lockfile importer synchronized.

Validated with strict peer dependency installs for both supported major versions.
…ayer-api-reference

# Conflicts:
#	docs/relayer/api-reference.md
…519) (#524)

* fix(noter): remove orphaned custom zkLogin login path [WALM-310][WALM-311]

The custom OAuth->JWT->prover->salt zkLogin flow (initiateLogin /
completeLogin) was never wired into the UI: no /auth/callback page, no
callers. The app's real login is Enoki. That dead path carried two
security defects: completeLogin decoded JWTs without verifying their
signature and its cached-proof branch skipped the prover (the only step
that would reject a forged token), enabling an account-takeover via a
forged JWT; and the salt was derived locally as SHA-256(iss::sub::aud)
with no secret, making a user's address computable from public claims.

Remove the path entirely rather than hardening it:

- delete lib/zklogin-client.ts and domain/zklogin.ts (fully orphaned once
  the two procedures are gone)
- drop initiateLogin / completeLogin procedures and the now-dead
  upsertZkLoginUser / updateZkLoginSession service functions
- remove dead input schemas, barrel re-exports, salt/prover config, and
  now-unused storage keys / error strings
- keep the zkLoginSessions table and its session-lookup readers for
  backward compatibility with any pre-existing sessions
- rewrite the auth doc to describe the Enoki / wallet / delegate-key flows

Enoki, wallet, delegate-key, getSession and logout are unchanged.

* fix(noter): require proof of ownership for Enoki auth and stop leaking delegate keys

The Enoki login path authenticated a session from a client-supplied Sui
address with no proof the caller controlled it, and returned the full user
row — including the delegate private key, which is the relayer signing
secret used to sign every memory operation. Anyone who knew a victim's
public address could obtain their session and that key.

Harden the flow, mirroring the pattern already used in the researcher app:

- add a server-issued single-use ownership challenge (issueEnokiChallenge)
  backed by Redis (SET NX to issue, atomic GETDEL to consume); connectEnoki
  verifies the signed challenge before any lookup, for both phases, and
  fails closed if the challenge store is unavailable
- verify the signature against the claimed address and require the
  recovered address to match
- return a sanitized user DTO from every auth path (connectEnoki,
  connectWallet, connectDelegateKey, getSession) that never includes the
  delegate private key or PII; expose the key only through a dedicated,
  challenge-gated exportDelegateKey procedure
- guard credential updates so registration cannot overwrite an existing
  account's stored delegate credentials
- normalize the Sui address at the route boundary so the challenge and the
  DB lookups key on the same canonical value
- add a one-time script to revoke legacy custom-zkLogin sessions on deploy
- correct the auth docs and required environment variables

* fix(noter): stop trusting legacy zkLogin sessions in code

The purge script revokes existing legacy zklogin_sessions rows on deploy,
but four read paths still authenticated a request from any row in that
table (the tRPC context, both memory write routes, and getActiveSession),
so the cutoff depended on the operator running the script and on nothing
ever writing the table again.

Drop the read-trust so sessions resolve only from wallet/enoki sessions,
which require proof of address ownership to create. A leftover row — or a
re-introduced writer — can no longer authenticate a request. Logout still
deletes any legacy row; the table and schema are retained.

* docs(noter): correct purge-script framing after read-trust removal

The legacy-session purge is now optional data hygiene, not a load-bearing
security step, since the code no longer reads or trusts zklogin_sessions
for authentication. Update the script header and README to match.

* build(noter): update lockfile for redis dependency

The redis dependency added for the Enoki auth challenge store was not
reflected in the root pnpm-lock.yaml, so CI's frozen-lockfile install
failed across the workspace. Regenerate the lockfile to match.

* fix(noter): address review nits on the Enoki auth change

- validate Sui address input with a 0x[64hex] regex on the challenge /
  connect / export procedures, so malformed input is rejected cleanly
  instead of being silently normalized into a valid-looking wrong address
- refresh stale docstrings/docs: getSession and connectEnoki no longer
  describe the removed behavior; the integration doc no longer claims a
  zklogin_sessions fallback that was removed
- drop the vestigial clientId/authUrl from OAUTH_PROVIDERS (Enoki owns the
  OAuth client config); the registry only enumerates providers now
- close the DB connection in a finally block in the purge script so it
  cannot hang on error

* fix(noter): scope export challenge, bind export to session, block cred overwrite

Addresses re-review of the Enoki auth hardening:

- Ownership challenges are now scoped to a purpose ("signin" vs "export"),
  baked into both the signed message and the stored record and required to
  match on verify. A signature obtained under a sign-in prompt can no longer
  be replayed to authorize a delegate-key export.
- exportDelegateKey is now a protected procedure: it requires a valid
  session, an export-purpose challenge, and that the session user's address
  matches the requested address, so a caller can only export their own key.
  A dedicated issueExportChallenge issues the export challenge for the
  session's own address only.
- Delegate-credential provisioning is insert-only: once a row has a delegate
  key, the connect/register path can no longer overwrite it with
  caller-supplied values.
- Add a vitest suite covering purpose scoping, single-use/replay, address
  binding, session-bound export, and the overwrite guard, wired into CI.

* test(noter): cover route-layer guards on delegate-key export

The service/challenge tests did not exercise the route's own gating, so a
refactor could weaken the export session binding while the suite stayed
green. Add route-level tests via createCaller: unauthenticated export is
rejected (UNAUTHORIZED), a session requesting a different address is
rejected (FORBIDDEN), the matching-session happy path returns the key, and
issueExportChallenge rejects an unauthenticated caller.
…gent-memory-embedstorerecall-loop-how-ai-agent
…aragraph

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01WoBWxqLzd9hAxrhoqv3iSv
Builders on EVM, Base, and Virtuals have been asking how to integrate,
and the docs had nothing. The answer is that the agent's chain is
independent of where its memory lives: ownership is a Sui account and
the agent authenticates with an Ed25519 delegate key, so there is
nothing to bridge.

Covers the two integration paths, the SDK and directly signed relayer
requests for languages with no SDK, and states plainly what is out of
scope so nobody designs around bridging that does not exist.

Closes BEDU-1086, closes BEDU-1114, closes BEDU-1120.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01WoBWxqLzd9hAxrhoqv3iSv
Per review: drops the essayistic framing for direct declarative prose,
renames the sections to describe tasks, and removes the See also block,
which no other page in this docs tree uses. The links it held now sit
inline where they are relevant.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01WoBWxqLzd9hAxrhoqv3iSv
…eads

Engineering review found two errors. The SDK has no general retry or
backoff, so the page now says what it does: signs each request, builds
and caches the Seal session key, and polls asynchronous jobs.

More importantly, the direct signed-request path was incomplete. The
five signed headers authenticate the caller but decrypt nothing, so
recall, ask, and restore also need a Seal credential. A client
following the old text would authenticate and then fail to decrypt.
Adds the three ways to supply it and notes that writes do not need it.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01WoBWxqLzd9hAxrhoqv3iSv
…t fields

Engineering review found the blanket claim that all /api/* routes take
signed headers to be wrong: account existence checks are public, the
security-delete families use their own flows, and /api/admin/* needs an
ADMIN_API_KEY. Scopes the statement to the routes this page documents
and names the families it leaves out, in the description too.

Also documents suiGrpcUrl and suiTransport from ConfigResponse, which
newer SDKs read to pick a transport, and marks the response as an
example rather than an exhaustive shape.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01WoBWxqLzd9hAxrhoqv3iSv
* feat(sdk): add deterministic offline mock clients

* fix(sdk): align mock recall with production

* fix(python): align sync mock with production client
ducnmm and others added 6 commits August 12, 2026 19:04
Fix: make remember writes idempotent — no duplicate paid Walrus blob
…tom connectors (1-2/3) (#584)

* feat(mcp): add OAuth 2.1 foundation for Claude custom connectors

Remote MCP currently only authenticates via a static delegate-key bearer
plus a custom X-MemWal-Account-Id header, which Claude's native
custom-connector flow can't use — it requires OAuth 2.1 discovery, PKCE,
and dynamic client registration. This lays the inert foundation: schema,
crypto, and validation logic, wired to nothing yet (MCP_OAUTH_ENABLED
defaults off, so this ships dark).

- migrations/010_mcp_oauth.sql: client registry, server-custodied delegate
  keys, authorize sessions, one-time codes, grants, and tokens.
- oauth.rs: AES-256-GCM envelope for delegate keys at rest, PKCE S256
  verification, redirect-URI matching (exact + RFC 8252 loopback), a
  Claude-specific registration allowlist (fixes the unauthenticated-DCR
  concern that stalled the unmerged WALM-30/ENG-1783 app_auth.rs prior
  art in PR #193), and sanitization for untrusted display text.
- db.rs: migration wiring + CRUD for the six new tables.
- Cargo/Config wiring for the above, config gated by MCP_OAUTH_ENABLED.

Routes and the proxy-side token resolution land in follow-up commits.

* feat(mcp): wire MCP OAuth authorization-server routes and consent UI

Builds on the OAuth foundation to expose the actual authorization server:
discovery, dynamic client registration, authorize/token/revoke, and the
consent screen that mints/verifies server-custodied delegate keys.

- routes/oauth.rs: RFC 9728/8414 discovery, RFC 7591 DCR (registration
  locked to Anthropic's known redirect hosts + RFC 8252 loopback — the
  direct fix for the self-serve-DCR safety concern that stalled the
  unmerged WALM-30/ENG-1783 app_auth.rs, PR #193), PKCE-required
  authorize/token exchange, refresh rotation with reuse detection,
  revoke. Accepts both form-urlencoded and JSON at /oauth/token per
  Claude's documented 415 failure mode.
- main.rs: routes mounted into public_routes only when MCP_OAUTH_ENABLED
  is set; hourly eviction task extended to sweep expired sessions/codes
  and prune unconsumed DCR clients.
- ConnectClaude.tsx: consent page at /connect/claude. Everything it
  displays comes from the server (GET /api/oauth/session/{id}), not raw
  query params — the WALM-288 lesson (the /connect/mcp phishing fix)
  applied here from the start. Reuses ConnectMcp.tsx's wallet-connect +
  add_delegate_key tx pattern; skips the on-chain tx entirely when the
  account already has a reusable delegate from a prior grant.

Still behind MCP_OAUTH_ENABLED=false. /api/mcp itself doesn't accept
these tokens yet — that's the next PR (mcp_proxy.rs resolution).

* feat(mcp): resolve OAuth bearers in the proxy, cutting over /api/mcp

Completes Claude custom-connector support: the reverse proxy now
classifies inbound bearers and, for an OAuth access token, resolves it
to the underlying delegate identity and rewrites the outbound request
into the legacy Authorization + X-MemWal-Account-Id shape the TS
sidecar already accepts. The sidecar itself needed zero changes.

- oauth.rs: resolve_oauth_bearer — hashes the presented token, joins
  through the grant to the delegate row, checks revocation/expiry/
  delegate status, decrypts the private key.
- mcp_proxy.rs: classify_and_resolve runs before every proxied request
  (SSE, messages, streamable). Legacy 64-hex bearers and MCP_OAUTH_ENABLED
  off both take the exact byte-for-byte passthrough path they take
  today. A resolved OAuth identity overwrites (never merges) the
  outbound authorization + x-memwal-account-id headers, so a forged
  X-MemWal-Account-Id alongside a valid token can't smuggle a different
  account through. Missing/invalid/expired/revoked tokens get an RFC
  9728 401 challenge with the resource_metadata pointer, straight from
  the proxy.
- docs/mcp/reference.md: document the OAuth flow and new public routes.

MCP_OAUTH_ENABLED still defaults to false.

* fix(oauth): remove MCP_OAUTH_ENABLED, derive from MEMWAL_RELAYER_URL

* docs(mcp): explain MCP_OAUTH_DELEGATE_ENCRYPTION_KEY

* fix(app): restore Claude OAuth connect flow

* Fix Claude connect delegate registration tx

* Fix connector setup resume and prep Claude plugin

* Point Claude plugin metadata to CommandOSSLabs repo

* Expand Claude plugin setup guide

* Document hosted Claude connector endpoint

* Document full MCP tool support

* Add Codex testing instructions

* Organize plugin usage docs

* Remove plugin artifacts from OAuth PR

* Restore base plugin manifests

* fix(oauth): bind grants and enforce scoped access

* test(oauth): include scope in proxy identity fixture

* fix(oauth): run migration and cover security regressions

---------

Co-authored-by: Harry Phan <phanhoangvinhhien@gmail.com>
Co-authored-by: ducnmm <165614309+ducnmm@users.noreply.github.com>
fix(security): remediate Researcher, Noter, and migration findings
@harrymove-ctrl
harrymove-ctrl self-requested a review August 13, 2026 02:52
@ducnmm
ducnmm deployed to benchmark-dev August 13, 2026 03:56 — with GitHub Actions Active
@railway-app
railway-app Bot temporarily deployed to Walrus Memory / dev August 13, 2026 03:56 Inactive
@ducnmm
ducnmm merged commit a8f8262 into staging Aug 13, 2026
28 of 31 checks passed
@ducnmm ducnmm mentioned this pull request Aug 13, 2026
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

5 participants