Skip to content

Initial ARC Support - #15

Merged
martindale merged 45 commits into
masterfrom
feature/rsi
Aug 14, 2026
Merged

Initial ARC Support#15
martindale merged 45 commits into
masterfrom
feature/rsi

Conversation

@martindale

@martindale martindale commented Jun 25, 2026

Copy link
Copy Markdown
Member

Initial sketches for ARC support.

Summary by CodeRabbit

  • New Features
    • Added token-protected watch-only Bitcoin xpub query support (new scantxoutset-based endpoints) and expanded document-market/feature-flag presets.
    • Added an admin “self-destruct” workflow, operator setup secret enforcement, account switching controls, and encrypted identity backup/restore.
    • Enhanced sidechain/statechain UI with head/journal/snapshots and added related HTTP/RPC endpoints.
  • Bug Fixes
    • Improved hub startup/probing reliability and strengthened chat/peer/offline-first persistence behavior.
    • Hardened client fetch URL safety for sensitive chat/document actions.
  • Documentation
    • Documented how to protect watch-only scantxoutset requests and operator setup secret verification.

@coderabbitai

coderabbitai Bot commented Jun 25, 2026

Copy link
Copy Markdown

Review Change Stack

Important

Review skipped

Too many files!

This PR contains 272 files, which is 172 over the limit of 100.

To get a review, reduce the PR to 100 files or fewer by splitting it into smaller PRs or changing its base branch.

Upgrade to a paid plan to raise the limit.

This review couldn't start because sufficient usage credits or metered capacity aren't available. Add credits or update usage-based reviews in the billing tab, then retry.

⚙️ Run configuration

Configuration used: defaults

Review profile: CHILL

Plan: Pro Plus

Run ID: cdaaf4c9-3417-4385-b143-566a2e9ec3db

📥 Commits

Reviewing files that changed from the base of the PR and between 7411a33 and b6d8010.

