一键导入
urlparse-netloc-reconstruction-traps
urllib.parse.urlparse로 URL 파싱 후 netloc 재조합 시 발생하는 두 침묵 버그 — userinfo 대소문자 소실과 cross-scheme 기본 포트 오삭제.
用 Codex 或 Claude 帮你安装 复制这段 Prompt,粘贴到 Codex、Claude 或其他助手里,让它检查 Skill 页面并帮你完成安装。
菜单
urllib.parse.urlparse로 URL 파싱 후 netloc 재조합 시 발생하는 두 침묵 버그 — userinfo 대소문자 소실과 cross-scheme 기본 포트 오삭제.
用 Codex 或 Claude 帮你安装 复制这段 Prompt,粘贴到 Codex、Claude 或其他助手里,让它检查 Skill 页面并帮你完成安装。
基于 SOC 职业分类
| name | urlparse-netloc-reconstruction-traps |
| description | urllib.parse.urlparse로 URL 파싱 후 netloc 재조합 시 발생하는 두 침묵 버그 — userinfo 대소문자 소실과 cross-scheme 기본 포트 오삭제. |
| version | 1.0.0 |
| task_types | ["coding","bugfix"] |
| triggers | [{"pattern":"URL 정규화, netloc 재조합, scheme/host 소문자화, 기본 포트 제거, urlparse, urlunparse"}] |
urllib.parse.urlparse 결과를 수정 후 urlunparse로 재조합할 때 발생하는 두 가지 침묵 버그.
두 버그 모두 예외를 발생시키지 않아 테스트 없이는 발견하기 어렵다.
parsed = urlparse("https://User:Pass@EXAMPLE.COM/path")
normalized = urlunparse((scheme, parsed.netloc.lower(), path, ...))
# → "https://user:pass@example.com/path" ← username/password 소문자화됨
RFC 3986 §3.2.1: userinfo는 case-sensitive. 소문자화 시 인증 실패 가능.
parsed.netloc은 userinfo@host:port 전체 raw 문자열이므로 .lower()가 모두 적용된다.
parsed.hostname(이미 소문자 반환)을 쓰고 netloc을 컴포넌트 단위로 수동 재조합:
host: str = parsed.hostname or "" # 이미 소문자
port: int | None = parsed.port
if parsed.username is not None:
userinfo = parsed.username # 원본 대소문자 보존
if parsed.password is not None:
userinfo = f"{userinfo}:{parsed.password}"
netloc = f"{userinfo}@{host}"
else:
netloc = host
if port is not None:
netloc = f"{netloc}:{port}"
def strip_default_port(scheme, port):
if port in (80, 443): # ← 버그: 스킴을 고려하지 않음
return None
return port
strip_default_port("http", 443) # → None ← 잘못된 삭제
strip_default_port("https", 80) # → None ← 잘못된 삭제
http://example.com:443에서 포트가 제거되어 전혀 다른 서비스를 가리키게 된다.
"80 또는 443이면 기본 포트"라는 직관. 실제로 포트는 스킴과 쌍으로만 기본값이다.
스킴별 기본 포트 딕셔너리와 대조:
_DEFAULT_PORTS: dict[str, int] = {"http": 80, "https": 443}
if port is not None and _DEFAULT_PORTS.get(scheme) == port:
port = None
from toolkit.urlnorm import normalize_url
# 트랩 1: userinfo 보존
assert normalize_url("https://User:Pass@EXAMPLE.COM/") == "https://User:Pass@example.com"
# 트랩 2: cross-scheme 포트 보존
assert normalize_url("http://example.com:443/path") == "http://example.com:443/path"
assert normalize_url("https://example.com:80/path") == "https://example.com:80/path"
# 정상 기본 포트 삭제
assert normalize_url("http://example.com:80/path") == "http://example.com/path"
assert normalize_url("https://example.com:443/path") == "https://example.com/path"
같은 트랩이 발생하는 상황:
BSVibe verification runs in a fresh /work clone of main for EVERY turn — files from failed prior turns are gone. Commit within the CURRENT turn or they vanish.
When you depend on a 3rd-party GitHub-hosted data artifact (CSV/JSON/schema/config) via `try: fetch new; except: use HARDCODED_DEFAULT_URL`, the "safety fallback" masks upstream file renames. Symptom — after upstream restructures naming, the auto-discovery quietly returns nothing (regex mismatch = empty result), the code falls through to the frozen default URL, that URL 404s, and the caller catches the exception and degrades to `whitelist=set()` / empty result / None — silently. No hard error, but every downstream measurement built on that data reshapes. bloasis PR59 caught this after 4 weeks of `whitelist=0` had already re-run the mention extractor with distorted sample sizes across per-bucket tables.
A CLI tool (claude/gh/aws/codex…) invoked by a launchd/systemd daemon fails auth with 401 while the identical command works in your interactive shell — because the daemon's security session can't read the OS keychain and silently falls back to a stale on-disk credential. Diagnose from the daemon context, not your shell.
After `pnpm install` in a git worktree, vitest/jest can discover a test file from a SECOND path — pnpm hardlinks the whole project (test/ included) into `~/Library/pnpm/store/v<n>/projects/<hash>/`, and the glob picks up that out-of-root mirror where the `@/` alias / vite root doesn't apply → bogus "Cannot find package '@/...'" resolution errors. Clearing the stale store-project mirror fixes it.
React/JS diff-render libraries (@git-diff-view/react, react-diff-view, diff2html) that take a `hunks`/diff-string input silently render NOTHING when fed a bare `@@ … @@` hunk — they need the full `diff --git` / `
When a host can't reach one service (e.g. github.com) but reaches others, do NOT assume a remote block / firewall / IP ban. The error TYPE discriminates: an immediate "Can't assign requested address" (EADDRNOTAVAIL) is a LOCAL socket failure (the packet never left), almost always ephemeral-port / TIME_WAIT exhaustion — not a remote block (which times out or RSTs). Check TIME_WAIT count vs the ephemeral port range first.