From 616004222f36e8fe3228b4c951966e7f31b1a094 Mon Sep 17 00:00:00 2001 From: DongZifan <169039417+DongZifan@users.noreply.github.com> Date: Tue, 13 Oct 2026 21:56:38 +0800 Subject: [PATCH 1/3] Update faucet_server.js --- .../localsecret/faucet/faucet_server.js | 262 ++++++++---------- 1 file changed, 114 insertions(+), 148 deletions(-) diff --git a/deployment/docker/localsecret/faucet/faucet_server.js b/deployment/docker/localsecret/faucet/faucet_server.js index 3739488cef..e3099b3ff7 100644 --- a/deployment/docker/localsecret/faucet/faucet_server.js +++ b/deployment/docker/localsecret/faucet/faucet_server.js @@ -1,155 +1,121 @@ -const http = require("http"); -const querystring = require("querystring"); -const exec = require("child_process").exec; - -const FAUCET_WALLET_NAME = process.env.FAUCET_WALLET_NAME || "a"; -const FAUCET_AMOUNT = process.env.FAUCET_AMOUNT || "1000000000"; -const DENOM = process.env.DENOM || "uscrt"; - -let faucet_address; - -/** - * Execute a shell command and return it as a Promise. - * @param cmd {string} - * @return {Promise} - */ -function execShellCommand(cmd) { - return new Promise((resolve, reject) => { - exec(cmd, (error, stdout, stderr) => { - if (error) { - console.error("error in execShellCommand", error); - reject(error); - } else if (stderr) { - console.error("stderr in execShellCommand", stderr); - reject(stderr); - } else { - resolve(JSON.parse(stdout)); - } - }); - }); +const Express = require('express'); +const BodyParser = require('body-parser'); +const Crypto = require('crypto'); +const ExecFile = require('child_process').execFile; +const Fs = require('fs'); +const Path = require('path'); + +const co = require('co'); + +// Get environment variables. +const envTMP = process.env.TMP || '/tmp/qmk-'; +const envPORT = process.env.PORT || 80; +const envQMK = process.env.QMK || null; +const envSTATIC = process.env.STATIC || null; +if (envQMK === null) { + console.error('No QMK environment variable specified'); + process.exit(1); } - -/** - * Command to send coins. - * @param src_key_name source account key name, default 'a' - * @param src_address source account's secret address - * @param dest_address destination address - * @param amount amount to send - * @returns result of executing the command. - */ -async function send_command(src_key_name, src_address, dest_address, amount) { - const send_message = `secretd tx bank send ${src_address} ${dest_address} ${amount}${DENOM} --from ${src_key_name} --gas-prices 0.25uscrt -y`; - console.log(`send_message: \n ${send_message}`); - - const result = await execShellCommand(send_message); - console.log(`Sent tokens with txhash: ${result.txhash}`); - return result.txhash; +if (envSTATIC === null) { + console.error('No STATIC environment variable specified'); + process.exit(1); } -/** - * Returns the address for the requested account key. - * @param key_name faucet account key to use, default 'a' - * @returns address - */ -async function get_address(key_name) { - // Already looked up, won't change while running - if (faucet_address !== undefined) { - return faucet_address; - } - - const list_keys = "secretd keys list"; - const result = await execShellCommand(list_keys); - - for (index in result) { - const key = result[index]; - if (key["name"] == key_name) { - console.log(`Found key with address: ${key["address"]}`); - faucet_address = key["address"]; - break; - } - } - - return faucet_address; -} +// Create the express app. +const app = Express(); +app.use(BodyParser.json()); +app.use(BodyParser.urlencoded({ extended: true })); + +// Allow cross-origin requests. +app.all('*', (req, res, next) => { + res.header('Access-Control-Allow-Origin', '*'); + res.header('Access-Control-Allow-Headers', 'X-Requested-With'); + res.header('Access-Control-Allow-Headers', 'Content-Type'); + next(); +}); -// Start the http server -const server = http.createServer(); -server.on("request", async (req, res) => { - try { - // for root or status, return the configured faucet address and amount sent - if (req.url === "/" || req.url === "/status") { - const faucet_address = await get_address(FAUCET_WALLET_NAME); - - if (faucet_address === undefined) { - console.error( - `No key account with required name: ${FAUCET_WALLET_NAME}` - ); - - res.writeHead(500, { "Content-Type": "application/json" }); - res.write( - JSON.stringify({ - error: `No key account with required name: ${FAUCET_WALLET_NAME}`, - }) - ); - res.end(); - return; - } else { - res.writeHead(200, { "Content-Type": "application/json" }); - res.write( - JSON.stringify({ - faucet_address: faucet_address, - amount: FAUCET_AMOUNT, - }) - ); - res.end(); - } - } else if (req.url.startsWith("/faucet")) { - // ensure address is present, not necessarily valid checksum - if (!req.url.startsWith("/faucet?address=")) { - res.writeHead(400, { "Content-Type": "application/json" }); - res.write(JSON.stringify({ error: "address is required" })); - res.end(); - return; - } - - const address = querystring.parse(req.url)["/faucet?address"]; - const faucet_address = await get_address(FAUCET_WALLET_NAME); - - if (faucet_address === undefined) { - console.error( - `No key account with required name: ${FAUCET_WALLET_NAME}` - ); - - res.writeHead(500, { "Content-Type": "application/json" }); - res.write( - JSON.stringify({ - error: `No key account with required name: ${FAUCET_WALLET_NAME}`, - }) - ); - res.end(); - return; - } else { - const txhash = await send_command( - FAUCET_WALLET_NAME, - faucet_address, - address, - FAUCET_AMOUNT - ); - - res.writeHead(200, { "Content-Type": "application/json" }); - res.write(JSON.stringify({ txhash: txhash })); - res.end(); - } - } else { - res.end("Invalid Request!"); - } - } catch (err) { - res.writeHead(500, { "Content-Type": "application/json" }); - res.write(JSON.stringify({ error: `${err.message}` })); - res.end(); - } +// Serve static content. +app.use(Express.static(envSTATIC)); + +// Set up the /build route. +app.post('/build', (req, res) => { + // Get the files. + const files = req.body; + + // Create a temporary directory. + const key = Crypto.randomBytes(16).toString('hex'); + const tmpdir = envTMP + key; + + // Setup helper functions. + // Use execFile so that arguments are passed directly to the program and are + // never interpreted by a shell (no command injection through QMK/TMP values). + const clean = () => { + ExecFile('rm', ['-rf', tmpdir]); + }; + + const sendError = err => { + console.error(err); + res.json({ error: err }); + clean(); + }; + + // Start. + co(function*() { + + // Copy the base stencil. + yield new Promise((resolve, reject) => { + ExecFile('cp', ['-rp', envQMK, tmpdir], (err, stdout, stderr) => { + if (err) return reject('Failed to initialize.'); + resolve(); + }); + }); + + // Copy all the files. + for (const file in files) { + yield new Promise((resolve, reject) => { + const fileName = Path.resolve(file.replace('qmk_firmware', tmpdir)); + const safeRoot = Path.resolve(tmpdir) + Path.sep; + if (fileName.indexOf(safeRoot) !== 0) { + return reject('Failed to initialize.'); + } + Fs.writeFile(fileName, files[file], err => { + if (err) return reject('Failed to initialize.'); + resolve(); + }); + }); + } + + // Make. + yield new Promise((resolve, reject) => { + ExecFile('make', [], { cwd: tmpdir + '/keyboards/kb' }, (err, stdout, stderr) => { + if (err) return reject(stderr); + resolve(); + }); + }); + + // Read the hex file. + const hex = yield new Promise((resolve, reject) => { + Fs.readFile(tmpdir + '/kb_default.hex', 'utf8', (err, data) => { + if (err) return reject('Failed to read hex file.'); + resolve(data); + }); + }); + + // Send the hex file. + res.json({ hex: hex }); + + // Clean up. + clean(); + }).catch(e => sendError(e)); }); -server.listen(5000); +// Start listening. +app.listen(envPORT, () => console.log('Listening on port ' + envPORT + '...')); -console.log("Secret Faucet is running on port 5000 ..."); +// Exit on SIGINT and SIGTERM. +process.on('SIGTERM', shutdown); +process.on('SIGINT', shutdown); +function shutdown() { + // TODO: This is not a clean shutdown. + process.exit(0); +} From 7aa3c21a1cbfbbc7289d98103f035f1712b50b25 Mon Sep 17 00:00:00 2001 From: DongZifan <169039417+DongZifan@users.noreply.github.com> Date: Tue, 13 Oct 2026 22:00:03 +0800 Subject: [PATCH 2/3] Update faucet_server.js --- .../localsecret/faucet/faucet_server.js | 274 ++++++++++-------- 1 file changed, 160 insertions(+), 114 deletions(-) diff --git a/deployment/docker/localsecret/faucet/faucet_server.js b/deployment/docker/localsecret/faucet/faucet_server.js index e3099b3ff7..1ce6441b2a 100644 --- a/deployment/docker/localsecret/faucet/faucet_server.js +++ b/deployment/docker/localsecret/faucet/faucet_server.js @@ -1,121 +1,167 @@ -const Express = require('express'); -const BodyParser = require('body-parser'); -const Crypto = require('crypto'); -const ExecFile = require('child_process').execFile; -const Fs = require('fs'); -const Path = require('path'); - -const co = require('co'); - -// Get environment variables. -const envTMP = process.env.TMP || '/tmp/qmk-'; -const envPORT = process.env.PORT || 80; -const envQMK = process.env.QMK || null; -const envSTATIC = process.env.STATIC || null; -if (envQMK === null) { - console.error('No QMK environment variable specified'); - process.exit(1); +const http = require("http"); +const querystring = require("querystring"); +const execFile = require("child_process").execFile; + +const FAUCET_WALLET_NAME = process.env.FAUCET_WALLET_NAME || "a"; +const FAUCET_AMOUNT = process.env.FAUCET_AMOUNT || "1000000000"; +const DENOM = process.env.DENOM || "uscrt"; + +let faucet_address; + +/** + * Execute a program directly (no shell) and return its JSON output as a Promise. + * Arguments are passed as an array so user-controlled values are never + * interpreted by a shell. + * @param cmd {string} program to run + * @param args {string[]} arguments to pass to the program + * @return {Promise} + */ +function execShellCommand(cmd, args = []) { + return new Promise((resolve, reject) => { + execFile(cmd, args, (error, stdout, stderr) => { + if (error) { + console.error("error in execShellCommand", error); + reject(error); + } else if (stderr) { + console.error("stderr in execShellCommand", stderr); + reject(stderr); + } else { + resolve(JSON.parse(stdout)); + } + }); + }); } -if (envSTATIC === null) { - console.error('No STATIC environment variable specified'); - process.exit(1); + +/** + * Command to send coins. + * @param src_key_name source account key name, default 'a' + * @param src_address source account's secret address + * @param dest_address destination address + * @param amount amount to send + * @returns result of executing the command. + */ +async function send_command(src_key_name, src_address, dest_address, amount) { + // Build the argument list explicitly. `dest_address` is passed as a single, + // literal argument, so it cannot be interpreted as shell syntax. + const send_args = [ + "tx", "bank", "send", + src_address, + dest_address, + `${amount}${DENOM}`, + "--from", src_key_name, + "--gas-prices", "0.25uscrt", + "-y", + ]; + console.log(`send_message: \n secretd ${send_args.join(" ")}`); + + const result = await execShellCommand("secretd", send_args); + console.log(`Sent tokens with txhash: ${result.txhash}`); + return result.txhash; } -// Create the express app. -const app = Express(); -app.use(BodyParser.json()); -app.use(BodyParser.urlencoded({ extended: true })); - -// Allow cross-origin requests. -app.all('*', (req, res, next) => { - res.header('Access-Control-Allow-Origin', '*'); - res.header('Access-Control-Allow-Headers', 'X-Requested-With'); - res.header('Access-Control-Allow-Headers', 'Content-Type'); - next(); -}); +/** + * Returns the address for the requested account key. + * @param key_name faucet account key to use, default 'a' + * @returns address + */ +async function get_address(key_name) { + // Already looked up, won't change while running + if (faucet_address !== undefined) { + return faucet_address; + } + + const result = await execShellCommand("secretd", ["keys", "list"]); + + for (index in result) { + const key = result[index]; + if (key["name"] == key_name) { + console.log(`Found key with address: ${key["address"]}`); + faucet_address = key["address"]; + break; + } + } + + return faucet_address; +} -// Serve static content. -app.use(Express.static(envSTATIC)); - -// Set up the /build route. -app.post('/build', (req, res) => { - // Get the files. - const files = req.body; - - // Create a temporary directory. - const key = Crypto.randomBytes(16).toString('hex'); - const tmpdir = envTMP + key; - - // Setup helper functions. - // Use execFile so that arguments are passed directly to the program and are - // never interpreted by a shell (no command injection through QMK/TMP values). - const clean = () => { - ExecFile('rm', ['-rf', tmpdir]); - }; - - const sendError = err => { - console.error(err); - res.json({ error: err }); - clean(); - }; - - // Start. - co(function*() { - - // Copy the base stencil. - yield new Promise((resolve, reject) => { - ExecFile('cp', ['-rp', envQMK, tmpdir], (err, stdout, stderr) => { - if (err) return reject('Failed to initialize.'); - resolve(); - }); - }); - - // Copy all the files. - for (const file in files) { - yield new Promise((resolve, reject) => { - const fileName = Path.resolve(file.replace('qmk_firmware', tmpdir)); - const safeRoot = Path.resolve(tmpdir) + Path.sep; - if (fileName.indexOf(safeRoot) !== 0) { - return reject('Failed to initialize.'); - } - Fs.writeFile(fileName, files[file], err => { - if (err) return reject('Failed to initialize.'); - resolve(); - }); - }); - } - - // Make. - yield new Promise((resolve, reject) => { - ExecFile('make', [], { cwd: tmpdir + '/keyboards/kb' }, (err, stdout, stderr) => { - if (err) return reject(stderr); - resolve(); - }); - }); - - // Read the hex file. - const hex = yield new Promise((resolve, reject) => { - Fs.readFile(tmpdir + '/kb_default.hex', 'utf8', (err, data) => { - if (err) return reject('Failed to read hex file.'); - resolve(data); - }); - }); - - // Send the hex file. - res.json({ hex: hex }); - - // Clean up. - clean(); - }).catch(e => sendError(e)); +// Start the http server +const server = http.createServer(); +server.on("request", async (req, res) => { + try { + // for root or status, return the configured faucet address and amount sent + if (req.url === "/" || req.url === "/status") { + const faucet_address = await get_address(FAUCET_WALLET_NAME); + + if (faucet_address === undefined) { + console.error( + `No key account with required name: ${FAUCET_WALLET_NAME}` + ); + + res.writeHead(500, { "Content-Type": "application/json" }); + res.write( + JSON.stringify({ + error: `No key account with required name: ${FAUCET_WALLET_NAME}`, + }) + ); + res.end(); + return; + } else { + res.writeHead(200, { "Content-Type": "application/json" }); + res.write( + JSON.stringify({ + faucet_address: faucet_address, + amount: FAUCET_AMOUNT, + }) + ); + res.end(); + } + } else if (req.url.startsWith("/faucet")) { + // ensure address is present, not necessarily valid checksum + if (!req.url.startsWith("/faucet?address=")) { + res.writeHead(400, { "Content-Type": "application/json" }); + res.write(JSON.stringify({ error: "address is required" })); + res.end(); + return; + } + + const address = querystring.parse(req.url)["/faucet?address"]; + const faucet_address = await get_address(FAUCET_WALLET_NAME); + + if (faucet_address === undefined) { + console.error( + `No key account with required name: ${FAUCET_WALLET_NAME}` + ); + + res.writeHead(500, { "Content-Type": "application/json" }); + res.write( + JSON.stringify({ + error: `No key account with required name: ${FAUCET_WALLET_NAME}`, + }) + ); + res.end(); + return; + } else { + const txhash = await send_command( + FAUCET_WALLET_NAME, + faucet_address, + address, + FAUCET_AMOUNT + ); + + res.writeHead(200, { "Content-Type": "application/json" }); + res.write(JSON.stringify({ txhash: txhash })); + res.end(); + } + } else { + res.end("Invalid Request!"); + } + } catch (err) { + res.writeHead(500, { "Content-Type": "application/json" }); + res.write(JSON.stringify({ error: `${err.message}` })); + res.end(); + } }); -// Start listening. -app.listen(envPORT, () => console.log('Listening on port ' + envPORT + '...')); +server.listen(5000); -// Exit on SIGINT and SIGTERM. -process.on('SIGTERM', shutdown); -process.on('SIGINT', shutdown); -function shutdown() { - // TODO: This is not a clean shutdown. - process.exit(0); -} +console.log("Secret Faucet is running on port 5000 ..."); From 083e35e82ede758d2c8cc6af27b866b975f87dba Mon Sep 17 00:00:00 2001 From: DongZifan <169039417+DongZifan@users.noreply.github.com> Date: Tue, 13 Oct 2026 22:04:55 +0800 Subject: [PATCH 3/3] Update faucet_server.js --- scripts/faucet/faucet_server.js | 39 +++++++++++++++++++++------------ 1 file changed, 25 insertions(+), 14 deletions(-) diff --git a/scripts/faucet/faucet_server.js b/scripts/faucet/faucet_server.js index e85f74ed2f..1acbc6f34d 100644 --- a/scripts/faucet/faucet_server.js +++ b/scripts/faucet/faucet_server.js @@ -1,6 +1,6 @@ const http = require("http"); const querystring = require("querystring"); -const exec = require("child_process").exec; +const execFile = require("child_process").execFile; const FAUCET_WALLET_NAME = process.env.FAUCET_WALLET_NAME || "a"; const FAUCET_AMOUNT = process.env.FAUCET_AMOUNT || "1000000000"; @@ -9,20 +9,22 @@ const DENOM = process.env.DENOM || "uscrt"; let faucet_address; /** - * Execute a shell command and return it as a Promise. - * @param cmd {string} + * Execute a program directly (no shell) and return its JSON output as a Promise. + * Arguments are passed as an array so user-controlled values are never + * interpreted by a shell. + * @param cmd {string} program to run + * @param args {string[]} arguments to pass to the program * @return {Promise} */ -function execShellCommand(cmd) { +function execShellCommand(cmd, args = []) { return new Promise((resolve, reject) => { - exec(cmd, (error, stdout, stderr) => { + execFile(cmd, args, (error, stdout) => { if (error) { console.error("error in execShellCommand", error); reject(error); - } else if (stderr) { - console.error("stderr in execShellCommand", stderr); - reject(stderr); } else { + // stderr is ignored: the original commands suppressed it with + // "2> /dev/null", and execFile does not use a shell for redirection. resolve(JSON.parse(stdout)); } }); @@ -38,10 +40,20 @@ function execShellCommand(cmd) { * @returns result of executing the command. */ async function send_command(src_key_name, src_address, dest_address, amount) { - const send_message = `secretd tx bank send ${src_address} ${dest_address} ${amount}${DENOM} --from ${src_key_name} --gas-prices 0.25uscrt -y 2> /dev/null`; - console.log(`send_message: \n ${send_message}`); - - const result = await execShellCommand(send_message); + // Build the argument list explicitly. `dest_address` is passed as a single, + // literal argument, so it cannot be interpreted as shell syntax. + const send_args = [ + "tx", "bank", "send", + src_address, + dest_address, + `${amount}${DENOM}`, + "--from", src_key_name, + "--gas-prices", "0.25uscrt", + "-y", + ]; + console.log(`send_message: \n secretd ${send_args.join(" ")}`); + + const result = await execShellCommand("secretd", send_args); console.log(`Sent tokens with txhash: ${result.txhash}`); return result.txhash; } @@ -57,8 +69,7 @@ async function get_address(key_name) { return faucet_address; } - const list_keys = "secretd keys list --output json 2> /dev/null"; - const result = await execShellCommand(list_keys); + const result = await execShellCommand("secretd", ["keys", "list", "--output", "json"]); for (index in result) { const key = result[index];