Skip to content

WIP: ARCs, Relays, and Hubs - #183

Open
martindale wants to merge 7 commits into
masterfrom
feature/rsi
Open

WIP: ARCs, Relays, and Hubs#183
martindale wants to merge 7 commits into
masterfrom
feature/rsi

Conversation

@martindale

@martindale martindale commented Aug 6, 2026

Copy link
Copy Markdown
Member

Implement the complete user story for ARCs, Relays, and Hubs.

Summary by CodeRabbit

  • New Features

    • Added contract-scoped reader and signer capabilities with validation and expiration support.
    • Added deterministic Taproot contract addresses, tiered spending policies, timelocks, failover, and withdrawal preparation.
    • Added contract-state attestations and spend-tier condition evaluation.
    • Expanded supported contract message types.
    • Added RFC6902 registry patch support for sidechain updates.
    • Contract messages now expose serialized data and identifiers.
    • Signed tokens can include contextual data.
  • Documentation

    • Updated npm 12+ installation guidance.
  • Chores

    • Improved Git-based dependency installation and reporting.

Note

High Risk
Changes contract publish authorization, P2P document handling, and new Bitcoin Taproot spend paths—security-sensitive areas that affect funds and mesh trust.

Overview
Adds contract Taproot tooling (contractTaproot, tier when predicates) plus Contract / Federation toAddress / toTaprootContract so spend policies compile to deterministic P2TR ladders (failover, decay/migrate, PSBT prep). Introduces contract-scoped capability tokens (OP_CONTRACT_READ / OP_CONTRACT_SIGN), k-of-n ContractStateTip signing, and new shared CONTRACT_MESSAGE body types (group journal catch-up, capability grants, withdrawal request/witness).

Peer / wire: contract:message events now carry wireMessage / messageHex for journal attach; contract publish/patch allow-lists are tightened (wire signer must match body authority arrays; publisher no longer auto-granted). Document private relay gains _sendPrivateRelayedDocumentRequest; peeled/relayed document requests stay observe-only via delivery options. SIDECHAIN_STATE_PATCH gains optional patchesCanonical for multi-op RFC6902 fidelity; message decodeBody defaults missing optional fields.

Chores: root .npmrc allow-git=all and install reporting for npm 12+ GitHub consumers; developer docs note npm 12+ requirement.

Reviewed by Cursor Bugbot for commit a97e752. Configure here.

@cursor

cursor Bot commented Aug 6, 2026

Copy link
Copy Markdown

Bugbot is not enabled for your account, so this pull request was not reviewed.

Enable Bugbot in the Cursor dashboard to get automatic reviews on future PRs.

@codacy-production

codacy-production Bot commented Aug 6, 2026

Copy link
Copy Markdown

Not up to standards ⛔

🔴 Issues 1 critical

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

Results:
1 new issue

Category Results
Security 1 critical

View in Codacy

🟢 Metrics 385 complexity · 8 duplication

Metric Results
Complexity 385
Duplication 8

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 commented Aug 6, 2026

Copy link
Copy Markdown

Review Change Stack

📝 Walkthrough

Walkthrough

The repository adds contract capability tokens, state attestations, conditional spend tiers, Taproot policy and withdrawal helpers, contract address integration, message and sidechain updates, audit fields, tests, and npm Git dependency configuration.

Changes

Contract runtime and integration

Layer / File(s) Summary
Contract authorization and attestations
functions/applicationNamespaces.js, functions/contractCapability.js, functions/contractStateSigning.js, tests/*
Adds contract message types, capability issuance and verification, signed contract-state tips, and unit tests.
Conditional spend tiers
functions/contractTierWhen.js, tests/contractTierWhen.unit.js
Adds state-path resolution and fail-closed evaluation for supported tier predicates.
Taproot policies and withdrawals
functions/contractTaproot.js, tests/contractTaproot.unit.js
Adds policy normalization, tier decay, Taproot leaves and trees, address generation, control blocks, and withdrawal PSBT preparation.
Contract and federation integration
types/contract.js, types/federation.js, API.md
Adds Taproot policy-input and address methods and routes federation address construction through shared helpers.
Message and sidechain compatibility
types/message.js, functions/documentRegistrySidechain.js, types/token.js, types/peer.js, MESSAGES.md, tests/*
Adds optional-field defaults, RFC6902 sidechain patches, token context serialization, peer audit fields, and related tests and documentation.
Peer API documentation
API.md
Documents relay delivery options, private relaying, contract signer authorization, registration, and allow-list behavior.
npm Git dependency configuration
.npmrc, package.json, DEVELOPERS.md, CHANGELOG.md
Configures npm Git access and documents the npm 12+ installation requirement.
Maintenance records
reports/TODO.txt
Removes obsolete TODO entries.

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

Sequence Diagram(s)

sequenceDiagram
  participant Contract
  participant contractTaproot
  participant BitcoinPSBT
  Contract->>contractTaproot: Build Taproot policy
  contractTaproot->>contractTaproot: Select active tier and leaf
  contractTaproot->>BitcoinPSBT: Prepare withdrawal PSBT
Loading

Possibly related PRs

🚥 Pre-merge checks | ✅ 4 | ❌ 1

❌ Failed checks (1 warning)

Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 55.56% which is insufficient. The required threshold is 80.00%. Write docstrings for the functions missing them to satisfy the coverage threshold.
✅ Passed checks (4 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.
Title check ✅ Passed The title clearly identifies the main feature area covered by the pull request: ARCs, Relays, and Hubs.
✨ Finishing Touches 💡 1
📝 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.

Comment thread .npmrc
# core/http/hub from GitHub need `allow-git=all` so nested git dep preparation
# (commit-SHA fetches) is not refused. Keep this file so the monorepo default
# matches Hub / http / app packages.
allow-git=all

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

Medium — Supply-chain: re-enables npm 12 git-dependency RCE path

allow-git=all opts this repo (and GitHub installs of it) out of npm 12’s default allow-git=none, which was added specifically to block install-time code execution via a git dependency’s .npmrc overriding the git binary (works even with --ignore-scripts).

Attack path: A compromised or malicious transitive dependency introduces a git+/github: URL → npm fetches it under all → that git checkout’s .npmrc can redirect git → arbitrary code at install time. @fabric/core currently declares no git deps, so all is broader than needed; root (or keeping the default and scoping the opt-in to Hub/http only) would preserve the boundary.

Same relaxation is also forced in package.json report:install (npm i --allow-git=all after wiping the lockfile), which further widens install-time trust during that script.

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

🤖 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 `@DEVELOPERS.md`:
- Line 26: Update the installation instructions in DEVELOPERS.md to require npm
12 or newer before running the documented install commands. Add an explicit npm
version check and upgrade step after selecting the package.json-compatible Node
version, or change the pinned Node release to one that bundles npm 12, while
preserving the existing allow-git=all guidance.
🪄 Autofix

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: Path: .coderabbit.yaml

Review profile: CHILL

Plan: Pro Plus

Run ID: 445cacea-94ae-48f2-bd82-e578a7ed9c85

📥 Commits

Reviewing files that changed from the base of the PR and between 214909a and 67d6edf.

⛔ Files ignored due to path filters (1)
  • package-lock.json is excluded by !**/package-lock.json
📒 Files selected for processing (4)
  • .npmrc
  • CHANGELOG.md
  • DEVELOPERS.md
  • package.json

Comment thread DEVELOPERS.md Outdated
Comment thread .npmrc
# core/http/hub from GitHub need `allow-git=all` so nested git dep preparation
# (commit-SHA fetches) is not refused. Keep this file so the monorepo default
# matches Hub / http / app packages.
allow-git=all

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

Medium — Supply-chain: re-enables npm 12 git-dependency RCE path

allow-git=all still opts this repo out of npm 12’s default allow-git=none, which blocks install-time code execution via a git dependency’s .npmrc overriding the git binary (even under --ignore-scripts). Confirmed still present at HEAD 91f034254; CHANGELOG notes root was rejected for nested SHA fetches.

Attack path: Compromised or malicious transitive git+/github: dep → fetched under all → that checkout’s .npmrc redirects git → arbitrary code at install time. This package still declares no git deps, so all is broader than needed.

Same relaxation remains in package.json report:install (npm i --allow-git=all after wiping the lockfile), which further widens install-time trust on a lockfile-free resolve.

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

🧹 Nitpick comments (5)
functions/contractTierWhen.js (1)

41-54: 🎯 Functional Correctness | 🔵 Trivial | ⚡ Quick win

allOf: [] activates the tier.

An empty allOf array passes the loop and returns true. The module documents fail-closed behavior for malformed input. Reject an empty predicate list.

♻️ Proposed change
   const allOf = Array.isArray(when.allOf) ? when.allOf : null;
