Skip to content
Merged
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
7 changes: 7 additions & 0 deletions .env.example
Original file line number Diff line number Diff line change
Expand Up @@ -16,3 +16,10 @@ MAX_MEMORY_MB=512
MAX_FILE_SIZE_MB=1
MAX_OPEN_FILES=64
MAX_RUN_TIME=5

# Cheat Mode
# CHEAT_CODE must be the SHA-256 hex digest of your secret passphrase.
# The raw secret is NEVER stored here — only its hash.
# To generate: python3 -c "import hashlib; print(hashlib.sha256(b'YOUR_SECRET').hexdigest())"
# State is in-memory only and resets on server restart (safe by design).
CHEAT_CODE=<sha256-hex-of-your-secret>
2 changes: 2 additions & 0 deletions src/api/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@
from .routes.riddle_routes import riddle_bp
from .routes.execution_routes import execution_bp
from .routes.chunk_routes import chunk_bp
from .routes.cheat_routes import cheat_bp

api_bp = Blueprint('api', __name__)

Expand All @@ -15,3 +16,4 @@
api_bp.register_blueprint(riddle_bp, url_prefix='/riddle')
api_bp.register_blueprint(chunk_bp, url_prefix='/chunk')
api_bp.register_blueprint(execution_bp) # execution handles its own prefixes (/code, /run)
api_bp.register_blueprint(cheat_bp) # cheat-flip at root level
43 changes: 43 additions & 0 deletions src/api/routes/cheat_routes.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,43 @@
from flask import Blueprint, request, jsonify
from core.cheat import toggle_cheat_mode

cheat_bp = Blueprint("cheat", __name__)


@cheat_bp.post("/cheat-flip")
def cheat_flip():
"""
Toggle cheat mode on/off.

Body (JSON):
{ "cheat-code": "<raw secret passphrase>" }

The server compares SHA-256(cheat-code) against the stored CHEAT_CODE hash
using a constant-time comparison, so brute-forcing via timing is not possible.

Returns 200 with current cheat_mode state on success.
Returns 400 for malformed requests, 401 for wrong code, 500 if unconfigured.
"""
if not request.is_json:
return jsonify(status="error", message="Request body must be JSON"), 400

data = request.get_json(silent=True) or {}
cheat_code = data.get("cheat-code")

if not cheat_code:
return jsonify(status="error", message="Missing 'cheat-code' in request body"), 400

result = toggle_cheat_mode(cheat_code)

if not result["success"]:
if result["cheat_mode"] is None:
# Server-side misconfiguration
return jsonify(status="error", message=result["message"]), 500
# Wrong code — do NOT reveal whether mode is on or off
return jsonify(status="error", message=result["message"]), 401

return jsonify(
status="success",
message=result["message"],
cheat_mode=result["cheat_mode"],
), 200
1 change: 1 addition & 0 deletions src/core/__init__.py
Original file line number Diff line number Diff line change
@@ -1,2 +1,3 @@
from .executor import execute_code, execute_custom_code
from .security.sanitizer import sanitize_code
from .cheat import toggle_cheat_mode, is_cheat_mode, make_all_passed_result
108 changes: 108 additions & 0 deletions src/core/cheat.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,108 @@
"""
Cheat mode utilities — in-memory state, security-hardened.

CHEAT_MODE is held purely in memory (module-level flag) and resets on every
server restart. This is intentional: cheat mode should not silently persist
across deployments or container restarts.

CHEAT_CODE env var holds a SHA-256 hex digest of the secret passphrase.
The raw secret is NEVER stored anywhere — only its hash is in .env.

Setup:
python3 -c "import hashlib; print(hashlib.sha256(b'YOUR_SECRET').hexdigest())"
# Set CHEAT_CODE=<that hash> in .env

Toggling:
POST /cheat-flip { "cheat-code": "YOUR_SECRET" }
"""
import os
import hmac
import hashlib
import threading

# ---------------------------------------------------------------------------
# In-memory state — process-local, resets on restart
# ---------------------------------------------------------------------------

# Initialize from CHEAT_MODE env var so you can pre-enable it if needed,
# but this is purely optional. Default is always False (safe).
_cheat_mode: bool = os.getenv("CHEAT_MODE", "false").lower() == "true"
_lock = threading.Lock()


# ---------------------------------------------------------------------------
# Security helpers
# ---------------------------------------------------------------------------

def _hash_secret(raw: str) -> str:
"""Return the SHA-256 hex digest of a raw passphrase."""
return hashlib.sha256(raw.encode()).hexdigest()


def _constant_time_verify(raw_input: str, stored_hash: str) -> bool:
"""
Timing-safe comparison: hash the input, then compare fixed-length digests.
Prevents timing-based brute-force attacks.
"""
input_hash = _hash_secret(raw_input)
return hmac.compare_digest(input_hash, stored_hash)


# ---------------------------------------------------------------------------
# Public API
# ---------------------------------------------------------------------------

def is_cheat_mode() -> bool:
"""Return current cheat mode state (thread-safe read)."""
with _lock:
return _cheat_mode


def toggle_cheat_mode(raw_cheat_code: str) -> dict:
"""
Validate raw_cheat_code against the stored SHA-256 hash, then flip the
in-memory cheat mode flag. No disk writes — fast and race-condition-safe.

Returns: { success: bool, message: str, cheat_mode: bool | None }
"""
global _cheat_mode

stored_hash = os.getenv("CHEAT_CODE")
if not stored_hash:
return {
"success": False,
"message": "Cheat mode is not configured on this server.",
"cheat_mode": None,
}

if not _constant_time_verify(raw_cheat_code, stored_hash):
return {
"success": False,
"message": "Invalid cheat code.",
"cheat_mode": None,
}

with _lock:
_cheat_mode = not _cheat_mode
new_mode = _cheat_mode

return {
"success": True,
"message": f"Cheat mode {'enabled' if new_mode else 'disabled'}.",
"cheat_mode": new_mode,
}


def make_all_passed_result(tests: list) -> dict:
"""Build a fake 'all passed' execution result for the given test list."""
results = [
{
"case": int(t.get("test_number", i + 1)),
"status": "passed",
"msg": "Test passed.",
"stdout": t.get("expected_output", "").strip(),
"stderr": "",
}
for i, t in enumerate(tests)
]
return {"status": "correct", "msg": "All tests passed!", "tests": results}
5 changes: 5 additions & 0 deletions src/core/executor.py
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@
import resource
from .config import COMPILERS, validate_code
from .security.sanitizer import sanitize_code
from .cheat import is_cheat_mode, make_all_passed_result

MAX_MEMORY_MB = int(os.getenv("MAX_MEMORY_MB", 128))
MAX_FILE_SIZE_MB = int(os.getenv("MAX_FILE_SIZE_MB", 1))
Expand Down Expand Up @@ -59,6 +60,10 @@ def execute_custom_code(code: str, lang: str) -> dict:

def execute_code(code: str, lang: str, tests: list, timeout: int = None, templates: dict = None, rules: dict = None) -> dict:
"""Execute code against test cases with validation and templating."""
# --- Cheat mode: skip all execution and return all-passed ---
if is_cheat_mode():
return make_all_passed_result(tests)

if timeout is None:
timeout = MAX_RUN_TIME
if lang not in COMPILERS:
Expand Down
Loading