Skip to content

wallet, seal: create private files 0600 instead of at the umask - #412

Open
xanimo wants to merge 1 commit into
dogecoinfoundation:0.1.5-devfrom
xanimo:0.1.5-dev-private-file-modes
Open

wallet, seal: create private files 0600 instead of at the umask#412
xanimo wants to merge 1 commit into
dogecoinfoundation:0.1.5-devfrom
xanimo:0.1.5-dev-private-file-modes

Conversation

@xanimo

@xanimo xanimo commented Aug 6, 2026

Copy link
Copy Markdown
Member

The wallet database and the sealed seed files were created with plain fopen(), so their permissions came from the process umask. Measured on a box with the common 0002:

plain fopen()            : 0664     <- world-readable, group-writable
dogecoin_fopen_private() : 0600

What's in those files decides how much it matters

The wallet database carries WALLET_DB_REC_TYPE_MASTERPUBKEY alongside the address and transaction records. No private keys — those live in the sealed files — but a master public key derives every address the wallet will ever use and reconstructs its entire transaction history. World-readable is the wrong default for that.

The seal files hold encrypted seeds and mnemonics. Encryption means a leak isn't immediate compromise, but the key is password-derived, and handing a local attacker the ciphertext lets them work on it offline at their leisure.

The change

Adds dogecoin_fopen_private(): opens with O_CREAT and 0600 on POSIX, defers to fopen() on Windows, where there's no umask and a new file inherits the directory ACL.

Four call sites move over — one in dogecoin_wallet_create, three in seal.c.

One behavioural note worth flagging

The mode applies only at creation, so an existing wallet or seal file keeps whatever permissions it already has. That's deliberate — it tightens new files without silently re-permissioning anyone's existing ones — but it does mean existing installs stay as they are until the file is recreated. If that's not the desired outcome, a one-time chmod on open would need its own decision, and I'd rather raise it than make that call quietly.

Test

Asserts the created mode is exactly 0600, that no group or other bits are set, and that reopening an existing file doesn't widen it. Skipped on Windows, where the mode has no meaning.

82/82.


Found while triaging the CodeQL cpp/world-writable-file-creation alerts. Two of the five were in test code and are already fixed; these were the two in library code, and they turned out to be the ones that mattered.

The wallet database and the sealed seed files were created with plain
fopen(), so their permissions came from the process umask. Under the
common 0002 that is 0664: readable by every local user on the machine,
and writable by the group.

    plain fopen()            : 0664
    dogecoin_fopen_private() : 0600

What each file holds decides how much that matters.

The wallet database carries WALLET_DB_REC_TYPE_MASTERPUBKEY along with
the address and transaction records. No private keys -- those live in the
sealed files -- but a master public key is enough to derive every address
the wallet will ever use and reconstruct its whole transaction history.
World-readable is the wrong default for that.

The seal files hold encrypted seeds and mnemonics. Encryption means a
leak is not an immediate compromise, but the encryption is derived from a
password, and handing a local attacker the ciphertext is an invitation to
work on it offline at their leisure.

Adds dogecoin_fopen_private(), which opens with O_CREAT and 0600 on POSIX
and defers to fopen() on Windows, where there is no umask and a new file
inherits the directory ACL. Four call sites move over: one in
dogecoin_wallet_create and three in seal.c.

The mode applies only when the file is created, so an existing wallet or
seal file keeps whatever permissions it already has. This tightens new
files without silently changing anyone's current ones -- and a note for
whoever picks that up, since it does mean existing installs stay as they
are until the file is recreated.

The test asserts the created mode is exactly 0600, that no group or other
bits are set at all, and that reopening an existing file does not widen
it. Skipped on Windows, where the mode has no meaning.

82/82.
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.

1 participant