-
Notifications
You must be signed in to change notification settings - Fork 0
Contributing
Thank you for your interest in contributing to Counterscarp Engine! This guide will help you get started with adding new features, fixing bugs, and improving the codebase.
Website: counterscarp.io
Issues: GitHub Issues
- #Development Setup
- #How to Add a New Heuristic Rule
- #How to Integrate a Custom Static Analyzer
- #How to Write Tests
- #Error Handling Conventions
- #Code Style
- #Environment Variables
- Python 3.10+ (required)
- pip for dependency management
- Git for version control
# 1. Clone the repository
git clone https://github.com/RunTimeAdmin/counterscarp.git
cd counterscarp-engine
# 2. Create a virtual environment (recommended)
python -m venv venv
# On Windows:
venv\Scripts\activate
# On macOS/Linux:
source venv/bin/activate
# 3. Install dependencies (using pyproject.toml)
pip install -e ".[dev]"
# 4. Verify installation
counterscarp-engine --help
# Or: python orchestrator.py --help# Run all tests
pytest
# Run with coverage
pytest --cov=. --cov-report=html
# Run specific test file
pytest tests/test_heuristic_scanner.py
# Run with verbose output
pytest -vHeuristic rules are defined in heuristic_scanner.py. Each rule detects a specific vulnerability pattern using regular expressions.
@dataclass
class HeuristicRule:
"""Represents a heuristic detection rule.
Attributes:
id: Unique identifier for this rule (UPPER_SNAKE_CASE).
description: Human-readable description of what this rule detects.
severity: Default severity level (CRITICAL, HIGH, MEDIUM, LOW, INFO).
pattern: Compiled regex pattern to match against code.
hint: Remediation hint for developers.
"""
id: str
description: str
severity: str
pattern: re.Pattern[str]
hint: str-
Open
heuristic_scanner.pyand locate theRULESlist (around line 77). -
Add your new rule to the list:
HeuristicRule(
id="MY_NEW_RULE",
description="Brief description of the vulnerability",
severity="HIGH", # CRITICAL, HIGH, MEDIUM, LOW, INFO
pattern=re.compile(r"your_regex_pattern_here"),
hint="Provide clear remediation guidance for developers..."
),-
Follow these guidelines:
- Use
UPPER_SNAKE_CASEfor rule IDs - Severity should reflect real-world exploit impact
- Patterns should be specific enough to avoid false positives
- Hints should include actionable fix suggestions
- Use
Simple Pattern Match:
HeuristicRule(
id="TX_ORIGIN_USAGE",
description="Use of tx.origin (dangerous for auth checks)",
severity="HIGH",
pattern=re.compile(r"tx\.origin"),
hint="Avoid tx.origin for authorization; use msg.sender and proper role-based access control.",
),Complex Pattern with Lookahead:
HeuristicRule(
id="ORACLE_STALENESS_CHECK",
description="Chainlink oracle without staleness/validity check",
severity="CRITICAL",
pattern=re.compile(r"(latestAnswer|latestRoundData)\(\)(?!.*require.*updatedAt|.*block\.timestamp)"),
hint="CRITICAL: Check updatedAt timestamp and answeredInRound to prevent stale price attacks.",
)The scanner automatically filters out matches that occur in:
- Single-line comments (
//) - Multi-line comments (
/* */) - String literals (
"..."or'...')
This is handled by the is_in_code_context() and is_in_multiline_comment() functions. You don't need to modify these unless you're adding new context types.
# Test the rule manually
python heuristic_scanner.py /path/to/test/contracts --config counterscarp-pr.toml
# Or write a unit test
def test_my_new_rule():
test_code = "// vulnerable code with tx.origin"
findings = scan_file("test.sol", config=None)
assert any(f.rule_id == "MY_NEW_RULE" for f in findings)You can integrate external static analysis tools by following the wrapper pattern used by existing analyzers like aderyn_wrapper.py.
-
Create a new wrapper file (e.g.,
my_analyzer_wrapper.py):
#!/usr/bin/env python3
"""Wrapper for My Custom Analyzer."""
from __future__ import annotations
import subprocess
import json
from typing import Dict, Any, List
from logger import get_logger
from exceptions import (
CounterscarperAnalysisError,
CounterscarperToolNotFoundError,
CounterscarperTimeoutError,
)
logger = get_logger(__name__)
def check_tool_installed() -> bool:
"""Check if the analyzer is available."""
try:
result = subprocess.run(
["my-analyzer", "--version"],
capture_output=True,
text=True,
timeout=5
)
return result.returncode == 0
except FileNotFoundError:
return False
def run_my_analyzer(project_root: str) -> Dict[str, Any]:
"""Run the analyzer and return structured results."""
if not check_tool_installed():
raise CounterscarperToolNotFoundError(
"my-analyzer not found",
details={"tool": "my-analyzer", "install_cmd": "pip install my-analyzer"}
)
cmd = ["my-analyzer", project_root, "--output", "json"]
try:
result = subprocess.run(
cmd,
capture_output=True,
text=True,
timeout=120
)
return json.loads(result.stdout)
except subprocess.TimeoutExpired as e:
raise CounterscarperTimeoutError(
"Analysis timed out",
details={"operation": "my_analyzer", "timeout_seconds": 120}
) from e
except Exception as e:
raise CounterscarperAnalysisError(
"Analysis failed",
details={"error": str(e)}
) from e-
Add graceful import fallback in
orchestrator.py:
# In orchestrator.py, add near other optional imports (around line 27)
try:
import my_analyzer_wrapper
logger.debug("My analyzer wrapper imported successfully")
except ImportError as e:
logger.info(f"My analyzer not available: {e}")
my_analyzer_wrapper = None- Integrate into the analysis pipeline:
# In orchestrator.py main(), add a new phase:
# [PHASE X] My Custom Analyzer
if args.my_analyzer and os.path.isdir(args.target):
print("\n>>> Running My Custom Analyzer...")
if my_analyzer_wrapper is None:
print("[!] My analyzer not available.")
else:
try:
my_results = my_analyzer_wrapper.run_my_analyzer(args.target)
except Exception:
print("[!] My analyzer failed; continuing without results.")
my_results = {"error": "Analysis failed"}Your wrapper should return a dictionary with this structure:
{
"findings": [
{
"rule_id": "RULE_NAME",
"severity": "CRITICAL", # CRITICAL, HIGH, MEDIUM, LOW, INFO
"title": "Short description",
"description": "Detailed explanation",
"file": "path/to/file.sol",
"line_no": 42,
"code_snippet": "optional code excerpt"
}
],
"summary": {
"total": 1,
"critical": 1,
"high": 0,
# ... etc
}
}counterscarp-engine/
├── tests/
│ ├── __init__.py
│ ├── conftest.py # Shared fixtures
│ ├── test_access_matrix.py
│ ├── test_config_loader.py
│ ├── test_exceptions.py
│ ├── test_heuristic_scanner.py
│ ├── test_intent_check.py
│ ├── test_orchestrator.py
│ ├── test_red_team_scan.py
│ ├── test_report_generator.py
│ ├── test_supply_chain_check.py
│ └── test_upgrade_diff.py
└── test_fixtures/ # Optional: test contract files
├── vulnerable_contract.sol
└── safe_contract.sol
Create a tests/conftest.py file with common fixtures:
"""Shared test fixtures for Counterscarp Engine."""
import pytest
import tempfile
import os
@pytest.fixture
def temp_sol_file():
"""Create a temporary Solidity file for testing."""
with tempfile.NamedTemporaryFile(
mode='w',
suffix='.sol',
delete=False
) as f:
f.write("""
pragma solidity ^0.8.0;
contract Test {
function vulnerable() public {
// Code with known vulnerability
tx.origin.call{value: 1 ether}("");
}
}
""")
temp_path = f.name
yield temp_path
# Cleanup
os.unlink(temp_path)
@pytest.fixture
def mock_config():
"""Return a mock configuration object."""
from unittest.mock import MagicMock
config = MagicMock()
config.heuristics.enabled = True
config.heuristics.disabled_rules = []
config.heuristics.get_rule_severity.return_value = "HIGH"
config.is_finding_suppressed.return_value = None
return configWhen testing code that calls external tools (Slither, Aderyn, etc.), use mocking:
import pytest
from unittest.mock import patch, MagicMock
def test_slither_wrapper_mocked():
"""Test slither wrapper with mocked subprocess."""
mock_result = MagicMock()
mock_result.returncode = 0
mock_result.stdout = '{"results": {"detectors": []}}'
mock_result.stderr = ''
with patch('subprocess.run', return_value=mock_result):
from red_team_scan import run_slither
results = run_slither('./test_contracts')
assert results == []
def test_aderyn_not_installed():
"""Test behavior when Aderyn is not installed."""
with patch('subprocess.run', side_effect=FileNotFoundError()):
from aderyn_wrapper import check_aderyn_installed
assert check_aderyn_installed() is Falsedef test_tx_origin_detection(temp_sol_file, mock_config):
"""Test that tx.origin usage is detected."""
from heuristic_scanner import scan_file
findings = scan_file(temp_sol_file, mock_config)
tx_origin_findings = [f for f in findings if f.rule_id == "TX_ORIGIN_USAGE"]
assert len(tx_origin_findings) > 0
assert tx_origin_findings[0].severity == "HIGH"All exceptions are defined in exceptions.py:
CounterscarperError (base)
├── CounterscarperConfigError # Configuration issues
├── CounterscarperAnalysisError # Analyzer failures
├── CounterscarperAPIError # External API failures
├── CounterscarperReportError # Report generation failures
├── CounterscarperToolNotFoundError # Missing external tools
├── CounterscarperValidationError # Input validation failures
└── CounterscarperTimeoutError # Operation timeouts
| Exception | Use When | Example |
|---|---|---|
CounterscarperConfigError |
TOML syntax error, invalid config value | Missing [engine] section |
CounterscarperAnalysisError |
Slither/Aderyn/Mythril fails | Compilation error, timeout |
CounterscarperAPIError |
OSV.dev or threat intel API fails | 500 error from API |
CounterscarperReportError |
Can't write report file | Permission denied on output dir |
CounterscarperToolNotFoundError |
External tool not installed | Slither not in PATH |
CounterscarperValidationError |
User input is invalid | Invalid contract address format |
CounterscarperTimeoutError |
Operation exceeds time limit | Mythril analysis > 5 minutes |
When adding new API integrations (threat intelligence, external services), use the resilient HTTP utilities:
from http_utils import resilient_get, resilient_post
# Automatic retry with exponential backoff
response = resilient_get(
"https://api.example.com/data",
timeout=30,
max_retries=3
)
# Rate-limited POST requests
response = resilient_post(
"https://api.example.com/submit",
json={"key": "value"},
rate_limit=5 # requests per second
)Features:
- Exponential backoff with jitter for failed requests
- Rate limiting to prevent API quota exhaustion
- Automatic retry on 5xx errors and timeouts
-
Respects
Retry-Afterheaders for rate limiting
Use the centralized logger from logger.py:
from logger import get_logger
logger = get_logger(__name__)
# Different log levels:
logger.debug("Detailed debugging info") # Development only
logger.info("General operational info") # Normal operation
logger.warning("Potential issue detected") # Non-critical problem
logger.error("Error occurred") # Something failed
logger.critical("Critical failure") # May crash the programBest Practices:
- Use
debugfor detailed tracing (regex matches, internal state) - Use
infofor milestones ("Scan started", "Report generated") - Use
warningfor recoverable issues ("Tool not found, skipping") - Use
errorfor failures that affect output quality - Use
criticalfor failures that prevent any output
Always chain exceptions to preserve context:
from exceptions import CounterscarperConfigError
try:
load_config(path)
except Exception as e:
raise CounterscarperConfigError(
"Failed to load configuration",
details={"path": path, "error": str(e)}
) from eAll public APIs must have type hints:
from typing import List, Dict, Optional, Any
def scan_target(
target: str,
config: Optional[CounterscarperConfig] = None
) -> List[HeuristicFinding]:
"""Scan a target file or directory.
Args:
target: Path to .sol file or directory.
config: Optional configuration object.
Returns:
List of heuristic findings.
"""
...Use Google-style docstrings for all functions and classes:
def calculate_risk_score(findings: List[Finding]) -> float:
"""Calculate overall risk score (0-100) based on finding severity.
Formula: weighted_sum / max_possible_weight
Args:
findings: List of findings to calculate score from.
Returns:
Risk score between 0 and 100.
Example:
>>> findings = [Finding(severity="CRITICAL", ...)]
>>> score = calculate_risk_score(findings)
>>> print(f"Risk: {score}/100")
"""Every Python file must include this import at the top:
#!/usr/bin/env python3
"""Module docstring."""
from __future__ import annotations
import os
import re
from typing import List, Optional
# Your code hereThis enables:
- Postponed evaluation of annotations (PEP 563)
- Use of forward references without quotes
- Better performance for type checking
The following environment variables are supported:
| Variable | Description | Default | Required |
|---|---|---|---|
COUNTERSCARP_LOG_LEVEL |
Log level: DEBUG, INFO, WARNING, ERROR, CRITICAL | INFO | No |
COUNTERSCARP_LOG_FORMAT |
Output format: "text" or "json" | text | No |
COUNTERSCARP_LOG_FILE |
Path to log file (optional) | (none) | No |
OPENAI_API_KEY |
OpenAI API key for exploit generation | (none) | For AI features |
# Debug logging to file
export COUNTERSCARP_LOG_LEVEL=DEBUG
export COUNTERSCARP_LOG_FILE=/var/log/counterscarp.log
python orchestrator.py --target ./contracts
# JSON format for structured logging
export COUNTERSCARP_LOG_FORMAT=json
python orchestrator.py --target ./contracts 2>&1 | jq
# OpenAI for exploit generation
export OPENAI_API_KEY="sk-..."
python exploit_generator.py --finding-json findings.json- Fork the repository and create a feature branch
- Make your changes following the guidelines above
- Add tests for new functionality
- Run the test suite to ensure nothing is broken
- Update documentation if needed
- Submit a pull request with a clear description
- Code follows the style guidelines
- Type hints on all public APIs
- Google-style docstrings added/updated
-
from __future__ import annotationsat top of file - Tests added for new functionality
- All tests pass (
pytest) - Documentation updated (README, CONTRIBUTING, etc.)
- No hardcoded paths or credentials
- Open an issue on GitHub
- Visit counterscarp.io
- Contact: contact@counterscarp.io
Thank you for contributing to Counterscarp Engine!
- Architecture — System architecture and data flows
- Plugin-Development — Creating plugins and extensions
- Rules-Catalog — Security rules reference
Scarpshield Wiki (Counterscarp Engine)
- Home
- Current Status
- Getting Started
- CLI Reference
- Configuration
- Web App Guide
- Deployment
- Report Formats
- Rules Catalog
- Architecture
- Plugin Development
- Contributing
- Security & Licensing
- Pricing & Pro Features
- FAQ & Troubleshooting
GitHub Repo | Web App | PyPI