-  if (!allOf) return false;
+  if (!allOf || !allOf.length) return false;
🤖 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/contractTierWhen.js` around lines 41 - 54, Update evaluateTierWhen
so an allOf array must contain at least one predicate; return false for an empty
list before iterating, while preserving the existing validation and evaluation
behavior for non-empty arrays.
types/contract.js (1)

342-345: 🎯 Functional Correctness | 🔵 Trivial | ⚡ Quick win

Prefer the explicit override over the stored setting.

ladder reads this.settings.spendLadder first. A caller that passes overrides.spendLadder cannot replace a configured ladder, because the stored value wins. The rest of the method treats overrides as the higher-precedence source. Invert the order for consistency.

♻️ Proposed change
-    const ladder = this.settings.spendLadder || overrides.spendLadder || null;
+    const ladder = overrides.spendLadder || this.settings.spendLadder || 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 `@types/contract.js` around lines 342 - 345, Update the ladder selection in the
surrounding contract-building method to prioritize overrides.spendLadder before
this.settings.spendLadder, while retaining null as the fallback and preserving
the existing tap.buildContractTaproot call.
functions/contractTaproot.js (3)

683-741: 📐 Maintainability & Code Quality | 🔵 Trivial | ⚡ Quick win

Document or remove the opts.useLadder condition.

The JSDoc states that { failover: true } or publisher plus csvBlocks selects the ladder. The condition at Line 696 also requires opts.useLadder, which the JSDoc does not mention. A caller that passes publisher and csvBlocks alone silently receives the legacy single-leaf vault, which uses a different address. Align the code with the documented contract.

🤖 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/contractTaproot.js` around lines 683 - 741, The ladder-selection
condition in buildFederationVaultFromPolicy should honor the documented
publisher-plus-csvBlocks inputs without requiring the undocumented
opts.useLadder flag. Remove that extra condition so failover or a valid
publisher with positive csvBlocks and ladder settings selects the ladder, or
update the JSDoc and contract if the flag is intentionally required.

616-629: 🚀 Performance & Scalability | 🔵 Trivial | 💤 Low value

Compile the leaves once.

compileLeaves(built.policy) runs at Line 616 and again at Line 625, and buildContractTaproot already compiled the same leaves. Each call re-parses every pubkey and recompiles every script. Reuse one array.

♻️ Proposed change
-  const leaf = compileLeaves(built.policy).find((l) => l.kind === 'spend' && l.id === tier.id);
+  const leaves = compileLeaves(built.policy);
+  const leaf = leaves.find((l) => l.kind === 'spend' && l.id === tier.id);
   if (!leaf) throw new Error('spend leaf missing');
@@
-    leaves: compileLeaves(built.policy),
+    leaves,
🤖 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/contractTaproot.js` around lines 616 - 629, Reuse the already
compiled leaves array in the surrounding build flow instead of calling
compileLeaves(built.policy) twice. Update the leaf lookup and prepareLeafPsbt
arguments in the code around prepareLeafPsbt, using the existing compiled-leaves
symbol from buildContractTaproot when available; otherwise compile once into a
local array and pass it to both consumers.

29-34: 🎯 Functional Correctness | 🔵 Trivial | ⚡ Quick win

Make the test fabric alias explicit.

signet => networks.testnet is acceptable because bech32 addresses match. test => networks.regtest is ambiguous, though, since test is commonly used for Bitcoin testnet (tb1…) while regtest produces bcrt1…. Rename this alias (for example to regtest) or add an explicit Fabric config documentation note.

🤖 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/contractTaproot.js` around lines 29 - 34, Update
networkForFabricName so the ambiguous "test" alias is no longer mapped to
networks.regtest; remove it or replace it with an explicit documented regtest
alias, while preserving the existing regtest, testnet, and signet mappings.
🤖 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 `@functions/contractCapability.js`:
- Around line 64-79: Update the unverified branch in the contract capability
validation function to require an explicit caller opt-in before decoding
parse-only tokens, and mark the returned payload as unverified so it cannot be
confused with the issuerKey-verified result. Preserve the existing signature
verification path and reject omitted opt-in requests rather than treating the
two-part format as authentication.

In `@functions/contractStateSigning.js`:
- Around line 79-97: Update verifyContractStateTip to validate threshold before
calling verifyFederationWitnessOnMessage, rejecting omitted, non-numeric, or
otherwise invalid values rather than allowing downstream normalization to
default to 1. Preserve the existing witness construction and verification flow
for valid thresholds.

