Skip to content

Commit 68ae2cc

Browse files
🛡️ Sentinel: [CRITICAL] Fix command injection in run_bitcoin_cli
Co-authored-by: bitcoiner-dev <75873427+bitcoiner-dev@users.noreply.github.com>
1 parent 7b4c4be commit 68ae2cc

48 files changed

Lines changed: 2150 additions & 712 deletions

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

.jules/sentinel.md

Lines changed: 6 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,9 @@
1+
## 2024-03-24 - Insecure Default File Permissions
2+
**Vulnerability:** The CLI application creates sensitive configuration files and directories (like wallets and snapshot data) using standard `fs::create_dir_all` and `fs::write` in Rust. These standard functions create files/directories using the system's default umask, which typically allows other users on the same Unix-like system to read the sensitive files.
3+
**Learning:** This could lead to a local privilege escalation or exposure of sensitive user data if the user runs the CLI on a shared machine. Relying on default system configurations for sensitive files is unsafe.
4+
**Prevention:** Always use `std::os::unix::fs::DirBuilderExt` and `std::os::unix::fs::OpenOptionsExt` to explicitly set file permissions (e.g., `0o700` for directories and `0o600` for files) when creating sensitive data on disk.
5+
16
## 2024-05-24 - Command Injection via Configured `bitcoin_cli` Binary
27
**Vulnerability:** The application allowed arbitrary command execution by reading the `bitcoin_cli` command to run from a user-provided profile configuration and executing it directly with `std::process::Command::new` without validating the binary name.
38
**Learning:** Profile configurations or configuration files can often be manipulated by users. Trusting arbitrary paths or commands specified in these files can lead to remote code execution (RCE) or local privilege escalation if the application is run with elevated privileges.
4-
**Prevention:** Implement a strict whitelist on the binary name allowed to be executed when the binary path is sourced from user configuration or input. Always extract the base filename and compare it against the expected executable name (e.g., `bitcoin-cli` or `bitcoin-cli.exe`).
9+
**Prevention:** Implement a strict whitelist on the binary name allowed to be executed when the binary path is sourced from user configuration or input. Always extract the base filename and compare it against the expected executable name (e.g., `bitcoin-cli` or `bitcoin-cli.exe`).

CHANGELOG.md

Lines changed: 20 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,25 @@ All notable changes to this project will be documented in this file.
66

77
- No changes yet.
88

9+
## [0.2.1] - 2026-03-26
10+
11+
### Fixed
12+
- Updated `zinc-core` dependency pin to `=0.1.1` for compatibility with current CLI features.
13+
- Removed local `path` dependency override for release packaging so `cargo package`/`cargo publish` resolve from crates.io in CI.
14+
15+
## [0.2.0] - 2026-03-26
16+
17+
### Added
18+
- ANSI logo asset and branded `version` command output.
19+
20+
### Changed
21+
- `inscription list` now shows newest received inscriptions first.
22+
- `offer` commands remain available via `zinc-cli offer ...` but are hidden from top-level help output.
23+
- Improved human-output inscription thumbnail rendering fidelity in terminal-friendly output modes.
24+
25+
### Docs
26+
- Aligned README/usage/contract/schema docs with the current CLI surface, including `--agent` output mode and thumbnail toggles (`--thumb`, `--no-thumb`).
27+
928
## [0.1.1] - 2026-03-21
1029

1130
### Fixed
@@ -15,7 +34,7 @@ All notable changes to this project will be documented in this file.
1534

1635
### Added
1736
- Initial standalone public packaging for `zinc-cli`.
18-
- Human-friendly command output plus stable `--json` agent envelope.
37+
- Human-friendly command output plus stable agent envelope.
1938
- Wallet profile management with profile lock and atomic writes.
2039
- PSBT create/analyze/sign/broadcast command family.
2140
- Snapshot, account switching, and diagnostic command support.

COMMAND_CONTRACT_V1.md

Lines changed: 22 additions & 24 deletions
Original file line numberDiff line numberDiff line change
@@ -8,25 +8,24 @@ This document defines the active `v1` command contract. It is additive with curr
88
## 1) Design Goals
99

