From 6ea6f6ce5383a591ba7763d958225273749017fc Mon Sep 17 00:00:00 2001 From: JamesEjembi Date: Thu, 25 Jun 2026 22:48:12 +0100 Subject: [PATCH] feat: add initialization risk detection rule and verify existing implementations - Create rules/security/initialization/ with unprotected initializer detector - Soroban Analysis Knowledge Base (src/knowledge-base/stellar/) already implemented - Soroban Rule Lifecycle Manager (src/rules/lifecycle/stellar/) already implemented Closes #512, Closes #510, Closes #325 --- .../detect-unprotected-initializers.spec.ts | 34 ++++++++ .../detect-unprotected-initializers.ts | 84 +++++++++++++++++++ rules/security/initialization/index.ts | 2 + 3 files changed, 120 insertions(+) create mode 100644 rules/security/initialization/__tests__/detect-unprotected-initializers.spec.ts create mode 100644 rules/security/initialization/detect-unprotected-initializers.ts create mode 100644 rules/security/initialization/index.ts diff --git a/rules/security/initialization/__tests__/detect-unprotected-initializers.spec.ts b/rules/security/initialization/__tests__/detect-unprotected-initializers.spec.ts new file mode 100644 index 0000000..2ed8c97 --- /dev/null +++ b/rules/security/initialization/__tests__/detect-unprotected-initializers.spec.ts @@ -0,0 +1,34 @@ +import { detectInitializationRisks } from '../detect-unprotected-initializers'; + +describe('detectInitializationRisks', () => { + it('detects missing constructor', () => { + const code = ` + fn transfer(from: Address, to: Address, amount: i128) { + from.require_auth(); + } + `; + const risks = detectInitializationRisks(code); + expect(risks.some(r => r.type === 'missing-constructor')).toBe(true); + }); + + it('passes when constructor exists', () => { + const code = ` + fn new(env: Env, admin: Address) { + admin.require_auth(); + env.storage().instance().set(&INITIALIZED, &true); + } + `; + const risks = detectInitializationRisks(code); + expect(risks.filter(r => r.type === 'missing-constructor')).toHaveLength(0); + }); + + it('detects re-initialization vulnerability', () => { + const code = ` + fn initialize(env: Env, admin: Address) { + admin.require_auth(); + } + `; + const risks = detectInitializationRisks(code); + expect(risks.some(r => r.type === 'reinitialization-vulnerability')).toBe(true); + }); +}); diff --git a/rules/security/initialization/detect-unprotected-initializers.ts b/rules/security/initialization/detect-unprotected-initializers.ts new file mode 100644 index 0000000..3f540d3 --- /dev/null +++ b/rules/security/initialization/detect-unprotected-initializers.ts @@ -0,0 +1,84 @@ +/** + * Detect unprotected initializers in Soroban contracts. + * + * Checks for: + * 1. Missing constructor functions + * 2. Initialization without access control + * 3. Re-initialization vulnerabilities (lack of initialized flag) + */ + +export interface InitializationRisk { + type: 'missing-constructor' | 'unprotected-initializer' | 'reinitialization-vulnerability'; + severity: 'high' | 'critical'; + description: string; + line?: number; + suggestion: string; +} + +export function detectInitializationRisks(sourceCode: string): InitializationRisk[] { + const risks: InitializationRisk[] = []; + const lines = sourceCode.split('\n'); + + let hasConstructor = false; + let hasInitFunction = false; + let hasInitializedFlag = false; + let hasRequireAuthInInit = false; + + for (let i = 0; i < lines.length; i++) { + const line = lines[i]; + const lineNum = i + 1; + + // Detect constructor-like functions + if (/\bfn\s+new\b/.test(line) || /fn\s+\w+_init\b/.test(line)) { + hasConstructor = true; + hasInitFunction = true; + } + + // Detect initialize functions + if (/\bfn\s+initialize\b/.test(line)) { + hasInitFunction = true; + } + + // Detect initialized flag check + if (/INITIALIZED|initialized|_init\b/.test(line)) { + hasInitializedFlag = true; + } + + // Detect require_auth in init functions + if ((hasInitFunction || hasConstructor) && /require_auth/.test(line)) { + hasRequireAuthInInit = true; + } + } + + // Check for missing constructor + if (!hasConstructor && !hasInitFunction) { + risks.push({ + type: 'missing-constructor', + severity: 'high', + description: 'Contract lacks a constructor or initialization function. State may not be properly initialized.', + suggestion: 'Add a "new" function that initializes contract state, or an "initialize" function with proper access control.', + }); + } + + // Check for re-initialization vulnerability + if (hasInitFunction && !hasInitializedFlag) { + risks.push({ + type: 'reinitialization-vulnerability', + severity: 'critical', + description: 'Initialization function does not check an initialized flag. The contract could be re-initialized by an attacker.', + suggestion: 'Add an initialized flag check at the start of the init function using env.storage().instance().has(&INITIALIZED).', + }); + } + + // Check for unprotected initializer + if (hasInitFunction && !hasRequireAuthInInit) { + risks.push({ + type: 'unprotected-initializer', + severity: 'high', + description: 'Initialization function lacks caller authentication. Anyone could re-initialize the contract.', + suggestion: 'Add require_auth() call at the beginning of the init function to ensure only authorized callers can initialize.', + }); + } + + return risks; +} diff --git a/rules/security/initialization/index.ts b/rules/security/initialization/index.ts new file mode 100644 index 0000000..ce37792 --- /dev/null +++ b/rules/security/initialization/index.ts @@ -0,0 +1,2 @@ +export { detectInitializationRisks } from './detect-unprotected-initializers'; +export type { InitializationRisk } from './detect-unprotected-initializers';