CVE-2026-14420
Overview
Background
- `ArrayLengthFromUniform`
- a Dawn/Tint GL-backend mechanism that supplies
arrayLength()for runtime-sized storage buffers by loading each SSBO’s bound byte-size from a UBO slot (enabled byToggle::GLUseArrayLengthFromUniform/gl_use_array_length_from_uniform) instead of a native GLSL length query. - `bindpoint_to_size_index`
- the Tint option map keyed by a
{group, binding}bindpoint that names the UBO slot holding that buffer’s size; if two bindpoints resolve to the same key, one entry silently overwrites the other andarrayLength()returns the wrong buffer’s size. - `SingleShaderStage` / `visibility`
- each bind group layout entry carries a
visibilitymask (Vertex/Fragment/Compute); only stages present in the mask should contribute to that binding’s codegen for the stage currently being compiled. - Robustness clamping
- Dawn’s Robustness pass rewrites a storage-buffer access
B[idx]asB[min(idx, arrayLength(&B)-1)], so the clamp is only as tight as thearrayLength()value it trusts.
Root Cause Analysis
GenerateArrayLengthFromuniformData() in ShaderModuleGL.cpp walked every buffer binding in the PipelineLayout and, for each binding whose type was Storage or kInternalStorageBufferBinding, assigned it an ArrayLengthFromUniform size-index entry — with no check that the binding was visible to the stage being compiled. A storage buffer visible only to another stage (e.g. a Fragment-only buffer while the Compute stage is compiled) therefore still received an entry, and it kept its original pre-remap {group, binding} key while a genuinely stage-visible buffer received its post-remap key. The regression test makes these coincide: buffer A (Fragment-only, group 0, @binding 1) keeps its pre-remap key {0, 1}, and buffer B (Compute, group 1, remapped to glIndex_B == 1) gets post-remap key {0, 1}, so A’s stale entry aliases B’s slot in bindpoint_to_size_index. The visible buffer B then loaded A’s much larger bound size as its own arrayLength(), breaking the invariant that a buffer’s reported length matches its own bound sub-range. Because Robustness clamps indices against that inflated length, a clamped access on B could reach far past its 64-byte bound range, yielding OOB read and write in the GPU process.
The fix threads SingleShaderStage stage into the function and continues past any binding whose visibility mask lacks StageBit(stage), so a stage-hidden buffer no longer contributes an entry that can collide.
{0, glIndex_B}), and it lands on the hidden buffer’s stale pre-remap key (A’s {0, 1}) — so the hidden buffer’s unfiltered entry hijacks the visible buffer’s arrayLength(), not the reverse.Attack Path
- Craft the aliasing layout
Build a pipeline layout with a large storage buffer A visible only to
Fragment(group 0,@binding1) and a storage buffer B visible toCompute(group 1,@binding0), so A’s pre-remap key{0, 1}equals B’s post-remap key{0, glIndex_B = 1}. - Bind the victim to a narrow sub-range
Bind B to only a 64-byte sub-range of a 16 KiB allocation (true
arrayLength()= 16) while binding A at its full 1 MiB size, so the two reported lengths differ sharply. - Trigger the collision at compile time
Compile the compute pipeline;
GenerateArrayLengthFromuniformData()emits a size-index entry for the Fragment-only buffer A, and after remapping A’s key overwrites B’s entry inbindpoint_to_size_index. - Inflate the clamp bound
At dispatch,
arrayLength(&B)returns A’s large bound size instead of 16, so Robustness clamping no longer constrains B’s index to its 64-byte range. - Perform the OOB access
Write
B[1000](byte 4000); the clamp passes it through, landing past B’s bound sub-range within the larger allocation or adjacent memory — OOB read/write in the GPU process. Chaining this into information disclosure or a sandbox escape is not shown by the patch(inferred).
Impact Assessment
Toggle::GLUseArrayLengthFromUniform active, WebGPU content that arranges the described key aliasing obtains out-of-bounds read and write in the GPU-process address space, bounded by the inflated arrayLength() and surrounding allocations. The advisory classifies this as a critical out-of-bounds read and write in Dawn; it is not recorded as exploited in the wild (ITW: False) or in CISA KEV (KEV: False). Whether the GPU-process corruption can be escalated to a sandbox escape or broader compromise is not demonstrated by the patch (inferred).Changed Functions
| Function | Change | Notes |
|---|---|---|
ifsrc/dawn/native/opengl/ShaderModuleGL.cpp |
modified | |
OpArrayLengthVisibilityCollisionTestsrc/dawn/tests/end2end/OpArrayLengthTests.cpp |
modified | |
TEST_Psrc/dawn/tests/end2end/OpArrayLengthTests.cpp |
modified |
Files Changed
src/dawn/native/opengl/ShaderModuleGL.cppsrc/dawn/tests/end2end/OpArrayLengthTests.cpp
Audit Directions
- Stage-visibility filtering in per-stage codegenAudit every place Dawn iterates all
PipelineLayoutbindings to emit stage-specific data (size indices, remapper tables) and confirm each filters bybindingInfo.visibility & StageBit(stage)before emitting anything for that stage. - Post-remap key aliasingReview Tint binding-remapper consumers such as
bindpoint_to_size_indexfor keys that can collide once unrelated bindings (kept at pre-remap keys) coexist with remapped ones, so that one entry cannot overwrite another. - `arrayLength()` provenance vs. RobustnessTrace all sources feeding
arrayLength()/bindpoint_to_size_indexacross backends and verify a buffer can never be handed another buffer’s bound size, since Robustness clamping trusts that value as the sole bound.
Patch
From 73591422f504902c597a67beaeb8e22101f030bd Mon Sep 17 00:00:00 2001 From: Stephen White <[email protected]> Date: Tue, 02 Jun 2026 13:24:30 -0700 Subject: [PATCH] GL: add missing stage visibility check In GenerateArrayLengthFromUniformData(), skip bindings that are not visible to the current stage. Bug: 517031505 Change-Id: Ida25ca9233a6880461f67a177e9d2257c1727094 Reviewed-on: https://dawn-review.googlesource.com/c/dawn/+/313155 Reviewed-by: Geoff Lang <[email protected]> Commit-Queue: Stephen White <[email protected]> --- diff --git a/src/dawn/native/opengl/ShaderModuleGL.cpp b/src/dawn/native/opengl/ShaderModuleGL.cpp index 1a8687b..f5e304a 100644 --- a/src/dawn/native/opengl/ShaderModuleGL.cpp +++ b/src/dawn/native/opengl/ShaderModuleGL.cpp @@ -273,6 +273,7 @@ bool GenerateArrayLengthFromuniformData( const BindingInfoArray& moduleBindingInfo, const PipelineLayout* layout, + SingleShaderStage stage, tint::glsl::writer::ArrayLengthFromUniformOptions& options) { const PipelineLayout::BindingIndexInfo& indexInfo = layout->GetBindingIndexInfo(); @@ -282,6 +283,11 @@ for (BindingIndex binding : bgl->GetBufferIndices()) { const BindingInfo& bindingInfo = bgl->GetBindingInfo(binding); + // Skip bindings that aren't visible to this stage. + if (!(bindingInfo.visibility & StageBit(stage))) { + continue; + } + switch (std::get<BufferBindingInfo>(bindingInfo.bindingLayout).type) { case wgpu::BufferBindingType::Storage: case kInternalStorageBufferBinding: @@ -409,7 +415,7 @@ if (GetDevice()->IsToggleEnabled(Toggle::GLUseArrayLengthFromUniform)) { *needsSSBOLengthUniformBuffer = GenerateArrayLengthFromuniformData( - moduleBindingInfo, layout, req.tintOptions.array_length_from_uniform); + moduleBindingInfo, layout, stage, req.tintOptions.array_length_from_uniform); if (*needsSSBOLengthUniformBuffer) { req.tintOptions.use_array_length_from_uniform = true; req.tintOptions.array_length_from_uniform.ubo_binding = { diff --git a/src/dawn/tests/end2end/OpArrayLengthTests.cpp b/src/dawn/tests/end2end/OpArrayLengthTests.cpp index ea36ca3..a96a58d 100644 --- a/src/dawn/tests/end2end/OpArrayLengthTests.cpp +++ b/src/dawn/tests/end2end/OpArrayLengthTests.cpp @@ -303,6 +303,93 @@ VulkanBackend(), WebGPUBackend()); +// Regression test for stage-visibility filtering in +// GenerateArrayLengthFromuniformData (ShaderModuleGL.cpp). A storage +// buffer in the layout that is *not* visible to the stage being +// compiled should not be given an entry in Tint's +// bindpoint_to_size_index nor in the remapper_data. Otherwise, +// ArrayLengthFromUniform loads the wrong UBO slot for arrayLength(), +// defeating Robustness clamping. +class OpArrayLengthVisibilityCollisionTest : public DawnTest { + protected: + void GetRequiredLimits(const dawn::utils::ComboLimits& supported, + dawn::utils::ComboLimits& required) override { + supported.UnlinkedCopyTo(&required); + } +}; + +TEST_P(OpArrayLengthVisibilityCollisionTest, ComputeWithFragmentOnlyBufferInLayout) { + DAWN_TEST_UNSUPPORTED_IF(GetSupportedLimits().maxStorageBuffersInFragmentStage < 1); + + // Buffer A: large, bound at full size to a FRAGMENT-only storage slot. + constexpr uint32_t kBufASize = 1u << 20; // 1 MiB → arrayLength<u32> = 262144 + wgpu::BufferDescriptor descA; + descA.size = kBufASize; + descA.usage = wgpu::BufferUsage::Storage; + wgpu::Buffer bufA = device.CreateBuffer(&descA); + + // Buffer B: large allocation, but only a 64-byte sub-range will be bound to + // the COMPUTE-visible storage slot. With the collision the shader receives + // A's bound size as B's arrayLength, so the Robustness clamp on B[idx] + // permits writes far past the 64-byte bound range. + constexpr uint32_t kBufBSize = 16384; // 16 KiB underlying allocation + constexpr uint32_t kBufBBound = 64; // 64 bytes bound → arrayLength = 16 + constexpr uint32_t kOobIndex = 1000; // Target B[1000] (byte 4000) – OOB + constexpr uint32_t kOobMarker = 0xDEAD4A11u; + std::vector<uint32_t> initialData(kBufBSize / 4, 0u); + wgpu::Buffer bufB = utils::CreateBufferFromData( + device, initialData.data(), initialData.size() * sizeof(uint32_t), + wgpu::BufferUsage::Storage | wgpu::BufferUsage::CopySrc | wgpu::BufferUsage::CopyDst); + + // PipelineLayoutGL assigns ssboIndex by iterating groups in order, so: + // group 0 (A, FRAGMENT-only) → ssboIndex 0 + // group 1 (B, COMPUTE) → ssboIndex 1 → glIndex_B = 1 + // Choose A's WGSL @binding == glIndex_B (= 1) so that A's pre-remap key + // {0,1} equals B's post-remap key {0, glIndex_B}. + wgpu::BindGroupLayout bglA = utils::MakeBindGroupLayout( + device, {{1, wgpu::ShaderStage::Fragment, wgpu::BufferBindingType::ReadOnlyStorage}}); + wgpu::BindGroupLayout bglB = utils::MakeBindGroupLayout( + device, {{0, wgpu::ShaderStage::Compute, wgpu::BufferBindingType::Storage}}); + + std::string shaderSource = R"( + @group(1) @binding(0) var<storage, read_write> B : array<u32>; + @compute @workgroup_size(1) fn main() { + B[0] = arrayLength(&B); + // Robustness wraps this as B[min(idx, arrayLength(&B)-1)]. + // With the bug arrayLength(&B) is huge, so the write lands at + // index 1000 – past the 64-byte bound range. + B[)" + std::to_string(kOobIndex) + + R"(u] = )" + std::to_string(kOobMarker) + R"(u; + })"; + + wgpu::ComputePipelineDescriptor pipelineDesc; + pipelineDesc.layout = utils::MakePipelineLayout(device, {bglA, bglB}); + pipelineDesc.compute.module = utils::CreateShaderModule(device, shaderSource); + pipelineDesc.compute.entryPoint = "main"; + wgpu::ComputePipeline pipeline = device.CreateComputePipeline(&pipelineDesc); + + wgpu::BindGroup bgA = utils::MakeBindGroup(device, bglA, {{1, bufA, 0, kBufASize}}); + wgpu::BindGroup bgB = utils::MakeBindGroup(device, bglB, {{0, bufB, 0, kBufBBound}}); + + wgpu::CommandEncoder encoder = device.CreateCommandEncoder(); + wgpu::ComputePassEncoder pass = encoder.BeginComputePass(); + pass.SetPipeline(pipeline); + pass.SetBindGroup(0, bgA); + pass.SetBindGroup(1, bgB); + pass.DispatchWorkgroups(1); + pass.End(); + wgpu::CommandBuffer commands = encoder.Finish(); + queue.Submit(1, &commands); + + EXPECT_BUFFER_U32_EQ(kBufBBound / 4, bufB, 0); + EXPECT_BUFFER_U32_EQ(0u, bufB, kOobIndex * sizeof(uint32_t)); +} + +DAWN_INSTANTIATE_TEST(OpArrayLengthVisibilityCollisionTest, + OpenGLESBackend(), + OpenGLESBackend({"gl_use_array_length_from_uniform"}), + VulkanBackend()); + enum class TieredLimits { No, Yes,
Regression Test / PoC
diff --git a/src/dawn/tests/end2end/OpArrayLengthTests.cpp b/src/dawn/tests/end2end/OpArrayLengthTests.cpp
index ea36ca3..a96a58d 100644
--- a/src/dawn/tests/end2end/OpArrayLengthTests.cpp
+++ b/src/dawn/tests/end2end/OpArrayLengthTests.cpp
@@ -303,6 +303,93 @@
VulkanBackend(),
WebGPUBackend());
+// Regression test for stage-visibility filtering in
+// GenerateArrayLengthFromuniformData (ShaderModuleGL.cpp). A storage
+// buffer in the layout that is *not* visible to the stage being
+// compiled should not be given an entry in Tint's
+// bindpoint_to_size_index nor in the remapper_data. Otherwise,
+// ArrayLengthFromUniform loads the wrong UBO slot for arrayLength(),
+// defeating Robustness clamping.
+class OpArrayLengthVisibilityCollisionTest : public DawnTest {
+ protected:
+ void GetRequiredLimits(const dawn::utils::ComboLimits& supported,
+ dawn::utils::ComboLimits& required) override {
+ supported.UnlinkedCopyTo(&required);
+ }
+};
+
+TEST_P(OpArrayLengthVisibilityCollisionTest, ComputeWithFragmentOnlyBufferInLayout) {
+ DAWN_TEST_UNSUPPORTED_IF(GetSupportedLimits().maxStorageBuffersInFragmentStage < 1);
+
+ // Buffer A: large, bound at full size to a FRAGMENT-only storage slot.
+ constexpr uint32_t kBufASize = 1u << 20; // 1 MiB → arrayLength<u32> = 262144
+ wgpu::BufferDescriptor descA;
+ descA.size = kBufASize;
+ descA.usage = wgpu::BufferUsage::Storage;
+ wgpu::Buffer bufA = device.CreateBuffer(&descA);
+
+ // Buffer B: large allocation, but only a 64-byte sub-range will be bound to
+ // the COMPUTE-visible storage slot. With the collision the shader receives
+ // A's bound size as B's arrayLength, so the Robustness clamp on B[idx]
+ // permits writes far past the 64-byte bound range.
+ constexpr uint32_t kBufBSize = 16384; // 16 KiB underlying allocation
+ constexpr uint32_t kBufBBound = 64; // 64 bytes bound → arrayLength = 16
+ constexpr uint32_t kOobIndex = 1000; // Target B[1000] (byte 4000) – OOB
+ constexpr uint32_t kOobMarker = 0xDEAD4A11u;
+ std::vector<uint32_t> initialData(kBufBSize / 4, 0u);
+ wgpu::Buffer bufB = utils::CreateBufferFromData(
+ device, initialData.data(), initialData.size() * sizeof(uint32_t),
+ wgpu::BufferUsage::Storage | wgpu::BufferUsage::CopySrc | wgpu::BufferUsage::CopyDst);
+
+ // PipelineLayoutGL assigns ssboIndex by iterating groups in order, so:
+ // group 0 (A, FRAGMENT-only) → ssboIndex 0
+ // group 1 (B, COMPUTE) → ssboIndex 1 → glIndex_B = 1
+ // Choose A's WGSL @binding == glIndex_B (= 1) so that A's pre-remap key
+ // {0,1} equals B's post-remap key {0, glIndex_B}.
+ wgpu::BindGroupLayout bglA = utils::MakeBindGroupLayout(
+ device, {{1, wgpu::ShaderStage::Fragment, wgpu::BufferBindingType::ReadOnlyStorage}});
+ wgpu::BindGroupLayout bglB = utils::MakeBindGroupLayout(
+ device, {{0, wgpu::ShaderStage::Compute, wgpu::BufferBindingType::Storage}});
+
+ std::string shaderSource = R"(
+ @group(1) @binding(0) var<storage, read_write> B : array<u32>;
+ @compute @workgroup_size(1) fn main() {
+ B[0] = arrayLength(&B);
+ // Robustness wraps this as B[min(idx, arrayLength(&B)-1)].
+ // With the bug arrayLength(&B) is huge, so the write lands at
+ // index 1000 – past the 64-byte bound range.
+ B[)" + std::to_string(kOobIndex) +
+ R"(u] = )" + std::to_string(kOobMarker) + R"(u;
+ })";
+
+ wgpu::ComputePipelineDescriptor pipelineDesc;
+ pipelineDesc.layout = utils::MakePipelineLayout(device, {bglA, bglB});
+ pipelineDesc.compute.module = utils::CreateShaderModule(device, shaderSource);
+ pipelineDesc.compute.entryPoint = "main";
+ wgpu::ComputePipeline pipeline = device.CreateComputePipeline(&pipelineDesc);
+
+ wgpu::BindGroup bgA = utils::MakeBindGroup(device, bglA, {{1, bufA, 0, kBufASize}});
+ wgpu::BindGroup bgB = utils::MakeBindGroup(device, bglB, {{0, bufB, 0, kBufBBound}});
+
+ wgpu::CommandEncoder encoder = device.CreateCommandEncoder();
+ wgpu::ComputePassEncoder pass = encoder.BeginComputePass();
+ pass.SetPipeline(pipeline);
+ pass.SetBindGroup(0, bgA);
+ pass.SetBindGroup(1, bgB);
+ pass.DispatchWorkgroups(1);
+ pass.End();
+ wgpu::CommandBuffer commands = encoder.Finish();
+ queue.Submit(1, &commands);
+
+ EXPECT_BUFFER_U32_EQ(kBufBBound / 4, bufB, 0);
+ EXPECT_BUFFER_U32_EQ(0u, bufB, kOobIndex * sizeof(uint32_t));
+}
+
+DAWN_INSTANTIATE_TEST(OpArrayLengthVisibilityCollisionTest,
+ OpenGLESBackend(),
+ OpenGLESBackend({"gl_use_array_length_from_uniform"}),
+ VulkanBackend());
+
enum class TieredLimits {
No,
Yes,
Original Bug Report
Bypass of Dawn's SSBO robustness checks via stage-filtering mismatch in OpenGL backend
Project Fortify, an experimental security project, has identified the following potential security issue. If you’re a feature owner CC-ed on this bug, please do your best to review these reports. Please see https://chromium.googlesource.com/chromium/src/+/main/docs/security/ai-generated-security-bugs-faq.md for more information.
Overview: A potential logic flaw in Dawn’s OpenGL/GLES backend stage-filtering can cause a mapping collision during Tint’s compilation phase. This mismatch allows an inactive shader storage buffer’s size lookup configuration to overwrite an active buffer’s configuration, potentially causing robustness checks to use an incorrect (larger) buffer size. An attacker could exploit this behavior to perform out-of-bounds memory reads and writes on storage buffers.
Affected files:
third_party/dawn/src/dawn/native/opengl/ShaderModuleGL.cppthird_party/dawn/src/tint/lang/glsl/writer/raise/raise.cc
Estimated timestamp from git blame: 2025-04-10
Description
There is a potential stage-filtering inconsistency in Dawn’s OpenGL backend during the translation of WGSL to GLSL. When generating the layout metadata for shader compilation, Dawn constructs remapping tables and uniform-based array length tables. However, the logic that populates these tables handles stage-visibility differently:
-
Active Binding Remapping:
GenerateBindingRemapping(defined inthird_party/dawn/src/dawn/native/TintUtils.h) correctly checks stage-visibility usingStageBit(stage). Non-stage-visible bindings are skipped:for (const auto& [bindingNumber, apiBindingIndex] : bgl->GetBindingMap()) { if (!(bgl->GetAPIBindingInfo(apiBindingIndex).visibility & StageBit(stage))) { continue; } -
Array Length Uniform Options:
GenerateArrayLengthFromuniformData(defined inthird_party/dawn/src/dawn/native/opengl/ShaderModuleGL.cpp) does not check stage-visibility. It populatesbindpoint_to_size_indexfor all storage buffers declared in the pipeline layout, regardless of whether they are active or visible in the current stage:for (BindingIndex binding : bgl->GetBufferIndices()) { const BindingInfo& bindingInfo = bgl->GetBindingInfo(binding); switch (std::get<BufferBindingInfo>(bindingInfo.bindingLayout).type) { case wgpu::BufferBindingType::Storage: // ... tint::BindingPoint srcBindingPoint = {uint32_t(group), uint32_t(bindingInfo.binding)}; options.bindpoint_to_size_index.emplace(srcBindingPoint, uint32_t(ssboIndex));
Potential Exploit Mechanism
An attacker could potentially configure a pipeline layout containing:
- Buffer A (inactive in the compute stage) at Bind Group
0, BindingN(pre-remap:{0, N}). - Buffer B (active in the compute stage) at Bind Group
1, BindingM(pre-remap:{1, M}).
During compilation of a compute shader that only utilizes Buffer B:
-
Remapping Stage:
GenerateBindingRemappingfilters out Buffer A. It remaps Buffer B from{1, M}to a flat 1D binding point (e.g.,{0, N}). Consequently, the remapper data used by Tint (remapper_data) maps{1, M} -> {0, N}. It contains no entries for Buffer A’s original binding point{0, N}. -
Array Length Options Stage:
GenerateArrayLengthFromuniformDatapopulates options for both buffers:{1, M} -> index_B(for Buffer B){0, N} -> index_A(for Buffer A)
-
Map Collision in Tint: During Tint’s raise phase in
third_party/dawn/src/tint/lang/glsl/writer/raise/raise.cc, Tint resolves the pre-remapped keys ofbindpoint_to_size_indexto their post-remapped targets to build a finalsize_indicesmap:std::unordered_map<BindingPoint, uint32_t> size_indices; for (auto& pair : options.array_length_from_uniform.bindpoint_to_size_index) { auto& bp = pair.first; auto& index = pair.second; where = remapper_data.find(bp); if (where != remapper_data.end()) { size_indices[where->second] = index; } else { size_indices[bp] = index; } }- When processing Buffer B (
{1, M}), Tint resolves it viaremapper_datato{0, N}and setssize_indices[{0, N}] = index_B. - When processing Buffer A (
{0, N}), Tint does not find it inremapper_dataand falls back to theelseblock, settingsize_indices[{0, N}] = index_A. - This causes a key collision inside the
std::unordered_map. Buffer A’s entry overwrites Buffer B’s size index configuration, setting it toindex_Ainstead ofindex_B.
- When processing Buffer B (
-
Robustness Bypass: When Tint substitutes
.length()calls or injects robustness-clamping bound checks for Buffer B (binding point{0, N}), it retrieves the size fromindex_A(the index corresponding to Buffer A). If the attacker binds a very large buffer to Buffer A and a small buffer to Buffer B, the runtime clamping checks for Buffer B will clamp against the size of Buffer A, allowing out-of-bounds reads and writes on the memory backing Buffer B.
Note: These steps are based on static code analysis; our tooling does not currently have the environment or capability to run a live dynamic Proof-of-Concept to verify execution.
Impact
This flaw allows a shader to bypass WebGPU/Tint’s software robustness safety guarantees. On GLES backends (such as older Android devices with PowerVR or Nvidia GPUs), the GPU process is typically unsandboxed, potentially allowing GPU memory corruption or sandbox escape.
Suggested Fix
Modify GenerateArrayLengthFromuniformData in third_party/dawn/src/dawn/native/opengl/ShaderModuleGL.cpp to filter out storage buffers that are not visible to the shader stage currently being compiled, matching the visibility filter used in GenerateBindingRemapping:
for (BindingIndex binding : bgl->GetBufferIndices()) {
const BindingInfo& bindingInfo = bgl->GetBindingInfo(binding);
if (!(bindingInfo.visibility & StageBit(stage))) {
continue;
}
// ...
Evaluated with Chrome root at commit: b1520ef4a76878853a31f0943b565e42060edec8
Results so far have been promising, but there can be wrong deductions. Feel free to adjust as follows:
- If you are familiar with the severity guidelines, you may adjust the severity.
- If this is a false positive, and there’s no work to be done, please close as WAI.
- If there is work to do here but not a vulnerability, please change the issue type to Task/Bug/FR.
Data from false positives will be used to improve accuracy over time. And please feel free to reach out to me directly if you have concerns or feedback on the project.