Skip to content
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
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: 9 additions & 3 deletions agent_reputation.py
Original file line number Diff line number Diff line change
Expand Up @@ -331,11 +331,14 @@ def check_eligibility():
Returns whether an agent is eligible to claim a job of given value.
"""
agent_id = request.args.get("agent_id", "").strip()
job_value = float(request.args.get("job_value", 0))

if not agent_id:
return jsonify({"error": "agent_id required"}), 400

try:
job_value = float(request.args.get("job_value", 0))
except (ValueError, TypeError):
return jsonify({"error": "job_value must be a number"}), 400

rep = _engine.get(agent_id)
max_val = rep["max_job_value_rtc"]
level = rep["level"]
Expand Down Expand Up @@ -364,7 +367,10 @@ def leaderboard():
GET /agent/reputation/leaderboard?limit=20
Returns top agents by reputation (from cache).
"""
limit = min(int(request.args.get("limit", 20)), 100)
try:
limit = min(int(request.args.get("limit", 20)), 100)
except (ValueError, TypeError):
return jsonify({"error": "limit must be an integer"}), 400
with _engine._lock:
entries = [(w, d["reputation_score"]) for w, (d, _) in _engine._cache.items()]
entries.sort(key=lambda x: x[1], reverse=True)
Expand Down
11 changes: 8 additions & 3 deletions explorer/explorer_websocket_server.py
Original file line number Diff line number Diff line change
Expand Up @@ -16,7 +16,7 @@

Integration:
from explorer_websocket_server import socketio, app, start_explorer_poller
socketio.init_app(app, cors_allowed_origins="*", async_mode="threading")
socketio.init_app(app, cors_allowed_origins=os.environ.get("WS_ALLOWED_ORIGINS", "http://localhost,http://127.0.0.1").split(","), async_mode="threading")
start_explorer_poller()

Author: RustChain Team
Expand Down Expand Up @@ -286,14 +286,19 @@ def start_explorer_poller():

# ─── Flask App ──────────────────────────────────────────────────────────────── #
app = Flask(__name__)
app.config['SECRET_KEY'] = os.environ.get('SECRET_KEY', 'rustchain-explorer-secret')
secret_key = os.environ.get('SECRET_KEY')
if not secret_key:
import secrets
secret_key = secrets.token_hex(32)
print(f"[WARNING] Using auto-generated SECRET_KEY. Set SECRET_KEY env var for production.")
app.config['SECRET_KEY'] = secret_key

# ─── Flask Blueprint ────────────────────────────────────────────────────────── #
ws_bp = Blueprint("explorer_ws", __name__)

if HAVE_SOCKETIO:
socketio = SocketIO(
cors_allowed_origins="*",
cors_allowed_origins=os.environ.get("WS_ALLOWED_ORIGINS", "http://localhost,http://127.0.0.1").split(","),
async_mode="threading",
ping_timeout=HEARTBEAT_S,
ping_interval=HEARTBEAT_S,
Expand Down
9 changes: 7 additions & 2 deletions explorer/realtime_server.py
Original file line number Diff line number Diff line change
Expand Up @@ -22,8 +22,13 @@

# Flask app with SocketIO
app = Flask(__name__)
app.config['SECRET_KEY'] = os.environ.get('SECRET_KEY', 'rustchain-explorer-secret')
socketio = SocketIO(app, cors_allowed_origins="*", async_mode='threading')
secret_key = os.environ.get('SECRET_KEY')
if not secret_key:
import secrets
secret_key = secrets.token_hex(32)
print(f"[WARNING] Using auto-generated SECRET_KEY. Set SECRET_KEY env var for production.")
app.config['SECRET_KEY'] = secret_key
socketio = SocketIO(app, cors_allowed_origins=os.environ.get("WS_ALLOWED_ORIGINS", "http://localhost,http://127.0.0.1").split(","), async_mode='threading')

# State tracking
class ExplorerState:
Expand Down
9 changes: 7 additions & 2 deletions explorer/ws_explorer_server.py
Original file line number Diff line number Diff line change
Expand Up @@ -24,8 +24,13 @@
PORT = int(os.environ.get("WS_EXPLORER_PORT", "8060"))

app = Flask(__name__, template_folder="templates", static_folder="static")
app.config["SECRET_KEY"] = os.environ.get("SECRET_KEY", "rustchain-ws-explorer")
socketio = SocketIO(app, cors_allowed_origins="*", async_mode="threading")
secret_key = os.environ.get("SECRET_KEY")
if not secret_key:
import secrets
secret_key = secrets.token_hex(32)
print(f"[WARNING] Using auto-generated SECRET_KEY. Set SECRET_KEY env var for production.")
app.config["SECRET_KEY"] = secret_key
socketio = SocketIO(app, cors_allowed_origins=os.environ.get("WS_ALLOWED_ORIGINS", "http://localhost,http://127.0.0.1").split(","), async_mode="threading")

# ── State ─────────────────────────────────────────────────────────
state = {
Expand Down
5 changes: 4 additions & 1 deletion faucet.py
Original file line number Diff line number Diff line change
Expand Up @@ -305,7 +305,10 @@ def drip():
Response:
{"ok": true, "amount": 0.5, "next_available": "2026-03-08T12:00:00Z"}
"""
data = request.get_json()
try:
data = request.get_json(silent=True)
except Exception:
return jsonify({'ok': False, 'error': 'Invalid JSON'}), 400

if not data or 'wallet' not in data:
return jsonify({'ok': False, 'error': 'Wallet address required'}), 400
Expand Down
24 changes: 24 additions & 0 deletions issue2307_boot_chime/boot_chime_api.py
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@
"""

from flask import Flask, request, jsonify, send_file
from werkzeug.utils import secure_filename
from flask_cors import CORS
import json
import os
Expand Down Expand Up @@ -158,6 +159,15 @@ def submit_proof():
audio_data = None
if 'audio' in request.files:
audio_file = request.files['audio']
# Security fix: validate filename and file type
filename = secure_filename(audio_file.filename or '')
if not filename.lower().endswith(('.wav', '.mp3', '.flac', '.ogg')):
return jsonify({'error': 'Invalid file type. Only audio files allowed.'}), 400
# Security fix: limit file size (max 10MB)
audio_file.seek(0, 2)
if audio_file.tell() > 10 * 1024 * 1024:
return jsonify({'error': 'File too large. Max size: 10MB.'}), 413
audio_file.seek(0)
with tempfile.NamedTemporaryFile(delete=False, suffix='.wav') as tmp:
audio_file.save(tmp)
tmp_path = tmp.name
Expand Down Expand Up @@ -238,6 +248,13 @@ def enroll_miner():
audio_file = None
if 'audio' in request.files:
audio = request.files['audio']
filename = secure_filename(audio.filename or '')
if not filename.lower().endswith(('.wav', '.mp3', '.flac', '.ogg')):
return jsonify({'error': 'Invalid file type. Only audio files allowed.'}), 400
audio.seek(0, 2)
if audio.tell() > 10 * 1024 * 1024:
return jsonify({'error': 'File too large. Max size: 10MB.'}), 413
audio.seek(0)
with tempfile.NamedTemporaryFile(delete=False, suffix='.wav') as tmp:
audio.save(tmp)
audio_file = tmp.name
Expand Down Expand Up @@ -413,6 +430,13 @@ def analyze_audio():
return jsonify({'error': 'audio file required'}), 400

audio_file = request.files['audio']
filename = secure_filename(audio_file.filename or '')
if not filename.lower().endswith(('.wav', '.mp3', '.flac', '.ogg')):
return jsonify({'error': 'Invalid file type. Only audio files allowed.'}), 400
audio_file.seek(0, 2)
if audio_file.tell() > 10 * 1024 * 1024:
return jsonify({'error': 'File too large. Max size: 10MB.'}), 413
audio_file.seek(0)

with tempfile.NamedTemporaryFile(delete=False, suffix='.wav') as tmp:
audio_file.save(tmp)
Expand Down
4 changes: 2 additions & 2 deletions issue2307_boot_chime/src/proof_of_iron.py
Original file line number Diff line number Diff line change
Expand Up @@ -499,7 +499,7 @@ def _save_features(self, features_hash: str,
conn = sqlite3.connect(self.db_path)
c = conn.cursor()

features_data = pickle.dumps({
features_data = json.dumps({
'mfcc_mean': features.mfcc_mean.tolist(),
'mfcc_std': features.mfcc_std.tolist(),
'spectral_centroid': features.spectral_centroid,
Expand Down Expand Up @@ -536,7 +536,7 @@ def _load_features(self, features_hash: str) -> Optional[FingerprintFeatures]:
conn.close()

if row:
data = pickle.loads(row[0])
data = json.loads(row[0])
return FingerprintFeatures(
mfcc_mean=np.array(data['mfcc_mean']),
mfcc_std=np.array(data['mfcc_std']),
Expand Down
3 changes: 2 additions & 1 deletion node/governance.py
Original file line number Diff line number Diff line change
Expand Up @@ -24,6 +24,7 @@
"""

import hashlib
import hmac
import json
import logging
import sqlite3
Expand Down Expand Up @@ -553,7 +554,7 @@ def founder_veto(proposal_id: int):
# Admin key is validated via environment variable (not hardcoded)
import os
expected_key = os.environ.get("RUSTCHAIN_ADMIN_KEY", "")
if not expected_key or admin_key != expected_key:
if not expected_key or not hmac.compare_digest(admin_key, expected_key):
return jsonify({"error": "invalid admin_key"}), 403

try:
Expand Down
2 changes: 1 addition & 1 deletion node/rustchain_sync_endpoints.py
Original file line number Diff line number Diff line change
Expand Up @@ -92,7 +92,7 @@ def require_admin(f):
@wraps(f)
def decorated(*args, **kwargs):
key = request.headers.get("X-Admin-Key") or request.headers.get("X-API-Key")
if not key or key != admin_key:
if not key or not hmac.compare_digest(key, admin_key):
return jsonify({"error": "Unauthorized"}), 401
return f(*args, **kwargs)

Expand Down
2 changes: 1 addition & 1 deletion node/websocket_feed.py
Original file line number Diff line number Diff line change
Expand Up @@ -150,7 +150,7 @@ def init_app(self, app: Flask):
self.app = app
self.socketio = SocketIO(
app,
cors_allowed_origins="*",
cors_allowed_origins=os.environ.get("WS_ALLOWED_ORIGINS", "http://localhost,http://127.0.0.1").split(","),
async_mode='threading',
ping_timeout=60,
ping_interval=25,
Expand Down
2 changes: 2 additions & 0 deletions rips/python/rustchain/deep_entropy.py
Original file line number Diff line number Diff line change
Expand Up @@ -484,6 +484,8 @@ def _verify_quirk_layer(
if result.get("detected") and result.get("confidence", 0) > 0.8:
detected += 1

if not profile.expected_quirks:
return 0.0
return detected / len(profile.expected_quirks)


Expand Down
27 changes: 15 additions & 12 deletions rips/python/rustchain/node.py
Original file line number Diff line number Diff line change
Expand Up @@ -161,18 +161,21 @@ def _process_block(self):
block = self.poa.process_block(previous_hash)

if block:
self.blocks.append(block)

# Update wallet balances
for miner in block.miners:
wallet_addr = miner.wallet.address
if wallet_addr not in self.wallets:
self.wallets[wallet_addr] = TokenAmount(0)
self.wallets[wallet_addr] += miner.reward

# Update totals
self.total_minted += block.total_reward
self.mining_pool -= block.total_reward
# Fix: Protect all state mutations with lock to prevent race conditions
# API endpoints read wallets/total_minted/mining_pool concurrently
with self.lock:
self.blocks.append(block)

# Update wallet balances
for miner in block.miners:
wallet_addr = miner.wallet.address
if wallet_addr not in self.wallets:
self.wallets[wallet_addr] = TokenAmount(0)
self.wallets[wallet_addr] += miner.reward

# Update totals
self.total_minted += block.total_reward
self.mining_pool -= block.total_reward

print(f"⛏️ Block #{block.height} processed")

Expand Down
12 changes: 12 additions & 0 deletions rips/rustchain-core/ledger/utxo_ledger.py
Original file line number Diff line number Diff line change
Expand Up @@ -459,6 +459,18 @@ def transfer(

Selects UTXOs to cover amount + fee, creates change output.
"""
# Security: validate inputs
if amount <= 0:
raise ValueError("Transfer amount must be positive")
if fee < 0:
raise ValueError("Fee cannot be negative")
if not from_address or not to_address:
raise ValueError("Addresses cannot be empty")
if from_address == to_address:
raise ValueError("Cannot transfer to same address")
if fee > amount:
raise ValueError("Fee cannot exceed transfer amount")

boxes = self._utxo_set.get_boxes_for_address(from_address)
available = sum(b.value for b in boxes)

Expand Down
6 changes: 3 additions & 3 deletions rips/rustchain-core/validator/entropy.py
Original file line number Diff line number Diff line change
Expand Up @@ -194,8 +194,8 @@ def fingerprint_cpu() -> EntropySource:
elapsed = time.perf_counter_ns() - start
timing_samples.append(elapsed)

data["timing_mean"] = sum(timing_samples) / len(timing_samples)
data["timing_variance"] = sum((t - data["timing_mean"])**2 for t in timing_samples) / len(timing_samples)
data["timing_mean"] = sum(timing_samples) / len(timing_samples) if timing_samples else 0.0
data["timing_variance"] = sum((t - data["timing_mean"])**2 for t in timing_samples) / len(timing_samples) if timing_samples else 0.0

# Hash the data
fingerprint = hashlib.sha256(str(data).encode()).hexdigest()
Expand Down Expand Up @@ -787,7 +787,7 @@ def check_drift(self, validator_id: str, new_profile: EntropyProfile) -> List[Dr
if old_hash and new_hash and old_hash != new_hash:
# Calculate drift percentage (simplified - hash difference)
diff_chars = sum(1 for a, b in zip(old_hash, new_hash) if a != b)
drift_pct = (diff_chars / len(old_hash)) * 100
drift_pct = (diff_chars / len(old_hash)) * 100 if old_hash else 0.0

if drift_pct > 0:
event = DriftEvent(
Expand Down
4 changes: 3 additions & 1 deletion rips/rustchain-core/validator/score.py
Original file line number Diff line number Diff line change
Expand Up @@ -234,7 +234,9 @@ def record_score(self, wallet: str, score: float):

# Update baseline (rolling average)
if len(self._history[wallet]) >= 10:
self._baselines[wallet] = sum(self._history[wallet]) / len(self._history[wallet])
history = self._history[wallet]
if history:
self._baselines[wallet] = sum(history) / len(history)

def check_drift(self, wallet: str, current_score: float) -> DriftRecord:
"""
Expand Down
Loading