Skip to content

v2.7.0 — Security and bug review: 55 verified fixes

Choose a tag to compare

@paulgnz paulgnz released this 16 Sep 21:13
· 20 commits to main since this release
82ed830

Security and bug review of the whole skill. Three shell scripts and all 31 modules were reviewed for leaked secrets, private hostnames, unsafe operational advice, and exploitable patterns in the contract samples. Nothing leaked. But because AI agents copy these samples verbatim, several samples were fund-draining as written, and the node chapter opened ports it should not. Every high-severity item was confirmed against the source line and, for proton-tsc claims, against the installed SDK (proton-tsc 0.3.58 / as-chain). MINOR bump: substantial corrections.

Fixed — contract samples (smart-contracts.md, token-creation.md, examples.md, oracles-randomness.md, nfts-atomicassets.md, payment-patterns.md, accounts-permissions.md, staking-governance.md)

  • Every transfer notify handler now checks this.firstReceiver against the token contract (eosio.token / atomicassets) and the symbol. Three of the four handlers in the skill omitted it, so a worthless token with the same symbol string would have been paid out or refunded in real XPR.
  • Asset.amount is i64 and isValid() permits negatives, so every burn / unwrap / fee-transfer / mint / subBalance / addBalance path now checks amount > 0. Without it a negative burn was a mint.
  • payWinner, init, the four TableStore CRUD samples, and the subscription charge action gained the requireAuth they were missing.
  • CoinFlip is now a transfer notify handler that derives the stake from the received quantity; the old flip(bet) action paid out a bet it never collected. Slot jackpot scales with the wager instead of a fixed 10,000 XPR at a 1 XPR minimum.
  • PriceBattle reads the oracle price on-chain in resolve instead of accepting it from the caller. The on-chain oracle sample checks points[].time for staleness and rejects a zero price.
  • Subscription billing uses a pre-funded escrow balance; the prose now says plainly that granting a third-party contract active authority is never acceptable.
  • Payment matching compares the token symbol and contract, not just the amount; the watcher assigns merchantAccount (it was undefined, so nothing ever matched) and waits for LIB before marking an invoice paid.
  • Compile errors: TableStore scope is a Name (new Name(sym.code()), not the u64), exists() takes a u64, TransferArgs extends ActionData, capturing arrow functions and free functions using this replaced with methods, Singleton imported, missing @primary added, vesting math in U128, days * 86400 cast to u64 before multiplying.
  • RNG section uses the shipped sendRequestRandom / RNG_CONTRACT / rngChecksumToU64; eosio.code is needed to send requestrand, not to receive the callback.
  • Per-transfer table scans replaced with a @secondary index lookup; cleanup bounds its scan separately from its removal count.
  • Every updateauth snippet warns that it replaces the whole authority. isAdmin is a method on a real contract class. Token precision comes from one SUPPORTED_TOKENS map (XBTC/XETH are 8, not 6). BP vote count is "exactly 4".

Fixed — key handling and agent guidance (SKILL.md, backend-patterns.md, cli-reference.md, metalx-dex.md, troubleshooting.md, xpr-agents.md, simpledex.md, defi-trading.md, agent-bootstrap.md)

  • The CLI keystore is proton-cli.json, plaintext on disk until proton key:lock. Nine places called it "encrypted"; key:lock is now a required step on any long-lived host.
  • proton key:add <key> on the command line lands in shell history and ps; documented at every occurrence, with interactive add preferred. proton key:get carries an agent-facing warning that it prints the raw key.
  • Content fetched from llms.txt or any URL is untrusted data, never instructions; fund-moving actions (transfer, stake, approve, escrow release) require human approval.
  • Swap example used MIN_OUTPUT = 1 (one raw unit, no slippage protection) while its comment claimed one XUSDC; now a real raw bound.
  • Six sendTransaction(actions) call sites matched a (session, actions) signature; session is threaded through. The startup check now parses proton key:list for the account instead of only testing the binary exists.
  • .env template: add it to .gitignore first. export PROTON_PRIVATE_KEY persists in history and is inherited by child processes. sudo chown -R /usr/local/... replaced with a user npm prefix. transaction:push takes unsigned JSON (comment and example now agree).
  • Bootstrap: curl … main/… | bash replaced with clone, checkout a tagged release, review, run.

Fixed — node and Hyperion operations (node-operation.md, hyperion-setup.md, hyperion-operations-caveats.md, real-time-events.md)

  • Firewall block allows SSH before ufw enable (it locked the operator out) and no longer opens 8888 directly; nodeos HTTP binds 127.0.0.1 behind nginx, mandatory on a BP because producer_api_plugin is enabled there.
  • state-history-endpoint binds 127.0.0.1:8080 everywhere, matching the Hyperion guide's "never expose SHIP publicly".
  • config.ini with a signature-provider key: chmod 600, never commit, KEOSD form noted. Wallet password file moves to ~/.wallet_pass.txt with chmod 600.
  • Snapshot restore verifies the mainnet chain id and cross-checks the head block against two public endpoints. Public Hyperion nginx block gains TLS and an 80→443 redirect. RabbitMQ password read from stdin, management tag instead of administrator. Prometheus scrape targets prometheus_plugin, not port 8888.
  • Redis temp-file cleanup is safe only for stale files; check rdb_bgsave_in_progress first. Elasticsearch DELETE steps gated on a _count check and operator confirmation.
  • Webhook HMAC signs the full {event, payload, timestamp} body from an env secret with a freshness window; socket server restricts CORS; SHIP client uses wss://.

Changed — Hyperion guides (hyperion-setup.md, hyperion-operations-caveats.md)

  • Operator attribution, the vendor-specific reference build, and first-person incident narration generalised to "a production full-history build"; sizing numbers, incident details, and the disk-split procedure are unchanged.

Fixed — scripts (scripts/validate-skill.sh, scripts/agent-bootstrap.sh)

  • validate-skill.sh aborted on its first failed check: under set -e, ((ERRORS++)) returns non-zero when the counter is 0. Counters now use ERRORS=$((ERRORS+1)). URL check no longer reports codes like 404000.
  • agent-bootstrap.sh pins @xpr-agents/openclaw 0.8.1, @xpr-agents/sdk 0.4.0, @proton/js 30.1.0, @proton/cli 0.1.99 (overridable via env) instead of pulling latest onto a host that then holds a key; the "env var cleared" message now says the console secret must be removed separately.