Skip to content

Redact sensitive credentials from API responses#305

Open
nitrobass24 wants to merge 2 commits intodevelopfrom
fix/redact-credentials-api
Open

Redact sensitive credentials from API responses#305
nitrobass24 wants to merge 2 commits intodevelopfrom
fix/redact-credentials-api

Conversation

@nitrobass24
Copy link
Owner

@nitrobass24 nitrobass24 commented Mar 20, 2026

Summary

/server/config/get no longer returns plaintext passwords and API keys. Sensitive fields are replaced with "********" in API responses.

Changes

  • Config serialization: SerializeConfig.config() redacts remote_password and api_key values
  • Set handler: /server/config/set no longer echoes sensitive values in responses
  • Frontend guard: OptionComponent skips sending updates when a password field value equals the redacted sentinel
  • Config API: Added Config.REDACTED_SENTINEL, Config.is_sensitive(), Config.sensitive_property_names()

What this means

Before: GET /server/config/get returned {"lftp": {"remote_password": "actual-password", ...}}
After: GET /server/config/get returns {"lftp": {"remote_password": "********", ...}}

Tests

  • 3 unit tests for serialization redaction
  • 2 integration tests for API endpoint behavior

Closes #257

🤖 Generated with Claude Code

Summary by CodeRabbit

  • New Features

    • Sensitive configuration fields (passwords, API keys) are redacted and shown as asterisks instead of real values.
    • Responses for updates to sensitive settings no longer echo the supplied value.
  • Bug Fixes

    • Setting a sensitive field to the redacted sentinel is blocked and returns an error instead of applying or echoing the sentinel.
  • Tests

    • Added unit and integration tests verifying redaction, non-echoing behavior, and actual storage of real secrets.

- /server/config/get now returns "********" for remote_password and
  api_key instead of plaintext values
- /server/config/set no longer echoes sensitive values in responses
- Frontend OptionComponent skips sending updates when password field
  value equals the redacted sentinel (prevents overwriting stored
  password with "********")
- Added Config.REDACTED_SENTINEL, Config.is_sensitive(), and
  Config.sensitive_property_names() for centralized sensitivity checks
- 5 new tests (3 unit, 2 integration)

Closes #257

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
@coderabbitai
Copy link

coderabbitai bot commented Mar 20, 2026

No actionable comments were generated in the recent review. 🎉

ℹ️ Recent review info
⚙️ Run configuration

Configuration used: Path: .coderabbit.yaml

Review profile: ASSERTIVE

Plan: Pro

Run ID: aedc96b7-66ed-4339-822e-12385647a189

📥 Commits

Reviewing files that changed from the base of the PR and between 19b1c46 and 224c793.

📒 Files selected for processing (2)
  • src/python/common/config.py
  • src/python/web/handler/config.py

📝 Walkthrough

Walkthrough

Add redaction of sensitive configuration values: backend tags specific fields as sensitive and returns a sentinel "********" in API responses and serializers; frontend ignores sentinel edits for password fields; handler prevents echoing sentinel on set and rejects attempts to set a sensitive field to the sentinel.

Changes

Cohort / File(s) Summary
Frontend Redaction Handling
src/angular/src/app/pages/settings/option.component.ts
Ignore incoming redacted sentinel ("********") in Password-type option change handler to avoid emitting/propagating the sentinel as a real update.
Backend Redaction Infrastructure
src/python/common/config.py
Introduce REDACTED_SENTINEL = "********", plus sensitive_property_names() and is_sensitive() helpers to enumerate and check sensitive section/option pairs.
Backend Serialization
src/python/web/serialize/serialize_config.py
Preprocess serialized config to replace sensitive option values with the redaction sentinel before producing JSON responses.
Backend Handler Logic
src/python/web/handler/config.py
Reject attempts to set a sensitive field to the sentinel (HTTP 400) and change success responses to avoid echoing sensitive values for sensitive keys.
Integration Tests
src/python/tests/integration/test_web/test_handler/test_config.py
Add tests verifying /server/config/get returns redacted values and that setting a sensitive field does not echo the raw value while persisting it.
Unit Tests
src/python/tests/unittests/test_web/test_serialize/test_serialize_config.py
Add unit tests asserting sensitive fields are redacted in serialized output and non-sensitive fields remain unchanged.

