| |
| """ |
| Qwythos-9B Security Adapter Benchmark |
| Loads mxguru1/qwythos-9b-security-unsloth adapter on Qwen3.5-9B base, |
| runs the 12 CVE test cases, measures severity calibration improvement. |
| """ |
| import sys, os, subprocess, json |
|
|
| sys.stdout.reconfigure(encoding="utf-8", errors="replace") |
| sys.stderr.reconfigure(encoding="utf-8", errors="replace") |
| os.environ.setdefault("PYTHONIOENCODING", "utf-8") |
|
|
| HF_TOKEN = os.environ.get("HF_TOKEN", "") |
| ADAPTER_ID = "mxguru1/qwythos-9b-security-unsloth" |
| BASE_MODEL = "Qwen/Qwen3.5-9B" |
|
|
| |
| os.environ["TRANSFORMERS_NO_VISION"] = "1" |
|
|
| print("=" * 60) |
| print("ADAPTER BENCHMARK: mxguru1/qwythos-9b-security-unsloth") |
| print("=" * 60) |
|
|
| |
| print("\n[1/4] Installing dependencies...") |
| subprocess.run([sys.executable, "-m", "pip", "install", "--quiet", "--no-cache-dir", |
| "unsloth", "transformers", "accelerate", "huggingface_hub"], timeout=300) |
|
|
| |
| print("\n[2/4] Loading Qwen3.5-9B + security adapter...") |
| import torch |
| from unsloth import FastLanguageModel |
| from transformers import AutoTokenizer |
|
|
| model, _ = FastLanguageModel.from_pretrained( |
| model_name=BASE_MODEL, |
| max_seq_length=2048, |
| load_in_4bit=True, |
| fast_inference=False, |
| token=HF_TOKEN, |
| ) |
| |
| tokenizer = AutoTokenizer.from_pretrained( |
| BASE_MODEL, |
| use_fast=True, |
| token=HF_TOKEN, |
| trust_remote_code=False, |
| ) |
| print(" Base model loaded (4-bit)") |
|
|
| |
| model = FastLanguageModel.get_peft_model(model, r=32) |
| FastLanguageModel.for_inference(model) |
|
|
| print(" Adapter attached and ready for inference") |
| print(f" GPU available: {torch.cuda.is_available()}") |
| if torch.cuda.is_available(): |
| print(f" GPU: {torch.cuda.get_device_name(0)}") |
|
|
| |
| print("\n[3/4] Running 12 CVE benchmark cases...") |
|
|
| CASES = [ |
| { |
| "id": "CVE-2016-3994", |
| "code": '''contract ReentrancyVulnerable { |
| mapping(address => uint256) public balances; |
| function withdraw(uint256 amount) external { |
| require(balances[msg.sender] >= amount); |
| (bool s,) = msg.sender.call{value: amount}(""); |
| require(s); |
| balances[msg.sender] -= amount; |
| } |
| }''', |
| "vuln": True, |
| "correct_severity": "CRITICAL", |
| "keywords": ["reentrancy", "call", "external call", "CEI violation"] |
| }, |
| { |
| "id": "SWC-101", |
| "code": '''contract IntegerOverflow { |
| function add(uint256 a, uint256 b) public pure returns (uint256) { |
| return a + b; |
| } |
| }''', |
| "vuln": True, |
| "correct_severity": "HIGH", |
| "keywords": ["overflow", "integer", "addition"] |
| }, |
| { |
| "id": "SWC-104", |
| "code": '''contract UncheckedCall { |
| function doTransfer(address to, uint256 amount) public { |
| address payable _to = payable(to); |
| _to.transfer(amount); |
| } |
| }''', |
| "vuln": True, |
| "correct_severity": "MEDIUM", |
| "keywords": ["transfer", "gas", "return value", "unchecked"] |
| }, |
| { |
| "id": "SWC-107", |
| "code": '''contract ReentrancyNoCEI { |
| mapping(address => uint256) balances; |
| function withdraw() external { |
| uint256 bal = balances[msg.sender]; |
| (bool ok,) = msg.sender.call{value: bal}(""); |
| balances[msg.sender] = 0; |
| } |
| }''', |
| "vuln": True, |
| "correct_severity": "CRITICAL", |
| "keywords": ["reentrancy", "CEI", "state update after external call"] |
| }, |
| { |
| "id": "SWC-102", |
| "code": '''contract UnderflowVuln { |
| function spend(uint256 amount) public { |
| uint256 balance = 100; |
| balance -= amount; |
| } |
| }''', |
| "vuln": True, |
| "correct_severity": "HIGH", |
| "keywords": ["underflow", "integer", "unchecked"] |
| }, |
| { |
| "id": "SWC-113", |
| "code": '''contract DoSVuln { |
| function loop(uint256 n) public view { |
| for (uint256 i = 0; i < n; i++) { } |
| } |
| }''', |
| "vuln": True, |
| "correct_severity": "MEDIUM", |
| "keywords": ["denial of service", "gas", "loop", "iteration"] |
| }, |
| { |
| "id": "FLASHLOAN-01", |
| "code": '''contract FlashloanVuln { |
| address constant DAI = 0x6B175474E89094C44Da98b954EesAAB765B2E7; |
| function exploit(address payable target) external { |
| IERC20(DAI).transfer(target, 1000e18); |
| } |
| }''', |
| "vuln": True, |
| "correct_severity": "HIGH", |
| "keywords": ["flash loan", "price oracle", "manipulation"] |
| }, |
| { |
| "id": "SWC-125", |
| "code": '''contract RandomnessVuln { |
| function random() public view returns (uint256) { |
| return uint256(keccak256(abi.encodePacked(block.timestamp, msg.sender))); |
| } |
| }''', |
| "vuln": True, |
| "correct_severity": "HIGH", |
| "keywords": ["randomness", "predictable", "block.timestamp"] |
| }, |
| { |
| "id": "SWC-111", |
| "code": '''contract Privileged { |
| address public owner; |
| function setOwner(address newOwner) public { |
| owner = newOwner; |
| } |
| }''', |
| "vuln": True, |
| "correct_severity": "MEDIUM", |
| "keywords": ["access control", "owner", "missing modifier"] |
| }, |
| { |
| "id": "SWC-100", |
| "code": '''contract TxOriginVuln { |
| function transfer(address to, uint256 amount) public { |
| require(tx.origin == address(this), "not owner"); |
| (bool s,) = to.call{value: amount}(""); |
| require(s); |
| } |
| }''', |
| "vuln": True, |
| "correct_severity": "HIGH", |
| "keywords": ["tx.origin", "authorization bypass"] |
| }, |
| { |
| "id": "RACE-01", |
| "code": '''contract RaceCondition { |
| mapping(address => uint256) public allowance; |
| function approve(address spender, uint256 amount) external { |
| allowance[spender] = amount; |
| } |
| }''', |
| "vuln": True, |
| "correct_severity": "MEDIUM", |
| "keywords": ["race condition", "approve", "front-running", "allowance"] |
| }, |
| { |
| "id": "SWC-122", |
| "code": '''contract TypeVuln { |
| function getLength(address a) public pure returns (uint256) { |
| return uint256(a); |
| } |
| }''', |
| "vuln": True, |
| "correct_severity": "LOW", |
| "keywords": ["type conversion", "address", "uint256", "overflow"] |
| }, |
| ] |
|
|
| PROMPT_TEMPLATE = """You are a Solidity smart contract security auditor. Analyze this contract for vulnerabilities and assign a severity. |
| |
| Contract: |
| ```{code} |
| {code} |
| ``` |
| |
| For each vulnerability found, respond with: |
| - CWE ID or SWC ID (if applicable) |
| - Severity: CRITICAL / HIGH / MEDIUM / LOW / INFO |
| |
| Respond with ONLY the vulnerability analysis. Format: "Severity: [level]" as your final assessment.""" |
|
|
| results = [] |
|
|
| for i, c in enumerate(CASES): |
| prompt = PROMPT_TEMPLATE.format(code=c["code"]) |
| messages = [{"role": "user", "content": prompt}] |
| text = tokenizer.apply_chat_template(messages, tokenize=False, add_generation_prompt=True) |
|
|
| inputs = tokenizer(text, return_tensors="pt", truncation=True, max_length=1500) |
| if torch.cuda.is_available(): |
| inputs = {k: v.cuda() for k, v in inputs.items()} |
|
|
| with torch.no_grad(): |
| outputs = model.generate( |
| **inputs, |
| max_new_tokens=512, |
| temperature=0.1, |
| do_sample=False, |
| use_cache=True, |
| ) |
|
|
| response = tokenizer.decode(outputs[0][inputs["input_ids"].shape[1]:], skip_special_tokens=True) |
|
|
| |
| correct_sev = c["correct_severity"].upper() |
| response_upper = response.upper() |
| sev_correct = correct_sev in response_upper |
|
|
| |
| response_lower = response.lower() |
| kw_matches = sum(1 for kw in c["keywords"] if kw.lower() in response_lower) |
| detected = kw_matches >= 1 |
|
|
| print(f" [{c['id']}] {('OK' if detected else 'MISS')} | Sev={('OK' if sev_correct else 'WRONG')} ({correct_sev}) | len={len(response)}") |
|
|
| results.append({ |
| "id": c["id"], |
| "correct_severity": correct_sev, |
| "response_snippet": response[:200], |
| "detected": detected, |
| "severity_correct": sev_correct, |
| }) |
|
|
| |
| print("\n[4/4] Results:") |
| detected_count = sum(1 for r in results if r["detected"]) |
| sev_correct_count = sum(1 for r in results if r["severity_correct"]) |
|
|
| print(f"\n Detection: {detected_count}/12 = {detected_count/12*100:.1f}%") |
| print(f" Severity: {sev_correct_count}/12 = {sev_correct_count/12*100:.1f}%") |
|
|
| print("\n Per-case:") |
| for r in results: |
| det = "DETECT" if r["detected"] else "MISS" |
| sev = "SEV_OK" if r["severity_correct"] else f"SEV_BAD({r['correct_severity']})" |
| print(f" [{r['id']}] {det:10s} {sev}") |
|
|
| |
| out = { |
| "adapter": ADAPTER_ID, |
| "base_model": BASE_MODEL, |
| "total_cases": 12, |
| "detected": detected_count, |
| "detected_pct": detected_count/12*100, |
| "severity_correct": sev_correct_count, |
| "severity_pct": sev_correct_count/12*100, |
| "cases": results, |
| } |
| out_path = "/data/adapter_bench_results.json" |
| with open(out_path, "w", encoding="utf-8") as f: |
| json.dump(out, f, indent=2) |
| print(f"\n Results saved to {out_path}") |
|
|