Skip to content

Fix credential redaction and bidi sanitization gaps - #8

Merged
alptekinege merged 1 commit into
mainfrom
floppy-clubs-yell
Aug 2, 2026
Merged

Fix credential redaction and bidi sanitization gaps#8
alptekinege merged 1 commit into
mainfrom
floppy-clubs-yell

Conversation

@alptekinege

@alptekinege alptekinege commented Aug 2, 2026

Copy link
Copy Markdown
Owner

Summary

Review of the recent security-hardening work found two gaps, both now fixed:

1. redact() leaked non-Bearer/Token authorization credentials

githubfetch/api.py — the Authorization scrubber only redacted the value when preceded by the Bearer/Token scheme:

  • Authorization: Basic dXNlcjpwYXNzAuthorization: ***REDACTED*** dXNlcjpwYXNz (base64 creds leaked to the terminal)
  • Authorization: Digest realm=x, nonce=deadbeef → parameters leaked

Fix: redact everything after Authorization: through the end of the line — scheme and credential together, regardless of scheme, with no multi-token gaps. Other lines and text before the header are preserved.

2. sanitize_text() missed two bidi format controls

githubfetch/sanitize.py — the zero-width/bidi blocklist missed the Cf format controls U+061C (Arabic Letter Mark) and U+08E2 (Arabic Disputed End of Ayah, which disables bidi for the rest of the line), both of which passed through hostile profile content to the terminal.

Fix: add both to _ZERO_WIDTH_AND_BIDI (targeted — legitimate Cf chars like emoji variation selectors are untouched).

Validation

  • 4 new targeted regression tests (test_api.py, test_sanitize.py)
  • Full suite: 310 passed, 2 skipped
  • ruff check . and mypy clean

Summary by CodeRabbit

  • Bug Fixes
    • Improved credential redaction for all authorization schemes, preventing sensitive values from appearing in sanitized output.
    • Enhanced text sanitization to remove additional invisible and bidirectional formatting characters.
  • Tests
    • Added coverage for varied authorization headers, multiline content, and repeated invisible characters.

redact(): the Authorization scrubber only handled the Bearer/Token
schemes, so "Authorization: Basic <base64>" leaked the credential and
Digest-style multi-token values leaked their parameters. Redact
everything after the header name to the end of the line.

sanitize(): the bidi-control blocklist missed the Cf format controls
U+061C (Arabic Letter Mark) and U+08E2 (Arabic Disputed End of Ayah),
both of which alter bidi processing when a hostile bio reaches the
terminal. Add them to the zero-width/bidi set.

Adds targeted regression tests for both; full suite 310 passed.
@coderabbitai

coderabbitai Bot commented Aug 2, 2026

Copy link
Copy Markdown

Review Change Stack

📝 Walkthrough

Walkthrough

Changes

The PR expands Authorization header redaction to remove complete line values for all schemes. It also removes two additional bidi control characters during sanitization and adds regression tests for both behaviors.

Text safety

Layer / File(s) Summary
Authorization header redaction
githubfetch/api.py, tests/test_api.py
redact removes complete authorization header values through each line ending. Tests cover Basic, Digest, and multiline input preservation.
Bidi control sanitization
githubfetch/sanitize.py, tests/test_sanitize.py
sanitize_text removes Arabic Letter Mark and Arabic Disputed End of Ayah controls, including repeated occurrences.

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

