Skip to content

fix: restrict insecure Stellar RPC URLs#262

Open
newmattock wants to merge 1 commit into
Fundable-Protocol:mainfrom
newmattock:codex/http-rpc-loopback-safety
Open

fix: restrict insecure Stellar RPC URLs#262
newmattock wants to merge 1 commit into
Fundable-Protocol:mainfrom
newmattock:codex/http-rpc-loopback-safety

Conversation

@newmattock
Copy link
Copy Markdown

@newmattock newmattock commented May 25, 2026

Summary:

  • Removes hardcoded allowHttp: true from SDK and web production code paths.
  • Rejects non-loopback plain-HTTP RPC URLs in the SDK unless callers explicitly opt into local loopback development.
  • Documents the local RPC opt-in and adds regression coverage for the SDK constructor.

Related bounty or issue:

What changed:

  • Added a shared SDK helper for local-only HTTP RPC allowance.
  • Added allowHttp?: boolean to DeployerConfig and GasEstimatorOptions.
  • Gated web RPC/Horizon allowHttp options behind non-production loopback URLs.
  • Removed unnecessary Horizon allowHttp options where the URL is always HTTPS.

Validation:

  • pnpm --filter @fundable/sdk exec vitest --run src/__tests__/ContractDeployer.test.ts -t "RPC URL safety" passed.
  • pnpm --filter @fundable/sdk exec vitest --run src/__tests__/GasEstimator.test.ts passed.

Notes:

  • pnpm --filter @fundable/sdk exec vitest --run src/__tests__/ContractDeployer.test.ts still has 3 pre-existing deploy-contract test failures: (intermediate value).toScAddress is not a function.
  • pnpm --filter @fundable/sdk build still fails on existing TypeScript/project issues such as extensionless imports under NodeNext, old Stellar SDK names, and missing test helpers.
  • pnpm --filter @fundable/web exec tsc --noEmit --pretty false still fails on existing syntax errors in src/app/(overview)/distribution/page.tsx and src/components/modules/payment-stream/CreatePaymentStream.tsx.

Summary by CodeRabbit

  • New Features

    • Implemented HTTP security validation restricting HTTP RPC URLs to local loopback hosts only, with explicit opt-in required for local development.
  • Bug Fixes

    • Removed overly permissive HTTP settings in RPC client initialization across services.
  • Documentation

    • Updated development guide documenting the new local-only HTTP policy for RPC endpoints.
  • Tests

    • Added comprehensive RPC URL safety test coverage.

Review Change Stack

@coderabbitai
Copy link
Copy Markdown
Contributor

coderabbitai Bot commented May 25, 2026

📝 Walkthrough

Walkthrough

This PR hardens HTTP security across Stellar RPC and Horizon client initialization by restricting plain-HTTP to loopback addresses only and disabling it in production. A new security utility validates URLs, enforces loopback-only allowance, and throws on non-compliant URLs. The SDK and web app are updated to apply this policy during RPC/Horizon server construction, configuration types are extended with optional allowHttp flags, and documentation and tests are added to explain and validate the new behavior.

Changes

HTTP RPC Security Policy: Loopback-Only with Opt-In

Layer / File(s) Summary
HTTP security validation utility
packages/sdk/src/utils/httpSecurity.ts
New shouldAllowLocalHttp(url, allowHttp) function validates whether a URL is permitted to use plain HTTP by enforcing protocol, parsing success, and restricting hostnames to loopback targets (localhost, 127.0.0.1, [::1]); throws a descriptive error for non-loopback HTTP unless explicitly opted in.
SDK ContractDeployer: HTTP validation and config
packages/sdk/src/deployer/types.ts, packages/sdk/src/deployer/ContractDeployer.ts
DeployerConfig gains optional allowHttp?: boolean field. ContractDeployer imports the security utility and applies it during RPC server construction, wrapping validation failures in DeployerError with code UNSAFE_HTTP_RPC_URL.
SDK GasEstimator: HTTP validation and config
packages/sdk/src/utils/GasEstimator.ts
GasEstimatorOptions gains optional allowHttp?: boolean field. Default RPC server construction replaces unconditional allowHttp: true with conditional logic using the security utility.
Web app ContractDeployer: loopback-only HTTP
apps/web/src/services/contract.deployer.ts
Local allowLocalHttp helper permits HTTP only for loopback hosts and disables HTTP entirely when NODE_ENV is production. Constructor wiring switches from unconditional allowHttp: true to conditional allowance.
Web app StellarService: loopback-only HTTP for RPC and Horizon
apps/web/src/services/stellar.service.ts
Local allowLocalHttp helper restricts HTTP to loopback hosts with production disabled. Both RpcServer and Horizon.Server initialization use conditional HTTP allowance based on the helper instead of unconditional enablement.
Distribution transaction: remove hardcoded allowHttp
apps/web/src/hooks/use-distribution-transaction.ts
Two Horizon client initializations in accountExists and checkSenderBalance stop passing { allowHttp: true }, deferring to default behavior.
Documentation and test coverage
packages/sdk/DEVELOPMENT_GUIDE.md, packages/sdk/src/__tests__/ContractDeployer.test.ts
Development guide documents the "Local RPC over HTTP" policy with HTTPS required for production/public testnet/mainnet and plain HTTP allowed only for loopback with explicit opt-in. Test suite validates ContractDeployer constructor rejection of non-loopback HTTP and acceptance of loopback HTTP with opt-in.

Estimated code review effort

🎯 3 (Moderate) | ⏱️ ~20 minutes

Possibly related PRs

Poem

🐰 HTTP wanderers, beware! 🔒
Loopback only—localhost's fair,
Production locks the gates so tight,
Keep those transactions safe from sight! ✨

🚥 Pre-merge checks | ✅ 4 | ❌ 1

❌ Failed checks (1 warning)

Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 25.00% 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
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check ✅ Passed The PR title 'fix: restrict insecure Stellar RPC URLs' accurately summarizes the main change—restricting allowHttp to prevent unencrypted RPC connections—and aligns with the actual codebase modifications.
Linked Issues check ✅ Passed The PR successfully addresses all coding requirements from issue #255: removed hardcoded allowHttp:true, implemented loopback-only validation via shouldAllowLocalHttp, added allowHttp flag to SDK types, gated web app behind production checks, and added test coverage.
Out of Scope Changes check ✅ Passed All file modifications are directly scoped to the HTTP RPC security fix: SDK utility/deployer updates, web service modifications, config type additions, documentation, and test coverage. No unrelated changes detected.

✏️ Tip: You can configure your own custom pre-merge checks in the settings.

✨ Finishing Touches
🧪 Generate unit tests (beta)
  • Create PR with unit tests

Warning

There were issues while running some tools. Please review the errors and either fix the tool's configuration or disable the tool if it's a critical failure.

🔧 ESLint

If the error stems from missing dependencies, add them to the package.json file. For unrecoverable errors (e.g., due to private dependencies), disable the tool in the CodeRabbit configuration.

ESLint skipped: no ESLint configuration detected in root package.json. To enable, add eslint to devDependencies.


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 and usage tips.

@newmattock newmattock marked this pull request as ready for review May 25, 2026 02:39
Copy link
Copy Markdown
Contributor

@coderabbitai coderabbitai Bot left a comment

Choose a reason for hiding this comment

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

🧹 Nitpick comments (2)
packages/sdk/src/__tests__/ContractDeployer.test.ts (1)

166-184: ⚡ Quick win

Add assertions for error code and message to strengthen test validation.

The tests correctly verify that DeployerError is thrown, but don't check the error code or message. According to the PR context, the error code should be UNSAFE_HTTP_RPC_URL. Adding specific assertions would better validate the contract and improve regression protection.

🧪 Proposed enhancement for error assertions
     it('rejects non-loopback HTTP RPC URLs by default', () => {
-      expect(
-        () =>
-          new ContractDeployer({
-            rpcUrl: 'http://example.com',
-            networkPassphrase: 'Test SDF Network ; September 2015',
-          })
-      ).toThrow(DeployerError);
+      try {
+        new ContractDeployer({
+          rpcUrl: 'http://example.com',
+          networkPassphrase: 'Test SDF Network ; September 2015',
+        });
+        expect.fail('Expected DeployerError to be thrown');
+      } catch (err) {
+        expect(err).toBeInstanceOf(DeployerError);
+        expect((err as DeployerError).code).toBe('UNSAFE_HTTP_RPC_URL');
+        expect((err as DeployerError).message).toContain('http://example.com');
+      }
     });

     it('requires explicit opt-in for local HTTP RPC URLs', () => {
-      expect(
-        () =>
-          new ContractDeployer({
-            rpcUrl: 'http://localhost:8000',
-            networkPassphrase: 'Test SDF Network ; September 2015',
-          })
-      ).toThrow(DeployerError);
+      try {
+        new ContractDeployer({
+          rpcUrl: 'http://localhost:8000',
+          networkPassphrase: 'Test SDF Network ; September 2015',
+        });
+        expect.fail('Expected DeployerError to be thrown');
+      } catch (err) {
+        expect(err).toBeInstanceOf(DeployerError);
+        expect((err as DeployerError).code).toBe('UNSAFE_HTTP_RPC_URL');
+        expect((err as DeployerError).message).toContain('allowHttp: 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 `@packages/sdk/src/__tests__/ContractDeployer.test.ts` around lines 166 - 184,
The tests currently only assert that a DeployerError is thrown; update both test
cases in ContractDeployer.test.ts to capture the thrown error and assert its
code equals 'UNSAFE_HTTP_RPC_URL' and that the error message contains a clear
substring (e.g., 'HTTP RPC URL is unsafe' or the specific message used by
ContractDeployer) to ensure precise validation; locate the throws around new
ContractDeployer({ rpcUrl: 'http://example.com', ... }) and new
ContractDeployer({ rpcUrl: 'http://localhost:8000', ... }), catch the error,
assert instance of DeployerError, then assert error.code ===
'UNSAFE_HTTP_RPC_URL' and assert the message includes the expected text.
apps/web/src/services/stellar.service.ts (1)

65-78: 💤 Low value

Consider extracting allowLocalHttp to a shared utility.

This helper is duplicated in contract.deployer.ts. For maintainability, consider extracting it to a shared location (e.g., @/utils/http-security.ts) so both services use the same implementation.

That said, the duplication is minimal and self-contained, so this is not urgent.

🤖 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 `@apps/web/src/services/stellar.service.ts` around lines 65 - 78, Extract the
duplicated allowLocalHttp logic (LOOPBACK_HOSTS and function allowLocalHttp)
into a single shared utility module (e.g., create a new exported function in a
utils file such as http-security) and replace the local definitions in both
stellar.service.ts and contract.deployer.ts with imports from that module; keep
the implementation identical (same host set, NODE_ENV production check, URL
parsing and protocol/hostname checks, and try/catch behavior) so callers of
allowLocalHttp behave unchanged.
🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Nitpick comments:
In `@apps/web/src/services/stellar.service.ts`:
- Around line 65-78: Extract the duplicated allowLocalHttp logic (LOOPBACK_HOSTS
and function allowLocalHttp) into a single shared utility module (e.g., create a
new exported function in a utils file such as http-security) and replace the
local definitions in both stellar.service.ts and contract.deployer.ts with
imports from that module; keep the implementation identical (same host set,
NODE_ENV production check, URL parsing and protocol/hostname checks, and
try/catch behavior) so callers of allowLocalHttp behave unchanged.

In `@packages/sdk/src/__tests__/ContractDeployer.test.ts`:
- Around line 166-184: The tests currently only assert that a DeployerError is
thrown; update both test cases in ContractDeployer.test.ts to capture the thrown
error and assert its code equals 'UNSAFE_HTTP_RPC_URL' and that the error
message contains a clear substring (e.g., 'HTTP RPC URL is unsafe' or the
specific message used by ContractDeployer) to ensure precise validation; locate
the throws around new ContractDeployer({ rpcUrl: 'http://example.com', ... })
and new ContractDeployer({ rpcUrl: 'http://localhost:8000', ... }), catch the
error, assert instance of DeployerError, then assert error.code ===
'UNSAFE_HTTP_RPC_URL' and assert the message includes the expected text.

ℹ️ Review info
⚙️ Run configuration

Configuration used: defaults

Review profile: CHILL

Plan: Pro

Run ID: 140446d6-4d2f-4bb6-9188-d4a9c3722599

📥 Commits

Reviewing files that changed from the base of the PR and between f2c023e and 22b75c7.

📒 Files selected for processing (9)
  • apps/web/src/hooks/use-distribution-transaction.ts
  • apps/web/src/services/contract.deployer.ts
  • apps/web/src/services/stellar.service.ts
  • packages/sdk/DEVELOPMENT_GUIDE.md
  • packages/sdk/src/__tests__/ContractDeployer.test.ts
  • packages/sdk/src/deployer/ContractDeployer.ts
  • packages/sdk/src/deployer/types.ts
  • packages/sdk/src/utils/GasEstimator.ts
  • packages/sdk/src/utils/httpSecurity.ts

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.

sdk + web: hardcoded allowHttp: true permits unencrypted RPC/Horizon connections in production

1 participant