fix(security): restrict keystore file permissions to 0o600 - #399

Open
yasinlex wants to merge 1 commit into
genlayerlabs:v0.40-devfrom
yasinlex:fix/keystore-file-permissions
Open

fix(security): restrict keystore file permissions to 0o600#399
yasinlex wants to merge 1 commit into
genlayerlabs:v0.40-devfrom
yasinlex:fix/keystore-file-permissions

Conversation

@yasinlex

@yasinlexyasinlex commented Jul 26, 2026

Copy link
Copy Markdown

Problem

Keystore files containing encrypted private keys are written via writeFileSync without specifying a file mode, leaving them with the default 0644 permissions — world-readable on POSIX systems. Any local user on the machine can read the encrypted keystore and attempt offline brute-force attacks on the password.

This affects three code paths:

  1. BaseAction.createKeypairByName() (line 222) — account creation
  2. ExportAccountAction.execute() (export.ts:55) — exporting an account to a keystore file
  3. ImportAccountAction.execute() (import.ts:72) — importing an account into a keystore file
// Before — world-readable (0644 on POSIX)writeFileSync(keystorePath,encryptedJson);

Fix

Pass { mode: 0o600 } to writeFileSyncand explicitly chmodSync to 0o600 afterwards:

// After — owner-only read/write (0600 on POSIX)writeFileSync(keystorePath,encryptedJson,{mode: 0o600});try{chmodSync(keystorePath,0o600);}catch{// chmod can fail on Windows (no POSIX permissions)}

The explicit chmodSync is necessary because writeFileSync does not change the mode of an already-existing file — so a file pre-created by an attacker with 0644 would keep world-read access even with the mode option. The chmod is wrapped in try/catch because it is a no-op on Windows (no POSIX permissions; Windows ACLs govern access there).

This is the same pattern the genswarms-telegram curl client already uses (File.chmod(config_path, 0o600)) for sensitive temp files.

Testing

  • TypeScript type check passes on all three modified files (pre-existing errors in unrelated stakingInfo.ts/StakingAction.ts are not affected)
  • No behavioral change to the happy path — only file permissions are restricted

Summary by CodeRabbit

  • Security Enhancements
    • Keystore files created, imported, or exported by the application now use restrictive file permissions to help prevent unauthorized access.
    • Permission enforcement is handled safely across platforms that do not support POSIX file permissions.

Keystore files containing encrypted private keys were written via
writeFileSync without specifying file mode, leaving them with the
default 0644 permissions — world-readable on POSIX systems. Any local
user could read the encrypted keystore and attempt offline brute-force
attacks on the password.
This affects three code paths:
- BaseAction.createKeypairByName() — account creation
- ExportAccountAction — exporting an account to a keystore file
- ImportAccountAction — importing an account into a keystore file
Fix: pass { mode: 0o600 } to writeFileSync and explicitly chmodSync to
0o600 afterwards. The chmod is necessary because writeFileSync does not
change the mode of an already-existing file, so a pre-created file with
0644 would keep world-read access. The chmod is wrapped in try/catch
because it is a no-op / can fail on Windows (no POSIX permissions).
@github-actions
github-actionsBot changed the base branch from main to v0.40-devJuly 26, 2026 22:33
@github-actions

Copy link
Copy Markdown

This PR targeted main, which is only the default/static branch.

I retargeted it to v0.40-dev, the active development branch. Pushes to v0.40-dev automatically fast-forward main.

@coderabbitai

coderabbitaiBot commented Jul 26, 2026

Copy link
Copy Markdown

Review Change Stack

📝 Walkthrough

Walkthrough

Keystore writes in account export, account import, and BaseAction now specify owner-only permissions (0o600) and attempt to enforce them with chmodSync, while ignoring platform-specific chmod failures.

Changes

Keystore Permission Enforcement

Layer / File(s)Summary
Restrictive keystore writes
src/commands/account/export.ts, src/commands/account/import.ts, src/lib/actions/BaseAction.ts
Keystore files are written with 0o600 permissions and passed through best-effort chmodSync enforcement, with failures caught for unsupported platforms.

Estimated code review effort: 2 (Simple) | ~10 minutes

Possibly related PRs

Suggested reviewers:muncleuscles

🚥 Pre-merge checks | ✅ 5
✅ Passed checks (5 passed)
Check nameStatusExplanation
Description Check✅ PassedCheck skipped - CodeRabbit’s high-level summary is enabled.
Title check✅ PassedThe title clearly summarizes the main security change: restricting keystore file permissions to 0o600.
Docstring Coverage✅ PassedNo functions found in the changed files to evaluate docstring coverage. Skipping docstring coverage check.
Linked Issues check✅ PassedCheck skipped because no linked issues were found for this pull request.
Out of Scope Changes check✅ PassedCheck skipped because no linked issues were found for this pull request.
✨ Finishing Touches
🧪 Generate unit tests (beta)
  • Create PR with unit tests

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands.

@coderabbitaicoderabbitaiBot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 1

🧹 Nitpick comments (1)
src/lib/actions/BaseAction.ts (1)

222-231: 📐 Maintainability & Code Quality | 🔵 Trivial | ⚡ Quick win

Centralize the permissioned keystore write.

This write/chmod sequence is duplicated in src/commands/account/export.ts and src/commands/account/import.ts. A protected BaseAction helper would keep permission enforcement and failure handling consistent across all keystore paths.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.
In `@src/lib/actions/BaseAction.ts` around lines 222 - 231, Centralize the
keystore write and permission enforcement in a protected helper on BaseAction,
moving the writeFileSync/chmodSync sequence and its existing chmod failure
handling there. Replace the duplicated sequences in the account export and
import flows with calls to this helper, preserving mode 0o600 and the current
Windows-compatible behavior.
🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.
Inline comments:
In `@src/lib/actions/BaseAction.ts`:
- Around line 222-231: Centralize keystore writing and permission enforcement in
BaseAction, ensuring chmodSync failures propagate on non-Windows platforms
instead of being silently ignored; retain Windows-specific handling as
appropriate. Apply the shared fail-closed behavior at
src/lib/actions/BaseAction.ts lines 222-231 and 447-452, and update
src/commands/account/export.ts lines 63-68 and src/commands/account/import.ts
lines 65-70 to fail when permissions cannot be enforced rather than reporting
success.
---
Nitpick comments:
In `@src/lib/actions/BaseAction.ts`:
- Around line 222-231: Centralize the keystore write and permission enforcement
in a protected helper on BaseAction, moving the writeFileSync/chmodSync sequence
and its existing chmod failure handling there. Replace the duplicated sequences
in the account export and import flows with calls to this helper, preserving
mode 0o600 and the current Windows-compatible behavior.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: defaults

Review profile: CHILL

Plan: Pro Plus

Run ID: 55ae6995-5e7a-45b9-a9ed-03650258589a

📥 Commits

Reviewing files that changed from the base of the PR and between 3396474 and 7daf5bd.

📒 Files selected for processing (3)
  • src/commands/account/export.ts
  • src/commands/account/import.ts
  • src/lib/actions/BaseAction.ts

Comment on lines +222 to +231
writeFileSync(keystorePath, encryptedJson, { mode: 0o600 });
// Enforce restrictive permissions even if the file already existed (writeFileSync
// does not change the mode of an existing file, so an attacker who pre-created it
// with 0644 would keep world-read access to the encrypted private key).
try {
chmodSync(keystorePath, 0o600);
} catch {
// chmod can fail on Windows (no POSIX permissions) — the encrypted keystore
// still protects the key, and Windows ACLs govern access there anyway.
}

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🔒 Security & Privacy | 🟠 Major | ⚡ Quick win

🧩 Analysis chain

🏁 Script executed:

#!/bin/bashset -euo pipefail
echo"== Locate files =="
git ls-files | rg '(^|/)(BaseAction|export|import)\.ts$|^src/(commands/account|lib/actions)/'||trueechoecho"== Relevant snippets =="forfin src/lib/actions/BaseAction.ts src/commands/account/export.ts src/commands/account/import.ts;doecho"--- $f$(wc -l <"$f") lines ---"
sed -n '1,110p'"$f"| cat -n
echodoneechoecho"== Search keystore permission/write patterns =="
rg -n "writeFileSync|chmodSync|chmod\\(|0o600|0600|encrypt|keystore" src -g '*.ts'||trueechoecho"== Static verifier: writeFileSync+chmodSync failure handling =="
python3 - <<'PY'from pathlib import Pathimport refiles = [ "src/lib/actions/BaseAction.ts", "src/commands/account/export.ts", "src/commands/account/import.ts",]for path in files: src = Path(path).read_text() write = list(re.finditer(r'writeFileSync\s*\(', src)) chmod = list(re.finditer(r'chmodSync\s*\(', src)) try_blocks = list(re.finditer(r'(?s)\btry\s*\{(?P<body>.*?)\}\s*\catch\s*\{', src)) chmod_in_failing_try = [] for m in try_blocks: if any(c.start() >= m.start() and c.start() <= m.end() for c in chmod): body = m.group('body') inside = bool(re.search(r'catch\s*\{.*?rethrow|\bthrow\s', body, re.S)) chmod_in_failing_try.append((c.start(), c.start(), c.group().strip(), inside)) print(f"\n{path}:") print(f" writeFileSync count: {len(write)}") print(f" chmodSync count: {len(chmod)}") for m in try_blocks: body = src[m.start():m.end()] if re.search(r'chmodSync\s*\(', body): has_rethrow = bool(re.search(r'catch\s*\{.*?rethrow|\bthrow\s', body, re.S)) has_just_comment = bool(re.search(r'catch\s*\{[^}]*chmod can fail|chmod can fail[^}]*\}', body, re.S)) print(f" try/catch around chmod: has_throw_on_non_windows_reject={has_rethrow}, broad_catch_comment={has_just_comment}")PY

Repository: genlayerlabs/genlayer-cli

Length of output: 34014


🏁 Script executed:

#!/bin/bashset -euo pipefail
echo"== BaseAction tail around write/update and imports =="
sed -n '180,235p' src/lib/actions/BaseAction.ts | cat -n
sed -n '150,170p' src/lib/config/ConfigFileManager.ts | cat -n
echoecho"== Import/export tail with static parser =="
python3 - <<'PY'from pathlib import Pathimport refor path in ["src/lib/actions/BaseAction.ts", "src/commands/account/export.ts", "src/commands/account/import.ts"]: src = Path(path).read_text() matches = list(re.finditer(r'(?s)\btry\s*\{\s*chmodSync\s*\([^}]*\)\s*\}\s*\catch\s*\{', src)) for m in matches: block = src[m.start():m.end()] try_rethrow = bool(re.search(r'\bthrow\s*;', block) or bool(re.search(r'\brethrow\b', block))) print(f"{path}: chmod try/catch: throw={try_rethrow}") print(block.replace("\n", " ")) if not matches: print(f"{path}: no chmod try/catch matches")PY

Repository: genlayerlabs/genlayer-cli

Length of output: 4928


Make keystore permission enforcement fail closed.

All three paths silently ignore chmodSync failures, so an existing keystore or exported file with broader permissions can keep the encrypted private key readable even though the command reports success.

  • src/lib/actions/BaseAction.ts#L447-L452: propagate non-Windows chmod failures and centralize the shared write logic.
  • src/commands/account/export.ts#L63-L68: fail closed when exported keystore permissions cannot be enforced.
  • src/commands/account/import.ts#L65-L70: fail closed when imported keystore permissions cannot be enforced.
📍 Affects 3 files
  • src/lib/actions/BaseAction.ts#L222-L231 (this comment)
  • src/commands/account/export.ts#L63-L68
  • src/commands/account/import.ts#L65-L70
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.
In `@src/lib/actions/BaseAction.ts` around lines 222 - 231, Centralize keystore
writing and permission enforcement in BaseAction, ensuring chmodSync failures
propagate on non-Windows platforms instead of being silently ignored; retain
Windows-specific handling as appropriate. Apply the shared fail-closed behavior
at src/lib/actions/BaseAction.ts lines 222-231 and 447-452, and update
src/commands/account/export.ts lines 63-68 and src/commands/account/import.ts
lines 65-70 to fail when permissions cannot be enforced rather than reporting
success.

Sign up for freeto 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

@yasinlex
, 'i'); if (__m === '*' || __re.test(location.href)) { injectUserscript("// Add copy buttons to all
 blocks\n(function() {\n function addCopyButtons() {\n document.querySelectorAll('pre code').forEach(function(codeBlock) {\n if (codeBlock.parentElement.hasAttribute('data-copy-added')) return;\n codeBlock.parentElement.setAttribute('data-copy-added', 'true');\n \n var btn = document.createElement('button');\n btn.textContent = 'Copy';\n btn.style.cssText = 'position:absolute;top:4px;right:4px;padding:2px 8px;font-size:11px;background:#4ecdc4;border:none;border-radius:4px;color:#1a1a2e;cursor:pointer;opacity:0.7;transition:opacity 0.2s;';\n btn.onmouseover = function() { this.style.opacity = '1'; };\n btn.onmouseout = function() { this.style.opacity = '0.7'; };\n btn.onclick = function() {\n navigator.clipboard.writeText(codeBlock.textContent).then(function() {\n btn.textContent = 'Copied!';\n setTimeout(function() { btn.textContent = 'Copy'; }, 1500);\n });\n };\n codeBlock.parentElement.style.position = 'relative';\n codeBlock.parentElement.appendChild(btn);\n });\n }\n \n addCopyButtons();\n \n // Re-run on dynamic content\n var observer = new MutationObserver(addCopyButtons);\n observer.observe(document.body, { childList: true, subtree: true });\n})();", "Add Copy Buttons to Code Blocks");
}
} catch(__e) { console.warn('[Userscript:Add Copy Buttons to Code Blocks]', __e); }
})();
(function(){
try {
var __m = "github.com";
var __re = new RegExp('^' + "github\\.com" + '
Skip to content

fix(security): restrict keystore file permissions to 0o600 - #399

Open
yasinlex wants to merge 1 commit into
genlayerlabs:v0.40-devfrom
yasinlex:fix/keystore-file-permissions
Open

fix(security): restrict keystore file permissions to 0o600#399
yasinlex wants to merge 1 commit into
genlayerlabs:v0.40-devfrom
yasinlex:fix/keystore-file-permissions

Conversation

@yasinlex

@yasinlexyasinlex commented Jul 26, 2026

Copy link
Copy Markdown

Problem

Keystore files containing encrypted private keys are written via writeFileSync without specifying a file mode, leaving them with the default 0644 permissions — world-readable on POSIX systems. Any local user on the machine can read the encrypted keystore and attempt offline brute-force attacks on the password.

This affects three code paths:

  1. BaseAction.createKeypairByName() (line 222) — account creation
  2. ExportAccountAction.execute() (export.ts:55) — exporting an account to a keystore file
  3. ImportAccountAction.execute() (import.ts:72) — importing an account into a keystore file
// Before — world-readable (0644 on POSIX)writeFileSync(keystorePath,encryptedJson);

Fix

Pass { mode: 0o600 } to writeFileSyncand explicitly chmodSync to 0o600 afterwards:

// After — owner-only read/write (0600 on POSIX)writeFileSync(keystorePath,encryptedJson,{mode: 0o600});try{chmodSync(keystorePath,0o600);}catch{// chmod can fail on Windows (no POSIX permissions)}

The explicit chmodSync is necessary because writeFileSync does not change the mode of an already-existing file — so a file pre-created by an attacker with 0644 would keep world-read access even with the mode option. The chmod is wrapped in try/catch because it is a no-op on Windows (no POSIX permissions; Windows ACLs govern access there).

This is the same pattern the genswarms-telegram curl client already uses (File.chmod(config_path, 0o600)) for sensitive temp files.

Testing

  • TypeScript type check passes on all three modified files (pre-existing errors in unrelated stakingInfo.ts/StakingAction.ts are not affected)
  • No behavioral change to the happy path — only file permissions are restricted

Summary by CodeRabbit

  • Security Enhancements
    • Keystore files created, imported, or exported by the application now use restrictive file permissions to help prevent unauthorized access.
    • Permission enforcement is handled safely across platforms that do not support POSIX file permissions.

Keystore files containing encrypted private keys were written via
writeFileSync without specifying file mode, leaving them with the
default 0644 permissions — world-readable on POSIX systems. Any local
user could read the encrypted keystore and attempt offline brute-force
attacks on the password.
This affects three code paths:
- BaseAction.createKeypairByName() — account creation
- ExportAccountAction — exporting an account to a keystore file
- ImportAccountAction — importing an account into a keystore file
Fix: pass { mode: 0o600 } to writeFileSync and explicitly chmodSync to
0o600 afterwards. The chmod is necessary because writeFileSync does not
change the mode of an already-existing file, so a pre-created file with
0644 would keep world-read access. The chmod is wrapped in try/catch
because it is a no-op / can fail on Windows (no POSIX permissions).
@github-actions
github-actionsBot changed the base branch from main to v0.40-devJuly 26, 2026 22:33
@github-actions

Copy link
Copy Markdown

This PR targeted main, which is only the default/static branch.

I retargeted it to v0.40-dev, the active development branch. Pushes to v0.40-dev automatically fast-forward main.

@coderabbitai

coderabbitaiBot commented Jul 26, 2026

Copy link
Copy Markdown

Review Change Stack

📝 Walkthrough

Walkthrough

Keystore writes in account export, account import, and BaseAction now specify owner-only permissions (0o600) and attempt to enforce them with chmodSync, while ignoring platform-specific chmod failures.

Changes

Keystore Permission Enforcement

Layer / File(s)Summary
Restrictive keystore writes
src/commands/account/export.ts, src/commands/account/import.ts, src/lib/actions/BaseAction.ts
Keystore files are written with 0o600 permissions and passed through best-effort chmodSync enforcement, with failures caught for unsupported platforms.

Estimated code review effort: 2 (Simple) | ~10 minutes

Possibly related PRs

Suggested reviewers:muncleuscles

🚥 Pre-merge checks | ✅ 5
✅ Passed checks (5 passed)
Check nameStatusExplanation
Description Check✅ PassedCheck skipped - CodeRabbit’s high-level summary is enabled.
Title check✅ PassedThe title clearly summarizes the main security change: restricting keystore file permissions to 0o600.
Docstring Coverage✅ PassedNo functions found in the changed files to evaluate docstring coverage. Skipping docstring coverage check.
Linked Issues check✅ PassedCheck skipped because no linked issues were found for this pull request.
Out of Scope Changes check✅ PassedCheck skipped because no linked issues were found for this pull request.
✨ Finishing Touches
🧪 Generate unit tests (beta)
  • Create PR with unit tests

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands.

@coderabbitaicoderabbitaiBot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 1

🧹 Nitpick comments (1)
src/lib/actions/BaseAction.ts (1)

222-231: 📐 Maintainability & Code Quality | 🔵 Trivial | ⚡ Quick win

Centralize the permissioned keystore write.

This write/chmod sequence is duplicated in src/commands/account/export.ts and src/commands/account/import.ts. A protected BaseAction helper would keep permission enforcement and failure handling consistent across all keystore paths.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.
In `@src/lib/actions/BaseAction.ts` around lines 222 - 231, Centralize the
keystore write and permission enforcement in a protected helper on BaseAction,
moving the writeFileSync/chmodSync sequence and its existing chmod failure
handling there. Replace the duplicated sequences in the account export and
import flows with calls to this helper, preserving mode 0o600 and the current
Windows-compatible behavior.
🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.
Inline comments:
In `@src/lib/actions/BaseAction.ts`:
- Around line 222-231: Centralize keystore writing and permission enforcement in
BaseAction, ensuring chmodSync failures propagate on non-Windows platforms
instead of being silently ignored; retain Windows-specific handling as
appropriate. Apply the shared fail-closed behavior at
src/lib/actions/BaseAction.ts lines 222-231 and 447-452, and update
src/commands/account/export.ts lines 63-68 and src/commands/account/import.ts
lines 65-70 to fail when permissions cannot be enforced rather than reporting
success.
---
Nitpick comments:
In `@src/lib/actions/BaseAction.ts`:
- Around line 222-231: Centralize the keystore write and permission enforcement
in a protected helper on BaseAction, moving the writeFileSync/chmodSync sequence
and its existing chmod failure handling there. Replace the duplicated sequences
in the account export and import flows with calls to this helper, preserving
mode 0o600 and the current Windows-compatible behavior.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: defaults

Review profile: CHILL

Plan: Pro Plus

Run ID: 55ae6995-5e7a-45b9-a9ed-03650258589a

📥 Commits

Reviewing files that changed from the base of the PR and between 3396474 and 7daf5bd.

📒 Files selected for processing (3)
  • src/commands/account/export.ts
  • src/commands/account/import.ts
  • src/lib/actions/BaseAction.ts

Comment on lines +222 to +231
writeFileSync(keystorePath, encryptedJson, { mode: 0o600 });
// Enforce restrictive permissions even if the file already existed (writeFileSync
// does not change the mode of an existing file, so an attacker who pre-created it
// with 0644 would keep world-read access to the encrypted private key).
try {
chmodSync(keystorePath, 0o600);
} catch {
// chmod can fail on Windows (no POSIX permissions) — the encrypted keystore
// still protects the key, and Windows ACLs govern access there anyway.
}

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🔒 Security & Privacy | 🟠 Major | ⚡ Quick win

🧩 Analysis chain

🏁 Script executed:

#!/bin/bashset -euo pipefail
echo"== Locate files =="
git ls-files | rg '(^|/)(BaseAction|export|import)\.ts$|^src/(commands/account|lib/actions)/'||trueechoecho"== Relevant snippets =="forfin src/lib/actions/BaseAction.ts src/commands/account/export.ts src/commands/account/import.ts;doecho"--- $f$(wc -l <"$f") lines ---"
sed -n '1,110p'"$f"| cat -n
echodoneechoecho"== Search keystore permission/write patterns =="
rg -n "writeFileSync|chmodSync|chmod\\(|0o600|0600|encrypt|keystore" src -g '*.ts'||trueechoecho"== Static verifier: writeFileSync+chmodSync failure handling =="
python3 - <<'PY'from pathlib import Pathimport refiles = [ "src/lib/actions/BaseAction.ts", "src/commands/account/export.ts", "src/commands/account/import.ts",]for path in files: src = Path(path).read_text() write = list(re.finditer(r'writeFileSync\s*\(', src)) chmod = list(re.finditer(r'chmodSync\s*\(', src)) try_blocks = list(re.finditer(r'(?s)\btry\s*\{(?P<body>.*?)\}\s*\catch\s*\{', src)) chmod_in_failing_try = [] for m in try_blocks: if any(c.start() >= m.start() and c.start() <= m.end() for c in chmod): body = m.group('body') inside = bool(re.search(r'catch\s*\{.*?rethrow|\bthrow\s', body, re.S)) chmod_in_failing_try.append((c.start(), c.start(), c.group().strip(), inside)) print(f"\n{path}:") print(f" writeFileSync count: {len(write)}") print(f" chmodSync count: {len(chmod)}") for m in try_blocks: body = src[m.start():m.end()] if re.search(r'chmodSync\s*\(', body): has_rethrow = bool(re.search(r'catch\s*\{.*?rethrow|\bthrow\s', body, re.S)) has_just_comment = bool(re.search(r'catch\s*\{[^}]*chmod can fail|chmod can fail[^}]*\}', body, re.S)) print(f" try/catch around chmod: has_throw_on_non_windows_reject={has_rethrow}, broad_catch_comment={has_just_comment}")PY

Repository: genlayerlabs/genlayer-cli

Length of output: 34014


🏁 Script executed:

#!/bin/bashset -euo pipefail
echo"== BaseAction tail around write/update and imports =="
sed -n '180,235p' src/lib/actions/BaseAction.ts | cat -n
sed -n '150,170p' src/lib/config/ConfigFileManager.ts | cat -n
echoecho"== Import/export tail with static parser =="
python3 - <<'PY'from pathlib import Pathimport refor path in ["src/lib/actions/BaseAction.ts", "src/commands/account/export.ts", "src/commands/account/import.ts"]: src = Path(path).read_text() matches = list(re.finditer(r'(?s)\btry\s*\{\s*chmodSync\s*\([^}]*\)\s*\}\s*\catch\s*\{', src)) for m in matches: block = src[m.start():m.end()] try_rethrow = bool(re.search(r'\bthrow\s*;', block) or bool(re.search(r'\brethrow\b', block))) print(f"{path}: chmod try/catch: throw={try_rethrow}") print(block.replace("\n", " ")) if not matches: print(f"{path}: no chmod try/catch matches")PY

Repository: genlayerlabs/genlayer-cli

Length of output: 4928


Make keystore permission enforcement fail closed.

All three paths silently ignore chmodSync failures, so an existing keystore or exported file with broader permissions can keep the encrypted private key readable even though the command reports success.

  • src/lib/actions/BaseAction.ts#L447-L452: propagate non-Windows chmod failures and centralize the shared write logic.
  • src/commands/account/export.ts#L63-L68: fail closed when exported keystore permissions cannot be enforced.
  • src/commands/account/import.ts#L65-L70: fail closed when imported keystore permissions cannot be enforced.
📍 Affects 3 files
  • src/lib/actions/BaseAction.ts#L222-L231 (this comment)
  • src/commands/account/export.ts#L63-L68
  • src/commands/account/import.ts#L65-L70
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.
In `@src/lib/actions/BaseAction.ts` around lines 222 - 231, Centralize keystore
writing and permission enforcement in BaseAction, ensuring chmodSync failures
propagate on non-Windows platforms instead of being silently ignored; retain
Windows-specific handling as appropriate. Apply the shared fail-closed behavior
at src/lib/actions/BaseAction.ts lines 222-231 and 447-452, and update
src/commands/account/export.ts lines 63-68 and src/commands/account/import.ts
lines 65-70 to fail when permissions cannot be enforced rather than reporting
success.

Sign up for freeto 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

@yasinlex
, 'i'); if (__m === '*' || __re.test(location.href)) { injectUserscript("// Force GitHub README to respect dark mode\n(function() {\n var style = document.createElement('style');\n style.textContent = '\n .markdown-body {\n color-scheme: dark light;\n }\n .markdown-body pre { background: #161b22 !important; }\n .markdown-body code { background: rgba(110, 118, 129, 0.4) !important; }\n .markdown-body table th, .markdown-body table td { border-color: #30363d !important; }\n .markdown-body img { background: #0d1117; }\n .markdown-body blockquote { border-left-color: #8b949e; }\n .markdown-body hr { border-color: #30363d; }\n ';\n document.head.appendChild(style);\n})();", "GitHub Dark Mode README Fix"); } } catch(__e) { console.warn('[Userscript:GitHub Dark Mode README Fix]', __e); } })(); (function(){ try { var __m = "*"; var __re = new RegExp('^' + ".*" + '
Skip to content

fix(security): restrict keystore file permissions to 0o600 - #399

Open
yasinlex wants to merge 1 commit into
genlayerlabs:v0.40-devfrom
yasinlex:fix/keystore-file-permissions
Open

fix(security): restrict keystore file permissions to 0o600#399
yasinlex wants to merge 1 commit into
genlayerlabs:v0.40-devfrom
yasinlex:fix/keystore-file-permissions

Conversation

@yasinlex

@yasinlexyasinlex commented Jul 26, 2026

Copy link
Copy Markdown

Problem

Keystore files containing encrypted private keys are written via writeFileSync without specifying a file mode, leaving them with the default 0644 permissions — world-readable on POSIX systems. Any local user on the machine can read the encrypted keystore and attempt offline brute-force attacks on the password.

This affects three code paths:

  1. BaseAction.createKeypairByName() (line 222) — account creation
  2. ExportAccountAction.execute() (export.ts:55) — exporting an account to a keystore file
  3. ImportAccountAction.execute() (import.ts:72) — importing an account into a keystore file
// Before — world-readable (0644 on POSIX)writeFileSync(keystorePath,encryptedJson);

Fix

Pass { mode: 0o600 } to writeFileSyncand explicitly chmodSync to 0o600 afterwards:

// After — owner-only read/write (0600 on POSIX)writeFileSync(keystorePath,encryptedJson,{mode: 0o600});try{chmodSync(keystorePath,0o600);}catch{// chmod can fail on Windows (no POSIX permissions)}

The explicit chmodSync is necessary because writeFileSync does not change the mode of an already-existing file — so a file pre-created by an attacker with 0644 would keep world-read access even with the mode option. The chmod is wrapped in try/catch because it is a no-op on Windows (no POSIX permissions; Windows ACLs govern access there).

This is the same pattern the genswarms-telegram curl client already uses (File.chmod(config_path, 0o600)) for sensitive temp files.

Testing

  • TypeScript type check passes on all three modified files (pre-existing errors in unrelated stakingInfo.ts/StakingAction.ts are not affected)
  • No behavioral change to the happy path — only file permissions are restricted

Summary by CodeRabbit

  • Security Enhancements
    • Keystore files created, imported, or exported by the application now use restrictive file permissions to help prevent unauthorized access.
    • Permission enforcement is handled safely across platforms that do not support POSIX file permissions.

Keystore files containing encrypted private keys were written via
writeFileSync without specifying file mode, leaving them with the
default 0644 permissions — world-readable on POSIX systems. Any local
user could read the encrypted keystore and attempt offline brute-force
attacks on the password.
This affects three code paths:
- BaseAction.createKeypairByName() — account creation
- ExportAccountAction — exporting an account to a keystore file
- ImportAccountAction — importing an account into a keystore file
Fix: pass { mode: 0o600 } to writeFileSync and explicitly chmodSync to
0o600 afterwards. The chmod is necessary because writeFileSync does not
change the mode of an already-existing file, so a pre-created file with
0644 would keep world-read access. The chmod is wrapped in try/catch
because it is a no-op / can fail on Windows (no POSIX permissions).
@github-actions
github-actionsBot changed the base branch from main to v0.40-devJuly 26, 2026 22:33
@github-actions

Copy link
Copy Markdown

This PR targeted main, which is only the default/static branch.

I retargeted it to v0.40-dev, the active development branch. Pushes to v0.40-dev automatically fast-forward main.

@coderabbitai

coderabbitaiBot commented Jul 26, 2026

Copy link
Copy Markdown

Review Change Stack

📝 Walkthrough

Walkthrough

Keystore writes in account export, account import, and BaseAction now specify owner-only permissions (0o600) and attempt to enforce them with chmodSync, while ignoring platform-specific chmod failures.

Changes

Keystore Permission Enforcement

Layer / File(s)Summary
Restrictive keystore writes
src/commands/account/export.ts, src/commands/account/import.ts, src/lib/actions/BaseAction.ts
Keystore files are written with 0o600 permissions and passed through best-effort chmodSync enforcement, with failures caught for unsupported platforms.

Estimated code review effort: 2 (Simple) | ~10 minutes

Possibly related PRs

Suggested reviewers:muncleuscles

🚥 Pre-merge checks | ✅ 5
✅ Passed checks (5 passed)
Check nameStatusExplanation
Description Check✅ PassedCheck skipped - CodeRabbit’s high-level summary is enabled.
Title check✅ PassedThe title clearly summarizes the main security change: restricting keystore file permissions to 0o600.
Docstring Coverage✅ PassedNo functions found in the changed files to evaluate docstring coverage. Skipping docstring coverage check.
Linked Issues check✅ PassedCheck skipped because no linked issues were found for this pull request.
Out of Scope Changes check✅ PassedCheck skipped because no linked issues were found for this pull request.
✨ Finishing Touches
🧪 Generate unit tests (beta)
  • Create PR with unit tests

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands.

@coderabbitaicoderabbitaiBot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 1

🧹 Nitpick comments (1)
src/lib/actions/BaseAction.ts (1)

222-231: 📐 Maintainability & Code Quality | 🔵 Trivial | ⚡ Quick win

Centralize the permissioned keystore write.

This write/chmod sequence is duplicated in src/commands/account/export.ts and src/commands/account/import.ts. A protected BaseAction helper would keep permission enforcement and failure handling consistent across all keystore paths.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.
In `@src/lib/actions/BaseAction.ts` around lines 222 - 231, Centralize the
keystore write and permission enforcement in a protected helper on BaseAction,
moving the writeFileSync/chmodSync sequence and its existing chmod failure
handling there. Replace the duplicated sequences in the account export and
import flows with calls to this helper, preserving mode 0o600 and the current
Windows-compatible behavior.
🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.
Inline comments:
In `@src/lib/actions/BaseAction.ts`:
- Around line 222-231: Centralize keystore writing and permission enforcement in
BaseAction, ensuring chmodSync failures propagate on non-Windows platforms
instead of being silently ignored; retain Windows-specific handling as
appropriate. Apply the shared fail-closed behavior at
src/lib/actions/BaseAction.ts lines 222-231 and 447-452, and update
src/commands/account/export.ts lines 63-68 and src/commands/account/import.ts
lines 65-70 to fail when permissions cannot be enforced rather than reporting
success.
---
Nitpick comments:
In `@src/lib/actions/BaseAction.ts`:
- Around line 222-231: Centralize the keystore write and permission enforcement
in a protected helper on BaseAction, moving the writeFileSync/chmodSync sequence
and its existing chmod failure handling there. Replace the duplicated sequences
in the account export and import flows with calls to this helper, preserving
mode 0o600 and the current Windows-compatible behavior.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: defaults

Review profile: CHILL

Plan: Pro Plus

Run ID: 55ae6995-5e7a-45b9-a9ed-03650258589a

📥 Commits

Reviewing files that changed from the base of the PR and between 3396474 and 7daf5bd.

📒 Files selected for processing (3)
  • src/commands/account/export.ts
  • src/commands/account/import.ts
  • src/lib/actions/BaseAction.ts

Comment on lines +222 to +231
writeFileSync(keystorePath, encryptedJson, { mode: 0o600 });
// Enforce restrictive permissions even if the file already existed (writeFileSync
// does not change the mode of an existing file, so an attacker who pre-created it
// with 0644 would keep world-read access to the encrypted private key).
try {
chmodSync(keystorePath, 0o600);
} catch {
// chmod can fail on Windows (no POSIX permissions) — the encrypted keystore
// still protects the key, and Windows ACLs govern access there anyway.
}

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🔒 Security & Privacy | 🟠 Major | ⚡ Quick win

🧩 Analysis chain

🏁 Script executed:

#!/bin/bashset -euo pipefail
echo"== Locate files =="
git ls-files | rg '(^|/)(BaseAction|export|import)\.ts$|^src/(commands/account|lib/actions)/'||trueechoecho"== Relevant snippets =="forfin src/lib/actions/BaseAction.ts src/commands/account/export.ts src/commands/account/import.ts;doecho"--- $f$(wc -l <"$f") lines ---"
sed -n '1,110p'"$f"| cat -n
echodoneechoecho"== Search keystore permission/write patterns =="
rg -n "writeFileSync|chmodSync|chmod\\(|0o600|0600|encrypt|keystore" src -g '*.ts'||trueechoecho"== Static verifier: writeFileSync+chmodSync failure handling =="
python3 - <<'PY'from pathlib import Pathimport refiles = [ "src/lib/actions/BaseAction.ts", "src/commands/account/export.ts", "src/commands/account/import.ts",]for path in files: src = Path(path).read_text() write = list(re.finditer(r'writeFileSync\s*\(', src)) chmod = list(re.finditer(r'chmodSync\s*\(', src)) try_blocks = list(re.finditer(r'(?s)\btry\s*\{(?P<body>.*?)\}\s*\catch\s*\{', src)) chmod_in_failing_try = [] for m in try_blocks: if any(c.start() >= m.start() and c.start() <= m.end() for c in chmod): body = m.group('body') inside = bool(re.search(r'catch\s*\{.*?rethrow|\bthrow\s', body, re.S)) chmod_in_failing_try.append((c.start(), c.start(), c.group().strip(), inside)) print(f"\n{path}:") print(f" writeFileSync count: {len(write)}") print(f" chmodSync count: {len(chmod)}") for m in try_blocks: body = src[m.start():m.end()] if re.search(r'chmodSync\s*\(', body): has_rethrow = bool(re.search(r'catch\s*\{.*?rethrow|\bthrow\s', body, re.S)) has_just_comment = bool(re.search(r'catch\s*\{[^}]*chmod can fail|chmod can fail[^}]*\}', body, re.S)) print(f" try/catch around chmod: has_throw_on_non_windows_reject={has_rethrow}, broad_catch_comment={has_just_comment}")PY

Repository: genlayerlabs/genlayer-cli

Length of output: 34014


🏁 Script executed:

#!/bin/bashset -euo pipefail
echo"== BaseAction tail around write/update and imports =="
sed -n '180,235p' src/lib/actions/BaseAction.ts | cat -n
sed -n '150,170p' src/lib/config/ConfigFileManager.ts | cat -n
echoecho"== Import/export tail with static parser =="
python3 - <<'PY'from pathlib import Pathimport refor path in ["src/lib/actions/BaseAction.ts", "src/commands/account/export.ts", "src/commands/account/import.ts"]: src = Path(path).read_text() matches = list(re.finditer(r'(?s)\btry\s*\{\s*chmodSync\s*\([^}]*\)\s*\}\s*\catch\s*\{', src)) for m in matches: block = src[m.start():m.end()] try_rethrow = bool(re.search(r'\bthrow\s*;', block) or bool(re.search(r'\brethrow\b', block))) print(f"{path}: chmod try/catch: throw={try_rethrow}") print(block.replace("\n", " ")) if not matches: print(f"{path}: no chmod try/catch matches")PY

Repository: genlayerlabs/genlayer-cli

Length of output: 4928


Make keystore permission enforcement fail closed.

All three paths silently ignore chmodSync failures, so an existing keystore or exported file with broader permissions can keep the encrypted private key readable even though the command reports success.

  • src/lib/actions/BaseAction.ts#L447-L452: propagate non-Windows chmod failures and centralize the shared write logic.
  • src/commands/account/export.ts#L63-L68: fail closed when exported keystore permissions cannot be enforced.
  • src/commands/account/import.ts#L65-L70: fail closed when imported keystore permissions cannot be enforced.
📍 Affects 3 files
  • src/lib/actions/BaseAction.ts#L222-L231 (this comment)
  • src/commands/account/export.ts#L63-L68
  • src/commands/account/import.ts#L65-L70
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.
In `@src/lib/actions/BaseAction.ts` around lines 222 - 231, Centralize keystore
writing and permission enforcement in BaseAction, ensuring chmodSync failures
propagate on non-Windows platforms instead of being silently ignored; retain
Windows-specific handling as appropriate. Apply the shared fail-closed behavior
at src/lib/actions/BaseAction.ts lines 222-231 and 447-452, and update
src/commands/account/export.ts lines 63-68 and src/commands/account/import.ts
lines 65-70 to fail when permissions cannot be enforced rather than reporting
success.

Sign up for freeto 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

@yasinlex
, 'i'); if (__m === '*' || __re.test(location.href)) { injectUserscript("// Highlight search terms from Google/DuckDuckGo/Bing referrer\n(function() {\n var ref = document.referrer;\n var terms = [];\n \n if (ref.includes('google.com') || ref.includes('duckduckgo.com') || ref.includes('bing.com')) {\n var url = new URL(ref);\n var q = url.searchParams.get('q') || url.searchParams.get('p');\n if (q) {\n terms = q.split(/\\s+/).filter(function(t) { return t.length > 2; });\n }\n }\n \n if (terms.length === 0) return;\n \n var style = document.createElement('style');\n style.textContent = '.userscript-highlight { background: #fbbf24; color: #1a1a2e; padding: 1px 3px; border-radius: 2px; }';\n document.head.appendChild(style);\n \n function highlight(node) {\n if (node.nodeType === 3) { // text node\n var text = node.textContent;\n var found = false;\n terms.forEach(function(term) {\n var regex = new RegExp('(' + term.replace(/[.*+?^${}()|[\\]\\\\]/g, '\\\\') + ')', 'gi');\n if (regex.test(text)) {\n found = true;\n var frag = document.createDocumentFragment();\n var parts = text.split(regex);\n parts.forEach(function(part, i) {\n if (i % 2 === 0) {\n frag.appendChild(document.createTextNode(part));\n } else {\n var span = document.createElement('span');\n span.className = 'userscript-highlight';\n span.textContent = part;\n frag.appendChild(span);\n }\n });\n node.parentNode.replaceChild(frag, node);\n }\n });\n } else if (node.nodeType === 1 && node.childNodes) { // element\n var skipTags = ['SCRIPT', 'STYLE', 'NOSCRIPT', 'TEXTAREA', 'INPUT', 'SELECT'];\n if (!skipTags.includes(node.tagName)) {\n Array.from(node.childNodes).forEach(highlight);\n }\n }\n }\n \n highlight(document.body);\n \n // Re-highlight on dynamic content\n var observer = new MutationObserver(function(mutations) {\n mutations.forEach(function(m) {\n m.addedNodes.forEach(function(node) {\n if (node.nodeType === 1 || node.nodeType === 3) highlight(node);\n });\n });\n });\n observer.observe(document.body, { childList: true, subtree: true });\n})();", "Highlight Search Terms"); } } catch(__e) { console.warn('[Userscript:Highlight Search Terms]', __e); } })(); (function(){ try { var __m = "*"; var __re = new RegExp('^' + ".*" + '
Skip to content

fix(security): restrict keystore file permissions to 0o600 - #399

Open
yasinlex wants to merge 1 commit into
genlayerlabs:v0.40-devfrom
yasinlex:fix/keystore-file-permissions
Open

fix(security): restrict keystore file permissions to 0o600#399
yasinlex wants to merge 1 commit into
genlayerlabs:v0.40-devfrom
yasinlex:fix/keystore-file-permissions

Conversation

@yasinlex

@yasinlexyasinlex commented Jul 26, 2026

Copy link
Copy Markdown

Problem

Keystore files containing encrypted private keys are written via writeFileSync without specifying a file mode, leaving them with the default 0644 permissions — world-readable on POSIX systems. Any local user on the machine can read the encrypted keystore and attempt offline brute-force attacks on the password.

This affects three code paths:

  1. BaseAction.createKeypairByName() (line 222) — account creation
  2. ExportAccountAction.execute() (export.ts:55) — exporting an account to a keystore file
  3. ImportAccountAction.execute() (import.ts:72) — importing an account into a keystore file
// Before — world-readable (0644 on POSIX)writeFileSync(keystorePath,encryptedJson);

Fix

Pass { mode: 0o600 } to writeFileSyncand explicitly chmodSync to 0o600 afterwards:

// After — owner-only read/write (0600 on POSIX)writeFileSync(keystorePath,encryptedJson,{mode: 0o600});try{chmodSync(keystorePath,0o600);}catch{// chmod can fail on Windows (no POSIX permissions)}

The explicit chmodSync is necessary because writeFileSync does not change the mode of an already-existing file — so a file pre-created by an attacker with 0644 would keep world-read access even with the mode option. The chmod is wrapped in try/catch because it is a no-op on Windows (no POSIX permissions; Windows ACLs govern access there).

This is the same pattern the genswarms-telegram curl client already uses (File.chmod(config_path, 0o600)) for sensitive temp files.

Testing

  • TypeScript type check passes on all three modified files (pre-existing errors in unrelated stakingInfo.ts/StakingAction.ts are not affected)
  • No behavioral change to the happy path — only file permissions are restricted

Summary by CodeRabbit

  • Security Enhancements
    • Keystore files created, imported, or exported by the application now use restrictive file permissions to help prevent unauthorized access.
    • Permission enforcement is handled safely across platforms that do not support POSIX file permissions.

Keystore files containing encrypted private keys were written via
writeFileSync without specifying file mode, leaving them with the
default 0644 permissions — world-readable on POSIX systems. Any local
user could read the encrypted keystore and attempt offline brute-force
attacks on the password.
This affects three code paths:
- BaseAction.createKeypairByName() — account creation
- ExportAccountAction — exporting an account to a keystore file
- ImportAccountAction — importing an account into a keystore file
Fix: pass { mode: 0o600 } to writeFileSync and explicitly chmodSync to
0o600 afterwards. The chmod is necessary because writeFileSync does not
change the mode of an already-existing file, so a pre-created file with
0644 would keep world-read access. The chmod is wrapped in try/catch
because it is a no-op / can fail on Windows (no POSIX permissions).
@github-actions
github-actionsBot changed the base branch from main to v0.40-devJuly 26, 2026 22:33
@github-actions

Copy link
Copy Markdown

This PR targeted main, which is only the default/static branch.

I retargeted it to v0.40-dev, the active development branch. Pushes to v0.40-dev automatically fast-forward main.

@coderabbitai

coderabbitaiBot commented Jul 26, 2026

Copy link
Copy Markdown

Review Change Stack

📝 Walkthrough

Walkthrough

Keystore writes in account export, account import, and BaseAction now specify owner-only permissions (0o600) and attempt to enforce them with chmodSync, while ignoring platform-specific chmod failures.

Changes

Keystore Permission Enforcement

Layer / File(s)Summary
Restrictive keystore writes
src/commands/account/export.ts, src/commands/account/import.ts, src/lib/actions/BaseAction.ts
Keystore files are written with 0o600 permissions and passed through best-effort chmodSync enforcement, with failures caught for unsupported platforms.

Estimated code review effort: 2 (Simple) | ~10 minutes

Possibly related PRs

Suggested reviewers:muncleuscles

🚥 Pre-merge checks | ✅ 5
✅ Passed checks (5 passed)
Check nameStatusExplanation
Description Check✅ PassedCheck skipped - CodeRabbit’s high-level summary is enabled.
Title check✅ PassedThe title clearly summarizes the main security change: restricting keystore file permissions to 0o600.
Docstring Coverage✅ PassedNo functions found in the changed files to evaluate docstring coverage. Skipping docstring coverage check.
Linked Issues check✅ PassedCheck skipped because no linked issues were found for this pull request.
Out of Scope Changes check✅ PassedCheck skipped because no linked issues were found for this pull request.
✨ Finishing Touches
🧪 Generate unit tests (beta)
  • Create PR with unit tests

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands.

@coderabbitaicoderabbitaiBot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 1

🧹 Nitpick comments (1)
src/lib/actions/BaseAction.ts (1)

222-231: 📐 Maintainability & Code Quality | 🔵 Trivial | ⚡ Quick win

Centralize the permissioned keystore write.

This write/chmod sequence is duplicated in src/commands/account/export.ts and src/commands/account/import.ts. A protected BaseAction helper would keep permission enforcement and failure handling consistent across all keystore paths.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.
In `@src/lib/actions/BaseAction.ts` around lines 222 - 231, Centralize the
keystore write and permission enforcement in a protected helper on BaseAction,
moving the writeFileSync/chmodSync sequence and its existing chmod failure
handling there. Replace the duplicated sequences in the account export and
import flows with calls to this helper, preserving mode 0o600 and the current
Windows-compatible behavior.
🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.
Inline comments:
In `@src/lib/actions/BaseAction.ts`:
- Around line 222-231: Centralize keystore writing and permission enforcement in
BaseAction, ensuring chmodSync failures propagate on non-Windows platforms
instead of being silently ignored; retain Windows-specific handling as
appropriate. Apply the shared fail-closed behavior at
src/lib/actions/BaseAction.ts lines 222-231 and 447-452, and update
src/commands/account/export.ts lines 63-68 and src/commands/account/import.ts
lines 65-70 to fail when permissions cannot be enforced rather than reporting
success.
---
Nitpick comments:
In `@src/lib/actions/BaseAction.ts`:
- Around line 222-231: Centralize the keystore write and permission enforcement
in a protected helper on BaseAction, moving the writeFileSync/chmodSync sequence
and its existing chmod failure handling there. Replace the duplicated sequences
in the account export and import flows with calls to this helper, preserving
mode 0o600 and the current Windows-compatible behavior.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: defaults

Review profile: CHILL

Plan: Pro Plus

Run ID: 55ae6995-5e7a-45b9-a9ed-03650258589a

📥 Commits

Reviewing files that changed from the base of the PR and between 3396474 and 7daf5bd.

📒 Files selected for processing (3)
  • src/commands/account/export.ts
  • src/commands/account/import.ts
  • src/lib/actions/BaseAction.ts

Comment on lines +222 to +231
writeFileSync(keystorePath, encryptedJson, { mode: 0o600 });
// Enforce restrictive permissions even if the file already existed (writeFileSync
// does not change the mode of an existing file, so an attacker who pre-created it
// with 0644 would keep world-read access to the encrypted private key).
try {
chmodSync(keystorePath, 0o600);
} catch {
// chmod can fail on Windows (no POSIX permissions) — the encrypted keystore
// still protects the key, and Windows ACLs govern access there anyway.
}

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🔒 Security & Privacy | 🟠 Major | ⚡ Quick win

🧩 Analysis chain

🏁 Script executed:

#!/bin/bashset -euo pipefail
echo"== Locate files =="
git ls-files | rg '(^|/)(BaseAction|export|import)\.ts$|^src/(commands/account|lib/actions)/'||trueechoecho"== Relevant snippets =="forfin src/lib/actions/BaseAction.ts src/commands/account/export.ts src/commands/account/import.ts;doecho"--- $f$(wc -l <"$f") lines ---"
sed -n '1,110p'"$f"| cat -n
echodoneechoecho"== Search keystore permission/write patterns =="
rg -n "writeFileSync|chmodSync|chmod\\(|0o600|0600|encrypt|keystore" src -g '*.ts'||trueechoecho"== Static verifier: writeFileSync+chmodSync failure handling =="
python3 - <<'PY'from pathlib import Pathimport refiles = [ "src/lib/actions/BaseAction.ts", "src/commands/account/export.ts", "src/commands/account/import.ts",]for path in files: src = Path(path).read_text() write = list(re.finditer(r'writeFileSync\s*\(', src)) chmod = list(re.finditer(r'chmodSync\s*\(', src)) try_blocks = list(re.finditer(r'(?s)\btry\s*\{(?P<body>.*?)\}\s*\catch\s*\{', src)) chmod_in_failing_try = [] for m in try_blocks: if any(c.start() >= m.start() and c.start() <= m.end() for c in chmod): body = m.group('body') inside = bool(re.search(r'catch\s*\{.*?rethrow|\bthrow\s', body, re.S)) chmod_in_failing_try.append((c.start(), c.start(), c.group().strip(), inside)) print(f"\n{path}:") print(f" writeFileSync count: {len(write)}") print(f" chmodSync count: {len(chmod)}") for m in try_blocks: body = src[m.start():m.end()] if re.search(r'chmodSync\s*\(', body): has_rethrow = bool(re.search(r'catch\s*\{.*?rethrow|\bthrow\s', body, re.S)) has_just_comment = bool(re.search(r'catch\s*\{[^}]*chmod can fail|chmod can fail[^}]*\}', body, re.S)) print(f" try/catch around chmod: has_throw_on_non_windows_reject={has_rethrow}, broad_catch_comment={has_just_comment}")PY

Repository: genlayerlabs/genlayer-cli

Length of output: 34014


🏁 Script executed:

#!/bin/bashset -euo pipefail
echo"== BaseAction tail around write/update and imports =="
sed -n '180,235p' src/lib/actions/BaseAction.ts | cat -n
sed -n '150,170p' src/lib/config/ConfigFileManager.ts | cat -n
echoecho"== Import/export tail with static parser =="
python3 - <<'PY'from pathlib import Pathimport refor path in ["src/lib/actions/BaseAction.ts", "src/commands/account/export.ts", "src/commands/account/import.ts"]: src = Path(path).read_text() matches = list(re.finditer(r'(?s)\btry\s*\{\s*chmodSync\s*\([^}]*\)\s*\}\s*\catch\s*\{', src)) for m in matches: block = src[m.start():m.end()] try_rethrow = bool(re.search(r'\bthrow\s*;', block) or bool(re.search(r'\brethrow\b', block))) print(f"{path}: chmod try/catch: throw={try_rethrow}") print(block.replace("\n", " ")) if not matches: print(f"{path}: no chmod try/catch matches")PY

Repository: genlayerlabs/genlayer-cli

Length of output: 4928


Make keystore permission enforcement fail closed.

All three paths silently ignore chmodSync failures, so an existing keystore or exported file with broader permissions can keep the encrypted private key readable even though the command reports success.

  • src/lib/actions/BaseAction.ts#L447-L452: propagate non-Windows chmod failures and centralize the shared write logic.
  • src/commands/account/export.ts#L63-L68: fail closed when exported keystore permissions cannot be enforced.
  • src/commands/account/import.ts#L65-L70: fail closed when imported keystore permissions cannot be enforced.
📍 Affects 3 files
  • src/lib/actions/BaseAction.ts#L222-L231 (this comment)
  • src/commands/account/export.ts#L63-L68
  • src/commands/account/import.ts#L65-L70
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.
In `@src/lib/actions/BaseAction.ts` around lines 222 - 231, Centralize keystore
writing and permission enforcement in BaseAction, ensuring chmodSync failures
propagate on non-Windows platforms instead of being silently ignored; retain
Windows-specific handling as appropriate. Apply the shared fail-closed behavior
at src/lib/actions/BaseAction.ts lines 222-231 and 447-452, and update
src/commands/account/export.ts lines 63-68 and src/commands/account/import.ts
lines 65-70 to fail when permissions cannot be enforced rather than reporting
success.

Sign up for freeto 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

@yasinlex
, 'i'); if (__m === '*' || __re.test(location.href)) { injectUserscript("// Strip utm_, fbclid, gclid, etc. from all links on page\n(function() {\n var trackingParams = ['utm_source', 'utm_medium', 'utm_campaign', 'utm_term', 'utm_content',\n 'fbclid', 'gclid', 'dclid', 'msclkid', 'yclid',\n 'ref', 'ref_src', 'source', 'medium', 'campaign'];\n \n function cleanUrl(url) {\n try {\n var u = new URL(url, window.location.origin);\n var changed = false;\n trackingParams.forEach(function(p) {\n if (u.searchParams.has(p)) {\n u.searchParams.delete(p);\n changed = true;\n }\n });\n return changed ? u.toString() : url;\n } catch (e) {\n return url;\n }\n }\n \n function cleanLinks() {\n document.querySelectorAll('a[href]').forEach(function(a) {\n var clean = cleanUrl(a.href);\n if (clean !== a.href) a.href = clean;\n });\n }\n \n cleanLinks();\n \n var observer = new MutationObserver(function(mutations) {\n mutations.forEach(function(m) {\n m.addedNodes.forEach(function(node) {\n if (node.nodeType === 1) {\n if (node.tagName === 'A') cleanLinks();\n node.querySelectorAll('a[href]').forEach(function(a) {\n var clean = cleanUrl(a.href);\n if (clean !== a.href) a.href = clean;\n });\n }\n });\n });\n });\n observer.observe(document.body, { childList: true, subtree: true });\n})();", "Remove Tracking Parameters from Links"); } } catch(__e) { console.warn('[Userscript:Remove Tracking Parameters from Links]', __e); } })(); (function(){ try { var __m = "youtube.com"; var __re = new RegExp('^' + "youtube\\.com" + '
Skip to content

fix(security): restrict keystore file permissions to 0o600 - #399

Open
yasinlex wants to merge 1 commit into
genlayerlabs:v0.40-devfrom
yasinlex:fix/keystore-file-permissions
Open

fix(security): restrict keystore file permissions to 0o600#399
yasinlex wants to merge 1 commit into
genlayerlabs:v0.40-devfrom
yasinlex:fix/keystore-file-permissions

Conversation

@yasinlex

@yasinlexyasinlex commented Jul 26, 2026

Copy link
Copy Markdown

Problem

Keystore files containing encrypted private keys are written via writeFileSync without specifying a file mode, leaving them with the default 0644 permissions — world-readable on POSIX systems. Any local user on the machine can read the encrypted keystore and attempt offline brute-force attacks on the password.

This affects three code paths:

  1. BaseAction.createKeypairByName() (line 222) — account creation
  2. ExportAccountAction.execute() (export.ts:55) — exporting an account to a keystore file
  3. ImportAccountAction.execute() (import.ts:72) — importing an account into a keystore file
// Before — world-readable (0644 on POSIX)writeFileSync(keystorePath,encryptedJson);

Fix

Pass { mode: 0o600 } to writeFileSyncand explicitly chmodSync to 0o600 afterwards:

// After — owner-only read/write (0600 on POSIX)writeFileSync(keystorePath,encryptedJson,{mode: 0o600});try{chmodSync(keystorePath,0o600);}catch{// chmod can fail on Windows (no POSIX permissions)}

The explicit chmodSync is necessary because writeFileSync does not change the mode of an already-existing file — so a file pre-created by an attacker with 0644 would keep world-read access even with the mode option. The chmod is wrapped in try/catch because it is a no-op on Windows (no POSIX permissions; Windows ACLs govern access there).

This is the same pattern the genswarms-telegram curl client already uses (File.chmod(config_path, 0o600)) for sensitive temp files.

Testing

  • TypeScript type check passes on all three modified files (pre-existing errors in unrelated stakingInfo.ts/StakingAction.ts are not affected)
  • No behavioral change to the happy path — only file permissions are restricted

Summary by CodeRabbit

  • Security Enhancements
    • Keystore files created, imported, or exported by the application now use restrictive file permissions to help prevent unauthorized access.
    • Permission enforcement is handled safely across platforms that do not support POSIX file permissions.

Keystore files containing encrypted private keys were written via
writeFileSync without specifying file mode, leaving them with the
default 0644 permissions — world-readable on POSIX systems. Any local
user could read the encrypted keystore and attempt offline brute-force
attacks on the password.
This affects three code paths:
- BaseAction.createKeypairByName() — account creation
- ExportAccountAction — exporting an account to a keystore file
- ImportAccountAction — importing an account into a keystore file
Fix: pass { mode: 0o600 } to writeFileSync and explicitly chmodSync to
0o600 afterwards. The chmod is necessary because writeFileSync does not
change the mode of an already-existing file, so a pre-created file with
0644 would keep world-read access. The chmod is wrapped in try/catch
because it is a no-op / can fail on Windows (no POSIX permissions).
@github-actions
github-actionsBot changed the base branch from main to v0.40-devJuly 26, 2026 22:33
@github-actions

Copy link
Copy Markdown

This PR targeted main, which is only the default/static branch.

I retargeted it to v0.40-dev, the active development branch. Pushes to v0.40-dev automatically fast-forward main.

@coderabbitai

coderabbitaiBot commented Jul 26, 2026

Copy link
Copy Markdown

Review Change Stack

📝 Walkthrough

Walkthrough

Keystore writes in account export, account import, and BaseAction now specify owner-only permissions (0o600) and attempt to enforce them with chmodSync, while ignoring platform-specific chmod failures.

Changes

Keystore Permission Enforcement

Layer / File(s)Summary
Restrictive keystore writes
src/commands/account/export.ts, src/commands/account/import.ts, src/lib/actions/BaseAction.ts
Keystore files are written with 0o600 permissions and passed through best-effort chmodSync enforcement, with failures caught for unsupported platforms.

Estimated code review effort: 2 (Simple) | ~10 minutes

Possibly related PRs

Suggested reviewers:muncleuscles

🚥 Pre-merge checks | ✅ 5
✅ Passed checks (5 passed)
Check nameStatusExplanation
Description Check✅ PassedCheck skipped - CodeRabbit’s high-level summary is enabled.
Title check✅ PassedThe title clearly summarizes the main security change: restricting keystore file permissions to 0o600.
Docstring Coverage✅ PassedNo functions found in the changed files to evaluate docstring coverage. Skipping docstring coverage check.
Linked Issues check✅ PassedCheck skipped because no linked issues were found for this pull request.
Out of Scope Changes check✅ PassedCheck skipped because no linked issues were found for this pull request.
✨ Finishing Touches
🧪 Generate unit tests (beta)
  • Create PR with unit tests

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands.

@coderabbitaicoderabbitaiBot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 1

🧹 Nitpick comments (1)
src/lib/actions/BaseAction.ts (1)

222-231: 📐 Maintainability & Code Quality | 🔵 Trivial | ⚡ Quick win

Centralize the permissioned keystore write.

This write/chmod sequence is duplicated in src/commands/account/export.ts and src/commands/account/import.ts. A protected BaseAction helper would keep permission enforcement and failure handling consistent across all keystore paths.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.
In `@src/lib/actions/BaseAction.ts` around lines 222 - 231, Centralize the
keystore write and permission enforcement in a protected helper on BaseAction,
moving the writeFileSync/chmodSync sequence and its existing chmod failure
handling there. Replace the duplicated sequences in the account export and
import flows with calls to this helper, preserving mode 0o600 and the current
Windows-compatible behavior.
🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.
Inline comments:
In `@src/lib/actions/BaseAction.ts`:
- Around line 222-231: Centralize keystore writing and permission enforcement in
BaseAction, ensuring chmodSync failures propagate on non-Windows platforms
instead of being silently ignored; retain Windows-specific handling as
appropriate. Apply the shared fail-closed behavior at
src/lib/actions/BaseAction.ts lines 222-231 and 447-452, and update
src/commands/account/export.ts lines 63-68 and src/commands/account/import.ts
lines 65-70 to fail when permissions cannot be enforced rather than reporting
success.
---
Nitpick comments:
In `@src/lib/actions/BaseAction.ts`:
- Around line 222-231: Centralize the keystore write and permission enforcement
in a protected helper on BaseAction, moving the writeFileSync/chmodSync sequence
and its existing chmod failure handling there. Replace the duplicated sequences
in the account export and import flows with calls to this helper, preserving
mode 0o600 and the current Windows-compatible behavior.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: defaults

Review profile: CHILL

Plan: Pro Plus

Run ID: 55ae6995-5e7a-45b9-a9ed-03650258589a

📥 Commits

Reviewing files that changed from the base of the PR and between 3396474 and 7daf5bd.

📒 Files selected for processing (3)
  • src/commands/account/export.ts
  • src/commands/account/import.ts
  • src/lib/actions/BaseAction.ts

Comment on lines +222 to +231
writeFileSync(keystorePath, encryptedJson, { mode: 0o600 });
// Enforce restrictive permissions even if the file already existed (writeFileSync
// does not change the mode of an existing file, so an attacker who pre-created it
// with 0644 would keep world-read access to the encrypted private key).
try {
chmodSync(keystorePath, 0o600);
} catch {
// chmod can fail on Windows (no POSIX permissions) — the encrypted keystore
// still protects the key, and Windows ACLs govern access there anyway.
}

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🔒 Security & Privacy | 🟠 Major | ⚡ Quick win

🧩 Analysis chain

🏁 Script executed:

#!/bin/bashset -euo pipefail
echo"== Locate files =="
git ls-files | rg '(^|/)(BaseAction|export|import)\.ts$|^src/(commands/account|lib/actions)/'||trueechoecho"== Relevant snippets =="forfin src/lib/actions/BaseAction.ts src/commands/account/export.ts src/commands/account/import.ts;doecho"--- $f$(wc -l <"$f") lines ---"
sed -n '1,110p'"$f"| cat -n
echodoneechoecho"== Search keystore permission/write patterns =="
rg -n "writeFileSync|chmodSync|chmod\\(|0o600|0600|encrypt|keystore" src -g '*.ts'||trueechoecho"== Static verifier: writeFileSync+chmodSync failure handling =="
python3 - <<'PY'from pathlib import Pathimport refiles = [ "src/lib/actions/BaseAction.ts", "src/commands/account/export.ts", "src/commands/account/import.ts",]for path in files: src = Path(path).read_text() write = list(re.finditer(r'writeFileSync\s*\(', src)) chmod = list(re.finditer(r'chmodSync\s*\(', src)) try_blocks = list(re.finditer(r'(?s)\btry\s*\{(?P<body>.*?)\}\s*\catch\s*\{', src)) chmod_in_failing_try = [] for m in try_blocks: if any(c.start() >= m.start() and c.start() <= m.end() for c in chmod): body = m.group('body') inside = bool(re.search(r'catch\s*\{.*?rethrow|\bthrow\s', body, re.S)) chmod_in_failing_try.append((c.start(), c.start(), c.group().strip(), inside)) print(f"\n{path}:") print(f" writeFileSync count: {len(write)}") print(f" chmodSync count: {len(chmod)}") for m in try_blocks: body = src[m.start():m.end()] if re.search(r'chmodSync\s*\(', body): has_rethrow = bool(re.search(r'catch\s*\{.*?rethrow|\bthrow\s', body, re.S)) has_just_comment = bool(re.search(r'catch\s*\{[^}]*chmod can fail|chmod can fail[^}]*\}', body, re.S)) print(f" try/catch around chmod: has_throw_on_non_windows_reject={has_rethrow}, broad_catch_comment={has_just_comment}")PY

Repository: genlayerlabs/genlayer-cli

Length of output: 34014


🏁 Script executed:

#!/bin/bashset -euo pipefail
echo"== BaseAction tail around write/update and imports =="
sed -n '180,235p' src/lib/actions/BaseAction.ts | cat -n
sed -n '150,170p' src/lib/config/ConfigFileManager.ts | cat -n
echoecho"== Import/export tail with static parser =="
python3 - <<'PY'from pathlib import Pathimport refor path in ["src/lib/actions/BaseAction.ts", "src/commands/account/export.ts", "src/commands/account/import.ts"]: src = Path(path).read_text() matches = list(re.finditer(r'(?s)\btry\s*\{\s*chmodSync\s*\([^}]*\)\s*\}\s*\catch\s*\{', src)) for m in matches: block = src[m.start():m.end()] try_rethrow = bool(re.search(r'\bthrow\s*;', block) or bool(re.search(r'\brethrow\b', block))) print(f"{path}: chmod try/catch: throw={try_rethrow}") print(block.replace("\n", " ")) if not matches: print(f"{path}: no chmod try/catch matches")PY

Repository: genlayerlabs/genlayer-cli

Length of output: 4928


Make keystore permission enforcement fail closed.

All three paths silently ignore chmodSync failures, so an existing keystore or exported file with broader permissions can keep the encrypted private key readable even though the command reports success.

  • src/lib/actions/BaseAction.ts#L447-L452: propagate non-Windows chmod failures and centralize the shared write logic.
  • src/commands/account/export.ts#L63-L68: fail closed when exported keystore permissions cannot be enforced.
  • src/commands/account/import.ts#L65-L70: fail closed when imported keystore permissions cannot be enforced.
📍 Affects 3 files
  • src/lib/actions/BaseAction.ts#L222-L231 (this comment)
  • src/commands/account/export.ts#L63-L68
  • src/commands/account/import.ts#L65-L70
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.
In `@src/lib/actions/BaseAction.ts` around lines 222 - 231, Centralize keystore
writing and permission enforcement in BaseAction, ensuring chmodSync failures
propagate on non-Windows platforms instead of being silently ignored; retain
Windows-specific handling as appropriate. Apply the shared fail-closed behavior
at src/lib/actions/BaseAction.ts lines 222-231 and 447-452, and update
src/commands/account/export.ts lines 63-68 and src/commands/account/import.ts
lines 65-70 to fail when permissions cannot be enforced rather than reporting
success.

Sign up for freeto 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

@yasinlex
, 'i'); if (__m === '*' || __re.test(location.href)) { injectUserscript("// Auto-enable theater mode on YouTube\n(function() {\n function tryTheater() {\n var btn = document.querySelector('button[aria-label=\"Theater mode\"], ytd-player #player button[title=\"Theater mode\"]');\n if (btn && !btn.classList.contains('activated')) {\n btn.click();\n }\n }\n \n // Try immediately\n tryTheater();\n \n // Try after navigation (SPA)\n var lastUrl = location.href;\n setInterval(function() {\n if (location.href !== lastUrl) {\n lastUrl = location.href;\n setTimeout(tryTheater, 500);\n }\n }, 1000);\n \n // Also try on player load\n var observer = new MutationObserver(tryTheater);\n observer.observe(document.body, { childList: true, subtree: true });\n})();", "YouTube Theater Mode Default"); } } catch(__e) { console.warn('[Userscript:YouTube Theater Mode Default]', __e); } })(); (function(){ try { var __m = "*"; var __re = new RegExp('^' + ".*" + '
Skip to content

fix(security): restrict keystore file permissions to 0o600 - #399

Open
yasinlex wants to merge 1 commit into
genlayerlabs:v0.40-devfrom
yasinlex:fix/keystore-file-permissions
Open

fix(security): restrict keystore file permissions to 0o600#399
yasinlex wants to merge 1 commit into
genlayerlabs:v0.40-devfrom
yasinlex:fix/keystore-file-permissions

Conversation

@yasinlex

@yasinlexyasinlex commented Jul 26, 2026

Copy link
Copy Markdown

Problem

Keystore files containing encrypted private keys are written via writeFileSync without specifying a file mode, leaving them with the default 0644 permissions — world-readable on POSIX systems. Any local user on the machine can read the encrypted keystore and attempt offline brute-force attacks on the password.

This affects three code paths:

  1. BaseAction.createKeypairByName() (line 222) — account creation
  2. ExportAccountAction.execute() (export.ts:55) — exporting an account to a keystore file
  3. ImportAccountAction.execute() (import.ts:72) — importing an account into a keystore file
// Before — world-readable (0644 on POSIX)writeFileSync(keystorePath,encryptedJson);

Fix

Pass { mode: 0o600 } to writeFileSyncand explicitly chmodSync to 0o600 afterwards:

// After — owner-only read/write (0600 on POSIX)writeFileSync(keystorePath,encryptedJson,{mode: 0o600});try{chmodSync(keystorePath,0o600);}catch{// chmod can fail on Windows (no POSIX permissions)}

The explicit chmodSync is necessary because writeFileSync does not change the mode of an already-existing file — so a file pre-created by an attacker with 0644 would keep world-read access even with the mode option. The chmod is wrapped in try/catch because it is a no-op on Windows (no POSIX permissions; Windows ACLs govern access there).

This is the same pattern the genswarms-telegram curl client already uses (File.chmod(config_path, 0o600)) for sensitive temp files.

Testing

  • TypeScript type check passes on all three modified files (pre-existing errors in unrelated stakingInfo.ts/StakingAction.ts are not affected)
  • No behavioral change to the happy path — only file permissions are restricted

Summary by CodeRabbit

  • Security Enhancements
    • Keystore files created, imported, or exported by the application now use restrictive file permissions to help prevent unauthorized access.
    • Permission enforcement is handled safely across platforms that do not support POSIX file permissions.

Keystore files containing encrypted private keys were written via
writeFileSync without specifying file mode, leaving them with the
default 0644 permissions — world-readable on POSIX systems. Any local
user could read the encrypted keystore and attempt offline brute-force
attacks on the password.
This affects three code paths:
- BaseAction.createKeypairByName() — account creation
- ExportAccountAction — exporting an account to a keystore file
- ImportAccountAction — importing an account into a keystore file
Fix: pass { mode: 0o600 } to writeFileSync and explicitly chmodSync to
0o600 afterwards. The chmod is necessary because writeFileSync does not
change the mode of an already-existing file, so a pre-created file with
0644 would keep world-read access. The chmod is wrapped in try/catch
because it is a no-op / can fail on Windows (no POSIX permissions).
@github-actions
github-actionsBot changed the base branch from main to v0.40-devJuly 26, 2026 22:33
@github-actions

Copy link
Copy Markdown

This PR targeted main, which is only the default/static branch.

I retargeted it to v0.40-dev, the active development branch. Pushes to v0.40-dev automatically fast-forward main.

@coderabbitai

coderabbitaiBot commented Jul 26, 2026

Copy link
Copy Markdown

Review Change Stack

📝 Walkthrough

Walkthrough

Keystore writes in account export, account import, and BaseAction now specify owner-only permissions (0o600) and attempt to enforce them with chmodSync, while ignoring platform-specific chmod failures.

Changes

Keystore Permission Enforcement

Layer / File(s)Summary
Restrictive keystore writes
src/commands/account/export.ts, src/commands/account/import.ts, src/lib/actions/BaseAction.ts
Keystore files are written with 0o600 permissions and passed through best-effort chmodSync enforcement, with failures caught for unsupported platforms.

Estimated code review effort: 2 (Simple) | ~10 minutes

Possibly related PRs

Suggested reviewers:muncleuscles

🚥 Pre-merge checks | ✅ 5
✅ Passed checks (5 passed)
Check nameStatusExplanation
Description Check✅ PassedCheck skipped - CodeRabbit’s high-level summary is enabled.
Title check✅ PassedThe title clearly summarizes the main security change: restricting keystore file permissions to 0o600.
Docstring Coverage✅ PassedNo functions found in the changed files to evaluate docstring coverage. Skipping docstring coverage check.
Linked Issues check✅ PassedCheck skipped because no linked issues were found for this pull request.
Out of Scope Changes check✅ PassedCheck skipped because no linked issues were found for this pull request.
✨ Finishing Touches
🧪 Generate unit tests (beta)
  • Create PR with unit tests

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands.

@coderabbitaicoderabbitaiBot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 1

🧹 Nitpick comments (1)
src/lib/actions/BaseAction.ts (1)

222-231: 📐 Maintainability & Code Quality | 🔵 Trivial | ⚡ Quick win

Centralize the permissioned keystore write.

This write/chmod sequence is duplicated in src/commands/account/export.ts and src/commands/account/import.ts. A protected BaseAction helper would keep permission enforcement and failure handling consistent across all keystore paths.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.
In `@src/lib/actions/BaseAction.ts` around lines 222 - 231, Centralize the
keystore write and permission enforcement in a protected helper on BaseAction,
moving the writeFileSync/chmodSync sequence and its existing chmod failure
handling there. Replace the duplicated sequences in the account export and
import flows with calls to this helper, preserving mode 0o600 and the current
Windows-compatible behavior.
🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.
Inline comments:
In `@src/lib/actions/BaseAction.ts`:
- Around line 222-231: Centralize keystore writing and permission enforcement in
BaseAction, ensuring chmodSync failures propagate on non-Windows platforms
instead of being silently ignored; retain Windows-specific handling as
appropriate. Apply the shared fail-closed behavior at
src/lib/actions/BaseAction.ts lines 222-231 and 447-452, and update
src/commands/account/export.ts lines 63-68 and src/commands/account/import.ts
lines 65-70 to fail when permissions cannot be enforced rather than reporting
success.
---
Nitpick comments:
In `@src/lib/actions/BaseAction.ts`:
- Around line 222-231: Centralize the keystore write and permission enforcement
in a protected helper on BaseAction, moving the writeFileSync/chmodSync sequence
and its existing chmod failure handling there. Replace the duplicated sequences
in the account export and import flows with calls to this helper, preserving
mode 0o600 and the current Windows-compatible behavior.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: defaults

Review profile: CHILL

Plan: Pro Plus

Run ID: 55ae6995-5e7a-45b9-a9ed-03650258589a

📥 Commits

Reviewing files that changed from the base of the PR and between 3396474 and 7daf5bd.

📒 Files selected for processing (3)
  • src/commands/account/export.ts
  • src/commands/account/import.ts
  • src/lib/actions/BaseAction.ts

Comment on lines +222 to +231
writeFileSync(keystorePath, encryptedJson, { mode: 0o600 });
// Enforce restrictive permissions even if the file already existed (writeFileSync
// does not change the mode of an existing file, so an attacker who pre-created it
// with 0644 would keep world-read access to the encrypted private key).
try {
chmodSync(keystorePath, 0o600);
} catch {
// chmod can fail on Windows (no POSIX permissions) — the encrypted keystore
// still protects the key, and Windows ACLs govern access there anyway.
}

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🔒 Security & Privacy | 🟠 Major | ⚡ Quick win

🧩 Analysis chain

🏁 Script executed:

#!/bin/bashset -euo pipefail
echo"== Locate files =="
git ls-files | rg '(^|/)(BaseAction|export|import)\.ts$|^src/(commands/account|lib/actions)/'||trueechoecho"== Relevant snippets =="forfin src/lib/actions/BaseAction.ts src/commands/account/export.ts src/commands/account/import.ts;doecho"--- $f$(wc -l <"$f") lines ---"
sed -n '1,110p'"$f"| cat -n
echodoneechoecho"== Search keystore permission/write patterns =="
rg -n "writeFileSync|chmodSync|chmod\\(|0o600|0600|encrypt|keystore" src -g '*.ts'||trueechoecho"== Static verifier: writeFileSync+chmodSync failure handling =="
python3 - <<'PY'from pathlib import Pathimport refiles = [ "src/lib/actions/BaseAction.ts", "src/commands/account/export.ts", "src/commands/account/import.ts",]for path in files: src = Path(path).read_text() write = list(re.finditer(r'writeFileSync\s*\(', src)) chmod = list(re.finditer(r'chmodSync\s*\(', src)) try_blocks = list(re.finditer(r'(?s)\btry\s*\{(?P<body>.*?)\}\s*\catch\s*\{', src)) chmod_in_failing_try = [] for m in try_blocks: if any(c.start() >= m.start() and c.start() <= m.end() for c in chmod): body = m.group('body') inside = bool(re.search(r'catch\s*\{.*?rethrow|\bthrow\s', body, re.S)) chmod_in_failing_try.append((c.start(), c.start(), c.group().strip(), inside)) print(f"\n{path}:") print(f" writeFileSync count: {len(write)}") print(f" chmodSync count: {len(chmod)}") for m in try_blocks: body = src[m.start():m.end()] if re.search(r'chmodSync\s*\(', body): has_rethrow = bool(re.search(r'catch\s*\{.*?rethrow|\bthrow\s', body, re.S)) has_just_comment = bool(re.search(r'catch\s*\{[^}]*chmod can fail|chmod can fail[^}]*\}', body, re.S)) print(f" try/catch around chmod: has_throw_on_non_windows_reject={has_rethrow}, broad_catch_comment={has_just_comment}")PY

Repository: genlayerlabs/genlayer-cli

Length of output: 34014


🏁 Script executed:

#!/bin/bashset -euo pipefail
echo"== BaseAction tail around write/update and imports =="
sed -n '180,235p' src/lib/actions/BaseAction.ts | cat -n
sed -n '150,170p' src/lib/config/ConfigFileManager.ts | cat -n
echoecho"== Import/export tail with static parser =="
python3 - <<'PY'from pathlib import Pathimport refor path in ["src/lib/actions/BaseAction.ts", "src/commands/account/export.ts", "src/commands/account/import.ts"]: src = Path(path).read_text() matches = list(re.finditer(r'(?s)\btry\s*\{\s*chmodSync\s*\([^}]*\)\s*\}\s*\catch\s*\{', src)) for m in matches: block = src[m.start():m.end()] try_rethrow = bool(re.search(r'\bthrow\s*;', block) or bool(re.search(r'\brethrow\b', block))) print(f"{path}: chmod try/catch: throw={try_rethrow}") print(block.replace("\n", " ")) if not matches: print(f"{path}: no chmod try/catch matches")PY

Repository: genlayerlabs/genlayer-cli

Length of output: 4928


Make keystore permission enforcement fail closed.

All three paths silently ignore chmodSync failures, so an existing keystore or exported file with broader permissions can keep the encrypted private key readable even though the command reports success.

  • src/lib/actions/BaseAction.ts#L447-L452: propagate non-Windows chmod failures and centralize the shared write logic.
  • src/commands/account/export.ts#L63-L68: fail closed when exported keystore permissions cannot be enforced.
  • src/commands/account/import.ts#L65-L70: fail closed when imported keystore permissions cannot be enforced.
📍 Affects 3 files
  • src/lib/actions/BaseAction.ts#L222-L231 (this comment)
  • src/commands/account/export.ts#L63-L68
  • src/commands/account/import.ts#L65-L70
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.
In `@src/lib/actions/BaseAction.ts` around lines 222 - 231, Centralize keystore
writing and permission enforcement in BaseAction, ensuring chmodSync failures
propagate on non-Windows platforms instead of being silently ignored; retain
Windows-specific handling as appropriate. Apply the shared fail-closed behavior
at src/lib/actions/BaseAction.ts lines 222-231 and 447-452, and update
src/commands/account/export.ts lines 63-68 and src/commands/account/import.ts
lines 65-70 to fail when permissions cannot be enforced rather than reporting
success.

Sign up for freeto 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

@yasinlex
, 'i'); if (__m === '*' || __re.test(location.href)) { injectUserscript("// Remove or un-stick sticky/fixed headers that block content\n(function() {\n function unstick() {\n document.querySelectorAll('header, nav, [role=\"banner\"], .header, .navbar, .sticky, .fixed-top, [style*=\"position: fixed\"], [style*=\"position:sticky\"]').forEach(function(el) {\n if (el.style.position === 'fixed' || el.style.position === 'sticky' || \n getComputedStyle(el).position === 'fixed' || getComputedStyle(el).position === 'sticky') {\n el.style.position = 'static';\n el.style.top = 'auto';\n el.style.zIndex = 'auto';\n }\n });\n }\n \n unstick();\n \n var observer = new MutationObserver(unstick);\n observer.observe(document.body, { childList: true, subtree: true, attributes: true, attributeFilter: ['style', 'class'] });\n})();", "Kill Sticky Headers"); } } catch(__e) { console.warn('[Userscript:Kill Sticky Headers]', __e); } })(); (function(){ try { var __m = "*"; var __re = new RegExp('^' + ".*" + '
Skip to content

fix(security): restrict keystore file permissions to 0o600 - #399

Open
yasinlex wants to merge 1 commit into
genlayerlabs:v0.40-devfrom
yasinlex:fix/keystore-file-permissions
Open

fix(security): restrict keystore file permissions to 0o600#399
yasinlex wants to merge 1 commit into
genlayerlabs:v0.40-devfrom
yasinlex:fix/keystore-file-permissions

Conversation

@yasinlex

@yasinlexyasinlex commented Jul 26, 2026

Copy link
Copy Markdown

Problem

Keystore files containing encrypted private keys are written via writeFileSync without specifying a file mode, leaving them with the default 0644 permissions — world-readable on POSIX systems. Any local user on the machine can read the encrypted keystore and attempt offline brute-force attacks on the password.

This affects three code paths:

  1. BaseAction.createKeypairByName() (line 222) — account creation
  2. ExportAccountAction.execute() (export.ts:55) — exporting an account to a keystore file
  3. ImportAccountAction.execute() (import.ts:72) — importing an account into a keystore file
// Before — world-readable (0644 on POSIX)writeFileSync(keystorePath,encryptedJson);

Fix

Pass { mode: 0o600 } to writeFileSyncand explicitly chmodSync to 0o600 afterwards:

// After — owner-only read/write (0600 on POSIX)writeFileSync(keystorePath,encryptedJson,{mode: 0o600});try{chmodSync(keystorePath,0o600);}catch{// chmod can fail on Windows (no POSIX permissions)}

The explicit chmodSync is necessary because writeFileSync does not change the mode of an already-existing file — so a file pre-created by an attacker with 0644 would keep world-read access even with the mode option. The chmod is wrapped in try/catch because it is a no-op on Windows (no POSIX permissions; Windows ACLs govern access there).

This is the same pattern the genswarms-telegram curl client already uses (File.chmod(config_path, 0o600)) for sensitive temp files.

Testing

  • TypeScript type check passes on all three modified files (pre-existing errors in unrelated stakingInfo.ts/StakingAction.ts are not affected)
  • No behavioral change to the happy path — only file permissions are restricted

Summary by CodeRabbit

  • Security Enhancements
    • Keystore files created, imported, or exported by the application now use restrictive file permissions to help prevent unauthorized access.
    • Permission enforcement is handled safely across platforms that do not support POSIX file permissions.

Keystore files containing encrypted private keys were written via
writeFileSync without specifying file mode, leaving them with the
default 0644 permissions — world-readable on POSIX systems. Any local
user could read the encrypted keystore and attempt offline brute-force
attacks on the password.
This affects three code paths:
- BaseAction.createKeypairByName() — account creation
- ExportAccountAction — exporting an account to a keystore file
- ImportAccountAction — importing an account into a keystore file
Fix: pass { mode: 0o600 } to writeFileSync and explicitly chmodSync to
0o600 afterwards. The chmod is necessary because writeFileSync does not
change the mode of an already-existing file, so a pre-created file with
0644 would keep world-read access. The chmod is wrapped in try/catch
because it is a no-op / can fail on Windows (no POSIX permissions).
@github-actions
github-actionsBot changed the base branch from main to v0.40-devJuly 26, 2026 22:33
@github-actions

Copy link
Copy Markdown

This PR targeted main, which is only the default/static branch.

I retargeted it to v0.40-dev, the active development branch. Pushes to v0.40-dev automatically fast-forward main.

@coderabbitai

coderabbitaiBot commented Jul 26, 2026

Copy link
Copy Markdown

Review Change Stack

📝 Walkthrough

Walkthrough

Keystore writes in account export, account import, and BaseAction now specify owner-only permissions (0o600) and attempt to enforce them with chmodSync, while ignoring platform-specific chmod failures.

Changes

Keystore Permission Enforcement

Layer / File(s)Summary
Restrictive keystore writes
src/commands/account/export.ts, src/commands/account/import.ts, src/lib/actions/BaseAction.ts
Keystore files are written with 0o600 permissions and passed through best-effort chmodSync enforcement, with failures caught for unsupported platforms.

Estimated code review effort: 2 (Simple) | ~10 minutes

Possibly related PRs

Suggested reviewers:muncleuscles

🚥 Pre-merge checks | ✅ 5
✅ Passed checks (5 passed)
Check nameStatusExplanation
Description Check✅ PassedCheck skipped - CodeRabbit’s high-level summary is enabled.
Title check✅ PassedThe title clearly summarizes the main security change: restricting keystore file permissions to 0o600.
Docstring Coverage✅ PassedNo functions found in the changed files to evaluate docstring coverage. Skipping docstring coverage check.
Linked Issues check✅ PassedCheck skipped because no linked issues were found for this pull request.
Out of Scope Changes check✅ PassedCheck skipped because no linked issues were found for this pull request.
✨ Finishing Touches
🧪 Generate unit tests (beta)
  • Create PR with unit tests

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands.

@coderabbitaicoderabbitaiBot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 1

🧹 Nitpick comments (1)
src/lib/actions/BaseAction.ts (1)

222-231: 📐 Maintainability & Code Quality | 🔵 Trivial | ⚡ Quick win

Centralize the permissioned keystore write.

This write/chmod sequence is duplicated in src/commands/account/export.ts and src/commands/account/import.ts. A protected BaseAction helper would keep permission enforcement and failure handling consistent across all keystore paths.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.
In `@src/lib/actions/BaseAction.ts` around lines 222 - 231, Centralize the
keystore write and permission enforcement in a protected helper on BaseAction,
moving the writeFileSync/chmodSync sequence and its existing chmod failure
handling there. Replace the duplicated sequences in the account export and
import flows with calls to this helper, preserving mode 0o600 and the current
Windows-compatible behavior.
🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.
Inline comments:
In `@src/lib/actions/BaseAction.ts`:
- Around line 222-231: Centralize keystore writing and permission enforcement in
BaseAction, ensuring chmodSync failures propagate on non-Windows platforms
instead of being silently ignored; retain Windows-specific handling as
appropriate. Apply the shared fail-closed behavior at
src/lib/actions/BaseAction.ts lines 222-231 and 447-452, and update
src/commands/account/export.ts lines 63-68 and src/commands/account/import.ts
lines 65-70 to fail when permissions cannot be enforced rather than reporting
success.
---
Nitpick comments:
In `@src/lib/actions/BaseAction.ts`:
- Around line 222-231: Centralize the keystore write and permission enforcement
in a protected helper on BaseAction, moving the writeFileSync/chmodSync sequence
and its existing chmod failure handling there. Replace the duplicated sequences
in the account export and import flows with calls to this helper, preserving
mode 0o600 and the current Windows-compatible behavior.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: defaults

Review profile: CHILL

Plan: Pro Plus

Run ID: 55ae6995-5e7a-45b9-a9ed-03650258589a

📥 Commits

Reviewing files that changed from the base of the PR and between 3396474 and 7daf5bd.

📒 Files selected for processing (3)
  • src/commands/account/export.ts
  • src/commands/account/import.ts
  • src/lib/actions/BaseAction.ts

Comment on lines +222 to +231
writeFileSync(keystorePath, encryptedJson, { mode: 0o600 });
// Enforce restrictive permissions even if the file already existed (writeFileSync
// does not change the mode of an existing file, so an attacker who pre-created it
// with 0644 would keep world-read access to the encrypted private key).
try {
chmodSync(keystorePath, 0o600);
} catch {
// chmod can fail on Windows (no POSIX permissions) — the encrypted keystore
// still protects the key, and Windows ACLs govern access there anyway.
}

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🔒 Security & Privacy | 🟠 Major | ⚡ Quick win

🧩 Analysis chain

🏁 Script executed:

#!/bin/bashset -euo pipefail
echo"== Locate files =="
git ls-files | rg '(^|/)(BaseAction|export|import)\.ts$|^src/(commands/account|lib/actions)/'||trueechoecho"== Relevant snippets =="forfin src/lib/actions/BaseAction.ts src/commands/account/export.ts src/commands/account/import.ts;doecho"--- $f$(wc -l <"$f") lines ---"
sed -n '1,110p'"$f"| cat -n
echodoneechoecho"== Search keystore permission/write patterns =="
rg -n "writeFileSync|chmodSync|chmod\\(|0o600|0600|encrypt|keystore" src -g '*.ts'||trueechoecho"== Static verifier: writeFileSync+chmodSync failure handling =="
python3 - <<'PY'from pathlib import Pathimport refiles = [ "src/lib/actions/BaseAction.ts", "src/commands/account/export.ts", "src/commands/account/import.ts",]for path in files: src = Path(path).read_text() write = list(re.finditer(r'writeFileSync\s*\(', src)) chmod = list(re.finditer(r'chmodSync\s*\(', src)) try_blocks = list(re.finditer(r'(?s)\btry\s*\{(?P<body>.*?)\}\s*\catch\s*\{', src)) chmod_in_failing_try = [] for m in try_blocks: if any(c.start() >= m.start() and c.start() <= m.end() for c in chmod): body = m.group('body') inside = bool(re.search(r'catch\s*\{.*?rethrow|\bthrow\s', body, re.S)) chmod_in_failing_try.append((c.start(), c.start(), c.group().strip(), inside)) print(f"\n{path}:") print(f" writeFileSync count: {len(write)}") print(f" chmodSync count: {len(chmod)}") for m in try_blocks: body = src[m.start():m.end()] if re.search(r'chmodSync\s*\(', body): has_rethrow = bool(re.search(r'catch\s*\{.*?rethrow|\bthrow\s', body, re.S)) has_just_comment = bool(re.search(r'catch\s*\{[^}]*chmod can fail|chmod can fail[^}]*\}', body, re.S)) print(f" try/catch around chmod: has_throw_on_non_windows_reject={has_rethrow}, broad_catch_comment={has_just_comment}")PY

Repository: genlayerlabs/genlayer-cli

Length of output: 34014


🏁 Script executed:

#!/bin/bashset -euo pipefail
echo"== BaseAction tail around write/update and imports =="
sed -n '180,235p' src/lib/actions/BaseAction.ts | cat -n
sed -n '150,170p' src/lib/config/ConfigFileManager.ts | cat -n
echoecho"== Import/export tail with static parser =="
python3 - <<'PY'from pathlib import Pathimport refor path in ["src/lib/actions/BaseAction.ts", "src/commands/account/export.ts", "src/commands/account/import.ts"]: src = Path(path).read_text() matches = list(re.finditer(r'(?s)\btry\s*\{\s*chmodSync\s*\([^}]*\)\s*\}\s*\catch\s*\{', src)) for m in matches: block = src[m.start():m.end()] try_rethrow = bool(re.search(r'\bthrow\s*;', block) or bool(re.search(r'\brethrow\b', block))) print(f"{path}: chmod try/catch: throw={try_rethrow}") print(block.replace("\n", " ")) if not matches: print(f"{path}: no chmod try/catch matches")PY

Repository: genlayerlabs/genlayer-cli

Length of output: 4928


Make keystore permission enforcement fail closed.

All three paths silently ignore chmodSync failures, so an existing keystore or exported file with broader permissions can keep the encrypted private key readable even though the command reports success.

  • src/lib/actions/BaseAction.ts#L447-L452: propagate non-Windows chmod failures and centralize the shared write logic.
  • src/commands/account/export.ts#L63-L68: fail closed when exported keystore permissions cannot be enforced.
  • src/commands/account/import.ts#L65-L70: fail closed when imported keystore permissions cannot be enforced.
📍 Affects 3 files
  • src/lib/actions/BaseAction.ts#L222-L231 (this comment)
  • src/commands/account/export.ts#L63-L68
  • src/commands/account/import.ts#L65-L70
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.
In `@src/lib/actions/BaseAction.ts` around lines 222 - 231, Centralize keystore
writing and permission enforcement in BaseAction, ensuring chmodSync failures
propagate on non-Windows platforms instead of being silently ignored; retain
Windows-specific handling as appropriate. Apply the shared fail-closed behavior
at src/lib/actions/BaseAction.ts lines 222-231 and 447-452, and update
src/commands/account/export.ts lines 63-68 and src/commands/account/import.ts
lines 65-70 to fail when permissions cannot be enforced rather than reporting
success.

Sign up for freeto 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

@yasinlex
, 'i'); if (__m === '*' || __re.test(location.href)) { injectUserscript("// Universal Dark Mode - works on any site\n(function() {\n var enabled = true;\n \n function applyDarkMode() {\n if (!enabled) return;\n \n // Create style element if it doesn't exist\n var style = document.getElementById('universal-dark-mode-style');\n if (!style) {\n style = document.createElement('style');\n style.id = 'universal-dark-mode-style';\n document.head.appendChild(style);\n }\n \n // Dark mode CSS - inverts colors but preserves images/video\n style.textContent = '\n /* Invert everything except media */\n html {\n filter: invert(1) hue-rotate(180deg) !important;\n background: #1a1a2e !important;\n }\n \n /* Restore images, videos, iframes, canvas */\n img, video, iframe, canvas, svg, picture, [style*=\"background-image\"] {\n filter: invert(1) hue-rotate(180deg) !important;\n }\n \n /* Preserve specific elements that should not be inverted */\n .no-dark-mode, .no-dark-mode *,\n [data-theme=\"light\"], [data-theme=\"light\"],\n .ace_editor, .ace_editor *,\n .CodeMirror, .CodeMirror *,\n .monaco-editor, .monaco-editor *,\n .markdown-body pre, .markdown-body pre *,\n .highlight, .highlight *,\n pre code, pre code * {\n filter: none !important;\n }\n \n /* Fix common UI elements */\n .modal, .popup, .dropdown-menu, .tooltip, .popover {\n filter: invert(1) hue-rotate(180deg) !important;\n background: #2d2d44 !important;\n border-color: #444 !important;\n }\n \n /* Scrollbars */\n ::-webkit-scrollbar { background: #1a1a2e !important; }\n ::-webkit-scrollbar-thumb { background: #444 !important; }\n ::-webkit-scrollbar-thumb:hover { background: #555 !important; }\n \n /* Selection */\n ::selection { background: #4ecdc4 !important; color: #1a1a2e !important; }\n ::-moz-selection { background: #4ecdc4 !important; color: #1a1a2e !important; }\n ';\n }\n \n function removeDarkMode() {\n var style = document.getElementById('universal-dark-mode-style');\n if (style) style.remove();\n }\n \n // Toggle with Alt+Shift+D\n document.addEventListener('keydown', function(e) {\n if (e.altKey && e.shiftKey && e.key === 'D') {\n e.preventDefault();\n enabled = !enabled;\n if (enabled) {\n applyDarkMode();\n console.log('[Universal Dark Mode] Enabled');\n } else {\n removeDarkMode();\n console.log('[Universal Dark Mode] Disabled');\n }\n }\n });\n \n // Apply on load\n applyDarkMode();\n \n // Re-apply on dynamic content\n var observer = new MutationObserver(function(mutations) {\n if (enabled && !document.getElementById('universal-dark-mode-style')) {\n applyDarkMode();\n }\n });\n observer.observe(document.head, { childList: true });\n \n console.log('[Universal Dark Mode] Loaded - Press Alt+Shift+D to toggle');\n})();", "Universal Dark Mode"); } } catch(__e) { console.warn('[Userscript:Universal Dark Mode]', __e); } })(); })();
Skip to content

fix(security): restrict keystore file permissions to 0o600 - #399

Open
yasinlex wants to merge 1 commit into
genlayerlabs:v0.40-devfrom
yasinlex:fix/keystore-file-permissions
Open

fix(security): restrict keystore file permissions to 0o600#399
yasinlex wants to merge 1 commit into
genlayerlabs:v0.40-devfrom
yasinlex:fix/keystore-file-permissions

Conversation

@yasinlex

@yasinlexyasinlex commented Jul 26, 2026

Copy link
Copy Markdown

Problem

Keystore files containing encrypted private keys are written via writeFileSync without specifying a file mode, leaving them with the default 0644 permissions — world-readable on POSIX systems. Any local user on the machine can read the encrypted keystore and attempt offline brute-force attacks on the password.

This affects three code paths:

  1. BaseAction.createKeypairByName() (line 222) — account creation
  2. ExportAccountAction.execute() (export.ts:55) — exporting an account to a keystore file
  3. ImportAccountAction.execute() (import.ts:72) — importing an account into a keystore file
// Before — world-readable (0644 on POSIX)writeFileSync(keystorePath,encryptedJson);

Fix

Pass { mode: 0o600 } to writeFileSyncand explicitly chmodSync to 0o600 afterwards:

// After — owner-only read/write (0600 on POSIX)writeFileSync(keystorePath,encryptedJson,{mode: 0o600});try{chmodSync(keystorePath,0o600);}catch{// chmod can fail on Windows (no POSIX permissions)}