⛔ Files ignored due to path filters (9)
  • assets/bundles/browser.min.js is excluded by !**/*.min.js
  • contracts/monetization.dot is excluded by !**/*.dot
  • contracts/network.dot is excluded by !**/*.dot
  • contracts/network.svg is excluded by !**/*.svg
  • contracts/ux.dot is excluded by !**/*.dot
  • contracts/ux.png is excluded by !**/*.png
  • contracts/ux.svg is excluded by !**/*.svg
  • package-lock.json is excluded by !**/package-lock.json
  • reports/install.log is excluded by !**/*.log
📒 Files selected for processing (272)
  • .codacy.yml
  • .env.local-mainnet.example
  • .github/workflows/ci.yml
  • .github/workflows/desktop.yml
  • .github/workflows/e2e-rc.yml
  • .github/workflows/test.yaml
  • .gitignore
  • .npmrc
  • .nvmrc
  • AGENTS.md
  • AUDIT.md
  • BITCOIN_NETWORKS.md
  • BRIDGE_UPGRADE.md
  • CHANGELOG.md
  • CHECKLIST.md
  • CLIENT_SECURITY_AUDIT.md
  • CONTRACTS.md
  • CONTRIBUTORS.md
  • DEVELOPERS.md
  • EVALUATION.md
  • EXTENSION.md
  • INVENTORY_HTLC_ONCHAIN.md
  • MESSAGE_PASSING_AUDIT.md
  • MESSAGE_TRANSPORT.md
  • PAYMENTS_PROTOCOL.md
  • PEER_MANAGEMENT_AND_LINK.md
  • PLAN.md
  • SECURITY.md
  • actions/bridgeActions.js
  • actions/contractActions.js
  • actions/documentActions.js
  • assets/config.local.example.js
  • assets/hub-mesh-bridge.html
  • assets/index.html
  • build/README.md
  • components/BitcoinBlockList.js
  • components/BitcoinResourcesHome.js
  • components/BitcoinWalletBranchBar.js
  • components/Bridge.js
  • components/ContractList.js
  • components/ContractsHome.js
  • components/CrowdfundingHome.js
  • components/Dashboard.js
  • components/DistributedFederationPanel.js
  • components/DocumentList.js
  • components/DocumentView.js
  • components/FaucetHome.js
  • components/FederationWalletMultisigPanel.js
  • components/GraphDocumentPreview.js
  • components/HubInterface.js
  • components/HubPagination.js
  • components/IdentityManager.js
  • components/Invoice.js
  • components/Onboarding.js
  • components/PeerList.js
  • components/PeerView.js
  • components/SecurityHome.js
  • components/SettingsBitcoinWallet.js
  • components/Splash.js
  • components/TrackedApplicationContractsPanel.js
  • contracts/beacon.js
  • contracts/beaconFederation.js
  • contracts/pricing.json
  • contracts/products.json
  • deploy/env.relay.goon.vc.example
  • docs/ADR-001-CONTRACT_NAMESPACE_SIDECHAINS.md
  • docs/BEACON_SIDECHAIN_DESIGN_AND_ROADMAP.md
  • docs/DESKTOP.md
  • docs/DISTRIBUTED_CONTRACT_EXECUTION.md
  • docs/DOCUMENTATION-AUDIT.md
  • docs/FABRIC_MESSAGE_ENVELOPE.md
  • docs/HUB_LIFECYCLE.md
  • docs/LOCAL_CLI_TESTING.md
  • docs/OUTSTANDING.md
  • docs/PAYMENTS_DOCUMENT_BINDING.md
  • docs/PAYMENTS_DOCUMENT_EXCHANGE_PLAN.md
  • docs/PRODUCTION-CHECKLIST.md
  • docs/PRODUCTION.md
  • docs/PRODUCTION_MARCH.md
  • docs/PRODUCTION_ROADMAP.md
  • docs/RELEASE_CHECKLIST.md
  • docs/RELEASE_UI_PLAN.md
  • docs/SIDECHAIN_AND_EXECUTION_INDEX.md
  • docs/UPSTREAM_MONOREPO.md
  • docs/index.html
  • extension/content.js
  • extension/manifest.json
  • extension/page-bridge.js
  • extension/popup.bundle.js.LICENSE.txt
  • extension/popup.html
  • extension/scripts/content.js
  • extension/scripts/page-bridge.js
  • extension/scripts/popup.js
  • functions/beaconFederationConstants.js
  • functions/beaconFederationSigning.js
  • functions/bitcoinClient.js
  • functions/bitcoinFaucetCapability.js
  • functions/bitcoinManagedAttach.js
  • functions/computeBestOffer.js
  • functions/contractMessageQueue.js
  • functions/contractStateSigning.js
  • functions/contractStatechains.js
  • functions/documentContentKey.js
  • functions/documentInventoryMarket.js
  • functions/documentOfferEscrow.js
  • functions/executionRegistryGate.js
  • functions/executionRunCommitment.js
  • functions/fabricAccountDerivedIdentity.js
  • functions/fabricBrowserIdentityDev.js
  • functions/fabricBrowserState.js
  • functions/fabricChatNormalize.js
  • functions/fabricChatNormalize.local.js
  • functions/fabricDelegation.js
  • functions/fabricDesktopAuth.js
  • functions/fabricDesktopLoginVerify.js
  • functions/fabricDeviceLink.js
  • functions/fabricDeviceLinkClient.js
  • functions/fabricDeviceLinkMessages.js
  • functions/fabricDistributedExecutionHttp.js
  • functions/fabricDocumentOfferEnvelope.js
  • functions/fabricDocumentPayment402.js
  • functions/fabricExecutionMachine.js
  • functions/fabricHallmarkBitcoin.js
  • functions/fabricHttpSemantic.js
  • functions/fabricHubAllowlist.js
  • functions/fabricHubLocalIdentity.js
  • functions/fabricIdentityBackupCrypto.js
  • functions/fabricIdentityCapabilities.js
  • functions/fabricLinkedDevices.js
  • functions/fabricMessageRegistry.js
  • functions/fabricPubkey.js
  • functions/fabricPubkey.local.js
  • functions/fabricWebRtcP2pRelay.js
  • functions/federationContractInvite.js
  • functions/federationVault.js
  • functions/fundLocalKeyFromHubFaucet.js
  • functions/httpRouteCatalog.js
  • functions/httpRouteProbe.js
  • functions/httpSharedMode.js
  • functions/hubCollaboration.js
  • functions/hubLifecycle.js
  • functions/hubPublicVisitor.js
  • functions/hubUiFeatureFlags.js
  • functions/identityCluster.js
  • functions/identityClusterHttp.js
  • functions/identityCrossSign.js
  • functions/identityCrossSignVerify.js
  • functions/messageTypes.js
  • functions/onionChatSeal.js
  • functions/oracleAttestation.js
  • functions/oracleAttestation.local.js
  • functions/patchLinkedFabricNodePath.js
  • functions/payjoinAsyncMailbox.js
  • functions/payjoinFabricProtocol.js
  • functions/payjoinPublicOrigin.js
  • functions/peerIdentity.js
  • functions/playnetMaskedConsole.js
  • functions/resolveFabricHttpSend402.js
  • functions/sendOnionRpc.js
  • functions/sidechainBlockScan.js
  • functions/sidechainHubClient.js
  • functions/sidechainState.js
  • functions/toast.js
  • functions/trackedApplicationContracts.js
  • package.json
  • reports/dependency-trees/@fabric_core.json
  • reports/dependency-trees/@fabric_core.txt
  • reports/dependency-trees/@fabric_http.json
  • reports/dependency-trees/@fabric_http.txt
  • reports/dependency-trees/@fabric_hub.json
  • reports/dependency-trees/@fabric_hub.txt
  • reports/dependency-trees/README.md
  • reports/dependency-trees/index.json
  • scripts/bitcoin-addnode.js
  • scripts/build-extension.js
  • scripts/build.js
  • scripts/ci-e2e-payjoin.sh
  • scripts/desktop.js
  • scripts/desktopHubProbe.js
  • scripts/ensure-fabric-linked.js
  • scripts/extract-hub-start-phases.js
  • scripts/fabric-operator-identity-sync.js
  • scripts/fabric-operator-identity.js
  • scripts/generate-dependency-trees.js
  • scripts/hub-ui-write-test-secrets.js
  • scripts/hub.js
  • scripts/immich-host-disk-and-migrate.sh
  • scripts/lib/playnetOps.js
  • scripts/link-fabric.sh
  • scripts/playnet-flush-chain.js
  • scripts/playnet-hub-with-settings.js
  • scripts/playnet-l1-resolve.js
  • scripts/playnet-mine-subsidy.js
  • scripts/playnet-regtest-mesh-launch.js
  • scripts/playnet-reset.js
  • scripts/playnet-status.js
  • scripts/prepare-hub-dev-port.js
  • scripts/run-hub-local-mainnet.sh
  • scripts/time-bitcoind-startup.js
  • scripts/verify-bitcoin-broadcast.js
  • scripts/verify-bitcoin-browser.js
  • scripts/verify-browser-e2e-suite.js
  • scripts/verify-contracts-l1-e2e-suite.js
  • scripts/verify-crowdfund-l1-e2e.js
  • scripts/verify-document-purchase-e2e.js
  • scripts/verify-execution-contract-e2e.js
  • scripts/verify-full-stack.js
  • scripts/verify-http-routes.js
  • scripts/verify-payjoin-e2e.js
  • scripts/verify-storage-contract-e2e.js
  • scripts/verify-value-flows-e2e.js
  • scripts/verify-webrtc-chat-e2e.js
  • services/email.js
  • services/fabric.js
  • services/hub.js
  • services/payjoin.js
  • services/peering.js
  • services/setup.js
  • settings/local.js
  • shims/noble-nist.js
  • shims/noble-secp256k1.js
  • shims/noble-utils.js
  • snippets/hello.md
  • tests/adversarialEnvironment.basics.test.js
  • tests/beacon.federationRetry.test.js
  • tests/bitcoinClient.authToken.test.js
  • tests/bitcoinFaucetCapability.test.js
  • tests/bitcoinManagedAttach.test.js
  • tests/browser.interface.test.js
  • tests/contractMessageQueue.test.js
  • tests/contractStatechains.test.js
  • tests/documentContentKeyHtlc.test.js
  • tests/documentInventoryMarket.test.js
  • tests/documentMarket.hub.test.js
  • tests/electron.desktopHubProbe.test.js
  • tests/executionRegistryGate.test.js
  • tests/fabric.deviceLink.test.js
  • tests/fabricBrowserIdentityDev.test.js
  • tests/fabricChatNormalize.test.js
  • tests/fabricDelegation.mount.test.js
  • tests/fabricDesktopLoginVerify.test.js
  • tests/fabricHallmark.test.js
  • tests/fabricHubAllowlist.test.js
  • tests/fabricHubLocalIdentity.fabricHdRole.test.js
  • tests/fabricIdentityBackupCrypto.test.js
  • tests/fabricIdentityCapabilities.test.js
  • tests/fabricMessageRegistry.test.js
  • tests/fabricWebRtcP2pRelay.test.js
  • tests/fundLocalKeyFromHubFaucet.test.js
  • tests/httpRouteProbe.test.js
  • tests/httpSharedMode.test.js
  • tests/hub.http.js
  • tests/hub.sendOnion.rpc.test.js
  • tests/hub.sendOnion.unit.test.js
  • tests/hub.webrtc.rpc.test.js
  • tests/hubLifecycle.test.js
  • tests/hubNetworkStatus.test.js
  • tests/identityCluster.test.js
  • tests/liftedApis.exports.test.js
  • tests/messageTypes.test.js
  • tests/payjoinBip78Bip77.test.js
  • tests/payjoinFabricProtocol.test.js
  • tests/peerIdentity.consolidate.test.js
  • tests/playnet.beacon.federation.integration.js
  • tests/playnet.contract.publish.scan.integration.js
  • tests/playnet.ops.sweep.test.js
  • tests/setup.verifyAdminToken.test.js
  • tests/sidechainState.test.js
  • tests/trackedApplicationContracts.test.js
  • types/spa.js
  • webpack.config.js
  • webpack.extension.config.js

You can disable this status message by setting the reviews.review_status to false in the CodeRabbit configuration file.

📝 Walkthrough

Walkthrough

This PR adds Fabric account identity derivation, encrypted identity backups, browser state handling, wallet authorization, document-offer and peer routing, sidechain state APIs, Hub setup security, desktop startup probing, semantic asset wiring, and related UI and test updates.

Changes

Hub identity, wallet, and client flows

Layer / File(s) Summary
Identity and storage primitives
functions/fabricAccountDerivedIdentity.js, functions/fabricHubLocalIdentity.js, functions/fabricIdentityBackupCrypto.js, functions/fabricBrowserState.js, functions/fabricDesktop*.js
Fabric account keys, encrypted local identity state, encrypted backups, browser-state cleanup, and signed desktop login verification are implemented.
Wallet and identity UI integration
functions/bitcoinClient.js, components/IdentityManager.js, components/HubInterface.js, components/fabricIdentity/*, components/Bitcoin*.js
Wallet derivation uses Fabric account indexes and hub tokens; identity UI adds account switching, onboarding secret handling, encrypted backup/import, and expanded lock state.
Client fetch and feature-flag updates
actions/*, functions/hubUiFeatureFlags.js, components/Document*.js, components/AdminHome.js
Client fetch paths are validated, feature flags use Fabric browser state, document UI is gated by server flags, and the document-market preset is added.

Hub runtime, peer, document, and sidechain flows

Layer / File(s) Summary
Peer and document transport
components/Bridge.js, components/PeerView.js, functions/fabricDocumentOfferEnvelope.js, functions/fabricMessageRegistry.js
Document-offer envelopes, normalized chat and peer frames, HTTP inventory fallback, aliases, HTLC key reveals, and tracked contract events are wired through Hub and browser transports.
Sidechain and contract state
functions/fabricStatechain.js, functions/sidechainState.js, functions/fabricDistributedExecutionHttp.js, services/hub.js, components/SidechainHome.js, contracts/beacon.js
Sidechain patches, policies, snapshots, journals, contract namespaces, Beacon integration, distributed HTTP routes, and operator displays are added or delegated to canonical implementations.
Hub HTTP and setup security
services/hub.js, services/setup.js, routes/documents/view_document.js, settings/local.js
Setup state moves into STATE, setup-secret and self-destruct endpoints are added, xpub queries and wallet routes receive authorization checks, and priced document responses include payment offers.

Runtime, assets, and validation

Layer / File(s) Summary
Desktop and asset startup
scripts/desktop.js, scripts/desktopHubProbe.js, scripts/prepare-hub-dev-port.js, scripts/build*.js, webpack.config.js, package.json
Desktop startup validates /settings, retries UI loading, avoids sample servers, synchronizes Fabric HTTP semantic assets, and packages the scripts-based entrypoint.
Documentation and regression coverage
AGENTS.md, DEVELOPERS.md, docs/*, tests/*
Operational documentation and tests cover identity, desktop authentication, browser storage, feature flags, routes, sidechain state, HTTP behavior, and onboarding.

Estimated code review effort: 5 (Critical) | ~120 minutes

🚥 Pre-merge checks | ✅ 3 | ❌ 2

❌ Failed checks (2 warnings)

Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 44.17% which is insufficient. The required threshold is 80.00%. Write docstrings for the functions missing them to satisfy the coverage threshold.
Title check ⚠️ Warning The title says “ARC Support,” but the objectives and changes describe initial RSI support and broad Hub functionality changes. Rename the title to identify the primary change, such as “Initial RSI Support,” if RSI is the intended scope.
✅ Passed checks (3 passed)
Check name Status Explanation
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.
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
✨ Finishing Touches
📝 Generate docstrings
  • Create stacked PR
  • Commit on current branch
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Commit unit tests in branch feature/rsi

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

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

@martindale
martindale marked this pull request as draft June 25, 2026 11:37
@codacy-production

codacy-production Bot commented Jun 25, 2026

Copy link
Copy Markdown

Not up to standards ⛔

🔴 Issues 16 critical · 81 high · 3 medium

Alerts:
⚠ 100 issues (≤ 0 issues of at least minor severity)

Results:
100 new issues

Category Results
ErrorProne 3 medium
17 high
Security 16 critical
64 high

View in Codacy

🟢 Metrics 4278 complexity · 2 duplication

Metric Results
Complexity 4278
Duplication 2

View in Codacy

NEW Get contextual insights on your PRs based on Codacy's metrics, along with PR and Jira context, without leaving GitHub. Enable AI reviewer
TIP This summary will be updated as you push new changes.

@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: 19

Note

Due to the large number of review comments, Critical, Major severity comments were prioritized as inline comments.

Caution

Some comments are outside the diff and can’t be posted inline due to platform limitations.

⚠️ Outside diff range comments (7)
scripts/desktop.js (1)

160-213: 🩺 Stability & Availability | 🟡 Minor | ⚡ Quick win

waitForHub polling can hang if the socket connects but never responds.

The overall timeoutMs deadline is only evaluated inside the res.on('end') and req.on('error') handlers. If the port accepts the connection but never sends a response (or never ends the body), neither handler fires, ping is never rescheduled, and the returned Promise never settles — the deadline is silently bypassed. Add a per-request timeout so a stalled socket is forced into the error/retry path.

🛡️ Proposed fix: add a request timeout
       const req = http.get(
         settingsUrl,
         {
           headers: {
             Accept: 'application/json'
-          }
+          },
+          timeout: 5000
         },
         (res) => {

Add alongside the existing req.on('error', ...):

req.on('timeout', () => {
  req.destroy(new Error('Hub /settings request timed out'));
});

(req.destroy(err) will surface through the existing error handler, which already re-checks the deadline and reschedules.)

🤖 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 `@scripts/desktop.js` around lines 160 - 213, The waitForHub polling in ping
can hang when http.get connects but never finishes the response, because the
existing timeout only runs in the end and error paths. Add a per-request timeout
on the req object in ping so stalled requests are destroyed and routed through
the existing req.on('error') retry logic, ensuring the overall deadline in
waitForHub is still enforced.
functions/bitcoinClient.js (2)

1246-1262: 🎯 Functional Correctness | 🟡 Minor | ⚡ Quick win

Honor bypassCache and maxCacheAgeMs on fallback.

Line 1262 always passes Infinity, so a manual refresh or explicit maxCacheAgeMs: 0 can still return stale wallet balances after fetchWalletSummary throws.

Proposed fix
-  const cached = cacheKey ? getCachedBalance(cacheKey, Infinity) : null;
+  const cached = cacheKey ? getCachedBalance(cacheKey, maxCacheAgeMs) : null;
🤖 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 `@functions/bitcoinClient.js` around lines 1246 - 1262, The fallback cache
lookup in fetchWalletSummaryWithCache ignores the computed cache policy, so
bypassCache and maxCacheAgeMs are not respected after fetchWalletSummary fails.
Update the cache read path in fetchWalletSummaryWithCache to use the derived
maxCacheAgeMs (or bypassCache) instead of always calling getCachedBalance with
Infinity, so explicit refresh requests cannot return stale balances.

1623-1626: 🎯 Functional Correctness | 🟠 Major | ⚡ Quick win

Use the selected Fabric account for crowdfunding keys.

These paths still derive account 0, while payments now use getBitcoinBip44AccountForIdentity(identity). In multi-account mode, beneficiary/refund keys won’t match the active wallet account.

Proposed fix
+  const accountN = getBitcoinBip44AccountForIdentity(identity);
   if (masterXprv) {
     try {
-      const d = deriveFabricBitcoinAccountKeys(masterXprv, masterXpub, BITCOIN_PAYMENTS_BIP44_ACCOUNT_INDEX);
+      const d = deriveFabricBitcoinAccountKeys(masterXprv, masterXpub, accountN);
+  const accountN = getBitcoinBip44AccountForIdentity(identity);
   try {
-    const d = deriveFabricBitcoinAccountKeys(masterXprv, masterXpub, BITCOIN_PAYMENTS_BIP44_ACCOUNT_INDEX);
+    const d = deriveFabricBitcoinAccountKeys(masterXprv, masterXpub, accountN);

Also applies to: 1667-1669

🤖 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 `@functions/bitcoinClient.js` around lines 1623 - 1626, The crowdfunding key
derivation is still hardcoded to account 0, so beneficiary/refund keys can drift
from the active Fabric wallet account. Update the crowdfunding derivation in
bitcoinClient.js to use the selected identity account via
getBitcoinBip44AccountForIdentity(identity) instead of
BITCOIN_PAYMENTS_BIP44_ACCOUNT_INDEX, and apply the same change in the related
beneficiary/refund key path so deriveFabricBitcoinAccountKeys stays aligned with
the active account.
actions/documentActions.js (2)

109-117: 🩺 Stability & Availability | 🟠 Major | ⚡ Quick win

Let the upload timeout race the request.

await fetch(...) completes before Promise.race, so a hung upload cannot be interrupted by timeoutPromise.

Proposed fix
-      const fetchPromise = await fetch(assertClientFetchPath('/files'), {
+      const fetchPromise = fetch(assertClientFetchPath('/files'), {
🤖 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 `@actions/documentActions.js` around lines 109 - 117, The upload timeout is not
working because fetchPromise is awaited before Promise.race, so a slow or hung
request cannot be interrupted. Update the upload flow in documentActions.js so
fetch(assertClientFetchPath('/files'), ...) is passed directly into Promise.race
alongside timeoutPromise, and keep the existing fileCreation handling in the
upload routine.

191-204: 🎯 Functional Correctness | 🟠 Major | ⚡ Quick win

Check edit failures before dispatching success.

A 4xx/5xx edit response is parsed and dispatched through editDocumentSuccess, which makes failed edits look successful.

Proposed fix
       const response = await fetch(
         assertClientFetchPath(`/documents/${encodeURIComponent(fabricID)}`),
         {
@@
         body: JSON.stringify({ title })
       });
 
+      if (!response.ok) {
+        const errorData = await response.json().catch(() => ({}));
+        throw new Error(errorData.message || 'Server error');
+      }
+
       const document = await response.json();
🤖 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 `@actions/documentActions.js` around lines 191 - 204, The edit flow in
editDocument is dispatching editDocumentSuccess even when the PATCH request
fails, so add a response.ok (or equivalent status check) before parsing and
dispatching. In the action that calls fetch for
/documents/${encodeURIComponent(fabricID)}, branch on failure to stop the
success path and handle the error state instead, then only call
editDocumentSuccess(document) when the response is successful.
components/IdentityManager.js (2)

1903-1925: 🔒 Security & Privacy | 🟠 Major | 🏗️ Heavy lift

Persist imported private-key backups encrypted, not watch-only.

When importing an xprv, local storage only receives id/xpub, while nextIdentity keeps the private key in memory with passwordProtected: false. After reload the user loses signing access, and before reload the key is not covered by auto-lock. Require a local encryption password and store via the encrypted identity path.

🤖 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 `@components/IdentityManager.js` around lines 1903 - 1925, The import flow in
IdentityManager is treating xprv-backed identities like watch-only entries by
writing only the xpub and marking nextIdentity as passwordProtected false.
Update the import path in the local-storage block and the nextIdentity
construction so xprv imports require a local encryption password and are
persisted through the encrypted identity flow instead of fabric.identity.local.
Make sure the logic around xprv, writeStorageJSON, and nextIdentity keeps the
private key protected and reloadable.

397-407: 🔒 Security & Privacy | 🟠 Major | ⚡ Quick win

Do not persist unlocked private keys to extension storage.

This sync writes xprv/masterXprv into chrome.storage.local whenever the identity is unlocked, bypassing the encrypted-at-rest flow used for fabric.identity.local. Keep extension sync watch-only unless the key material is encrypted first.

Proposed fix
         const payload = {
           id: localIdentity.id,
           xpub: localIdentity.xpub,
-          xprv: localIdentity.xprv || undefined,
           passwordProtected: !!localIdentity.passwordProtected,
           fabricIdentityMode: localIdentity.fabricIdentityMode || undefined,
           fabricAccountIndex: localIdentity.fabricAccountIndex,
           fabricHdRole: localIdentity.fabricHdRole || undefined,
-          masterXprv: localIdentity.masterXprv || undefined,
           masterXpub: localIdentity.masterXpub || undefined
         };
🤖 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 `@components/IdentityManager.js` around lines 397 - 407, The sync payload in
IdentityManager’s local identity persistence is writing unlocked private key
material directly to extension storage. Update the logic around the
localIdentity payload build so it does not include xprv or masterXprv in the
chrome.storage.local sync path; keep this flow watch-only unless the key
material is first encrypted using the existing encrypted-at-rest mechanism for
fabric.identity.local. Verify the payload construction in the local identity
save/sync branch only carries public or non-sensitive fields.
🟡 Minor comments (6)
assets/scripts/assets/manifest.json-6-8 (1)

6-8: 🗄️ Data Integrity & Integration | 🟡 Minor

Remove duplicate main.js entry mapping to index.min.js.

The manifest defines both index.min.js and main.js pointing to the same external CDN URL (https://fabric.pub/index.min.js) with identical integrity hashes. Since main.js is not used as a distinct entry point in the loading scripts and serves no differentiating purpose here, this appears to be an accidental duplicate. Please remove the main.js key to avoid confusion.

  "main.js": {
    "src": "https://fabric.pub/index.min.js",
    "integrity": "sha256-..."
  }
🤖 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 `@assets/scripts/assets/manifest.json` around lines 6 - 8, The manifest has a
duplicate asset mapping where main.js points to the same CDN source and
integrity as index.min.js, so remove the redundant main.js entry from the assets
manifest. Update the manifest object in assets/scripts/assets/manifest.json so
only the distinct entry remains, and keep the existing index.min.js mapping
unchanged.
functions/fabricBrowserState.js-235-238 (1)

235-238: 🔒 Security & Privacy | 🟡 Minor | ⚡ Quick win

Clear session unlock state through the resolved browser global.

This function uses w everywhere else, but Line 236 falls back to direct window; if getFabricBrowserGlobal() resolves a provided browser global, the unlock blob may remain.

Proposed fix
-      if (typeof window !== 'undefined' && window.sessionStorage) {
-        window.sessionStorage.removeItem('fabric.identity.unlocked');
+      if (w.sessionStorage) {
+        w.sessionStorage.removeItem('fabric.identity.unlocked');
       }
🤖 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 `@functions/fabricBrowserState.js` around lines 235 - 238, The unlock-state
cleanup in fabricBrowserState should use the resolved browser global
consistently instead of falling back to window. Update the sessionStorage
removal inside the unlock cleanup logic to reference the same browser-global
variable used elsewhere in the function (for example, the one returned by
getFabricBrowserGlobal and stored in w), so the identity key is cleared even
when a provided browser global is resolved.
components/BitcoinWalletBranchBar.js-12-13 (1)

12-13: 🎯 Functional Correctness | 🟡 Minor | ⚡ Quick win

Display masterXpub for the “Master xpub” label.

In account mode, identity.xpub is the selected account xpub. Use identity.masterXpub for the master label, or relabel this as the account xpub.

Proposed fix
 function BitcoinWalletBranchBar ({ identity }) {
   const xpub = identity && identity.xpub ? String(identity.xpub) : '';
+  const masterXpub = identity && identity.masterXpub ? String(identity.masterXpub) : xpub;
   const acct = getBitcoinBip44AccountForIdentity(identity || {});
@@
-          {xpub ? (
+          {masterXpub ? (
             <p style={{ margin: 0, color: '`#666`', fontSize: '0.85em', wordBreak: 'break-all' }}>
               <strong>Master xpub:</strong>{' '}
               <code>
-                {xpub.slice(0, 18)}…{xpub.slice(-10)}
+                {masterXpub.slice(0, 18)}…{masterXpub.slice(-10)}
               </code>
             </p>

Also applies to: 36-40

🤖 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 `@components/BitcoinWalletBranchBar.js` around lines 12 - 13, The “Master xpub”
label is currently using the selected account xpub from BitcoinWalletBranchBar,
so update the xpub source to use identity.masterXpub for that label and keep
identity.xpub only for the account-level display. Adjust the related rendering
logic in BitcoinWalletBranchBar so the master/account distinction is explicit,
and verify any other uses in the referenced range still point to the correct
symbol.
components/BitcoinBlockList.js-39-48 (1)

39-48: 🎯 Functional Correctness | 🟡 Minor | ⚡ Quick win

Use the same effective admin token for both channels.

When the token exists only in browser storage, upstreamAdmin.hubAdminToken gets it but fetchExplorerData(..., { adminToken }) still receives ''. That can break admin-gated explorer calls that read the options token.

Proposed fix
       const adminTok = (this.props.adminToken != null && String(this.props.adminToken).trim())
         ? String(this.props.adminToken).trim()
         : '';
+      const effectiveAdminTok = adminTok || readHubAdminTokenFromBrowser(null) || '';
       const upstreamAdmin = {
         ...this.state.upstream,
-        hubAdminToken: adminTok || readHubAdminTokenFromBrowser(null) || ''
+        hubAdminToken: effectiveAdminTok
       };
       const [data, status] = await Promise.all([
-        fetchExplorerData(upstreamAdmin, spend, { network: net, adminToken: adminTok }).catch(() => ({})),
+        fetchExplorerData(upstreamAdmin, spend, { network: net, adminToken: effectiveAdminTok }).catch(() => ({})),
         fetchBitcoinStatus(upstreamAdmin).catch(() => ({}))
       ]);
🤖 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 `@components/BitcoinBlockList.js` around lines 39 - 48, The admin token is
resolved into upstreamAdmin.hubAdminToken but fetchExplorerData still receives
the raw adminTok value, so browser-stored tokens are not forwarded to the
explorer call. Update BitcoinBlockList’s token handling so the same effective
token is computed once and passed consistently to both upstreamAdmin and the
fetchExplorerData options.adminToken, keeping fetchBitcoinStatus aligned with
the same resolved token path.
functions/hubUiFeatureFlags.js-189-190 (1)

189-190: 🎯 Functional Correctness | 🟡 Minor | ⚡ Quick win

Let persisted settings replace stale local flags.

The header says persisted { value } overwrites locals, but merging over loadHubUiFeatureFlags() preserves old browser-only values for any omitted server keys. Normalize the persisted payload directly so omitted keys fall back to bundled defaults.

Proposed fix
-    const localBefore = loadHubUiFeatureFlags();
-    const next = normalizeFlags({ ...localBefore, ...raw });
+    const next = normalizeFlags(raw);
🤖 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 `@functions/hubUiFeatureFlags.js` around lines 189 - 190, The flag merge in
loadHubUiFeatureFlags/normalizeFlags is preserving stale local browser-only
values because it spreads loadHubUiFeatureFlags() into raw before normalization.
Update this flow so the persisted payload is normalized directly, with omitted
keys falling back to bundled defaults rather than any previously saved local
state. Keep the fix focused in hubUiFeatureFlags.js around loadHubUiFeatureFlags
and the normalizeFlags call.
services/hub.js-208-212 (1)

208-212: 🔒 Security & Privacy | 🟡 Minor

Default exposePaymentTestRoute implements opt-out posture instead of secure opt-in.

The current logic exposes the payment test route unless FABRIC_HTTP_PAYMENTS_HIDE_TEST_ROUTE is explicitly set, defaulting to true when the variable is undefined. Documentation for @fabric/http indicates this setting should be opt-in (disabled by default) to prevent exposing payment settlement hooks in public hubs. This implementation inadvertently leaves test routes open by default.

Refer to the snippet below:

Current logic (services/hub.js:208-212)
exposePaymentTestRoute:
  !(
    process.env.FABRIC_HTTP_PAYMENTS_HIDE_TEST_ROUTE === '1' ||
    process.env.FABRIC_HTTP_PAYMENTS_HIDE_TEST_ROUTE === 'true'
  ),

Consider switching to an explicit opt-in approach for production safety.

🤖 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 `@services/hub.js` around lines 208 - 212, The exposePaymentTestRoute setting
in hub configuration is using an unsafe opt-out default and should be changed to
secure opt-in behavior. Update the payment route flag logic in the hub setup so
it is disabled unless explicitly enabled, instead of defaulting to true when
FABRIC_HTTP_PAYMENTS_HIDE_TEST_ROUTE is unset. Use the exposePaymentTestRoute
configuration block in services/hub.js as the fix point and keep the
environment-variable check aligned with the documented default.
🧹 Nitpick comments (6)
package.json (1)

120-121: 📐 Maintainability & Code Quality | 🔵 Trivial | ⚡ Quick win

Branch-pinned Fabric deps are non-reproducible.

Pinning @fabric/core and @fabric/http to feature/rsi means npm i resolves to whatever the branch HEAD is at install time, so builds are not reproducible and can silently drift. Fine for this WIP PR, but pin to a commit SHA or tag before merging to master.

🤖 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 `@package.json` around lines 120 - 121, The Fabric dependencies are pinned to
moving branch refs, which makes installs non-reproducible; update the
`@fabric/core` and `@fabric/http` entries in package.json to immutable references
such as a commit SHA or a release tag before merging. Keep the dependency names
the same, but replace the feature/rsi branch targets so future npm i runs
resolve to a fixed version.
scripts/prepare-hub-dev-port.js (2)

108-116: 📐 Maintainability & Code Quality | 🔵 Trivial | 💤 Low value

Redundant error branch swallows all probe failures identically.

Both the ECONNREFUSED/EHOSTUNREACH case and the fallthrough return, so the error-code check has no effect — any OPTIONS failure (timeout, parse, other network error) is treated as "port is free." For a best-effort prestart that's acceptable, but the branch is dead and misleading; consider collapsing it or logging non-connection errors so an unexpectedly-failing sample listener isn't silently ignored.

♻️ Optional simplification
   try {
     j = await optionsJson();
   } catch (e) {
-    if (e && (e.code === 'ECONNREFUSED' || e.code === 'EHOSTUNREACH')) {
-      return;
-    }
+    // Nothing reachable / unparseable on the port: treat as free for prestart.
     return;
   }
🤖 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 `@scripts/prepare-hub-dev-port.js` around lines 108 - 116, The error handling
in the options probe is redundant because both the ECONNREFUSED/EHOSTUNREACH
path and the fallback branch do the same thing. Update the try/catch around
optionsJson in prepare-hub-dev-port.js to either collapse the branches into a
single best-effort return or, if keeping the distinction, log non-connection
failures before returning. Keep the change localized to the optionsJson probe
logic so the behavior remains clear and intentional.

63-78: 🔒 Security & Privacy | 🔵 Trivial | 💤 Low value

execSync with interpolated values — confirm inputs stay numeric.

The static analysis flags command injection here. In practice p is Number()-coerced and pid comes from parsed lsof output mapped through Number(...), so current call sites are safe. To make this robust against future callers and silence the warning, prefer execFileSync with an argument array.

🛡️ Suggested hardening
-const { execSync } = require('child_process');
+const { execFileSync } = require('child_process');
@@
-    const out = execSync(`lsof -nP -iTCP:${p} -sTCP:LISTEN -t`, { encoding: 'utf8' });
+    const out = execFileSync('lsof', ['-nP', `-iTCP:${p}`, '-sTCP:LISTEN', '-t'], { encoding: 'utf8' });
@@
-    return execSync(`ps -p ${pid} -o args=`, { encoding: 'utf8' }).trim();
+    return execFileSync('ps', ['-p', String(pid), '-o', 'args='], { encoding: 'utf8' }).trim();
🤖 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 `@scripts/prepare-hub-dev-port.js` around lines 63 - 78, The
`pidsListeningOnPort` and `commandLineForPid` helpers are using `execSync` with
interpolated values, which triggers command-injection warnings. Harden these
calls by switching to `execFileSync` (or equivalent argument-array execution) in
both helpers, keeping the existing numeric parsing in place so `p` and `pid`
remain validated inputs. This change should be applied within the
`pidsListeningOnPort` and `commandLineForPid` functions in
`prepare-hub-dev-port.js`.

Source: Linters/SAST tools

tests/fabricHubLocalIdentity.fabricHdRole.test.js (1)

108-115: 📐 Maintainability & Code Quality | 🔵 Trivial

Remove unused second argument from buildLocalFabricIdentityPayload call.

The function buildLocalFabricIdentityPayload defined in functions/fabricHubLocalIdentity.js accepts only a single parsed object. The second argument { unlockPlaintextMaster: true } is ignored and serves no purpose.

Code that should be removed
    const bl = buildLocalFabricIdentityPayload(
      {
        fabricIdentityMode: 'account',
        fabricAccountIndex: 2,
        xpub: dk.xpub
      },
-     { unlockPlaintextMaster: 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 `@tests/fabricHubLocalIdentity.fabricHdRole.test.js` around lines 108 - 115,
Remove the unused second argument from the `buildLocalFabricIdentityPayload`
call in `fabricHubLocalIdentity.fabricHdRole.test.js`;
`buildLocalFabricIdentityPayload` in `functions/fabricHubLocalIdentity.js` only
accepts the parsed identity object, so update the test to pass just that single
object and drop the ignored `{ unlockPlaintextMaster: true }` argument.
functions/resolveFabricHttpSend402.js (1)

8-20: 📐 Maintainability & Code Quality | 🔵 Trivial | 💤 Low value

Dynamic require is safe here, but the Codacy gate is failing.

The argument is built from require.resolve('@fabric/http/package.json'), so the path is package-controlled, not untrusted input — the warning is a false positive. Since the Codacy check is reported as failing on Line 10, consider adding a scoped suppression so the gate stays green without weakening the analysis elsewhere.

♻️ Optional: scoped suppression
 try {
   const root = path.dirname(require.resolve('`@fabric/http/package.json`'));
+  // codacy:ignore detect-non-literal-require — path derived from resolved `@fabric/http` root
   module.exports = require(path.join(root, 'functions/sendPaymentRequired402Response.js'));
 } catch (_) {
🤖 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 `@functions/resolveFabricHttpSend402.js` around lines 8 - 20, The dynamic
require in resolveFabricHttpSend402 is using a package-controlled path from
require.resolve('`@fabric/http/package.json`'), so this is a false positive from
Codacy. Add a scoped suppression around the require(path.join(root,
'functions/sendPaymentRequired402Response.js')) call (or the minimal surrounding
block) so the gate passes without disabling the rule globally, and keep the
fallback export for sendPaymentRequired402Response unchanged.

Source: Linters/SAST tools

services/hub.js (1)

5691-5703: 🔒 Security & Privacy | 🔵 Trivial | 💤 Low value

Use a constant-time comparison for the xpub query token.

bearer === required / qTok === required / headerStr === required are short-circuiting string comparisons and are timing-observable. The codebase already has timingSafeSha256Utf8Match for secret comparison; reuse it here for parity with the setup-secret path.

🤖 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 `@services/hub.js` around lines 5691 - 5703, The xpub query token check in the
token-validation path is using plain string equality on bearer, query, and
header values, which should be replaced with a constant-time secret comparison.
Update the comparison logic in the hub token gate to use
timingSafeSha256Utf8Match, following the same pattern used for the setup-secret
flow, and keep the existing ok/403 response behavior intact.
🤖 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 `@actions/documentActions.js`:
- Around line 87-89: The document load path is destructuring the auth token
incorrectly, so the value passed into fetchFromAPI is undefined. Update the
token retrieval in documentActions.js to read directly from getState().auth and
pass that token into fetchFromAPI, using the existing load logic around the
document fetch in the action that requests
`/documents/${encodeURIComponent(fabricID)}`.

In `@components/BitcoinHome.js`:
- Around line 316-332: The admin token is being forwarded to non-Hub endpoints
through the bitcoin client helpers, which can leak credentials to external
services. Update the call flow around BitcoinHome’s fetches and the underlying
bitcoinClient functions (fetchBitcoinStatusAtBase, fetchWalletSummary,
fetchUTXOs, and any shared tryRequests/auth token setup) so hubAdminToken is
only used when the destination is the internal Hub RPC endpoint
(/services/bitcoin). For explorerBaseUrl and paymentsBaseUrl, either validate
they are internal before attaching auth or explicitly omit hubAdminToken and
fall back to non-admin auth only.

In `@components/Bridge.js`:
- Around line 4271-4273: The relay filter in Bridge.js is too narrow: the
inventory branch only allows INVENTORY_RESPONSE and
FABRIC_DOCUMENT_OFFER_RESPONSE before calling
isDocumentInventoryDocumentsOfferResponse, so FABRIC_DOCUMENT_OFFER_REPLY never
reaches the merge path. Update the conditional around the relay handling in
Bridge to include all document-offer response aliases accepted by the predicate,
especially FABRIC_DOCUMENT_OFFER_REPLY, so those envelopes are parsed and merged
into peer inventory.

In `@components/HubInterface.js`:
- Around line 984-993: The forget/destroy cleanup paths currently clear
persisted identity data but leave fabric.identity.unlocked in sessionStorage.
Update _handleIdentityManagerForget and the matching destroy cleanup block to
invoke the existing session cleanup helper that removes unlocked session key
material, alongside the current storage removals, so private keys are cleared
consistently.
- Around line 1185-1187: The safety timer in HubInterface.js is marking setup as
checked without also indicating that setup is configured, which can bypass
onboarding when /settings hangs. Update the timeout handler in the setup flow so
that the fallback state does not leave needsSetup at its default false; ensure
the logic around setupChecked and needsSetup in the relevant HubInterface
methods treats an unknown setup status as not configured until the settings
check completes.
- Around line 1019-1022: Account switching is reading the master key only from
stored local identity, so it can no-op when the unlocked session already has the
master key. Update the account-switch flow in HubInterface’s account switching
logic to prefer the unlocked master key from the current identity/session state
(where masterXprv is kept) and only fall back to
plaintextMasterFromStored(parsed) if needed. Keep the
deriveFabricAccountIdentityKeys path and the surrounding switch-account handling
intact, but ensure it can proceed when fabric.identity.local is empty.

In `@components/IdentityManager.js`:
- Around line 1493-1503: The new identity save path in IdentityManager should
not use encryptLocalIdentityAtRest for fresh key material because it relies on
weak unauthenticated encryption. Update the identity persistence flow around the
plaintextPayload/encryptLocalIdentityAtRest/writeStorageJSON block to use an
authenticated scheme like the backup helper’s PBKDF2 + AES-GCM, or strengthen
encryptLocalIdentityAtRest before it is called here. Make sure the fix applies
to the account identity creation path that stores fabric.identity.local so newly
saved identities are protected with the stronger format.

In `@components/SettingsBitcoinWallet.js`:
- Around line 35-37: The “Master xpub” display in SettingsBitcoinWallet is still
using identity.xpub, which can mismatch Fabric account-mode derivation; update
the xpub source in the SettingsBitcoinWallet component to prefer
identity.masterXpub for account-mode identities, with a safe fallback only when
that field is unavailable, so the shown value matches the derivation model
described in the copy.

In `@functions/fabricAccountDerivedIdentity.js`:
- Around line 44-48: The helper fabricBech32IdFromCompressedPubHex currently
trusts Buffer.from(..., "hex"), which can accept malformed input; add strict
validation before hashing. In fabricBech32IdFromCompressedPubHex, verify
compressedPubHex is a trimmed hex string for a compressed secp256k1 pubkey:
exactly 66 hex characters, starts with 02 or 03, and represents 33 bytes, then
only proceed to Hash256.digest and Bech32 conversion. If the input fails
validation, throw a clear error before any Buffer.from processing.

In `@functions/fabricBrowserIdentityDev.js`:
- Around line 139-140: Only clear suppression for the seed currently being
restored, not globally. Update the restore/import flow in
fabricBrowserIdentityDev.js so the wipe marker is tied to the specific mnemonic
or seed being re-enabled, and adjust the call around clearDevSeedSuppression()
accordingly. Use the existing restore/import logic and the seed-handling code
paths in this module to ensure importing a different seed does not re-enable
bootstrap for a previously wiped one.

In `@functions/fabricDesktopLoginVerify.js`:
- Around line 126-140: The desktop login verifier currently falls through to
success when only one of the expected bindings is present, so tighten the guard
in fabricDesktopLoginVerify.js around parseDesktopLoginMessage and
originsMatchForDesktopSession to fail closed unless both sessionId and origin
are supplied. Update the existing expected/session binding check so the helper
returns a rejection error whenever either wantSid or wantOrigin is missing, and
only accepts a signed message after both parsed.sessionId and parsed.origin are
validated against the expected values.

In `@functions/fabricHttpSemantic.js`:
- Around line 78-95: Add a guard in syncSemanticAssetsFromRoot to prevent
copying when fabricHttpRoot and hubRoot resolve to the same assets directory,
since the current fs.rmSync/fs.cpSync flow can delete the source before copying.
Use the existing syncSemanticAssetsFromRoot function and the
sourceAssets/hubAssets paths to detect this self-sync case early and skip or
throw before iterating SEMANTIC_FILES and SEMANTIC_DIRS.

In `@functions/fabricHubLocalIdentity.js`:
- Around line 200-203: The key derivation and encryption in
fabricHubLocalIdentity are too weak and unauthenticated; replace the current
`crypto.createHash('sha256').update(salt + pwd)` flow with a stronger KDF in the
identity encryption/decryption path, and switch the private-key protection logic
to an authenticated mode such as AES-GCM. Update the relevant encrypt/decrypt
helpers and any code that reads or writes `xprvEnc` to store the extra auth data
needed by `aes-256-gcm`, and add a compatibility/migration path so existing
encrypted records can still be decrypted and re-encrypted in the new format.

In `@functions/fabricIdentityBackupCrypto.js`:
- Around line 108-124: Validate and bound the KDF inputs in
fabricIdentityBackupCrypto before calling subtle.deriveBits: in the backup
import path, reject malformed or missing encryptedFile.kdf.salt and
encryptedFile.iv after b64ToU8 conversion, and clamp/validate
encryptedFile.kdf.iterations to a safe numeric range instead of blindly using
the file-provided value. Apply the checks near the existing salt, iv, rawCipher,
and iterations handling in the backup decryption flow so deriveBits only runs
with trusted parameters.

In `@functions/fabricIdentityCapabilities.js`:
- Around line 22-34: `hasMaster` in `fabricIdentityCapabilities` is using
`plaintextMasterFromStored(parsed)`, which is always empty and keeps
`canSwitchFabricAccount` and `canExportFabricAccountSubtreeBackup` disabled.
Update `fabricIdentityCapabilities` to derive `hasMaster` from the in-memory
unlocked state on `parsed` (for example `parsed.masterXprv` or the equivalent
runtime flag) instead of the stored plaintext helper. Keep the
`canSwitchFabricAccount` and `canExportFabricAccountSubtreeBackup` conditions
unchanged aside from the corrected master check so the capability flags reflect
actual device state.

In `@functions/hubCollaboration.js`:
- Around line 22-26: The fixed ID validators in hubCollaboration.js are using
dynamic new RegExp(...) calls, which triggers the static check. Replace the
CONTACT_ID_RE, INVITATION_ID_RE, and GROUP_ID_RE definitions with equivalent
literal regex expressions while keeping the same matching behavior, and leave
COLLAB_ID_SUFFIX_HEX_LEN only if it is still needed elsewhere.

In `@functions/hubPublicVisitor.js`:
- Around line 40-49: `computePublicHubVisitor` is missing a persisted-identity
check, so a stored locked/watch-only Fabric identity can still be classified as
a public visitor before `localIdentity` hydrates. Update the decision flow in
`computePublicHubVisitor` to consult `hasPersistedFabricIdentity()` alongside
the existing `hasUnlockedHubSigningIdentity`, `localIdentity`, and
`hasExternalSigningDelegation` checks, and return false whenever a persisted
identity is present even if `localIdentity` is not yet populated.

In `@scripts/build.js`:
- Around line 43-52: The Semantic asset sync in syncSemanticAssetsFromFabricHttp
can crash the build when `@fabric/http` is a source-only checkout because
sourceRoot may point to a directory without assets/ and
syncSemanticAssetsFromRoot then throws. Update scripts/build.js to handle this
path gracefully by either wrapping the syncSemanticAssetsFromRoot call in a
try/catch and logging/skipping on failure, or by matching the source-checkout
handling used in buildSemanticAssets/runBuildSemantic so source-only installs
build assets before syncing. Use the existing syncSemanticAssetsFromFabricHttp
and main flow as the fix point.

In `@services/setup.js`:
- Around line 226-230: Update verifyAdminToken in setup.js so it no longer
treats any token signed by _rootKey as admin access. Keep using
Token.verifySigned, but capture its returned payload and only return true when
the payload is non-null and its capability is OP_IDENTITY with subject equal to
admin; otherwise return false. Use verifyAdminToken, Token.verifySigned, and
_rootKey as the key symbols when making the change.

---

Outside diff comments:
In `@actions/documentActions.js`:
- Around line 109-117: The upload timeout is not working because fetchPromise is
awaited before Promise.race, so a slow or hung request cannot be interrupted.
Update the upload flow in documentActions.js so
fetch(assertClientFetchPath('/files'), ...) is passed directly into Promise.race
alongside timeoutPromise, and keep the existing fileCreation handling in the
upload routine.
- Around line 191-204: The edit flow in editDocument is dispatching
editDocumentSuccess even when the PATCH request fails, so add a response.ok (or
equivalent status check) before parsing and dispatching. In the action that
calls fetch for /documents/${encodeURIComponent(fabricID)}, branch on failure to
stop the success path and handle the error state instead, then only call
editDocumentSuccess(document) when the response is successful.

In `@components/IdentityManager.js`:
- Around line 1903-1925: The import flow in IdentityManager is treating
xprv-backed identities like watch-only entries by writing only the xpub and
marking nextIdentity as passwordProtected false. Update the import path in the
local-storage block and the nextIdentity construction so xprv imports require a
local encryption password and are persisted through the encrypted identity flow
instead of fabric.identity.local. Make sure the logic around xprv,
writeStorageJSON, and nextIdentity keeps the private key protected and
reloadable.
- Around line 397-407: The sync payload in IdentityManager’s local identity
persistence is writing unlocked private key material directly to extension
storage. Update the logic around the localIdentity payload build so it does not
include xprv or masterXprv in the chrome.storage.local sync path; keep this flow
watch-only unless the key material is first encrypted using the existing
encrypted-at-rest mechanism for fabric.identity.local. Verify the payload
construction in the local identity save/sync branch only carries public or
non-sensitive fields.

In `@functions/bitcoinClient.js`:
- Around line 1246-1262: The fallback cache lookup in
fetchWalletSummaryWithCache ignores the computed cache policy, so bypassCache
and maxCacheAgeMs are not respected after fetchWalletSummary fails. Update the
cache read path in fetchWalletSummaryWithCache to use the derived maxCacheAgeMs
(or bypassCache) instead of always calling getCachedBalance with Infinity, so
explicit refresh requests cannot return stale balances.
- Around line 1623-1626: The crowdfunding key derivation is still hardcoded to
account 0, so beneficiary/refund keys can drift from the active Fabric wallet
account. Update the crowdfunding derivation in bitcoinClient.js to use the
selected identity account via getBitcoinBip44AccountForIdentity(identity)
instead of BITCOIN_PAYMENTS_BIP44_ACCOUNT_INDEX, and apply the same change in
the related beneficiary/refund key path so deriveFabricBitcoinAccountKeys stays
aligned with the active account.

In `@scripts/desktop.js`:
- Around line 160-213: The waitForHub polling in ping can hang when http.get
connects but never finishes the response, because the existing timeout only runs
in the end and error paths. Add a per-request timeout on the req object in ping
so stalled requests are destroyed and routed through the existing
req.on('error') retry logic, ensuring the overall deadline in waitForHub is
still enforced.

---

Minor comments:
In `@assets/scripts/assets/manifest.json`:
- Around line 6-8: The manifest has a duplicate asset mapping where main.js
points to the same CDN source and integrity as index.min.js, so remove the
redundant main.js entry from the assets manifest. Update the manifest object in
assets/scripts/assets/manifest.json so only the distinct entry remains, and keep
the existing index.min.js mapping unchanged.

In `@components/BitcoinBlockList.js`:
- Around line 39-48: The admin token is resolved into
upstreamAdmin.hubAdminToken but fetchExplorerData still receives the raw
adminTok value, so browser-stored tokens are not forwarded to the explorer call.
Update BitcoinBlockList’s token handling so the same effective token is computed
once and passed consistently to both upstreamAdmin and the fetchExplorerData
options.adminToken, keeping fetchBitcoinStatus aligned with the same resolved
token path.

In `@components/BitcoinWalletBranchBar.js`:
- Around line 12-13: The “Master xpub” label is currently using the selected
account xpub from BitcoinWalletBranchBar, so update the xpub source to use
identity.masterXpub for that label and keep identity.xpub only for the
account-level display. Adjust the related rendering logic in
BitcoinWalletBranchBar so the master/account distinction is explicit, and verify
any other uses in the referenced range still point to the correct symbol.

In `@functions/fabricBrowserState.js`:
- Around line 235-238: The unlock-state cleanup in fabricBrowserState should use
the resolved browser global consistently instead of falling back to window.
Update the sessionStorage removal inside the unlock cleanup logic to reference
the same browser-global variable used elsewhere in the function (for example,
the one returned by getFabricBrowserGlobal and stored in w), so the identity key
is cleared even when a provided browser global is resolved.

In `@functions/hubUiFeatureFlags.js`:
- Around line 189-190: The flag merge in loadHubUiFeatureFlags/normalizeFlags is
preserving stale local browser-only values because it spreads
loadHubUiFeatureFlags() into raw before normalization. Update this flow so the
persisted payload is normalized directly, with omitted keys falling back to
bundled defaults rather than any previously saved local state. Keep the fix
focused in hubUiFeatureFlags.js around loadHubUiFeatureFlags and the
normalizeFlags call.

In `@services/hub.js`:
- Around line 208-212: The exposePaymentTestRoute setting in hub configuration
is using an unsafe opt-out default and should be changed to secure opt-in
behavior. Update the payment route flag logic in the hub setup so it is disabled
unless explicitly enabled, instead of defaulting to true when
FABRIC_HTTP_PAYMENTS_HIDE_TEST_ROUTE is unset. Use the exposePaymentTestRoute
configuration block in services/hub.js as the fix point and keep the
environment-variable check aligned with the documented default.

---

Nitpick comments:
In `@functions/resolveFabricHttpSend402.js`:
- Around line 8-20: The dynamic require in resolveFabricHttpSend402 is using a
package-controlled path from require.resolve('`@fabric/http/package.json`'), so
this is a false positive from Codacy. Add a scoped suppression around the
require(path.join(root, 'functions/sendPaymentRequired402Response.js')) call (or
the minimal surrounding block) so the gate passes without disabling the rule
globally, and keep the fallback export for sendPaymentRequired402Response
unchanged.

In `@package.json`:
- Around line 120-121: The Fabric dependencies are pinned to moving branch refs,
which makes installs non-reproducible; update the `@fabric/core` and `@fabric/http`
entries in package.json to immutable references such as a commit SHA or a
release tag before merging. Keep the dependency names the same, but replace the
feature/rsi branch targets so future npm i runs resolve to a fixed version.

In `@scripts/prepare-hub-dev-port.js`:
- Around line 108-116: The error handling in the options probe is redundant
because both the ECONNREFUSED/EHOSTUNREACH path and the fallback branch do the
same thing. Update the try/catch around optionsJson in prepare-hub-dev-port.js
to either collapse the branches into a single best-effort return or, if keeping
the distinction, log non-connection failures before returning. Keep the change
localized to the optionsJson probe logic so the behavior remains clear and
intentional.
- Around line 63-78: The `pidsListeningOnPort` and `commandLineForPid` helpers
are using `execSync` with interpolated values, which triggers command-injection
warnings. Harden these calls by switching to `execFileSync` (or equivalent
argument-array execution) in both helpers, keeping the existing numeric parsing
in place so `p` and `pid` remain validated inputs. This change should be applied
within the `pidsListeningOnPort` and `commandLineForPid` functions in
`prepare-hub-dev-port.js`.

In `@services/hub.js`:
- Around line 5691-5703: The xpub query token check in the token-validation path
is using plain string equality on bearer, query, and header values, which should
be replaced with a constant-time secret comparison. Update the comparison logic
in the hub token gate to use timingSafeSha256Utf8Match, following the same
pattern used for the setup-secret flow, and keep the existing ok/403 response
behavior intact.

In `@tests/fabricHubLocalIdentity.fabricHdRole.test.js`:
- Around line 108-115: Remove the unused second argument from the
`buildLocalFabricIdentityPayload` call in
`fabricHubLocalIdentity.fabricHdRole.test.js`; `buildLocalFabricIdentityPayload`
in `functions/fabricHubLocalIdentity.js` only accepts the parsed identity
object, so update the test to pass just that single object and drop the ignored
`{ unlockPlaintextMaster: true }` argument.
🪄 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: defaults

Review profile: CHILL

Plan: Pro

Run ID: b14ac3d3-dcdc-4a38-9b3d-6f703884d9dd

📥 Commits

Reviewing files that changed from the base of the PR and between bd4df9b and 2e5ea9b.

⛔ Files ignored due to path filters (92)
  • assets/bundles/browser.min.js is excluded by !**/*.min.js
  • assets/scripts/semantic.min.js is excluded by !**/*.min.js
  • assets/semantic.min.js is excluded by !**/*.min.js
  • assets/styles/themes/default/assets/fonts/RobotoMono-Bold.ttf is excluded by !**/*.ttf
  • assets/styles/themes/default/assets/fonts/RobotoMono-BoldItalic.ttf is excluded by !**/*.ttf
  • assets/styles/themes/default/assets/fonts/RobotoMono-ExtraLight.ttf is excluded by !**/*.ttf
  • assets/styles/themes/default/assets/fonts/RobotoMono-ExtraLightItalic.ttf is excluded by !**/*.ttf
  • assets/styles/themes/default/assets/fonts/RobotoMono-Italic.ttf is excluded by !**/*.ttf
  • assets/styles/themes/default/assets/fonts/RobotoMono-Light.ttf is excluded by !**/*.ttf
  • assets/styles/themes/default/assets/fonts/RobotoMono-LightItalic.ttf is excluded by !**/*.ttf
  • assets/styles/themes/default/assets/fonts/RobotoMono-Medium.ttf is excluded by !**/*.ttf
  • assets/styles/themes/default/assets/fonts/RobotoMono-MediumItalic.ttf is excluded by !**/*.ttf
  • assets/styles/themes/default/assets/fonts/RobotoMono-Regular.ttf is excluded by !**/*.ttf
  • assets/styles/themes/default/assets/fonts/RobotoMono-SemiBold.ttf is excluded by !**/*.ttf
  • assets/styles/themes/default/assets/fonts/RobotoMono-SemiBoldItalic.ttf is excluded by !**/*.ttf
  • assets/styles/themes/default/assets/fonts/RobotoMono-Thin.ttf is excluded by !**/*.ttf
  • assets/styles/themes/default/assets/fonts/RobotoMono-ThinItalic.ttf is excluded by !**/*.ttf
  • assets/styles/themes/default/assets/fonts/brand-icons.eot is excluded by !**/*.eot
  • assets/styles/themes/default/assets/fonts/brand-icons.svg is excluded by !**/*.svg
  • assets/styles/themes/default/assets/fonts/brand-icons.ttf is excluded by !**/*.ttf
  • assets/styles/themes/default/assets/fonts/brand-icons.woff is excluded by !**/*.woff
  • assets/styles/themes/default/assets/fonts/brand-icons.woff2 is excluded by !**/*.woff2
  • assets/styles/themes/default/assets/fonts/icons.eot is excluded by !**/*.eot
  • assets/styles/themes/default/assets/fonts/icons.svg is excluded by !**/*.svg
  • assets/styles/themes/default/assets/fonts/icons.ttf is excluded by !**/*.ttf
  • assets/styles/themes/default/assets/fonts/icons.woff is excluded by !**/*.woff
  • assets/styles/themes/default/assets/fonts/icons.woff2 is excluded by !**/*.woff2
  • assets/styles/themes/default/assets/fonts/outline-icons.eot is excluded by !**/*.eot
  • assets/styles/themes/default/assets/fonts/outline-icons.svg is excluded by !**/*.svg
  • assets/styles/themes/default/assets/fonts/outline-icons.ttf is excluded by !**/*.ttf
  • assets/styles/themes/default/assets/fonts/outline-icons.woff is excluded by !**/*.woff
  • assets/styles/themes/default/assets/fonts/outline-icons.woff2 is excluded by !**/*.woff2
  • assets/styles/themes/default/assets/images/flags.png is excluded by !**/*.png
  • assets/themes/default/assets/fonts/Lato-Bold.woff is excluded by !**/*.woff
  • assets/themes/default/assets/fonts/Lato-Bold.woff2 is excluded by !**/*.woff2
  • assets/themes/default/assets/fonts/Lato-BoldItalic.woff is excluded by !**/*.woff
  • assets/themes/default/assets/fonts/Lato-BoldItalic.woff2 is excluded by !**/*.woff2
  • assets/themes/default/assets/fonts/Lato-Italic.woff is excluded by !**/*.woff
  • assets/themes/default/assets/fonts/Lato-Italic.woff2 is excluded by !**/*.woff2
  • assets/themes/default/assets/fonts/Lato-Regular.woff is excluded by !**/*.woff
  • assets/themes/default/assets/fonts/Lato-Regular.woff2 is excluded by !**/*.woff2
  • assets/themes/default/assets/fonts/LatoLatin-Bold.woff is excluded by !**/*.woff
  • assets/themes/default/assets/fonts/LatoLatin-Bold.woff2 is excluded by !**/*.woff2
  • assets/themes/default/assets/fonts/LatoLatin-BoldItalic.woff is excluded by !**/*.woff
  • assets/themes/default/assets/fonts/LatoLatin-BoldItalic.woff2 is excluded by !**/*.woff2
  • assets/themes/default/assets/fonts/LatoLatin-Italic.woff is excluded by !**/*.woff
  • assets/themes/default/assets/fonts/LatoLatin-Italic.woff2 is excluded by !**/*.woff2
  • assets/themes/default/assets/fonts/LatoLatin-Regular.woff is excluded by !**/*.woff
  • assets/themes/default/assets/fonts/LatoLatin-Regular.woff2 is excluded by !**/*.woff2
  • assets/themes/default/assets/fonts/brand-icons.eot is excluded by !**/*.eot
  • assets/themes/default/assets/fonts/brand-icons.svg is excluded by !**/*.svg
  • assets/themes/default/assets/fonts/brand-icons.ttf is excluded by !**/*.ttf
  • assets/themes/default/assets/fonts/brand-icons.woff is excluded by !**/*.woff
  • assets/themes/default/assets/fonts/brand-icons.woff2 is excluded by !**/*.woff2
  • assets/themes/default/assets/fonts/icons.eot is excluded by !**/*.eot
  • assets/themes/default/assets/fonts/icons.svg is excluded by !**/*.svg
  • assets/themes/default/assets/fonts/icons.ttf is excluded by !**/*.ttf
  • assets/themes/default/assets/fonts/icons.woff is excluded by !**/*.woff
  • assets/themes/default/assets/fonts/icons.woff2 is excluded by !**/*.woff2
  • assets/themes/default/assets/fonts/outline-icons.eot is excluded by !**/*.eot
  • assets/themes/default/assets/fonts/outline-icons.svg is excluded by !**/*.svg
  • assets/themes/default/assets/fonts/outline-icons.ttf is excluded by !**/*.ttf
  • assets/themes/default/assets/fonts/outline-icons.woff is excluded by !**/*.woff
  • assets/themes/default/assets/fonts/outline-icons.woff2 is excluded by !**/*.woff2
  • assets/themes/default/assets/images/flags.png is excluded by !**/*.png
  • assets/themes/fabric/assets/fonts/Arvo.woff2 is excluded by !**/*.woff2
  • assets/themes/fabric/assets/fonts/arvo-italic-400.ttf is excluded by !**/*.ttf
  • assets/themes/fabric/assets/fonts/arvo-italic-400.woff2 is excluded by !**/*.woff2
  • assets/themes/fabric/assets/fonts/arvo-italic-700.ttf is excluded by !**/*.ttf
  • assets/themes/fabric/assets/fonts/arvo-italic-700.woff2 is excluded by !**/*.woff2
  • assets/themes/fabric/assets/fonts/arvo-normal-400.ttf is excluded by !**/*.ttf
  • assets/themes/fabric/assets/fonts/arvo-normal-400.woff2 is excluded by !**/*.woff2
  • assets/themes/fabric/assets/fonts/arvo-normal-700.ttf is excluded by !**/*.ttf
  • assets/themes/fabric/assets/fonts/arvo-normal-700.woff2 is excluded by !**/*.woff2
  • assets/themes/fabric/assets/fonts/brand-icons.eot is excluded by !**/*.eot
  • assets/themes/fabric/assets/fonts/brand-icons.svg is excluded by !**/*.svg
  • assets/themes/fabric/assets/fonts/brand-icons.ttf is excluded by !**/*.ttf
  • assets/themes/fabric/assets/fonts/brand-icons.woff is excluded by !**/*.woff
  • assets/themes/fabric/assets/fonts/brand-icons.woff2 is excluded by !**/*.woff2
  • assets/themes/fabric/assets/fonts/icons.eot is excluded by !**/*.eot
  • assets/themes/fabric/assets/fonts/icons.svg is excluded by !**/*.svg
  • assets/themes/fabric/assets/fonts/icons.ttf is excluded by !**/*.ttf
  • assets/themes/fabric/assets/fonts/icons.woff is excluded by !**/*.woff
  • assets/themes/fabric/assets/fonts/icons.woff2 is excluded by !**/*.woff2
  • assets/themes/fabric/assets/fonts/outline-icons.eot is excluded by !**/*.eot
  • assets/themes/fabric/assets/fonts/outline-icons.svg is excluded by !**/*.svg
  • assets/themes/fabric/assets/fonts/outline-icons.ttf is excluded by !**/*.ttf
  • assets/themes/fabric/assets/fonts/outline-icons.woff is excluded by !**/*.woff
  • assets/themes/fabric/assets/fonts/outline-icons.woff2 is excluded by !**/*.woff2
  • assets/themes/fabric/assets/images/flags.png is excluded by !**/*.png
  • package-lock.json is excluded by !**/package-lock.json
  • reports/install.log is excluded by !**/*.log
📒 Files selected for processing (108)
  • .codacy.yaml
  • DEVELOPERS.md
  • actions/apiActions.js
  • actions/bridgeActions.js
  • actions/chatActions.js
  • actions/documentActions.js
  • assets/index.html
  • assets/scripts/assets/manifest.json
  • assets/scripts/semantic.js
  • assets/semantic.css
  • assets/semantic.js
  • assets/semantic.min.css
  • assets/semantic.rtl.css
  • assets/semantic.rtl.min.css
  • assets/styles/semantic.min.css
  • assets/themes/default/assets/fonts/LICENSE_Lato.txt
  • assets/themes/default/assets/fonts/LICENSE_icons.txt
  • components/AdminHome.js
  • components/BeaconAdminPanel.js
  • components/BitcoinBlockList.js
  • components/BitcoinHome.js
  • components/BitcoinPaymentsHome.js
  • components/BitcoinResourcesHome.js
  • components/BitcoinTransactionsHome.js
  • components/BitcoinWalletBranchBar.js
  • components/Bridge.js
  • components/Dashboard.js
  • components/FaucetHome.js
  • components/HubInterface.js
  • components/IdentityManager.js
  • components/Onboarding.js
  • components/PeerList.js
  • components/PeerView.js
  • components/SettingsBitcoinWallet.js
  • components/TopPanel.js
  • components/fabricIdentity/FabricHubAdminTokenNotice.js
  • components/fabricIdentity/FabricIdentityAccountControls.js
  • components/fabricIdentity/FabricPostSetupIdentityWizard.js
  • functions/bitcoinClient.js
  • functions/fabricAccountDerivedIdentity.js
  • functions/fabricBrowserIdentityDev.js
  • functions/fabricBrowserState.js
  • functions/fabricBrowserStore.js
  • functions/fabricDesktopAuth.js
  • functions/fabricDesktopLoginVerify.js
  • functions/fabricDocumentOfferEnvelope.js
  • functions/fabricHttpSemantic.js
  • functions/fabricHubLocalIdentity.js
  • functions/fabricIdentityBackupCrypto.js
  • functions/fabricIdentityCapabilities.js
  • functions/fabricIdentityLockPrefs.js
  • functions/fabricMessageRegistry.js
  • functions/fabricPostSetupBrowserIdentity.js
  • functions/fabricProtocolUrl.js
  • functions/httpSpaShell.js
  • functions/hubAdminTokenBrowser.js
  • functions/hubCollaboration.js
  • functions/hubPublicVisitor.js
  • functions/hubUiFeatureFlags.js
  • functions/patchLinkedFabricNodePath.js
  • functions/resolveFabricHttpSend402.js
  • functions/sampleHubOptions.js
  • libraries/semantic/src/themes/fabric/globals/site.variables
  • package.json
  • reducers/documentReducer.js
  • routes/contracts/create_contract.js
  • routes/documents/create_document.js
  • routes/documents/view_document.js
  • routes/index.js
  • routes/messages/create_message.js
  • routes/messages/list_messages.js
  • routes/messages/view_message.js
  • routes/peers/create_peer.js
  • routes/peers/list_peers.js
  • routes/peers/view_peer.js
  • scripts/build-semantic-assets.js
  • scripts/build.js
  • scripts/desktop-preload.js
  • scripts/desktop.js
  • scripts/desktopHubProbe.js
  • scripts/download-fonts.js
  • scripts/hub.js
  • scripts/prepare-hub-dev-port.js
  • services/hub.js
  • services/setup.js
  • settings/local.js
  • tests/bitcoinClient.bip44TabAccount.test.js
  • tests/bridge.webrtc.signal.test.js
  • tests/browser.hubAlerts.test.js
  • tests/browser.interface.test.js
  • tests/browser.onboardingPeers.e2e.test.js
  • tests/desktopHubProbe.test.js
  • tests/electron.desktopHubProbe.test.js
  • tests/fabricBrowserIdentityClear.test.js
  • tests/fabricBrowserIdentityDev.test.js
  • tests/fabricDesktopLoginVerify.test.js
  • tests/fabricDocumentOfferEnvelope.test.js
  • tests/fabricHubLocalIdentity.fabricHdRole.test.js
  • tests/fabricIdentityBackupCrypto.test.js
  • tests/fabricIdentityCapabilities.test.js
  • tests/fabricIdentityLockPrefs.test.js
  • tests/formativeVision.test.js
  • tests/hub.collaboration.test.js
  • tests/hub.http.js
  • tests/hubPublicVisitor.test.js
  • tests/hubUiFeatureFlags.test.js
  • types/spa.js
  • webpack.config.js
💤 Files with no reviewable changes (11)
  • routes/documents/create_document.js
  • assets/themes/default/assets/fonts/LICENSE_Lato.txt
  • routes/peers/view_peer.js
  • routes/contracts/create_contract.js
  • assets/themes/default/assets/fonts/LICENSE_icons.txt
  • routes/peers/create_peer.js
  • scripts/download-fonts.js
  • routes/messages/list_messages.js
  • routes/peers/list_peers.js
  • routes/messages/create_message.js
  • routes/messages/view_message.js

Comment thread actions/documentActions.js Outdated
Comment thread components/BitcoinHome.js
Comment thread components/Bridge.js Outdated
Comment thread components/HubInterface.js
Comment thread components/HubInterface.js Outdated
Comment thread functions/fabricIdentityCapabilities.js Outdated
Comment thread functions/hubCollaboration.js Outdated
Comment thread functions/hubPublicVisitor.js
Comment thread scripts/build.js
Comment on lines +43 to +52
function syncSemanticAssetsFromFabricHttp () {
const root = path.join(__dirname, '..');
const roots = resolveFabricHttpRoots(root);
const sourceRoot = roots.withAssets || roots.withSources;
if (!sourceRoot) {
console.warn('[BUILD:SITE] @fabric/http assets not found, skipping Semantic asset sync.');
return;
}
syncSemanticAssetsFromRoot(sourceRoot, root);
}

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 | 🟠 Major

🧩 Analysis chain

🏁 Script executed:

#!/bin/bash
# Confirm withSources is keyed on gulpfile (not assets) and that build.js never rebuilds.
rg -nP 'hasFomanticSources|hasSemanticAssets|withSources|withAssets' functions/fabricHttpSemantic.js -C1
rg -nP 'runBuildSemantic' scripts/build.js

Repository: FabricLabs/hub.fabric.pub

Length of output: 683


🏁 Script executed:

#!/bin/bash
# Verify syncSemanticAssetsFromRoot behavior on missing assets and main() error handling
cat -n scripts/build.js | sed -n '30,70p'
echo "--- Sync Semantic Assets From Root implementation ---"
rg -A 10 "function syncSemanticAssetsFromRoot" scripts/functionalities/semanticSync.js 2>/dev/null || rg -A 10 "function syncSemanticAssetsFromRoot" scripts/build.js
echo "--- Check build-semantic-assets.js logic for comparison ---"
rg -A 15 "resolveFabricHttpRoots|syncSemanticAssetsFromRoot" scripts/build-semantic-assets.js | head -30

Repository: FabricLabs/hub.fabric.pub

Length of output: 2793


🏁 Script executed:

#!/bin/bash
# Inspect syncSemanticAssetsFromRoot to confirm throwing behavior and import source
rg -B 5 -A 20 "function syncSemanticAssetsFromRoot" functions/fabricHttpSemantic.js
echo "--- Check imports in build.js and build-semantic-assets.js ---"
head -20 scripts/build.js
head -20 scripts/build-semantic-assets.js

Repository: FabricLabs/hub.fabric.pub

Length of output: 2171


Critical failure in build process if @fabric/http is installed in source-only mode.

When @fabric/http is present as a source checkout (detected via gulpfile.js) without pre-built assets, syncSemanticAssetsFromFabricHttp sets sourceRoot to that source directory. Since sourceRoot lacks the assets/ directory, syncSemanticAssetsFromRoot throws an error. Unlike scripts/build-semantic-assets.js, scripts/build.js does not invoke runBuildSemantic for source checkouts, nor does it wrap the sync in a try/catch block inside main(), causing the entire build to crash.

Required fix:

  1. Add a try/catch block around the syncSemanticAssetsFromRoot call to fail gracefully, OR
  2. Replicate the logic in scripts/build-semantic-assets.js to detect source-only checkouts and invoke runBuildSemantic before syncing.
🛠️ Current failing code path
 function syncSemanticAssetsFromFabricHttp () {
   const root = path.join(__dirname, '..');
   const roots = resolveFabricHttpRoots(root);
   const sourceRoot = roots.withAssets || roots.withSources;
   if (!sourceRoot) {
     console.warn('[BUILD:SITE] `@fabric/http` assets not found, skipping Semantic asset sync.');
     return;
   }
-  syncSemanticAssetsFromRoot(sourceRoot, root);
+  try {
+    syncSemanticAssetsFromRoot(sourceRoot, root);
+  } catch (err) {
+    console.warn(`[BUILD:SITE] Skipping Semantic asset sync: ${err && err.message ? err.message : err}`);
+    return;
+  }
 }
📝 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 syncSemanticAssetsFromFabricHttp () {
const root = path.join(__dirname, '..');
const roots = resolveFabricHttpRoots(root);
const sourceRoot = roots.withAssets || roots.withSources;
if (!sourceRoot) {
console.warn('[BUILD:SITE] @fabric/http assets not found, skipping Semantic asset sync.');
return;
}
syncSemanticAssetsFromRoot(sourceRoot, root);
}
function syncSemanticAssetsFromFabricHttp () {
const root = path.join(__dirname, '..');
const roots = resolveFabricHttpRoots(root);
const sourceRoot = roots.withAssets || roots.withSources;
if (!sourceRoot) {
console.warn('[BUILD:SITE] `@fabric/http` assets not found, skipping Semantic asset sync.');
return;
}
try {
syncSemanticAssetsFromRoot(sourceRoot, root);
} catch (err) {
console.warn(`[BUILD:SITE] Skipping Semantic asset sync: ${err && err.message ? err.message : err}`);
return;
}
}
🤖 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 `@scripts/build.js` around lines 43 - 52, The Semantic asset sync in
syncSemanticAssetsFromFabricHttp can crash the build when `@fabric/http` is a
source-only checkout because sourceRoot may point to a directory without assets/
and syncSemanticAssetsFromRoot then throws. Update scripts/build.js to handle
this path gracefully by either wrapping the syncSemanticAssetsFromRoot call in a
try/catch and logging/skipping on failure, or by matching the source-checkout
handling used in buildSemanticAssets/runBuildSemantic so source-only installs
build assets before syncing. Use the existing syncSemanticAssetsFromFabricHttp
and main flow as the fix point.

Comment thread services/setup.js
Comment on lines 226 to 230
verifyAdminToken (bearerToken) {
if (!bearerToken || typeof bearerToken !== 'string') return false;
if (this._rootKey && Token.verifySigned(bearerToken, this._rootKey) !== null) return true;
if (!this.fs) return false;
const raw = this.fs.readFile(ADMIN_TOKEN_FILE);
if (!raw) return false;
try {
const stored = typeof raw === 'string' ? JSON.parse(raw) : JSON.parse(raw.toString('utf8'));
const storedToken = stored && stored.token;
if (!storedToken) return false;
const a = Buffer.from(bearerToken, 'utf8');
const b = Buffer.from(storedToken, 'utf8');
if (a.length !== b.length) return false;
return crypto.timingSafeEqual(a, b);
} catch {
return false;
}
if (!this._rootKey) return false;
return Token.verifySigned(bearerToken, this._rootKey) !== null;
}

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 | 🟠 Major

🧩 Analysis chain

🏁 Script executed:

#!/bin/bash
# Confirm whether Token.verifySigned enforces expiry and exposes capability/subject.
fd token.js -p node_modules/@fabric/core/types --exec sed -n '1,200p' {}

Repository: FabricLabs/hub.fabric.pub

Length of output: 282


Enforce capability and subject validation in verifyAdminToken.

The current implementation relies solely on Token.verifySigned returning a non-null value, which confirms signature validity but does not restrict the token's scope. Since _rootKey is used to mint various tokens (e.g., this._fabric.token), any token signed by this key—regardless of its capability or subject—would currently grant access.

You must decode the token payload returned by verifySigned and assert that capability === 'OP_IDENTITY' and subject === 'admin' before granting access.

  verifyAdminToken (bearerToken) {
    if (!bearerToken || typeof bearerToken !== 'string') return false;
    if (!this._rootKey) return false;
    const payload = Token.verifySigned(bearerToken, this._rootKey);
    return payload !== null &&
           payload.capability === 'OP_IDENTITY' &&
           payload.subject === 'admin';
  }
🤖 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 `@services/setup.js` around lines 226 - 230, Update verifyAdminToken in
setup.js so it no longer treats any token signed by _rootKey as admin access.
Keep using Token.verifySigned, but capture its returned payload and only return
true when the payload is non-null and its capability is OP_IDENTITY with subject
equal to admin; otherwise return false. Use verifyAdminToken,
Token.verifySigned, and _rootKey as the key symbols when making the change.

@martindale
martindale marked this pull request as ready for review June 25, 2026 14:14

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

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

Terrible URI

@vercel

vercel Bot commented Jul 19, 2026

Copy link
Copy Markdown

The latest updates on your projects. Learn more about Vercel for GitHub.

Project Deployment Actions Updated (UTC)
hub-fabric-pub Ready Ready Preview Aug 14, 2026 11:40am

@martindale
martindale merged commit 39218ee into master Aug 14, 2026
11 of 13 checks passed
@coderabbitai coderabbitai Bot mentioned this pull request Aug 16, 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.

1 participant