Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
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
16 changes: 12 additions & 4 deletions RECORD_ADD_DOCUMENTATION.md
Original file line number Diff line number Diff line change
Expand Up @@ -36,6 +36,7 @@ record-add --title "Record Title" --record-type "RECORD_TYPE" [OPTIONS] [FIELDS.
**Special Value Syntax:**
- `$JSON:{"key": "value"}` - For complex object fields
- `$GEN` - Generate passwords, TOTP codes, or key pairs
- `$BASE64:<base64_string>` - Decode base64-encoded values for any string field
- `file=@filename` - File attachments

## Record Types
Expand Down Expand Up @@ -511,10 +512,11 @@ echo "Emergency database access: $URL" | secure-send user@company.com
1. **Use single-line commands for copy-paste** to avoid trailing space issues
2. **Quote JSON values** to prevent shell interpretation
3. **Use $GEN for passwords** instead of hardcoding them
4. **Test with simple records first** before creating complex ones
5. **Use custom fields (c.) for non-standard data**
6. **Organize records in folders** using the `--folder` parameter
7. **Add meaningful notes** with `--notes` for context
4. **Use $BASE64: for complex passwords** with special characters to avoid shell escaping issues
5. **Test with simple records first** before creating complex ones
6. **Use custom fields (c.) for non-standard data**
7. **Organize records in folders** using the `--folder` parameter
8. **Add meaningful notes** with `--notes` for context

## Troubleshooting

Expand All @@ -538,6 +540,12 @@ echo "Emergency database access: $URL" | secure-send user@company.com
- Ensure file path is accessible
- Use absolute paths to avoid confusion

**Base64 decoding errors**
- Ensure the base64 string is valid (test with `echo <string> | base64 -d`)
- Use the `$BASE64:` prefix: `password='$BASE64:UEBzc3cwcmQh'`
- Remove any newlines or spaces from the base64 string
- Check that the decoded value is valid UTF-8 text

## Record-Update vs Record-Add

While `record-add` creates new records, `record-update` modifies existing records. Here's how they compare:
Expand Down
36 changes: 21 additions & 15 deletions keepercommander/commands/record_edit.py
Original file line number Diff line number Diff line change
Expand Up @@ -290,8 +290,6 @@ def assign_legacy_fields(self, record, fields):
elif self.is_base64_value(parsed_field.value, action_params):
if action_params:
record.password = action_params[0]
else:
logging.warning('Base64 decoding failed for password field')
else:
record.password = parsed_field.value
elif parsed_field.type == 'url':
Expand Down Expand Up @@ -322,11 +320,13 @@ def is_json_value(self, value, parameters): # type: (str, List[Any]) -> Option
value = value[5:]
if value.startswith(':'):
j_str = value[1:]
if j_str and isinstance(parameters, list):
if not j_str:
self.on_warning('JSON value cannot be empty. Format: $JSON:<json_object>')
elif isinstance(parameters, list):
try:
parameters.append(json.loads(j_str))
except Exception as e:
self.on_warning(f'Invalid JSON value: {j_str}: {e}')
self.on_warning(f'Invalid JSON value: {e}')
return True

@staticmethod
Expand All @@ -339,19 +339,27 @@ def is_generate_value(value, parameters): # type: (str, List[str]) -> Optiona
parameters.extend((x.strip() for x in gen_parameters.split(',')))
return True

@staticmethod
def is_base64_value(value, parameters): # type: (str, List[str]) -> Optional[bool]
def is_base64_value(self, value, parameters): # type: (str, List[str]) -> Optional[bool]
"""Check if value is base64-encoded and decode it."""
if value.startswith("$BASE64:"):
encoded_value = value[8:] # Skip "$BASE64:"
if encoded_value and isinstance(parameters, list):

# Validate and provide helpful error messages
if not encoded_value:
self.on_warning('Base64 value cannot be empty. Format: $BASE64:<base64_string>')
elif isinstance(parameters, list):
try:
decoded_bytes = base64.b64decode(encoded_value)
decoded_str = decoded_bytes.decode('utf-8')
parameters.append(decoded_str)
return True
decoded_bytes = base64.b64decode(encoded_value, validate=True)
if not decoded_bytes:
self.on_warning('Base64 decoded to empty value')
else:
decoded_str = decoded_bytes.decode('utf-8')
if not decoded_str:
self.on_warning('Base64 decoded to empty string')
else:
parameters.append(decoded_str)
except Exception as e:
logging.warning(f'Failed to decode base64 value: {e}')
self.on_warning(f'Invalid base64 value: {e}')
return True
return False

Expand Down Expand Up @@ -599,10 +607,8 @@ def assign_typed_fields(self, record, fields):
else:
self.on_warning(f'Cannot generate a value for a \"{record_field.type}\" field.')
elif self.is_base64_value(parsed_field.value, action_params):
if len(action_params) > 0:
if action_params:
value = action_params[0]
else:
self.on_warning(f'Base64 decoding failed for field \"{record_field.type}\".')
elif self.is_json_value(parsed_field.value, action_params):
if len(action_params) > 0:
value = self.validate_json_value(record_field.type, action_params[0])
Expand Down
17 changes: 13 additions & 4 deletions keepercommander/rest_api.py
Original file line number Diff line number Diff line change
Expand Up @@ -226,14 +226,23 @@ def execute_rest(context, endpoint, payload):
server_key_id = failure['key_id']
if 'qrc_ec_key_id' in failure:
qrc_ec_key_id = failure['qrc_ec_key_id']
if context.server_key_id != qrc_ec_key_id:
# Defensive check: qrc_ec_key_id must be EC key (7-18)
if not (7 <= qrc_ec_key_id <= 18):
logging.warning(f"Server returned invalid qrc_ec_key_id={qrc_ec_key_id} (expected EC key 7-18). Falling back to EC-only encryption.")
context.disable_qrc()
elif context.server_key_id != qrc_ec_key_id:
# EC key mismatch: update and retry with QRC
logging.debug(f"QRC EC key mismatch: updating from {context.server_key_id} to {qrc_ec_key_id}")
context.server_key_id = qrc_ec_key_id
run_request = True
continue
run_request = True
continue
elif server_key_id != context.server_key_id:
context.server_key_id = server_key_id
# If server returns non-EC key without qrc_ec_key_id, disable QRC
if not (7 <= server_key_id <= 18):
logging.warning(f"Server returned non-EC key_id={server_key_id} without qrc_ec_key_id. Falling back to EC-only encryption.")
context.disable_qrc()
else:
context.server_key_id = server_key_id
run_request = True
continue
elif rs.status_code == 403:
Expand Down