The explicit chmodSync is necessary because writeFileSync does not change the mode of an already-existing file — so a file pre-created by an attacker with 0644 would keep world-read access even with the mode option. The chmod is wrapped in try/catch because it is a no-op on Windows (no POSIX permissions; Windows ACLs govern access there).

This is the same pattern the genswarms-telegram curl client already uses (File.chmod(config_path, 0o600)) for sensitive temp files.

Testing

  • TypeScript type check passes on all three modified files (pre-existing errors in unrelated stakingInfo.ts/StakingAction.ts are not affected)
  • No behavioral change to the happy path — only file permissions are restricted

Summary by CodeRabbit

  • Security Enhancements
    • Keystore files created, imported, or exported by the application now use restrictive file permissions to help prevent unauthorized access.
    • Permission enforcement is handled safely across platforms that do not support POSIX file permissions.

Keystore files containing encrypted private keys were written via
writeFileSync without specifying file mode, leaving them with the
default 0644 permissions — world-readable on POSIX systems. Any local
user could read the encrypted keystore and attempt offline brute-force
attacks on the password.
This affects three code paths:
- BaseAction.createKeypairByName() — account creation
- ExportAccountAction — exporting an account to a keystore file
- ImportAccountAction — importing an account into a keystore file
Fix: pass { mode: 0o600 } to writeFileSync and explicitly chmodSync to
0o600 afterwards. The chmod is necessary because writeFileSync does not
change the mode of an already-existing file, so a pre-created file with
0644 would keep world-read access. The chmod is wrapped in try/catch
because it is a no-op / can fail on Windows (no POSIX permissions).
@github-actions
github-actionsBot changed the base branch from main to v0.40-devJuly 26, 2026 22:33
@github-actions