In `@functions/contractTaproot.js`:
- Around line 79-94: Update normalizeLock’s CSV branch to reject block values
above 65535, returning null for any value outside the valid low-16-bit nSequence
range while preserving existing handling for positive in-range values.
- Around line 212-239: Update the tier validation loop around normalizeLock,
lockValue, and prevAfter so ordering comparisons are performed only when the
compared locks have the same lock type; otherwise reject the mixed ladder with a
clear error. Apply this to both the previous-tier after check and the
until-versus-after check, while preserving existing same-type ordering rules.
- Around line 59-73: Do not clamp thresholds to the number of unique keys. In
buildKOfNTapscript, validate the normalized threshold and throw when it exceeds
keys.length instead of lowering k; preserve the existing minimum-threshold
handling. Apply the same validation in normalizeContractSpendPolicy for thr so
duplicate-key policies cannot compile with a weaker quorum.
- Around line 525-591: Update prepareLeafPsbt to accept the after lock
descriptor, and when it identifies a cltv leaf, set the PSBT locktime to the
descriptor’s value and ensure the input sequence is non-final. Preserve the
existing sequence behavior for CSV and other leaves, while allowing the
resulting CLTV transaction to satisfy its lock requirement.

In `@functions/contractTierWhen.js`:
- Around line 30-33: Update statePathEq to require that both pred.path and
pred.value are present before comparing values; return false for malformed
predicates, including missing value or path, while preserving the existing
strict comparison for valid predicates.
- Around line 13-22: Update getPath so each path segment is accepted only when
it is an own property of the current object, rejecting inherited and
prototype-chain keys such as __proto__, constructor, and prototype; return
undefined immediately for invalid segments while preserving normal traversal of
own contract-state properties.

In `@types/federation.js`:
- Around line 295-316: Unify taproot policy derivation between Federation and
Contract: in types/federation.js lines 295-316, replace the duplicated
validators/threshold/publisher/network/csvBlocks derivation with the inherited
toTaprootContract call, supplying federation-specific overrides; in
types/contract.js lines 340-364, extract the shared derivation into a protected
helper used by both classes and select one consistent default network instead of
the current regtest/bitcoin discrepancy.
- Around line 304-306: Update the csvBlocks fallback in the surrounding
federation configuration logic so settings.timeout is never converted directly
into a block count. Prefer the existing tap.DEFAULT_CSV_BLOCKS when csvBlocks is
unset, unless a clearly defined duration-to-block conversion is already
available; preserve explicit settings.csvBlocks handling.
- Around line 307-309: Update the custom spend-ladder branch in the relevant
federation address method so tap.toAddress receives this.settings.network,
matching the synthesized branch’s network behavior. Ensure configured ladders
are converted using the federation’s network setting rather than relying on the
ladder’s embedded network.

In `@types/token.js`:
- Around line 100-102: Update the context selection in the Token signing flow to
distinguish an explicitly provided options.ctx of null from an absent option, so
null suppresses this.settings.ctx and omits payload.ctx. Add a regression test
covering new Token({ ctx: { contractId } }).toSignedString({ ctx: null }) and
assert the signed payload contains no ctx.

---

Nitpick comments:
In `@functions/contractTaproot.js`:
- Around line 683-741: The ladder-selection condition in
buildFederationVaultFromPolicy should honor the documented
publisher-plus-csvBlocks inputs without requiring the undocumented
opts.useLadder flag. Remove that extra condition so failover or a valid
publisher with positive csvBlocks and ladder settings selects the ladder, or
update the JSDoc and contract if the flag is intentionally required.
- Around line 616-629: Reuse the already compiled leaves array in the
surrounding build flow instead of calling compileLeaves(built.policy) twice.
Update the leaf lookup and prepareLeafPsbt arguments in the code around
prepareLeafPsbt, using the existing compiled-leaves symbol from
buildContractTaproot when available; otherwise compile once into a local array
and pass it to both consumers.
- Around line 29-34: Update networkForFabricName so the ambiguous "test" alias
is no longer mapped to networks.regtest; remove it or replace it with an
explicit documented regtest alias, while preserving the existing regtest,
testnet, and signet mappings.

In `@functions/contractTierWhen.js`:
- Around line 41-54: Update evaluateTierWhen so an allOf array must contain at
least one predicate; return false for an empty list before iterating, while
preserving the existing validation and evaluation behavior for non-empty arrays.

In `@types/contract.js`:
- Around line 342-345: Update the ladder selection in the surrounding
contract-building method to prioritize overrides.spendLadder before
this.settings.spendLadder, while retaining null as the fallback and preserving
the existing tap.buildContractTaproot call.
🪄 Autofix

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: Path: .coderabbit.yaml

Review profile: CHILL

Plan: Pro Plus

Run ID: 8ad37535-1ff4-4939-9572-b2e7eb5b3125