1010
1. One wallet engine and one command model for both humans and agents.
11-
2. Stable machine-readable responses in `--json` mode.
11+
2. Stable machine-readable responses in `--agent` mode.
1212
3. Strict, typed error taxonomy for automation reliability.
1313
4. Backward-compatible rollout from current `SCHEMAS.md` behavior.
1414

1515
## 2) Execution Profiles
1616

1717
1. Human profile
18-
- CLI called without `--json`.
19-
- Output may be user-friendly text.
18+
- Default output mode (`ZINC_CLI_OUTPUT=human`).
19+
- Output is a curated, styled, user-friendly presentation.
2020

2121
2. Agent profile
22-
- CLI called with `--agent` (preferred) or `--json`.
23-
- `--agent` implies `--json --quiet --ascii`.
22+
- CLI called with `--agent` or `ZINC_CLI_OUTPUT=agent`.
2423
- Exactly one JSON object on `stdout` per invocation.
2524
- Non-JSON noise must not be printed to `stdout`.
2625

2726
## 3) Global Flags (Supported)
2827

29-
`--json`, `--agent`, `--quiet`, `--yes`, `--password`, `--password-env`, `--password-stdin`, `--reveal`, `--data-dir`, `--profile`, `--network`, `--scheme`, `--esplora-url`, `--ord-url`, `--ascii`, `--no-images`, `--correlation-id`, `--log-json`, `--idempotency-key`, `--network-timeout-secs`, `--network-retries`, `--policy-mode`
28+
`--agent`, `--quiet`, `--yes`, `--password`, `--password-env`, `--password-stdin`, `--reveal`, `--data-dir`, `--profile`, `--network`, `--scheme`, `--esplora-url`, `--ord-url`, `--ascii`, `--no-images`, `--thumb`, `--no-thumb`, `--correlation-id`, `--log-json`, `--idempotency-key`, `--network-timeout-secs`, `--network-retries`, `--policy-mode`
3029

3130
Global flags are supported both before and after command tokens.
3231

@@ -38,6 +37,11 @@ Reliability defaults:
3837
- `--network-retries` defaults to `0`.
3938
- `--policy-mode` defaults to `warn`.
4039

40+
Human-output defaults:
41+
- Thumbnails are enabled by default in human mode.
42+
- `--no-thumb` or `--no-images` disables thumbnails.
43+
- In `--agent` mode thumbnails are disabled by default unless `--thumb` is set.
44+
4145
## 4) JSON Envelope
4246

4347
## Success
@@ -117,7 +121,7 @@ Idempotency for mutating commands:
117121

118122
## 7) Command Contracts
119123

120-
All commands below describe `--json` response payloads.
124+
All commands below describe `--agent` response payloads.
121125

122126
## 7.1 wallet init
123127

@@ -333,37 +337,31 @@ Command:
333337
`scenario mine [--blocks N] [--address <addr>]`
334338

335339
Success fields:
336-
`action`, `blocks`, `address`, `raw`
340+
`blocks`, `address`, `raw_output`
337341

338342
## 7.24 scenario fund
339343

340344
Command:
341345
`scenario fund [--amount-btc <decimal>] [--address <addr>] [--mine-blocks N]`
342346

343347
Success fields:
344-
`action`, `address`, `amount_btc`, `txid`, `mine_blocks`, `mine_address`, `generated_blocks`
348+
`address`, `amount_btc`, `txid`, `mine_blocks`, `mine_address`, `generated_blocks`
345349

346350
## 7.25 scenario reset
347351

348352
Command:
349353
`scenario reset [--remove-profile] [--remove-snapshots]`
350354

351355
Success fields:
352-
`action`, `removed`
356+
`removed`
353357

354358
## 7.26 doctor
355359

356360
Command:
357361
`doctor`
358362

