这是一篇面向 Soroban Registry API 和智能合约开发的安全最佳实践文档,系统介绍了认证授权、API 安全、合约审计、密钥管理、输入校验、加密与安全头等关键措施。内容覆盖了从开发、部署到运维的完整安全流程,并给出多语言示例代码和部署前检查清单。
本文档涵盖了使用 Soroban Registry API、部署智能合约以及处理敏感数据的安全最佳实践。遵循这些指南有助于保护你的应用程序、用户以及更广泛的 Stellar 生态系统。
目前,Soroban Registry API 对读取操作是公开可访问的,无需认证。未来版本将实现 API key 认证。
API Key 认证:
GET /api/contracts/{id}
Authorization: Bearer your-api-key-here
最佳实践:
该 registry 实现基于角色的访问控制(RBAC):
| Role | Permissions |
|---|---|
| Anonymous | 读取公开合约、搜索、查看已验证源码 |
| Registered User | 发布合约、验证合约、管理自己的合约 |
| Publisher | 以已验证身份发布、管理 publisher profile |
| Admin | 管理所有合约、用户、审核内容 |
最小权限原则:始终只使用你的用例所需的最小权限。
在生产环境中,绝不要通过 HTTP 发送请求:
// ✅ 正确
const API_URL = 'https://registry.soroban.example';
// ❌ 错误
const API_URL = 'http://registry.soroban.example';
确保你的 HTTP client 会验证 TLS certificates:
import requests
## ✅ 正确(默认)
response = requests.get(url, verify=True)
## ❌ 错误 - 禁用证书验证
response = requests.get(url, verify=False)
始终设置超时以防止请求挂起:
import requests
response = requests.get(
url,
timeout=30 # 30 秒超时
)
const response = await fetch(url, {
signal: AbortSignal.timeout(30000) // 30 秒超时
});
绝不要将用户输入直接插入 API 请求中:
## ❌ 错误 - 易受注入攻击
contract_id = user_input
url = f"https://api.example/contracts/{contract_id}"
## ✅ 正确 - 先验证
import re
if re.match(r'^C[A-Z0-9]{55}$', user_input):
contract_id = user_input
url = f"https://api.example/contracts/{contract_id}"
else:
raise ValueError("Invalid contract ID")
不要在错误消息中暴露敏感信息:
## ❌ 错误 - 暴露敏感数据
except Exception as e:
return f"Error: {e}, API_KEY: {API_KEY}, DB_CONN: {DB_CONN}"
## ✅ 正确 - 清理错误消息
except Exception as e:
logger.error(f"API error: {e}", extra={"correlation_id": correlation_id})
return {"error": "An error occurred. Please try again.", "correlation_id": correlation_id}
在发布之抢跑 soroban-lint:
soroban-lint scan src/ --all-rules
需要检查的常见漏洞:
| Vulnerability | Risk | Prevention |
|---|---|---|
| 缺少 auth 检查 | 未经授权访问 | 使用 env.current_contract_address() 检查 |
| 整数溢出 | 计算错误 | 使用 checked arithmetic |
| Reentrancy | 状态损坏 | 使用 checks-effects-interactions 模式 |
| 无界循环 | 通过 gas 耗尽导致 DoS | 设置最大迭代限制 |
| Unsafe unwrap | panic/合约停止 | 使用 ? 操作符或适当的错误处理 |
## 运行所有测试
cargo test
## 使用 fuzzing 测试
cargo fuzz
## 在 testnet 上进行集成测试
soroban contract invoke --id $CONTRACT --network testnet
部署后始终验证你的合约:
soroban-registry verify \
--contract-id $CONTRACT_ID \
--source ./src \
--network mainnet
为什么? 验证可以向用户证明已部署的 bytecode 与公开源代码一致。
// ❌ 错误 - 硬编码 secrets
const ADMIN_KEY: &str = "SDFY3LK...PRIVATE_KEY";
// ✅ 正确 - 作为参数传入
pub fn admin_action(env: Env, admin: Address) {
admin.require_auth();
// ...
}
pub fn transfer(env: Env, from: Address, to: Address, amount: i128) {
// ✅ 验证输入
if amount <= 0 {
panic!("Amount must be positive");
}
if from == to {
panic!("Cannot transfer to self");
}
// 执行转账
}
如果你的应用通过 registry 收集个人数据:
| Classification | Examples | Protection Required |
|---|---|---|
| Public | 合约地址、已验证源码 | 无 |
| Internal | API metrics、usage statistics | 访问控制 |
| Confidential | 用户 emails、API keys | 加密 + 访问控制 |
| Secret | 私钥、数据库密码 | HSM/vault + 严格访问 |
## 添加到 .gitignore
.env
.env.local
*.key
*.pem
secrets/
## .env(绝不要提交这个文件)
DATABASE_URL=postgresql://user:password@localhost/db
API_KEY=your-api-key-here
STELLAR_SECRET_KEY=SDFY3LK...
import os
from dotenv import load_dotenv
load_dotenv()
DATABASE_URL = os.getenv('DATABASE_URL')
API_KEY = os.getenv('API_KEY')
用于生产环境:
HashiCorp Vault 示例:
import hvac
client = hvac.Client(url='https://vault.example.com')
client.token = os.getenv('VAULT_TOKEN')
secret = client.secrets.kv.v2.read_secret_version(path='soroban/api-key')
API_KEY = secret['data']['data']['key']
永远不要信任用户输入。始终在服务器端进行验证。
import re
def validate_contract_id(contract_id: str) -> bool:
"""
Stellar contract IDs 是 56 个字符,以 'C' 开头
"""
pattern = r'^C[A-Z2-7]{55}$'
return bool(re.match(pattern, contract_id))
## 用法
if not validate_contract_id(user_input):
raise ValueError("Invalid contract ID format")
VALID_NETWORKS = {'mainnet', 'testnet', 'futurenet'}
def validate_network(network: str) -> bool:
return network.lower() in VALID_NETWORKS
def validate_pagination(limit: int, offset: int):
if not (1 <= limit <= 1000):
raise ValueError("Limit must be between 1 and 1000")
if offset < 0:
raise ValueError("Offset must be non-negative")
当显示用户生成内容(合约名称、描述)时:
// ✅ React 会自动转义
function ContractCard({ name, description }) {
return (
<div>
<h2>{name}</h2> {/* 自动转义 */}
<p>{description}</p>
</div>
);
}
// ❌ 危险 - 可能注入 scripts
function ContractCard({ name, description }) {
return (
<div dangerouslySetInnerHTML={{ __html: description }} />
);
}
用于 HTML sanitization:
import DOMPurify from 'dompurify';
const cleanHtml = DOMPurify.sanitize(userInput);
始终使用参数化查询:
## ❌ 错误 - 易受 SQL injection 攻击
query = f"SELECT * FROM contracts WHERE id = '{contract_id}'"
result = db.execute(query)
## ✅ 正确 - 参数化查询
query = "SELECT * FROM contracts WHERE id = $1"
result = await db.fetch_one(query, contract_id)
// ✅ 使用 sqlx 正确写法
let contract = sqlx::query_as::<_, Contract>(
"SELECT * FROM contracts WHERE contract_id = $1"
)
.bind(contract_id)
.fetch_one(&pool)
.await?;
详情请参见 API Rate Limiting。
要点:
X-RateLimit-Remaining Headerclass RateLimiter {
constructor(maxRequests, perMilliseconds) {
this.max = maxRequests;
this.window = perMilliseconds;
this.requests = [];
}
async acquire() {
const now = Date.now();
this.requests = this.requests.filter(t => t > now - this.window);
if (this.requests.length >= this.max) {
const oldestRequest = this.requests[0];
const waitTime = oldestRequest + this.window - now;
await new Promise(resolve => setTimeout(resolve, waitTime));
return this.acquire();
}
this.requests.push(now);
}
}
// 用法:每分钟 100 次请求
const limiter = new RateLimiter(100, 60000);
async function apiCall(url) {
await limiter.acquire();
return fetch(url);
}
正确配置 CORS 以防止未经授权的跨域请求:
// 后端 CORS 配置
use tower_http::cors::{CorsLayer, Any};
let cors = CorsLayer::new()
.allow_origin("https://app.example.com".parse::<HeaderValue>().unwrap())
.allow_methods([Method::GET, Method::POST])
.allow_headers([AUTHORIZATION, CONTENT_TYPE]);
生产环境:白名单指定 origins
开发环境:可以使用 allow_origin(Any),但绝不要用于生产环境
向前端添加 CSP headers:
<meta http-equiv="Content-Security-Policy"
content="default-src 'self';
script-src 'self' 'unsafe-inline';
style-src 'self' 'unsafe-inline';
img-src 'self' data: https:;
connect-src 'self' https://registry.soroban.example;">
## 防止 clickjacking
X-Frame-Options: DENY
## 防止 MIME sniffing
X-Content-Type-Options: nosniff
## 启用 XSS protection
X-XSS-Protection: 1; mode=block
## 强制 HTTPS
Strict-Transport-Security: max-age=31536000; includeSubDomains
## Referrer policy
Referrer-Policy: strict-origin-when-cross-origin
数据库加密:
示例:在数据库中加密 API keys
from cryptography.fernet import Fernet
## 生成 key(安全存储,不要写在代码中!)
ENCRYPTION_KEY = os.getenv('ENCRYPTION_KEY')
cipher = Fernet(ENCRYPTION_KEY)
def encrypt_api_key(api_key: str) -> bytes:
return cipher.encrypt(api_key.encode())
def decrypt_api_key(encrypted_key: bytes) -> str:
return cipher.decrypt(encrypted_key).decode()
始终使用 TLS 1.2 或更高版本:
## Nginx TLS 配置
ssl_protocols TLSv1.2 TLSv1.3;
ssl_ciphers HIGH:!aNULL:!MD5;
ssl_prefer_server_ciphers on;
证书管理:
在部署到生产环境之前使用此检查清单:
.env 和 .env.local 已加入 .gitignorecargo audit、npm audit)审查代码时,请检查:
定期扫描漏洞:
## 安装 cargo-audit
cargo install cargo-audit
## 扫描漏洞
cargo audit
## 自动修复(如果可用)
cargo audit fix
## 扫描
npm audit
## 自动修复
npm audit fix
## 仅针对生产依赖
npm audit --production
## 安装 safety
pip install safety
## 扫描
safety check
## 检查特定文件
safety check -r requirements.txt
保持所有软件为最新:
## Rust toolchain
rustup update
## Soroban CLI
cargo install soroban-cli --force
## 依赖
cargo update
npm update
自动化更新:
如果你发现了安全漏洞:
最后更新: 2026-02-24
保持安全!🔒
- 原文链接: github.com/ALIPHATICHYD/...
- 登链社区 AI 助手,为大家转译优秀英文文章,如有翻译不通的地方,还请包涵~
如果觉得我的文章对您有用,请随意打赏。你的支持将鼓励我继续创作!