关于 sast-pathtraversal
用于代码库安全评估的路径遍历检测技能,面向检查文件访问逻辑的开发者。它先查找动态路径文件操作,再分批追踪用户输入并检查路径约束,最后汇总结果。
主要功能
- 01查找动态路径的文件读取与服务操作
- 02按批次追踪用户输入来源
- 03检查规范化、目录限制及文件名白名单
- 04合并验证结果并跳过无候选项分析
适用场景
- 01检查下载接口是否允许越界读取文件
- 02审查文件包含与文件服务逻辑
- 03检查 ZIP 或 TAR 解压路径是否受限
安装
复制下面的命令,在终端中运行。
npx skills add https:/ / github.com/ utkusen/ sast-skills --skill sast-pathtraversal安装后在 Claude Code 中输入/sast-pathtraversal确认已加载。
详细介绍
路径遍历检测
你正在对代码库进行有针对性的安全评估,以查找路径遍历漏洞。此技能采用由子 Agent 执行的三阶段方法:侦察(查找使用动态路径的文件加载接收点)、分批验证(追踪用户输入,并以每批 3 个的方式并行检查缓解措施),以及合并(将各批结果整合为一份报告)。
前提条件:sast/architecture.md 必须存在。如果不存在,请先运行分析技能。
什么是路径遍历
路径遍历(也称为目录遍历)是指将用户提供的输入加入文件路径,并用该路径从文件系统读取、写入或提供文件,却未能将最终路径限制在预期的基础目录内。攻击者可以提供 ../ 之类的序列或其编码形式(%2e%2e%2f、..%2f、%2e%2e/),以逃出预期目录并访问任意文件,例如 /etc/passwd、应用程序源代码、凭据或私钥。
核心模式:未经验证的用户输入到达文件系统操作,且未验证解析后的路径是否仍位于预期基础目录内。
什么情况属于路径遍历
- 直接从基础目录提供用户请求的文件名,未先规范化并检查最终路径:
open(os.path.join(BASE_DIR, user_filename)) - 从 URL 参数构造文件路径,并将其传递给文件读取函数:
fs.readFile(path.join(__dirname, req.query.file), ...) - 由用户输入驱动的模板渲染或 include 指令:
include($_GET['page'] . '.php') - 存档解压(
ZipFile、tarfile、zipslip),将条目名称用作输出路径,却未移除其中的../组成部分 - 将未经清理的用户控制路径用于
send_file()/send_from_directory()/res.sendFile() - 读取路径来源于用户控制的数据库值,且该值存储时未经过清理的文件
什么情况不属于路径遍历
不要将以下情况标记为路径遍历:
- SSRF:根据用户输入获取远程 URL——这是服务器端请求伪造,属于另一类问题
- 通过文件写入实现 RCE:将攻击者控制的内容写入任意路径——这与路径遍历相关,但影响类别不同(应标记为 RCE 或文件上传)
- 静态文件服务:提供的文件路径完全硬编码,不受用户影响
- 安全的路径拼接后接 realpath 和前缀检查:代码计算
realpath(),并验证其以预期的基础目录开头 - 拼接前调用 basename():只使用文件名部分会移除路径遍历序列(但请注意,这只能阻止选择目录,并不只是阻止路径遍历)
可防止路径遍历的模式
当你看到以下缓解措施在文件操作之前应用时,代码通常不存在漏洞:
1. realpath / resolve 后进行基础目录前缀检查(最稳健的修复方法)
# Python
import os
BASE = '/var/www/files'
safe_path = os.path.realpath(os.path.join(BASE, user_input))
if not safe_path.startswith(BASE + os.sep):
raise PermissionError("Path escape detected")
with open(safe_path) as f:
...
// Node.js
const BASE = path.resolve('/var/www/files');
const resolved = path.resolve(BASE, req.query.file);
if (!resolved.startsWith(BASE + path.sep)) {
return res.status(403).send('Forbidden');
}
fs.readFile(resolved, ...);
// Java
Path base = Paths.get("/var/www/files").toRealPath();
Path resolved = base.resolve(userInput).normalize();
if (!resolved.startsWith(base)) {
throw new SecurityException("Path escape");
}
Files.readAllBytes(resolved);
2. 使用 basename() / path.basename() 移除目录组成部分
# Python — strips all directory parts, only the filename remains
filename = os.path.basename(user_input)
with open(os.path.join(BASE, filename)) as f:
...
// PHP
$filename = basename($_GET['file']);
readfile('/var/www/uploads/' . $filename);
3. 允许的文件名或扩展名白名单
ALLOWED = {'report.pdf', 'manual.txt', 'logo.png'}
if user_input not in ALLOWED:
abort(400)
with open(os.path.join(BASE, user_input)) as f:
...
4. 框架提供的安全文件服务
# Flask — send_from_directory validates the path stays within the directory
return send_from_directory('/var/www/files', filename)
# Django — FileResponse with a path that was never user-controlled
易受攻击与安全示例
Python — Flask
# VULNERABLE: user-controlled filename joined without realpath check
@app.route('/download')
def download():
filename = request.args.get('file')
filepath = os.path.join('/var/www/files', filename)
return send_file(filepath)
# SECURE: resolve and verify the path stays within the base directory
@app.route('/download')
def download():
filename = request.args.get('file')
base = os.path.realpath('/var/www/files')
filepath = os.path.realpath(os.path.join(base, filename))
if not filepath.startswith(base + os.sep):
abort(403)
return send_file(filepath)
Python — FastAPI
# VULNERABLE: path parameter used directly in file read
@app.get('/file/{name}')
async def get_file(name: str):
return FileResponse(f'/app/static/{name}')
# SECURE: basename strips traversal sequences
@app.get('/file/{name}')
async def get_file(name: str):
safe_name = os.path.basename(name)
return FileResponse(os.path.join('/app/static', safe_name))
Node.js — Express
// VULNERABLE: req.query.file used directly in readFile
app.get('/file', (req, res) => {
const filePath = path.join(__dirname, 'uploads', req.query.file);
fs.readFile(filePath, (err, data) => res.send(data));
});
// SECURE: resolve and check prefix
app.get('/file', (req, res) => {
const base = path.resolve(__dirname, 'uploads');
const filePath = path.resolve(base, req.query.file);
if (!filePath.startsWith(base + path.sep)) {
return res.status(403).send('Forbidden');
}
fs.readFile(filePath, (err, data) => res.send(data));
});
PHP
// VULNERABLE: direct inclusion of user input
<?php
$page = $_GET['page'];
include($page . '.php');
// VULNERABLE: readfile with unsanitized path
$file = $_GET['file'];
readfile('/var/www/uploads/' . $file);
// SECURE: basename strips directory components
$file = basename($_GET['file']);
readfile('/var/www/uploads/' . $file);
// SECURE: realpath + prefix check
$base = realpath('/var/www/uploads');
$path = realpath($base . '/' . $_GET['file']);
if ($path === false || strpos($path, $base . DIRECTORY_SEPARATOR) !== 0) {
http_response_code(403);
exit;
}
readfile($path);
Ruby on Rails
# VULNERABLE: params[:file] used directly in file read
def show
file_path = Rails.root.join('public', 'reports', params[:file])
send_file file_path
end
# SECURE: basename only
def show
safe_name = File.basename(params[:file])
send_file Rails.root.join('public', 'reports', safe_name)
end
Java — Spring
// VULNERABLE: path variable used directly to read file
@GetMapping("/file/{name}")
public ResponseEntity<Resource> getFile(@PathVariable String name) throws IOException {
Path filePath = Paths.get("/var/www/files").resolve(name);
Resource resource = new UrlResource(filePath.toUri());
return ResponseEntity.ok(resource);
}
// SECURE: normalize and check prefix
@GetMapping("/file/{name}")
public ResponseEntity<Resource> getFile(@PathVariable String name) throws IOException {
Path base = Paths.get("/var/www/files").toRealPath();
Path resolved = base.resolve(name).normalize();
if (!resolved.startsWith(base)) {
return ResponseEntity.status(403).build();
}
Resource resource = new UrlResource(resolved.toUri());
return ResponseEntity.ok(resource);
}
Go
// VULNERABLE: query param joined directly to base directory
func fileHandler(w http.ResponseWriter, r *http.Request) {
name := r.URL.Query().Get("file")
http.ServeFile(w, r, filepath.Join("/var/www/files", name))
}
// SECURE: filepath.Clean + prefix check
func fileHandler(w http.ResponseWriter, r *http.Request) {
name := r.URL.Query().Get("file")
base := "/var/www/files"
clean := filepath.Join(base, filepath.Clean("/"+name))
if !strings.HasPrefix(clean, base+string(os.PathSeparator)) {
http.Error(w, "Forbidden", http.StatusForbidden)
return
}
http.ServeFile(w, r, clean)
}
存档解压(ZipSlip)
# VULNERABLE: ZipSlip — zip entry names can contain ../
import zipfile
with zipfile.ZipFile(user_zip) as zf:
zf.extractall('/var/www/uploads')
# SECURE: validate each entry path stays within the target directory
import zipfile, os
base = os.path.realpath('/var/www/uploads')
with zipfile.ZipFile(user_zip) as zf:
for member in zf.namelist():
target = os.path.realpath(os.path.join(base, member))
if not target.startswith(base + os.sep):
raise ValueError(f"ZipSlip detected: {member}")
zf.extractall(base)
执行
此技能使用子 Agent 分三个阶段运行。将 sast/architecture.md 的内容作为上下文传递给所有子 Agent。
阶段 1:查找使用动态路径的文件加载接收点
启动一个子 Agent,并向其提供以下指示:
目标:找出代码库中所有使用动态构造路径打开、读取、提供或解压文件的位置——也就是说,路径(或其组成部分)存储在变量中,而不是完全硬编码的字符串。将结果写入
sast/pathtraversal-recon.md。背景:你会获得项目的架构摘要。利用它了解技术栈、Web 框架、文件提供模式,以及任何文件上传或下载功能。
搜索目标——包含动态路径组件的文件加载接收点:
查找路径参数中包含变量的任何文件读取/提供函数调用(无论变量来自何处)。本阶段不追踪用户输入——那是第 2 阶段的任务。只需找出所有动态文件访问模式。
- 路径包含变量的直接文件打开/读取调用:
- Python:
open(var)、open(os.path.join(..., var))、pathlib.Path(var).read_text()、pathlib.Path(var).read_bytes()- Node.js:
fs.readFile(var, ...)、fs.readFileSync(var)、fs.createReadStream(var)- PHP:
file_get_contents(var)、fopen(var, ...)、readfile(var)、include(var)、require(var)、include_once(var)、require_once(var)- Ruby:
File.read(var)、File.open(var)、IO.read(var)、IO.binread(var)- Java:
new FileInputStream(var)、new File(var)、Files.readAllBytes(Paths.get(var))、Files.newInputStream(path)- Go:
os.Open(var)、os.ReadFile(var)、ioutil.ReadFile(var)、os.OpenFile(var, ...)- C#:
File.ReadAllText(var)、File.ReadAllBytes(var)、new FileStream(var, ...)、System.IO.File.Open(var, ...)- 路径包含变量的框架文件提供调用:
- Flask:
send_file(var)、send_from_directory(base, var)- FastAPI/Starlette:
FileResponse(var)- Django:
FileResponse(open(var, 'rb'))、对已打开文件使用StreamingHttpResponse- Express:
res.sendFile(var)、res.download(var)、使用动态根目录的express.static- Spring:
new UrlResource(path.toUri())、ResourceLoader.getResource(var)、ClassPathResource(var)- Rails:
send_file var、render file: var- Go:
http.ServeFile(w, r, var)、http.ServeContent(w, r, var, ...)- 至少有一个组件为变量的路径构造函数:
os.path.join(BASE, var)、os.path.join(var1, var2)path.join(__dirname, var)、path.resolve(base, var)Paths.get(base).resolve(var)filepath.Join(base, var)- 用作路径的字符串拼接:
BASE + var、f"{BASE}/{var}"、`${base}/${var}`- 使用用户提供的归档文件进行解压(ZipSlip 模式):
- Python:
zipfile.ZipFile.extractall(...)、tarfile.TarFile.extractall(...)- Java:将
ZipEntry.getName()用作输出路径- Node.js:
unzipper、adm-zip、node-tar的解压调用- Go:未验证条目名称就进行解压的
archive/zip或archive/tar跳过的内容(这些没有动态路径组件——不要标记):
- 完全硬编码的字符串字面量文件路径,不含变量部分
- 完全从服务器端配置/环境变量派生,且不含用户提供组件的路径(例如,
open(settings.LOG_FILE),其中LOG_FILE是配置值)- 根目录硬编码的框架内置静态文件中间件(例如,根目录固定的
express.static('public'))输出格式——写入
sast/pathtraversal-recon.md:# 路径遍历侦察:[项目名称] ## 摘要 发现 [N] 个使用动态构造路径访问文件的位置。 ## 文件加载接收点 ### 1. [描述性名称——例如,“下载端点中的动态 readFile”] - **文件**:`path/to/file.ext`(第 X-Y 行) - **函数/端点**:[函数名称或路由] - **接收点**:[open / fs.readFile / send_file / include / FileInputStream / 等] - **路径构造**:[os.path.join / path.join / 字符串拼接 / f-string / 等] - **动态变量**:`var_name` —— [简要说明其可能代表什么,例如,“看起来是来自请求的文件名”或“来源未知”] - **代码片段**:[路径构造 + 文件操作调用]
[每个接收点重复以上内容]
阶段 1 后:检查候选项,然后再继续
阶段 1 完成后,读取 sast/pathtraversal-recon.md。如果侦察结果发现零个文件加载接收点(摘要显示“Found 0”,或“File-Loading Sinks”部分为空或不存在),则完全跳过阶段 2 和阶段 3。请改为将以下内容写入 sast/pathtraversal-results.md,然后删除 sast/pathtraversal-recon.md,并停止:
# Path Traversal Analysis Results
No vulnerabilities found.
只有在阶段 1 发现至少一个文件加载接收点时,才继续阶段 2。
阶段 2:验证——追踪污点并检查缓解措施(分批处理)
阶段 1 完成后,读取 sast/pathtraversal-recon.md,并将文件加载接收点拆分为每批最多 3 个接收点。并行启动每批一个子 Agent。每个子 Agent 只分析分配给自己的接收点,并将结果写入各自的批次文件。
分批流程(由你作为编排者执行,而非子 Agent):
- 读取
sast/pathtraversal-recon.md,并统计编号的接收点部分(### 1.、### 2. 等)。 - 将它们分成每批最多 3 个。例如,8 个接收点 → 3 批(1-3、4-6、7-8)。
- 从侦察文件中提取每批对应接收点部分的完整文本。
- 并行启动所有批次子 Agent,每个子 Agent 只提供分配给它的接收点。
- 每个子 Agent 将结果写入
sast/pathtraversal-batch-N.md,其中 N 为从 1 开始的批次编号。 - 从
sast/architecture.md中确定项目的主要语言/框架,并从上文的“易受攻击与安全示例”部分中仅选择匹配的示例(并将“防止路径遍历的模式”/“不属于路径遍历的情况”作为参考)。例如,如果项目使用 Node.js/Express,则包含“Node.js — Express”代码块。在下方标注为[TECH-STACK EXAMPLES]的位置,将这些选定的示例纳入每个子 Agent 的指令中。
向每个批次子 Agent 提供以下指令(替换为该批次对应的值):
目标:对于每个指定的文件加载汇,判断用户提供的值是否会到达动态路径变量,以及是否有任何缓解措施可防止路径逃逸出预期的基础目录。我们的目标是查找路径遍历漏洞。将结果写入
sast/pathtraversal-batch-[N].md。分配给你的汇(来自侦察阶段):
[在此粘贴分配给你的汇部分的完整文本,并保留原始编号]
背景:你将获得项目的架构摘要。利用它了解请求入口、中间件以及数据在应用中的流向。
路径遍历参考——需要检查的内容:
用户提供的输入被纳入文件系统路径,却未将解析后的路径限制在预期的基础目录内(包括 ZipSlip 风格的归档解压)。不要将 SSRF、纯 RCE/文件写入类问题、完全硬编码的路径,或安全的
realpath/resolve+ 基础路径前缀检查判定为路径遍历(如有需要,请参阅主技能文档中的“路径遍历不包括什么”和“可防止路径遍历的模式”章节)。对于每个汇,执行两项检查:
检查 A——路径变量是否受用户控制?
向上追踪动态变量的来源:
- 直接用户输入——变量直接从请求来源赋值:
- HTTP 查询参数:
request.GET.get(...)、req.query.x、params[:x]、$_GET['x']、c.Query("x")- 路径参数:
request.path_params['name']、req.params.name、params[:name]、c.Param("name")- 请求正文/表单字段:
request.POST.get(...)、req.body.x、params[:x]、$_POST['x']- HTTP 标头:
request.headers.get(...)、req.headers['x']- Cookie:
request.COOKIES.get(...)、req.cookies.x- 多部分文件名:
file.filename、req.file.originalname、$_FILES['file']['name']- 间接用户输入——变量经由转换、中间赋值或函数调用从用户输入派生。追踪完整链路:
- 变量由辅助函数赋值 → 检查该函数的源代码
- 变量作为参数传入 → 检查所有调用点
- 变量从数据库值读取,而该值最初由用户输入存储
- 服务端/硬编码值——变量来自配置、环境变量、硬编码常量,或不受用户影响的服务端逻辑——此汇无法通过路径遍历利用。
检查 B——是否通过有效缓解措施防止路径逃逸?
即使用户输入到达了路径,以下缓解措施也能防止路径遍历。检查它们是否在文件操作之前正确应用:
realpath/os.path.realpath()+ 基础目录前缀检查:解析符号链接和..序列,然后验证结果是否以预期基础目录开头。这是最可靠的修复方式。
os.path.realpath(path).startswith(BASE + os.sep)——有效 ✓os.path.realpath(path).startswith(BASE)不带尾部分隔符——可能可绕过,因为 BASE 可能是另一个目录名的前缀 ✗path.resolve()+startsWith(base + sep)(Node.js)——有效 ✓Paths.get(...).normalize()+startsWith(base)(Java)——仅当base也通过toRealPath()获取时才有效 ✓filepath.Clean()+strings.HasPrefix(clean, base+sep)(Go)——有效 ✓basename()/path.basename()/File.basename()——移除所有目录部分;可有效防止路径遍历,但会阻止访问子目录- 允许的文件名白名单——如果白名单严格,并且输入在使用前会与白名单进行比较,则完全有效
- 框架的
send_from_directory(Flask)——Flask 的send_from_directory内部会调用safe_join,在发生路径遍历时抛出错误;有效 ✓不足以防止问题的缓解措施:
- 使用简单的
replace('../', '')移除../——可通过....//或 URL 编码绕过- 检查输入是否以
/开头——无法防止相对路径遍历- 单独使用
os.path.join,不配合realpath——os.path.join('/base', '../etc/passwd')仍会生成/etc/passwd- 对输入只进行一次 URL 解码——攻击者可进行双重编码:
%252e%252e%252f→%2e%2e%2f→../- 类型验证(例如检查扩展名是否为
../../etc/passwd%00.pdf(空字节),或构造路径使其以正确的扩展名结尾适用于本项目技术栈的漏洞与安全示例:
[TECH-STACK EXAMPLES]
分类:
- 存在漏洞:明确证实用户输入到达路径变量,且在文件操作前未实施有效缓解措施。
- 可能存在漏洞:用户输入可能到达路径变量(间接流),或存在薄弱/不完整的缓解措施(例如
replace('../', ''),前缀检查不含尾部分隔符)。- 不存在漏洞:路径变量仅由服务端控制,或已正确应用有效缓解措施(
realpath+ 前缀检查、basename、严格白名单、安全的框架辅助函数)。- 需要人工审查:无法有把握地确定变量来源(经过不透明辅助函数或复杂的条件流程),或缓解逻辑不常见,难以通过静态分析评估。
输出格式——写入
sast/pathtraversal-batch-[N].md:# Path Traversal Batch [N] Results ## Findings ### [VULNERABLE] Descriptive name - **File**: `path/to/file.ext` (lines X-Y) - **Endpoint / function**: [route or function name] - **Issue**: [e.g., "HTTP query param `file` flows directly into os.path.join without realpath check"] - **Taint trace**: [Step-by-step from entry point to the file operation] - **Missing mitigation**: [What check is absent] - **Impact**: Read arbitrary files accessible to the process user, including `/etc/passwd`, application config, source code, private keys. - **Remediation**: [Specific fix] - **Dynamic Test**:[curl command or payload to confirm; show traversal and encoded variants as appropriate]
### [LIKELY VULNERABLE] Descriptive name - **File**: `path/to/file.ext` (lines X-Y) - **Endpoint / function**: [route or function name] - **Issue**: [e.g., "Variable likely sourced from user input via helper" or "Weak mitigation: strips ../ but bypassable with ....//"] - **Taint trace**: [Best-effort trace with the uncertain step identified] - **Concern**: [Why it remains a risk despite partial mitigation] - **Remediation**: [Apply realpath + prefix check or basename before joining] - **Dynamic Test**:[payloads to attempt bypass of the partial mitigation]
### [NOT VULNERABLE] Descriptive name - **File**: `path/to/file.ext` (lines X-Y) - **Endpoint / function**: [route or function name] - **Reason**: [e.g., "Path is derived entirely from server-side config" or "os.path.realpath() + prefix check correctly applied"] ### [NEEDS MANUAL REVIEW] Descriptive name - **File**: `path/to/file.ext` (lines X-Y) - **Endpoint / function**: [route or function name] - **Uncertainty**: [Why the variable's origin or mitigation could not be determined] - **Suggestion**: [What to trace manually]
阶段 3:合并 — 汇总批次结果
在所有阶段 2 批次子 Agent 完成后,读取每个 sast/pathtraversal-batch-*.md 文件,并将它们合并到单个 sast/pathtraversal-results.md 中。由你(编排者)直接完成此操作——无需子 Agent。
合并流程:
- 读取所有
sast/pathtraversal-batch-1.md、sast/pathtraversal-batch-2.md……文件。 - 收集每个批次文件中的所有发现并合并为一个列表,同时保留原始分类和所有详细字段。
- 汇总所有批次中的数量,供执行摘要使用。
- 使用以下格式将合并后的报告写入
sast/pathtraversal-results.md:
# Path Traversal Analysis Results: [Project Name]
## Executive Summary
- Sinks analyzed: [total across all batches]
- Vulnerable: [N]
- Likely Vulnerable: [N]
- Not Vulnerable: [N]
- Needs Manual Review: [N]
## Findings
[All findings from all batches, grouped by classification:
VULNERABLE first, then LIKELY VULNERABLE, then NEEDS MANUAL REVIEW, then NOT VULNERABLE.
Preserve every field from the batch results exactly as written.]
- 写入
sast/pathtraversal-results.md后,删除所有中间批次文件(sast/pathtraversal-batch-*.md)。
重要提醒
- 读取
sast/architecture.md,并将其内容作为上下文传递给所有子 Agent。 - 阶段 2 必须在阶段 1 完成后运行——它依赖侦察输出。
- 阶段 3 必须在所有阶段 2 批次完成后运行——它依赖所有批次输出。
- 每个子 Agent 处理 3 个 sink。如果总共只有 1-3 个 sink,则使用单个子 Agent。如果有 10 个,则使用 4 个子 Agent(3+3+3+1)。
- 并行启动所有批次子 Agent——不要按顺序运行。
- 每个批次子 Agent 只接收侦察文件中分配给它的 sink 文本,而不是整个侦察文件。这样可以让每个子 Agent 的上下文保持精简且聚焦。
- 阶段 1 纯粹是结构性分析:标记路径包含动态部分的任何文件加载 sink,无论其来源如何。阶段 1 不要尝试追踪用户输入——这是阶段 2 的任务。
- 阶段 2 是污点分析和缓解措施审查:对阶段 1 发现的每个 sink,(a)追踪路径变量的来源;(b)检查是否有有效的缓解措施可防止路径逃出预期目录。
os.path.join和path.join本身不能防止路径遍历——os.path.join('/base', '../etc/passwd')会解析为/etc/passwd。只有realpath加前缀检查才能防止这种情况。- 编码后的路径遍历变体(
%2e%2e%2f、%252e%252e%252f、..%2f、%2e%2e/)会绕过简单的字符串匹配过滤器;只有文件系统级解析(realpath)才能可靠地处理它们。 - Flask 中的
send_from_directory本身是安全的(内部会调用safe_join)——除非用户输入也被用作基目录参数,否则不要将其标记出来。 - 归档提取(ZipSlip)是路径遍历的一种变体:zip/tar 条目名称中可能包含
../序列。对于任何使用条目名称作为输出路径、却未对每个条目进行验证的提取操作,都要标记出来。 - 可能发生二阶路径遍历:从用户输入写入数据库的文件名,之后可能在代码库的其他位置用于读取文件。将从数据库读取的路径值视为可能受污点影响,并追踪其写入位置。
- 如有疑问,将其分类为“需要人工审查”,而不是“未发现漏洞”。在安全评估中,漏报比误报更糟。
- 清理中间文件:写入最终的
sast/pathtraversal-results.md后,删除sast/pathtraversal-recon.md和所有sast/pathtraversal-batch-*.md文件。