🚥 Pre-merge checks | ✅ 5
✅ Passed checks (5 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check ✅ Passed The title clearly and concisely summarizes both security fixes: credential redaction and bidi sanitization.
Docstring Coverage ✅ Passed No functions found in the changed files to evaluate docstring coverage. Skipping docstring coverage check.
Linked Issues check ✅ Passed Check skipped because no linked issues were found for this pull request.
Out of Scope Changes check ✅ Passed Check skipped because no linked issues were found for this pull request.
✨ 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 floppy-clubs-yell

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.

@coderabbitai coderabbitai Bot 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: 2

🤖 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 `@githubfetch/api.py`:
- Around line 67-68: Update the Authorization-redaction regex in re.sub to use
horizontal whitespace only around the header delimiter, replacing
newline-permitting spacing with a space/tab character class so redaction cannot
consume subsequent physical lines.

In `@tests/test_api.py`:
- Around line 77-90: Strengthen test_redact_scrubs_arbitrary_auth_scheme and
test_redact_preserves_text_before_header_and_other_lines to verify complete
authorization-value removal: assert realm=example is absent from the Digest
output and trailing text such as rejected is absent from the single-line output,
or assert the exact expected redacted lines.
🪄 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: 23171179-d110-434d-a71d-48a77264af56

📥 Commits

Reviewing files that changed from the base of the PR and between cf40c89 and be08912.

📒 Files selected for processing (4)
  • githubfetch/api.py
  • githubfetch/sanitize.py
  • tests/test_api.py
  • tests/test_sanitize.py

Comment thread githubfetch/api.py
Comment on lines 67 to +68
out = re.sub(
r"(?i)(authorization[\"']?\s*[:=]\s*[\"']?)(bearer|token)?\s*\S+",
r"(?i)(authorization[\"']?\s*[:=]\s*[\"']?)[^\n\r]*",

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

🎯 Functional Correctness | 🟡 Minor | ⚡ Quick win

🧩 Analysis chain

🏁 Script executed:

#!/usr/bin/env bash
set -euo pipefail

python - <<'PY'
import re

sample = "Authorization: \nNext: keep"
replacement = r"\1***REDACTED***"

current = re.compile(
    r"(?i)(authorization[\"']?\s*[:=]\s*[\"']?)[^\n\r]*"
)
fixed = re.compile(
    r"(?i)(authorization[\"']?[ \t]*[:=][ \t]*[\"']?)[^\n\r]*"
)

assert current.sub(replacement, sample) == "Authorization: \n***REDACTED***"
assert fixed.sub(replacement, sample) == (
    "Authorization: ***REDACTED***\nNext: keep"
)
PY

Repository: alptekinege/githubfetch

Length of output: 161


🏁 Script executed:

#!/usr/bin/env bash
set -euo pipefail

echo "## File existence and line count"
if [ -f githubfetch/api.py ]; then
  wc -l githubfetch/api.py
else
  fd -a 'api.py$' . | sed 's#^\./##'
fi

echo
echo "## githubfetch/api.py outline"
ast-grep outline githubfetch/api.py || true

echo
echo "## Relevant lines 40-90"
sed -n '40,90p' githubfetch/api.py | cat -n

echo
echo "## Search for redact usages and tests"
rg -n "def redact|redact\(|Authorization|authorization|authorization" githubfetch tests . 2>/dev/null | head -200

Repository: alptekinege/githubfetch

Length of output: 7643


Keep Authorization header redaction on the same physical line.

The regex allows \s* before and after :/=, so Authorization: \nNext: keep can consume Next: keep as part of the header value and redact unrelated output. Restrict delimiter spacing to horizontal whitespace, e.g. [ \t]*.

🤖 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 `@githubfetch/api.py` around lines 67 - 68, Update the Authorization-redaction
regex in re.sub to use horizontal whitespace only around the header delimiter,
replacing newline-permitting spacing with a space/tab character class so
redaction cannot consume subsequent physical lines.

Comment thread tests/test_api.py
Comment on lines +77 to +90
def test_redact_scrubs_arbitrary_auth_scheme():
out = redact("authorization = \"Digest realm=example, nonce=deadbeef\"")
assert "nonce=deadbeef" not in out
assert "Digest" not in out


def test_redact_preserves_text_before_header_and_other_lines():
out = redact("Error 401: Authorization: Bearer abc123 rejected")
assert "abc123" not in out
assert "Error 401:" in out
multiline = redact("GET /users HTTP/1.1\nAuthorization: Bearer abc123\nAccept: */*")
assert "abc123" not in multiline
assert "GET /users HTTP/1.1" in multiline
assert "Accept: */*" in multiline

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 | 🟡 Minor | ⚡ Quick win

Assert removal of the complete authorization value.

The Digest test does not assert that realm=example is removed. The single-line test does not assert that trailing text such as rejected is removed. Add these assertions, or assert the exact redacted line, so a partial redaction cannot pass.

Suggested test additions
     out = redact("authorization = \"Digest realm=example, nonce=deadbeef\"")
     assert "nonce=deadbeef" not in out
+    assert "realm=example" not in out
     assert "Digest" not in out
...
     out = redact("Error 401: Authorization: Bearer abc123 rejected")
     assert "abc123" not in out
+    assert "rejected" not in out
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
def test_redact_scrubs_arbitrary_auth_scheme():
out = redact("authorization = \"Digest realm=example, nonce=deadbeef\"")
assert "nonce=deadbeef" not in out
assert "Digest" not in out
def test_redact_preserves_text_before_header_and_other_lines():
out = redact("Error 401: Authorization: Bearer abc123 rejected")
assert "abc123" not in out
assert "Error 401:" in out
multiline = redact("GET /users HTTP/1.1\nAuthorization: Bearer abc123\nAccept: */*")
assert "abc123" not in multiline
assert "GET /users HTTP/1.1" in multiline
assert "Accept: */*" in multiline
def test_redact_scrubs_arbitrary_auth_scheme():
out = redact("authorization = \"Digest realm=example, nonce=deadbeef\"")
assert "nonce=deadbeef" not in out
assert "realm=example" not in out
assert "Digest" not in out
def test_redact_preserves_text_before_header_and_other_lines():
out = redact("Error 401: Authorization: Bearer abc123 rejected")
assert "abc123" not in out
assert "rejected" not in out
assert "Error 401:" in out
multiline = redact("GET /users HTTP/1.1\nAuthorization: Bearer abc123\nAccept: */*")
assert "abc123" not in multiline
assert "GET /users HTTP/1.1" in multiline
assert "Accept: */*" in multiline
🤖 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 `@tests/test_api.py` around lines 77 - 90, Strengthen
test_redact_scrubs_arbitrary_auth_scheme and
test_redact_preserves_text_before_header_and_other_lines to verify complete
authorization-value removal: assert realm=example is absent from the Digest
output and trailing text such as rejected is absent from the single-line output,
or assert the exact expected redacted lines.

@alptekinege
alptekinege merged commit 9ca3f23 into main Aug 2, 2026
12 checks passed
@alptekinege
alptekinege deleted the floppy-clubs-yell branch August 2, 2026 00:52
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