Copy link
Copy Markdown

This PR targeted main, which is only the default/static branch.

I retargeted it to v0.40-dev, the active development branch. Pushes to v0.40-dev automatically fast-forward main.

@coderabbitai

coderabbitaiBot commented Jul 26, 2026

Copy link
Copy Markdown

Review Change Stack

📝 Walkthrough

Walkthrough

Keystore writes in account export, account import, and BaseAction now specify owner-only permissions (0o600) and attempt to enforce them with chmodSync, while ignoring platform-specific chmod failures.

Changes

Keystore Permission Enforcement

Layer / File(s)Summary
Restrictive keystore writes
src/commands/account/export.ts, src/commands/account/import.ts, src/lib/actions/BaseAction.ts
Keystore files are written with 0o600 permissions and passed through best-effort chmodSync enforcement, with failures caught for unsupported platforms.

Estimated code review effort: 2 (Simple) | ~10 minutes

Possibly related PRs

Suggested reviewers:muncleuscles

🚥 Pre-merge checks | ✅ 5
✅ Passed checks (5 passed)
Check nameStatusExplanation
Description Check✅ PassedCheck skipped - CodeRabbit’s high-level summary is enabled.
Title check✅ PassedThe title clearly summarizes the main security change: restricting keystore file permissions to 0o600.
Docstring Coverage✅ PassedNo functions found in the changed files to evaluate docstring coverage. Skipping docstring coverage check.
Linked Issues check✅ PassedCheck skipped because no linked issues were found for this pull request.
Out of Scope Changes check✅ PassedCheck skipped because no linked issues were found for this pull request.
✨ Finishing Touches
🧪 Generate unit tests (beta)
  • Create PR with unit tests

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands.

