Skip to content

fix: set file permissions on AWS credentials, remove sensitive data from SSH logs#1873

Closed
jlima8900 wants to merge 2 commits intoKeeper-Security:releasefrom
jlima8900:fix/security-audit-findings
Closed

fix: set file permissions on AWS credentials, remove sensitive data from SSH logs#1873
jlima8900 wants to merge 2 commits intoKeeper-Security:releasefrom
jlima8900:fix/security-audit-findings

Conversation

@jlima8900
Copy link
Copy Markdown

Summary

Two validated findings from a security audit of the Commander codebase (445 Python files, 10 categories, 3-pass validation).

KC-2026-001: AWS credentials file permissions

The awskey plugin writes rotated AWS credentials without setting restrictive file permissions. If the credentials file is newly created (custom path), it inherits umask defaults (typically 0644), making the AWS secret access key readable by other users on multi-user systems.

File: keepercommander/plugins/awskey/aws_accesskey.py:138

Fix: Add os.chmod(creds_filename, 0o600) after writing credentials file and backup.

KC-2026-002: SSH plugin logs password command output

The SSH password rotation plugin logs raw terminal output from the passwd command at DEBUG level. This output can include echoed password characters, credential-related prompts, or error messages that reveal password context.

File: keepercommander/plugins/ssh/ssh.py:118,126,131,138

Fix: Replace raw output logging with response size only ('Rotation command responded (%d bytes)'). The actual credential values are no longer included in log statements.

Audit methodology

  • 130 initial candidates from static analysis
  • 17 after first validation (context check)
  • 5 after second validation (exploitability check)
  • 2 after third validation (data flow trace)
  • 98.5% false positive elimination rate

Test plan

  • AWS plugin: verify credentials file has 0600 permissions after rotation
  • AWS plugin: verify backup file also has 0600 permissions
  • SSH plugin: verify DEBUG logs no longer contain terminal output
  • SSH plugin: verify password rotation still works correctly
  • No regressions in existing plugin tests

@craiglurey craiglurey changed the base branch from master to release March 20, 2026 05:50
shutil.copy2(creds_filename, backup_file)
with open(creds_filename, 'w') as f:
cp.write(f)
os.chmod(creds_filename, 0o600)
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

One concern — race condition (TOCTOU): The file is opened, written, closed, and then chmod'd. There is a brief window where the file exists at 0644 (or whatever the process umask dictates) before permissions are restricted. On a paranoid multi-user system, the correct fix would be to either:

Set a restrictive umask before open() and restore it afterward,
or
Open the file descriptor with os.open(path, os.O_WRONLY | os.O_CREAT | os.O_TRUNC, 0o600) and wrap it with open(fd, 'w').

@jlima8900 jlima8900 force-pushed the fix/security-audit-findings branch from 8e71c18 to 02e8131 Compare March 23, 2026 15:00
@jlima8900
Copy link
Copy Markdown
Author

Good catch on the TOCTOU window. You're right — there's a brief moment where the file exists with default umask permissions before chmod is called.

I'll update to use os.open() with explicit mode to eliminate the race:

fd = os.open(path, os.O_WRONLY | os.O_CREAT | os.O_TRUNC, 0o600)
with open(fd, 'w') as f:
    f.write(content)

This way the file is created with 0600 from the start — no window for another process to read it.

…on narrowing

AWS credentials (aws_accesskey.py):
- Eliminate TOCTOU race: use os.open() with 0o600 mode instead of
  open() + chmod() so file is never world-readable
- Create backup with same atomic permission pattern
- Use os.fdopen + fchmod to harden existing files

SSH logs (ssh.py):
- Sanitize debug output: log byte counts instead of raw content
  to prevent credential leakage in log files

PAM base (base.py):
- Narrow all json.loads catches to json.JSONDecodeError only
  (drop unreachable ValueError, TypeError, KeyError)
- Add structured error details (e.msg, e.lineno, e.colno) to logs
- Split import_schedule_field handler into separate JSONDecodeError
  vs (ValueError, KeyError) blocks

Tests:
- 19 tests covering file permissions, log sanitization, exception
  narrowing, and TOCTOU atomicity
- Skip botocore-dependent tests when botocore not installed
@jlima8900 jlima8900 force-pushed the fix/security-audit-findings branch from 82d99f3 to f1cbf64 Compare March 27, 2026 16:39
…patterns

- SEC-1/2: Rewrite set_file_permissions and ensure_config_permissions to
  use O_NOFOLLOW + fstat + fchmod (fd-based) instead of islink + stat + chmod
  to prevent symlink swap between check and chmod
- SEC-4: Apply sanitize_debug_data() in debug_decorator so credentials
  passed as function args/return values are redacted in DEBUG logs
- SEC-7: Add Commander-specific secret patterns (private_pem_key,
  device_token, access_token, gateway_token, session_token, data_key)
- ERR-1: Narrow except Exception to (OSError, SubprocessError) in
  set_file_permissions
- BUG-2: Fix ensure_config_permissions to warn only when permissions
  are more permissive than 0o600 (was treating 0o400 as insecure)
@jlima8900 jlima8900 closed this Mar 28, 2026
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants