T09 · Insecure Skill Coding Practices
Warning
- Location
- scripts/bailian_faiss_kb.py:291
- Finding
- Symlink-Based Arbitrary File Read and External Disclosure<![CDATA[ ## Vulnerability Details **File Location**: `scripts/bailian_faiss_kb.py:291-293, 347, 472-476, 501-504, 590-607` **Vulnerability Type**: Symlink traversal leading to unauthorized file disclosure **Risk Level**: Medium ### Vulnerable Code ```python def validate_doc_dir(doc_dir: Path, kb_root: Path, kb_name: str) -> Path: expected_parent = kb_dir(kb_root, kb_name) doc_dir = doc_dir.resolve() if doc_dir.parent != expected_parent.resolve(): raise KBError(f"Document directory must be directly under {expected_parent}") return doc_dir ``` ```python def read_markdown_file(path: Path) -> str: return normalize_text(path.read_text(encoding="utf-8", errors="ignore")) ``` ```python for chunk_path in sorted(chunks_dir.glob("chunk-*.md")): chunk_id = parse_chunk_id(chunk_path) text = read_markdown_file(chunk_path) ``` ```python for question_path in sorted(t2q_dir.glob("*.md")): chunk_id, q_id = parse_t2q_name(question_path) if chunk_id not in chunk_lookup: raise KBError(f"T2Q file {question_path.name} references missing chunk {chunk_id}") text = collapse_text(question_path.read_text(encoding="utf-8", errors="ignore")) ``` ```python def embed_texts(texts: list[str], batch_size: int = 10) -> list[list[float]]: if not texts: return [] requests = load_requests() api_key = load_api_key() outputs: list[list[float]] = [] for start in range(0, len(texts), batch_size): batch = texts[start : start + batch_size] payload = { "model": EMBEDDING_MODEL, "input": batch, "dimensions": EMBEDDING_DIMENSIONS, "encoding_format": "float", } response = requests.post( EMBEDDING_URL, headers={"Authorization": f"Bearer {api_key}", "Content-Type": "application/json"}, json=payload, timeout=120, ) ``` ### Technical Analysis The implementation resolves and validates the ...[truncated 3399 chars]
- Remediation
- <![CDATA[ ## Remediation Suggestions 1. **Reject symbolic links for every consumed asset** - Check `path.is_symlink()` before reading source text, summaries, chunks, and T2Q files. - Reject symbolic-link document directories and symbolic-link `chunks/` or `t2q/` directories. 2. **Enforce resolved-path containment** - Resolve every file with `resolve(strict=True)`. - Require the resolved file to remain under the resolved document directory using `Path.is_relative_to()` on Python 3.10 or later. - Apply this validation to source text files, `summary.txt`, every chunk, and every T2Q file. 3. **Require regular files** - Verify that each resolved input is a regular file rather than a device, FIFO, socket, or directory. - Do not rely solely on filename patterns or `Path.is_file()`, because checks followed by ordinary opens may still be vulnerable to replacement races. 4. **Mitigate time-of-check/time-of-use races** - On supported platforms, open files using `os.open()` with `O_NOFOLLOW`, then read through the resulting file descriptor. - Use `fstat()` to confirm that the opened object is a regular file. - Avoid validating a path and later reopening it by name when untrusted users can modify the directory concurrently. 5. **Restrict filesystem permissions** - Run the Skill under a dedicated, minimally privileged service account. - Ensure untrusted users cannot modify knowledge-base directories processed by trusted indexing jobs. - Prevent the runtime account from reading unrelated credential and configuration directories unless operationally required. 6. **Reduce disclosure persistence** - Validate all inputs before beginning embedding or writing index artifacts. - If validation fails, abort without transmitting content or partially updating `vectors.jsonl`, FAISS, BM25, or manifest files. - Document that semantic indexing and reranking transmit document or query content externally, and require keyword-only mode where ex ...[truncated 40 chars]
