Skip to content
Merged

Release #1741

Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
20 commits
Select commit Hold shift + click to select a range
8962673
rename PEDM to EPM
sk-keeper Dec 22, 2025
950e646
Login if only email if typed at prompt
sk-keeper Dec 22, 2025
3e71631
Cron syntax parser does not support L[W] modifiers
sk-keeper Dec 22, 2025
52464b0
DR-1130, DR-1131 Rule Engine Changes
jwalstra-keeper Dec 18, 2025
788be7d
Added alias for domain management in service mode
pvagare-ks Dec 23, 2025
7550c87
KC-1063 Fix compliance report aging data returning null values
aaunario-keeper Dec 30, 2025
74c1588
DR-1153 Add support for configure_resource protobuf
jwalstra-keeper Dec 23, 2025
24e4a40
Corrected typo in CLI manual (#1735)
lthievenaz-keeper Dec 31, 2025
df3a2ea
Add support for setting reset-password during login flow
lthievenaz-keeper Dec 31, 2025
15abef3
Added type definitions in comment
lthievenaz-keeper Dec 31, 2025
ddd00e3
Update dockerfile with gcc package required for MLKEM C compilation
amangalampalli-ks Dec 23, 2025
cb7cdb2
Improve error message for security-alerts-detail event code
lthievenaz-keeper Dec 18, 2025
c7afc6f
Add SuperShell - Full-Screen TUI for Keeper Vault (#1737)
craiglurey Jan 2, 2026
137402b
KC-975 build pam connection capability in commander cli (#1722)
idimov-keeper Jan 2, 2026
b3fb45a
Revise README with installation and usage details
craiglurey Jan 3, 2026
1bd7b96
Improve CLI login flow, help system, and SuperShell UI polish
craiglurey Jan 3, 2026
25b38df
Improve passkey display and SuperShell JSON copy functionality
craiglurey Jan 4, 2026
dc00788
Fixed use of "unmask" in get command for general record types
craiglurey Jan 4, 2026
e382c15
timestamp fails on Windows if date is pre-1970
sk-keeper Jan 5, 2026
f7e659a
Release 17.2.2
sk-keeper Jan 5, 2026
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 3 additions & 1 deletion Dockerfile
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,9 @@ RUN apt-get update && \
apt-get upgrade -y && \
apt-get install -y --no-install-recommends \
openssl \
ca-certificates && \
ca-certificates \
gcc \
libc6-dev && \
pip install --no-cache-dir --upgrade pip setuptools wheel && \
apt-get autoremove -y && \
apt-get clean && \
Expand Down
61 changes: 53 additions & 8 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -3,16 +3,61 @@
![Keeper Commander](https://raw.githubusercontent.com/Keeper-Security/Commander/master/images/commander-black.png)

### About Keeper Commander
Keeper Commander is a command-line CLI and Python SDK interface to Keeper® Password Manager and KeeperPAM. Commander can be used to access and control your Keeper vault, perform administrative functions (managing users, teams, roles, SSO, privileged access resources, data import/export), launch sessions, rotate passwords, integrate with developer tools, eliminate hardcoded passwords, run as a REST service and more. Keeper Commander is an open source project with contributions from Keeper's engineering team and partners.
Keeper Commander is a command-line and terminal UI interface to Keeper® Password Manager and KeeperPAM. Commander can be used to access and control your Keeper vault, perform administrative actions (managing users, teams, roles, SSO, privileged access resources, device approvals, data import/export), launch sessions, rotate passwords, integrate with developer tools, eliminate hardcoded passwords, run as a REST service and more. Keeper Commander is an open source project with contributions from Keeper's engineering team, customers and partners.

### Documentation and Getting Started
- Keeper Commander documentation: [https://docs.keeper.io/en/keeperpam/commander-cli/overview](https://docs.keeper.io/en/keeperpam/commander-cli/overview)
### Windows and macOS Binaries
See the [Releases](https://github.com/Keeper-Security/Commander/releases)

### About Keeper Security
Keeper is the leading cybersecurity platform for preventing password-related data breaches and cyberthreats. KeeperPAM is the leading zero-trust privileged access management ("PAM") platform for securing and managing access to your critical infrastructure.
### Linux / Python using PIP
```
python3 -m venv keeper-env
source keeper-env/bin/activate
pip install keepercommander
```

### Running from Source
```
git clone https://github.com/Keeper-Security/Commander
cd Commander
python3 -m venv venv
source venv/bin/activate
pip install -r requirements.txt
pip install -e .
pip install -e '.[email]'
```

### Starting Commander
For a list of all available commands:
```
keeper help
```

To launch the interactive command shell:

- Learn More about Keeper: [https://keepersecurity.com](https://keepersecurity.com)
```
keeper shell
```

- Encryption and Security Model: [https://docs.keeper.io/en/enterprise-guide/keeper-encryption-model](https://docs.keeper.io/en/enterprise-guide/keeper-encryption-model)
or for a full terminal vault user interface
```
keeper supershell
```

Once logged in, check out the `this-device` command to set up persistent login sessions, logout timer and 2FA frequency. Also check out the `biometric register` command to enable biometric authentication on supported platforms.

### Documentation
- [Commander Documentation Home](https://docs.keeper.io/en/keeperpam/commander-cli/overview)
- [Installation](https://docs.keeper.io/en/keeperpam/commander-cli/commander-installation-setup)
- [Full Command Reference](https://docs.keeper.io/en/keeperpam/commander-cli/command-reference)
- [Service Mode REST API](https://docs.keeper.io/en/keeperpam/commander-cli/service-mode-rest-api)
- [Commander SDK](https://docs.keeper.io/en/keeperpam/commander-sdk/keeper-commander-sdks)
- [All Keeper Documentation](https://docs.keeper.io/)

### About Keeper Security
Keeper Security is the creator of KeeperPAM - the zero-trust and zero-knowledge privileged access management ("PAM") platform for securing and managing access to your critical infrastructure.
- [Keeper Security Homepage](https://keepersecurity.com)
- [Privileged Access Management](https://www.keepersecurity.com/privileged-access-management/)
- [Endpoint Privilege Manager](https://www.keepersecurity.com/endpoint-privilege-management/)
- [Encryption and Security Model](https://docs.keeper.io/en/enterprise-guide/keeper-encryption-model)
- [Downloads](https://www.keepersecurity.com/download.html?t=d)

- Documentation Home: [https://docs.keeper.io/](https://docs.keeper.io/)
2 changes: 1 addition & 1 deletion keepercommander/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -10,4 +10,4 @@
# Contact: ops@keepersecurity.com
#

__version__ = '17.2.1'
__version__ = '17.2.2'
119 changes: 108 additions & 11 deletions keepercommander/__main__.py
Original file line number Diff line number Diff line change
Expand Up @@ -29,6 +29,7 @@
from . import cli, utils
from .params import KeeperParams
from .config_storage import loader
from .constants import resolve_server, KEEPER_SERVERS


def get_params_from_config(config_filename=None, launched_with_shortcut=False, data_dir=None): # type: (Optional[str], bool, Optional[str]) -> KeeperParams
Expand Down Expand Up @@ -96,12 +97,54 @@ def get_env_config():


def usage(m):
"""Show full help with all commands - used for 'keeper help' or 'keeper ?'"""
print(m)
parser.print_help()
cli.display_command_help(show_enterprise=True, show_shell=True, show_legacy=True)
cli.display_command_help(show_enterprise=True, show_shell=True, show_legacy=False)
sys.exit(1)


def show_brief_help():
"""Show brief help for 'keeper -h' - just global options and guidance"""
print('')
print('Keeper Commander - CLI-based vault and admin interface to the Keeper platform')
print('')
print('Usage: keeper [OPTIONS] [COMMAND] [COMMAND_OPTIONS]')
print('')
print('Global Options:')
print(' --server, -ks SERVER Keeper region or host')
print(' Regions: US, EU, AU, CA, JP, GOV')
print(' Dev/QA: US_DEV, EU_DEV, GOV_QA, etc.')
print(' --user, -ku USER Email address for the account')
print(' --password, -kp PASSWORD Master password for the account')
print(' --config CONFIG Config file to use')
print(' --debug Turn on debug mode')
print(' --batch-mode Run in batch/non-interactive mode')
print(' --proxy PROXY Proxy server')
print(' --new-login Force full login (bypass persistent login)')
print(' --version Display version')
print('')
print('Getting Started:')
print(' keeper shell Open interactive command shell')
print(' keeper supershell Open full-screen vault browser (TUI)')
print(' keeper login Login to your Keeper account')
print('')
print('Getting Help:')
print(' keeper help Show hundreds of available commands')
print(' keeper help <command> Show help for a specific command')
print(' keeper <command> -h Show help for a specific command')
print('')
print('Examples:')
print(' keeper shell # Start interactive shell')
print(' keeper --server EU login # Login to EU region')
print(' keeper login -h # Show login command help')
print(' keeper search "github" --format=json # Search and output JSON')
print('')
print('User Guide: https://docs.keeper.io/en/keeperpam/commander-cli')
print('')
sys.exit(0)


parser = argparse.ArgumentParser(prog='keeper', add_help=False, allow_abbrev=False)
parser.add_argument('--server', '-ks', dest='server', action='store', help='Keeper Host address.')
parser.add_argument('--user', '-ku', dest='user', action='store', help='Email address for the account.')
Expand All @@ -120,6 +163,7 @@ def usage(m):
'server-side throttling'
parser.add_argument('--fail-on-throttle', action='store_true', help=fail_on_throttle_help)
parser.add_argument('--data-dir', dest='data_dir', action='store', help='Directory to use for Commander data (config, cache, etc.). Overrides environment variables.')
parser.add_argument('--new-login', dest='new_login', action='store_true', help='Force full login flow (bypass persistent login)')
parser.add_argument('command', nargs='?', type=str, action='store', help='Command')
parser.add_argument('options', nargs='*', action='store', help='Options')
parser.error = usage
Expand Down Expand Up @@ -286,7 +330,19 @@ def main(from_package=False):
params.proxy = opts.proxy

if opts.server:
params.server = opts.server
resolved_server = resolve_server(opts.server)
if resolved_server:
params.server = resolved_server
else:
# Show error and valid options
print(f"\nError: '{opts.server}' is not a valid Keeper server.")
print('\nValid server codes:')
print(' Production: US, EU, AU, CA, JP, GOV')
print(' Dev: US_DEV, EU_DEV, AU_DEV, CA_DEV, JP_DEV, GOV_DEV')
print(' QA: US_QA, EU_QA, AU_QA, CA_QA, JP_QA, GOV_QA')
print('\nYou can also use the full hostname (e.g., keepersecurity.com, keepersecurity.eu)')
print('')
sys.exit(1)

if opts.user is not None:
params.user = opts.user
Expand All @@ -308,24 +364,62 @@ def main(from_package=False):
print(f'Keeper Commander, version {__version__}')
return

if flags and len(flags) > 0:
if flags[0] in ('-h', '--help'):
flags.clear()
opts.command = '?'
elif opts.command == 'help' and len(opts.options) == 0:
opts.command = '?'
if (opts.command or '') == '?':
# Handle help flags and commands
has_help_flag = flags and len(flags) > 0 and flags[0] in ('-h', '--help')

if has_help_flag:
if not opts.command:
# 'keeper -h' with no command → show brief help
show_brief_help()
else:
# 'keeper <command> -h' → pass -h to the command (keep it in original_args)
# The -h is already in original_args_after_command, so just continue
pass

# Handle 'keeper help' and 'keeper help <command>'
if opts.command == 'help':
if len(opts.options) == 0:
# 'keeper help' with no args → show full command list
usage('')
else:
# 'keeper help <command>' → convert to '<command> --help'
opts.command = opts.options[0]
original_args_after_command = ['--help']

# Handle 'keeper ?'
if opts.command == '?':
usage('')

if not opts.command and from_package:
opts.command = 'shell'

# If no command provided, show helpful welcome message
if not opts.command and not params.commands:
print('')
print('Keeper Commander - CLI-based vault and admin interface to the Keeper platform')
print('')
print('To get started:')
print(' keeper shell Open interactive command shell')
print(' keeper supershell Open full-screen vault browser (TUI)')
print(' keeper -h Show help and available options')
print('')
print('Learn more at https://docs.keeper.io/en/keeperpam/commander-cli/overview')
print('')
return

if isinstance(params.timedelay, int) and params.timedelay >= 1 and params.commands:
cli.runcommands(params)
else:
if opts.command in {'shell', 'login', '-'}:
# Check if -h/--help is in the arguments for a command
command_wants_help = any(arg in ('-h', '--help') for arg in original_args_after_command)

if opts.command in {'shell', '-'} and not command_wants_help:
# Special handling for shell/- when NOT asking for help
if opts.command == '-':
params.batch_mode = True
elif opts.command == 'login' and not original_args_after_command and not command_wants_help:
# 'keeper login' with no args - just open shell and let it handle login
pass
elif opts.command and os.path.isfile(opts.command):
with open(opts.command, 'r') as f:
lines = f.readlines()
Expand All @@ -336,12 +430,15 @@ def main(from_package=False):
if opts.command:
# Use the filtered original argument order to preserve proper flag/value pairing
options = ' '.join([shlex.quote(x) for x in original_args_after_command]) if original_args_after_command else ''
# Inject --new-login into login command if main parser captured it
if opts.command == 'login' and opts.new_login:
options = '--new-login ' + options if options else '--new-login'
command = ' '.join([opts.command or '', options]).strip()
params.commands.append(command)
params.commands.append('q')
params.batch_mode = True

errno = cli.loop(params)
errno = cli.loop(params, new_login=opts.new_login)

sys.exit(errno)

Expand Down
3 changes: 2 additions & 1 deletion keepercommander/api.py
Original file line number Diff line number Diff line change
Expand Up @@ -69,7 +69,8 @@
def login(params, new_login=False, login_ui=None):
# type: (KeeperParams, bool, Optional[Any]) -> None

logging.info('Logging in to Keeper Commander')
logging.info(f'Logging in to Keeper as {params.user}')

flow = loginv3.LoginV3Flow(login_ui)
try:
flow.login(params, new_login=new_login)
Expand Down
82 changes: 56 additions & 26 deletions keepercommander/auth/console_ui.py
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@
import webbrowser
from typing import Optional, List

from colorama import Fore, Style
from . import login_steps
from .. import utils
from ..display import bcolors
Expand All @@ -23,48 +24,77 @@ def __init__(self):

def on_device_approval(self, step):
if self._show_device_approval_help:
print("\nDevice Approval Required\n")

print("Approve by selecting a method below:")
print("\t\"" + bcolors.OKGREEN + "email_send" + bcolors.ENDC + "\" to send email")
print("\t\"" + bcolors.OKGREEN + "email_code=<code>" + bcolors.ENDC + "\" to validate verification code sent via email")
print("\t\"" + bcolors.OKGREEN + "keeper_push" + bcolors.ENDC + "\" to send Keeper Push notification")
print("\t\"" + bcolors.OKGREEN + "2fa_send" + bcolors.ENDC + "\" to send 2FA code")
print("\t\"" + bcolors.OKGREEN + "2fa_code=<code>" + bcolors.ENDC + "\" to validate a code provided by 2FA application")
print("\t\"" + bcolors.OKGREEN + "<Enter>" + bcolors.ENDC + "\" to resume")

print(f"\n{Style.BRIGHT}Device Approval Required{Style.RESET_ALL}\n")
print("Select an approval method:")
print(f" {Fore.GREEN}1{Fore.RESET}. Email - Send approval link to your email")
print(f" {Fore.GREEN}2{Fore.RESET}. Keeper Push - Send notification to an approved device")
print(f" {Fore.GREEN}3{Fore.RESET}. 2FA Push - Send code via your 2FA method")
print()
print(f" {Fore.GREEN}c{Fore.RESET}. Enter code - Enter a verification code")
print(f" {Fore.GREEN}q{Fore.RESET}. Cancel login")
print()
self._show_device_approval_help = False
else:
print(bcolors.BOLD + "\nWaiting for device approval." + bcolors.ENDC)
print("Check email, SMS message or push notification on the approved device.\n")
print(f"\n{Style.BRIGHT}Waiting for device approval.{Style.RESET_ALL}")
print(f"Check email, SMS, or push notification on the approved device.")
print(f"Enter {Fore.GREEN}c <code>{Fore.RESET} to submit a verification code.\n")

try:
selection = input('Type your selection or <Enter> to resume: ')
selection = input('Selection (or Enter to check status): ').strip().lower()

if selection == "email_send" or selection == "es":
if selection == '1' or selection == 'email_send' or selection == 'es':
step.send_push(login_steps.DeviceApprovalChannel.Email)
print(bcolors.WARNING + "\nAn email with instructions has been sent to " + step.username + bcolors.WARNING + '\nPress <Enter> when approved.')
print(f"\n{Fore.GREEN}Email sent to {step.username}{Fore.RESET}")
print("Click the approval link in the email, then press Enter.\n")

elif selection == '2' or selection == 'keeper_push' or selection == 'kp':
step.send_push(login_steps.DeviceApprovalChannel.KeeperPush)
print(f"\n{Fore.GREEN}Push notification sent.{Fore.RESET}")
print("Approve on your device, then press Enter.\n")

elif selection == '3' or selection == '2fa_send' or selection == '2fs':
step.send_push(login_steps.DeviceApprovalChannel.TwoFactor)
print(f"\n{Fore.GREEN}2FA code sent.{Fore.RESET}")
print("Enter the code using option 'c'.\n")

elif selection == 'c' or selection.startswith('c '):
# Support both "c" (prompts for code) and "c <code>" (code inline)
if selection == 'c':
code_input = input('Enter verification code: ').strip()
else:
code_input = selection[2:].strip() # Extract code after "c "

if code_input:
# Try email code first, then 2FA
try:
step.send_code(login_steps.DeviceApprovalChannel.Email, code_input)
print(f"{Fore.GREEN}Successfully verified email code.{Fore.RESET}")
except KeeperApiError:
try:
step.send_code(login_steps.DeviceApprovalChannel.TwoFactor, code_input)
print(f"{Fore.GREEN}Successfully verified 2FA code.{Fore.RESET}")
except KeeperApiError as e:
print(f"{Fore.YELLOW}Invalid code. Please try again.{Fore.RESET}")

elif selection.startswith("email_code="):
code = selection.replace("email_code=", "")
step.send_code(login_steps.DeviceApprovalChannel.Email, code)
print("Successfully verified email code.")

elif selection == "2fa_send" or selection == "2fs":
step.send_push(login_steps.DeviceApprovalChannel.TwoFactor)
print(bcolors.WARNING + "\n2FA code was sent." + bcolors.ENDC)
print(f"{Fore.GREEN}Successfully verified email code.{Fore.RESET}")

elif selection.startswith("2fa_code="):
code = selection.replace("2fa_code=", "")
step.send_code(login_steps.DeviceApprovalChannel.TwoFactor, code)
print("Successfully verified 2FA code.")
print(f"{Fore.GREEN}Successfully verified 2FA code.{Fore.RESET}")

elif selection == "keeper_push" or selection == "kp":
step.send_push(login_steps.DeviceApprovalChannel.KeeperPush)
logging.info('Successfully made a push notification to the approved device.\nPress <Enter> when approved.')
elif selection == 'q':
step.cancel()

elif selection == "":
elif selection == '':
step.resume()

else:
print(f"{Fore.YELLOW}Invalid selection. Enter 1, 2, 3, c, q, or press Enter.{Fore.RESET}")

except KeyboardInterrupt:
step.cancel()
except KeeperApiError as kae:
Expand Down Expand Up @@ -240,7 +270,7 @@ def on_two_factor(self, step):

def on_password(self, step):
if self._show_password_help:
print(f'Enter password for {step.username}')
print(f'Enter master password for {step.username}')

if self._failed_password_attempt > 0:
print('Forgot password? Type "recover"<Enter>')
Expand Down
Loading
Loading