diff --git a/rules/stellar/access-control/detect-missing-access-control.ts b/rules/stellar/access-control/detect-missing-access-control.ts new file mode 100644 index 0000000..f713900 --- /dev/null +++ b/rules/stellar/access-control/detect-missing-access-control.ts @@ -0,0 +1,44 @@ +/** + * Detect Missing Soroban Access Control Layers (#375) + * Flags privileged functions that expose sensitive operations without role validation. + */ + +export interface AccessControlResult { + detected: boolean; + flaggedFunctions: string[]; + message: string; + suggestion: string; +} + +const PRIVILEGED_PATTERNS = [ + /fn\s+(set_admin|transfer_admin|withdraw|pause|unpause|mint|burn|upgrade)\s*\(/g, +]; + +const AUTH_GUARD = /require_auth|only_admin|assert_owner|has_role/; + +export function detectMissingAccessControl(code: string): AccessControlResult { + const flaggedFunctions: string[] = []; + + for (const pattern of PRIVILEGED_PATTERNS) { + for (const match of code.matchAll(pattern)) { + const fnName = match[1]; + // Extract a small window of code after the function signature to check for an auth guard + const startIdx = match.index ?? 0; + const window = code.slice(startIdx, startIdx + 300); + if (!AUTH_GUARD.test(window)) { + flaggedFunctions.push(fnName); + } + } + } + + if (flaggedFunctions.length === 0) { + return { detected: false, flaggedFunctions: [], message: 'Access control present on all privileged functions.', suggestion: '' }; + } + + return { + detected: true, + flaggedFunctions, + message: `Privileged function(s) lack access control: ${flaggedFunctions.join(', ')}.`, + suggestion: 'Add `require_auth()` or an admin role check at the start of each privileged function.', + }; +} diff --git a/rules/stellar/cross-contract/detect-unsafe-cross-contract-invocation.ts b/rules/stellar/cross-contract/detect-unsafe-cross-contract-invocation.ts new file mode 100644 index 0000000..617ec50 --- /dev/null +++ b/rules/stellar/cross-contract/detect-unsafe-cross-contract-invocation.ts @@ -0,0 +1,45 @@ +/** + * Detect Unsafe Cross-Contract Invocation (#378) + * Flags cross-contract calls that lack caller validation or return-value checks. + */ + +export interface CrossContractResult { + detected: boolean; + message: string; + suggestion: string; +} + +const INVOCATION_PATTERNS = [ + /invoke_contract\s*\(/, + /Client::new\s*\(/, + /env\.invoke\s*\(/, + /call_contract\s*\(/, +]; + +const VALIDATION_PATTERNS = [ + /require_auth/, + /verify_contract/, + /\.unwrap_or_else/, + /\.expect\s*\(/, + /match\s+.*\{/, +]; + +export function detectUnsafeCrossContractInvocation(code: string): CrossContractResult { + const hasInvocation = INVOCATION_PATTERNS.some((p) => p.test(code)); + + if (!hasInvocation) { + return { detected: false, message: 'No cross-contract invocations found.', suggestion: '' }; + } + + const hasValidation = VALIDATION_PATTERNS.some((p) => p.test(code)); + + if (!hasValidation) { + return { + detected: true, + message: 'Cross-contract invocation detected without caller validation or result verification.', + suggestion: 'Validate the callee address and handle invocation results explicitly to prevent unexpected behaviour.', + }; + } + + return { detected: false, message: 'Cross-contract invocation appears guarded.', suggestion: '' }; +} diff --git a/rules/stellar/optimization/detect-inefficient-symbol-usage.ts b/rules/stellar/optimization/detect-inefficient-symbol-usage.ts new file mode 100644 index 0000000..382061e --- /dev/null +++ b/rules/stellar/optimization/detect-inefficient-symbol-usage.ts @@ -0,0 +1,36 @@ +/** + * Detect Inefficient Symbol Usage (#374) + * Flags repeated inline Symbol construction that could be replaced with static references. + */ + +export interface SymbolUsageResult { + detected: boolean; + repeated: string[]; + message: string; + suggestion: string; +} + +export function detectInefficientSymbolUsage(code: string): SymbolUsageResult { + const matches = [...code.matchAll(/Symbol::new\(\s*&env\s*,\s*["']([^"']+)["']\s*\)/g)]; + const names = matches.map((m) => m[1]); + + const counts: Record = {}; + for (const name of names) { + counts[name] = (counts[name] ?? 0) + 1; + } + + const repeated = Object.entries(counts) + .filter(([, count]) => count > 1) + .map(([name]) => name); + + if (repeated.length === 0) { + return { detected: false, repeated: [], message: 'No repeated symbol construction found.', suggestion: '' }; + } + + return { + detected: true, + repeated, + message: `Symbol(s) constructed multiple times: ${repeated.join(', ')}.`, + suggestion: 'Define symbols as constants or lazy statics to avoid repeated allocation overhead.', + }; +} diff --git a/rules/stellar/upgradeability/detect-missing-upgrade-guards.ts b/rules/stellar/upgradeability/detect-missing-upgrade-guards.ts new file mode 100644 index 0000000..3e77cca --- /dev/null +++ b/rules/stellar/upgradeability/detect-missing-upgrade-guards.ts @@ -0,0 +1,33 @@ +/** + * Detect Missing Contract Upgrade Guards (#373) + * Flags upgrade functions that lack admin/owner validation. + */ + +export interface UpgradeGuardResult { + detected: boolean; + message: string; + suggestion: string; +} + +const UPGRADE_PATTERNS = [/fn\s+upgrade\s*\(/, /fn\s+update_contract\s*\(/, /fn\s+set_wasm\s*\(/]; +const AUTH_PATTERNS = [/require_auth/, /admin\.require_auth/, /only_admin/, /assert_admin/]; + +export function detectMissingUpgradeGuards(code: string): UpgradeGuardResult { + const hasUpgradeMethod = UPGRADE_PATTERNS.some((p) => p.test(code)); + + if (!hasUpgradeMethod) { + return { detected: false, message: 'No upgrade methods found.', suggestion: '' }; + } + + const hasAuthCheck = AUTH_PATTERNS.some((p) => p.test(code)); + + if (!hasAuthCheck) { + return { + detected: true, + message: 'Upgrade method detected without admin authorization guard.', + suggestion: 'Add `admin.require_auth()` or an equivalent role check before allowing contract upgrades.', + }; + } + + return { detected: false, message: 'Upgrade guard present.', suggestion: '' }; +}