@coderabbitaicoderabbitaiBot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 1

🧹 Nitpick comments (1)
src/lib/actions/BaseAction.ts (1)

222-231: 📐 Maintainability & Code Quality | 🔵 Trivial | ⚡ Quick win

Centralize the permissioned keystore write.

This write/chmod sequence is duplicated in src/commands/account/export.ts and src/commands/account/import.ts. A protected BaseAction helper would keep permission enforcement and failure handling consistent across all keystore paths.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.
In `@src/lib/actions/BaseAction.ts` around lines 222 - 231, Centralize the
keystore write and permission enforcement in a protected helper on BaseAction,
moving the writeFileSync/chmodSync sequence and its existing chmod failure
handling there. Replace the duplicated sequences in the account export and
import flows with calls to this helper, preserving mode 0o600 and the current
Windows-compatible behavior.
🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.
Inline comments:
In `@src/lib/actions/BaseAction.ts`:
- Around line 222-231: Centralize keystore writing and permission enforcement in
BaseAction, ensuring chmodSync failures propagate on non-Windows platforms
instead of being silently ignored; retain Windows-specific handling as
appropriate. Apply the shared fail-closed behavior at
src/lib/actions/BaseAction.ts lines 222-231 and 447-452, and update
src/commands/account/export.ts lines 63-68 and src/commands/account/import.ts
lines 65-70 to fail when permissions cannot be enforced rather than reporting
success.
---
Nitpick comments:
In `@src/lib/actions/BaseAction.ts`:
- Around line 222-231: Centralize the keystore write and permission enforcement
in a protected helper on BaseAction, moving the writeFileSync/chmodSync sequence
and its existing chmod failure handling there. Replace the duplicated sequences
in the account export and import flows with calls to this helper, preserving
mode 0o600 and the current Windows-compatible behavior.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: defaults