359363
Success fields:
360-
`healthy`, `esplora`, `ord`
361-
362-
`esplora` shape:
363-
`{ url: string, reachable: bool }`
364-
365-
`ord` shape:
366-
`{ url: string, reachable: bool, indexing_height: u32 | null, error: string | null }`
364+
`healthy`, `esplora_url`, `esplora_reachable`, `ord_url`, `ord_reachable`, `ord_indexing_height`, `ord_error`
367365

368366
## 7.27 offer create
369367

@@ -376,47 +374,47 @@ Notes:
376374
- `--seller-payout-address` overrides payout destination output while preserving seller input metadata from ord inscription output.
377375

378376
Success fields:
379-
`inscription`, `seller_address`, `seller_outpoint`, `postage_sats`, `ask_sats`, `fee_rate_sat_vb`, `seller_input_index`, `buyer_input_count`, `psbt`, `offer`, `submitted_ord`, `ord_url`
377+
`inscription`, `ask_sats`, `fee_rate_sat_vb`, `seller_address`, `seller_outpoint`, `seller_pubkey_hex`, `expires_at_unix`, `thumbnail_lines?`, `hide_inscription_ids`, `raw_response`
380378

381379
## 7.28 offer publish
382380

383381
Command:
384382
`offer publish [--offer-json <json> | --offer-file <path> | --offer-stdin] --secret-key-hex <hex> --relay <url>... [--created-at-unix <unix>] [--timeout-ms N]`
385383

386384
Success fields:
387-
`event`, `publish_results`, `accepted_relays`, `total_relays`
385+
`event_id`, `accepted_relays`, `total_relays`, `publish_results`, `raw_response`
388386

389387
## 7.29 offer discover
390388

391389
Command:
392390
`offer discover --relay <url>... [--limit N] [--timeout-ms N]`
393391

394392
Success fields:
395-
`events`, `offers`, `event_count`, `offer_count`
393+
`event_count`, `offer_count`, `offers`, `thumbnail_lines?`, `hide_inscription_ids`, `raw_response`
396394

397395
## 7.30 offer submit-ord
398396

399397
Command:
400398
`offer submit-ord [--psbt <base64> | --psbt-file <path> | --psbt-stdin]`
401399

402400
Success fields:
403-
`submitted`, `ord_url`
401+
`ord_url`, `submitted`, `raw_response`
404402

405403
## 7.31 offer list-ord
406404

407405
Command:
408406
`offer list-ord`
409407

410408
Success fields:
411-
`ord_url`, `offers`, `count`
409+
`ord_url`, `count`, `offers`, `raw_response`
412410

413411
## 7.32 offer accept
414412

415413
Command:
416414
`offer accept [--offer-json <json> | --offer-file <path> | --offer-stdin] [--expect-inscription <id>] [--expect-ask-sats <u64>] [--dry-run]`
417415

418416
Success fields:
419-
`accepted`, `dry_run`, `offer_id`, `seller_input_index`, `input_count`, `inscription_id`, `ask_sats`, `safe_to_send`, `inscription_risk`, `policy_reasons`, `analysis`, `txid?`
417+
`inscription`, `ask_sats`, `txid`, `dry_run`, `inscription_risk`, `thumbnail_lines?`, `hide_inscription_ids`, `raw_response`
420418

421419
## 8) Input Source Rules (PSBT and Offer Commands)
422420

