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:
2026-07-16 14:11:18 -04:00
parent 64f402eeae
commit 69d877a360
14 changed files with 1328 additions and 22 deletions

View File

@@ -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(_) => {}
}