T09 · Insecure Skill Coding Practices
- Location
references/storage-patterns.md:142- Finding
Unauthenticated Public Function Permits Forged Audit-Log Records
- Content
View full analysis
Vulnerability Details
File Location:
references/storage-patterns.md, lines 142-155
Vulnerability Type: Missing authentication and caller impersonation
Risk Level: Mediummove public entry fun log_transaction( action: String, executor: address, amount: u64 ) acquires AuditLog { let log = borrow_global_mut<AuditLog>(@my_addr); smart_vector::push_back(&mut log.transactions, TxRecord { timestamp: timestamp::now_seconds(), action, executor, amount, }); }Technical Analysis
The reusable example exposes
log_transactionas a public entry function without accepting a signer or performing an authorization check. Consequently, any transaction sender can invoke it.The
executorfield is supplied directly by the caller rather than being derived from an authenticated signer. An attacker can therefore attribute an arbitrary action and amount to any address, including an administrator or another user. The record is then appended to the authoritative on-chainAuditLog.The function also lacks validation or size limits for
actionand does not constrainamount. Repeated calls can grow the stored log and impose avoidable storage costs.Attack Path
- The victim deploys a contract based on this documented storage pattern.
- An attacker invokes the public
log_transactionentry function. - The attacker supplies a privileged or victim address as
executor. - The attacker supplies arbitrary
actionandamountvalues. - The function appends the forged record without checking the transaction sender.
- Indexers, monitoring systems, accounting logic, or users may treat the forged record as an authentic action by the impersonated address.
- The attacker can repeat the operation to pollute the log and increase storage consumption.
Impact Assessment
This issue does not directly grant account or admin ...[truncated 544 chars]
- Remediation
View remediation
Remediation Suggestions
- Add a
caller: &signerparameter to the entry function. - Derive the executor from
signer::address_of(caller)instead of accepting an arbitrary executor address. - If only trusted code may create records, compare the caller against a stored administrator or operator role before modifying the log.
- Validate
actionwith non-empty and maximum-length constraints. - Validate
amountaccording to the application’s permitted range. - Emit authenticated events instead of maintaining an indefinitely growing on-chain history when records are only needed for off-chain querying.
- If on-chain storage is required, implement retention or bounded-capacity controls.
A safer interface would follow this structure:
move public entry fun log_transaction( caller: &signer, action: String, amount: u64 ) acquires AuditLog { let executor = signer::address_of(caller); // Validate caller authority, action length, and amount here. // Append the authenticated record or emit an event. }- Add a