@@ -455,7 +453,7 @@ When `--policy-mode strict` is set, `psbt sign`, `psbt broadcast`, and `offer ac
455453

456454
## 10) Security Contract for Agent Usage
457455

458-
1. Agents should always use `--json`.
456+
1. Agents should always use `--agent`.
459457
2. Agents should prefer password env variables over plaintext flags.
460458
3. Policy/ordinals failures must be surfaced as `error.type = "policy"` with actionable messages.
461459
4. Commands that mutate wallet state should remain explicit and single-purpose.

CONTRIBUTING.md

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -25,7 +25,7 @@ cargo doc -p zinc-core --no-deps
2525

2626
1. Publish `zinc-core` to crates.io first.
2727
2. Update `Cargo.toml` to an exact `zinc-core` pin:
28-
- `zinc-core = { version = "=X.Y.Z", path = "../zinc-core-public" }`
28+
- `zinc-core = { version = "=X.Y.Z" }`
2929
3. Push a `zinc-cli` release tag.
3030
4. Ensure tag CI passes:
3131
- exact pin validation succeeds,

Cargo.toml

Lines changed: 5 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
[package]
22
name = "zinc-wallet-cli"
3-
version = "0.1.1"
3+
version = "0.2.1"
44
edition = "2021"
55
rust-version = "1.88"
66
license = "MIT"
@@ -52,7 +52,6 @@ ui = [
5252
"dep:figlet-rs",
5353
"dep:rand",
5454
"dep:bip39",
55-
"dep:image",
5655
]
5756

5857
[dependencies]
@@ -77,10 +76,12 @@ tui-big-text = { version = "0.8.2", optional = true }
7776
figlet-rs = { version = "0.1.5", optional = true }
7877
supports-unicode = "3.0.0"
7978

80-
zinc-core = { version = "=0.1.0", path = "../zinc-core-public" }
79+
zinc-core = { version = "=0.1.1" }
8180
rand = { version = "0.8", optional = true }
8281
bip39 = { version = "2.1.0", optional = true }
83-
image = { version = "0.25.10", features = ["avif", "webp"], optional = true }
82+
image = { version = "0.25.10", features = ["avif", "webp"] }
83+
console = "0.15"
84+
viuer = "0.11.0"
8485

8586

8687

README.md

Lines changed: 7 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -11,7 +11,7 @@ It uses an account-based model where each account has:
1111
- a native segwit address for BTC payments
1212

1313
Default behavior is optimized for automation (`--agent` JSON envelopes).
14-
Optional human mode is available via `--features ui`.
14+
Optional interactive dashboard mode is available via `--features ui`.
1515

1616
## Install
1717

@@ -62,6 +62,10 @@ The `ui` feature enables a basic terminal dashboard for humans that shows:
6262
- inscriptions
6363
- ordinals/payment addresses per account
6464

65+
Even without `ui`, human command output can be tuned with:
66+
- `--thumb` to force thumbnail rendering on
67+
- `--no-thumb` to disable thumbnail rendering
68+
6569
Run dashboard:
6670

6771
```bash
@@ -83,7 +87,7 @@ Reports are written to `demo/artifacts/`.
8387
- sync: `sync chain|ordinals`
8488
- addresses and balance: `address taproot|payment`, `balance`
8589
- transfers: `psbt create|analyze|sign|broadcast`
86-
- offers: `offer create|publish|discover|accept|submit-ord|list-ord`
90+
- advanced offers (hidden from top-level help): `offer create|publish|discover|accept|submit-ord|list-ord`
8791
- accounts: `account list|use`
8892
- waits and tx: `wait tx-confirmed|balance`, `tx list`
8993
- operations: `snapshot save|restore|list`, `lock info|clear`, `doctor`
@@ -101,7 +105,7 @@ Reports are written to `demo/artifacts/`.
101105

102106
- Prefer `ZINC_WALLET_PASSWORD` (default password env) or `--password-stdin`.
103107
- Use `--password-env` only when you need a non-default env var name.
104-
- In `--json` mode, mnemonic output is redacted unless `--reveal` is set.
108+
- In `--agent` mode, mnemonic output is redacted unless `--reveal` is set.
105109
- See [SECURITY.md](./SECURITY.md).
106110

107111
## License

SCHEMAS.md

Lines changed: 37 additions & 34 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
# zinc-cli JSON Schemas (v1.0)
22

3-
All commands support `--json` and emit exactly one JSON object to `stdout`.
3+
All commands support `--agent` and emit exactly one JSON object to `stdout`.
44

55
For full contract details (typing, compatibility, and command policy), see `COMMAND_CONTRACT_V1.md`.
66
For task-oriented command examples (human + agent), see `USAGE.md`.
@@ -81,47 +81,46 @@ Optional output files:
8181

