Skip to content

fix: security and correctness follow-ups for Sepolia operator tooling (PR #176)#177

Merged
lionakhnazarov merged 5 commits intofeat/testnet4-deployment-supportfrom
fix/testnet4-security-and-correctness
Apr 15, 2026
Merged

fix: security and correctness follow-ups for Sepolia operator tooling (PR #176)#177
lionakhnazarov merged 5 commits intofeat/testnet4-deployment-supportfrom
fix/testnet4-security-and-correctness

Conversation

@piotr-roslaniec
Copy link
Copy Markdown
Contributor

Context

Follow-up to #176 (Sepolia TokenStaking deploy, ExtendedTokenStaking upgrade, and operator setup tooling). Addresses security and correctness issues identified in code review.

Changes

P0 -- Private key exposure

  • scripts/create-operator-keystore.js -- remove console.log("Private key:", ...). Address and keystore path are sufficient output.
  • scripts/get-operator-key.js -- same; remove private key log line.
  • scripts/setup-new-staking-provider.js -- remove NEW_STAKING_PROVIDER_KEY and NEW_OPERATOR_KEY from generated .env files (keys written to disk are a git add . leak risk). Staking provider key is still printed once to terminal with a "copy now" warning since it is generated ephemerally.

P1 -- Keys in CLI arguments

  • scripts/run-new-operator-setup.sh -- replace all --private-key $KEY flags with ETH_PRIVATE_KEY="$KEY" cast send via _sp_cast_send_ok / _op_cast_send_ok wrappers. --private-key exposes the raw key in ps aux and shell history.
  • scripts/fund-new-operator.sh -- same for the deployer key.

P1 -- Operator key script bugs

  • scripts/get-operator-key.js -- remove hardcoded UUID default path (c88df450-...) that caused ENOENT for all users except the original developer. Require explicit path argument with usage error.
  • scripts/get-operator-key.js -- fix --list directory path: ../../operator-1-keystore resolved above the repo root; corrected to ../operator-1-keystore.

P1 -- Proxy upgrade kind

  • deploy/54_upgrade_token_staking_extended.ts -- remove hardcoded kind: "transparent" from upgradeProxy options. Let the OZ plugin infer the proxy type. Hardcoding risks a mismatch if the plugin defaulted differently during the original deploy. Add a comment with the cast storage command to verify the proxy type on-chain before any future mainnet run.

P2 -- Code quality and correctness

  • scripts/create-operator-keystore.js and scripts/setup-new-staking-provider.js -- require non-empty password; empty-password keystores are trivially decryptable.
  • deploy/54_upgrade_token_staking_extended.ts -- replace two inline const fs = require("fs") with a single top-level import * as fs from "fs".
  • deployments/mainnet/TokenStaking.json -- restore file deleted without explanation in Sepolia TokenStaking proxy deploy, ExtendedTokenStaking upgrade, and operator setup tooling #176. Downstream consumers relying on deployments/mainnet/ break silently.
  • contracts/staking/TokenStaking.sol -- add application != address(0) guard in increaseAuthorization for consistency with approveApplication.

Housekeeping (async refactor in scripts)

The three JS scripts were also converted from sync fs.*Sync calls to fs.promises.* and gained try/catch inside main(). This was required to pass the pre-commit UBS scan (sync I/O in scripts triggers a warning) but also makes error handling explicit.

Test plan

  • node scripts/create-operator-keystore.js without args exits with usage error
  • node scripts/create-operator-keystore.js mypassword creates keystore, prints address only (no key)
  • node scripts/get-operator-key.js without args exits with usage error
  • node scripts/get-operator-key.js --list lists keystores from operator-1-keystore/ (not parent dir)
  • node scripts/setup-new-staking-provider.js without args exits with usage error
  • node scripts/setup-new-staking-provider.js mypassword generates wallets; .env.new-operator contains addresses but no private keys; terminal shows SP key with warning
  • deployments/mainnet/TokenStaking.json exists and has correct address 0x01B67b1194C75264d06F808A921228a95C765dd7
  • yarn compile passes

- Remove private key logging from create-operator-keystore and
  get-operator-key; address is sufficient for confirmation
- Remove staking provider and operator private keys from generated
  .env files; keys written to disk are a git-leak risk
- Print staking provider key once to terminal with a prominent
  "copy now" warning instead of persisting it to .env
- Require non-empty password in create-operator-keystore and
  setup-new-staking-provider; empty-password keystores are
  trivially decryptable
- Require explicit keystore path in get-operator-key; remove the
  hardcoded developer-machine UUID default that caused ENOENT for
  all other users
- Fix --list path in get-operator-key: ../../operator-1-keystore
  resolved above repo root; corrected to ../operator-1-keystore
- Convert sync fs calls to fs.promises and add try/catch inside
  main() across all three scripts
…args

Passing --private-key as a CLI argument exposes the key in ps aux
output and persists in shell history. Using ETH_PRIVATE_KEY as an
inline env assignment (ETH_PRIVATE_KEY="$key" cast send ...) keeps
the key out of the argv list.

Introduce _sp_cast_send_ok / _op_cast_send_ok wrappers in
run-new-operator-setup.sh that inject ETH_PRIVATE_KEY for the
respective signer, and replace all --private-key flag usages.

Update fund-new-operator.sh likewise for the deployer key.

Update run-new-operator-setup.sh usage comment to reflect that
NEW_STAKING_PROVIDER_KEY and NEW_OPERATOR_KEY are no longer
written to .env files and must be exported by the operator.
- Drop kind: "transparent" from upgradeProxy options; let the OZ
  plugin infer the proxy type from the deployed proxy admin slot.
  Hardcoding the kind risks a mismatch if the original deploy
  defaulted differently. Add a comment with the cast storage
  command to verify proxy type on-chain.
- Replace two inline const fs = require("fs") declarations with a
  single top-level import * as fs from "fs" to match TypeScript
  conventions and avoid the duplicate binding.
The file was removed in the parent branch commit without explanation.
Downstream consumers relying on deployments/mainnet/TokenStaking.json
break silently without it. Restored from the last known-good version
(commit ab29e02).
approveApplication already checks application != address(0) but
increaseAuthorization did not. The APPROVED status check provides
a functional backstop, but adding the explicit guard makes the
invariant consistent across both entry points.
@lionakhnazarov lionakhnazarov merged commit 1e32bf1 into feat/testnet4-deployment-support Apr 15, 2026
11 checks passed
@lionakhnazarov lionakhnazarov deleted the fix/testnet4-security-and-correctness branch April 15, 2026 08:59
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants