forked from bitcoinafterlife/bal-server
docs: add comprehensive knowledge base and security audit
- Add docs/INDEX.md with navigable index and quick reference guides - Add 9 knowledge base files covering project overview, Bitcoin domain, architecture, modules, API reference, database schema, deployment/security - Update AGENTS.md with knowledge base reference and update policy - Add tests/sql_injection_tests.rs with regression tests for SQL injection - Fix SQL injection vulnerabilities in bal-pusher.rs: * Replace string-formatted UPDATE IN with loop + parameterized queries * Replace string-formatted UPDATE push_err with parameterized query * Add chain name validation in calculate_stats to prevent env var tampering - Update .gitignore to exclude bal-pusher.env and bal-pusher.sh
This commit is contained in:
@@ -33,7 +33,6 @@ const LOCKTIME_THRESHOLD: i64 = 5000000;
|
||||
const VERSION: &str = "0.0.2";
|
||||
#[derive(Debug, Clone, Serialize, Deserialize)]
|
||||
struct MyConfig {
|
||||
zmq_listener: String,
|
||||
db_file: String,
|
||||
bitcoin_dir: String,
|
||||
regtest: NetworkParams,
|
||||
@@ -49,8 +48,6 @@ struct MyConfig {
|
||||
impl Default for MyConfig {
|
||||
fn default() -> Self {
|
||||
MyConfig {
|
||||
zmq_listener: env::var("BAL_PUSHER_ZMQ_LISTENER")
|
||||
.unwrap_or("tcp://127.0.0.1:28332".to_string()),
|
||||
db_file: env::var("BAL_PUSHER_DB_FILE").unwrap_or("bal.db".to_string()),
|
||||
bitcoin_dir: env::var("BAL_PUSHER_BITCOIN_DIR").unwrap_or("".to_string()),
|
||||
regtest: get_network_params_default(Network::Regtest),
|
||||
@@ -102,7 +99,7 @@ fn get_network_params_default(network: Network) -> NetworkParams {
|
||||
},
|
||||
Network::Testnet4 => NetworkParams {
|
||||
host: "http://i27.0.0.1".to_string(),
|
||||
port: 18332,
|
||||
port: 48332,
|
||||
dir_path: "testnet4/".to_string(),
|
||||
db_field: "testnet4".to_string(),
|
||||
cookie_file: "".to_string(),
|
||||
@@ -199,6 +196,7 @@ fn get_client(
|
||||
network: &NetworkParams,
|
||||
) -> Result<(Client, GetBlockchainInfoResult), Box<dyn StdError>> {
|
||||
let url = format!("{}:{}/", network.host, &network.port);
|
||||
debug!("trying to connect to bitcoin daemon:{url}");
|
||||
match get_client_from_username(&url, network) {
|
||||
Ok(client) => Ok(client),
|
||||
Err(_) => match get_client_from_cookie(&url, &network) {
|
||||
@@ -297,23 +295,18 @@ async fn main_result(cfg: &MyConfig, network_params: &NetworkParams) -> Result<(
|
||||
};
|
||||
}
|
||||
|
||||
if pushed_txs.len() > 0 {
|
||||
let sql = format!(
|
||||
"UPDATE tbl_tx SET status = 1 WHERE txid in ('{}');",
|
||||
pushed_txs.join("','")
|
||||
);
|
||||
trace!("sqlok: {}", &sql);
|
||||
let _ = db.execute(&sql);
|
||||
for txid in &pushed_txs {
|
||||
let sql = "UPDATE tbl_tx SET status = 1 WHERE txid = ?";
|
||||
let mut stmt = db.prepare(sql).unwrap();
|
||||
stmt.bind((1, Value::String(txid.clone()))).unwrap();
|
||||
let _ = stmt.next();
|
||||
}
|
||||
if invalid_txs.len() > 0 {
|
||||
for (txid, txerr) in &invalid_txs {
|
||||
//let _ = db.execute(format!("UPDATE tbl_tx SET status = 2 WHERE txid in ('{}'Yp);",invalid_txs.join("','")));
|
||||
let sql = format!(
|
||||
"UPDATE tbl_tx SET status = 2, push_err='{txerr}' WHERE txid = '{txid}'"
|
||||
);
|
||||
trace!("sqlerror: {}", &sql);
|
||||
let _ = db.execute(&sql);
|
||||
}
|
||||
for (txid, txerr) in &invalid_txs {
|
||||
let sql = "UPDATE tbl_tx SET status = 2, push_err = ? WHERE txid = ?";
|
||||
let mut stmt = db.prepare(sql).unwrap();
|
||||
stmt.bind((1, Value::String(txerr.clone()))).unwrap();
|
||||
stmt.bind((2, Value::String(txid.clone()))).unwrap();
|
||||
let _ = stmt.next();
|
||||
}
|
||||
let _ = send_stats_report(cfg, bcinfo).await;
|
||||
let _ = calculate_stats(&db, network_params.db_field.clone()).await;
|
||||
@@ -325,8 +318,13 @@ async fn main_result(cfg: &MyConfig, network_params: &NetworkParams) -> Result<(
|
||||
Ok(())
|
||||
}
|
||||
async fn calculate_stats(db: &Connection, chain: String) -> Result<(), reqwest::Error> {
|
||||
// Validate chain to prevent SQL injection via environment variable tampering
|
||||
if !chain.chars().all(|c| c.is_alphanumeric() || c == '-' || c == '_') || chain.is_empty() {
|
||||
error!("Invalid chain name: {chain}");
|
||||
return Ok(());
|
||||
}
|
||||
//let sql = "drop table if exists tbl_stats;";
|
||||
let sql = "DELETE FROM tbl_stats WHERE chain = '{chain}';";
|
||||
let sql = format!("DELETE FROM tbl_stats WHERE chain = '{chain}';");
|
||||
if let Err(err) = db.execute(&sql) {
|
||||
error!("error deleting from tbl_stats where chain:{chain} error: {err}");
|
||||
}
|
||||
@@ -517,9 +515,11 @@ fn parse_env_netconfig(cfg_lock: &mut MyConfig, chain: &str) -> NetworkParams {
|
||||
}
|
||||
Err(_) => {}
|
||||
}
|
||||
println!("{}",format!("BAL_PUSHER_{}_ZMQ_HASHBLOCK", chain.to_uppercase()));
|
||||
match env::var(format!("BAL_PUSHER_{}_ZMQ_HASHBLOCK", chain.to_uppercase())) {
|
||||
Ok(value) => {
|
||||
cfg.rpc_pass = value;
|
||||
println!("value:{}",value);
|
||||
cfg.zmq_listener = value;
|
||||
}
|
||||
Err(_) => {}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user