Restructure openscad-0001 (was in wrong dir, Java test) into proper openscad-0001/ with TICKET.md and Python test. New openscad-0002 patches PolySetBuilder::endPolygon + appendPolySet std::find on colors_ vector -> unordered_map; Color4f already has std::hash. New openscad-0003 patches OctoPrint::requestApiKey and getJsonData to stop logging API responses verbatim (app_token + api responses exposed under --debug). MOADs 0002/0003/0005 CLEAN.
107 lines
3.7 KiB
Python
107 lines
3.7 KiB
Python
"""
|
|
test_openscad_0003.py — CWE-312 OctoPrint requestApiKey logs app_token verbatim
|
|
|
|
Defect: OctoPrint.cc requestApiKey() calls PRINTDB with the full JSON response
|
|
body including app_token. When --debug is active, the temporary authentication
|
|
token is written to the debug log in plain text (CWE-312).
|
|
|
|
Fix: extract the token from the parsed JSON, log only a redacted presence
|
|
indicator, never the token value.
|
|
"""
|
|
|
|
import json
|
|
import sys
|
|
|
|
|
|
# --- Simulate defective logging: returns what gets logged ---
|
|
def request_api_key_defective(response_json: dict) -> tuple:
|
|
"""
|
|
Simulates the defective path: logs the full JSON response.
|
|
Returns (token, logged_string).
|
|
"""
|
|
doc_str = json.dumps(response_json)
|
|
logged = f"Response: {doc_str}" # PRINTDB("Response: %s", ...)
|
|
token = response_json.get("app_token", "")
|
|
return token, logged
|
|
|
|
|
|
# --- Fixed: logs only presence, not value ---
|
|
def request_api_key_fixed(response_json: dict) -> tuple:
|
|
"""
|
|
Simulates the fixed path: extracts token, logs only presence indicator.
|
|
Returns (token, logged_string).
|
|
"""
|
|
token = response_json.get("app_token", "")
|
|
if token:
|
|
logged = "requestApiKey: app_token received (redacted)"
|
|
else:
|
|
logged = "requestApiKey: app_token absent"
|
|
return token, logged
|
|
|
|
|
|
def test_token_not_in_log():
|
|
"""Defect: app_token appears in log. Fix: token absent from log."""
|
|
response = {
|
|
"app_token": "secret-token-abc123xyz",
|
|
"status": "pending"
|
|
}
|
|
|
|
# Defective path: token in log
|
|
token_def, log_def = request_api_key_defective(response)
|
|
assert token_def == "secret-token-abc123xyz", "token extraction failed"
|
|
assert "secret-token-abc123xyz" in log_def, "defect: token should appear in defective log"
|
|
|
|
# Fixed path: token NOT in log
|
|
token_fix, log_fix = request_api_key_fixed(response)
|
|
assert token_fix == "secret-token-abc123xyz", "fixed: token extraction failed"
|
|
assert "secret-token-abc123xyz" not in log_fix, \
|
|
f"CWE-312: token still appears in fixed log: {log_fix}"
|
|
assert "redacted" in log_fix, "fixed log should mention redaction"
|
|
|
|
print(f"PASS defective log contains token: True")
|
|
print(f"PASS fixed log contains token: False")
|
|
print(f"PASS fixed log: '{log_fix}'")
|
|
|
|
|
|
def test_empty_token():
|
|
"""Edge case: absent token should be handled gracefully."""
|
|
response = {"status": "pending"}
|
|
|
|
token_fix, log_fix = request_api_key_fixed(response)
|
|
assert token_fix == "", "expected empty token"
|
|
assert "absent" in log_fix, f"expected 'absent' in log, got: {log_fix}"
|
|
print(f"PASS empty token handled: '{log_fix}'")
|
|
|
|
|
|
def test_get_json_data_redaction():
|
|
"""
|
|
getJsonData() also logs full response verbatim — may include api_key,
|
|
printer config, etc. Fixed: logs only a presence indicator.
|
|
"""
|
|
sensitive_responses = [
|
|
{"api": "0.1", "server": "1.8.6"},
|
|
{"api_key": "master-key-xyz"},
|
|
{"slicers": {"cura": {"default": True, "key": "secret-profile-key"}}},
|
|
]
|
|
|
|
for resp in sensitive_responses:
|
|
resp_str = json.dumps(resp)
|
|
# Defective: log full response
|
|
defective_log = f"Response: {resp_str}"
|
|
|
|
# Fixed: never log body
|
|
fixed_log = "Response received for endpoint (body redacted)"
|
|
|
|
# Check no keys from response body appear in fixed log
|
|
for key in resp:
|
|
assert key not in fixed_log, \
|
|
f"CWE-312: key '{key}' from response appears in fixed log"
|
|
|
|
print(f"PASS getJsonData redaction for response with keys: {list(resp.keys())}")
|
|
|
|
|
|
if __name__ == "__main__":
|
|
test_token_not_in_log()
|
|
test_empty_token()
|
|
test_get_json_data_redaction()
|
|
print("ALL TESTS PASSED")
|