T09 · Insecure Skill Coding Practices
- Location
scripts/migrate_swagger_to_openapi.py:119- Finding
Out-of-Project File Modification Through Symbolic-Link Files in the Complete Migrator
- Content
View full analysis
Vulnerability Details
File Location:
scripts/migrate_swagger_to_openapi.py:119-125, 152-172
Vulnerability Type: Symbolic-link following and insufficient path-boundary validation
Risk Level: MediumVulnerable Code
python def find_java_files(self) -> List[Path]: """查找项目中所有的 Java 文件""" java_files = [] for root, dirs, files in os.walk(self.project_path): # 排除常见的非源代码目录 dirs[:] = [d for d in dirs if d not in ['.git', '.idea', 'target', 'build', 'node_modules']] for file in files: if file.endswith('.java'): java_files.append(Path(root) / file) return java_filespython def process_file(self, file_path: Path) -> bool: """处理单个文件""" try: with open(file_path, 'r', encoding='utf-8') as f: content = f.read() original_content = content # 替换注解 content, anno_count = self.replace_annotations(content) # 替换 import content, import_count = self.replace_imports(content) self.stats['files_processed'] += 1 if content != original_content: self.stats['files_modified'] += 1 self.stats['annotations_replaced'] += anno_count self.stats['imports_replaced'] += import_count if not self.dry_run: with open(file_path, 'w', encoding='utf-8') as f: f.write(content)Technical Analysis
File discovery accepts every entry with a
.javasuffix without checking whether it is a symbolic link. The subsequent calls toopen()follow symbolic links by default. The script also does not resolve each candidate and confirm that its final target remains under the resolved project directory.Consequently, a symbolic-link file located inside the selected project can refer to a writable Java file outside that project. If the external target contains an annotation or import matching a migration rule, the script reads, transforms, trun ...[truncated 1326 chars]
- Remediation
View remediation
Remediation Suggestions
- Reject symbolic-link candidates before reading them:
python if file_path.is_symlink(): continue- Resolve every candidate and ensure it remains within the project boundary:
python project_root = self.project_path.resolve() resolved_file = file_path.resolve(strict=True) if not resolved_file.is_relative_to(project_root): raise ValueError(f"File escapes project root: {file_path}")- Revalidate the path immediately before writing because a file can be replaced with a symbolic link after discovery.
- Open files using operating-system protections such as
os.open(..., os.O_NOFOLLOW)where supported, then operate through the returned descriptor. - Write through a securely created temporary file in the same directory and atomically replace the intended directory entry. Ensure the destination is revalidated before replacement.
- Fail closed and report the unsafe file instead of silently processing it.
- Add regression tests covering file symlinks, broken symlinks, links escaping the project, and link-swap attempts.