📥 Commits

Reviewing files that changed from the base of the PR and between 67d6edf and 91f0342.

⛔ Files ignored due to path filters (3)
  • docs/APPLICATION_NAMESPACES.md is excluded by !docs/**
  • docs/DISTRIBUTED_EXECUTION.md is excluded by !docs/**
  • reports/install.log is excluded by !**/*.log
📒 Files selected for processing (13)
  • functions/applicationNamespaces.js
  • functions/contractCapability.js
  • functions/contractStateSigning.js
  • functions/contractTaproot.js
  • functions/contractTierWhen.js
  • tests/applicationNamespaces.unit.js
  • tests/contractCapability.unit.js
  • tests/contractStateSigning.unit.js
  • tests/contractTaproot.unit.js
  • types/contract.js
  • types/federation.js
  • types/peer.js
  • types/token.js

Comment thread functions/contractCapability.js Outdated
Comment thread functions/contractStateSigning.js
Comment thread functions/contractTaproot.js
Comment thread functions/contractTaproot.js
Comment thread functions/contractTaproot.js
Comment thread functions/contractTierWhen.js
Comment thread types/federation.js Outdated
Comment thread types/federation.js Outdated
Comment thread types/federation.js Outdated
Comment thread types/token.js Outdated
@codecov

codecov Bot commented Aug 7, 2026

Copy link
Copy Markdown

Codecov Report

❌ Patch coverage is 96.09375% with 5 lines in your changes missing coverage. Please review.
✅ Project coverage is 86.88%. Comparing base (214909a) to head (a97e752).

Files with missing lines Patch % Lines
types/message.js 82.14% 5 Missing ⚠️
Additional details and impacted files
@@            Coverage Diff             @@
##           master     #183      +/-   ##
==========================================
+ Coverage   86.70%   86.88%   +0.18%     
==========================================
  Files          96       96              
  Lines       34285    34355      +70     
  Branches        1        1              
==========================================
+ Hits        29727    29850     +123     
+ Misses       4558     4505      -53     

☔ View full report in Codecov by Harness.
📢 Have feedback on the report? Share it here.

🚀 New features to boost your workflow:
  • ❄️ Test Analytics: Detect flaky tests, report on failures, and find test suite problems.
  • 📦 JS Bundle Analysis: Save yourself from yourself by tracking and limiting bundle sizes in JS merges.

Comment thread .npmrc
# core/http/hub from GitHub need `allow-git=all` so nested git dep preparation
# (commit-SHA fetches) is not refused. Keep this file so the monorepo default
# matches Hub / http / app packages.
allow-git=all

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

Medium — Supply-chain: re-enables npm 12 git-dependency RCE path

allow-git=all opts this repo (and GitHub installs of it) out of npm 12’s default allow-git=none, which blocks install-time code execution via a git dependency’s .npmrc overriding the git binary (works even with --ignore-scripts). Confirmed still present at HEAD 3a4d87ab.

Attack path: Compromised or malicious transitive git+/github: dependency → fetched under all → that checkout’s .npmrc redirects git → arbitrary code at install time. @fabric/core still declares no git deps, so all is broader than needed (.npmrc is not in the published files list, but applies to clones and github: installs).

Same relaxation remains in package.json report:install (npm i --allow-git=all after wiping the lockfile), which further widens install-time trust on a lockfile-free resolve.

Comment thread .npmrc
# core/http/hub from GitHub need `allow-git=all` so nested git dep preparation
# (commit-SHA fetches) is not refused. Keep this file so the monorepo default
# matches Hub / http / app packages.
allow-git=all

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

Medium — Supply-chain: re-enables npm 12 git-dependency RCE path

allow-git=all opts this repo (and GitHub installs of it) out of npm 12’s default allow-git=none, which blocks install-time code execution via a git dependency’s .npmrc overriding the git binary (works even with --ignore-scripts). Confirmed still present at HEAD b984c1f60.

Attack path: Compromised or malicious transitive git+/github: dependency → fetched under all → that checkout’s .npmrc redirects git → arbitrary code at install time. @fabric/core still declares no git deps, so all is broader than needed (.npmrc is not in the published files list, but applies to clones and github: installs).

Same relaxation remains in package.json report:install (npm i --allow-git=all after wiping the lockfile), which further widens install-time trust on a lockfile-free resolve.

Comment thread .npmrc
# core/http/hub from GitHub need `allow-git=all` so nested git dep preparation
# (commit-SHA fetches) is not refused. Keep this file so the monorepo default
# matches Hub / http / app packages.
allow-git=all

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

