Goal
Expand the scanner's authorization, admin and ownership coverage. The detectors below are on the roadmap and not yet implemented — each one is an independent, self-contained piece of work.
These are ready to pick up today. Every entry already has a committed test fixture pair under test-contracts/, so the expected behaviour is pinned before you write a line of code: the scanner must report a finding on the -vulnerable crate and stay silent on the -safe one. You are not guessing at requirements.
| Detector |
Fixture (under test-contracts/) |
address_from_str |
address-from-str-vulnerable / address-from-str-safe |
address_str_eq |
address_str_eq-vulnerable |
addr_param_no_auth |
addr-param-no-auth-vulnerable / addr-param-no-auth-safe |
admin_in_temp |
admin-in-temp-vulnerable / admin-in-temp-safe |
auth_after_write |
auth-after-write-vulnerable / auth-after-write-safe |
auth_in_branch |
auth_in_branch-vulnerable / auth_in_branch-safe |
auth_on_literal_addr |
auth-on-literal-addr-vulnerable / auth-on-literal-addr-safe |
auth_shadow |
auth-shadow-vulnerable / auth-shadow-safe |
auth_untrusted_storage |
auth_untrusted_storage-vulnerable / auth_untrusted_storage-safe |
ownership_immediate |
ownership-immediate-vulnerable / ownership-immediate-safe |
redundant_auth_args |
redundant-auth-args-vulnerable / redundant-auth-args-safe |
renounce_no_backup |
renounce-no-backup-vulnerable / renounce-no-backup-safe |
sig_verify_inverted |
sig-verify-inverted-vulnerable / sig-verify-inverted-safe |
unauth_address_tuple |
unauth-address-tuple-vulnerable / unauth-address-tuple-safe |
unauth_fee_setter |
unauth-fee-setter-vulnerable / unauth-fee-setter-safe |
unauth_storage_remove |
unauth-storage-remove-vulnerable / unauth-storage-remove-safe |
How to build one
Pick one detector from the table and claim it in a comment. One detector per PR — that keeps review fast and lets several people work in parallel.
- Create
crates/checks/src/<name>.rs.
- Implement the
Check trait — name() and run(&self, file: &syn::File, source: &str) -> Vec<Finding>. crates/checks/src/auth.rs is the reference implementation to model: it uses syn::visit::Visit to walk function bodies and crate::util::contractimpl_functions to enumerate #[contractimpl] methods.
- Register it in
crates/checks/src/lib.rs in all three places, or it will compile but never run:
pub mod <name>;
pub use <name>::<Struct>;
Box::new(<Struct>) inside default_checks()
- Add unit tests in a
#[cfg(test)] module in your file.
- Verify against the fixtures:
cargo build --release
./target/release/soroban-guard scan test-contracts/<name>-vulnerable # must report your finding
./target/release/soroban-guard scan test-contracts/<name>-safe # must not
Acceptance criteria
- Fires on the
-vulnerable fixture, silent on the -safe one.
- Unit tests cover at least one positive and one negative case.
- Severity is justified in the PR description.
cargo fmt, cargo clippy -- -D warnings and cargo test are clean.
Notes on quality
A good detector is precise, not just present. Aim for:
- Low false positives. Flagging correct code is worse than missing a bug — it trains people to ignore the tool. The
-safe fixture is the floor, not the target; think about what other legitimate patterns could trip your logic.
- Actionable descriptions. The
description field should tell the reader what an attacker can actually do, not restate the rule. Compare "missing require_auth" against "Callers may mutate contract state without proving they are authorized."
- Set
check_name, severity, line and function_name. Leave file_path empty — the analyzer fills it in.
Goal
Expand the scanner's authorization, admin and ownership coverage. The detectors below are on the roadmap and not yet implemented — each one is an independent, self-contained piece of work.
These are ready to pick up today. Every entry already has a committed test fixture pair under
test-contracts/, so the expected behaviour is pinned before you write a line of code: the scanner must report a finding on the-vulnerablecrate and stay silent on the-safeone. You are not guessing at requirements.test-contracts/)address_from_straddress-from-str-vulnerable/address-from-str-safeaddress_str_eqaddress_str_eq-vulnerableaddr_param_no_authaddr-param-no-auth-vulnerable/addr-param-no-auth-safeadmin_in_tempadmin-in-temp-vulnerable/admin-in-temp-safeauth_after_writeauth-after-write-vulnerable/auth-after-write-safeauth_in_branchauth_in_branch-vulnerable/auth_in_branch-safeauth_on_literal_addrauth-on-literal-addr-vulnerable/auth-on-literal-addr-safeauth_shadowauth-shadow-vulnerable/auth-shadow-safeauth_untrusted_storageauth_untrusted_storage-vulnerable/auth_untrusted_storage-safeownership_immediateownership-immediate-vulnerable/ownership-immediate-saferedundant_auth_argsredundant-auth-args-vulnerable/redundant-auth-args-saferenounce_no_backuprenounce-no-backup-vulnerable/renounce-no-backup-safesig_verify_invertedsig-verify-inverted-vulnerable/sig-verify-inverted-safeunauth_address_tupleunauth-address-tuple-vulnerable/unauth-address-tuple-safeunauth_fee_setterunauth-fee-setter-vulnerable/unauth-fee-setter-safeunauth_storage_removeunauth-storage-remove-vulnerable/unauth-storage-remove-safeHow to build one
Pick one detector from the table and claim it in a comment. One detector per PR — that keeps review fast and lets several people work in parallel.
crates/checks/src/<name>.rs.Checktrait —name()andrun(&self, file: &syn::File, source: &str) -> Vec<Finding>.crates/checks/src/auth.rsis the reference implementation to model: it usessyn::visit::Visitto walk function bodies andcrate::util::contractimpl_functionsto enumerate#[contractimpl]methods.crates/checks/src/lib.rsin all three places, or it will compile but never run:pub mod <name>;pub use <name>::<Struct>;Box::new(<Struct>)insidedefault_checks()#[cfg(test)]module in your file.Acceptance criteria
-vulnerablefixture, silent on the-safeone.cargo fmt,cargo clippy -- -D warningsandcargo testare clean.Notes on quality
A good detector is precise, not just present. Aim for:
-safefixture is the floor, not the target; think about what other legitimate patterns could trip your logic.descriptionfield should tell the reader what an attacker can actually do, not restate the rule. Compare "missing require_auth" against "Callers may mutate contract state without proving they are authorized."check_name,severity,lineandfunction_name. Leavefile_pathempty — the analyzer fills it in.