As of 3 Nov 2026, Shentu Chain has 5 audit reports by 1 security firm on Trustblock; the latest is by CertiK, dated 31 Dec 2024. Those reports list no open critical findings.
Record last updated . It holds audits, findings and contracts up to Nov 2026, and incidents up to Oct 2026. Trustblock does not review code.
Newest first
1 other security report listed, not counted
| Audit | Security firm | Date | Scope | Critical findings | Report |
|---|---|---|---|---|---|
| Shentu Chain - DAPP pentest Penetration test of off-chain systemsNot counted as an audit report | Security firm: CertiK | Date: | Scope: Not recorded (see the report) | No open critical findings | Open · skynet.certik.com |
| Shentu - MultiSig | Security firm: CertiK | Date: | Scope: Files named in the report | 1 critical finding, fixed | Open · skynet.certik.com |
| Shentu - DeepWallet | Security firm: CertiK | Date: | Scope: Not recorded (see the report) | No open critical findings | Open · skynet.certik.com |
| Shentu - Restaking | Security firm: CertiK | Date: | Scope: Files named in the report | No open critical findings | Open · skynet.certik.com |
| Shentu Chain - audit | Security firm: CertiK | Date: | Scope: Files named in the report | No open critical findings | Open · skynet.certik.com |
| Shentu Security Oracle | Security firm: CertiK | Date: | Scope: Files named in the report | No open critical findings | Open · skynet.certik.com |
Statuses are the security firm’s own, from each report.
| Severity | Open | Acknowledged | Fixed |
|---|---|---|---|
| Critical | 0 | 0 | 1 |
| High | 4 | 0 | 3 |
| Medium | 5 | 0 | 11 |
| Low | 19 | 0 | 27 |
Repository: - multisig-service File: - controller/login.go The `LoginWithWallet` function provides users with a JWT token that includes their wallet address, which is used for authentication. However, both the public key and signature are derived from the message the user submits. In the following code snippet: ```go=31 if !util.VerifyADR036Sign(req.Address, req.PubKey, req.Signature, req.Msg) { logger.Errorw("Invalid signature information", zap.Any("req", req)) SendBadReqResponse(c, errno.InvalidRequestErr, nil) return } ``` the verification process checks whether the provided signature matches the corresponding public key and message. The vulnerability arises because an attacker can create a valid signature for a message and address of another user by utilizing the attacker's own private key. As the signature verification at line 31 only ensures that the signature matches the given public key and message, it would incorrectly validate as true. This enables the attacker to impersonate a legitimate user, allowing unauthorized actions, such as using the task owner's address to accept comments or engage in unauthorized economic activities.
Open the reportRepository: - multisig-service File: - service/account.go The `CreateAccount` function in the multisig service allows users to create an account using a public key and an address. However, there is no verification process to ensure that the user creating the account actually owns the address they are registering. This issue is exacerbated by the fact that the same address cannot be registered more than once. ```go=106 dbAccount, err := m.Select(m.Address).Where(m.Address.Eq(req.Address)).First() ``` This could lead to a vulnerability where a malicious actor uses their own public key to register an account with someone else's address, effectively squatting on that address. Once an address is registered, the legitimate owner cannot create an account with it. This vulnerability could prevent legitimate users from creating multisig accounts with their own addresses, potentially leading to a denial of service for those users. It could also result in unauthorized control or association of addresses with malicious actors.
Open the reportFiles: - `x/shield/keeper/staking_purchase.go` Commit: - [`3a7e28ae8dec6bbf4d7b35252a745b82a463f781`](https://github.com/shentufoundation/shentu/tree/3a7e28ae8dec6bbf4d7b35252a745b82a463f781) In the shield module, the function `ProcessStakeForShieldExpiration` is called at the conclusion of each blocker to handle expired shields. Funds from expired Shield stakes will be transferred to the user's account, and any remaining shields that are not withdrawn will automatically be used to purchase new shields. ```go=200 renewShieldInt := amount.ToDec().Quo(sPRate).TruncateInt() renewShield := sdk.NewCoins(sdk.NewCoin(bondDenom, renewShieldInt)) if renewShieldInt.IsZero() { return nil } ``` However, the function employs the variable `amount` instead of `renew` to calculate the renewed shield amount. This discrepancy may result in using the entire fund amount of the expired shield to repurchase the shield.
Open the reportIn the shield module, multiple operations are processed at the end of each block: - remove expired purchases and distribute service fees, - process completed withdraws, and - close pools with no shield and shield limits are set to zero. ```go=15 // EndBlocker processes premium payment at every block. func EndBlocker(ctx sdk.Context, k keeper.Keeper) { // Remove expired purchases and distribute service fees. k.RemoveExpiredPurchasesAndDistributeFees(ctx) // Process completed withdraws. k.DequeueCompletedWithdrawQueue(ctx) // Close pools who do not have any shield and shield limits are set to zero. k.ClosePools(ctx) } ``` A large number of operations will be performed in this process. For example, in `RemoveExpiredPurchasesAndDistributeFees()`, fees are distributed to all shield providers: ```go=301 // Distribute service fees. totalCollateral := k.GetTotalCollateral(ctx) providers := k.GetAllProviders(ctx) for _, provider := range providers { ``` An attacker might initiate a Denial of Service (DoS) attack by creating numerous provider accounts and purchases, and initiating a high volume of withdrawals. This strategy could significantly increase the computational resources required at the end of a block, potentially overwhelming the nodes.
Open the reportThe authentication mechanism of the current application is undermined by a permissive password policy, which stipulates a minimum password length of 8 characters but lacks complexity requirements. The strength of authentication mechanisms is intrinsically linked to the robustness of user credentials. A weak password policy reduces the search space for potential passwords, making it significantly easier to perform brute-force attacks.
Open the reportRepository: - multisig-service Files: - service/chain_registry.go - service/account.go Two different create account functions `createAccounts` and `CreateAccount` have inconsistent handling of multisig account members. In the first function, `createAccounts`, members are constructed as a comma-separated string, **service/chain_registry.go** ```go=118 for _, pubkey := range account.PubKey.PublicKeys { if len(members) != 0 { members += "," } member, err := util.AddressFromPubKey(pubkey.Key, pubkey.KeyType, bech32Prefix) if err != nil { util.Logger.Errorf("get member err:%s", err.Error()) continue } members += member } ``` while in the second function, `CreateAccount`, members are serialized into JSON format. **service/account.go** ```go=93 membersJson, _ := json.Marshal(req.Members) ``` This inconsistency can lead to issues in data storage, account creation logic, and future account operations. Specifically, if the members are formatted as a comma-separated string, it causes the `RenameMemberAccount` function to fail. **service/account.go** ```go=241 func (s *MultisigService) RenameMemberAccount(callerHexAddr string, req entity.AccountRequest) *errno.Errno { ... var members []entity.MemberAccount err = json.Unmarshal([]byte(dbAccount.Members), &members) if err != nil { s.logger.Errorf("Error decoding JSON: %v", err) return errno.Err(errno.InternalServerErr, "invalid db data") } ... ```
Open the reportRepository: - multisig-service Files: - service/account.go The `CreateAccount` function in the `MultisigService` lacks proper input validation for the `pubkey` and `members` fields. Specifically, there is no limit on the length of the `pubkey`, and the `members` array does not have a length limit or duplication check. This could lead to excessively large database entries, potentially causing performance issues or even crashing the node if exploited by a malicious user through automated scripts.
Open the reportRepository: - multisig-service Files: - service/chain_registry.go - dal/model/multisig_account.gen.go In the `createAccounts` function, the `MultisigAccount` struct requires the `Pubkey` and `Status` fields to be set, as they are defined as not null in the database schema. **dal/model/multisig_account.gen.go** ```go=14 type MultisigAccount struct { Address string `gorm:"column:address;primaryKey" json:"address"` Name string `gorm:"column:name" json:"name"` Threshold int32 `gorm:"column:threshold;not null" json:"threshold"` ChainName string `gorm:"column:chain_name;not null" json:"chain_name"` Members string `gorm:"column:members;not null" json:"members"` Status string `gorm:"column:status;not null" json:"status"` Pubkey string `gorm:"column:pubkey;not null" json:"pubkey"` CreateTime time.Time `gorm:"column:create_time" json:"create_time"` } ``` However, these fields are not populated when creating a multisig account, resulting in the database operation failing. This issue leads to the consistent failure of account creation attempts, impacting the functionality where multisig accounts are intended to be created and stored.
Open the reportRepository: - multisig-service Files: - util/token.go The `ParseToken` function in the code may lead to a nil pointer dereference if an invalid JWT token is passed. Specifically, if the JWT token contains more than two periods (`.`), the `jwt.ParseWithClaims` function may return a `nil` token. Subsequent access to `token.Claims` without checking if `token` is `nil` can result in a runtime panic. ```go=35 func ParseToken(tokenString string) (*CustomClaims, error) { token, err := jwt.ParseWithClaims(tokenString, &CustomClaims{}, func(token *jwt.Token) (interface{}, error) { return []byte(Cfg().Server.TokenKey), nil }) claims, ok := token.Claims.(*CustomClaims) if !ok || token.Valid { return nil, err } if claims.Issuer != common.MultisigIssuer+Cfg().Server.Env { return nil, fmt.Errorf("invalid issuer") } if claims.Subject != common.MultisigSubject { return nil, fmt.Errorf("invalid task") } return claims, nil } ```
Open the reportRepository: - multisig-service Files: - multisig-service-20241103-778c2ec8/util/token.go The audited code contains private keys stored in plaintext. This practice, initially implemented for early development or testing purposes, poses a significant security risk. These private keys must be removed to ensure the security of the system.
Open the reportAccording to the current implementation, the function `prepForZeroHeightGenesis()` retrieves an invalid validator address from the KV store, which may be causing the export of the genesis file to fail. The function `prepForZeroHeightGenesis()` is intended to export the genesis state from the Coreum chain, which includes getting and exporting the validator information from the KV store. ```go=142 // Iterate through validators by power descending, reset bond heights, and // update bond intra-tx counters. store := ctx.KVStore(app.keys[stakingtypes.StoreKey]) iter := sdk.KVStoreReversePrefixIterator(store, stakingtypes.ValidatorsKey) counter := int16(0) for ; iter.Valid(); iter.Next() { addr := sdk.ValAddress(iter.Key()[1:]) validator, found := app.StakingKeeper.GetValidator(ctx, addr) if !found { panic("expected validator, not found") } validator.UnbondingHeight = 0 if applyAllowedAddrs && !allowedAddrsMap[addr.String()] { validator.Jailed = true } app.StakingKeeper.SetValidator(ctx, validator) counter++ } ... ``` In line 149, the validator address is fetched via `iter.Key()[1:]`, which excludes the `ValidatorKey`, but it still includes the prefix, the length of the address. This can be demonstrated in the function `GetValidatorKey()`: **cosmos-sdk/x/staking/types/keys.go** ```go=81 // GetValidatorKey creates the key for the validator with the address // VALUE: staking/Validator func GetValidatorKey(operatorAddr sdk.ValAddress) []byte { return append(ValidatorsKey, address.MustLengthPrefix(operatorAddr)...) } ``` Therefore, the `iter.Key()[1:]` returns the bytes of the length of the address plus the address, and it cannot be correctly converted to the validator address via the function `sdk.ValAddress()`. In this case, it will cause panic because it cannot be found in the staking keeper.
Open the reportFiles: - `x/shield/keeper/pool.go` - `x/shield/keeper/keep.go` Commit: - [`3a7e28ae8dec6bbf4d7b35252a745b82a463f781`](https://github.com/shentufoundation/shentu/tree/3a7e28ae8dec6bbf4d7b35252a745b82a463f781) In the context of the `GetPoolsBySponsor` function, it is expected to use the sponsor address as the input parameter. ```go=61 func (k Keeper) GetPoolsBySponsor(ctx sdk.Context, sponsorAddr string) ([]types.Pool, bool) { var ret []types.Pool found := false k.IterateAllPools(ctx, func(pool types.Pool) bool { if pool.SponsorAddr == sponsorAddr { ret = append(ret, pool) found = true } return false }) return ret, found } ``` However, in the `createPool` function, there is a potential issue where the incorrect parameter, `sponsor`, is passed when invoking the `GetPoolsBySponsor` function. This could potentially result in the failure to create a pool due to conflicts in the sponsor's name. ```go=175 if _, found := k.GetPoolsBySponsor(ctx, sponsor); found { return 0, types.ErrSponsorAlreadyExists } ```
Open the reportFiles: - `x/shield/types/msg.go` Commit: - [`3a7e28ae8dec6bbf4d7b35252a745b82a463f781`](https://github.com/shentufoundation/shentu/tree/3a7e28ae8dec6bbf4d7b35252a745b82a463f781) In `MsgUpdateSponsor`'s `ValidationBasic` function, the sponsor address is checked to ensure it is non-empty. However, `sponsorAddr` is decoded from `msg.From` in the implementation, which means `msg.sponsorAddr` is unchecked: ```go=595 sponsorAddr, err := sdk.AccAddressFromBech32(msg.From) if err != nil { panic(err) } if sponsorAddr.Empty() || strings.TrimSpace(msg.Sponsor) == "" { return ErrEmptySponsor } ``` This could potentially result in assigning the zero address to the `sponsorAddr` when updating the sponsor information of a specific pool if the actual `sponsorAddr` is zero.
Open the reportFiles: - `x/shield/keeper.go` Commit: - [`3a7e28ae8dec6bbf4d7b35252a745b82a463f781`](https://github.com/shentufoundation/shentu/tree/3a7e28ae8dec6bbf4d7b35252a745b82a463f781) In the `SecureCollaterals` function, if the provided `purchaseId` does not exist in the purchase list, there is a potential risk of misinterpreting a purchase record with an index of zero (the default value when it is declared) as the intended purchase. ```go=45 var index int for i, entry := range purchaseList.Entries { if entry.PurchaseId == purchaseID { index = i break } } purchase := &purchaseList.Entries[index] if lossAmt.GT(purchase.Shield) { return types.ErrNotEnoughShield } ```
Open the reportThe following test fails with an error indicating a payout was not made from unbonding: ```shell $ go test ./app -run TestFullAppSimulation -Enabled=true -NumBlocks=500 -Genesis= -Verbose=true -Commit=true -Seed=2989 -Period=50 -ExportParamsPath /tmp/sim-logs-2148526441/sim_params-2989.json -ExportStatePath /tmp/sim-logs-2148526441/sim_state-2989.json -v -timeout 24h ... D[2023-12-29|15:30:56.979] absent validator module=x/slashing height=66 validator=cosmosvalcons1r94y8vq7tkftf7r0ey09x77xer8ynz7nrdhg4y missed=66 threshold=0 D[2023-12-29|15:30:56.979] absent validator module=x/slashing height=66 validator=cosmosvalcons1h86dyzynpcfmjsgvmtkzfxupzz078fnlr60fru missed=25 threshold=0 I[2023-12-29|15:30:56.979] ignored equivocation; validator already tombstoned module=x/evidence validator=cosmosvalcons18w9cg8yt7ucrf23hprea64e4lttxq2l9wwtpd2 infraction_height=9 infraction_time="5372-04-29 14:44:18 +0000 UTC" Simulating... block 66/500, operation 50/285. Logs to writing to /home/ubuntu/.simapp/simulations/2023-12-29_15:30:58.log simulate.go:299: error on block 66/500, operation (62/285) from x/gov: recovered: exact pay out was not made from unbondings stack: goroutine 10 [running]: runtime/debug.Stack() /usr/local/go/src/runtime/debug/stack.go:24 +0x65 github.com/cosmos/cosmos-sdk/baseapp.newDefaultRecoveryMiddleware.func1({0x1b985c0, 0x28c77a0}) /home/ubuntu/go/pkg/mod/github.com/cosmos/cosmos-sdk@v0.45.11/baseapp/recovery.go:71 +0x27 github.com/cosmos/cosmos-sdk/baseapp.newRecoveryMiddleware.func1({0x1b985c0?, 0x28c77a0?}) /home/ubuntu/go/pkg/mod/github.com/cosmos/cosmos-sdk@v0.45.11/baseapp/recovery.go:39 +0x30 github.com/cosmos/cosmos-sdk/baseapp.processRecovery({0x1b985c0, 0x28c77a0}, 0xc002ad6900?) /home/ubuntu/go/pkg/mod/github.com/cosmos/cosmos-sdk@v0.45.11/baseapp/recovery.go:28 +0x37 github.com/cosmos/cosmos-sdk/baseapp.processRecovery({0x1b985c0, 0x28c77a0}, 0x28f9060?) /home/ubuntu/go/pkg/mod/github.com/cosmos/cosmos-sdk@v0.45.11/baseapp/recovery.go:33 +0x5e github.com/cosmos/cosmos-sdk/baseapp.(*BaseApp).runTx.func1() /home/ubuntu/go/pkg/mod/github.com/cosmos/cosmos-sdk@v0.45.11/baseapp/baseapp.go:632 +0x110 panic({0x1b985c0, 0x28c77a0}) /usr/local/go/src/runtime/panic.go:884 +0x213 github.com/shentufoundation/shentu/v2/x/shield/keeper.Keeper.MakePayoutByProviderDelegations({{0x28cfa60, 0xc000593820}, {0x28f74d0, 0xc00112a610}, {0x28ecd60, 0xc001131e60}, {0x28f4d68, 0xc0004bc780}, {0x2902c48, 0xc0001f23c0}, ...}, ...) /home/ubuntu/shentu/x/shield/keeper/proposal.go:454 +0x78c github.com/shentufoundation/shentu/v2/x/shield/keeper.Keeper.CreateReimbursement({{0x28cfa60, 0xc000593820}, {0x28f74d0, 0xc00112a610}, {0x28ecd60, 0xc001131e60}, {0x28f4d68, 0xc0004bc780}, {0x2902c48, 0xc0001f23c0}, ...}, ...) /home/ubuntu/shentu/x/shield/keeper/proposal.go:288 +0x805 github.com/shentufoundation/shentu/v2/x/shield.handleShieldClaimProposal({{0x28e8cb8, 0xc000056078}, {0x28f9060, 0xc007abf0c0}, {{0x0, 0x0}, {0x1e9b819, 0xe}, 0x42, {0x0, ...}, ...}, ...}, ...) /home/ubuntu/shentu/x/shield/handler.go:92 +0xed github.com/shentufoundation/shentu/v2/x/shield.NewShieldClaimProposalHandler.func1({{0x28e8cb8, 0xc000056078}, {0x28f9060, 0xc007abf0c0}, {{0x0, 0x0}, {0x1e9b819, 0xe}, 0x42, {0x0, ...}, ...}, ...}, ...) /home/ubuntu/shentu/x/shield/handler.go:80 +0x9d github.com/shentufoundation/shentu/v2/x/gov/keeper.Keeper.SubmitProposal({{{0x7fab585f5ff0, 0xc001177980}, {0x28e9fc0, 0xc0001383f0}, {0x28f18b0, 0xc0004bca80}, {0x7fab585f6018, 0xc0001f2f00}, {0x0, 0x0}, ...}, ...}, ...) /home/ubuntu/shentu/x/gov/keeper/proposal.go:103 +0x2c8 github.com/shentufoundation/shentu/v2/x/gov/keeper.msgServer.SubmitProposal({{{{0x7fab585f5ff0, 0xc001177980}, {0x28e9fc0, 0xc0001383f0}, {0x28f18b0, 0xc0004bca80}, {0x7fab585f6018, 0xc0001f2f00}, {0x0, 0x0}, ...}, ...}}, ...) /home/ubuntu/shentu/x/gov/keeper/msg_server.go:35 +0x238 github.com/cosmos/cosmos-sdk/x/gov/types._Msg_SubmitProposal_Handler.func1({0x28e8d28, 0xc0070a0120}, {0x1e3fb20?, 0xc0055539b0}) /home/ubuntu/go/pkg/mod/github.com/cosmos/cosmos-sdk@v0.45.11/x/gov/types/tx.pb.go:527 +0x78 github.com/cosmos/cosmos-sdk/baseapp.(*MsgServiceRouter).RegisterService.func2.1({0x28e8d28, 0xc0070a00c0}, {0x526b06?, 0x40d88b?}, 0x1e734e0?, 0xc000548888) /home/ubuntu/go/pkg/mod/github.com/cosmos/cosmos-sdk@v0.45.11/baseapp/msg_service_router.go:113 +0x90 github.com/cosmos/cosmos-sdk/x/gov/types._Msg_SubmitProposal_Handler({0x1e7a900?, 0xc001136800}, {0x28e8d28, 0xc0070a00c0}, 0x2696698, 0xc00485a080) /home/ubuntu/go/pkg/mod/git
Open the reportContent Security Policy (CSP) is a critical defense against Cross-Site Scripting (XSS) and data injection attacks. If the CSP header is missing from an HTTP response, it means the site is not able to effectively restrict the loading of malicious scripts, which makes it more vulnerable to a variety of cyberattacks such as XSS, data injection, clickjacking, and more.
Open the reportThe "api/v1/multisig/acoount/rename" api endpoint of the https://multisig.shentu.org does not effectively filter the data submitted by users, which may lead to potential risks of XSS and SQL injection.
Open the reportRepository: - multisig-service File: - util/http.go The `HttpGetReq` function in its current form sets an HTTP client timeout of 10 minutes (Line 17). ```go=10 func HttpGetReq(url string) ([]byte, error) { client := &http.Client{} req, err := http.NewRequest("GET", url, nil) if err != nil { Logger.Errorw("", zap.Error(err)) return nil, err } client.Timeout = time.Minute * 10 res, err := client.Do(req) if err != nil { Logger.Errorw("", zap.Error(err)) return nil, err } defer res.Body.Close() body, err := io.ReadAll(res.Body) if err != nil { Logger.Errorw("", zap.Error(err)) return nil, err } //Logger.Debugw("", zap.Any("body", string(body))) return body, nil } ``` This duration is excessive for most use cases, where responses are generally expected within seconds. Setting a long timeout can lead to unnecessary resource consumption, especially in systems handling multiple concurrent requests. Additionally, it could cause delays in detecting network or server issues, thus impacting the overall performance of the application.
Open the reportRepository: - multisig-service Files: - service/transactions.go The `CreateTx` function includes a validation check that prevents the creation of transactions with the same `txContent` for the same `MultisigAddress`. While this is useful for avoiding duplicate transactions, it may restrict legitimate cases where the same transaction content needs to be submitted multiple times (e.g., due to external errors or retries). This could lead to user frustration or blocked transactions when the same content is expected to be processed under different circumstances. ```go=51 pTx, err := p.Select(p.ID).Where(p.WalletAddress.Eq(req.MultisigAddress), p.TxContent.Eq(string(txContent))).First() if err == nil { s.logger.Errorf("has already uploaded tx content, pending id:%s", pTx.ID) return nil, errno.Err(errno.InvalidRequestErr, "already uploaded tx content") } ``` Additionally, this validation logic does not take into account other relevant factors like transaction `Deleted` status that might warrant processing the same `txContent` again.
Open the reportRepository: - multisig-service File: - util/token.go The `CreateToken` function in the current implementation of JWT (JSON Web Token) only includes the `ExpiresAt` claim. This omission of other critical claims such as `Issuer`, `Subject`, `Audience`, `IssuedAt`, and `NotBefore` reduces the security and flexibility of the token. ```go=16 func CreateToken(hexAddr string) (string, error) { expTime := time.Now().Add(time.Hour * 720).Unix() claims := CustomClaims{ HexAddr: hexAddr, StandardClaims: jwt.StandardClaims{ ExpiresAt: expTime, }, } token := jwt.NewWithClaims(jwt.SigningMethodHS256, claims) tokenString, err := token.SignedString(signingTokenKey) return tokenString, err } ``` Without claims like `Audience` or `Issuer`, it's difficult to ensure that the token is used only by the intended recipient or in the correct system. This can increase the risk of token misuse or forgery, especially in distributed or multi-service environments
Open the reportRepository: - multisig-service File: - service/account.go The `CreateAccount` function in the multisig service allows users to specify a list of members for a multisig account. However, there is no deduplication check in place for the members list. This means that the same member can be added multiple times, potentially leading to incorrect account configurations and security issues. ```go=69 for _, member := range req.Members { if len(member.NickName) > common.NickNameLength { s.logger.Errorf("too long member name:%s", member.NickName) return errno.Err(errno.InvalidRequestErr, "member name to long") } if member.Address == caller { isFoundCaller = true continue } memberHrp, _, err := bech32.DecodeAndConvert(member.Address) if err != nil { s.logger.Errorf("invalid member address:%s", member) return errno.Err(errno.InvalidRequestErr, fmt.Sprintf("invalid %s member address", member)) } if memberHrp != chainInfo.Bech32Prefix { s.logger.Errorf("caller bech32 prefix:%s not equal to multisig member address prefix:%s", chainInfo.Bech32Prefix, memberHrp) return errno.Err(errno.InvalidRequestErr, "The address prefix is inconsistent") } } ``` Having duplicate members in the members list can lead to unintended behavior, such as skewing the threshold calculations or granting more influence to a single member than intended. This compromises the integrity and security of the multisig account.
Open the reportRepository: - multisig-service File: - service/chain_registry.go In the `createAccounts` function, there is no explicit validation to check whether an account already exists before attempting to create it. Although this does not lead to duplicate accounts being created due to the database constraints, the function relies on the database to throw an error when a duplicate account creation is attempted. This approach may cause unnecessary database operations and error handling.
Open the reportRepository: - deepwallet In the keplr-wallet codebase, several issues were identified and resolved following the release of version v0.12.123. However, these fixes have not been implemented in the deepwallet codebase. While these issues may not be critical, addressing them would enhance the project's resilience and overall robustness. - [*Fix an issue if signature v is 0 or 1*](https://github.com/chainapsis/keplr-wallet/commit/420a1dcb833ca4b02cc67fbd134a137d16005a29) - [*Fix an issue that message event args is not object*](https://github.com/chainapsis/keplr-wallet/commit/cfefd5233630d95d6dd9821bcdb5514f60dd8a56) - [*Throw error if request method parameter is not a string Beta*](https://github.com/chainapsis/keplr-wallet/commit/68f13e6dfddc55b4ad1d9ae9e0f2d5377aaac7aa) - [*Fix an issue that ledger sign evm tx failed if tx is legacy*](https://github.com/chainapsis/keplr-wallet/commit/b6eac519c6d97f2e2e81d164a1daf6ee52d0d378)
Open the reportIn the oracle module, a proposer is assigned to each operator when the operator is created: ```go=9 // NewOperator returns an Operator object. func NewOperator(address sdk.AccAddress, proposer sdk.AccAddress, collateral sdk.Coins, accumulatedRewards sdk.Coins, name string) Operator { return Operator{ Address: address.String(), Proposer: proposer.String(), Collateral: collateral, AccumulatedRewards: accumulatedRewards, Name: name, } } ``` The naming implies the proposer is the one who proposes the new operator. However, `MsgCreateOperator` requires `m.Address` (i.e. the operator account) as the signer: ```go=74 // GetSigners defines whose signature is required. func (m MsgCreateOperator) GetSigners() []sdk.AccAddress { addr := sdk.MustAccAddressFromBech32(m.Address) return []sdk.AccAddress{addr} } ``` Moreover, there are no restrictions or checks on the proposer field, meaning the operator can assign any value (for example, the certifier account) as its proposer. This may be misleading to users. Given the issues identified in the oracle module's design (refer to findings ABO-01 and TAS-01 for details), the auditing team seeks clarification from the Shentu team. Specifically, the auditing team want to confirm whether the design intends to restrict proposal rights exclusively to certain roles, such as certifiers or Oracle admins.
Open the reportFiles: - `x/oracle/keeper/task.go` - `x/oracle/types/params.go` Commit: - [`3a7e28ae8dec6bbf4d7b35252a745b82a463f781`](https://github.com/shentufoundation/shentu/tree/3a7e28ae8dec6bbf4d7b35252a745b82a463f781) The `TotalValidTaskCollateral` function determines the total valid collateral for a task across three scenarios: when the task score matches `MinScore`, when the task score is below `ThresholdScore`, and when the task score is equal to or exceeds `ThresholdScore`. ```go=17 var ( MinScore = sdk.NewInt(0) MaxScore = sdk.NewInt(100) DefaultThresholdScore = sdk.NewInt(50) DefaultAggregationResult = sdk.NewInt(50) DefaultExpirationDuration = time.Duration(24) * time.Hour DefaultAggregationWindow = int64(20) DefaultEpsilon1 = sdk.NewInt(1) DefaultEpsilon2 = sdk.NewInt(100) DefaultShortcutQuorum = sdk.NewDecWithPrec(50, 2) DefaultLockedInBlocks = int64(30) DefaultMinimumCollateral = int64(50000) ) ``` With MinScore defaulting to 0 and `ThresholdScore` set to 50, the total valid collateral amount for the scenario where the task score falls below `ThresholdScore` inherently includes the total valid collateral amount for the scenario where the task score equals `MinScore`. This implies that the collateral amount associated with an operator responding with a `MinScore` will also be considered as the total valid collateral amount for the scenario where the task score falls below `ThresholdScore`. ```go=537 } else if task.GetScore() < taskParams.ThresholdScore.Int64() { for _, response := range responses { if response.Score.LT(taskParams.ThresholdScore) { operatorAddr := sdk.MustAccAddressFromBech32(response.Operator) collateral, err := k.GetCollateralAmount(ctx, operatorAddr) if err != nil { continue } totalValidTaskCollateral = totalValidTaskCollateral.Add( amplifier.Mul(collateral).Quo(response.Score.Add(taskParams.Epsilon1)), ) } } } ``` This aspect of the current design could potentially impact an operator's likelihood of successfully scoring a task with a `MinScore`, thereby increasing their chances of receiving a bounty. The auditing team would like to check with the Shentu team if this is an intended design.
Open the reportFiles: - `x/shield/keeper/pool.go` - `x/shield/types/shield.pb.go` Commit: - [`3a7e28ae8dec6bbf4d7b35252a745b82a463f781`](https://github.com/shentufoundation/shentu/tree/3a7e28ae8dec6bbf4d7b35252a745b82a463f781) According to the comments in the `CreatePool` function, the purpose of this function is to create a pool and the sponsor's shield. However, in the actual logic implementation, the shield is paid by the admin account instead of the sponsor. The audit team seeks clarification from the Shentu team to confirm if this aligns with the original design. ```go=169 // CreatePool creates a pool and sponsor's shield. func (k Keeper) CreatePool(ctx sdk.Context, creator sdk.AccAddress, shield sdk.Coins, serviceFees sdk.Coins, sponsor string, sponsorAddr sdk.AccAddress, description string, shieldLimit sdk.Int) (uint64, error) { admin := k.GetAdmin(ctx) if !creator.Equals(admin) { return 0, types.ErrNotShieldAdmin } if _, found := k.GetPoolsBySponsor(ctx, sponsor); found { return 0, types.ErrSponsorAlreadyExists } // Set the new project pool. poolID := k.GetNextPoolID(ctx) pool := types.NewPool(poolID, description, sponsor, sponsorAddr, shieldLimit, sdk.ZeroInt()) k.SetPool(ctx, pool) k.SetNextPoolID(ctx, poolID+1) // Purchase shield for the pool. if _, err := k.purchaseShield(ctx, poolID, shield, "shield for sponsor", creator, serviceFees, sdk.NewCoins()); err != nil { return poolID, err } return poolID, nil } ``` Furthermore, considering the context of the `shield` module, there are no functionalities related to the sponsor. The audit team would appreciate more clarification from the Shentu team regarding the role and functionality of the sponsor in this context.
Open the reportThe current codebase relies on the following dependency that has been disclosed with security issues: - The ibc-go version below `v6.1.0` is affected by the [`huckleberry`](https://forum.cosmos.network/t/ibc-security-advisory-huckleberry/10731) and [`ViperStrike`](https://github.com/cosmos/ibc-apps/security/advisories/GHSA-w6rp-vxj2-fjhr) that could lead to potential chain-halt due to error non-determinism. **Reference**: - https://forum.cosmos.network/t/ibc-security-advisory
Open the reportFiles: - `x/shield/keeper/pool.go` Commit: - [`3a7e28ae8dec6bbf4d7b35252a745b82a463f781`](https://github.com/shentufoundation/shentu/tree/3a7e28ae8dec6bbf4d7b35252a745b82a463f781) In the shield module, the absence of validation for the amount of `serviceFees` in the function allows the admin to potentially call it and create a pool, purchasing any amount of the shield without paying any service fees. This could result in an unfair situation for other purchasers. ```go=186 func (k Keeper) CreatePool(ctx sdk.Context, creator sdk.AccAddress, shield sdk.Coins, serviceFees sdk.Coins, sponsor string, sponsorAddr sdk.AccAddress, description string, shieldLimit sdk.Int) (uint64, error) { admin := k.GetAdmin(ctx) if !creator.Equals(admin) { return 0, types.ErrNotShieldAdmin } if _, found := k.GetPoolsBySponsor(ctx, sponsor); found { return 0, types.ErrSponsorAlreadyExists } // Set the new project pool. poolID := k.GetNextPoolID(ctx) pool := types.NewPool(poolID, description, sponsor, sponsorAddr, shieldLimit, sdk.ZeroInt()) k.SetPool(ctx, pool) k.SetNextPoolID(ctx, poolID+1) // Purchase shield for the pool. if _, err := k.purchaseShield(ctx, poolID, shield, "shield for sponsor", creator, serviceFees, sdk.NewCoins()); err != nil { return poolID, err } return poolID, nil } ``` The audit team would like to confirm with the Shentu team whether this is aligned with the original design.
Open the reportFiles: - `x/oracle/keeper/pool.go` Commit: - [`3a7e28ae8dec6bbf4d7b35252a745b82a463f781`](https://github.com/shentufoundation/shentu/tree/3a7e28ae8dec6bbf4d7b35252a745b82a463f781) The negative check below is redundant because the panic will already be triggered by the `currentCollateral.Sub(decrement)` if any negative value exists within the coins. ```go=54 if currentCollateral.IsAnyNegative() { return types.ErrNoEnoughTotalCollateral } ```
Open the reportThe bounty module enables whitehats to submit findings for registered programs. The findings will include the following information when submitted: ```go=14 func NewFinding(pid, fid, title, detail, hash string, operator sdk.AccAddress, createTime time.Time, level SeverityLevel) (Finding, error) { return Finding{ ProgramId: pid, FindingId: fid, Title: title, FindingHash: hash, SubmitterAddress: operator.String(), SeverityLevel: level, Status: FindingStatusSubmitted, Detail: detail, CreateTime: createTime, }, nil } ``` (Meanwhile, `Description` and `ProofOfConcept` are not provided until the finding is published.) Public visibility of this information could pose risks to the program, particularly when a high-severity issue is reported. Even if the whitehat doesn't disclose specific vulnerability details in the 'Detail' field, the mere claim of a high-severity finding may attract increased attention from potential hackers to the targeted program. Additionally, since the confirmation and payment processes appear to be mostly conducted off-chain, our auditing team is interested in obtaining more information about the use of the bounty module. This will help us better comprehend the security implications associated with it.
Open the reportEach struct packs its members in 256-bit chunks. The Result struct contains the score (uint8) and the expiration (uint256) members, thus reserving two 256-bit chunks in storage.
Open the reportThe linked functions should check the value of their respective `contractAddress` parameter.
Open the reportIf the compiler version is above `0.6.0`, then the contract raises a compilation error due to the `fallback()` function in the Proxy.sol file.
Open the reportIf the compiler version is between `0.4.21` and `0.4.26`, then the contract raises a compilation error due to the keyword `payable` .
Open the reportIf the compiler version is between `0.4.21` and `0.4.26`, then the contract raises a compilation error.
Open the reportSince an empty `bytes4` variable, i.e. 0, points to the default score of a contract, it is possible to have the same score applied to a function of the contract as well. The "identifier" of a contract is simply the first 4 bytes of the `keccak256` hash of the signature, meaning that an attacker would simply need to generate a function signature that results in a `keccak256` hash with 4 leading zeroes which is not an impossible achievement.
Open the reportThe linked variable is prefixed with an underscore yet is declared as public .
Open the reportThe linked variable is prefixed with an underscore yet is declared as public.
Open the reportAn unlocked compiler version in the source code of the contract permits the user to compile it at or above a particular version. This, in turn, leads to differences in the generated bytecode between compilations due to differing compiler version numbers.This can lead to an ambiguity when debugging as compiler specific bugs may occur in the codebase that would be hard to identify over a span of multiple compiler versions rather than a specific one.
Open the reportIf the compiler version is between `0.4.21` and `0.4.26`, then the contract raises a compilation error due to the keyword `payable`.
Open the reportAn unlocked compiler version in the source code of the contract permits the user to compile it at or above a particular version. This, in turn, leads to differences in the generated bytecode between compilations due to differing compiler version numbers. This can lead to an ambiguity when debugging as compiler specific bugs may occur in the codebase that would be hard to identify over a span of multiple compiler versions rather than a specific one.
Open the reportThe linked variable is redundantly stored in `storage` , as `storage` look-ups make the gas price higher.
Open the reportThe reports name a repository or files. Open a report to see its scope.
Is this your project? Show this record on your own website. The label states what the record holds, links to this page and updates by itself. It is free and needs no sign-in.
Automatic: the three strongest facts of this project’s own record (its last audit and two more), highlighted. They update with the record. Picking a fact turns Automatic off.
<head><script src="https://widget.trustblock.run/project-widget.min.js" defer></script></head><body><divdata-tb-project-widgetdata-tb-project-slug="shentu-chain"></div></body>
Paste the code where the label should appear. It uses no API credit and updates by itself.
Something wrong on this page? A missing report, a wrong date, a finding that was fixed since. Tell us and we check it against its source.
Tell us