8282
- `offer create`:
8383
- `inscription`
84-
- `seller_address`
85-
- `seller_outpoint`
86-
- `postage_sats`
8784
- `ask_sats`
8885
- `fee_rate_sat_vb`
89-
- `seller_input_index`
90-
- `buyer_input_count`
91-
- `psbt`
92-
- `offer` (offer envelope)
93-
- `submitted_ord`
94-
- `ord_url`
86+
- `seller_address`
87+
- `seller_outpoint`
88+
- `seller_pubkey_hex`
89+
- `expires_at_unix`
90+
- `thumbnail_lines` (optional)
91+
- `hide_inscription_ids`
92+
- `raw_response` (canonical full response payload)
9593
- `offer publish`:
96-
- `event` (signed nostr event)
97-
- `publish_results` (per-relay acceptance/message rows)
94+
- `event_id`
9895
- `accepted_relays` (count)
9996
- `total_relays` (count)
97+
- `publish_results` (per-relay acceptance/message rows)
98+
- `raw_response`
10099
- `offer discover`:
101-
- `events` (decoded nostr events)
102-
- `offers` (decoded offer envelopes with event metadata)
103100
- `event_count`
104101
- `offer_count`
102+
- `offers`
103+
- `thumbnail_lines` (optional)
104+
- `hide_inscription_ids`
105+
- `raw_response`
105106
- `offer submit-ord`:
106-
- `submitted` (bool)
107107
- `ord_url`
108+
- `submitted` (bool)
109+
- `raw_response`
108110
- `offer list-ord`:
109111
- `ord_url`
110-
- `offers` (array of base64 PSBT strings)
111112
- `count`
113+
- `offers`
114+
- `raw_response`
112115
- `offer accept`:
113-
- `accepted` (bool)
114-
- `dry_run` (bool)
115-
- `offer_id`
116-
- `seller_input_index`
117-
- `input_count`
118-
- `inscription_id`
116+
- `inscription`
119117
- `ask_sats`
120-
- `safe_to_send`
118+
- `txid`
119+
- `dry_run` (bool)
121120
- `inscription_risk`
122-
- `policy_reasons`
123-
- `analysis`
124-
- `txid` (present when `dry_run=false`)
121+
- `thumbnail_lines` (optional)
122+
- `hide_inscription_ids`
123+
- `raw_response`
125124

126125
Input modes and rules:
127126

@@ -145,9 +144,9 @@ Input modes and rules:
145144
## Account/Wait/Snapshot
146145

147146
- `account list`: `accounts`
148-
- `account use`: `account_index`
149-
- `wait tx-confirmed`: `txid`, `confirmation_time`
150-
- `wait balance`: `confirmed`
147+
- `account use`: `previous_account_index`, `account_index`, `taproot_address`, `payment_address?`
148+
- `wait tx-confirmed`: `txid`, `confirmation_time`, `confirmed`, `waited_secs`
149+
- `wait balance`: `confirmed`, `confirmed_balance`, `target`, `waited_secs`
151150
- `snapshot save`: `snapshot`
152151
- `snapshot restore`: `restored`
153152
- `snapshot list`: `snapshots`
@@ -156,13 +155,17 @@ Input modes and rules:
156155

157156
## Scenario (Regtest)
158157

159-
- `scenario mine`: `action`, `blocks`, `address`, `raw`
160-
- `scenario fund`: `action`, `address`, `amount_btc`, `txid`, `mine_blocks`, `mine_address`, `generated_blocks`
161-
- `scenario reset`: `action`, `removed` (paths)
158+
- `scenario mine`: `blocks`, `address`, `raw_output`
159+
- `scenario fund`: `address`, `amount_btc`, `txid`, `mine_blocks`, `mine_address`, `generated_blocks`
160+
- `scenario reset`: `removed` (paths)
162161

163162
## Doctor
164163

165164
- `doctor`:
166165
- `healthy`
167-
- `esplora` (`url`, `reachable`)
168-
- `ord` (`url`, `reachable`, `indexing_height`, `error`)
166+
- `esplora_url`
167+
- `esplora_reachable`
168+
- `ord_url`
169+
- `ord_reachable`
170+
- `ord_indexing_height`
171+
- `ord_error`

0 commit comments

Comments
 (0)