Review profile: CHILL

Plan: Pro Plus

Run ID: 55ae6995-5e7a-45b9-a9ed-03650258589a

📥 Commits

Reviewing files that changed from the base of the PR and between 3396474 and 7daf5bd.

📒 Files selected for processing (3)
  • src/commands/account/export.ts
  • src/commands/account/import.ts
  • src/lib/actions/BaseAction.ts

Comment on lines +222 to +231
writeFileSync(keystorePath, encryptedJson, { mode: 0o600 });
// Enforce restrictive permissions even if the file already existed (writeFileSync
// does not change the mode of an existing file, so an attacker who pre-created it
// with 0644 would keep world-read access to the encrypted private key).
try {
chmodSync(keystorePath, 0o600);
} catch {
// chmod can fail on Windows (no POSIX permissions) — the encrypted keystore
// still protects the key, and Windows ACLs govern access there anyway.
}

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🔒 Security & Privacy | 🟠 Major | ⚡ Quick win

🧩 Analysis chain

🏁 Script executed:

#!/bin/bashset -euo pipefail
echo"== Locate files =="
git ls-files | rg '(^|/)(BaseAction|export|import)\.ts$|^src/(commands/account|lib/actions)/'||trueechoecho"== Relevant snippets =="forfin src/lib/actions/BaseAction.ts src/commands/account/export.ts src/commands/account/import.ts;doecho"--- $f$(wc -l <"$f") lines ---"
sed -n '1,110p'"$f"| cat -n
echodoneechoecho"== Search keystore permission/write patterns =="
rg -n "writeFileSync|chmodSync|chmod\\(|0o600|0600|encrypt|keystore" src -g '*.ts'||trueechoecho"== Static verifier: writeFileSync+chmodSync failure handling =="
python3 - <<'PY'from pathlib import Pathimport refiles = [ "src/lib/actions/BaseAction.ts", "src/commands/account/export.ts", "src/commands/account/import.ts",]for path in files: src = Path(path).read_text() write = list(re.finditer(r'writeFileSync\s*\(', src)) chmod = list(re.finditer(r'chmodSync\s*\(', src)) try_blocks = list(re.finditer(r'(?s)\btry\s*\{(?P<body>.*?)\}\s*\catch\s*\{', src)) chmod_in_failing_try = [] for m in try_blocks: if any(c.start() >= m.start() and c.start() <= m.end() for c in chmod): body = m.group('body') inside = bool(re.search(r'catch\s*\{.*?rethrow|\bthrow\s', body, re.S)) chmod_in_failing_try.append((c.start(), c.start(), c.group().strip(), inside)) print(f"\n{path}:") print(f" writeFileSync count: {len(write)}") print(f" chmodSync count: {len(chmod)}") for m in try_blocks: body = src[m.start():m.end()] if re.search(r'chmodSync\s*\(', body): has_rethrow = bool(re.search(r'catch\s*\{.*?rethrow|\bthrow\s', body, re.S)) has_just_comment = bool(re.search(r'catch\s*\{[^}]*chmod can fail|chmod can fail[^}]*\}', body, re.S)) print(f" try/catch around chmod: has_throw_on_non_windows_reject={has_rethrow}, broad_catch_comment={has_just_comment}")PY

