Welcome and thanks for contributing!
Before opening an issue or pull request, review the project community policies:
The fastest way to start contributing is using GitHub Codespaces, which provides a pre-configured development environment with all dependencies installed:
- Click the "Code" button on the repository page
- Select the "Codespaces" tab
- Click "Create codespace on main" (or your branch)
The devcontainer will automatically install:
- Rust toolchain
- Z3 theorem prover
- soroban-cli
- wasm-pack
- VS Code extensions (rust-analyzer, even-better-toml)
After the container builds, all dependencies will be ready and cargo build --workspace will have completed.
Before you begin, ensure you have the following installed:
- Rust 1.78+ (rustup)
- Node.js 24+ (nvm recommended)
- Z3 Theorem Prover:
- Debian/Ubuntu:
sudo apt-get install -y libz3-dev - macOS:
brew install z3 llvm - Windows: Download from Z3Prover/z3 releases
- Debian/Ubuntu:
- Clang/LLVM (for Z3 bindings):
- Debian/Ubuntu:
sudo apt-get install -y clang libclang-dev - macOS:
brew install llvm
- Debian/Ubuntu:
- soroban-cli:
cargo install soroban-cli - wasm-pack:
cargo install wasm-pack
Run the automated setup script to install all prerequisites:
make dev-setupThis will:
- Update and set the stable Rust toolchain
- Add the WASM target
- Install wasm-pack and soroban-cli
- Install Node.js dependencies
After running make dev-setup, you'll need to manually install Z3 (see platform-specific instructions above).
If you prefer to set up manually:
-
Install Rust toolchain:
rustup update stable rustup default stable rustup target add wasm32-unknown-unknown
-
Install wasm-pack:
cargo install wasm-pack
-
Install soroban-cli:
cargo install soroban-cli
-
Install Node.js dependencies:
npm install
-
Install Z3 (platform-specific, see Prerequisites above)
-
Build the project:
make build
-
Run tests:
make test
Sanctifier follows a 5-layer architecture for analyzing Soroban smart contracts:
┌─────────────────────────────────────────────────────────────┐
│ USER INTERFACE │
│ ┌──────────────┐ ┌──────────────┐ ┌──────────────────┐ │
│ │ CLI │ │ Web UI │ │ GitHub Action │ │
│ │ (sanctifier │ │ (Frontend) │ │ (Automation) │ │
│ │ deploy) │ │ │ │ │ │
│ └──────┬───────┘ └──────┬───────┘ └───────┬──────────┘ │
└─────────┼─────────────────┼─────────────────┼──────────────┘
│ │ │
└─────────────────┼─────────────────┘
│
┌───────────────────────────▼─────────────────────────────────┐
│ ORCHESTRATION LAYER │
│ • CLI command parsing & validation │
│ • GitHub Actions workflow coordination │
│ • Configuration management │
└───────────────────────────┬─────────────────────────────────┘
│
┌───────────────────────────▼─────────────────────────────────┐
│ ANALYSIS ENGINE │
│ • Rule execution & scheduling │
│ • Finding aggregation & deduplication │
│ • Severity scoring & filtering │
│ • Report generation (SARIF, JSON) │
└───────────────────────────┬─────────────────────────────────┘
│
┌───────────────────────────▼─────────────────────────────────┐
│ RULES LAYER │
│ • 50+ security rules (reentrancy, overflow, access control) │
│ • Pattern matching & AST traversal │
│ • Z3-based formal verification (optional) │
│ • Custom rule support via YAML config │
└───────────────────────────┬─────────────────────────────────┘
│
┌───────────────────────────▼─────────────────────────────────┐
│ PARSER LAYER │
│ • Soroban contract source parsing │
│ • Rust syntax tree (syn) extraction │
│ • WASM artifact analysis │
│ • Contract interface discovery │
└──────────────────────────────────────────────────────────────┘
-
Parser Layer (
tooling/sanctifier-core/src/parser/)- Parses Soroban smart contract source code
- Extracts Rust syntax trees using
syn - Analyzes WASM artifacts when available
- Discovers contract interfaces and entry points
-
Rules Layer (
tooling/sanctifier-core/src/rules/)- Implements 50+ security analysis rules
- Pattern matching for common vulnerabilities (reentrancy, overflow, etc.)
- Z3-based formal verification for invariant checking
- Custom rule support via YAML configuration
-
Engine Layer (
tooling/sanctifier-core/src/engine/)- Orchestrates rule execution
- Aggregates and deduplicates findings
- Applies severity scoring and filtering
- Generates reports in SARIF, JSON, and other formats
-
Orchestration Layer (
tooling/sanctifier-cli/)- CLI command parsing and validation
- GitHub Actions workflow coordination
- Configuration and environment management
- Deployment automation for runtime guard wrappers
-
User Interface Layer (
frontend/,tooling/sanctifier-cli/)- Web-based UI for interactive analysis
- CLI for command-line usage
- GitHub Action for CI/CD integration
- Results visualization and reporting
Follow these steps to contribute a new security analysis rule:
- Identify the vulnerability pattern you want to detect
- Review existing rules in
tooling/sanctifier-core/src/rules/for similar patterns - Determine if the rule requires Z3 formal verification or can use pattern matching
- Define the rule's severity (Low, Medium, High, Critical)
Create a new file in tooling/sanctifier-core/src/rules/ (e.g., your_rule_name.rs):
use sanctifier_core::rules::{Rule, RuleContext, Finding, Severity};
pub struct YourRuleName;
impl Rule for YourRuleName {
fn id(&self) -> &'static str {
"YOUR-RULE-ID" // e.g., "R001"
}
fn name(&self) -> &'static str {
"Your Rule Name"
}
fn description(&self) -> &'static str {
"Description of what this rule detects"
}
fn severity(&self) -> Severity {
Severity::High // or Low, Medium, Critical
}
fn check(&self, context: &RuleContext) -> Vec<Finding> {
let mut findings = Vec::new();
// Your analysis logic here
// Use context.ast, context.contract_info, etc.
findings
}
}Add your rule to the rule registry in tooling/sanctifier-core/src/rules/mod.rs:
mod your_rule_name;
pub fn all_rules() -> Vec<Box<dyn Rule>> {
vec![
// ... existing rules
Box::new(your_rule_name::YourRuleName),
]
}Create comprehensive tests in tooling/sanctifier-core/tests/:
- Positive tests: Contracts that should trigger the rule
- Negative tests: Contracts that should NOT trigger the rule
- Edge cases: Boundary conditions and unusual patterns
Example test structure:
#[test]
fn test_your_rule_detects_vulnerability() {
let contract = r#"
// Vulnerable contract code
"#;
let findings = analyze_contract(contract);
assert!(findings.iter().any(|f| f.rule_id == "YOUR-RULE-ID"));
}- Add your rule to
docs/rules/with:- Rule ID and name
- Description of what it detects
- Example vulnerable code
- Example fixed code
- Severity and category
Ensure all tests pass:
# Run core tests
cargo test -p sanctifier-core --all-features
# Run your specific rule tests
cargo test -p sanctifier-core test_your_rule
# Run linting
cargo fmt --all
cargo clippy -p sanctifier-core -- -D warningsFollow the PR process outlined below.
Required reading before proposing a Z-rule: the ZK threat model (#1193) and the namespace/severity ADR (#1192). These two documents define the contracts that every Z-rule must honour.
Z-rules are Sanctifier's family of security-analysis rules targeting zero-knowledge (ZK) circuit and verifier patterns used in Soroban smart contracts. They live in the same rules layer as S-rules but occupy their own finding-code namespace and carry ZK-specific severity guidance.
All ZK findings use the Z0xx namespace — parallel to the S0xx namespace
used by standard static rules:
| Range | Reserved for |
|---|---|
Z001–Z049 |
Circuit constraint violations (under-constrained outputs, missing range checks) |
Z050–Z099 |
Verifier integration issues (missing nullifier checks, replay attacks) |
Z100–Z149 |
Proof-system misuse (wrong curve, insecure hash-to-field) |
Z150+ |
Reserved for future ZK vulnerability classes |
Choose the next available ID in the appropriate range. If your rule spans multiple classes, place it in the class that represents the highest-impact consequence.
ZK vulnerabilities can have a higher blast radius than typical Soroban bugs
because a single under-constrained wire may compromise every proof ever
generated. Apply the following guidance in addition to the general severity
taxonomy (schemas/severity-taxonomy.schema.json):
| ZK vulnerability class | Minimum severity |
|---|---|
| Under-constrained output wire | Critical |
| Missing nullifier uniqueness check | Critical |
| Replay / double-spend vector | High |
| Missing range check on private input | High |
| Insecure hash-to-field (non-injective) | High |
| Proof system parameter mismatch | Medium |
| Missing public-input binding | Medium |
Follow the same seven steps as for S-rules (see How to Add a New Rule above), with these ZK-specific additions:
fn id(&self) -> &'static str {
"Z042" // next free ID in the relevant range
}Place the implementation file in tooling/sanctifier-core/src/rules/ and
prefix it z to group it with other Z-rules:
tooling/sanctifier-core/src/rules/z042_missing_nullifier_check.rs
Every Z-rule must ship two fixture files in
tooling/sanctifier-core/tests/fixtures/z-rules/:
z042_VULNERABLE.rs— a minimal Soroban contract that triggers the rule.z042_SAFE.rs— the same contract with the vulnerability fixed; the rule must not fire.
Without both fixtures the PR will not be accepted.
After assigning your ID, add a corresponding entry to
data/sarif/severity-map.yaml following the pattern for existing rules.
Omitting this entry causes the SARIF report to emit none for your finding,
which breaks the CI severity gate.
Add a rule reference page at docs/rules/Z042.md (copy the template from
any existing docs/rules/S0xx.md). Include:
- Finding code and name
- Affected ZK pattern (circuit constraint, verifier call, etc.)
- Example vulnerable contract snippet
- Example fixed contract snippet
- Links to the threat model section that motivates the rule (#1193)
- ID chosen from the correct
Z0xxrange - Implementation file prefixed
zand placed inrules/ - Both
z<ID>_VULNERABLE.rsandz<ID>_SAFE.rsfixtures present -
data/sarif/severity-map.yamlentry added -
docs/rules/Z<ID>.mdrule reference page added - Severity set per the guidance table above (never lower than the minimum)
- Threat model reference included in doc and code comments
- Threat model (#1193) — lists the prioritised ZK vulnerability classes
- Namespace/severity ADR (#1192) — the authoritative spec for
Z0xxconventions docs/rule-authoring-guide.md— general YAML/Rust rule authoring tutorialdocs/rules/— existing S-rule reference pages as structural templates
Before submitting a pull request, ensure the following:
- Code follows Rust style guidelines (
cargo fmt --all) - No Clippy warnings (
cargo clippy --workspace -- -D warnings) - All tests pass (
cargo test --workspace) - New code has appropriate test coverage (>80%)
- Public APIs have documentation comments (
///) - No unwrap() in library code (use
?or proper error handling)
- Unit tests added for new functionality
- Integration tests pass
- Frontend tests pass (if applicable):
cd frontend && npm test - E2E tests pass (if applicable):
cd frontend && npm run test:e2e
-
CONTRIBUTING.mdupdated (if adding new workflows/processes) -
ARCHITECTURE.mdupdated (if changing system architecture) - Rule documentation added in
docs/rules/(if adding analysis rules) - README.md updated (if adding user-facing features)
- CHANGELOG.md updated with your changes
- GitHub Actions workflows pass
- No new warnings in CI logs
- Code coverage maintained or improved
- Follows Conventional Commits format:
type(scope): description - Types:
feat,fix,docs,test,refactor,ci, etc. - Descriptive subject line (50 chars or less)
- Body explains "what" and "why" (not "how")
Example:
feat(rules): add detection for integer overflow in token transfers
Implements rule R050 to detect potential integer overflow vulnerabilities
in SPL token transfer operations. Uses pattern matching on arithmetic
operations in transfer functions.
Closes #123
- Formatting: Use
cargo fmt --all(standard rustfmt config) - Linting: Use
cargo clippy --all-targets --all-features -- -D warnings - Naming:
snake_casefor functions and variablesPascalCasefor types and traitsSCREAMING_SNAKE_CASEfor constants
- Error Handling: Prefer
Result<T, E>overpanic!/unwrap()in library code - Documentation: Every public item must have a doc comment (
///) - Function Size: Keep functions short and focused; extract helpers rather than nesting deeply
- Imports: Group imports (std, external crates, internal modules) with blank lines between groups
- Formatting: Use Prettier (
npm run formatorpnpm format) - Linting: Use ESLint (
npm run lintorpnpm lint) - Naming:
camelCasefor variables and functionsPascalCasefor components and typesUPPER_SNAKE_CASEfor constants
- Type Safety:
- All React components must be typed with explicit prop interfaces
- No
anytypes without a comment explaining why - Prefer
interfaceovertypefor object shapes
- Components:
- One component per file
- Co-locate styles with components
- Use functional components with hooks
- Use 2-space indentation
- Quote strings that contain special characters
- Use
${{ }}syntax for GitHub Actions expressions - Group related steps with comments
- Use ATX-style headers (
#not===underlines) - Wrap lines at 80 characters for readability
- Use fenced code blocks with language identifiers
- Include alt text for images
This project follows Conventional Commits specification. All commit messages should be structured as follows:
<type>(<scope>): <description>
[optional body]
[optional footer(s)]
feat:- A new featurefix:- A bug fixperf:- A code change that improves performancetest:- Adding missing tests or correcting existing testsdocs:- Documentation only changesci:- Changes to CI configuration files and scriptsrefactor:- A code change that neither fixes a bug nor adds a feature (no behaviour change)style:- Changes that do not affect the meaning of the code (white-space, formatting, etc)build:- Changes that affect the build system or external dependencieschore:- Other changes that don't modify src or test files
feat(rules): add reentrancy detection for cross-contract calls
fix(parser): correct overflow check in token transfer
perf(engine): optimize WASM parsing for large contracts
docs: update deployment guide with Stellar testnet instructions
ci: add commitlint validation to PR workflow
refactor(rules): extract common validation logic into helper module
test: add property-based tests for AMM pool
Breaking changes should be indicated by a ! after the type or by adding BREAKING CHANGE: in the footer:
feat(api)!: change analysis result format
BREAKING CHANGE: The analysis API now returns findings in a nested structure
- Create an issue or confirm there is already one describing the problem/feature.
- Fork the repository and create a branch:
git checkout -b issue-###-description. - Implement the code following the guidelines above.
- Run tests locally:
make lint cargo test --workspace cd frontend && npm test
- Write commit messages following the Conventional Commits specification.
- Push to your fork and open a PR to
HyperSafeD/Sanctifier:main. - Ensure CI passes - all required status checks must be green.
- Seek review - request at least one approving review.
- Address feedback - make requested changes and push updates.
- Merge - once approved and CI passes, a maintainer will merge.
This repo uses branch protection for main:
- Required status check:
Continuous Integration - Require branches to be up to date before merging
- Require at least 1 review approval
- Disallow force pushes
See BRANCH_PROTECTION.md for details.
Sanctifier ensures the integrity of its vulnerability database and JSON schemas:
- Deterministic Formatting: All JSON artifacts in
data/andschemas/must be pretty-printed. Run./scripts/verify-artifacts.shto fix formatting. - Provenance Manifest: A
CHECKSUMS.txtfile tracks SHA-256 hashes of critical artifacts. - Artifact Attestations: Official releases include GitHub Artifact Attestations (SLSA-aligned) to prevent tampering.
Contributors should ensure that any changes to data/ or schemas/ are correctly formatted and that CHECKSUMS.txt is updated if required.
Before submitting your PR, verify:
- Branch created for specific issue
- All tests pass locally (
make test) - Linting passes (
make lint) - Frontend tests pass (if applicable)
- Documentation updated
- Commit messages follow Conventional Commits
- CI passes on opened PR
- Peer review completed
- No direct push to main
| Stage | Target |
|---|---|
| First response (triage / acknowledgement) | 3 business days |
| Full review (approve / request changes) | 5 business days |
| Re-review after changes | 2 business days |
If your PR has not received a response within the SLA, ping @HyperSafeD in the PR thread.
| Label | Meaning |
|---|---|
type: bug |
Something is broken or behaves incorrectly |
type: feature |
New capability or enhancement |
type: docs |
Documentation-only change |
type: refactor |
Code restructuring with no behaviour change |
type: test |
Test additions or fixes |
area: core-engine |
Changes to tooling/sanctifier-core |
area: frontend |
Changes to frontend/ |
area: contracts |
Changes to contracts/ |
area: docs |
Changes to documentation files |
area: testing |
Test infrastructure or coverage |
difficulty: easy |
Good for first-time contributors; well-scoped |
difficulty: medium |
Requires familiarity with the codebase |
difficulty: hard |
Complex; discuss approach before starting |
priority: high |
Blocking or time-sensitive |
priority: medium |
Important but not blocking |
priority: low |
Nice-to-have |
good first issue |
Recommended starting point for new contributors |
Stellar Wave |
Part of the Stellar Wave contributor programme |
status: blocked |
Waiting on another issue or external dependency |
status: needs-info |
Awaiting clarification from the reporter |
status: wip |
Work in progress — do not pick up |
- Documentation: Check docs/ for detailed guides
- Issues: Search existing issues or create a new one
- Discussions: Use GitHub Discussions for questions
- Security: See SECURITY.md for vulnerability reporting
Thank you for contributing to Sanctifier! 🛡️