Sequence Diagram(s)

sequenceDiagram
  participant Client as Client (Frontend)
  participant Server as API Server
  participant Serializer as SerializeConfig
  participant ConfigStore as Config (in-memory / persistence)

  Client->>Server: GET /server/config/get
  Server->>Serializer: SerializeConfig.config(inner_config)
  Serializer->>ConfigStore: read all sections/options
  alt option is sensitive
    Serializer->>Serializer: replace value with "********"
  end
  Serializer-->>Server: JSON with redactions
  Server-->>Client: 200 OK + redacted JSON

  Client->>Server: POST/PUT /server/config/set/<section>/<option>/<value>
  Server->>ConfigStore: verify section/option exists
  alt value == "********" and option is sensitive
    Server-->>Client: 400 Bad Request (reject sentinel)
  else
    Server->>ConfigStore: set_property(section, option, value)
    alt option is sensitive
      Server-->>Client: 200 OK ("section.option updated")
    else
      Server-->>Client: 200 OK ("section.option set to <value>")
    end
  end
Loading

Estimated code review effort

🎯 3 (Moderate) | ⏱️ ~20 minutes

Poem

🐰 I nibbled secrets, soft and small,

Stars of stars that hide them all,
I hop and tuck the real keys tight,
Outside they wear a mask tonight,
A whisper: "safety first" — and all is right.

🚥 Pre-merge checks | ✅ 3 | ❌ 2

❌ Failed checks (2 warnings)

Check name Status Explanation Resolution
Linked Issues check ⚠️ Warning The PR implements redaction in API responses but does not implement the core requirement: encryption of sensitive values at rest in the config file. Implement encryption key generation/loading, encrypt sensitive fields before writing to config file, decrypt on read, and ensure file permissions (0600). Add auto-migration for existing plaintext values.
Docstring Coverage ⚠️ Warning Docstring coverage is 23.08% which is insufficient. The required threshold is 80.00%. Write docstrings for the functions missing them to satisfy the coverage threshold.
✅ Passed checks (3 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check ✅ Passed The title accurately summarizes the main change: redacting sensitive credentials from API responses across all modified files.
Out of Scope Changes check ✅ Passed All changes are within scope—redacting credentials from API responses, handling sentinel values in UI, adding Config utilities, and adding tests align with the PR's stated objectives.

✏️ Tip: You can configure your own custom pre-merge checks in the settings.

✨ Finishing Touches
📝 Generate docstrings
  • Create stacked PR
  • Commit on current branch
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Commit unit tests in branch fix/redact-credentials-api

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 and usage tips.

Copy link

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

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

Actionable comments posted: 3

🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Inline comments:
In `@src/angular/src/app/pages/settings/option.component.ts`:
- Around line 57-62: The check in option.component.ts uses the magic string
'********' to detect the backend redaction sentinel (used when
OptionType.Password), which duplicates backend Config.REDACTED_SENTINEL; extract
that literal into a named constant (e.g., REDACTED_SENTINEL) exported from a
shared config/module or at least a single constant in this component, then
replace the inline string check (the if referencing OptionType.Password) to
compare against REDACTED_SENTINEL and add a short comment documenting the
coupling to the backend sentinel so future changes are noticed.

In `@src/python/common/config.py`:
- Around line 468-478: The method sensitive_property_names is declared as a
`@classmethod` but never uses cls; change it to a `@staticmethod` to reflect that it
does not depend on class state: replace the `@classmethod` decorator with
`@staticmethod` for the function sensitive_property_names and keep its return
value and signature (no self/cls parameter), ensuring any callers still access
it as ConfigClass.sensitive_property_names() or via an instance if used
elsewhere.

In `@src/python/web/handler/config.py`:
- Around line 39-43: When updating config in the handler, add backend validation
to reject the redaction sentinel '********' for sensitive fields: before calling
inner_config.set_property in the code path where Config.is_sensitive(section,
key) is true, check if value == '********' and return an error HTTPResponse
(e.g., 400 with a message like "Cannot set sensitive field to redacted
sentinel") instead of persisting; this ensures inner_config.set_property and the
handler do not overwrite real secrets with the literal sentinel.

ℹ️ Review info
⚙️ Run configuration

Configuration used: Path: .coderabbit.yaml

Review profile: ASSERTIVE

Plan: Pro

Run ID: 48df2dd1-15a4-4a85-a65f-fe9ddfe3c1f0

📥 Commits

Reviewing files that changed from the base of the PR and between e7b5577 and 19b1c46.

📒 Files selected for processing (6)
  • src/angular/src/app/pages/settings/option.component.ts
  • src/python/common/config.py
  • src/python/tests/integration/test_web/test_handler/test_config.py
  • src/python/tests/unittests/test_web/test_serialize/test_serialize_config.py
  • src/python/web/handler/config.py
  • src/python/web/serialize/serialize_config.py

Comment on lines +57 to +62
// Don't send updates for password fields when the value is the redacted sentinel.
// The server returns "********" for sensitive fields; sending it back would
// overwrite the real password with the sentinel.
if (this.type() === OptionType.Password && value === '********') {
return;
}
Copy link

Choose a reason for hiding this comment

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

🧹 Nitpick | 🔵 Trivial

Consider extracting the sentinel constant to avoid magic string duplication.

The logic correctly prevents sending the redacted sentinel '********' back to the server, which would overwrite the real password. However, this string literal is duplicated between the frontend and backend (Config.REDACTED_SENTINEL in Python).

If the sentinel value ever changes on the backend, this frontend check would silently break. Consider extracting this to a shared constant or documenting the coupling.

Also note: if a user intentionally enters '********' as their actual password, the change will be silently ignored. This is an acceptable security trade-off but worth documenting.

💡 Suggested improvement
 export enum OptionType {
   Text,
   Checkbox,
   Password,
   Select,
 }

+/** Sentinel value used by the server to represent redacted sensitive fields. */
+const REDACTED_SENTINEL = '********';
+
 `@Component`({

Then use REDACTED_SENTINEL in the check:

-    if (this.type() === OptionType.Password && value === '********') {
+    if (this.type() === OptionType.Password && value === REDACTED_SENTINEL) {
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@src/angular/src/app/pages/settings/option.component.ts` around lines 57 - 62,
The check in option.component.ts uses the magic string '********' to detect the
backend redaction sentinel (used when OptionType.Password), which duplicates
backend Config.REDACTED_SENTINEL; extract that literal into a named constant
(e.g., REDACTED_SENTINEL) exported from a shared config/module or at least a
single constant in this component, then replace the inline string check (the if
referencing OptionType.Password) to compare against REDACTED_SENTINEL and add a
short comment documenting the coupling to the backend sentinel so future changes
are noticed.

- Reject setting sensitive fields to "********" (the redacted sentinel)
  with 400 error — prevents accidentally overwriting real credentials
  via direct API access
- Change sensitive_property_names from classmethod to staticmethod
  (cls was unused)

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
@nitrobass24
Copy link
Owner Author

@CodeRabbit review

@coderabbitai
Copy link

coderabbitai bot commented Mar 20, 2026

✅ Actions performed

Review triggered.

Note: CodeRabbit is an incremental review system and does not re-review already reviewed commits. This command is applicable only when automatic reviews are paused.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant