Add CLI on top of config keys refactor - #6082
Conversation
|
Review the following changes in direct dependencies. Learn more about Socket for GitHub.
|
|
Warning Review the following alerts detected in dependencies. According to your organization's Security Policy, it is recommended to resolve "Warn" alerts. Learn more about Socket for GitHub.
|
285b97c to
d1a82c6
Compare
Separate env into config.json and keys.json, then load plugin keys from the info server with attestation budget, DeviceSettings cache, and baked-in fallback.
Keep the Edge API secret out of JS and keys.json by generating XOR-split C sources from edgeKey.json at build time, bridging HMAC through native modules, and deriving the runtime pad from post-patch applicationId / bundle ID.
Drop RN from network/utils load paths via fiatConstants, lazy locale boot, injected initInfoServer params, and configureNetwork.
2c77f58 to
67406a5
Compare
Addresses review-code critical in src/util/network.ts: NetInfo reconnect called initInfoServer again and stacked setInterval pollers. Keep a single module-level interval handle so reconnect only re-queries without stacking timers.
Keep exchangeRates on network.fetchRates and utils.removeIsoPrefix; inject only Airship showError via exchangeRatesGui at app start.
Include a Node N-API Edge API HMAC signer for production embeds, node-safe smoke in precommit, and CLI rollup build deps.
Addresses review-code critical in src/cli/engine/cliConfig.ts: asCliConfig omitted testMode so cleaners stripped edge-cli.conf testMode and left the engine on production servers. Add asOptional(asBoolean) so file config matches CliConfig.
Addresses review-code criticals in src/cli/engine/routes/login.ts: watch and GET raced sessions.create for the same pending login, and DELETE left pending.watch live so a late done could create a session after cancel. Serialize via sessionPromise, unwatch on DELETE/expire, and refuse/create-then-logout when cancelled. Also serialize auto-logout ticks so overlapping forceLogout cannot run concurrently (sessions.ts warning).
Addresses review-code criticals on makeApiSigner/buildNodeApiSigner: missing apiSecret no longer writes Node shards (and deletes prior ones), keeps existing mobile shards unless --allow-stub, and buildNodeApiSigner fails closed. Node shards always use the fixed co.edgesecure.app pad so branded mobile ids cannot desync HMAC.
Addresses review-code warnings in makeCoreContext.ts: Buffer.from of 0x-prefixed secrets yielded empty keys, and a present N-API addon ignored -k/keys.json. Strip 0x before hex decode; skip the native signer when -k or EDGE_CLI_FORCE_KEYS_JSON is set.
Addresses review-code warning that package.json publish:cli pointed at a missing scripts/publishCli.ts. Add a stub that exits with a clear message until the real publisher is restored.
Addresses review-code warning in envFiles.test.ts: the empty {}
ramp skip was stale—envSplit keeps empty stubs. Restore full
round-trip coverage so keep-empty regressions fail CI.
Addresses review-code warning in nodeApiSigner.ts: require() failures for an existing .node were swallowed, hiding ABI/OS mismatches. Log the path and error at warn level before trying the next candidate.
Addresses review-code warning in solveCaptcha.ts: hung challenge GETs/POSTs stalled --solve-captcha indefinitely. Set a 30s req.setTimeout that destroys the socket so login retry fails cleanly.
Addresses review-code warning in src/cli/index.ts: key-login was in loginCommands but retryLoginCommand fell through to a re-run hint. POST /v1/login/key with username, loginKey, and challengeId like the other login retries.
Addresses review-code warning in idleShutdown.ts: shuttingDown stayed true after a failed onFire, so later idle fires no-op'd and the engine never exited. Reset the flag, re-arm the timer, and log timer-path errors instead of an empty catch.
Addresses review-code warning in objectHandles.ts: async sweep() on setInterval could overlap slow onExpire handlers. Guard with sweepInFlight so expiry cleanup cannot run concurrently.
Addresses review-code warning in keysConfig.ts: loadKeys caught all errors and treated a present but invalid keys.json like a missing file. Only skip ENOENT; throw on JSON/cleaner failures.
Addresses review-code suggestion in cliNodeSafeSmoke.js: include nodeApiSigner.ts in SHARED_MODULES so RN leaks on the signer loader path fail precommit.
Addresses review-code criticals and warnings on the engine's HTTP listeners (src/cli/engine/server.ts, json.ts, discovery.ts). The REST API had no authentication and explicitly accepted text/plain, which is CORS-safelisted. Any web page the user visited could therefore issue simple cross-site requests against an engine started with --tcp and drive spends, enumerate sessions, or shut it down. Now: requests carrying Origin or Sec-Fetch-Mode are refused, the TCP listener requires a per-run bearer token (32 random bytes recorded in the 0600 run file and sent by ApiClient), the Host header is pinned to loopback plus the bind host to blunt DNS rebinding, and only application/json bodies reach a handler. The unix socket is already owner-only at 0600, so it carries no token. readJsonBody buffered request bodies with no cap, so one large or slow upload could exhaust engine memory and take live sessions down with it. Bodies are now rejected at 4 MB (both by declared Content-Length and while streaming) and both listeners set headersTimeout/requestTimeout. listenUnix unconditionally unlinked the socket before binding while cleanupStaleLock only removed artifacts for dead pids, so starting a second engine for the same profile silently stole the live socket and left two EdgeContexts writing the same directory. cleanupStaleLock now reports a live pid (treating EPERM as alive) and startup exits with a clear message instead. Also drops waitForPortFree, which was unreferenced and leaked a socket on its error path. Verified against a running engine: unauthenticated TCP 401, valid token 200, bad token 401, Origin 403, rebound Host 403, text/plain 415, 5 MB body 413 with the connection closed, unix socket 200, and a second engine for the same profile refuses to start while the first stays reachable.
Addresses review-code warning on src/cli/engine/idleShutdown.ts: the idle timer was disarmed for as long as an account was logged in, and nothing re-armed it when the last session went away. IdleShutdown.reset returns early while getSessionCount() > 0 and only ran again on the next inbound request, but the logout itself calls touch() before the session is removed. An engine whose user logged out therefore stayed resident indefinitely, holding the profile lock and an EdgeContext. SessionStore now reports membership changes through onSessionsChanged, which the engine wires to IdleShutdown.notifySessionsChanged. Both the request path and the auto-logout ticker path re-arm as a result. The callback is invoked inside a try/catch so a listener can never break login or logout.
Addresses three review-code findings around key resolution and startup diagnostics. loadKeys returned the first keys.json that parsed, so running from a GUI checkout meant the repo-root keys.json — which carries 66 plugin keys but no edgeApiKey — permanently shadowed ~/.edge-cli/keys.json. The engine then fell back to an empty apiKey and failed at login with an opaque server error. A file with no edgeApiKey is now kept only as a fallback for its plugin keys and does not stop the search, and makeCoreContext fails immediately, naming the searched paths, when it has neither a native signer nor a key. makeCoreContext also paired an explicit -k key with the keys.json edgeApiSecret, signing every request with a secret belonging to a different key. The secret is dropped when -k overrides the key. ensureEngine spawned the engine with stdio 'ignore', so a child that died during startup (unreadable keys.json, plugin load failure) surfaced only as a 30-second timeout with no cause. Child output now goes to engine-startup.log in the run directory and its tail is included in the timeout error.
Addresses review-code warning in src/cli/engine/routes/admin.ts:
POST /v1/admin/lobby returned the lobby's id and current replies and
then dropped the only reference to the EdgeLobby. A lobby polls the
login server until closed, so every call leaked a timer that ran for
the remaining life of the engine.
The lobby is now parked in the object handle store under a new
'lobby' kind, with onExpire closing it. It is released by the normal
5-minute TTL sweep, by clearAll on shutdown, or immediately via
DELETE /v1/admin/lobby-handle/{objectId}. That path is distinct from
GET /v1/admin/lobby/{lobbyId}, which still takes the server-side
lobby id rather than an engine handle.
Addresses review-code warning in src/cli/engine/routes/spend.ts: the combined spend route ran broadcastTx then saveTx, and a saveTx rejection propagated as a 500. The transaction was already on the network at that point, so the caller lost the txid of a real spend and had no way to recover it from the response. After a successful broadcast, a saveTx failure is now logged and returned as a `saveError` field alongside the transaction. When the caller asked for save without broadcast nothing has happened yet, so that case still throws.
Documents the transport rules the engine now enforces, which the API reference still described as unauthenticated. Section 2 covers the TCP bearer token and where to read it, Host header pinning, the blanket rejection of browser-originated requests, the 4 MiB body cap, the captured startup log, and the one-engine-per-profile rule. Section 7 gains the UNAUTHORIZED, FORBIDDEN, and PAYLOAD_TOO_LARGE codes, and the run file example gains tcpToken. Also replaces /Users/paul in two sample payloads with /Users/you, so the reference stops showing one developer's home directory.
Addresses review-code warnings on engine log and run-file handling. EngineLogger created ~/.edge-cli/logs and its log files at the default umask, so engine logs — which record usernames, login ids and core diagnostics — were world-readable. The directory is now 0700 and log files 0600, and both are chmodded on open so files left by an earlier run are tightened rather than trusted. writeRunFile gets the same treatment because its `mode` option only applies when creating, and that file now carries the TCP bearer token. EngineLogger.close also returned before the write stream drained, so the last lines before an idle or requested shutdown could be lost. It now resolves once the stream has flushed, and shutdown awaits it.
Addresses review-code warning in src/cli/engine/events.ts: EventHub wrote to every SSE response and ignored the return value of write, so a subscriber that stopped reading — a paused terminal, a wedged script — made Node buffer every subsequent event in the engine's heap with no ceiling. Clients that have ended or been destroyed are now dropped on the next emit, and any client with more than 1 MiB still queued is disconnected rather than buffered further. Events are best-effort notifications, so dropping a stalled listener is preferable to growing unboundedly on its behalf.
Co-authored-by: Cursor <cursoragent@cursor.com>
Co-authored-by: Cursor <cursoragent@cursor.com>
Co-authored-by: Cursor <cursoragent@cursor.com>
Co-authored-by: Cursor <cursoragent@cursor.com>
Co-authored-by: Cursor <cursoragent@cursor.com>
Co-authored-by: Cursor <cursoragent@cursor.com>
Align EDGE_CLI_API.md and EDGE_CLI.md with the real admin, voucher, and object-handle routes. Removes invented paths (username-hash, filename-hash, /admin/lobbies*, repos without dataKey) and documents hash-username, lobby handles, repo delete, and objects GET/DELETE.
Cancel edge-login tears down an already-created session, and ensureEdgeSession stops retrying after a sticky create error so GET cannot wedge a failed pending login into a create loop.
Honor pluginApiKeys[id] === false (and enabled:false) for currency plugins the same way accountbased plugins already did, so keys.json can disable a currency plugin.
Validate bare --idle-timeout <n> the same as --idle-timeout=<n>, and fail closed when no edge-engine entry file exists instead of spawning a nonexistent default path.
Treat CAPTCHA HTTP bodies as utf8 Buffer chunks and require 2xx on the challenge GET/POST so a non-HTML error page cannot look like success.
Clarify object-handle TTL is absolute from create/update (reads do not slide it), and drop the redundant bare node_modules gitignore entry.
Move the module JSDoc above the imports so simple-import-sort and readers see the Node-safe rates contract at the top of the file.
Reconcile CLI command names with the registry (engine-status etc.), mark REST-only endpoints that have no edge-cli wrapper, and document EDGE_CLI_FORCE_KEYS_JSON.
Bugbot: currency-configs listed swap plugins; GET /v1/users omitted the users envelope; wallet-create and sign-bytes field names disagreed with the REST docs. Filter to currency plugins, wrap localUsers, and accept both documented and CLI field aliases.
Drop TCP bearer auth, Host allowlisting, and Origin/Sec-Fetch rejection. The engine is a local open-source convenience daemon; Edge account authentication remains on the login server.
Summary
edge-cli/ engine).develop.testModeconfig, etc.).Notes for reviewers
develop(config/keys, native HMAC, Node-safe splits, CLI). It is intentionally draft-style until dependencies / base strategy are finalized; there is nofuture!pseudo-merge in the history.publish:cli) is a placeholder until packaging/bin metadata is restored.edgeKey.json(build:cli:native); stub builds are refused.Test plan
npm run test:cli:node-safenpm run build:cli/npm run build:cli:native(withedgeKey.json)npm run test:cli:node-hmac(withedgeKey.json)npm run test:cli/npm run test:cli:edge-loginas applicablenpm test/tsc