Medium — Supply-chain: re-enables npm 12 git-dependency RCE path

allow-git=all opts this repo (and GitHub installs of it) out of npm 12’s default allow-git=none, which blocks install-time code execution via a git dependency’s .npmrc overriding the git binary (works even with --ignore-scripts). Confirmed still present at HEAD 3fc4b6602.

Attack path: Compromised or malicious transitive git+/github: dependency → fetched under all → that checkout’s .npmrc redirects git → arbitrary code at install time. @fabric/core still declares no git deps, so all is broader than needed (.npmrc is not in the published files list, but applies to clones and github: installs).

Same relaxation remains in package.json report:install (npm i --allow-git=all after wiping the lockfile), which further widens install-time trust on a lockfile-free resolve.

@cursor cursor 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.

Cursor Bugbot has reviewed your changes using high effort and found 2 potential issues.

Fix All in Cursor

❌ Bugbot Autofix is OFF. To automatically fix reported issues with cloud agents, enable autofix in the Cursor dashboard.

Reviewed by Cursor Bugbot for commit a97e752. Configure here.

let sequence = opts.sequence != null ? Number(opts.sequence) : undefined;
let locktime;
if (afterLock && afterLock.type === 'csv') {
if (sequence == null) sequence = afterLock.value;

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

CSV input sequence mis-encoded

High Severity

In prepareLeafPsbt, CSV timelock tiers set the input sequence to the raw block count (e.g. 144). BIP68 requires enabling relative locktime (0x00400000) in nSequence with the block count in the low 16 bits, so prepared tier and decay-migration PSBTs will not satisfy OP_CHECKSEQUENCEVERIFY on broadcast.

Fix in Cursor Fix in Web

Reviewed by Cursor Bugbot for commit a97e752. Configure here.

let catalog;
try {
catalog = JSON.parse(fields.catalogCanonical);
} catch (err) {

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

Patches blocked by catalog guard

Medium Severity

applyRegistryUpdateFields rejects any update when catalogCanonical is missing or empty before it reads patchesCanonical. Multi-op sidechain patches that rely on an empty catalog placeholder (or decoded empty string with a non-empty patchesCanonical) fail with catalogCanonical required instead of applying the patch array.

Additional Locations (1)
Fix in Cursor Fix in Web

Reviewed by Cursor Bugbot for commit a97e752. Configure here.

@cursor cursor 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.

Security review

Re-validated prior automation findings against HEAD a97e7523a (diff 214909a44…a97e7523a). Latest commit adds optional patchesCanonical on SIDECHAIN_STATE_PATCH plus message decode defaults.

Open: 1 medium (supply-chain). Prior contract auth/crypto concerns (unverified capability parse, tip-threshold fail-open, silent k-of-n clamp, malformed statePathEq) remain addressed.

No additional medium+ vulnerabilities identified in the new ARC/registry path: applyRegistryUpdateFields still requires caller-supplied fields and optional path policy; federation/auth gates are outside this helper. Peer contract:message now attaches wire hex for audit — not a new trust boundary bypass.

Note: Slack summary could not be posted — no Slack action is configured for this automation.

Open in Web View Automation 

Sent by Cursor Automation: Find vulnerabilities

Comment thread .npmrc
# core/http/hub from GitHub need `allow-git=all` so nested git dep preparation
# (commit-SHA fetches) is not refused. Keep this file so the monorepo default
# matches Hub / http / app packages.
allow-git=all

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

Medium — Supply-chain: re-enables npm 12 git-dependency RCE path

allow-git=all opts this repo (and GitHub installs of it) out of npm 12’s default allow-git=none, which blocks install-time code execution via a git dependency’s .npmrc overriding the git binary (works even with --ignore-scripts). Confirmed still present at HEAD a97e7523a.

Attack path: Compromised or malicious transitive git+/github: dependency → fetched under all → that checkout’s .npmrc redirects git → arbitrary code at install time. @fabric/core still declares no git deps, so all is broader than needed (.npmrc is not in the published files list, but applies to clones and github: installs).

Same relaxation remains in package.json report:install (npm i --allow-git=all after wiping the lockfile), which further widens install-time trust on a lockfile-free resolve.

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

Caution

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

⚠️ Outside diff range comments (1)
functions/documentRegistrySidechain.js (1)

123-125: 🎯 Functional Correctness | 🟠 Major | ⚡ Quick win

Allow patch-only updates.

Lines 124-125 reject an empty catalogCanonical before the patchesCanonical branch runs. This conflicts with the patch-first design and forces patch senders to include unused catalog JSON. Validate catalogCanonical only after no patch sequence is supplied.

Proposed fix
 function applyRegistryUpdateFields (state, fields, policy = null) {
-  if (!fields || typeof fields.catalogCanonical !== 'string' || !fields.catalogCanonical) {
-    return { ok: false, error: 'catalogCanonical required' };
-  }
+  if (!fields || typeof fields !== 'object') {
+    return { ok: false, error: 'fields required' };
+  }

   // ... basis validation and patchesCanonical handling ...

+  if (typeof fields.catalogCanonical !== 'string' || !fields.catalogCanonical) {
+    return { ok: false, error: 'catalogCanonical required' };
+  }
   let catalog;
🤖 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/documentRegistrySidechain.js` around lines 123 - 125, Update
applyRegistryUpdateFields so the initial validation does not require
catalogCanonical when a patchesCanonical sequence is supplied. Validate
catalogCanonical only in the non-patch update path, while preserving the
existing required-field error for updates without patchesCanonical.
🤖 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 `@tests/contractTaproot.unit.js`:
- Around line 302-314: Update normalizeLock to enforce the Bitcoin CLTV
threshold consistently: accept height values below 500000000 only when
represented as height, accept unix values at or above 500000000 only when
represented as unix, and reject mismatched representations. Add coverage in the
normalizeLock tests for height: 500000000 and unix: 499999999, ensuring
timelockMature and transaction locktime behavior remain aligned.

In `@types/contract.js`:
- Around line 340-345: Update the JSDoc link in the shared spend-policy inputs
comment to use the class-qualified Contract member reference for
toTaprootContract, ensuring API.md resolves the generated link to the Contract
method.

In `@types/message.js`:
- Around line 1226-1227: Update the default branch of the optional field type
handling in encodeBody/decodeBody to throw a TypeError for unsupported trailing
optional field types instead of returning null, matching the rejection behavior
for non-empty decodeBody inputs.

---

Outside diff comments:
In `@functions/documentRegistrySidechain.js`:
- Around line 123-125: Update applyRegistryUpdateFields so the initial
validation does not require catalogCanonical when a patchesCanonical sequence is
supplied. Validate catalogCanonical only in the non-patch update path, while
preserving the existing required-field error for updates without
patchesCanonical.
🪄 Autofix

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: Path: .coderabbit.yaml

Review profile: CHILL

Plan: Pro Plus

Run ID: c04ede96-8598-4957-b53d-2b9cb99c6cc0

📥 Commits

Reviewing files that changed from the base of the PR and between 91f0342 and a97e752.

⛔ Files ignored due to path filters (2)
  • docs/DISTRIBUTED_EXECUTION.md is excluded by !docs/**
  • docs/MESSAGE_BODY.md is excluded by !docs/**
📒 Files selected for processing (20)
  • API.md
  • DEVELOPERS.md
  • MESSAGES.md
  • functions/contractCapability.js
  • functions/contractStateSigning.js
  • functions/contractTaproot.js
  • functions/contractTierWhen.js
  • functions/documentRegistrySidechain.js
  • reports/TODO.txt
  • tests/contractCapability.unit.js
  • tests/contractStateSigning.unit.js
  • tests/contractTaproot.unit.js
  • tests/contractTierWhen.unit.js
  • tests/fabric.token.js
  • tests/functions.documentRegistrySidechain.js
  • types/contract.js
  • types/federation.js
  • types/message.js
  • types/peer.js
  • types/token.js
💤 Files with no reviewable changes (1)
  • reports/TODO.txt
🚧 Files skipped from review as they are similar to previous changes (8)
  • types/token.js
  • DEVELOPERS.md
  • types/peer.js
  • functions/contractStateSigning.js
  • functions/contractTierWhen.js
  • tests/contractStateSigning.unit.js
  • functions/contractCapability.js
  • functions/contractTaproot.js

Comment on lines +302 to +314
it('timelockMature for csv and cltv', function () {
assert.strictEqual(timelockMature(null, { utxoAgeBlocks: 0 }), true);
assert.strictEqual(timelockMature({ type: 'csv', blocks: 10 }, { utxoAgeBlocks: 9 }), false);
assert.strictEqual(timelockMature({ type: 'csv', blocks: 10 }, { utxoAgeBlocks: 10 }), true);
assert.strictEqual(timelockMature({ type: 'cltv', height: 100 }, { tipHeight: 99 }), false);
assert.strictEqual(timelockMature({ type: 'cltv', height: 100 }, { tipHeight: 100 }), true);
});

it('normalizeLock rejects CSV above 65535', function () {
assert.ok(normalizeLock({ type: 'csv', blocks: 65535 }));
assert.strictEqual(normalizeLock({ type: 'csv', blocks: 65536 }), null);
assert.strictEqual(normalizeLock({ type: 'csv', blocks: 0 }), 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 | ⚡ Quick win

Validate the CLTV locktime type boundary.

normalizeLock accepts unix values below 500000000 and height values at or above it. timelockMature then uses the field name, but OP_CHECKLOCKTIMEVERIFY uses the numeric locktime threshold. This can mark a tier active while its withdrawal transaction is not mineable, or reject a tier that Bitcoin considers mature. (bips.dev)

Reject mismatched values and add boundary tests for height: 500000000 and unix: 499999999.

Proposed fix
+const CLTV_TIMESTAMP_THRESHOLD = 500000000;
+const MAX_LOCKTIME = 0xffffffff;
+
 function normalizeLock (raw) {
   // ...
   if (type === 'cltv') {
-    const value = Math.max(0, Math.floor(Number(raw.height != null ? raw.height : raw.unix) || 0));
-    if (!value) return null;
-    return { type: 'cltv', value, height: raw.height != null ? value : undefined, unix: raw.unix != null ? value : undefined };
+    const hasHeight = raw.height != null;
+    const hasUnix = raw.unix != null;
+    if (hasHeight === hasUnix) return null;
+    const value = Math.floor(Number(hasHeight ? raw.height : raw.unix));
+    if (!Number.isFinite(value) || value < 1 || value > MAX_LOCKTIME) return null;
+    if ((hasHeight && value >= CLTV_TIMESTAMP_THRESHOLD) ||
+        (hasUnix && value < CLTV_TIMESTAMP_THRESHOLD)) return null;
+    return {
+      type: 'cltv',
+      value,
+      height: hasHeight ? value : undefined,
+      unix: hasUnix ? value : 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 `@tests/contractTaproot.unit.js` around lines 302 - 314, Update normalizeLock
to enforce the Bitcoin CLTV threshold consistently: accept height values below
500000000 only when represented as height, accept unix values at or above
500000000 only when represented as unix, and reject mismatched representations.
Add coverage in the normalizeLock tests for height: 500000000 and unix:
499999999, ensuring timelockMature and transaction locktime behavior remain
aligned.

Comment thread types/contract.js
Comment on lines +340 to +345
/**
* Shared spend-policy inputs for {@link #toTaprootContract}.
* Subclasses (e.g. Federation) may override to supply validators from state.
* @param {object} [overrides]
* @returns {object}
*/

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

Fix the generated API link.

Line 341 generates the invalid #toTaprootContract fragment in API.md. Use the class-qualified member link so the generated reference resolves to the Contract method.

Proposed fix
-   * Shared spend-policy inputs for {`@link` `#toTaprootContract`}.
+   * Shared spend-policy inputs for {`@link` Contract#toTaprootContract}.
📝 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
/**
* Shared spend-policy inputs for {@link #toTaprootContract}.
* Subclasses (e.g. Federation) may override to supply validators from state.
* @param {object} [overrides]
* @returns {object}
*/
/**
* Shared spend-policy inputs for {`@link` Contract#toTaprootContract}.
* Subclasses (e.g. Federation) may override to supply validators from state.
* `@param` {object} [overrides]
* `@returns` {object}
*/
🤖 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 `@types/contract.js` around lines 340 - 345, Update the JSDoc link in the
shared spend-policy inputs comment to use the class-qualified Contract member
reference for toTaprootContract, ensuring API.md resolves the generated link to
the Contract method.

Source: Linters/SAST tools

Comment thread types/message.js
Comment on lines +1226 to +1227
default:
return 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.

🎯 Functional Correctness | 🟡 Minor | ⚡ Quick win

Reject unsupported optional field types.

Line 1226 accepts an unsupported trailing optional field as null. encodeBody and non-empty decodeBody inputs reject the same schema. Throw TypeError in this branch.

Proposed fix
     case 'u64':
       return 0n;
     default:
-      return null;
+      throw new TypeError(`unsupported field type: ${def.type}`);
📝 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
default:
return null;
default:
throw new TypeError(`unsupported field type: ${def.type}`);
🤖 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 `@types/message.js` around lines 1226 - 1227, Update the default branch of the
optional field type handling in encodeBody/decodeBody to throw a TypeError for
unsupported trailing optional field types instead of returning null, matching
the rejection behavior for non-empty decodeBody inputs.

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