Skip to content

merge v0.4.10 into master#474

Merged
RetricSu merged 7 commits into
masterfrom
develop
Jul 26, 2026
Merged

merge v0.4.10 into master#474
RetricSu merged 7 commits into
masterfrom
develop

Conversation

@RetricSu

Copy link
Copy Markdown
Collaborator

No description provided.

humble-little-bear and others added 7 commits July 23, 2026 11:58
…463) (#464)

* fix(status): enable Terminal RPC module and TCP streaming for devnet

ckb-tui panels were always empty on devnet because the bundled devnet
ckb.toml did not meet ckb-tui's two data requirements:

- the Terminal RPC module (provides get_overview system metrics), which
  upstream CKB now enables by default, was missing from rpc.modules, so
  the overview dashboards showed N/A
- rpc.tcp_listen_address was commented out and the status command never
  passed -t, so the mempool (new/rejected transactions) and logs
  dashboards had no subscription stream to read from

Enable both in the devnet config template and have the status command
read tcp_listen_address from the running node's ckb.toml and pass it to
ckb-tui via -t (wildcard binds are dialed as localhost). Testnet and
mainnet keep HTTP-only behavior since their proxied public RPCs expose
no TCP stream.



* chore: add patch changeset for status devnet fix

* fix(devnet): bind RPC to loopback instead of 0.0.0.0

Address CodeRabbit review on PR #463: with the Terminal module enabled,
binding the unauthenticated JSON-RPC to 0.0.0.0 exposes host system
metrics (and the rest of the RPC surface) to any host on the network.
Bind to 127.0.0.1 by default; all offckb-internal consumers (proxy,
ckb-tui, miner, forks) already talk to 127.0.0.1:8114. Users who need
remote access can edit rpc.listen_address via the config editor.

* fix(devnet): align embedded reference template with devnet ckb.toml

Add Terminal to rpc.modules and enable tcp_listen_address in the config
editor's embedded template so configurations based on it also provide the
metrics stream that offckb status needs.



---------

Co-authored-by: claude-bear <noreply@anthropic.com>
…-parser for security advisories (#465)

- tar ^7.5.3 -> ^7.5.19 (locked 7.5.21): fixes GHSA-23hp-3jrh-7fpw (critical),
  GHSA-8x88-c5mf-7j5w (high), GHSA-w8wr-v893-vjvp / GHSA-gvwx-54wh-qm9j (moderate)
- brace-expansion -> 1.1.16 / 5.0.7 via overrides: fixes GHSA-3jxr-9vmj-r5cp (high)
- js-yaml 4.x -> 4.3.0 via override: fixes GHSA-52cp-r559-cp3m (high)
- hono -> 4.12.27 via override: fixes GHSA-xgm2-5f3f-mvvc, GHSA-hvrm-45r6-mjfj,
  GHSA-w62v-xxxg-mg59 (moderate, dev-only)
- fast-uri -> 3.1.4 via override: fixes GHSA-v2hh-gcrm-f6hx, GHSA-4c8g-83qw-93j6
  (high, dev-only)
- body-parser 2.x -> 2.3.0 via override: fixes GHSA-v422-hmwv-36x6 (low, dev-only)

Not fixed: elliptic GHSA-848j-6mx2-7j84 (no patched release published) and
@hono/node-server GHSA-frvp-7c67-39w9 (fix requires breaking 1.x -> 2.x bump that
violates @modelcontextprotocol/sdk's ^1.19.9 range; dev-only, Windows-only).

Co-authored-by: Claude Fable 5 <noreply@anthropic.com>
…w fixes (#466)

* fix: rename mainnet-fork override flag and apply leftover 0.4.9 review fixes

- Rename --allow-mainnet-replay-risk to --allow-external-key-on-mainnet-fork (#460)
- Enforce the Mainnet-fork replay guard in transfer-all, udt issue/destroy,
  and deploy, threading the fork boundary into input selection (#462)
- Validate --tx-hash before it is used in debug cache paths
- Only read the fork boundary after the spawned process binds the RPC port
- Reject symlinked entries when copying fork source chain data
- Accept extended xUDT type args (owner hash + flags/extension)
- Per-kind UDT scan budgets, deep-cloned settings fallbacks, accurate
  config-set errors, preserved devnet-config error, execFile process lookup,
  aligned ckb-tui download timeouts, EXDEV-safe install, README TOC entry

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>

* fix: address PR #466 review comments

- Keep --allow-mainnet-replay-risk as a hidden deprecated alias folded into
  --allow-external-key-on-mainnet-fork (with a deprecation warning) so 0.4.9
  scripts keep working under a patch release
- Treat lsof probe failures with stderr output as indeterminate (null)
  instead of "not listening"; only an empty-stderr exit is a genuine
  no-match, so permission errors fall back to the weaker genesis signal
- Reject a symlinked data root before enumerating source chain data
- Stage cross-device ckb-tui installs inside binDir and publish with an
  atomic rename, so concurrent installs never see a truncated binary

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>

* fix: bound the lsof port probe with a timeout

A hung lsof would block execFileSync (and with it daemon startup)
indefinitely, and its empty-stderr timeout error would be misread as a
genuine no-match. Cap the probe at 5s and classify ETIMEDOUT as
indeterminate (null) so the genesis fallback proceeds.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>

* test: make lsof probe tests platform-independent

isProcessListeningOnPort short-circuits to null on win32, so the lsof
outcome-mapping tests failed on the Windows CI runner (mock never
called). Force a unix platform for the lsof-probing cases, cover the
win32 short-circuit explicitly, and pin the probe timeout to exactly
5000 ms per review feedback.

---------

Co-authored-by: Claude Fable 5 <noreply@anthropic.com>
fix(devnet): offer Terminal module in config editor and migrate legacy ckb.toml
A healthy devnet emits almost no warn-level logs, so with the previous
default filter (warn,ckb-script=debug) the `offckb status` Logs panel
stayed permanently empty and looked broken. Switch the devnet ckb.toml
and ckb-miner.toml templates (and the config editor's embedded reference
templates) to info,ckb-script=debug so the per-block log stream is
visible while script debug output is preserved.

Existing chains keep their current filter; edit [logger] filter in the
devnet ckb.toml to opt in.

Co-authored-by: Claude Fable 5 <noreply@anthropic.com>
@github-actions

Copy link
Copy Markdown

Missing Changeset

Please add a changeset describing your changes:

pnpm changeset

If your changes do not need a version bump (docs, CI, refactoring),
add the skip-changeset label to this PR.

For dependency updates, use the dependencies label.

@coderabbitai

coderabbitai Bot commented Jul 25, 2026

Copy link
Copy Markdown

Review Change Stack

📝 Walkthrough

Summary by CodeRabbit

  • New Features

    • Added safer Mainnet-fork transaction handling with the --allow-external-key-on-mainnet-fork option.
    • Improved devnet status reporting by detecting TCP subscription endpoints.
    • Added automatic migration support for older devnet configurations.
    • Expanded UDT validation and accepted XUDT type-argument formats.
  • Bug Fixes

    • Improved transaction-hash validation and devnet fork safety.
    • Prevented unsafe symlink copying during devnet forks.
    • Improved cross-filesystem CLI tool installation and process detection.
    • Increased useful devnet log visibility and restricted RPC access to localhost.
  • Documentation

    • Updated setup guidance, fork warnings, configuration examples, and migration notes.

Walkthrough

This release updates devnet RPC/status integration, migrates legacy devnet configurations, strengthens mainnet-fork transaction safeguards, improves node and installer reliability, tightens validation, and updates package metadata and dependency security overrides.

Changes

Mainnet fork safety and input boundaries

Layer / File(s) Summary
Fork override normalization and CLI wiring
src/util/fork-safety.ts, src/cli.ts, tests/*fork*
Adds the new external-key override, preserves the deprecated alias, and applies normalization across guarded commands.
Fork boundary propagation through commands and SDK
src/cmd/*, src/deploy/index.ts, src/sdk/ckb.ts, tests/*
Propagates rejectInputsAtOrBeforeBlock through deploy, transfer, transfer-all, and UDT transaction paths.

Devnet RPC, status, and legacy migration

Layer / File(s) Summary
Devnet templates and configuration metadata
ckb/devnet/*, src/tui/*, tests/devnet-config-metadata.test.ts
Enables Terminal/TCP RPC settings, restricts listeners to loopback, increases log visibility, and synchronizes module metadata.
Status TCP endpoint wiring
src/cmd/status.ts, tests/status.test.ts
Reads the devnet TCP listener from ckb.toml, normalizes wildcard addresses, and passes it to ckb-tui when available.
Legacy RPC configuration migration
src/node/init-chain.ts, src/cfg/setting.ts, tests/init-chain.test.ts
Migrates existing RPC configurations while preserving formatting and returning cloned default settings.

Node, fork-copy, and ckb-tui reliability

Layer / File(s) Summary
Process inspection and startup readiness
src/cmd/node.ts, tests/node-*.test.ts
Uses argument-safe process inspection, probes RPC listeners, and waits for the spawned node before clearing fork state.
Symlink-safe devnet fork copying
src/devnet/fork.ts, tests/devnet-fork.test.ts
Rejects symlinks at all copied data-tree levels.
ckb-tui installation portability
src/tools/ckb-tui.ts
Uses the shared download timeout and supports cross-filesystem installation fallback.

Validation, configuration errors, and release maintenance

Layer / File(s) Summary
Input validation and error handling
src/util/validator.ts, src/cmd/debug.ts, src/cmd/config.ts, src/cmd/devnet-config.ts, tests/*
Adds transaction-hash validation, updates UDT type-argument rules, and narrows configuration error handling.
Release documentation and package maintenance
CHANGELOG.md, README.md, package.json, pnpm-workspace.yaml
Publishes version 0.4.10 metadata, documentation, dependency updates, and security overrides.

Estimated code review effort: 4 (Complex) | ~45 minutes

Possibly related PRs

  • ckb-devrel/offckb#466: Overlaps with the fork-safety flag rename, transaction validation, symlink checks, and process reliability changes.
  • ckb-devrel/offckb#468: Overlaps with legacy devnet RPC migration and Terminal/TCP configuration handling.
  • ckb-devrel/offckb#465: Overlaps with package and dependency security updates.

Suggested labels: dependencies

Suggested reviewers: humble-little-bear

🚥 Pre-merge checks | ✅ 3 | ❌ 2

❌ Failed checks (1 warning, 1 inconclusive)

Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 25.58% which is insufficient. The required threshold is 80.00%. Write docstrings for the functions missing them to satisfy the coverage threshold.
Description check ❓ Inconclusive No pull request description was provided, so there is no meaningful description to evaluate. Add a short summary of the release or key changes so reviewers can quickly understand the PR.
✅ Passed checks (3 passed)
Check name Status Explanation
Title check ✅ Passed The title matches the release merge and points to version 0.4.10 being merged into master.
Linked Issues check ✅ Passed Check skipped because no linked issues were found for this pull request.
Out of Scope Changes check ✅ Passed Check skipped because no linked issues were found for this pull request.

Comment @coderabbitai help to get the list of available commands.

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 5

🧹 Nitpick comments (3)
tests/status.test.ts (1)

44-49: 📐 Maintainability & Code Quality | 🔵 Trivial | 💤 Low value

Temp dirs created here are never removed.

useDevnetConfig mkdtemps per test with no teardown, leaving directories in os.tmpdir(). Track the created dirs and fs.rmSync(..., { recursive: true, force: true }) in an afterEach (as tests/init-chain.test.ts does).

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@tests/status.test.ts` around lines 44 - 49, Update useDevnetConfig to track
each directory returned by fs.mkdtempSync, then add an afterEach teardown that
removes all tracked directories with fs.rmSync using recursive and force
options, following the cleanup pattern in init-chain tests.
src/tui/devnet-reference-templates.ts (1)

83-95: 📐 Maintainability & Code Quality | 🔵 Trivial | ⚡ Quick win

Consider guarding the embedded template against drift from ckb/devnet/ckb.toml.

This template duplicates the bundled ckb/devnet/ckb.toml verbatim, and this PR had to hand-edit both. A test asserting the embedded string equals the bundled file (or loading the file at build time) would prevent the two from silently diverging on the next config change.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@src/tui/devnet-reference-templates.ts` around lines 83 - 95, Keep the
embedded devnet template synchronized with the bundled ckb.toml by adding a test
that compares the template string against the file contents, or by loading the
bundled file at build time. Anchor the change to the embedded template
definition in devnet-reference-templates.ts and ensure future configuration
edits cannot silently cause the two representations to diverge.
src/sdk/ckb.ts (1)

208-227: 🚀 Performance & Scalability | 🔵 Trivial | ⚡ Quick win

Sequential per-input RPC calls could slow down transferAll sweeps under a Mainnet fork.

assertInputsCreatedAfter (unchanged helper, ckb.ts lines 422-440) awaits one getTransactionNoCache call per input inside a for loop. This is now wired into transferAll, which by design sweeps every spendable cell — the case most likely to accumulate many small inputs on a heavily-used devnet. When a Mainnet fork boundary is active, each sweep now pays one serial RPC round-trip per input before signing.

Consider parallelizing the origin-block lookups (e.g. Promise.all, with a bounded concurrency if the RPC endpoint is rate-sensitive) to avoid compounding latency as the number of swept inputs grows. The same helper is reused by udtIssue, udtDestroy, deployScript, deployNewTypeIDScript, and upgradeTypeIdScript, but those paths typically touch far fewer inputs.

♻️ Possible direction (illustrative, not a full diff)
 private async assertInputsCreatedAfter(tx: ccc.Transaction, blockNumber?: bigint): Promise<void> {
   if (blockNumber == null) return;

-  for (const input of tx.inputs) {
-    const outPoint = input.previousOutput;
-    const origin = await this.client.getTransactionNoCache(outPoint.txHash);
-    if (origin?.blockNumber == null) {
-      throw new Error(...);
-    }
-    if (origin.blockNumber <= blockNumber) {
-      throw new Error(...);
-    }
-  }
+  await Promise.all(
+    tx.inputs.map(async (input) => {
+      const outPoint = input.previousOutput;
+      const origin = await this.client.getTransactionNoCache(outPoint.txHash);
+      if (origin?.blockNumber == null) throw new Error(...);
+      if (origin.blockNumber <= blockNumber) throw new Error(...);
+    }),
+  );
 }
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@src/sdk/ckb.ts` around lines 208 - 227, Update assertInputsCreatedAfter to
perform per-input getTransactionNoCache lookups concurrently, using Promise.all
or the project’s bounded-concurrency approach, while preserving the existing
rejection behavior for inputs at or before the specified block. Keep transferAll
and the other callers unchanged.
🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Inline comments:
In `@README.md`:
- Line 334: Update the README sentence to use the grammatically correct “OffCKB
uses” and describe `info,ckb-script=debug` as a default log filter rather than a
log level, preserving the existing customization example.
- Line 418: Update the README replay-guard sentence to describe the shared
Mainnet-fork behavior across transfer, transfer-all, UDT issue, UDT destroy, and
deploy. Include the non-built-in-key override, Mainnet-copied input rejection,
and deprecated --allow-mainnet-replay-risk alias requirements for all listed
commands.

In `@src/cmd/status.ts`:
- Around line 27-41: Update devnetTcpListenAddress to log the caught error with
logger.debug before returning undefined, preserving its best-effort behavior.
Extend wildcard-address handling for IPv6 forms such as [::]:port and :::port by
mapping them to a dialable loopback address or returning undefined so ckb-tui is
not given an undialable target.

In `@src/node/init-chain.ts`:
- Around line 61-70: Update the single-line rewrite in the modules migration
logic around modulesStart and singleLine so an empty modules array becomes valid
TOML with only TERMINAL_RPC_MODULE, while retaining the comma separator for
non-empty arrays. Preserve existing indentation and trailing comments.
- Around line 141-156: Update the RPC migration flow around the
needsTerminal/needsTcp checks to inspect rpc.listen_address and detect the
legacy non-loopback 0.0.0.0:8114 bind. Migrate that bind to 127.0.0.1:8114
before or while adding the Terminal module, or skip Terminal enablement and emit
the required warning for non-loopback RPC; add a regression test covering this
configuration.

---

Nitpick comments:
In `@src/sdk/ckb.ts`:
- Around line 208-227: Update assertInputsCreatedAfter to perform per-input
getTransactionNoCache lookups concurrently, using Promise.all or the project’s
bounded-concurrency approach, while preserving the existing rejection behavior
for inputs at or before the specified block. Keep transferAll and the other
callers unchanged.

In `@src/tui/devnet-reference-templates.ts`:
- Around line 83-95: Keep the embedded devnet template synchronized with the
bundled ckb.toml by adding a test that compares the template string against the
file contents, or by loading the bundled file at build time. Anchor the change
to the embedded template definition in devnet-reference-templates.ts and ensure
future configuration edits cannot silently cause the two representations to
diverge.

In `@tests/status.test.ts`:
- Around line 44-49: Update useDevnetConfig to track each directory returned by
fs.mkdtempSync, then add an afterEach teardown that removes all tracked
directories with fs.rmSync using recursive and force options, following the
cleanup pattern in init-chain tests.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: Organization UI

Review profile: CHILL

Plan: Pro Plus

Run ID: 0d61465b-8b67-48eb-89f0-3d0b3a7cd911

📥 Commits

Reviewing files that changed from the base of the PR and between 0cdf9f8 and 920ef4d.

⛔ Files ignored due to path filters (1)
  • pnpm-lock.yaml is excluded by !**/pnpm-lock.yaml
📒 Files selected for processing (38)
  • CHANGELOG.md
  • README.md
  • ckb/devnet/ckb-miner.toml
  • ckb/devnet/ckb.toml
  • package.json
  • pnpm-workspace.yaml
  • src/cfg/setting.ts
  • src/cli.ts
  • src/cmd/config.ts
  • src/cmd/debug.ts
  • src/cmd/deploy.ts
  • src/cmd/devnet-config.ts
  • src/cmd/node.ts
  • src/cmd/status.ts
  • src/cmd/transfer-all.ts
  • src/cmd/transfer.ts
  • src/cmd/udt.ts
  • src/deploy/index.ts
  • src/devnet/fork.ts
  • src/node/init-chain.ts
  • src/sdk/ckb.ts
  • src/tools/ckb-tui.ts
  • src/tui/devnet-config-metadata.ts
  • src/tui/devnet-reference-templates.ts
  • src/util/fork-safety.ts
  • src/util/validator.ts
  • tests/cli-mainnet-fork-flag.test.ts
  • tests/debug-tx-file.test.ts
  • tests/devnet-config-metadata.test.ts
  • tests/devnet-fork.test.ts
  • tests/fork-safety.test.ts
  • tests/init-chain.test.ts
  • tests/node-command.test.ts
  • tests/node-listener.test.ts
  • tests/status.test.ts
  • tests/transfer-all.test.ts
  • tests/udt.test.ts
  • tests/validator.test.ts

Comment thread README.md
### 6. Tweak Devnet Config {#tweak-devnet-config}

By default, OffCKB use a fixed Devnet config. You can customize it, for example by modifying the default log level (`warn,ckb-script=debug`).
By default, OffCKB use a fixed Devnet config. You can customize it, for example by modifying the default log level (`info,ckb-script=debug`).

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

📐 Maintainability & Code Quality | 🟡 Minor | ⚡ Quick win

Correct the grammar and terminology.

Use “OffCKB uses” instead of “OffCKB use”, and call info,ckb-script=debug a log filter rather than a single log level.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@README.md` at line 334, Update the README sentence to use the grammatically
correct “OffCKB uses” and describe `info,ckb-script=debug` as a default log
filter rather than a log level, preserving the existing customization example.

Comment thread README.md
> CKB transactions carry no chain id, so a transaction built on a mainnet fork that spends copied mainnet cells is also valid on mainnet (CKB provides no replay protection). offckb's own flows only use dev keys and fork-mined cells, which cannot replay. Never sign transactions with real mainnet keys against a fork unless you intend to broadcast them yourself.

`offckb transfer` fails closed on a Mainnet fork: non-built-in keys require `--allow-mainnet-replay-risk`, and inputs copied from Mainnet are rejected even with that override.
`offckb transfer` fails closed on a Mainnet fork: non-built-in keys require `--allow-external-key-on-mainnet-fork`, and inputs copied from Mainnet are rejected even with that override. (`--allow-mainnet-replay-risk` from 0.4.9 remains as a deprecated alias.)

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

📐 Maintainability & Code Quality | 🟡 Minor | ⚡ Quick win

Document all guarded signing commands.

The changelog says the replay guard also covers transfer-all, udt issue, udt destroy, and deploy, but this README sentence documents only offckb transfer. Reword it as a cross-command rule and list the affected commands so users understand the same failure behavior and override requirements.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@README.md` at line 418, Update the README replay-guard sentence to describe
the shared Mainnet-fork behavior across transfer, transfer-all, UDT issue, UDT
destroy, and deploy. Include the non-built-in-key override, Mainnet-copied input
rejection, and deprecated --allow-mainnet-replay-risk alias requirements for all
listed commands.

Comment thread src/cmd/status.ts
Comment on lines +27 to +41
function devnetTcpListenAddress(): string | undefined {
try {
const settings = readSettings();
const ckbTomlPath = path.join(settings.devnet.configPath, 'ckb.toml');
if (!fs.existsSync(ckbTomlPath)) return undefined;
const parsed = toml.parse(fs.readFileSync(ckbTomlPath, 'utf8'));
const rpc = parsed.rpc as JsonMap | undefined;
const address = rpc?.tcp_listen_address;
if (typeof address !== 'string' || address.trim().length === 0) return undefined;
// A wildcard bind is not a dialable address; the node runs on this host.
return address.trim().replace(/^0\.0\.0\.0:/, '127.0.0.1:');
} catch {
return undefined;
}
}

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🩺 Stability & Availability | 🟡 Minor | ⚡ Quick win

Silent failure and IPv6 wildcard not normalized.

Two small gaps in this best-effort helper:

  • The catch swallows the reason entirely; a logger.debug makes "why are my mempool/logs panels empty?" diagnosable.
  • Only 0.0.0.0: is rewritten. A node configured with [::]:18114 (or :::18114) yields an address ckb-tui cannot dial; better to omit -t (or map to loopback) than to pass an undialable value.
♻️ Suggested handling
-    // A wildcard bind is not a dialable address; the node runs on this host.
-    return address.trim().replace(/^0\.0\.0\.0:/, '127.0.0.1:');
-  } catch {
-    return undefined;
-  }
+    // A wildcard bind is not a dialable address; the node runs on this host.
+    const trimmed = address.trim();
+    const port = trimmed.match(/^(?:0\.0\.0\.0|\[::\]|::):(\d+)$/);
+    return port ? `127.0.0.1:${port[1]}` : trimmed;
+  } catch (error) {
+    logger.debug(`skipping ckb-tui TCP endpoint discovery: ${(error as Error).message}`);
+    return undefined;
+  }
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
function devnetTcpListenAddress(): string | undefined {
try {
const settings = readSettings();
const ckbTomlPath = path.join(settings.devnet.configPath, 'ckb.toml');
if (!fs.existsSync(ckbTomlPath)) return undefined;
const parsed = toml.parse(fs.readFileSync(ckbTomlPath, 'utf8'));
const rpc = parsed.rpc as JsonMap | undefined;
const address = rpc?.tcp_listen_address;
if (typeof address !== 'string' || address.trim().length === 0) return undefined;
// A wildcard bind is not a dialable address; the node runs on this host.
return address.trim().replace(/^0\.0\.0\.0:/, '127.0.0.1:');
} catch {
return undefined;
}
}
function devnetTcpListenAddress(): string | undefined {
try {
const settings = readSettings();
const ckbTomlPath = path.join(settings.devnet.configPath, 'ckb.toml');
if (!fs.existsSync(ckbTomlPath)) return undefined;
const parsed = toml.parse(fs.readFileSync(ckbTomlPath, 'utf8'));
const rpc = parsed.rpc as JsonMap | undefined;
const address = rpc?.tcp_listen_address;
if (typeof address !== 'string' || address.trim().length === 0) return undefined;
// A wildcard bind is not a dialable address; the node runs on this host.
const trimmed = address.trim();
const port = trimmed.match(/^(?:0\.0\.0\.0|\[::\]|::):(\d+)$/);
return port ? `127.0.0.1:${port[1]}` : trimmed;
} catch (error) {
logger.debug(`skipping ckb-tui TCP endpoint discovery: ${(error as Error).message}`);
return undefined;
}
}
🧰 Tools
🪛 ast-grep (0.44.1)

[warning] 31-31: Filesystem path is not a string literal; a request-/variable-derived path can enable path traversal. Validate and normalize the path before use.
Context: fs.readFileSync(ckbTomlPath, 'utf8')
Note: [CWE-22] Improper Limitation of a Pathname to a Restricted Directory ('Path Traversal').

(detect-non-literal-fs-filename-typescript)

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@src/cmd/status.ts` around lines 27 - 41, Update devnetTcpListenAddress to log
the caught error with logger.debug before returning undefined, preserving its
best-effort behavior. Extend wildcard-address handling for IPv6 forms such as
[::]:port and :::port by mapping them to a dialable loopback address or
returning undefined so ckb-tui is not given an undialable target.

Comment thread src/node/init-chain.ts
Comment on lines +61 to +70
const modulesStart = lines.findIndex(
(line, index) => index > section.start && index < section.end && /^\s*modules\s*=\s*\[/.test(line),
);
if (modulesStart < 0) return false;

const singleLine = lines[modulesStart].match(/^(\s*modules\s*=\s*\[[^\]]*)\](\s*(?:#.*)?)$/);
if (singleLine) {
lines[modulesStart] = `${singleLine[1]}, "${TERMINAL_RPC_MODULE}"]${singleLine[2]}`;
return true;
}

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🗄️ Data Integrity & Integration | 🟠 Major | ⚡ Quick win

Empty single-line modules = [] produces invalid TOML.

needsTerminal is true for an empty array ([].every(...) is true, includes false), and the single-line branch then rewrites modules = [] as modules = [, "Terminal"], which no longer parses — CKB will refuse to start with the migrated config. Insert without a leading comma when the array body is empty.

🐛 Proposed fix
   const singleLine = lines[modulesStart].match(/^(\s*modules\s*=\s*\[[^\]]*)\](\s*(?:#.*)?)$/);
   if (singleLine) {
-    lines[modulesStart] = `${singleLine[1]}, "${TERMINAL_RPC_MODULE}"]${singleLine[2]}`;
+    const hasEntries = singleLine[1].slice(singleLine[1].indexOf('[') + 1).trim().length > 0;
+    const separator = hasEntries ? ', ' : '';
+    lines[modulesStart] = `${singleLine[1]}${separator}"${TERMINAL_RPC_MODULE}"]${singleLine[2]}`;
     return true;
   }
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
const modulesStart = lines.findIndex(
(line, index) => index > section.start && index < section.end && /^\s*modules\s*=\s*\[/.test(line),
);
if (modulesStart < 0) return false;
const singleLine = lines[modulesStart].match(/^(\s*modules\s*=\s*\[[^\]]*)\](\s*(?:#.*)?)$/);
if (singleLine) {
lines[modulesStart] = `${singleLine[1]}, "${TERMINAL_RPC_MODULE}"]${singleLine[2]}`;
return true;
}
const modulesStart = lines.findIndex(
(line, index) => index > section.start && index < section.end && /^\s*modules\s*=\s*\[/.test(line),
);
if (modulesStart < 0) return false;
const singleLine = lines[modulesStart].match(/^(\s*modules\s*=\s*\[[^\]]*)\](\s*(?:#.*)?)$/);
if (singleLine) {
const hasEntries = singleLine[1].slice(singleLine[1].indexOf('[') + 1).trim().length > 0;
const separator = hasEntries ? ', ' : '';
lines[modulesStart] = `${singleLine[1]}${separator}"${TERMINAL_RPC_MODULE}"]${singleLine[2]}`;
return true;
}
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@src/node/init-chain.ts` around lines 61 - 70, Update the single-line rewrite
in the modules migration logic around modulesStart and singleLine so an empty
modules array becomes valid TOML with only TERMINAL_RPC_MODULE, while retaining
the comma separator for non-empty arrays. Preserve existing indentation and
trailing comments.

Comment thread src/node/init-chain.ts
Comment on lines +141 to +156
const modules = rpc.modules;
const needsTerminal =
Array.isArray(modules) && modules.every((m) => typeof m === 'string') && !modules.includes(TERMINAL_RPC_MODULE);
const tcpAddress = rpc.tcp_listen_address;
const needsTcp = typeof tcpAddress !== 'string' || tcpAddress.trim().length === 0;
if (!needsTerminal && !needsTcp) return false;

const lines = source.split('\n');
const section = findRpcSection(lines);
if (section == null) return false;

const changes: string[] = [];
if (needsTerminal && addTerminalModule(lines, section)) {
changes.push('Terminal RPC module');
}
if (needsTcp && enableTcpListenAddress(lines, findRpcSection(lines) ?? section)) {

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🔒 Security & Privacy | 🟡 Minor | ⚡ Quick win

🧩 Analysis chain

🏁 Script executed:

#!/bin/bash
set -euo pipefail

# Map the relevant file and nearby helpers
ast-grep outline src/node/init-chain.ts --view expanded || true

# Inspect the target range and related helpers with line numbers
sed -n '1,260p' src/node/init-chain.ts | cat -n

# Search for listen_address handling and the legacy devnet template references
rg -n "listen_address|tcp_listen_address|TERMINAL_RPC_MODULE|get_overview|0\.0\.0\.0:8114|127\.0\.0\.1:8114|bundled template|devnet" src test . -g '!node_modules' -g '!dist' -g '!build' || true

Repository: ckb-devrel/offckb

Length of output: 50374


🏁 Script executed:

#!/bin/bash
set -euo pipefail

# Check the file that the comment references if it exists in the repo
git ls-files | rg '^.*ckb\.toml$|^.*devnet.*toml$|^.*template.*toml$|^.*init-chain\.ts$' || true

Repository: ckb-devrel/offckb

Length of output: 250


🏁 Script executed:

#!/bin/bash
set -euo pipefail

# If tests exist, inspect the migration tests around init-chain.
rg -n "init-chain|Terminal RPC module|tcp_listen_address|listen_address" test src -g '!node_modules' -g '!dist' -g '!build' || true

Repository: ckb-devrel/offckb

Length of output: 2405


🌐 Web query:

offckb 699a850 devnet ckb.toml 0.0.0.0 8114 rpc.listen_address Terminal RPC module

💡 Result:

The configuration and components you mentioned relate to running and monitoring a Nervos CKB node, particularly within a development environment using the OffCKB tool. RPC Configuration (ckb.toml) In a CKB node's ckb.toml file, the rpc.listen_address setting determines the network interface and port on which the node's JSON-RPC server listens [1][2]. By default, this is often set to 127.0.0.1:8114 to ensure only local processes can access the RPC [2]. Configuring this to 0.0.0.0:8114 instructs the node to listen on all available network interfaces, allowing external machines to make RPC requests [1][2]. This should only be done if strictly necessary, and it is highly recommended to secure the port with a firewall (e.g., via iptables) to restrict access to trusted IP addresses [3][2]. OffCKB and Proxy RPC OffCKB is a CLI tool designed to simplify local CKB development [4][5]. When you start a devnet via OffCKB, it typically runs a CKB node and an optional Proxy RPC server [4][6]. While the core CKB RPC server listens at 127.0.0.1:8114, the OffCKB Proxy RPC server is designed to forward requests to the node while recording them and automatically dumping failed transactions for easier debugging [4][6][5]. Note that port numbers for this proxy can vary (e.g., 28114 or 9000 depending on your version and configuration) [4][6][5]. Terminal RPC Module The Terminal module is a specific RPC feature introduced in recent CKB updates (e.g., via CKB-TUI integrations) to provide observability [7]. It serves as a dedicated interface for terminal-based monitoring tools (like ckb-tui) to retrieve essential runtime data, including system metrics, mining information, transaction pool status, and network peer information [7]. It uses an LRU cache with specific Time-to-Live (TTL) settings to ensure high performance when supplying this data to monitoring interfaces [7]. The reference 699a850 likely refers to a specific commit hash within a CKB-related repository, which identifies the state of the software at the time of that build [7].

Citations:


🌐 Web query:

site:github.com/nervosnetwork/ckb "rpc.tcp_listen_address" Terminal RPC module listen_address

💡 Result:

In the context of the Nervos CKB node software, rpc.tcp_listen_address is a configuration parameter used to enable and specify the bind address for a TCP-based JSON-RPC server [1][2][3]. This configuration is primarily used to support full-duplex communication, which is required for RPC subscription services (where the node pushes updates to the client) [1][2]. While CKB nodes primarily use an HTTP-based RPC server (configured via rpc.listen_address), the TCP server provides an alternative transport for these specialized, long-lived subscription connections [4][3]. The Terminal module, recently introduced in CKB v0.205.0, is a distinct RPC module designed to provide aggregated system and node data (such as mining info, transaction pool status, and network metrics) for the CKB-TUI (Terminal User Interface) [5][6]. It is one of several modules enabled within the CKB node configuration, but it is not inherently tied to the rpc.tcp_listen_address parameter [3][5]. The Terminal module's data is accessed through the standard RPC interface; the tcp_listen_address setting is simply a transport-layer configuration that may be used to handle the persistent connections required by various RPC modules if the operator chooses to enable TCP transport [2][3].

Citations:


Information Disclosure (CWE-200): Exposure of Sensitive Information to an Unauthorized Actor

Reachability: External
● Entry
  src/cmd/node.ts:77
  initChainIfNeeded
│
▼
● Sink
  src/node/init-chain.ts

Handle rpc.listen_address too The migration still leaves legacy devnet configs on 0.0.0.0:8114, so adding Terminal can expose unauthenticated get_overview over the network. Migrate that legacy bind to 127.0.0.1:8114, or skip enabling Terminal and warn on non-loopback RPC. Add a regression test for a config with rpc.listen_address = "0.0.0.0:8114".

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@src/node/init-chain.ts` around lines 141 - 156, Update the RPC migration flow
around the needsTerminal/needsTcp checks to inspect rpc.listen_address and
detect the legacy non-loopback 0.0.0.0:8114 bind. Migrate that bind to
127.0.0.1:8114 before or while adding the Terminal module, or skip Terminal
enablement and emit the required warning for non-loopback RPC; add a regression
test covering this configuration.

@RetricSu
RetricSu merged commit ffff028 into master Jul 26, 2026
13 of 14 checks passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants