Skip to content
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
49 commits
Select commit Hold shift + click to select a range
05b4762
update battleship: add A18 + exhausted cells + PR tracking
waefrebeorn May 25, 2026
5160879
battleship: add A19 (beacon_api field lengths -> PR #6262)
waefrebeorn May 25, 2026
0e12446
battleship: add A20 (bridge address lengths -> PR #6263)
waefrebeorn May 25, 2026
4b9683c
battleship: add A21 (airdrop field lengths -> PR #6264)
waefrebeorn May 25, 2026
f9ce59b
battleship: add A22 (lock_ledger -> PR #6265)
waefrebeorn May 25, 2026
c02eefa
battleship: add A23 (sync endpoints -> PR #6266)
waefrebeorn May 25, 2026
b9388aa
fix: cap unbounded string fields in bridge/bridge_api.py (A24)
waefrebeorn May 25, 2026
2832c96
docs: add A24 to BATTLESHIP_PROGRESS
waefrebeorn May 25, 2026
1c9b2dc
fix: cap wallet address in faucet.py (A25)
waefrebeorn May 25, 2026
e8300bd
docs: add A25 to BATTLESHIP_PROGRESS
waefrebeorn May 25, 2026
914245d
fix: cap miner_id in sophia_api.py (A26)
waefrebeorn May 25, 2026
b78ae6a
docs: add A26 to BATTLESHIP_PROGRESS
waefrebeorn May 25, 2026
7af5c79
fix: cap unbounded path params in explorer (A27/A29) and p2p (A28)
waefrebeorn May 25, 2026
36d0d6a
docs: add A27-A29 to BATTLESHIP_PROGRESS
waefrebeorn May 25, 2026
70d458d
fix: cap string fields in testnet faucet and rent-a-relic (A30-A31)
waefrebeorn May 25, 2026
d6060c8
docs: add A30-A31 to BATTLESHIP_PROGRESS
waefrebeorn May 25, 2026
48ff7ce
fix: cap address/search in explorer-api (A32-A33)
waefrebeorn May 25, 2026
ace2a62
docs: add A32-A33 to BATTLESHIP_PROGRESS
waefrebeorn May 25, 2026
29e80ee
fix: cap unbounded path params in 4 route files (A34-A37)
waefrebeorn May 25, 2026
fdefdd7
docs: add A34-A37 to BATTLESHIP_PROGRESS
waefrebeorn May 25, 2026
3702de5
fix: cap unbounded fields in contributor_registry and hall_of_rust (A…
waefrebeorn May 25, 2026
f182f1e
docs: add A38-A39 to BATTLESHIP_PROGRESS
waefrebeorn May 25, 2026
c8e41f4
fix: cap string fields in machine_passport_api.py (A40)
waefrebeorn May 25, 2026
f5c9173
docs: add A40 to BATTLESHIP_PROGRESS
waefrebeorn May 25, 2026
b2a729d
fix: cap agent_name in bottube_mood_engine.py (A41)
waefrebeorn May 25, 2026
45597b8
docs: add A41 to BATTLESHIP_PROGRESS
waefrebeorn May 25, 2026
c6180af
docs: expand grid to 400 cells, vault 56 accomplished, add Row S stub…
waefrebeorn May 25, 2026
c44b7ea
docs: S1 completed (PR #6286), next S2
waefrebeorn May 25, 2026
1bf19c1
docs: S3 done (PR #6287), S2 removed (false positive-tests only)
waefrebeorn May 25, 2026
f277aa2
docs: mark S4-S7, S13, S17-S20 done on battleship board
waefrebeorn May 25, 2026
6a56b42
docs: mark S8-S12 done on battleship board
waefrebeorn May 25, 2026
208ba1e
docs: mark S26 done on board
waefrebeorn May 25, 2026
d1d68d9
docs: mark S15 done
waefrebeorn May 25, 2026
e509c37
docs: mark M5 done on battleship board
waefrebeorn May 25, 2026
ce8fb08
docs: vault 103 cells, rewrite battleship with 297 fresh gaps from co…
waefrebeorn May 25, 2026
bd32a60
docs: update M4-M6 to jaxint APPROVED (9 total approvals)
waefrebeorn May 25, 2026
845c774
docs: update board F1-F2 vaulted via #6312 (105/400 cells, 48 PRs)
waefrebeorn May 25, 2026
fa54bda
fix(telegram_bot): replace bare except:pass with logger.warning [F6 F7]
waefrebeorn May 25, 2026
66af3cc
docs: update board F8 vaulted via #6314 (108/400 cells, 50 PRs)
waefrebeorn May 25, 2026
cf94373
docs: add T1 test coverage vaulted via #6316
waefrebeorn May 25, 2026
39cdbb3
docs: add T2 test coverage vaulted via #6317 (109/400 cells, 51 PRs)
waefrebeorn May 25, 2026
28ed004
docs: add T3 test coverage vaulted via #6320 (110/400 cells, 52 PRs)
waefrebeorn May 25, 2026
d499109
docs: add T4 test coverage vaulted via #6324 (111/400 cells, 53 PRs)
waefrebeorn May 25, 2026
69a0d36
docs: add T6 test coverage vaulted via #6325 (112/400 cells, 54 PRs)
waefrebeorn May 25, 2026
a0aac59
docs: add T10 test coverage vaulted via #6326, update jaxint approval…
waefrebeorn May 25, 2026
79fc0db
test(claims_settlement): add 82 unit tests for claims batch settlemen…
waefrebeorn May 25, 2026
230520a
board: T12 vaulted (82 test, PR #6327), 114/400 vaulted
waefrebeorn May 25, 2026
0a8541a
docs: update bug bounty badge to 114/400
waefrebeorn May 25, 2026
fa1fd23
fix: replace bare except + add proper genesis validation (F20)
waefrebeorn May 25, 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
463 changes: 463 additions & 0 deletions BATTLESHIP_PROGRESS.md

Large diffs are not rendered by default.

906 changes: 461 additions & 445 deletions README.md

Large diffs are not rendered by default.

24 changes: 24 additions & 0 deletions bottube_mood_engine.py
Original file line number Diff line number Diff line change
Expand Up @@ -946,6 +946,12 @@ def _mood_service_unavailable(operation: str):
return jsonify({"error": "Mood service unavailable"}), 500


def _require_valid_agent_name(agent_name: str) -> Optional[tuple]:
if len(agent_name) > 128:
return jsonify({"error": "agent_name too long"}), 400
return None


@mood_bp.route("/<agent_name>/mood", methods=["GET"])
def get_agent_mood_endpoint(agent_name: str):
"""
Expand All @@ -956,6 +962,9 @@ def get_agent_mood_endpoint(agent_name: str):
Query Parameters:
include_stats - Include mood statistics (default: false)
"""
err = _require_valid_agent_name(agent_name)
if err:
return err
try:
engine = get_mood_engine()
include_stats = request.args.get("include_stats", "false").lower() == "true"
Expand Down Expand Up @@ -984,6 +993,9 @@ def record_mood_signal(agent_name: str):
value - Signal value data
weight - Optional signal weight (default: 1.0)
"""
err = _require_valid_agent_name(agent_name)
if err:
return err
try:
engine = get_mood_engine()
data, error = _get_json_object()
Expand Down Expand Up @@ -1014,6 +1026,9 @@ def generate_mood_title(agent_name: str):
Request Body:
topic - Video topic/theme
"""
err = _require_valid_agent_name(agent_name)
if err:
return err
try:
engine = get_mood_engine()
data, error = _get_json_object()
Expand Down Expand Up @@ -1044,6 +1059,9 @@ def generate_mood_comment(agent_name: str):
Request Body:
base_comment - Optional base comment to modify
"""
err = _require_valid_agent_name(agent_name)
if err:
return err
try:
engine = get_mood_engine()
data, error = _get_json_object(allow_empty=True)
Expand All @@ -1070,6 +1088,9 @@ def get_post_probability(agent_name: str):

Get probability of agent posting based on current mood.
"""
err = _require_valid_agent_name(agent_name)
if err:
return err
try:
engine = get_mood_engine()
probability = engine.get_post_probability(agent_name)
Expand All @@ -1093,6 +1114,9 @@ def get_mood_statistics_endpoint(agent_name: str):

Get mood statistics for an agent.
"""
err = _require_valid_agent_name(agent_name)
if err:
return err
try:
engine = get_mood_engine()
stats = engine.get_mood_statistics(agent_name)
Expand Down
20 changes: 11 additions & 9 deletions bridge/bridge_api.py
Original file line number Diff line number Diff line change
Expand Up @@ -194,7 +194,7 @@ def _json_object_body():
return data, None


def _clean_string_field(data, field_name, *, optional=False, lower=False):
def _clean_string_field(data, field_name, *, optional=False, lower=False, max_length=0):
value = data.get(field_name)
if value is None:
return None if optional else ""
Expand All @@ -205,6 +205,8 @@ def _clean_string_field(data, field_name, *, optional=False, lower=False):
value = value.lower()
if optional and not value:
return None
if max_length > 0 and len(value) > max_length:
raise ValueError(f"{field_name} exceeds maximum length of {max_length}")
return value


Expand Down Expand Up @@ -242,11 +244,11 @@ def lock_rtc():

# ── Validate inputs ──
try:
sender = _clean_string_field(data, "sender_wallet")
sender = _clean_string_field(data, "sender_wallet", max_length=128)
target_chain = _clean_string_field(data, "target_chain", lower=True)
target_wallet = _clean_string_field(data, "target_wallet")
tx_hash = _clean_string_field(data, "tx_hash", optional=True)
receipt_signature = _clean_string_field(data, "receipt_signature", optional=True, lower=True)
target_wallet = _clean_string_field(data, "target_wallet", max_length=128)
tx_hash = _clean_string_field(data, "tx_hash", optional=True, max_length=128)
receipt_signature = _clean_string_field(data, "receipt_signature", optional=True, lower=True, max_length=256)
except ValueError as exc:
return jsonify({"error": str(exc)}), 400

Expand Down Expand Up @@ -394,8 +396,8 @@ def confirm_lock():
return error_response
try:
lock_id = _clean_string_field(data, "lock_id")
proof_ref = _clean_string_field(data, "proof_ref")
notes = _clean_string_field(data, "notes", optional=True)
proof_ref = _clean_string_field(data, "proof_ref", max_length=256)
notes = _clean_string_field(data, "notes", optional=True, max_length=1024)
except ValueError as exc:
return jsonify({"error": str(exc)}), 400

Expand Down Expand Up @@ -460,8 +462,8 @@ def release_wrtc():
return error_response
try:
lock_id = _clean_string_field(data, "lock_id")
release_tx = _clean_string_field(data, "release_tx")
notes = _clean_string_field(data, "notes", optional=True)
release_tx = _clean_string_field(data, "release_tx", max_length=128)
notes = _clean_string_field(data, "notes", optional=True, max_length=1024)
except ValueError as exc:
return jsonify({"error": str(exc)}), 400

Expand Down
2 changes: 2 additions & 0 deletions contributor_registry.py
Original file line number Diff line number Diff line change
Expand Up @@ -314,6 +314,8 @@ def api_contributors():
def approve_contributor(username):
if not _contributor_admin_authorized():
abort(401)
if len(username) > 128:
abort(400, description="Username too long")

with db_connection() as conn:
conn.execute(
Expand Down
4 changes: 4 additions & 0 deletions explorer/app.py
Original file line number Diff line number Diff line change
Expand Up @@ -102,10 +102,14 @@ def get_network_stats():

@app.route('/miner/<miner_id>')
def miner_detail(miner_id):
if len(miner_id) > 128:
return render_template('404.html'), 404
return render_template('miner_detail.html', miner_id=miner_id)

@app.route('/api/miner/<miner_id>')
def get_miner_detail(miner_id):
if len(miner_id) > 128:
return jsonify({'error': 'Miner not found'}), 404
try:
response = requests.get(MINERS_ENDPOINT, timeout=5)
if response.status_code == 200:
Expand Down
2 changes: 2 additions & 0 deletions explorer/rustchain_dashboard.py
Original file line number Diff line number Diff line change
Expand Up @@ -746,6 +746,8 @@ def api_stats():
@app.route('/api/wallet/<wallet_address>')
def api_wallet_lookup(wallet_address):
"""Look up wallet balance and info"""
if len(wallet_address) > 128:
return jsonify({"error": "Invalid wallet address", "balance": 0}), 400
try:
with sqlite3.connect(DB_PATH) as conn:
# Get balance
Expand Down
3 changes: 3 additions & 0 deletions faucet.py
Original file line number Diff line number Diff line change
Expand Up @@ -388,6 +388,9 @@ def drip():

wallet = wallet_value.strip()

if len(wallet) > 128:
return jsonify({'ok': False, 'error': 'Wallet address too long'}), 400

# Basic wallet validation (accept Ethereum-style and native RTC wallets)
if not is_valid_wallet_address(wallet):
return jsonify({'ok': False, 'error': 'Invalid wallet address'}), 400
Expand Down
2 changes: 2 additions & 0 deletions health-dashboard/server.py
Original file line number Diff line number Diff line change
Expand Up @@ -455,6 +455,8 @@ def api_status():
@app.route('/api/history/<node_id>')
def api_history(node_id: str):
"""API endpoint for historical data (24 hours)"""
if len(node_id) > 128:
return jsonify({"error": "Invalid node_id", "history": []}), 400
conn = sqlite3.connect(DB_PATH)
conn.row_factory = sqlite3.Row
cursor = conn.cursor()
Expand Down
13 changes: 11 additions & 2 deletions node/beacon_x402.py
Original file line number Diff line number Diff line change
Expand Up @@ -104,13 +104,16 @@ def _json_object_body():
return data, None


def _json_string_field(data, field_name, default=""):
def _json_string_field(data, field_name, default="", max_length=0):
value = data.get(field_name, default)
if value is None:
return ""
if not isinstance(value, str):
raise ValueError(f"{field_name} must be a string")
return value.strip()
value = value.strip()
if max_length > 0 and len(value) > max_length:
raise ValueError(f"{field_name} exceeds maximum length of {max_length}")
return value


def _is_base_address(value: str) -> bool:
Expand Down Expand Up @@ -211,6 +214,9 @@ def set_agent_wallet(agent_id):
if request.method == "OPTIONS":
return _cors_json({"ok": True})

if len(agent_id) > 128:
return _cors_json({"error": "agent_id too long"}, 400)

# Simple admin check ? require admin key in header
admin_error = _require_beacon_admin()
if admin_error:
Expand Down Expand Up @@ -248,6 +254,9 @@ def get_agent_wallet(agent_id):
if request.method == "OPTIONS":
return _cors_json({"ok": True})

if len(agent_id) > 128:
return _cors_json({"error": "agent_id too long"}, 400)

db = get_db_func()

# Check beacon_wallets table (native agents)
Expand Down
6 changes: 6 additions & 0 deletions node/bottube_embed.py
Original file line number Diff line number Diff line change
Expand Up @@ -816,6 +816,8 @@ def embed_player(video_id: str):
Returns:
HTML page with embedded video player
"""
if len(video_id) > 256:
return Response("<html><body><h1>Invalid video ID</h1></body></html>", status=400, mimetype="text/html")
# Get video data
video = _get_mock_video(video_id)

Expand Down Expand Up @@ -861,6 +863,8 @@ def oembed():
JSON oEmbed response
"""
url = request.args.get("url", "")
if len(url) > 2048:
return jsonify({"error": "URL too long"}), 400
format_param = request.args.get("format", "json")
maxwidth = request.args.get("maxwidth", 854)
maxheight = request.args.get("maxheight", 480)
Expand Down Expand Up @@ -955,6 +959,8 @@ def watch_page(video_id: str):
Returns:
Full HTML watch page
"""
if len(video_id) > 256:
return Response("<html><body><h1>Invalid video ID</h1></body></html>", status=400, mimetype="text/html")
# Get video data
video = _get_mock_video(video_id)

Expand Down
13 changes: 11 additions & 2 deletions node/hall_of_rust.py
Original file line number Diff line number Diff line change
Expand Up @@ -165,6 +165,8 @@ def induct_machine():
# SECURITY FIX: Fingerprint based on HARDWARE ONLY (not wallet ID)
# This prevents multiple wallets on same machine from getting multiple Hall entries
hw_serial = data.get('cpu_serial', data.get('hardware_id', 'unknown'))
if not isinstance(hw_serial, str) or len(hw_serial) > 256:
hw_serial = 'unknown'
fp_data = f"{data.get('device_model', '')}{data.get('device_arch', '')}{hw_serial}"
fingerprint_hash = hashlib.sha256(fp_data.encode()).hexdigest()[:32]

Expand All @@ -180,8 +182,15 @@ def induct_machine():
existing = c.fetchone()

now = int(time.time())
model = data.get('device_model', 'Unknown')
arch = data.get('device_arch', 'modern')
miner_id = (data.get('miner_id') or 'anonymous')[:128]
model = (data.get('device_model', 'Unknown') or 'Unknown')[:256]
arch = (data.get('device_arch', 'modern') or 'modern')[:32]
device_family = (data.get('device_family', 'Unknown') or 'Unknown')[:128]

# Use defaults after truncation if empty
model = model or 'Unknown'
arch = arch or 'modern'
device_family = device_family or 'Unknown'

if existing:
# Update attestation count
Expand Down
27 changes: 21 additions & 6 deletions node/machine_passport_api.py
Original file line number Diff line number Diff line change
Expand Up @@ -284,6 +284,14 @@ def create_passport():
'message': "Field 'machine_id' is required or provide 'hardware_fingerprint'",
}), 400

# Cap string fields to prevent DB abuse
name = (data.get('name') or '')[:256]
owner_miner_id = (data.get('owner_miner_id') or '')[:128]
architecture = (data.get('architecture') or '')[:64] if data.get('architecture') else None
photo_hash = (data.get('photo_hash') or '')[:128] if data.get('photo_hash') else None
photo_url = (data.get('photo_url') or '')[:2048] if data.get('photo_url') else None
provenance = (data.get('provenance') or '')[:1024] if data.get('provenance') else None

ledger = get_ledger()

# Check if passport already exists
Expand All @@ -297,13 +305,13 @@ def create_passport():

passport = MachinePassport(
machine_id=machine_id,
name=data['name'],
owner_miner_id=data['owner_miner_id'],
name=name,
owner_miner_id=owner_miner_id,
manufacture_year=data.get('manufacture_year'),
architecture=data.get('architecture'),
photo_hash=data.get('photo_hash'),
photo_url=data.get('photo_url'),
provenance=data.get('provenance'),
architecture=architecture,
photo_hash=photo_hash,
photo_url=photo_url,
provenance=provenance,
)

success, msg = ledger.create_passport(passport)
Expand Down Expand Up @@ -350,6 +358,13 @@ def update_passport(machine_id: str):
'message': 'JSON body required',
}), 400

# Cap string fields to prevent DB abuse
for field in ('name', 'owner_miner_id', 'architecture', 'photo_hash', 'photo_url', 'provenance', 'machine_id'):
if field in data and isinstance(data[field], str):
max_len = {'machine_id': 128, 'name': 256, 'owner_miner_id': 128, 'architecture': 64,
'photo_hash': 128, 'photo_url': 2048, 'provenance': 1024}.get(field, 256)
data[field] = data[field][:max_len]

success, msg = ledger.update_passport(machine_id, data)

if success:
Expand Down
3 changes: 3 additions & 0 deletions node/rustchain_p2p_sync.py
Original file line number Diff line number Diff line change
Expand Up @@ -446,6 +446,9 @@ def announce_peer():

peer_url = peer_url.strip()

if len(peer_url) > 1024:
return jsonify({"ok": False, "error": "peer_url too long"}), 400

if peer_url:
success = peer_manager.add_peer(peer_url)
return jsonify({"ok": success, "peers": len(peer_manager.get_active_peers())})
Expand Down
Loading
Loading