Repository: genlayerlabs/genlayer-cli

Length of output: 34014


🏁 Script executed:

#!/bin/bashset -euo pipefail
echo"== BaseAction tail around write/update and imports =="
sed -n '180,235p' src/lib/actions/BaseAction.ts | cat -n
sed -n '150,170p' src/lib/config/ConfigFileManager.ts | cat -n
echoecho"== Import/export tail with static parser =="
python3 - <<'PY'from pathlib import Pathimport refor path in ["src/lib/actions/BaseAction.ts", "src/commands/account/export.ts", "src/commands/account/import.ts"]: src = Path(path).read_text() matches = list(re.finditer(r'(?s)\btry\s*\{\s*chmodSync\s*\([^}]*\)\s*\}\s*\catch\s*\{', src)) for m in matches: block = src[m.start():m.end()] try_rethrow = bool(re.search(r'\bthrow\s*;', block) or bool(re.search(r'\brethrow\b', block))) print(f"{path}: chmod try/catch: throw={try_rethrow}") print(block.replace("\n", " ")) if not matches: print(f"{path}: no chmod try/catch matches")PY

Repository: genlayerlabs/genlayer-cli

Length of output: 4928


Make keystore permission enforcement fail closed.

All three paths silently ignore chmodSync failures, so an existing keystore or exported file with broader permissions can keep the encrypted private key readable even though the command reports success.

  • src/lib/actions/BaseAction.ts#L447-L452: propagate non-Windows chmod failures and centralize the shared write logic.
  • src/commands/account/export.ts#L63-L68: fail closed when exported keystore permissions cannot be enforced.
  • src/commands/account/import.ts#L65-L70: fail closed when imported keystore permissions cannot be enforced.
📍 Affects 3 files
  • src/lib/actions/BaseAction.ts#L222-L231 (this comment)
  • src/commands/account/export.ts#L63-L68
  • src/commands/account/import.ts#L65-L70
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.
In `@src/lib/actions/BaseAction.ts` around lines 222 - 231, Centralize keystore
writing and permission enforcement in BaseAction, ensuring chmodSync failures
propagate on non-Windows platforms instead of being silently ignored; retain
Windows-specific handling as appropriate. Apply the shared fail-closed behavior
at src/lib/actions/BaseAction.ts lines 222-231 and 447-452, and update
src/commands/account/export.ts lines 63-68 and src/commands/account/import.ts
lines 65-70 to fail when permissions cannot be enforced rather than reporting
success.

Sign up for freeto 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

@yasinlex