Skip to content

Ord/refactor existing endpoints#41

Open
Neurvinch wants to merge 4 commits intoOWASP-BLT:mainfrom
Neurvinch:ord/refactor-existing-endpoints
Open

Ord/refactor existing endpoints#41
Neurvinch wants to merge 4 commits intoOWASP-BLT:mainfrom
Neurvinch:ord/refactor-existing-endpoints

Conversation

@Neurvinch
Copy link
Copy Markdown
Contributor

@Neurvinch Neurvinch commented Mar 17, 2026

Summary

  • Add validate_live_auth() — constant-time password check via hmac.compare_digest (prevents timing side-channel attacks on the wallet API password)
  • Safe default: missing WALLET_API_PASSWORD env var rejects all live transactions with 503
  • Refactor all 3 existing endpoints to use shared helpers (make_base_command, make_wallet_args, run_ord_command, sanitize_error, write_temp_yaml, validate_fee_rate)
  • Temp YAML files are cleaned up in finally blocks after each request
  • Add requests to requirements.txt
  • Add PROJECT_INFO.md to ord-server/.gitignore

Security

The previous password != os.getenv("WALLET_API_PASSWORD") comparison was vulnerable to timing side-channel attacks. Replaced with hmac.compare_digest() which runs in constant time regardless of where the mismatch occurs.

Changes

  • ord-server/ord-api.pyvalidate_live_auth() + endpoint refactoring
  • ord-server/.env.example — updated YAML_FILE_PATH comment
  • ord-server/.gitignore — add PROJECT_INFO.md
  • ord-server/requirements.txt — add requests

Test plan

  • Existing endpoints still work with valid webhook signature
  • Non-dry-run without password → 401
  • Non-dry-run with wrong password → 401 (constant-time)
  • Missing WALLET_API_PASSWORD env var → 503
  • Temp YAML files are deleted after request completes

Neurvinch and others added 4 commits March 17, 2026 12:39
…roxy

Replace placeholder worker with a real Cloudflare Worker that:
- Routes /mainnet/* and /regtest/* as API calls, everything else as static site
- Validates X-Signature-256 HMAC-SHA256 signature before forwarding
- Proxies authenticated API requests to the private ord backend (ORD_BACKEND_URL)
- Returns JSON errors for missing config or invalid signatures
- Add ORD_BACKEND_URL to wrangler.toml [vars]

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
…helpers

- run_ord_command: exponential-backoff retry on transient errors
  (connection refused, timeout, etc.)
- sanitize_error: strip RPC passwords from stderr before returning
- write_temp_yaml: UUID-suffixed temp files to avoid race conditions

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
- make_base_command: build ord CLI prefix for mainnet/regtest
- make_wallet_args: build wallet sub-command args per network
- validate_fee_rate: range check (1-10000 sat/vbyte)
- Fix mainnet RPC URL default port (8332, not 18443)

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
- validate_live_auth: constant-time password check via hmac.compare_digest
  (prevents timing side-channel attacks on the wallet password)
- Refactor all 3 endpoints to use make_base_command, make_wallet_args,
  run_ord_command, sanitize_error, write_temp_yaml, validate_fee_rate
- Temp YAML files are cleaned up after each request
- Add requests to requirements.txt

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
@owasp-blt
Copy link
Copy Markdown

owasp-blt bot commented Mar 17, 2026

📊 Monthly Leaderboard

Hi @Neurvinch! Here's how you rank for March 2026:

Rank User Open PRs PRs (merged) PRs (closed) Reviews Comments Total
31 snk-git-hub @snk-git-hub 3 1 0 1 9 36
32 Neurvinch @Neurvinch 11 2 0 0 2 35
33 xovishnukosuri @xovishnukosuri 2 2 0 0 1 24

Scoring this month (across OWASP-BLT org): Open PRs (+1 each), Merged PRs (+10), Closed (not merged) (−2), Reviews (+5; first two per PR in-month), Comments (+2, excludes CodeRabbit). Run /leaderboard on any issue or PR to see your rank!

@owasp-blt
Copy link
Copy Markdown

owasp-blt bot commented Mar 17, 2026

👋 Hi @Neurvinch!

This pull request needs a peer review before it can be merged. Please request a review from a team member who is not:

  • The PR author
  • coderabbitai
  • copilot

Once a valid peer review is submitted, this check will pass automatically. Thank you!

⚠️ Peer review enforcement is active.

@coderabbitai
Copy link
Copy Markdown

coderabbitai bot commented Mar 17, 2026

Walkthrough

The changes introduce robust retry logic with transient error detection for ord CLI commands, add signature-based authentication to the API gateway for backend proxying, implement validation helpers for inputs, and add backend URL configuration for the API gateway routing.

Changes

Cohort / File(s) Summary
Configuration & Dependencies
ord-server/.env.example, ord-server/requirements.txt, wrangler.toml
Updated YAML_FILE_PATH comment, added requests library dependency, and introduced ORD_BACKEND_URL configuration variable for backend routing.
Core API Resilience & Validation
ord-server/ord-api.py
Added transient error detection, retry logic with exponential backoff, and helper utilities for command execution (run_ord_command). Introduced validation functions for fee rates and authentication, error sanitization to redact credentials, and temporary YAML file handling with cleanup. Refactored webhook endpoints to use new helpers and error patterns.
API Gateway & Authentication
src/index.py
Implemented HMAC-SHA256 signature verification for authenticated requests, added JSON error response helper, and reworked routing to proxy authenticated API requests to backend while serving static content for /mainnet/ and /regtest/ paths.

Sequence Diagram(s)

sequenceDiagram
    participant Client
    participant OrdAPI as Ord API Server
    participant OrdCLI as Ord CLI
    participant ErrorDetector

    Client->>OrdAPI: POST /send-bacon-tokens (with payload)
    OrdAPI->>OrdAPI: Validate inputs (fee_rate, auth)
    OrdAPI->>OrdAPI: Write YAML to temp file
    
    OrdAPI->>OrdCLI: Execute command (attempt 1)
    OrdCLI-->>OrdAPI: stderr with transient error
    OrdAPI->>ErrorDetector: Classify error
    ErrorDetector-->>OrdAPI: Is transient? Yes
    
    OrdAPI->>OrdAPI: Wait (exponential backoff)
    OrdAPI->>OrdCLI: Execute command (attempt 2)
    OrdCLI-->>OrdAPI: Success/Permanent error
    
    OrdAPI->>OrdAPI: Sanitize error if needed
    OrdAPI->>OrdAPI: Cleanup temp files
    OrdAPI-->>Client: JSON response
Loading
sequenceDiagram
    participant Client
    participant Gateway as API Gateway
    participant Verifier as Signature Verifier
    participant Backend as Backend Server

    Client->>Gateway: POST /api/... (with X-Webhook-Signature header)
    Gateway->>Verifier: Verify signature (HMAC-SHA256)
    Verifier-->>Gateway: Valid? Yes/No
    
    alt Signature valid
        Gateway->>Backend: Proxy request (forward body + headers)
        Backend-->>Gateway: Response
        Gateway-->>Client: JSON response
    else Signature invalid
        Gateway-->>Client: 401 JSON error (Unauthorized)
    end
Loading

Estimated code review effort

🎯 4 (Complex) | ⏱️ ~60 minutes

Suggested labels

quality: high

🚥 Pre-merge checks | ✅ 1 | ❌ 2

❌ Failed checks (1 warning, 1 inconclusive)

Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 78.57% which is insufficient. The required threshold is 80.00%. Write docstrings for the functions missing them to satisfy the coverage threshold.
Title check ❓ Inconclusive The title 'Ord/refactor existing endpoints' partially matches the changeset. While refactoring is mentioned, the PR's primary objective is adding new etch-rune endpoints and their supporting infrastructure. Consider a more specific title that captures the main feature: 'Add etch-rune endpoints with retry logic and API gateway' or similar.
✅ Passed checks (1 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.

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

✨ Finishing Touches
🧪 Generate unit tests (beta)
  • Create PR with unit tests
📝 Coding Plan
  • Generate coding plan for human review comments

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.

Copy link
Copy Markdown

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

Actionable comments posted: 1

Caution

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

⚠️ Outside diff range comments (1)
ord-server/ord-api.py (1)

5-9: 🛠️ Refactor suggestion | 🟠 Major

Remove duplicate imports.

hmac and hashlib are imported twice (lines 5-6 and lines 8-9).

♻️ Proposed fix
 import uuid
 import time
 import hmac
 import hashlib
 import yaml
-import hmac
-import hashlib
 from flask import Flask, jsonify, request
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@ord-server/ord-api.py` around lines 5 - 9, Remove the duplicate imports of
hmac and hashlib in ord-api.py; keep a single import statement for each module
(e.g., import hmac and import hashlib) and delete the repeated lines so there is
only one import occurrence per module (verify any other duplicate import lines
in ord_api.py and clean them similarly).
🧹 Nitpick comments (5)
wrangler.toml (1)

8-10: Clarify configuration approach: secret vs. plain var.

The comment suggests setting ORD_BACKEND_URL via wrangler secret put, but the value is defined under [vars] which stores plaintext variables, not secrets. Secrets set via wrangler secret put override vars at runtime but aren't defined in wrangler.toml.

If this URL should be treated as sensitive, remove the default value and rely solely on the secret. Otherwise, update the comment to reflect that this is a plain configuration variable with an optional secret override.

📝 Suggested comment clarification
 [vars]
 ENVIRONMENT = "production"
-# URL of the private ord backend service (Flask server on your ord node)
-# Set via: npx wrangler secret put ORD_BACKEND_URL
+# URL of the private ord backend service (Flask server on your ord node).
+# Override in production via: npx wrangler secret put ORD_BACKEND_URL
 ORD_BACKEND_URL = "https://your-ord-backend.example.com"
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@wrangler.toml` around lines 8 - 10, The wrangler.toml currently defines
ORD_BACKEND_URL under [vars] but the comment instructs using `npx wrangler
secret put` which is misleading; either remove the plaintext default and treat
ORD_BACKEND_URL exclusively as a secret (delete the value from wrangler.toml and
rely on the secret set at runtime via `wrangler secret put ORD_BACKEND_URL`) or
keep the plaintext var and update the comment to state this is a non-sensitive
[vars] entry with an optional secret override, making sure the ORD_BACKEND_URL
key name is referenced correctly in the chosen approach.
ord-server/ord-api.py (2)

132-142: Consider using tempfile module for safer temp file handling.

While the UUID suffix provides good uniqueness, using Python's tempfile module would provide additional safety guarantees (secure permissions, atomic creation) and is more idiomatic.

♻️ Suggested improvement
+import tempfile
+
 def write_temp_yaml(content: str) -> str:
     """Write content to a uniquely named temp file and return its path.

     Using a UUID suffix avoids race conditions when concurrent requests
     share the same YAML_FILE_PATH directory.
     """
     base_dir = os.path.dirname(YAML_FILE_PATH) or "/tmp"
-    tmp_path = os.path.join(base_dir, f"batch-{uuid.uuid4().hex}.yaml")
-    with open(tmp_path, "w") as f:
-        f.write(content)
+    fd, tmp_path = tempfile.mkstemp(suffix=".yaml", prefix="batch-", dir=base_dir)
+    try:
+        with os.fdopen(fd, "w") as f:
+            f.write(content)
+    except:
+        os.close(fd)
+        raise
     return tmp_path
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@ord-server/ord-api.py` around lines 132 - 142, The write_temp_yaml function
currently creates temp files by composing paths with UUIDs; replace this with
the tempfile API to ensure secure, atomic creation and correct permissions: use
tempfile.NamedTemporaryFile or tempfile.mkstemp with dir derived from
os.path.dirname(YAML_FILE_PATH) (fallback to "/tmp"), create the file with
delete=False so it persists, write the provided content into that temporary
file, close it, and return its path; keep the function name write_temp_yaml and
preserve behavior of returning the filepath for downstream use.

145-160: Credentials in command-line arguments are visible in process listings.

Passing --bitcoin-rpc-password on the command line exposes credentials to other users on the system via ps aux or /proc. While sanitize_error redacts these from error output, the credentials remain visible during command execution.

The ord CLI help output does not indicate support for passing credentials via environment variables or configuration files. If such an alternative exists in ord, consider using it instead of embedding credentials in command-line arguments.

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@ord-server/ord-api.py` around lines 145 - 160, make_base_command currently
embeds credentials (e.g. --bitcoin-rpc-password) into the argv passed to
ORD_PATH which exposes secrets in process listings; remove the password flags
from the returned lists in make_base_command (keep ORD_PATH, username and url
flags or remove username too if ord supports env-based auth) and change callers
that invoke subprocess to set the RPC password via the environment (e.g. pass an
env dict with BITCOIN_RPC_PASSWORD_REGTEST or BITCOIN_RPC_PASSWORD_MAINNET)
instead of including --bitcoin-rpc-password on the command line; reference
make_base_command, ORD_PATH and the --bitcoin-rpc-password flag when locating
the changes and ensure sanitize_error handling stays if needed.
src/index.py (1)

1-1: Nit: Replace en-dash with hyphen-minus in docstring.

The docstring uses an en-dash () which may cause issues with some tools. Use a standard hyphen-minus (-) instead.

📝 Suggested fix
-"""BLT-Rewards Cloudflare Worker – public API gateway + static site host."""
+"""BLT-Rewards Cloudflare Worker - public API gateway + static site host."""
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@src/index.py` at line 1, The module docstring in src/index.py uses an en-dash
character in the string """BLT-Rewards Cloudflare Worker – public API gateway +
static site host."""; replace the en-dash with a standard hyphen-minus so the
docstring reads """BLT-Rewards Cloudflare Worker - public API gateway + static
site host.""" ensuring the module-level string is updated accordingly.
ord-server/requirements.txt (1)

1-5: Consider pinning dependency versions for reproducibility.

None of the dependencies have pinned versions. This can lead to inconsistent builds and potential breakage when upstream packages release incompatible updates. Consider pinning to specific versions or version ranges.

♻️ Example with pinned versions
-Flask
-python-dotenv
-PyYAML
-gunicorn
-requests
+Flask>=3.0,<4.0
+python-dotenv>=1.0,<2.0
+PyYAML>=6.0,<7.0
+gunicorn>=21.0,<23.0
+requests>=2.31,<3.0
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@ord-server/requirements.txt` around lines 1 - 5, The requirements.txt
currently lists unpinned packages (Flask, python-dotenv, PyYAML, gunicorn,
requests); update it to pin each dependency to a specific stable version or an
acceptable version range (e.g., == or ~= operators) to ensure reproducible
builds, for example replace each bare name with a pinned version (Flask,
python-dotenv, PyYAML, gunicorn, requests) and commit the change; optionally
include a comment at the top explaining the chosen pinning strategy and add a
periodic review note to upgrade versions deliberately.
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Inline comments:
In `@src/index.py`:
- Around line 59-62: The proxy currently only forwards Content-Type when calling
fetch and thus drops the X-Signature-256 header required by backend
verify_webhook_signature; update the headers passed to fetch in the call inside
src/index.py (the fetch invocation) to include the incoming request's
X-Signature-256 (e.g., read request.headers.get("X-Signature-256") or
equivalent) and add it to the headers dict sent to the backend so the backend's
verify_webhook_signature can validate the request; alternatively, if you intend
the gateway to be sole authenticator, remove or skip backend
verify_webhook_signature instead (prefer forwarding the header for
compatibility).

---

Outside diff comments:
In `@ord-server/ord-api.py`:
- Around line 5-9: Remove the duplicate imports of hmac and hashlib in
ord-api.py; keep a single import statement for each module (e.g., import hmac
and import hashlib) and delete the repeated lines so there is only one import
occurrence per module (verify any other duplicate import lines in ord_api.py and
clean them similarly).

---

Nitpick comments:
In `@ord-server/ord-api.py`:
- Around line 132-142: The write_temp_yaml function currently creates temp files
by composing paths with UUIDs; replace this with the tempfile API to ensure
secure, atomic creation and correct permissions: use tempfile.NamedTemporaryFile
or tempfile.mkstemp with dir derived from os.path.dirname(YAML_FILE_PATH)
(fallback to "/tmp"), create the file with delete=False so it persists, write
the provided content into that temporary file, close it, and return its path;
keep the function name write_temp_yaml and preserve behavior of returning the
filepath for downstream use.
- Around line 145-160: make_base_command currently embeds credentials (e.g.
--bitcoin-rpc-password) into the argv passed to ORD_PATH which exposes secrets
in process listings; remove the password flags from the returned lists in
make_base_command (keep ORD_PATH, username and url flags or remove username too
if ord supports env-based auth) and change callers that invoke subprocess to set
the RPC password via the environment (e.g. pass an env dict with
BITCOIN_RPC_PASSWORD_REGTEST or BITCOIN_RPC_PASSWORD_MAINNET) instead of
including --bitcoin-rpc-password on the command line; reference
make_base_command, ORD_PATH and the --bitcoin-rpc-password flag when locating
the changes and ensure sanitize_error handling stays if needed.

In `@ord-server/requirements.txt`:
- Around line 1-5: The requirements.txt currently lists unpinned packages
(Flask, python-dotenv, PyYAML, gunicorn, requests); update it to pin each
dependency to a specific stable version or an acceptable version range (e.g., ==
or ~= operators) to ensure reproducible builds, for example replace each bare
name with a pinned version (Flask, python-dotenv, PyYAML, gunicorn, requests)
and commit the change; optionally include a comment at the top explaining the
chosen pinning strategy and add a periodic review note to upgrade versions
deliberately.

In `@src/index.py`:
- Line 1: The module docstring in src/index.py uses an en-dash character in the
string """BLT-Rewards Cloudflare Worker – public API gateway + static site
host."""; replace the en-dash with a standard hyphen-minus so the docstring
reads """BLT-Rewards Cloudflare Worker - public API gateway + static site
host.""" ensuring the module-level string is updated accordingly.

In `@wrangler.toml`:
- Around line 8-10: The wrangler.toml currently defines ORD_BACKEND_URL under
[vars] but the comment instructs using `npx wrangler secret put` which is
misleading; either remove the plaintext default and treat ORD_BACKEND_URL
exclusively as a secret (delete the value from wrangler.toml and rely on the
secret set at runtime via `wrangler secret put ORD_BACKEND_URL`) or keep the
plaintext var and update the comment to state this is a non-sensitive [vars]
entry with an optional secret override, making sure the ORD_BACKEND_URL key name
is referenced correctly in the chosen approach.
🪄 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: Repository: OWASP-BLT/coderabbit/.coderabbit.yml

Review profile: CHILL

Plan: Pro

Run ID: 1d47a1da-00b2-487e-ab85-b5f7dc13065e

📥 Commits

Reviewing files that changed from the base of the PR and between 9a1b356 and 01e7f72.

📒 Files selected for processing (5)
  • ord-server/.env.example
  • ord-server/ord-api.py
  • ord-server/requirements.txt
  • src/index.py
  • wrangler.toml

Comment on lines +59 to +62
return await fetch(
f"{backend_url}{path}{query}",
{"method": request.method, "headers": {"Content-Type": "application/json"}, "body": body_text},
)
Copy link
Copy Markdown

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🔴 Critical

Backend signature verification will fail: X-Signature-256 header not forwarded.

The backend endpoints (e.g., /mainnet/send-bacon-tokens at ord-server/ord-api.py:206-209) also call verify_webhook_signature() which requires the X-Signature-256 header. The proxy only forwards Content-Type, so all proxied requests will receive a 401 from the backend.

Either forward the signature header to the backend, or remove the duplicate verification from the backend if the gateway is the sole authenticator.

🐛 Option 1: Forward the signature header
     return await fetch(
         f"{backend_url}{path}{query}",
-        {"method": request.method, "headers": {"Content-Type": "application/json"}, "body": body_text},
+        {
+            "method": request.method,
+            "headers": {
+                "Content-Type": "application/json",
+                "X-Signature-256": signature,
+            },
+            "body": body_text,
+        },
     )
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@src/index.py` around lines 59 - 62, The proxy currently only forwards
Content-Type when calling fetch and thus drops the X-Signature-256 header
required by backend verify_webhook_signature; update the headers passed to fetch
in the call inside src/index.py (the fetch invocation) to include the incoming
request's X-Signature-256 (e.g., read request.headers.get("X-Signature-256") or
equivalent) and add it to the headers dict sent to the backend so the backend's
verify_webhook_signature can validate the request; alternatively, if you intend
the gateway to be sole authenticator, remove or skip backend
verify_webhook_signature instead (prefer forwarding the header for
compatibility).

@github-project-automation github-project-automation bot moved this from Backlog to Ready in 📌 OWASP BLT Project Board Mar 17, 2026
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Projects

Status: Ready

Development

Successfully merging this pull request may close these issues.

1 participant