Skip to content

Commit a14bb01

Browse files
Merge pull request #141 from codeflash-ai/connector-live-debug-diagnostics
Print sanitized live connector diagnostics
2 parents e400c52 + a50093f commit a14bb01

8 files changed

Lines changed: 291 additions & 0 deletions

tests/live_connector_common.sh

Lines changed: 220 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -16,6 +16,226 @@ _live_report_retained_log() {
1616
fi
1717
}
1818

19+
_live_debug_sanitize_file() {
20+
local path="$1"
21+
local mode="${2:-text}"
22+
local max_lines="${3:-${LOCALITY_LIVE_DEBUG_TAIL_LINES:-160}}"
23+
24+
if [[ ! -s "$path" ]]; then
25+
return 0
26+
fi
27+
28+
python3 - "$path" "$mode" "$max_lines" <<'PY'
29+
import json
30+
import os
31+
import pathlib
32+
import re
33+
import sys
34+
35+
path = pathlib.Path(sys.argv[1])
36+
mode = sys.argv[2]
37+
max_lines = int(sys.argv[3])
38+
39+
SENSITIVE_ENV_NAME = re.compile(
40+
r"(TOKEN|SECRET|CREDENTIAL|PASSWORD|API_KEY|AUTH|ISSUE_ID|"
41+
r"CONVERSATION_ID|TO_EMAIL|EMAIL|REFRESH)",
42+
re.IGNORECASE,
43+
)
44+
SENSITIVE_KEY_FRAGMENTS = (
45+
"token",
46+
"secret",
47+
"credential",
48+
"authorization",
49+
"api_key",
50+
"password",
51+
"refresh",
52+
"client_secret",
53+
"cookie",
54+
)
55+
IDENTIFIER_KEYS = {
56+
"id",
57+
"remote_id",
58+
"remote_ids",
59+
"changed_remote_ids",
60+
"message_id",
61+
"thread_id",
62+
"draft_id",
63+
"conversation_id",
64+
"issue_id",
65+
"document_id",
66+
"event_id",
67+
"channel_id",
68+
}
69+
PII_KEYS = {
70+
"email",
71+
"to",
72+
"from",
73+
"cc",
74+
"bcc",
75+
"recipient",
76+
"recipients",
77+
"account_label",
78+
}
79+
80+
81+
def sensitive_env_values():
82+
values = []
83+
for name, value in os.environ.items():
84+
if not value or not SENSITIVE_ENV_NAME.search(name):
85+
continue
86+
value = str(value)
87+
if len(value) < 4 or value in {"true", "false", "none", "null"}:
88+
continue
89+
values.append(value)
90+
values.sort(key=len, reverse=True)
91+
return values
92+
93+
94+
ENV_VALUES = sensitive_env_values()
95+
96+
97+
def redact_text(text):
98+
if not text:
99+
return text
100+
for value in ENV_VALUES:
101+
text = text.replace(value, "<redacted>")
102+
replacements = [
103+
(r"(?i)(authorization:\s*bearer\s+)[^\s]+", r"\1<redacted>"),
104+
(r"(?i)(bearer\s+)[A-Za-z0-9._~+/=-]+", r"\1<redacted>"),
105+
(r"lin_api_[A-Za-z0-9]+", "<redacted>"),
106+
(r"ya29\.[A-Za-z0-9._-]+", "<redacted>"),
107+
(r"xox[a-zA-Z]-[A-Za-z0-9-]+", "<redacted>"),
108+
(r"gh[pousr]_[A-Za-z0-9_]+", "<redacted>"),
109+
(r"AIza[0-9A-Za-z_-]{20,}", "<redacted>"),
110+
(
111+
r"(?i)\b[A-Z0-9._%+-]+@[A-Z0-9.-]+\.[A-Z]{2,}\b",
112+
"<redacted>",
113+
),
114+
(
115+
r'(?i)("?(?:access_token|refresh_token_handle|api_key|token|secret|credential|authorization|client_secret|password)"?\s*[:=]\s*)("[^"]*"|[^,\s}]+)',
116+
r"\1<redacted>",
117+
),
118+
]
119+
for pattern, replacement in replacements:
120+
text = re.sub(pattern, replacement, text)
121+
return text
122+
123+
124+
def is_sensitive_key(key):
125+
lowered = str(key).lower()
126+
return any(fragment in lowered for fragment in SENSITIVE_KEY_FRAGMENTS)
127+
128+
129+
def sanitize_json(value, key=""):
130+
key_lower = str(key).lower()
131+
if isinstance(value, dict):
132+
return {k: sanitize_json(v, k) for k, v in value.items()}
133+
if isinstance(value, list):
134+
if key_lower in IDENTIFIER_KEYS or key_lower in PII_KEYS or is_sensitive_key(key):
135+
return ["<redacted>" for _ in value]
136+
return [sanitize_json(item, key) for item in value]
137+
if isinstance(value, str):
138+
if key_lower in IDENTIFIER_KEYS or key_lower in PII_KEYS or is_sensitive_key(key):
139+
return "<redacted>"
140+
return redact_text(value)
141+
return value
142+
143+
144+
try:
145+
raw_text = path.read_text(encoding="utf-8", errors="replace")
146+
except OSError as error:
147+
print(f"could not read {path}: {error}", file=sys.stderr)
148+
raise SystemExit(0)
149+
150+
if mode == "json":
151+
try:
152+
parsed = json.loads(raw_text)
153+
except Exception:
154+
text = raw_text
155+
else:
156+
text = json.dumps(sanitize_json(parsed), indent=2, sort_keys=True)
157+
max_bytes = int(os.environ.get("LOCALITY_LIVE_DEBUG_JSON_BYTES", "20000"))
158+
if len(text.encode("utf-8")) > max_bytes:
159+
text = text.encode("utf-8")[:max_bytes].decode("utf-8", errors="replace")
160+
text += "\n... <truncated>"
161+
else:
162+
lines = raw_text.splitlines()
163+
if max_lines > 0 and len(lines) > max_lines:
164+
text = f"... showing last {max_lines} of {len(lines)} lines ...\n"
165+
text += "\n".join(lines[-max_lines:])
166+
else:
167+
text = "\n".join(lines)
168+
169+
print(redact_text(text))
170+
PY
171+
}
172+
173+
_live_debug_emit_file() {
174+
local label="$1"
175+
local path="$2"
176+
local mode="${3:-text}"
177+
local max_lines="${4:-${LOCALITY_LIVE_DEBUG_TAIL_LINES:-160}}"
178+
179+
if [[ -z "$path" || ! -s "$path" ]]; then
180+
return 0
181+
fi
182+
183+
{
184+
echo "::group::privacy-safe diagnostics: $label"
185+
echo "path: $path"
186+
_live_debug_sanitize_file "$path" "$mode" "$max_lines"
187+
echo "::endgroup::"
188+
} >&2
189+
}
190+
191+
_live_debug_emit_var_file() {
192+
local var_name="$1"
193+
local label="$2"
194+
local mode="${3:-text}"
195+
local max_lines="${4:-${LOCALITY_LIVE_DEBUG_TAIL_LINES:-160}}"
196+
local path="${!var_name:-}"
197+
198+
_live_debug_emit_file "$label" "$path" "$mode" "$max_lines"
199+
}
200+
201+
emit_live_debug_diagnostics() {
202+
local label="${1:-live connector}"
203+
local report_var
204+
local log_var
205+
206+
echo "privacy-safe diagnostics: $label" >&2
207+
for report_var in \
208+
mount_report \
209+
connect_report \
210+
initial_pull_report \
211+
first_pull_report \
212+
second_pull_report \
213+
diff_report \
214+
push_report \
215+
pull_after_push_report \
216+
edit_diff_report \
217+
edit_push_report \
218+
pull_after_edit_report \
219+
restore_diff_report \
220+
restore_push_report \
221+
cleanup_diff_report \
222+
cleanup_push_report \
223+
cleanup_pull_report \
224+
status_report \
225+
info_report \
226+
doctor_report \
227+
drive_search_report \
228+
calendar_search_report \
229+
drafts_list_report \
230+
draft_get_report; do
231+
_live_debug_emit_var_file "$report_var" "$report_var" json
232+
done
233+
234+
for log_var in command_log daemon_log fuse_log; do
235+
_live_debug_emit_var_file "$log_var" "$log_var" text
236+
done
237+
}
238+
19239
require_live_env() {
20240
local name
21241
for name in "$@"; do

tests/live_connector_common_selftest.sh

Lines changed: 60 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -195,6 +195,66 @@ if ! grep -Fq "self-test not-ok report did not report ok=true" "$not_ok_error";
195195
live_fail "assert_json_ok did not explain the ok=false failure"
196196
fi
197197

198+
export LINEAR_API_KEY="lin_api_selftestsecret"
199+
export LOCALITY_LINEAR_LIVE_ISSUE_ID="lin_selftest_secret_issue"
200+
export LOCALITY_GMAIL_LIVE_TO_EMAIL="private-selftest@example.com"
201+
debug_report="$tmp_root/debug-report.json"
202+
debug_command_log="$tmp_root/debug-commands.err.log"
203+
debug_daemon_log="$tmp_root/debug-localityd.log"
204+
debug_fuse_log="$tmp_root/debug-locality-fuse.log"
205+
debug_output="$tmp_root/debug-output.err"
206+
cat >"$debug_report" <<'JSON'
207+
{
208+
"ok": false,
209+
"status": "error",
210+
"code": "selftest_permission_denied",
211+
"error": {
212+
"code": "linear_push_failed",
213+
"message": "failed to update lin_selftest_secret_issue for private-selftest@example.com with lin_api_selftestsecret"
214+
},
215+
"changed_remote_ids": ["lin_selftest_secret_issue"],
216+
"access_token": "ya29.selftest-secret-access-token"
217+
}
218+
JSON
219+
cat >"$debug_command_log" <<'LOG'
220+
curl failed with Authorization: Bearer ya29.selftest-secret-access-token
221+
linear key was lin_api_selftestsecret
222+
LOG
223+
cat >"$debug_daemon_log" <<'LOG'
224+
localityd is running with private-selftest@example.com
225+
LOG
226+
cat >"$debug_fuse_log" <<'LOG'
227+
fuse saw lin_selftest_secret_issue
228+
LOG
229+
230+
push_report="$debug_report" \
231+
command_log="$debug_command_log" \
232+
daemon_log="$debug_daemon_log" \
233+
fuse_log="$debug_fuse_log" \
234+
emit_live_debug_diagnostics "Self-test connector" 2>"$debug_output"
235+
if ! grep -Fq "privacy-safe diagnostics: Self-test connector" "$debug_output"; then
236+
live_fail "debug diagnostics did not include the connector label"
237+
fi
238+
if ! grep -Fq "selftest_permission_denied" "$debug_output"; then
239+
live_fail "debug diagnostics did not include the report error code"
240+
fi
241+
if ! grep -Fq "linear_push_failed" "$debug_output"; then
242+
live_fail "debug diagnostics did not include the nested error code"
243+
fi
244+
if ! grep -Fq "<redacted>" "$debug_output"; then
245+
live_fail "debug diagnostics did not show redaction placeholders"
246+
fi
247+
for leaked_debug_value in \
248+
"lin_api_selftestsecret" \
249+
"lin_selftest_secret_issue" \
250+
"private-selftest@example.com" \
251+
"ya29.selftest-secret-access-token"; do
252+
if grep -Fq "$leaked_debug_value" "$debug_output"; then
253+
live_fail "debug diagnostics leaked $leaked_debug_value"
254+
fi
255+
done
256+
unset LINEAR_API_KEY LOCALITY_LINEAR_LIVE_ISSUE_ID LOCALITY_GMAIL_LIVE_TO_EMAIL
257+
198258
init_live_state "$loc_bin" "$state_root"
199259
seed_connector_credential "$loc_bin" "$state_root" "google-docs" "google-docs-live" "$credential_json"
200260

tests/live_gmail_vfs_roundtrip.sh

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -394,6 +394,7 @@ on_error() {
394394
local code=$?
395395
echo "live Gmail VFS round trip failed during: $step" >&2
396396
echo "privacy-safe diagnostics: exit=$code" >&2
397+
emit_live_debug_diagnostics "Gmail VFS round trip" || true
397398
return "$code"
398399
}
399400

tests/live_google_calendar_vfs_roundtrip.sh

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -217,6 +217,7 @@ on_error() {
217217
local code=$?
218218
echo "live Google Calendar VFS round trip failed during: $step" >&2
219219
echo "privacy-safe diagnostics: exit=$code" >&2
220+
emit_live_debug_diagnostics "Google Calendar VFS round trip" || true
220221
return "$code"
221222
}
222223

tests/live_google_docs_vfs_roundtrip.sh

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -196,6 +196,7 @@ on_error() {
196196
local code=$?
197197
echo "live Google Docs VFS round trip failed during: $step" >&2
198198
echo "privacy-safe diagnostics: exit=$code" >&2
199+
emit_live_debug_diagnostics "Google Docs VFS round trip" || true
199200
return "$code"
200201
}
201202

tests/live_granola_vfs_read.sh

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,11 @@ if [[ "${LOCALITY_LIVE_GRANOLA_VFS:-}" != "1" ]]; then
66
exit 0
77
fi
88

9+
script_dir="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
10+
11+
# shellcheck source=tests/live_connector_common.sh
12+
source "$script_dir/live_connector_common.sh"
13+
914
if [[ "$(uname -s)" != "Linux" ]]; then
1015
echo "skip: live Granola VFS test requires Linux"
1116
exit 0
@@ -71,6 +76,7 @@ on_error() {
7176
local code=$?
7277
echo "live Granola VFS test failed during: $step" >&2
7378
echo "privacy-safe diagnostics: exit=$code" >&2
79+
emit_live_debug_diagnostics "Granola VFS read test" || true
7480
if [[ -n "$localityd_pid" ]]; then
7581
if kill -0 "$localityd_pid" >/dev/null 2>&1; then
7682
echo "privacy-safe diagnostics: daemon=running" >&2

tests/live_linear_vfs_roundtrip.sh

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -17,6 +17,7 @@ on_error() {
1717
local code=$?
1818
echo "live Linear VFS round trip failed during: $step" >&2
1919
echo "privacy-safe diagnostics: exit=$code" >&2
20+
emit_live_debug_diagnostics "Linear VFS round trip" || true
2021
return "$code"
2122
}
2223

tests/live_slack_vfs_read.sh

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -283,6 +283,7 @@ on_error() {
283283
local code=$?
284284
echo "live Slack VFS read-only test failed during: $step" >&2
285285
echo "privacy-safe diagnostics: exit=$code" >&2
286+
emit_live_debug_diagnostics "Slack VFS read-only test" || true
286287
return "$code"
287288
}
288289

0 commit comments

Comments
 (0)