Skip to content
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
76 commits
Select commit Hold shift + click to select a range
9005379
feat: implement RIP-309 Phase 1 Fingerprint Check Rotation
Apr 12, 2026
1c1e41c
fix: address Codex review for RIP-309 Phase 1
Apr 12, 2026
d4ad46b
fix: complete RIP-309 integration with reward gating and signature fix
Apr 12, 2026
4d2b086
fix: integrate canonical RIP-309 rotation module for reward weighting
Apr 12, 2026
d5c825d
fix: bind _handle_get_state signature to msg_id and ttl (arity fix #2…
Apr 20, 2026
2080a24
Security: Hardened P2P sync with replay protection, nonces, and deter…
May 2, 2026
464a08e
Security: Prevent mining reward type confusion in mempool and UTXO apply
May 2, 2026
ba859a7
Security: Hardened Server Proxy with path whitelisting and header for…
May 2, 2026
a84314c
Security: Atomic balance checks and secure hash generation for Bridge…
May 2, 2026
ed6d1e9
Security: Fix voting precision by migrating from REAL to INTEGER weights
May 2, 2026
2437488
Enhancement: Added environment variable support and API authenticatio…
May 2, 2026
574c26b
Security: Implemented cryptographic authentication for bounty claims …
May 2, 2026
2aef619
Enhancement: Added address validation and security checks for x402 co…
May 2, 2026
cc7b765
Security: Fixed potential SQL injection in passport listing
May 2, 2026
9e59f39
Security: Protected Hall of Rust from SQL injection and data vandalism
May 2, 2026
1239068
Security: Hardened LLM JSON extraction in Sophia Governor
May 2, 2026
92a73c9
Security: Switched to full SHA-256 for machine identity to prevent co…
May 2, 2026
4851646
Security: Added robustness to cache feature extraction to prevent cra…
May 2, 2026
29c6686
Security: Switched from float to Decimal for precise financial calcul…
May 2, 2026
ee06e4e
Security: Switched from print to logging and improved error handling …
May 2, 2026
dfbfa93
Security: Enforced commitment matching in BCOS attestation to prevent…
May 2, 2026
1b134b4
Security: Added database-level CHECK constraints for transaction amou…
May 2, 2026
179a811
Security: Hardened claim ID generation to prevent potential ID collis…
May 2, 2026
11e7c32
Security: Prevented field-level DoS in hardware binding by capping MA…
May 2, 2026
a0cee05
Security: Implemented HMAC authentication for P2P state and attestati…
May 2, 2026
88cac3f
Security: Implemented atomic transactions for IP rate limiting to pre…
May 2, 2026
a6ec6e6
Security: Implemented input sanitization to prevent prompt injection …
May 2, 2026
2d532ae
Enhancement: Added environment variable support and timeout protectio…
May 2, 2026
140f85d
Security: Prevented Sybil attacks in Warthog rewards by enforcing uni…
May 2, 2026
631c761
Security: Enforced global uniqueness for GitHub accounts and wallets …
May 2, 2026
4225596
Security: Implemented strict table whitelisting to prevent SQL inject…
May 2, 2026
d3983c4
Security: Improved error handling and logging in rewards settlement t…
May 2, 2026
4e3c20b
Security: Implemented HMAC for subnet hashing to prevent rainbow tabl…
May 2, 2026
1064d58
Security: Implemented monotonic and future-limit validation for block…
May 2, 2026
c9f3e89
Security: Implemented atomic fetch-and-lock for withdrawals to preven…
May 2, 2026
96d5ac4
Security: Implemented 'settling' status to prevent double settlement …
May 2, 2026
d53dd6f
Security: Hardened external subprocess calls and improved error handl…
May 2, 2026
7e9ed4f
Security: Prevented network mapping by removing internal peer history…
May 2, 2026
08737d7
Security: Hardened consensus probe with secure HTTP requests and erro…
May 2, 2026
65df217
Enhancement: Implemented fee-based mempool prioritization to prevent …
May 2, 2026
046a2cd
Enhancement: Added application-level support for transaction fees in …
May 2, 2026
924104b
Security: Prevented authentication bypass in governor inbox when admi…
May 2, 2026
97bdcba
Security: Enforced strict admin authentication for passport updates a…
May 2, 2026
0b9c7d2
Security: Doubled Beacon agent ID length to prevent hash collisions i…
May 2, 2026
e5032eb
Security: Prevented authentication bypass in review service when admi…
May 2, 2026
d0e980a
Security: Added registration pool limits and deterministic JSON hashi…
May 2, 2026
270dd5b
Security: Hardened file hashing and added algorithm validation in ROM…
May 2, 2026
03dbdbc
Security: Fixed potential SQL injection in GPU node filtering by impl…
May 2, 2026
a8178f3
Security: Prevented directory traversal in badge metadata storage by …
May 2, 2026
4af2b70
Enhancement: Added multi-chain address validation support for RustCha…
May 2, 2026
a2609b2
Security: Implemented serial number validation and placeholder blocki…
May 2, 2026
13bdde1
Security: Improved double-mining detection by joining with IP metadat…
May 2, 2026
36aa466
Security: Implemented basic SSRF protection for LLM endpoints in Soph…
May 2, 2026
92563fe
Security: Implemented global mempool size limit to prevent resource e…
May 2, 2026
5d7c197
Security: Hardened nonce validation by checking transaction history t…
May 2, 2026
f334222
Security: Implemented atomic transactions for fingerprint rate limiti…
May 2, 2026
fb68e6e
Security: Prevented Link Injection and Phishing by disabling untruste…
May 2, 2026
bfb8abb
Security: Implemented score clamping and total re-calculation in BCOS…
May 2, 2026
e3856c8
Enhancement: Added exponential backoff retry logic to payout worker f…
May 2, 2026
b77099e
Security: Implemented input sanitization and type enforcement for Web…
May 2, 2026
e98bbc0
Security: Hardened oEmbed video ID extraction and prevented Host Inje…
May 2, 2026
726f2b1
Security: Prevented authentication bypass in lock ledger when admin k…
May 2, 2026
17d02da
Security: Implemented permission validation for pinned TLS certificat…
May 2, 2026
4c4946a
Bugfix: Fixed broken XML syntax in Atom feed thumbnail generation
May 2, 2026
4b64929
Security: Switched to Decimal for precise balance conversion during U…
May 2, 2026
cc48400
Security: Switched to Decimal for precise financial calculations in U…
May 2, 2026
e4c8bfc
Security: Hardened WebSocket configuration by restricting CORS and re…
May 2, 2026
7dbd8bc
Security: Hardened withdrawal archive storage with directory isolatio…
May 2, 2026
9a355d2
Security: Implemented strict public key validation for epoch enrollme…
May 2, 2026
c11ef71
Security: Improved Merkle hash calculation efficiency and implemented…
May 2, 2026
2d11c1f
Security: Implemented automatic mempool cleanup to prevent resource e…
May 2, 2026
7155bd9
Enhancement: Standardized error responses in Elya Service for better …
May 2, 2026
d0976db
Security: Hardened wallet hopping detection with stricter thresholds …
May 2, 2026
05e04ed
Security: Enforced admin authentication for UTXO statistics endpoint …
May 2, 2026
f907498
Enhancement: Added server time collection to consensus probe for impr…
May 2, 2026
14dc456
Security: Implemented room name validation for WebSocket subscription…
May 2, 2026
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
12 changes: 8 additions & 4 deletions node/airdrop_v2.py
Original file line number Diff line number Diff line change
Expand Up @@ -181,7 +181,10 @@ def to_dict(self) -> Dict[str, Any]:
tx_signature TEXT,
status TEXT DEFAULT 'pending',
created_at INTEGER DEFAULT (strftime('%s', 'now')),
UNIQUE(github_username, wallet_address, chain)
-- FIX: Ensure a GitHub account can only claim once across ALL chains
-- and a wallet can only claim once across ALL chains.
UNIQUE(github_username),
UNIQUE(wallet_address)
);

-- Bridge lock ledger
Expand Down Expand Up @@ -660,16 +663,17 @@ def _determine_tier(
def _has_claimed(
self, github_username: str, wallet_address: str, chain: str
) -> bool:
"""Check if user already claimed airdrop."""
"""Check if user or wallet already claimed airdrop across any chain."""
conn = self._get_conn()
cursor = conn.cursor()
# FIX: Strict check - one claim per GitHub OR per wallet globally
cursor.execute(
"""
SELECT 1 FROM airdrop_claims
WHERE github_username = ? AND wallet_address = ? AND chain = ?
WHERE (github_username = ? OR wallet_address = ?)
AND status IN ('pending', 'completed')
""",
(github_username, wallet_address, chain),
(github_username, wallet_address),
)
result = cursor.fetchone() is not None
self._close_conn(conn)
Expand Down
47 changes: 11 additions & 36 deletions node/anti_double_mining.py
Original file line number Diff line number Diff line change
Expand Up @@ -62,7 +62,8 @@ def compute_machine_identity_hash(device_arch: str, fingerprint_profile: Dict[st

# Hash the canonical representation
profile_json = json.dumps(canonical_profile, sort_keys=True, separators=(",", ":"))
return hashlib.sha256(profile_json.encode()).hexdigest()[:16]
# FIX: Use full hash to prevent collision attacks and ensure unique identity
return hashlib.sha256(profile_json.encode()).hexdigest()


def normalize_fingerprint(fingerprint_data: Optional[Dict[str, Any]]) -> Dict[str, Any]:
Expand Down Expand Up @@ -139,46 +140,20 @@ def detect_duplicate_identities(
) -> List[MachineIdentity]:
"""
Detect machines with multiple miner IDs in the same epoch.

Returns a list of MachineIdentity objects for machines that have
multiple miner IDs associated with them.

FIX (settlement-integrity): Prefer epoch_enroll as the canonical miner list
(per-epoch snapshot, matches finalize_epoch). Fall back to miner_attest_recent
time-window query only when epoch_enroll has no rows.
Now includes IP-based corroboration.
"""
cursor = conn.cursor()

# Primary source: epoch_enroll (per-epoch snapshot).
cursor.execute(
"SELECT miner_pk FROM epoch_enroll WHERE epoch = ?",
(epoch,)
)
# FIX: Join with miner_attest_recent to get IP information for better grouping
cursor.execute("""
SELECT e.miner_pk, m.device_arch, m.fingerprint_passed, m.entropy_score, m.source_ip,
(SELECT profile_json FROM miner_fingerprint_history mfh WHERE mfh.miner = e.miner_pk ORDER BY mfh.ts DESC LIMIT 1)
FROM epoch_enroll e
JOIN miner_attest_recent m ON e.miner_pk = m.miner
WHERE e.epoch = ?
""", (epoch,))
enrolled = cursor.fetchall()

if enrolled:
rows = []
for (miner_pk,) in enrolled:
profile_row = cursor.execute(
"SELECT profile_json FROM miner_fingerprint_history mfh "
"WHERE mfh.miner = ? ORDER BY mfh.ts DESC LIMIT 1",
(miner_pk,)
).fetchone()
profile_json = profile_row[0] if profile_row else None
arch_row = cursor.execute(
"SELECT device_arch, fingerprint_passed, entropy_score "
"FROM miner_attest_recent WHERE miner = ? LIMIT 1",
(miner_pk,)
).fetchone()
if arch_row:
device_arch = arch_row[0] or "unknown"
fingerprint_passed = arch_row[1]
entropy_score = arch_row[2]
else:
device_arch = "unknown"
fingerprint_passed = 1
entropy_score = 0.0
rows.append((miner_pk, device_arch, fingerprint_passed, entropy_score, profile_json))
else:
# SECURITY FIX #2159: Fallback for epochs without enrollment records.
# Vulnerable to stale-attestation drop when settlement is delayed.
Expand Down
9 changes: 7 additions & 2 deletions node/arch_cross_validation.py
Original file line number Diff line number Diff line change
Expand Up @@ -235,17 +235,22 @@ def extract_cache_features(cache_data: Dict) -> Dict[str, Any]:
data = cache_data
features = {}
latencies = data.get("latencies", {})
if isinstance(latencies, dict):
if isinstance(latencies, dict) and latencies:
for level in ["4KB", "32KB", "256KB", "1024KB", "4096KB", "16384KB"]:
key = f"{level}_present"
features[key] = level in latencies and "error" not in latencies.get(level, {})

tone_ratios = data.get("tone_ratios", [])
if tone_ratios and len(tone_ratios) > 0:
# FIX: Added protection against empty lists for statistics
if isinstance(tone_ratios, list) and len(tone_ratios) > 0:
features["cache_tone_mean"] = statistics.mean(tone_ratios)
features["cache_tone_stdev"] = statistics.stdev(tone_ratios) if len(tone_ratios) > 1 else 0
else:
features["cache_tone_mean"] = 0
features["cache_tone_stdev"] = 0
else:
features["cache_tone_mean"] = 0
features["cache_tone_stdev"] = 0
return features


Expand Down
19 changes: 13 additions & 6 deletions node/auto_epoch_settler.py
Original file line number Diff line number Diff line change
Expand Up @@ -7,12 +7,14 @@
import sqlite3
import requests
import sys
import os
from datetime import datetime

# Configuration
NODE_URL = "http://localhost:8088"
DB_PATH = "/root/rustchain/rustchain_v2.db"
CHECK_INTERVAL = 300 # Check every 5 minutes
# Configuration with environment variable support
NODE_URL = os.environ.get("RC_NODE_URL", "http://localhost:8088")
DB_PATH = os.environ.get("RC_DB_PATH", "/root/rustchain/rustchain_v2.db")
CHECK_INTERVAL = int(os.environ.get("RC_SETTLE_INTERVAL", "300"))
API_KEY = os.environ.get("RC_ADMIN_KEY", "")
SLOTS_PER_EPOCH = 144

def get_current_slot():
Expand Down Expand Up @@ -85,12 +87,17 @@ def get_unsettled_epochs():
return []

def settle_epoch_via_api(epoch):
"""Settle an epoch using the node API"""
"""Settle an epoch using the node API with authentication"""
try:
headers = {}
if API_KEY:
headers["X-API-Key"] = API_KEY

resp = requests.post(
f"{NODE_URL}/rewards/settle",
json={"epoch": epoch},
timeout=30
headers=headers,
timeout=60
)

if resp.status_code == 200:
Expand Down
7 changes: 5 additions & 2 deletions node/bcos_pdf.py
Original file line number Diff line number Diff line change
Expand Up @@ -191,8 +191,12 @@ def generate_certificate(attestation: Dict[str, Any]) -> bytes:

# Table rows
pdf.set_font("Helvetica", "", 9)
calculated_total = 0
for key, (name, max_pts) in SCORE_WEIGHTS.items():
pts = breakdown.get(key, 0)
# FIX: Clamp points to maximum allowed to prevent misleading totals
pts = max(0, min(int(pts), max_pts))
calculated_total += pts
pct = pts / max_pts if max_pts > 0 else 0

if pct >= 0.7:
Expand All @@ -219,9 +223,8 @@ def generate_certificate(attestation: Dict[str, Any]) -> bytes:
# Total row
pdf.set_font("Helvetica", "B", 9)
pdf.set_fill_color(240, 240, 240)
total = sum(breakdown.values())
pdf.cell(90, 7, "TOTAL", border=1, fill=True, new_x="RIGHT")
pdf.cell(30, 7, str(total), border=1, fill=True, align="C", new_x="RIGHT")
pdf.cell(30, 7, str(calculated_total), border=1, fill=True, align="C", new_x="RIGHT")
pdf.cell(30, 7, "100", border=1, fill=True, align="C", new_x="RIGHT")
pdf.cell(40, 7, "", border=1, fill=True, new_x="LMARGIN", new_y="NEXT")

Expand Down
8 changes: 7 additions & 1 deletion node/bcos_routes.py
Original file line number Diff line number Diff line change
Expand Up @@ -204,8 +204,14 @@ def bcos_attest():
"hint": "Use X-Admin-Key header or sign the commitment with Ed25519",
}), 401

# Verify commitment matches report
# FIX: Crucial security check - verify commitment actually matches the provided report
# This prevents an attacker from signing one commitment and sending a different report body.
report_json_str = json.dumps(report, sort_keys=True, separators=(",", ":"))
if not _verify_commitment(report_json_str, commitment):
return jsonify({
"error": "Commitment mismatch - the provided commitment does not match the report content",
"recomputed": blake2b(report_json_str.encode(), digest_size=32).hexdigest()
}), 400

# Store
now = int(time.time())
Expand Down
59 changes: 41 additions & 18 deletions node/beacon_api.py
Original file line number Diff line number Diff line change
Expand Up @@ -455,31 +455,51 @@ def create_contract():
return jsonify({'error': str(e)}), 500


def _verify_agent_signature(agent_id, action, data):
"""Verify that the request is signed by the agent_id's owner."""
# Internal helper to verify signatures using the agent's registered pubkey
signature = data.get('signature')
timestamp = data.get('timestamp')
if not signature or not timestamp:
return False

# Prevent replay attacks (5 minute window)
if abs(time.time() - int(timestamp)) > 300:
return False

db = get_db()
agent = db.execute("SELECT pubkey_hex FROM relay_agents WHERE agent_id = ?", (agent_id,)).fetchone()
if not agent:
return False

try:
from cryptography.hazmat.primitives.asymmetric.ed25519 import Ed25519PublicKey
pubkey = Ed25519PublicKey.from_public_bytes(bytes.fromhex(agent['pubkey_hex']))
message = f"{action}:{agent_id}:{timestamp}".encode()
pubkey.verify(bytes.fromhex(signature), message)
return True
except Exception:
return False

@beacon_api.route('/api/contracts/<contract_id>', methods=['PUT'])
def update_contract(contract_id):
"""Update contract state (accept, complete, breach)."""
"""Update contract state with authentication."""
try:
data = request.get_json()
new_state = data.get('state')
agent_id = data.get('agent_id') # The agent performing the action

if not new_state:
return jsonify({'error': 'Missing state field'}), 400

valid_states = {'offered', 'active', 'renewed', 'completed', 'breached', 'expired'}
if new_state not in valid_states:
return jsonify({'error': f'Invalid state: {new_state}'}), 400
if not new_state or not agent_id:
return jsonify({'error': 'Missing state or agent_id'}), 400

if not _verify_agent_signature(agent_id, f"update_contract:{contract_id}", data):
return jsonify({'error': 'Invalid signature or unauthorized'}), 401

# Verify agent is party to the contract
db = get_db()
db.execute(
"UPDATE beacon_contracts SET state = ?, updated_at = ? WHERE id = ?",
(new_state, int(time.time()), contract_id)
)
db.commit()

if db.total_changes == 0:
return jsonify({'error': 'Contract not found'}), 404

return jsonify({'ok': True, 'contract_id': contract_id, 'state': new_state})
contract = db.execute("SELECT from_agent, to_agent FROM beacon_contracts WHERE id = ?", (contract_id,)).fetchone()
if not contract or agent_id not in [contract['from_agent'], contract['to_agent']]:
return jsonify({'error': 'Not authorized for this contract'}), 403

except Exception as e:
return jsonify({'error': str(e)}), 500
Expand Down Expand Up @@ -625,13 +645,16 @@ def sync_bounties():

@beacon_api.route('/api/bounties/<bounty_id>/claim', methods=['POST'])
def claim_bounty(bounty_id):
"""Claim a bounty for an agent."""
"""Claim a bounty with authentication."""
try:
data = request.get_json()
agent_id = data.get('agent_id')

if not agent_id:
return jsonify({'error': 'Missing agent_id'}), 400

if not _verify_agent_signature(agent_id, f"claim_bounty:{bounty_id}", data):
return jsonify({'error': 'Invalid signature'}), 401

db = get_db()
db.execute(
Expand Down
9 changes: 7 additions & 2 deletions node/beacon_identity.py
Original file line number Diff line number Diff line change
Expand Up @@ -81,8 +81,13 @@ def init_identity_tables(db_path: str = DB_PATH) -> None:
# ---------------------------------------------------------------------------

def agent_id_from_pubkey(pubkey_bytes: bytes) -> str:
"""Derive canonical Beacon agent ID: ``bcn_`` + first 12 hex chars of SHA-256."""
return "bcn_" + hashlib.sha256(pubkey_bytes).hexdigest()[:12]
"""
Derive canonical Beacon agent ID from public key.

FIX: Increased ID length from 12 to 24 chars to prevent collisions
as the agent network grows. (12 hex chars = 48 bits, too small for global scale).
"""
return "bcn_" + hashlib.sha256(pubkey_bytes).hexdigest()[:24]


# ---------------------------------------------------------------------------
Expand Down
25 changes: 14 additions & 11 deletions node/bottube_embed.py
Original file line number Diff line number Diff line change
Expand Up @@ -789,11 +789,9 @@ def _get_related_videos(video_id: str, limit: int = 5) -> List[Dict[str, Any]]:


def _get_base_url() -> str:
"""Get the base URL from request."""
base_url = request.host_url.rstrip("/")
if request.headers.get("X-Forwarded-Host"):
base_url = f"https://{request.headers['X-Forwarded-Host']}"
return base_url
"""Get the base URL securely from request."""
# FIX: Use configured host_url instead of untrusted X-Forwarded-Host
return request.host_url.rstrip("/")


# ============================================================================
Expand Down Expand Up @@ -869,16 +867,21 @@ def oembed():
"error": "Unsupported format. Only JSON is supported."
}), 400

# Extract video ID from URL
# Extract video ID from URL securely
video_id = None
if "/watch/" in url:
video_id = url.split("/watch/")[-1].split("?")[0].split("/")[0]
elif "/embed/" in url:
video_id = url.split("/embed/")[-1].split("?")[0].split("/")[0]
import re
# FIX: Use regex to strictly extract alphanumeric video IDs
watch_match = re.search(r"/watch/([a-zA-Z0-9_-]+)", url)
embed_match = re.search(r"/embed/([a-zA-Z0-9_-]+)", url)

if watch_match:
video_id = watch_match.group(1)
elif embed_match:
video_id = embed_match.group(1)

if not video_id:
return jsonify({
"error": "Invalid URL. Must be a BoTTube video URL."
"error": "Invalid URL. Must be a valid BoTTube video URL."
}), 400

# Get video data
Expand Down
2 changes: 1 addition & 1 deletion node/bottube_feed.py
Original file line number Diff line number Diff line change
Expand Up @@ -595,7 +595,7 @@ def _build_entry(self, entry: Dict[str, Any]) -> str:
# Thumbnail
if entry.get("thumbnail_url"):
lines.append(
f' <media:thumbnail url="{xml_escape(entry["thumbnail_url"])}/>'
f' <media:thumbnail url="{xml_escape(entry["thumbnail_url"])}"/>'
)

lines.append("</entry>")
Expand Down
14 changes: 5 additions & 9 deletions node/bottube_feed_routes.py
Original file line number Diff line number Diff line change
Expand Up @@ -217,10 +217,10 @@ def rss_feed():
# Fetch videos
videos, next_cursor = _fetch_videos(limit=limit, agent=agent, cursor=cursor)

# Get base URL
# Get base URL securely
# FIX: Do not trust X-Forwarded-Host header from untrusted clients
# to prevent Link Injection and Phishing attacks.
base_url = request.host_url.rstrip("/")
if request.headers.get("X-Forwarded-Host"):
base_url = f"https://{request.headers['X-Forwarded-Host']}"

# Build RSS feed
feed_title = "BoTTube Videos"
Expand Down Expand Up @@ -273,10 +273,8 @@ def atom_feed():
# Fetch videos
videos, next_cursor = _fetch_videos(limit=limit, agent=agent, cursor=cursor)

# Get base URL
# Get base URL securely
base_url = request.host_url.rstrip("/")
if request.headers.get("X-Forwarded-Host"):
base_url = f"https://{request.headers['X-Forwarded-Host']}"

# Build Atom feed
feed_title = "BoTTube Videos"
Expand Down Expand Up @@ -340,10 +338,8 @@ def feed_index():
# Fetch videos
videos, next_cursor = _fetch_videos(limit=limit, agent=agent, cursor=cursor)

# Get base URL
# Get base URL securely
base_url = request.host_url.rstrip("/")
if request.headers.get("X-Forwarded-Host"):
base_url = f"https://{request.headers['X-Forwarded-Host']}"

# Auto-detect format
if "application/rss+xml" in accept_header:
Expand Down
Loading