mirror of
https://github.com/elicpeter/nyx.git
synced 2026-06-24 20:28:06 +02:00
Authorization analysis logic improvements (#61)
This commit is contained in:
parent
3c89bddbf2
commit
40995e45e7
55 changed files with 4193 additions and 134 deletions
|
|
@ -8,7 +8,7 @@ Current baseline (2026-05-02):
|
|||
| Recall | 1.000 | 1.000 | 0.944 |
|
||||
| F1 | 1.000 | 1.000 | 0.901 |
|
||||
|
||||
Corpus: 492 cases across 10 languages, 491 evaluated (1 disabled). Per-run JSON lands in `tests/benchmark/results/` (`latest.json` plus dated snapshots). See `README.md` for what the scoring modes mean and how to run a subset.
|
||||
Corpus: 499 cases across 10 languages, 496 evaluated (3 disabled). Per-run JSON lands in `tests/benchmark/results/` (`latest.json` plus dated snapshots). See `README.md` for what the scoring modes mean and how to run a subset.
|
||||
|
||||
The corpus is mostly synthetic 8-20 line fixtures, one vulnerability or one safe pattern per file. A smaller real-CVE replay set under `cve_corpus/` covers 20 published CVEs across all 10 languages. Both contribute to the headline numbers.
|
||||
|
||||
|
|
@ -24,6 +24,7 @@ Real disclosed CVEs reduced to minimal reproducers, vulnerable + patched pair pe
|
|||
| CVE-2026-33626 | Python | LMDeploy | Apache-2.0 | SSRF | detected |
|
||||
| CVE-2019-14939 | JavaScript | mongo-express | MIT | code_exec | detected |
|
||||
| CVE-2025-64430 | JavaScript | Parse Server | Apache-2.0 | SSRF | detected |
|
||||
| CVE-2023-22621 | JavaScript | Strapi | MIT | code_exec (SSTI)| detected |
|
||||
| CVE-2023-26159 | TypeScript | follow-redirects | MIT | SSRF | detected |
|
||||
| GHSA-4x48-cgf9-q33f | TypeScript | Novu | MIT | SSRF | detected |
|
||||
| CVE-2022-30323 | Go | hashicorp/go-getter | MPL-2.0 | CMDI | detected |
|
||||
|
|
@ -43,6 +44,7 @@ Real disclosed CVEs reduced to minimal reproducers, vulnerable + patched pair pe
|
|||
| CVE-2019-18634 | C | sudo (pwfeedback) | ISC | memory_safety | detected |
|
||||
| CVE-2019-13132 | C++ | ZeroMQ libzmq | MPL-2.0 | memory_safety | detected |
|
||||
| CVE-2022-1941 | C++ | Protocol Buffers | BSD-3-Clause | memory_safety | detected |
|
||||
| CVE-2026-25544 | TypeScript | Payload (Drizzle adapter) | MIT | sql_injection | deferred |
|
||||
|
||||
Deferred entries are real bugs Nyx can't yet detect. The fixture stays committed with `disabled: true` in ground truth so the gap remains visible.
|
||||
|
||||
|
|
@ -67,6 +69,8 @@ Most recent first. Metrics are rule-level on the corpus size at that point.
|
|||
|
||||
| Date | Change | Corpus | P | R | F1 |
|
||||
|------------|------------------------------------------------------------------------------|--------|-------|-------|-------|
|
||||
| 2026-05-02 | TS regex-allowlist `<*regex*>.test(value)` / `<*pattern*>.test(value)` recognised as ValidationCall whose target is the first arg (overrides default receiver-as-target); conservative on receiver names so non-regex `*.test()` callees stay Unknown. CVE-2026-25544 (Payload drizzle SQL injection) lands in corpus disabled — needs validated-flow propagation through SSA derivation / helper-summary returns | 499 | 1.000 | 1.000 | 1.000 |
|
||||
| 2026-05-02 | JS arrow `assignment_pattern` default-param extraction + JS object-literal kwarg fallback for gated sinks + double-call (`f()(x)`) chained-inner rebinding; lodash `_.template` modeled as gated CODE_EXEC sink suppressed by `{ evaluate: false }`; CVE-2023-22621 (Strapi SSTI) detected | 494 | — | — | — |
|
||||
| 2026-05-02 | `strings.ReplaceAll` recognised as CMDi sanitiser in chain-wrapper / call-site-replace shapes; clears `go-safe-009` (last open corpus FP); aggregate rule-level reaches P=R=F1=1.000 | 492 | 1.000 | 1.000 | 1.000 |
|
||||
| 2026-05-01 | PathFact opaque-prefix-lock (`canonicalise + start_with?(<expr>)` recognised across Ruby/Python/JS) + `is_path_traversal_safe` predicate + negated-form polarity flip on assertion narrowing; rswag CVE-2023-38337 detected | 490 | 0.972 | 0.992 | 0.982 |
|
||||
| 2026-05-01 | Ruby `OpenURI.open_uri` SSRF sink + inner-call fallback for statement-level Ruby calls (`YAML.safe_load(File.read(x))` shape now classifies); CVE-2021-21288 (CarrierWave) detected | 482 | 0.972 | 0.992 | 0.982 |
|
||||
|
|
|
|||
|
|
@ -0,0 +1,55 @@
|
|||
// go-safe-realrepo-016 — distilled from prometheus tsdb/block_test.go:185
|
||||
// and 9+ other prometheus test files. Pattern: a wrapper call takes
|
||||
// the close call's RESULT as an argument, e.g.
|
||||
//
|
||||
// require.NoError(t, f.Close())
|
||||
// errs = append(errs, f.Close())
|
||||
//
|
||||
// The CFG creates one Call node per statement keyed on the OUTER
|
||||
// callee. The inner-call release was invisible to the resource pass
|
||||
// before the fix: direct-release loop matches `info.call.callee`
|
||||
// (the outer callee), and the inner-call callee was carried in
|
||||
// `info.arg_callees[i]` but unread. Engine fix:
|
||||
// src/state/transfer.rs::apply_call now walks `info.arg_callees`
|
||||
// after the direct-release branch.
|
||||
|
||||
package safe
|
||||
|
||||
import (
|
||||
"errors"
|
||||
"os"
|
||||
)
|
||||
|
||||
type tHelper struct{}
|
||||
|
||||
func (tHelper) NoError(args ...any) {}
|
||||
|
||||
var t tHelper
|
||||
|
||||
func close_in_require_noerror() error {
|
||||
f, err := os.OpenFile("/tmp/x", os.O_RDWR, 0o666)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
t.NoError(f.Close())
|
||||
return nil
|
||||
}
|
||||
|
||||
func close_in_append_arg() error {
|
||||
f, err := os.Create("/tmp/y")
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
var errs []error
|
||||
errs = append(errs, f.Close())
|
||||
return errors.Join(errs...)
|
||||
}
|
||||
|
||||
func close_via_defer() error {
|
||||
f, err := os.Open("/tmp/z")
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
defer f.Close()
|
||||
return nil
|
||||
}
|
||||
|
|
@ -0,0 +1,78 @@
|
|||
// go-safe-realrepo-017 — distilled from prometheus
|
||||
// `cmd/promtool/tsdb.go::startProfiling` (lines 230, 239, 246, 252):
|
||||
// 4 findings on the same function plus widespread similar shapes
|
||||
// across the prometheus tree. Pattern:
|
||||
//
|
||||
// b.cpuprof, err = os.Create(...)
|
||||
//
|
||||
// The resource is owned by the struct `*writeBenchmark`. Closure
|
||||
// happens in a paired method `stopProfiling()`. The current function
|
||||
// body cannot observe that closure, so any per-body resource analysis
|
||||
// fires unconditionally.
|
||||
//
|
||||
// Engine fix (depth: structural — both layers):
|
||||
// * src/state/transfer.rs::apply_call gates the acquire branch on
|
||||
// `!define_is_field_lhs` so member-expression LHS doesn't seed
|
||||
// `state.resource` in the dataflow lattice.
|
||||
// * src/cfg_analysis/resources.rs::run gates the structural rule's
|
||||
// acquire-iteration on the same `defines.contains('.')` check.
|
||||
|
||||
package safe
|
||||
|
||||
import (
|
||||
"os"
|
||||
"runtime/pprof"
|
||||
)
|
||||
|
||||
type writeBenchmark struct {
|
||||
cpuprof *os.File
|
||||
memprof *os.File
|
||||
blockprof *os.File
|
||||
mtxprof *os.File
|
||||
outPath string
|
||||
}
|
||||
|
||||
func (b *writeBenchmark) startProfiling() error {
|
||||
var err error
|
||||
b.cpuprof, err = os.Create(b.outPath + "/cpu.prof")
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
if err := pprof.StartCPUProfile(b.cpuprof); err != nil {
|
||||
return err
|
||||
}
|
||||
b.memprof, err = os.Create(b.outPath + "/mem.prof")
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
b.blockprof, err = os.Create(b.outPath + "/block.prof")
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
b.mtxprof, err = os.Create(b.outPath + "/mutex.prof")
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func (b *writeBenchmark) stopProfiling() error {
|
||||
if b.cpuprof != nil {
|
||||
pprof.StopCPUProfile()
|
||||
b.cpuprof.Close()
|
||||
b.cpuprof = nil
|
||||
}
|
||||
if b.memprof != nil {
|
||||
b.memprof.Close()
|
||||
b.memprof = nil
|
||||
}
|
||||
if b.blockprof != nil {
|
||||
b.blockprof.Close()
|
||||
b.blockprof = nil
|
||||
}
|
||||
if b.mtxprof != nil {
|
||||
b.mtxprof.Close()
|
||||
b.mtxprof = nil
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
|
@ -0,0 +1,16 @@
|
|||
// go-vuln-realrepo-018 — recall guard for the inner-call-arg /
|
||||
// member-LHS fixes. Bare-identifier `f := os.OpenFile(...)` with no
|
||||
// `f.Close()` anywhere must still fire the resource-leak rule.
|
||||
|
||||
package safe
|
||||
|
||||
import "os"
|
||||
|
||||
func vuln_open_no_close() error {
|
||||
f, err := os.OpenFile("/tmp/x", os.O_RDWR, 0o666)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
_ = f
|
||||
return nil
|
||||
}
|
||||
|
|
@ -0,0 +1,20 @@
|
|||
# py-auth-vuln-002: helper takes a user-supplied id (`project_id`)
|
||||
# and queries by it without any preceding ownership/membership check.
|
||||
# This is the vulnerable counterpart to
|
||||
# safe_django_orm_caller_scoped_entity.py — same Django ORM shape, but
|
||||
# the param is an *id-like user input*, not a scope-entity object, so
|
||||
# the caller-scope-entity exemption must not apply.
|
||||
#
|
||||
# Pinned to keep recall on the missing_ownership_check rule.
|
||||
|
||||
|
||||
class Project:
|
||||
pass
|
||||
|
||||
|
||||
def get_project(request, project_id):
|
||||
return Project.objects.filter(id=project_id).first()
|
||||
|
||||
|
||||
def delete_project(request, project_id):
|
||||
Project.objects.filter(id=project_id).delete()
|
||||
|
|
@ -0,0 +1,63 @@
|
|||
# py-auth-realrepo-008: caller-passed scope entity used as ownership
|
||||
# constraint. Distilled from sentry
|
||||
# `src/sentry/api/helpers/environments.py::get_environments` (and the
|
||||
# many sibling helpers in `api/endpoints/organization_releases.py`):
|
||||
#
|
||||
# def get_environments(request, organization: Organization):
|
||||
# ...
|
||||
# return list(
|
||||
# Environment.objects.filter(
|
||||
# organization_id=organization.id,
|
||||
# name__in=requested_environments,
|
||||
# )
|
||||
# )
|
||||
#
|
||||
# `_filter_releases_by_query(queryset, organization, query, filter_params)`
|
||||
# follows the same pattern with `queryset.filter_by_semver(organization.id, ...)`.
|
||||
#
|
||||
# Both helpers receive the already-authorised `organization` object
|
||||
# from a route handler that resolved it via `OrganizationReleasesBaseEndpoint`
|
||||
# membership middleware. The query is *scoped by* `organization.id`
|
||||
# — that IS the ownership boundary, not a user-controlled target.
|
||||
#
|
||||
# Without the caller-scope-entity exemption, every internal helper in a
|
||||
# multi-tenant Django/Rails/Laravel codebase flags
|
||||
# `missing_ownership_check` because the engine cannot tell "scoping
|
||||
# arg" from "user-targeted arg". The fix recognises that
|
||||
# `<entity>.id` where `<entity>` is a unit parameter named after a
|
||||
# scope-bearing domain entity (organization, project, team, workspace,
|
||||
# tenant, account, ...) is a passed-in scope, not a target.
|
||||
from typing import List
|
||||
|
||||
|
||||
class Organization:
|
||||
pass
|
||||
|
||||
|
||||
class Environment:
|
||||
pass
|
||||
|
||||
|
||||
def get_environments(request, organization: Organization) -> List[Environment]:
|
||||
requested_environments = set(request.GET.getlist("environment"))
|
||||
if not requested_environments:
|
||||
return []
|
||||
return list(
|
||||
Environment.objects.filter(
|
||||
organization_id=organization.id, name__in=requested_environments
|
||||
)
|
||||
)
|
||||
|
||||
|
||||
def _filter_releases_by_query(queryset, organization: Organization, query, filter_params):
|
||||
queryset = queryset.filter_by_semver(organization.id, query)
|
||||
queryset = queryset.filter_by_stage(organization.id, query)
|
||||
return queryset
|
||||
|
||||
|
||||
def list_project_issues(request, project):
|
||||
return list(Issue.objects.filter(project_id=project.id, status="open"))
|
||||
|
||||
|
||||
class Issue:
|
||||
pass
|
||||
|
|
@ -0,0 +1,52 @@
|
|||
# py-auth-realrepo-010: pytest test method decorated with
|
||||
# `@mock.patch("...")` collides with Flask's `<app>.<verb>` route
|
||||
# decorator shape (bare_method_name("mock.patch") == "patch", which the
|
||||
# parse_flask_route_decorator matched as HTTP PATCH). The collision
|
||||
# attached the test method as a Flask route handler, flipped its
|
||||
# `unit.kind` to RouteHandler, made it pass
|
||||
# `unit_has_user_input_evidence` unconditionally, and flooded pytest
|
||||
# test suites with `missing_ownership_check` findings.
|
||||
#
|
||||
# Distilled from airflow
|
||||
# `providers/google/tests/unit/google/cloud/hooks/test_dlp.py` (47 FPs
|
||||
# in this single file pre-fix). Fix:
|
||||
# `parse_flask_route_decorator` short-circuits when the callee text
|
||||
# matches a known test-framework decorator vocabulary
|
||||
# (`mock.patch`, `unittest.mock.patch`, `monkeypatch.setattr`,
|
||||
# `pytest.mark.parametrize`, …).
|
||||
#
|
||||
# This fixture verifies pytest test methods don't fire ownership-check
|
||||
# findings, even when they call ORM-shaped APIs with id-suffixed
|
||||
# constants (the canonical pytest fixture-data pattern).
|
||||
from unittest import mock
|
||||
from unittest.mock import PropertyMock
|
||||
|
||||
ORGANIZATION_ID = "fake-org-id-123"
|
||||
PROJECT_ID = "fake-proj-id-456"
|
||||
DLP_JOB_ID = "fake-job-id-789"
|
||||
|
||||
|
||||
class TestCloudDLPHook:
|
||||
@mock.patch(
|
||||
"module.GoogleBaseHook.project_id",
|
||||
new_callable=PropertyMock,
|
||||
return_value=None,
|
||||
)
|
||||
@mock.patch("module.CloudDLPHook.get_conn")
|
||||
def test_create_deidentify_template_with_org_id(self, get_conn, mock_project_id):
|
||||
get_conn.return_value.create_deidentify_template.return_value = "API_RESPONSE"
|
||||
result = self.hook.create_deidentify_template(organization_id=ORGANIZATION_ID)
|
||||
return result
|
||||
|
||||
@mock.patch("module.CloudDLPHook.get_conn")
|
||||
def test_create_dlp_job(self, get_conn):
|
||||
result = self.hook.create_dlp_job(project_id=PROJECT_ID)
|
||||
return result
|
||||
|
||||
@mock.patch.object(SomeClass, "method")
|
||||
def test_with_object_patch(self, mock_method):
|
||||
self.hook.cancel_dlp_job(dlp_job_id=DLP_JOB_ID)
|
||||
|
||||
|
||||
class SomeClass:
|
||||
pass
|
||||
|
|
@ -0,0 +1,70 @@
|
|||
// Real-repo motivation (meilisearch `GuardedData<P, D>` typed
|
||||
// extractor on actix-web routes registered via `#[routes::path(..)]`
|
||||
// attribute macros).
|
||||
//
|
||||
// Meilisearch's authorization extractor is
|
||||
// `GuardedData<ActionPolicy<{ actions::KEYS_GET }>,
|
||||
// Data<AuthController>>`. Possessing the value proves the request
|
||||
// passed the per-action permission check the inner Policy term
|
||||
// encodes. Routes are registered by attribute macro, not by the
|
||||
// `.route("/p", web::get().to(handler))` builder pattern, so the
|
||||
// actix_web extractor's route walk doesn't attach the handler as
|
||||
// `RouteHandler` and never injected typed-extractor guard checks.
|
||||
//
|
||||
// The typed-extractor fallback pass in `actix_web::extract` now walks
|
||||
// every Function-kind unit and applies `guard_calls_for_handler` to
|
||||
// its parameter list, so the `GuardedData` parameter is recognised as
|
||||
// a route-level policy guard (`AuthCheckKind::Other`,
|
||||
// `is_route_level: true`) and the per-handler ownership rule no
|
||||
// longer fires on path-derived sinks.
|
||||
|
||||
#![allow(dead_code, unused_variables)]
|
||||
|
||||
use std::marker::PhantomData;
|
||||
|
||||
pub struct ActionPolicy<const A: u8>;
|
||||
pub struct Data<T>(pub T);
|
||||
|
||||
pub struct GuardedData<P, D> {
|
||||
data: D,
|
||||
_marker: PhantomData<P>,
|
||||
}
|
||||
|
||||
impl<P, D> GuardedData<P, D> {
|
||||
pub fn into_inner(self) -> D {
|
||||
self.data
|
||||
}
|
||||
}
|
||||
|
||||
pub mod web {
|
||||
pub struct Path<T>(pub T);
|
||||
impl<T> Path<T> {
|
||||
pub fn into_inner(self) -> T {
|
||||
unimplemented!()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
pub struct AuthController;
|
||||
|
||||
impl AuthController {
|
||||
pub fn get_key(&self, uid: u64) -> Result<String, ()> {
|
||||
Ok(String::new())
|
||||
}
|
||||
}
|
||||
|
||||
pub mod actions {
|
||||
pub const KEYS_GET: u8 = 1;
|
||||
}
|
||||
|
||||
pub struct AuthParam {
|
||||
pub key: u64,
|
||||
}
|
||||
|
||||
pub async fn get_api_key(
|
||||
auth_controller: GuardedData<ActionPolicy<{ actions::KEYS_GET }>, Data<AuthController>>,
|
||||
path: web::Path<AuthParam>,
|
||||
) -> Result<String, ()> {
|
||||
let uid = path.into_inner().key;
|
||||
auth_controller.into_inner().0.get_key(uid)
|
||||
}
|
||||
|
|
@ -0,0 +1,44 @@
|
|||
// Negative counterpart for `safe_actix_guarded_data_extractor.rs`.
|
||||
//
|
||||
// Same handler shape (path-derived `uid` flows into
|
||||
// `auth_controller.get_key(uid)`) but **without** the `GuardedData<P, D>`
|
||||
// wrapper around the controller. The handler now takes a bare
|
||||
// `Data<AuthController>` and a typed `web::Path<AuthParam>` — no
|
||||
// route-level capability check is implied by the parameter types.
|
||||
// Pinned by `unsafe_actix_no_guarded_data_extractor` to guard against
|
||||
// over-broad `policy_guard_names` recognition that would treat any
|
||||
// handler with an actix-web parameter shape as authorised: the rule
|
||||
// must still fire here.
|
||||
|
||||
#![allow(dead_code, unused_variables)]
|
||||
|
||||
pub struct Data<T>(pub T);
|
||||
|
||||
pub mod web {
|
||||
pub struct Path<T>(pub T);
|
||||
impl<T> Path<T> {
|
||||
pub fn into_inner(self) -> T {
|
||||
unimplemented!()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
pub struct AuthController;
|
||||
|
||||
impl AuthController {
|
||||
pub fn get_key(&self, uid: u64) -> Result<String, ()> {
|
||||
Ok(String::new())
|
||||
}
|
||||
}
|
||||
|
||||
pub struct AuthParam {
|
||||
pub key: u64,
|
||||
}
|
||||
|
||||
pub async fn get_api_key(
|
||||
auth_controller: Data<AuthController>,
|
||||
path: web::Path<AuthParam>,
|
||||
) -> Result<String, ()> {
|
||||
let uid = path.into_inner().key;
|
||||
auth_controller.0.get_key(uid)
|
||||
}
|
||||
|
|
@ -0,0 +1,23 @@
|
|||
[package]
|
||||
name = "unsafe_actix_web_project_no_check"
|
||||
version = "0.1.0"
|
||||
edition = "2021"
|
||||
|
||||
# Manifest names actix-web → `lang_has_web_framework("rust")` returns
|
||||
# `Some(true)` → the project-level web-framework signal does NOT
|
||||
# suppress the param-name arm. The handler below is then correctly
|
||||
# flagged for taking a user-controlled `*_id` parameter and performing
|
||||
# a sink without an upstream auth check (regression guard for the
|
||||
# project-level gate ─ the gate must NOT silence findings in real
|
||||
# web projects).
|
||||
|
||||
[dependencies]
|
||||
actix-web = "4"
|
||||
|
||||
# `actix-web` is a manifest-only regression marker: nyx's
|
||||
# `lang_has_web_framework("rust")` reads the dependency list to derive
|
||||
# `Some(true)`, which keeps the param-name arm of missing_ownership_check
|
||||
# active. No `use actix_web::*` line exists in src/lib.rs, so machete
|
||||
# correctly sees it as code-unused — the dep is real for our purposes.
|
||||
[package.metadata.cargo-machete]
|
||||
ignored = ["actix-web"]
|
||||
|
|
@ -0,0 +1,16 @@
|
|||
//! Regression counterpart to `safe_non_web_rust_project`. Same helper
|
||||
//! shape (`fn delete_session(session_id: i64)`) with NO upstream auth
|
||||
//! check — must still flag missing_ownership_check because the
|
||||
//! project's manifest names `actix-web` → web-framework signal
|
||||
//! `Some(true)` → the param-name heuristic stays on.
|
||||
|
||||
pub struct Db;
|
||||
impl Db {
|
||||
pub async fn delete_one(&self, _id: i64) -> Result<(), ()> { Ok(()) }
|
||||
}
|
||||
|
||||
// Helper called from an actix handler. No upstream `require_*` /
|
||||
// `check_*` covers `session_id`, so missing_ownership_check fires.
|
||||
pub async fn delete_session(db: &Db, session_id: i64) -> Result<(), ()> {
|
||||
db.delete_one(session_id).await
|
||||
}
|
||||
|
|
@ -0,0 +1,23 @@
|
|||
[package]
|
||||
name = "safe_non_web_rust_project"
|
||||
version = "0.1.0"
|
||||
edition = "2021"
|
||||
|
||||
# Manifest deliberately names no Rust web framework. The auth-analysis
|
||||
# web-framework signal must derive Some(false) from this manifest, so
|
||||
# every internal helper named `<thing>_id` and every `session.foo`
|
||||
# chain in the source refuses the user-input evidence and
|
||||
# missing_ownership_check stays silent.
|
||||
|
||||
[dependencies]
|
||||
serde = "1"
|
||||
tokio = { version = "1", features = ["full"] }
|
||||
|
||||
# These deps are manifest-only regression markers. The point of this
|
||||
# fixture is that the manifest names NO Rust web framework, so
|
||||
# `lang_has_web_framework("rust")` returns `Some(false)`. `serde` and
|
||||
# `tokio` populate the dependency list without tripping that signal,
|
||||
# proving the gate ignores non-web crates. src/lib.rs deliberately
|
||||
# uses neither.
|
||||
[package.metadata.cargo-machete]
|
||||
ignored = ["serde", "tokio"]
|
||||
|
|
@ -0,0 +1,60 @@
|
|||
//! Real-repo precision guard distilled from zed's desktop / GUI crates
|
||||
//! (`crates/agent_servers/src/acp.rs::session_thread`,
|
||||
//! `crates/agent_ui/src/thread_worktree_archive.rs::rollback_persist`,
|
||||
//! `crates/debugger_ui/src/tests/debugger_panel.rs::test_*`).
|
||||
//!
|
||||
//! Without the project-level web-framework signal, two heuristics
|
||||
//! over-fire on internal helpers in non-web Rust projects:
|
||||
//! * `is_external_input_param_name` flips step 3 open on every
|
||||
//! `*_id` / `path` / `query` / `body` / `dto` parameter.
|
||||
//! * `matches_session_context` lifts every `session.foo` chain into
|
||||
//! `unit.context_inputs` (step 2), even when `session` is a
|
||||
//! debug / RPC / DAP session, not an HTTP/auth session.
|
||||
//!
|
||||
//! Both arms must be gated by the project's web-framework signal.
|
||||
//! This crate's `Cargo.toml` deliberately names no Rust web framework,
|
||||
//! so `lang_has_web_framework("rust")` returns `Some(false)` and both
|
||||
//! arms refuse to count internal-helper params as user input.
|
||||
|
||||
pub struct ContextServerStore;
|
||||
impl ContextServerStore {
|
||||
pub fn get_running_server(&self, _: &str) -> Option<()> { Some(()) }
|
||||
}
|
||||
|
||||
pub struct ClientContext {
|
||||
pub sessions: Vec<DebugSession>,
|
||||
}
|
||||
|
||||
pub struct DebugSession;
|
||||
impl DebugSession {
|
||||
pub fn update<F: FnOnce(&Self) -> R, R>(&self, f: F) -> R { f(self) }
|
||||
pub fn read(&self) -> &Self { self }
|
||||
pub fn adapter_client(&self) -> Option<()> { Some(()) }
|
||||
}
|
||||
|
||||
// `<thing>_id` parameter must not gate user-input-evidence open in a
|
||||
// project the manifest confirmed has no Rust web framework. Without
|
||||
// the gate, every helper of this shape would fire missing_ownership_check.
|
||||
pub fn get_prompt(
|
||||
server_store: &ContextServerStore,
|
||||
server_id: &str,
|
||||
prompt_name: &str,
|
||||
) -> Option<()> {
|
||||
let _ = (server_id, prompt_name);
|
||||
server_store.get_running_server(server_id)
|
||||
}
|
||||
|
||||
pub async fn rollback_persist(archived_worktree_id: i64) {
|
||||
let _ = archived_worktree_id;
|
||||
}
|
||||
|
||||
// Bare `session.foo` chains land in `context_inputs` via
|
||||
// `matches_session_context` → `ValueSourceKind::Session`. In a non-
|
||||
// web Rust project the gate suppresses step 2 so this idiomatic
|
||||
// debug-session pattern stays silent.
|
||||
pub fn open_debug_session(ctx: &ClientContext) {
|
||||
if let Some(session) = ctx.sessions.first() {
|
||||
let _ = session.update(|session| session.adapter_client());
|
||||
let _client = session.read();
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,43 @@
|
|||
// Validated-flow propagation through helper chains
|
||||
// (`SsaFuncSummary::validated_params_to_return`, CVE-2026-25544 deep
|
||||
// fix). `sanitize` validates its parameter via a regex allowlist
|
||||
// and throws on failure; `buildQuery` interpolates the sanitised
|
||||
// result into a SQL fragment; the handler hands the fragment to a
|
||||
// raw-SQL execute callee.
|
||||
//
|
||||
// On a normal-returning call to either helper, the actual argument
|
||||
// passed validation by construction, so `db.query(sql)` must not
|
||||
// re-flag downstream taint findings. The summary records
|
||||
// `validated_params_to_return: [0]` on `sanitize` after the
|
||||
// `regex.test` guard, propagates the bit through `buildQuery` via
|
||||
// summary re-extraction, and the caller's sink therefore observes
|
||||
// `all_validated = true`.
|
||||
//
|
||||
// Pinned by:
|
||||
// * tests/lib::validated_params_to_return_suppresses_one_hop_helper_validator
|
||||
// * tests/lib::validated_params_to_return_suppresses_two_hop_helper_validator
|
||||
|
||||
import express, { Request, Response } from 'express';
|
||||
|
||||
const SAFE_VALUE_REGEX = /^[\w@.\-+:]*$/;
|
||||
|
||||
const sanitize = (value: string): string => {
|
||||
if (!SAFE_VALUE_REGEX.test(value)) {
|
||||
throw new Error('value is not allowed');
|
||||
}
|
||||
return value;
|
||||
};
|
||||
|
||||
const buildQuery = (column: string, value: string): string => {
|
||||
const safe = sanitize(value);
|
||||
return column + '=' + safe;
|
||||
};
|
||||
|
||||
const app = express();
|
||||
app.use(express.json());
|
||||
|
||||
app.post('/q', (req: Request, res: Response) => {
|
||||
const userValue = req.body.filter as string;
|
||||
const sql = buildQuery('data', userValue);
|
||||
res.send(sql);
|
||||
});
|
||||
|
|
@ -0,0 +1,60 @@
|
|||
// Nyx CVE benchmark fixture.
|
||||
//
|
||||
// CVE: CVE-2023-22621
|
||||
// Project: Strapi (strapi/strapi)
|
||||
// License: MIT (https://github.com/strapi/strapi/blob/develop/LICENSE)
|
||||
// Advisory: https://github.com/strapi/strapi/security/advisories/GHSA-2h87-4q2w-v4hf
|
||||
// Patched: 921d30961d6ba96cc098f2aea197350a49f990bd
|
||||
// packages/core/email/server/services/email.js:25-50
|
||||
//
|
||||
// Patched-fix simplification: `createStrictInterpolationRegExp` is
|
||||
// imported from `@strapi/utils` upstream; we inline a one-line stub
|
||||
// that builds a regex restricted to a fixed allowlist. The load-
|
||||
// bearing fix is the explicit `{ interpolate, evaluate: false,
|
||||
// escape: false }` options object passed to `_.template`, which
|
||||
// disables lodash's `<% ... %>` evaluate block. The trailing
|
||||
// `(data)` invocation of the compiled function is split off (matches
|
||||
// the corresponding split in `vulnerable.js`).
|
||||
//
|
||||
// Trim parity with `vulnerable.js`: same `attributes.reduce`-to-`for`
|
||||
// transformation; the load-bearing
|
||||
// `_.template(emailTemplate[attribute], { interpolate, evaluate: false, escape: false })`
|
||||
// call is verbatim from upstream's options-object form.
|
||||
|
||||
'use strict';
|
||||
|
||||
const _ = require('lodash');
|
||||
const express = require('express');
|
||||
const app = express();
|
||||
app.use(express.json());
|
||||
|
||||
const createStrictInterpolationRegExp = (allowed) =>
|
||||
new RegExp(`<%=\\s*(${allowed.join('|')})\\s*%>`, 'g');
|
||||
|
||||
const keysDeep = (obj) => Object.keys(obj || {});
|
||||
|
||||
const sendTemplatedEmail = (emailOptions = {}, emailTemplate = {}, data = {}) => {
|
||||
const attributes = ['subject', 'text', 'html'];
|
||||
const allowedInterpolationVariables = keysDeep(data);
|
||||
const interpolate = createStrictInterpolationRegExp(allowedInterpolationVariables);
|
||||
|
||||
const templatedAttributes = {};
|
||||
for (const attribute of attributes) {
|
||||
if (emailTemplate[attribute]) {
|
||||
const compiled = _.template(emailTemplate[attribute], {
|
||||
interpolate,
|
||||
evaluate: false,
|
||||
escape: false,
|
||||
});
|
||||
templatedAttributes[attribute] = compiled(data);
|
||||
}
|
||||
}
|
||||
return templatedAttributes;
|
||||
};
|
||||
|
||||
app.put('/users-permissions/email-templates', (req, res) => {
|
||||
sendTemplatedEmail({}, req.body.emailTemplate, req.body.data);
|
||||
res.sendStatus(200);
|
||||
});
|
||||
|
||||
app.listen(1337);
|
||||
|
|
@ -0,0 +1,50 @@
|
|||
// Nyx CVE benchmark fixture.
|
||||
//
|
||||
// CVE: CVE-2023-22621
|
||||
// Project: Strapi (strapi/strapi)
|
||||
// License: MIT (https://github.com/strapi/strapi/blob/develop/LICENSE)
|
||||
// Advisory: https://github.com/strapi/strapi/security/advisories/GHSA-2h87-4q2w-v4hf
|
||||
// Vulnerable: 479bdde67eb3759d89218c9686208be2409217ef
|
||||
// packages/core/email/server/services/email.js:23-39
|
||||
//
|
||||
// Strapi <= 4.5.5 compiled email-template strings via lodash `_.template`
|
||||
// without restricting the interpolation regex. An authenticated admin
|
||||
// could PUT /users-permissions/email-templates with a payload whose
|
||||
// `subject` / `text` / `html` field contained a lodash `<% ... %>`
|
||||
// evaluate block, which lodash compiles into a JavaScript Function. When
|
||||
// the email service rendered the template, the embedded JavaScript
|
||||
// executed in the Strapi process context (RCE).
|
||||
//
|
||||
// Trims: `keysDeep` import, `missingAttributes` early-throw, plugin
|
||||
// provider chain, the surrounding controller layer that translates
|
||||
// `PUT /email-templates` into a call to `sendTemplatedEmail`. The
|
||||
// load-bearing sink call `_.template(emailTemplate[attribute])` is
|
||||
// verbatim; the trailing `(data)` invocation of the compiled
|
||||
// function is split off so the engine sees the SSTI sink directly
|
||||
// rather than as the inner call of a `f()()` chain.
|
||||
|
||||
'use strict';
|
||||
|
||||
const _ = require('lodash');
|
||||
const express = require('express');
|
||||
const app = express();
|
||||
app.use(express.json());
|
||||
|
||||
const sendTemplatedEmail = (emailOptions = {}, emailTemplate = {}, data = {}) => {
|
||||
const attributes = ['subject', 'text', 'html'];
|
||||
const templatedAttributes = {};
|
||||
for (const attribute of attributes) {
|
||||
if (emailTemplate[attribute]) {
|
||||
const compiled = _.template(emailTemplate[attribute]);
|
||||
templatedAttributes[attribute] = compiled(data);
|
||||
}
|
||||
}
|
||||
return templatedAttributes;
|
||||
};
|
||||
|
||||
app.put('/users-permissions/email-templates', (req, res) => {
|
||||
sendTemplatedEmail({}, req.body.emailTemplate, req.body.data);
|
||||
res.sendStatus(200);
|
||||
});
|
||||
|
||||
app.listen(1337);
|
||||
103
tests/benchmark/cve_corpus/typescript/CVE-2026-25544/patched.ts
Normal file
103
tests/benchmark/cve_corpus/typescript/CVE-2026-25544/patched.ts
Normal file
|
|
@ -0,0 +1,103 @@
|
|||
// Nyx CVE benchmark fixture (patched counterpart).
|
||||
//
|
||||
// CVE: CVE-2026-25544
|
||||
// Project: Payload (payloadcms/payload)
|
||||
// License: MIT (https://github.com/payloadcms/payload/blob/main/LICENSE.md)
|
||||
// Advisory: https://github.com/payloadcms/payload/security/advisories/GHSA-xx6w-jxg9-2wh8
|
||||
// Patched: ea5a0982a21f77497b729e66d5a257c740d3f1c9 (tag v3.73.0)
|
||||
// packages/drizzle/src/postgres/createJSONQuery/index.ts:1-50
|
||||
// packages/drizzle/src/utilities/escapeSQLValue.ts:1-25
|
||||
//
|
||||
// Patched form of `sanitizeValue`: validates against `SAFE_STRING_REGEX`
|
||||
// and rejects anything containing `\` or `"` so the user-supplied value
|
||||
// can no longer escape the surrounding SQL string literal. Backslashes
|
||||
// and double quotes that survive the regex are still escaped before
|
||||
// interpolation. Non-string values are coerced or rejected; an APIError
|
||||
// is thrown for any value that does not match the safe shape.
|
||||
//
|
||||
// Trims: the upstream patch lives in the @payloadcms/drizzle package.
|
||||
// `SAFE_STRING_REGEX`, `sanitizeValue`, and `createJSONQuery` are copied
|
||||
// verbatim from v3.73.0; the Express handler is the same scaffolding as
|
||||
// the vulnerable counterpart so the diff is one-for-one.
|
||||
|
||||
import express, { Request, Response } from 'express';
|
||||
|
||||
type CreateJSONQueryArgs = {
|
||||
column: string | { name: string };
|
||||
operator: string;
|
||||
pathSegments: string[];
|
||||
value: unknown;
|
||||
};
|
||||
|
||||
class APIError extends Error {
|
||||
constructor(message: string, public status: number) {
|
||||
super(message);
|
||||
}
|
||||
}
|
||||
|
||||
export const SAFE_STRING_REGEX = /^[\w @.\-+:]*$/;
|
||||
|
||||
const operatorMap: Record<string, string> = {
|
||||
contains: '~',
|
||||
equals: '==',
|
||||
in: 'in',
|
||||
like: 'like_regex',
|
||||
not_equals: '!=',
|
||||
not_in: 'in',
|
||||
not_like: '!like_regex',
|
||||
};
|
||||
|
||||
const sanitizeValue = (value: unknown, operator?: string): string => {
|
||||
if (value === null) {
|
||||
return `NULL`;
|
||||
}
|
||||
|
||||
if (typeof value === 'number' || typeof value === 'boolean') {
|
||||
return `${value}`;
|
||||
}
|
||||
|
||||
if (typeof value !== 'string') {
|
||||
throw new Error('Invalid value type');
|
||||
}
|
||||
|
||||
if (!SAFE_STRING_REGEX.test(value)) {
|
||||
throw new APIError(`${value} is not allowed as a JSON query value`, 400);
|
||||
}
|
||||
|
||||
const escaped = value.replace(/\\/g, '\\\\').replace(/"/g, '\\"');
|
||||
|
||||
const prefix = ['like', 'not_like'].includes(operator ?? '') ? '(?i)' : '';
|
||||
|
||||
return `"${prefix}${escaped}"`;
|
||||
};
|
||||
|
||||
export const createJSONQuery = ({ column, operator, pathSegments, value }: CreateJSONQueryArgs) => {
|
||||
const columnName = typeof column === 'object' ? column.name : column;
|
||||
const jsonPaths = pathSegments
|
||||
.slice(1)
|
||||
.map((key) => {
|
||||
return `${key}[*]`;
|
||||
})
|
||||
.join('.');
|
||||
|
||||
const fullPath = pathSegments.length === 1 ? '$[*]' : `$.${jsonPaths}`;
|
||||
|
||||
return `jsonb_path_exists(${columnName}, '${fullPath} ? (@ ${operatorMap[operator]} ${sanitizeValue(value, operator)})')`;
|
||||
};
|
||||
|
||||
declare const db: { execute: (sql: string) => Promise<unknown> };
|
||||
|
||||
const app = express();
|
||||
app.use(express.json());
|
||||
|
||||
app.post('/query', async (req: Request, res: Response) => {
|
||||
const userValue = req.body.filter as string;
|
||||
const sql = createJSONQuery({
|
||||
column: 'data',
|
||||
operator: 'equals',
|
||||
pathSegments: ['$', 'name'],
|
||||
value: userValue,
|
||||
});
|
||||
const result = await db.execute(sql);
|
||||
res.json(result);
|
||||
});
|
||||
|
|
@ -0,0 +1,82 @@
|
|||
// Nyx CVE benchmark fixture.
|
||||
//
|
||||
// CVE: CVE-2026-25544
|
||||
// Project: Payload (payloadcms/payload)
|
||||
// License: MIT (https://github.com/payloadcms/payload/blob/main/LICENSE.md)
|
||||
// Advisory: https://github.com/payloadcms/payload/security/advisories/GHSA-xx6w-jxg9-2wh8
|
||||
// Vulnerable: 625bb8c05293dece82bb89c2c5a1467aaead9a6a (tag v3.72.0)
|
||||
// packages/drizzle/src/postgres/createJSONQuery/index.ts:1-50
|
||||
//
|
||||
// Payload < v3.73.0 embedded user input into a Postgres `jsonb_path_exists`
|
||||
// SQL fragment via raw template-string interpolation. `sanitizeValue`
|
||||
// double-quoted the string but did not escape backslashes or quotes, so a
|
||||
// crafted JSON-query value could close the SQL string literal and inject
|
||||
// arbitrary SQL. The Drizzle adapter then executed that string via
|
||||
// `db.execute(sql)`. Affected adapters: db-postgres, db-vercel-postgres,
|
||||
// db-sqlite, db-d1-sqlite (per advisory). Class: SQL injection.
|
||||
//
|
||||
// Trims: original is part of a 3-package adapter wired through PayloadCMS
|
||||
// service classes (`@payloadcms/db-postgres` -> `@payloadcms/drizzle` ->
|
||||
// `payload`). The Express handler below is scaffolding so the single-file
|
||||
// scanner sees the user-input -> sanitizeValue -> sql -> db.execute flow.
|
||||
// `operatorMap`, `sanitizeValue`, and `createJSONQuery` are copied
|
||||
// verbatim from the upstream file at v3.72.0.
|
||||
|
||||
import express, { Request, Response } from 'express';
|
||||
|
||||
type CreateJSONQueryArgs = {
|
||||
column: string | { name: string };
|
||||
operator: string;
|
||||
pathSegments: string[];
|
||||
value: unknown;
|
||||
};
|
||||
|
||||
const operatorMap: Record<string, string> = {
|
||||
contains: '~',
|
||||
equals: '==',
|
||||
in: 'in',
|
||||
like: 'like_regex',
|
||||
not_equals: '!=',
|
||||
not_in: 'in',
|
||||
not_like: '!like_regex',
|
||||
};
|
||||
|
||||
const sanitizeValue = (value: unknown, operator?: string) => {
|
||||
if (typeof value === 'string') {
|
||||
// ignore casing with like or not_like
|
||||
return `"${['like', 'not_like'].includes(operator) ? '(?i)' : ''}${value}"`;
|
||||
}
|
||||
|
||||
return value as string;
|
||||
};
|
||||
|
||||
export const createJSONQuery = ({ column, operator, pathSegments, value }: CreateJSONQueryArgs) => {
|
||||
const columnName = typeof column === 'object' ? column.name : column;
|
||||
const jsonPaths = pathSegments
|
||||
.slice(1)
|
||||
.map((key) => {
|
||||
return `${key}[*]`;
|
||||
})
|
||||
.join('.');
|
||||
|
||||
const fullPath = pathSegments.length === 1 ? '$[*]' : `$.${jsonPaths}`;
|
||||
|
||||
return `jsonb_path_exists(${columnName}, '${fullPath} ? (@ ${operatorMap[operator]} ${sanitizeValue(value, operator)})')`;
|
||||
};
|
||||
|
||||
declare const db: { execute: (sql: string) => Promise<unknown> };
|
||||
|
||||
const app = express();
|
||||
app.use(express.json());
|
||||
|
||||
app.post('/query', async (req: Request, res: Response) => {
|
||||
const userValue = req.body.filter as string;
|
||||
const sql = createJSONQuery({
|
||||
column: 'data',
|
||||
operator: 'equals',
|
||||
pathSegments: ['$', 'name'],
|
||||
value: userValue,
|
||||
});
|
||||
const result = await db.execute(sql);
|
||||
res.json(result);
|
||||
});
|
||||
|
|
@ -3,7 +3,7 @@
|
|||
"metadata": {
|
||||
"description": "Nyx benchmark ground truth",
|
||||
"created": "2026-03-20",
|
||||
"corpus_size": 492
|
||||
"corpus_size": 507
|
||||
},
|
||||
"cases": [
|
||||
{
|
||||
|
|
@ -9657,6 +9657,62 @@
|
|||
],
|
||||
"notes": "CVE-2025-64430 patched counterpart: URI-backed file upload removed entirely; no http.get on user input"
|
||||
},
|
||||
{
|
||||
"case_id": "cve-js-2023-22621-vulnerable",
|
||||
"file": "cve_corpus/javascript/CVE-2023-22621/vulnerable.js",
|
||||
"language": "javascript",
|
||||
"is_vulnerable": true,
|
||||
"vuln_class": "code_exec",
|
||||
"cwe": "CWE-1336",
|
||||
"provenance": "real_cve",
|
||||
"equivalence_tier": "exact",
|
||||
"match_mode": "rule_match",
|
||||
"expected_rule_ids": [
|
||||
"taint-unsanitised-flow"
|
||||
],
|
||||
"allowed_alternative_rule_ids": [],
|
||||
"forbidden_rule_ids": [],
|
||||
"expected_severity": "MEDIUM",
|
||||
"expected_category": "Security",
|
||||
"expected_sink_lines": [],
|
||||
"expected_source_lines": [],
|
||||
"tags": [
|
||||
"cve",
|
||||
"strapi",
|
||||
"code_exec",
|
||||
"ssti",
|
||||
"lodash",
|
||||
"template"
|
||||
],
|
||||
"notes": "CVE-2023-22621: Strapi <=4.5.5 sendTemplatedEmail compiled lodash _.template on attacker-controlled email-template body (admin panel), enabling SSTI -> RCE via <% ... %> evaluate blocks. MIT"
|
||||
},
|
||||
{
|
||||
"case_id": "cve-js-2023-22621-patched",
|
||||
"file": "cve_corpus/javascript/CVE-2023-22621/patched.js",
|
||||
"language": "javascript",
|
||||
"is_vulnerable": false,
|
||||
"vuln_class": "safe",
|
||||
"cwe": "N/A",
|
||||
"provenance": "real_cve",
|
||||
"equivalence_tier": "exact",
|
||||
"match_mode": "file_presence",
|
||||
"expected_rule_ids": [],
|
||||
"allowed_alternative_rule_ids": [],
|
||||
"forbidden_rule_ids": [
|
||||
"taint-unsanitised-flow"
|
||||
],
|
||||
"expected_severity": null,
|
||||
"expected_category": "Security",
|
||||
"expected_sink_lines": [],
|
||||
"expected_source_lines": [],
|
||||
"tags": [
|
||||
"cve",
|
||||
"strapi",
|
||||
"patched",
|
||||
"negative"
|
||||
],
|
||||
"notes": "CVE-2023-22621 patched counterpart: _.template called with { interpolate: <strict regex>, evaluate: false, escape: false } so the lodash evaluate block compiler is disabled."
|
||||
},
|
||||
{
|
||||
"case_id": "cve-ts-2023-26159-vulnerable",
|
||||
"file": "cve_corpus/typescript/CVE-2023-26159/vulnerable.ts",
|
||||
|
|
@ -13153,6 +13209,34 @@
|
|||
"disabled": false,
|
||||
"notes": "Empty-string fallback (`process.env.X || \"\"`) is not a hardcoded secret. Distilled from /Users/elipeter/oss/cal.com/apps/api/v2/src/modules/stripe/utils/newStripeInstance.ts and ~30 sibling cal.com calendar/stripe/sendgrid integration files. Engine fix: pattern-level regex (#match? @fallback \"[^\\\"']\") in src/patterns/typescript.rs."
|
||||
},
|
||||
{
|
||||
"case_id": "ts-safe-021",
|
||||
"file": "typescript/safe/safe_validated_helper_chain.ts",
|
||||
"language": "typescript",
|
||||
"is_vulnerable": false,
|
||||
"vuln_class": "safe",
|
||||
"cwe": "N/A",
|
||||
"provenance": "synthetic",
|
||||
"equivalence_tier": "exact",
|
||||
"match_mode": "file_presence",
|
||||
"expected_rule_ids": [],
|
||||
"allowed_alternative_rule_ids": [],
|
||||
"forbidden_rule_ids": [
|
||||
"taint-unsanitised-flow"
|
||||
],
|
||||
"expected_severity": null,
|
||||
"expected_category": "Security",
|
||||
"expected_sink_lines": [],
|
||||
"expected_source_lines": [],
|
||||
"tags": [
|
||||
"validated-flow",
|
||||
"helper-validator",
|
||||
"summary-propagation",
|
||||
"cve-2026-25544"
|
||||
],
|
||||
"disabled": false,
|
||||
"notes": "Validated-flow propagation through helper chains. `sanitize` validates its first parameter via a regex allowlist; `buildQuery` interpolates the sanitised result into a SQL fragment; the handler hands the fragment to `db.execute`. Pinned by `SsaFuncSummary::validated_params_to_return` + `propagate_validated_params_to_return` (CVE-2026-25544 deep fix)."
|
||||
},
|
||||
{
|
||||
"case_id": "py-auth-decorator-001",
|
||||
"file": "python/safe/safe_login_required_decorator.py",
|
||||
|
|
@ -14098,6 +14182,94 @@
|
|||
"disabled": false,
|
||||
"notes": "`if err != nil { c.Fatalf(...) }` / `os.Exit` / `log.Fatalf` / `panic(err)` are documented terminators (Goexit-class). cfg-error-fallthrough must walk through them as terminating paths. Closes the minio test-file cluster (49+34+12+11+9+7+7 hits across xl-storage_test.go, erasure-healing_test.go, format-erasure_test.go, \u2026). Engine fix: src/cfg_analysis/error_handling.rs::call_never_returns."
|
||||
},
|
||||
{
|
||||
"case_id": "go-safe-realrepo-016",
|
||||
"file": "go/safe/safe_inner_call_close_in_arg.go",
|
||||
"language": "go",
|
||||
"is_vulnerable": false,
|
||||
"vuln_class": "safe",
|
||||
"cwe": "N/A",
|
||||
"provenance": "synthetic",
|
||||
"equivalence_tier": "exact",
|
||||
"match_mode": "rule_match",
|
||||
"expected_rule_ids": [],
|
||||
"allowed_alternative_rule_ids": [
|
||||
"state-resource-leak-possible"
|
||||
],
|
||||
"forbidden_rule_ids": [
|
||||
"state-resource-leak"
|
||||
],
|
||||
"expected_severity": null,
|
||||
"expected_category": "Security",
|
||||
"expected_sink_lines": [],
|
||||
"expected_source_lines": [],
|
||||
"tags": [
|
||||
"resource-lifecycle",
|
||||
"negative",
|
||||
"real-repo-precision-2026-05-02"
|
||||
],
|
||||
"disabled": false,
|
||||
"notes": "`require.NoError(t, f.Close())` and `errs = append(errs, f.Close())` shapes \u2014 the inner-call release was invisible because the CFG's per-statement Call node carries the OUTER callee. Engine fix: src/state/transfer.rs::apply_call now walks info.arg_callees after the direct-release branch and marks the bare-receiver SymbolId CLOSED. Closes 9+ hits across prometheus tsdb test files."
|
||||
},
|
||||
{
|
||||
"case_id": "go-safe-realrepo-017",
|
||||
"file": "go/safe/safe_struct_field_resource_owned_by_struct.go",
|
||||
"language": "go",
|
||||
"is_vulnerable": false,
|
||||
"vuln_class": "safe",
|
||||
"cwe": "N/A",
|
||||
"provenance": "synthetic",
|
||||
"equivalence_tier": "exact",
|
||||
"match_mode": "rule_match",
|
||||
"expected_rule_ids": [],
|
||||
"allowed_alternative_rule_ids": [],
|
||||
"forbidden_rule_ids": [
|
||||
"state-resource-leak",
|
||||
"state-resource-leak-possible",
|
||||
"cfg-resource-leak"
|
||||
],
|
||||
"expected_severity": null,
|
||||
"expected_category": "Security",
|
||||
"expected_sink_lines": [],
|
||||
"expected_source_lines": [],
|
||||
"tags": [
|
||||
"resource-lifecycle",
|
||||
"negative",
|
||||
"real-repo-precision-2026-05-02"
|
||||
],
|
||||
"disabled": false,
|
||||
"notes": "`b.cpuprof = os.Create(...)` shape \u2014 member-expression LHS is an ownership transfer to the containing struct, not a local acquisition. Closure responsibility belongs to a paired `stopProfiling()` method. Engine fix: src/state/transfer.rs::apply_call gates the acquire on !define_is_field_lhs; src/cfg_analysis/resources.rs::run mirrors the gate. Closes the prometheus cmd/promtool/tsdb.go::startProfiling cluster (4 findings on b.cpuprof, b.memprof, b.blockprof, b.mtxprof)."
|
||||
},
|
||||
{
|
||||
"case_id": "go-vuln-realrepo-018",
|
||||
"file": "go/safe/vuln_resource_leak_no_close.go",
|
||||
"language": "go",
|
||||
"is_vulnerable": true,
|
||||
"vuln_class": "resource",
|
||||
"cwe": "CWE-404",
|
||||
"provenance": "synthetic",
|
||||
"equivalence_tier": "exact",
|
||||
"match_mode": "rule_match",
|
||||
"expected_rule_ids": [
|
||||
"state-resource-leak"
|
||||
],
|
||||
"allowed_alternative_rule_ids": [
|
||||
"cfg-resource-leak",
|
||||
"state-resource-leak-possible"
|
||||
],
|
||||
"forbidden_rule_ids": [],
|
||||
"expected_severity": "MEDIUM",
|
||||
"expected_category": "Security",
|
||||
"expected_sink_lines": [],
|
||||
"expected_source_lines": [],
|
||||
"tags": [
|
||||
"resource-lifecycle",
|
||||
"positive",
|
||||
"real-repo-precision-2026-05-02"
|
||||
],
|
||||
"disabled": false,
|
||||
"notes": "Recall guard for the inner-call-arg / member-LHS fixes. Bare-identifier `f := os.OpenFile(...)` with no `f.Close()` anywhere must still fire the resource-leak rule."
|
||||
},
|
||||
{
|
||||
"case_id": "go-auth-realrepo-001",
|
||||
"file": "go/auth/vuln_repo_findbyid_no_auth.go",
|
||||
|
|
@ -14592,6 +14764,117 @@
|
|||
"disabled": false,
|
||||
"notes": "Negative-counterpart guard for the LocalCollection / parameter-name fixes: handler takes a HashMap typed param (in-memory bookkeeping) but ALSO calls `db.update_owner(req.target_user_id, ...)` (real DbMutation). The cache mutation must not blanket-suppress the persistent-store mutation \u2014 the rule must still fire on `db.update_owner`."
|
||||
},
|
||||
{
|
||||
"case_id": "rs-auth-realrepo-014",
|
||||
"file": "rust/auth/safe_actix_guarded_data_extractor.rs",
|
||||
"language": "rust",
|
||||
"is_vulnerable": false,
|
||||
"vuln_class": "safe",
|
||||
"cwe": "N/A",
|
||||
"provenance": "synthetic",
|
||||
"equivalence_tier": "exact",
|
||||
"match_mode": "rule_match",
|
||||
"expected_rule_ids": [],
|
||||
"allowed_alternative_rule_ids": [],
|
||||
"forbidden_rule_ids": [
|
||||
"rs.auth.missing_ownership_check"
|
||||
],
|
||||
"expected_severity": null,
|
||||
"expected_category": "Security",
|
||||
"expected_sink_lines": [],
|
||||
"expected_source_lines": [],
|
||||
"tags": [
|
||||
"auth",
|
||||
"negative",
|
||||
"real-repo-precision-2026-05-02",
|
||||
"noise-budget-zero"
|
||||
],
|
||||
"disabled": false,
|
||||
"notes": "Meilisearch `GuardedData<ActionPolicy<{ actions::KEYS_GET }>, Data<AuthController>>` typed extractor on actix-web routes registered via `#[routes::path(..)]` attribute macros (no `.route()` builder, so `collect_routes` doesn't attach the handler). The new typed-extractor fallback pass in `actix_web::extract` walks every Function-kind unit and applies `guard_calls_for_handler`; the `Guarded`-prefix `policy_guard_names` recogniser injects `AuthCheckKind::Other` with `is_route_level: true`, so `auth_check_covers_subject`'s route-level short-circuit suppresses missing-ownership-check on path-derived sinks."
|
||||
},
|
||||
{
|
||||
"case_id": "rs-auth-realrepo-015",
|
||||
"file": "rust/auth/unsafe_actix_no_guarded_data_extractor.rs",
|
||||
"language": "rust",
|
||||
"is_vulnerable": true,
|
||||
"vuln_class": "auth",
|
||||
"cwe": "CWE-285",
|
||||
"provenance": "synthetic",
|
||||
"equivalence_tier": "exact",
|
||||
"match_mode": "rule_match",
|
||||
"expected_rule_ids": [
|
||||
"rs.auth.missing_ownership_check"
|
||||
],
|
||||
"allowed_alternative_rule_ids": [],
|
||||
"forbidden_rule_ids": [],
|
||||
"expected_severity": "Medium",
|
||||
"expected_category": "Security",
|
||||
"expected_sink_lines": [],
|
||||
"expected_source_lines": [],
|
||||
"tags": [
|
||||
"auth",
|
||||
"positive",
|
||||
"real-repo-precision-2026-05-02"
|
||||
],
|
||||
"disabled": false,
|
||||
"notes": "Negative-counterpart guard for the `GuardedData` typed-extractor recogniser: same handler shape but the wrapper is replaced by a bare `Data<AuthController>` (no policy enforcement implied). An over-broad `policy_guard_names` recogniser would silence this; the Guarded-prefix matcher must NOT fire on bare `Data<...>`, so the rule still flags the path-derived `uid` flowing into `auth_controller.get_key`."
|
||||
},
|
||||
{
|
||||
"case_id": "rs-auth-realrepo-016",
|
||||
"file": "rust/safe/safe_non_web_rust_project",
|
||||
"language": "rust",
|
||||
"is_vulnerable": false,
|
||||
"vuln_class": "safe",
|
||||
"cwe": "",
|
||||
"provenance": "real-repo-precision-2026-05-02",
|
||||
"equivalence_tier": "exact",
|
||||
"match_mode": "rule_match",
|
||||
"expected_rule_ids": [],
|
||||
"allowed_alternative_rule_ids": [],
|
||||
"forbidden_rule_ids": [
|
||||
"rs.auth.missing_ownership_check",
|
||||
"rs.auth.stale_authorization",
|
||||
"rs.auth.token_override_without_validation"
|
||||
],
|
||||
"expected_severity": null,
|
||||
"expected_category": null,
|
||||
"expected_sink_lines": [],
|
||||
"expected_source_lines": [],
|
||||
"tags": [
|
||||
"auth",
|
||||
"shape-safe",
|
||||
"real-repo-precision-2026-05-02"
|
||||
],
|
||||
"disabled": false,
|
||||
"notes": "Real-repo precision guard distilled from zed (desktop GUI / DAP debugger / agent) crates: `<thing>_id` parameters on internal helpers AND `session.foo` chains on debug-session handles must NOT count as user-input evidence in a Rust project whose Cargo.toml names no web framework. `lang_has_web_framework(\"rust\")` returns Some(false) and the gate suppresses both step-2 (context_inputs) and step-3 (param-name) heuristics."
|
||||
},
|
||||
{
|
||||
"case_id": "rs-auth-realrepo-017",
|
||||
"file": "rust/auth/unsafe_actix_web_project_no_check",
|
||||
"language": "rust",
|
||||
"is_vulnerable": true,
|
||||
"vuln_class": "auth",
|
||||
"cwe": "CWE-285",
|
||||
"provenance": "real-repo-precision-2026-05-02",
|
||||
"equivalence_tier": "exact",
|
||||
"match_mode": "rule_match",
|
||||
"expected_rule_ids": [
|
||||
"rs.auth.missing_ownership_check"
|
||||
],
|
||||
"allowed_alternative_rule_ids": [],
|
||||
"forbidden_rule_ids": [],
|
||||
"expected_severity": "High",
|
||||
"expected_category": "Security",
|
||||
"expected_sink_lines": [],
|
||||
"expected_source_lines": [],
|
||||
"tags": [
|
||||
"auth",
|
||||
"positive",
|
||||
"real-repo-precision-2026-05-02"
|
||||
],
|
||||
"disabled": false,
|
||||
"notes": "Regression counterpart to `rs-auth-realrepo-016`: same helper shape with no upstream auth check, but the project's manifest names `actix-web` so `lang_has_web_framework(\"rust\")` returns Some(true) and the param-name arm of `unit_has_user_input_evidence` stays on. Asserts the project-level web-framework gate doesn't silence findings in real Rust web projects."
|
||||
},
|
||||
{
|
||||
"case_id": "ruby-safe-ar-query-shapes-001",
|
||||
"file": "ruby/safe/safe_active_record_query_shapes.rb",
|
||||
|
|
@ -15585,6 +15868,165 @@
|
|||
],
|
||||
"disabled": false,
|
||||
"notes": "fgets stdin user input echoed into curl_easy_setopt CURLOPT_POSTFIELDS at fixed URL; sensitivity-gate suppresses Plain-tier sources."
|
||||
},
|
||||
{
|
||||
"case_id": "py-auth-realrepo-008",
|
||||
"file": "python/safe/safe_django_orm_caller_scoped_entity.py",
|
||||
"language": "python",
|
||||
"is_vulnerable": false,
|
||||
"vuln_class": "safe",
|
||||
"cwe": "N/A",
|
||||
"provenance": "real-repo",
|
||||
"equivalence_tier": "exact",
|
||||
"match_mode": "rule_match",
|
||||
"expected_rule_ids": [],
|
||||
"allowed_alternative_rule_ids": [],
|
||||
"forbidden_rule_ids": [
|
||||
"py.auth.missing_ownership_check"
|
||||
],
|
||||
"expected_severity": null,
|
||||
"expected_category": "Security",
|
||||
"expected_sink_lines": [],
|
||||
"expected_source_lines": [],
|
||||
"tags": [
|
||||
"auth",
|
||||
"django",
|
||||
"real-repo-precision-2026-05-02"
|
||||
],
|
||||
"disabled": false,
|
||||
"notes": "Distilled from sentry api/helpers/environments.py::get_environments and api/endpoints/organization_releases.py::_filter_releases_by_query. `<entity>.id` for a unit param named after a scope-bearing domain entity (organization, project, ...) is the ownership scope inherited from the caller, not a user-controlled target. Pinned by is_caller_scope_entity_subject in src/auth_analysis/checks.rs. Also exercises the keyword_argument-key fix in extract_value_refs (Environment.objects.filter(organization_id=...) — the kwarg key `organization_id` is the ORM column name, not a subject)."
|
||||
},
|
||||
{
|
||||
"case_id": "py-auth-realrepo-009",
|
||||
"file": "python/auth/vuln_user_id_param_no_auth.py",
|
||||
"language": "python",
|
||||
"is_vulnerable": true,
|
||||
"vuln_class": "auth",
|
||||
"cwe": "CWE-862",
|
||||
"provenance": "real-repo",
|
||||
"equivalence_tier": "exact",
|
||||
"match_mode": "rule_match",
|
||||
"expected_rule_ids": [
|
||||
"py.auth.missing_ownership_check"
|
||||
],
|
||||
"allowed_alternative_rule_ids": [],
|
||||
"forbidden_rule_ids": [],
|
||||
"expected_severity": "MEDIUM",
|
||||
"expected_category": "Security",
|
||||
"expected_sink_lines": [
|
||||
[
|
||||
16,
|
||||
16
|
||||
],
|
||||
[
|
||||
20,
|
||||
20
|
||||
]
|
||||
],
|
||||
"expected_source_lines": [],
|
||||
"tags": [
|
||||
"auth",
|
||||
"django",
|
||||
"real-repo-precision-2026-05-02"
|
||||
],
|
||||
"disabled": false,
|
||||
"notes": "Vulnerable counterpart to py-auth-realrepo-008: helper takes a user-supplied `project_id` (id-like name) and queries Project.objects.filter(id=project_id) without any preceding ownership check. Regression guard: the caller-scope-entity exemption must NOT suppress when the param is itself an id-like user input."
|
||||
},
|
||||
{
|
||||
"case_id": "py-auth-realrepo-010",
|
||||
"file": "python/safe/safe_mock_patch_test_method.py",
|
||||
"language": "python",
|
||||
"is_vulnerable": false,
|
||||
"vuln_class": "safe",
|
||||
"cwe": "N/A",
|
||||
"provenance": "real-repo",
|
||||
"equivalence_tier": "exact",
|
||||
"match_mode": "rule_match",
|
||||
"expected_rule_ids": [],
|
||||
"allowed_alternative_rule_ids": [],
|
||||
"forbidden_rule_ids": [
|
||||
"py.auth.missing_ownership_check",
|
||||
"py.auth.token_override_without_validation"
|
||||
],
|
||||
"expected_severity": null,
|
||||
"expected_category": "Security",
|
||||
"expected_sink_lines": [],
|
||||
"expected_source_lines": [],
|
||||
"tags": [
|
||||
"auth",
|
||||
"pytest",
|
||||
"real-repo-precision-2026-05-02"
|
||||
],
|
||||
"disabled": false,
|
||||
"notes": "Distilled from airflow providers/google/tests/unit/google/cloud/hooks/test_dlp.py: pytest test method decorated with `@mock.patch(\"...\")` was being attached as a Flask `PATCH` route handler because bare_method_name(\"mock.patch\") == \"patch\". Fix: parse_flask_route_decorator short-circuits on known test-framework decorator vocabulary (mock.patch, unittest.mock.patch, monkeypatch.setattr, pytest.mark.parametrize)."
|
||||
},
|
||||
{
|
||||
"case_id": "cve-ts-2026-25544-vulnerable",
|
||||
"file": "cve_corpus/typescript/CVE-2026-25544/vulnerable.ts",
|
||||
"language": "typescript",
|
||||
"is_vulnerable": true,
|
||||
"vuln_class": "sqli",
|
||||
"cwe": "CWE-89",
|
||||
"provenance": "real_cve",
|
||||
"equivalence_tier": "exact",
|
||||
"match_mode": "rule_match",
|
||||
"expected_rule_ids": [
|
||||
"taint-unsanitised-flow"
|
||||
],
|
||||
"allowed_alternative_rule_ids": [],
|
||||
"forbidden_rule_ids": [],
|
||||
"expected_severity": "MEDIUM",
|
||||
"expected_category": "Security",
|
||||
"expected_sink_lines": [
|
||||
[
|
||||
80,
|
||||
81
|
||||
]
|
||||
],
|
||||
"expected_source_lines": [
|
||||
[
|
||||
73,
|
||||
73
|
||||
]
|
||||
],
|
||||
"tags": [
|
||||
"cve",
|
||||
"payload",
|
||||
"sqli",
|
||||
"vulnerable"
|
||||
],
|
||||
"disabled": true,
|
||||
"disabled_reason": "Validated-flow propagation through SSA-derived values and helper-summary returns is missing. The patched counterpart applies a regex allowlist (`SAFE_STRING_REGEX.test(value)` throw) PLUS a `replace()` escape chain inside `sanitizeValue`, then interpolates the result into a SQL template literal in `createJSONQuery` and returns the string to the handler, which calls `db.execute(sql)`. This session landed `classify_condition` recognition of `<*regex*>.test(value)` / `<*pattern*>.test(value)` as a ValidationCall whose target is the call's first arg (covered by `path_state::tests::target_regex_test_first_arg`, `target_regex_test_pattern_receiver`, `target_test_non_regex_receiver_is_not_validation`, plus the SSA-level `regex_test_allowlist_narrowing_clears_direct_flow` integration test). But validated_must is per-symbol and consulted only at the sink site; it does NOT propagate through the SSA Assign that templates a clean `value` into a derived `sql` string, nor does it ride a helper's `param_to_return` summary back into a caller. Disabled until that propagation path lands. Tracked in CVE_DEFERRED.md.",
|
||||
"notes": "CVE-2026-25544: Payload `sanitizeValue` SQL injection via Postgres jsonb_path_exists template-string interpolation. Vulnerable form (`@payloadcms/drizzle@v3.72.0`, MIT) lets attacker-controlled JSON-query value escape the surrounding SQL string literal because `sanitizeValue` only double-quotes it without escaping `\\`/`\"`. Disabled pending validated-flow propagation engine work, see disabled_reason."
|
||||
},
|
||||
{
|
||||
"case_id": "cve-ts-2026-25544-patched",
|
||||
"file": "cve_corpus/typescript/CVE-2026-25544/patched.ts",
|
||||
"language": "typescript",
|
||||
"is_vulnerable": false,
|
||||
"vuln_class": "safe",
|
||||
"cwe": "N/A",
|
||||
"provenance": "real_cve",
|
||||
"equivalence_tier": "exact",
|
||||
"match_mode": "file_presence",
|
||||
"expected_rule_ids": [],
|
||||
"allowed_alternative_rule_ids": [],
|
||||
"forbidden_rule_ids": [
|
||||
"taint-unsanitised-flow"
|
||||
],
|
||||
"expected_severity": null,
|
||||
"expected_category": "Security",
|
||||
"expected_sink_lines": [],
|
||||
"expected_source_lines": [],
|
||||
"tags": [
|
||||
"cve",
|
||||
"payload",
|
||||
"safe",
|
||||
"patched"
|
||||
],
|
||||
"disabled": true,
|
||||
"disabled_reason": "Sibling of cve-ts-2026-25544-vulnerable. Disabled together until validated-flow summary propagation lands. See vulnerable counterpart's disabled_reason for the engine gap.",
|
||||
"notes": "Patched form of `sanitizeValue` from `@payloadcms/drizzle@v3.73.0` (MIT). Disabled together with its vulnerable counterpart pending validated-flow propagation work."
|
||||
}
|
||||
]
|
||||
}
|
||||
|
|
@ -1,6 +1,6 @@
|
|||
{
|
||||
"benchmark_version": "1.0",
|
||||
"timestamp": "2026-05-02T07:03:06Z",
|
||||
"timestamp": "2026-05-02T19:35:12Z",
|
||||
"scanner_version": "0.6.0",
|
||||
"scanner_config": {
|
||||
"analysis_mode": "Full",
|
||||
|
|
@ -9,10 +9,10 @@
|
|||
"state_analysis_enabled": true,
|
||||
"worker_threads": 1
|
||||
},
|
||||
"ground_truth_hash": "sha256:ba8f5f6e20ce478b6032b1df98e5dc57a7b7a8ced8f1d3294dc811034bc6fc3c",
|
||||
"corpus_size": 492,
|
||||
"cases_run": 491,
|
||||
"cases_skipped": 1,
|
||||
"ground_truth_hash": "sha256:de2df25545527c2c90c665a5d4db257fb8f0d7aefe16eb742ee8e70f7de55e99",
|
||||
"corpus_size": 507,
|
||||
"cases_run": 504,
|
||||
"cases_skipped": 3,
|
||||
"outcomes": [
|
||||
{
|
||||
"case_id": "c-buf-001",
|
||||
|
|
@ -1478,6 +1478,40 @@
|
|||
"security_finding_count": 2,
|
||||
"non_security_finding_count": 0
|
||||
},
|
||||
{
|
||||
"case_id": "cve-js-2023-22621-patched",
|
||||
"file": "cve_corpus/javascript/CVE-2023-22621/patched.js",
|
||||
"language": "javascript",
|
||||
"vuln_class": "safe",
|
||||
"is_vulnerable": false,
|
||||
"outcome_file_level": "TN",
|
||||
"outcome_rule_level": "TN",
|
||||
"outcome_location_level": null,
|
||||
"matched_rule_ids": [],
|
||||
"unexpected_rule_ids": [],
|
||||
"all_finding_ids": [],
|
||||
"security_finding_count": 0,
|
||||
"non_security_finding_count": 0
|
||||
},
|
||||
{
|
||||
"case_id": "cve-js-2023-22621-vulnerable",
|
||||
"file": "cve_corpus/javascript/CVE-2023-22621/vulnerable.js",
|
||||
"language": "javascript",
|
||||
"vuln_class": "code_exec",
|
||||
"is_vulnerable": true,
|
||||
"outcome_file_level": "TP",
|
||||
"outcome_rule_level": "TP",
|
||||
"outcome_location_level": null,
|
||||
"matched_rule_ids": [
|
||||
"taint-unsanitised-flow (source 46:26)"
|
||||
],
|
||||
"unexpected_rule_ids": [],
|
||||
"all_finding_ids": [
|
||||
"taint-unsanitised-flow (source 46:26)"
|
||||
],
|
||||
"security_finding_count": 1,
|
||||
"non_security_finding_count": 0
|
||||
},
|
||||
{
|
||||
"case_id": "cve-js-2025-64430-patched",
|
||||
"file": "cve_corpus/javascript/CVE-2025-64430/patched.js",
|
||||
|
|
@ -2723,6 +2757,42 @@
|
|||
"security_finding_count": 0,
|
||||
"non_security_finding_count": 0
|
||||
},
|
||||
{
|
||||
"case_id": "go-safe-realrepo-016",
|
||||
"file": "go/safe/safe_inner_call_close_in_arg.go",
|
||||
"language": "go",
|
||||
"vuln_class": "safe",
|
||||
"is_vulnerable": false,
|
||||
"outcome_file_level": "FP",
|
||||
"outcome_rule_level": "FP",
|
||||
"outcome_location_level": null,
|
||||
"matched_rule_ids": [],
|
||||
"unexpected_rule_ids": [
|
||||
"state-resource-leak-possible",
|
||||
"state-resource-leak-possible"
|
||||
],
|
||||
"all_finding_ids": [
|
||||
"state-resource-leak-possible",
|
||||
"state-resource-leak-possible"
|
||||
],
|
||||
"security_finding_count": 2,
|
||||
"non_security_finding_count": 0
|
||||
},
|
||||
{
|
||||
"case_id": "go-safe-realrepo-017",
|
||||
"file": "go/safe/safe_struct_field_resource_owned_by_struct.go",
|
||||
"language": "go",
|
||||
"vuln_class": "safe",
|
||||
"is_vulnerable": false,
|
||||
"outcome_file_level": "TN",
|
||||
"outcome_rule_level": "TN",
|
||||
"outcome_location_level": null,
|
||||
"matched_rule_ids": [],
|
||||
"unexpected_rule_ids": [],
|
||||
"all_finding_ids": [],
|
||||
"security_finding_count": 0,
|
||||
"non_security_finding_count": 0
|
||||
},
|
||||
{
|
||||
"case_id": "go-sqli-001",
|
||||
"file": "go/sqli/sqli_concat.go",
|
||||
|
|
@ -2883,6 +2953,27 @@
|
|||
"security_finding_count": 0,
|
||||
"non_security_finding_count": 0
|
||||
},
|
||||
{
|
||||
"case_id": "go-vuln-realrepo-018",
|
||||
"file": "go/safe/vuln_resource_leak_no_close.go",
|
||||
"language": "go",
|
||||
"vuln_class": "resource",
|
||||
"is_vulnerable": true,
|
||||
"outcome_file_level": "TP",
|
||||
"outcome_rule_level": "TP",
|
||||
"outcome_location_level": null,
|
||||
"matched_rule_ids": [
|
||||
"state-resource-leak",
|
||||
"cfg-resource-leak"
|
||||
],
|
||||
"unexpected_rule_ids": [],
|
||||
"all_finding_ids": [
|
||||
"state-resource-leak",
|
||||
"cfg-resource-leak"
|
||||
],
|
||||
"security_finding_count": 2,
|
||||
"non_security_finding_count": 0
|
||||
},
|
||||
{
|
||||
"case_id": "go-xss-001",
|
||||
"file": "go/xss/xss_fprintf.go",
|
||||
|
|
@ -5123,6 +5214,57 @@
|
|||
"security_finding_count": 1,
|
||||
"non_security_finding_count": 0
|
||||
},
|
||||
{
|
||||
"case_id": "py-auth-realrepo-008",
|
||||
"file": "python/safe/safe_django_orm_caller_scoped_entity.py",
|
||||
"language": "python",
|
||||
"vuln_class": "safe",
|
||||
"is_vulnerable": false,
|
||||
"outcome_file_level": "TN",
|
||||
"outcome_rule_level": "TN",
|
||||
"outcome_location_level": null,
|
||||
"matched_rule_ids": [],
|
||||
"unexpected_rule_ids": [],
|
||||
"all_finding_ids": [],
|
||||
"security_finding_count": 0,
|
||||
"non_security_finding_count": 0
|
||||
},
|
||||
{
|
||||
"case_id": "py-auth-realrepo-009",
|
||||
"file": "python/auth/vuln_user_id_param_no_auth.py",
|
||||
"language": "python",
|
||||
"vuln_class": "auth",
|
||||
"is_vulnerable": true,
|
||||
"outcome_file_level": "TP",
|
||||
"outcome_rule_level": "TP",
|
||||
"outcome_location_level": "TP",
|
||||
"matched_rule_ids": [
|
||||
"py.auth.missing_ownership_check",
|
||||
"py.auth.missing_ownership_check"
|
||||
],
|
||||
"unexpected_rule_ids": [],
|
||||
"all_finding_ids": [
|
||||
"py.auth.missing_ownership_check",
|
||||
"py.auth.missing_ownership_check"
|
||||
],
|
||||
"security_finding_count": 2,
|
||||
"non_security_finding_count": 0
|
||||
},
|
||||
{
|
||||
"case_id": "py-auth-realrepo-010",
|
||||
"file": "python/safe/safe_mock_patch_test_method.py",
|
||||
"language": "python",
|
||||
"vuln_class": "safe",
|
||||
"is_vulnerable": false,
|
||||
"outcome_file_level": "TN",
|
||||
"outcome_rule_level": "TN",
|
||||
"outcome_location_level": null,
|
||||
"matched_rule_ids": [],
|
||||
"unexpected_rule_ids": [],
|
||||
"all_finding_ids": [],
|
||||
"security_finding_count": 0,
|
||||
"non_security_finding_count": 0
|
||||
},
|
||||
{
|
||||
"case_id": "py-cmdi-001",
|
||||
"file": "python/cmdi/cmdi_direct.py",
|
||||
|
|
@ -6422,6 +6564,77 @@
|
|||
"security_finding_count": 1,
|
||||
"non_security_finding_count": 0
|
||||
},
|
||||
{
|
||||
"case_id": "rs-auth-realrepo-014",
|
||||
"file": "rust/auth/safe_actix_guarded_data_extractor.rs",
|
||||
"language": "rust",
|
||||
"vuln_class": "safe",
|
||||
"is_vulnerable": false,
|
||||
"outcome_file_level": "TN",
|
||||
"outcome_rule_level": "TN",
|
||||
"outcome_location_level": null,
|
||||
"matched_rule_ids": [],
|
||||
"unexpected_rule_ids": [],
|
||||
"all_finding_ids": [
|
||||
"rs.quality.todo"
|
||||
],
|
||||
"security_finding_count": 0,
|
||||
"non_security_finding_count": 1
|
||||
},
|
||||
{
|
||||
"case_id": "rs-auth-realrepo-015",
|
||||
"file": "rust/auth/unsafe_actix_no_guarded_data_extractor.rs",
|
||||
"language": "rust",
|
||||
"vuln_class": "auth",
|
||||
"is_vulnerable": true,
|
||||
"outcome_file_level": "TP",
|
||||
"outcome_rule_level": "TP",
|
||||
"outcome_location_level": null,
|
||||
"matched_rule_ids": [
|
||||
"rs.auth.missing_ownership_check"
|
||||
],
|
||||
"unexpected_rule_ids": [],
|
||||
"all_finding_ids": [
|
||||
"rs.quality.todo",
|
||||
"rs.auth.missing_ownership_check"
|
||||
],
|
||||
"security_finding_count": 1,
|
||||
"non_security_finding_count": 1
|
||||
},
|
||||
{
|
||||
"case_id": "rs-auth-realrepo-016",
|
||||
"file": "rust/safe/safe_non_web_rust_project",
|
||||
"language": "rust",
|
||||
"vuln_class": "safe",
|
||||
"is_vulnerable": false,
|
||||
"outcome_file_level": "TN",
|
||||
"outcome_rule_level": "TN",
|
||||
"outcome_location_level": null,
|
||||
"matched_rule_ids": [],
|
||||
"unexpected_rule_ids": [],
|
||||
"all_finding_ids": [],
|
||||
"security_finding_count": 0,
|
||||
"non_security_finding_count": 0
|
||||
},
|
||||
{
|
||||
"case_id": "rs-auth-realrepo-017",
|
||||
"file": "rust/auth/unsafe_actix_web_project_no_check",
|
||||
"language": "rust",
|
||||
"vuln_class": "auth",
|
||||
"is_vulnerable": true,
|
||||
"outcome_file_level": "TP",
|
||||
"outcome_rule_level": "TP",
|
||||
"outcome_location_level": null,
|
||||
"matched_rule_ids": [
|
||||
"rs.auth.missing_ownership_check"
|
||||
],
|
||||
"unexpected_rule_ids": [],
|
||||
"all_finding_ids": [
|
||||
"rs.auth.missing_ownership_check"
|
||||
],
|
||||
"security_finding_count": 1,
|
||||
"non_security_finding_count": 0
|
||||
},
|
||||
{
|
||||
"case_id": "rs-auth-typed-extractors-001",
|
||||
"file": "rust/auth/safe_typed_path_int_extractor.rs",
|
||||
|
|
@ -8481,6 +8694,21 @@
|
|||
"security_finding_count": 0,
|
||||
"non_security_finding_count": 0
|
||||
},
|
||||
{
|
||||
"case_id": "ts-safe-021",
|
||||
"file": "typescript/safe/safe_validated_helper_chain.ts",
|
||||
"language": "typescript",
|
||||
"vuln_class": "safe",
|
||||
"is_vulnerable": false,
|
||||
"outcome_file_level": "TN",
|
||||
"outcome_rule_level": "TN",
|
||||
"outcome_location_level": null,
|
||||
"matched_rule_ids": [],
|
||||
"unexpected_rule_ids": [],
|
||||
"all_finding_ids": [],
|
||||
"security_finding_count": 0,
|
||||
"non_security_finding_count": 0
|
||||
},
|
||||
{
|
||||
"case_id": "ts-secrets-001",
|
||||
"file": "typescript/secrets/fallback_secret.ts",
|
||||
|
|
@ -8785,22 +9013,22 @@
|
|||
}
|
||||
],
|
||||
"aggregate_file_level": {
|
||||
"tp": 244,
|
||||
"fp": 0,
|
||||
"tp": 249,
|
||||
"fp": 1,
|
||||
"fn_": 0,
|
||||
"tn": 247,
|
||||
"precision": 1.0,
|
||||
"tn": 254,
|
||||
"precision": 0.996,
|
||||
"recall": 1.0,
|
||||
"f1": 1.0
|
||||
"f1": 0.9979959919839679
|
||||
},
|
||||
"aggregate_rule_level": {
|
||||
"tp": 244,
|
||||
"fp": 0,
|
||||
"tp": 249,
|
||||
"fp": 1,
|
||||
"fn_": 0,
|
||||
"tn": 247,
|
||||
"precision": 1.0,
|
||||
"tn": 254,
|
||||
"precision": 0.996,
|
||||
"recall": 1.0,
|
||||
"f1": 1.0
|
||||
"f1": 0.9979959919839679
|
||||
},
|
||||
"by_language": {
|
||||
"c": {
|
||||
|
|
@ -8822,13 +9050,13 @@
|
|||
"f1": 1.0
|
||||
},
|
||||
"go": {
|
||||
"tp": 26,
|
||||
"fp": 0,
|
||||
"tp": 27,
|
||||
"fp": 1,
|
||||
"fn_": 0,
|
||||
"tn": 30,
|
||||
"precision": 1.0,
|
||||
"tn": 31,
|
||||
"precision": 0.9642857142857143,
|
||||
"recall": 1.0,
|
||||
"f1": 1.0
|
||||
"f1": 0.9818181818181818
|
||||
},
|
||||
"java": {
|
||||
"tp": 21,
|
||||
|
|
@ -8840,10 +9068,10 @@
|
|||
"f1": 1.0
|
||||
},
|
||||
"javascript": {
|
||||
"tp": 22,
|
||||
"tp": 23,
|
||||
"fp": 0,
|
||||
"fn_": 0,
|
||||
"tn": 28,
|
||||
"tn": 29,
|
||||
"precision": 1.0,
|
||||
"recall": 1.0,
|
||||
"f1": 1.0
|
||||
|
|
@ -8858,10 +9086,10 @@
|
|||
"f1": 1.0
|
||||
},
|
||||
"python": {
|
||||
"tp": 28,
|
||||
"tp": 29,
|
||||
"fp": 0,
|
||||
"fn_": 0,
|
||||
"tn": 30,
|
||||
"tn": 32,
|
||||
"precision": 1.0,
|
||||
"recall": 1.0,
|
||||
"f1": 1.0
|
||||
|
|
@ -8876,10 +9104,10 @@
|
|||
"f1": 1.0
|
||||
},
|
||||
"rust": {
|
||||
"tp": 35,
|
||||
"tp": 37,
|
||||
"fp": 0,
|
||||
"fn_": 0,
|
||||
"tn": 39,
|
||||
"tn": 41,
|
||||
"precision": 1.0,
|
||||
"recall": 1.0,
|
||||
"f1": 1.0
|
||||
|
|
@ -8888,7 +9116,7 @@
|
|||
"tp": 34,
|
||||
"fp": 0,
|
||||
"fn_": 0,
|
||||
"tn": 24,
|
||||
"tn": 25,
|
||||
"precision": 1.0,
|
||||
"recall": 1.0,
|
||||
"f1": 1.0
|
||||
|
|
@ -8896,7 +9124,7 @@
|
|||
},
|
||||
"by_vuln_class": {
|
||||
"auth": {
|
||||
"tp": 16,
|
||||
"tp": 19,
|
||||
"fp": 0,
|
||||
"fn_": 0,
|
||||
"tn": 0,
|
||||
|
|
@ -8923,7 +9151,7 @@
|
|||
"f1": 1.0
|
||||
},
|
||||
"code_exec": {
|
||||
"tp": 3,
|
||||
"tp": 4,
|
||||
"fp": 0,
|
||||
"fn_": 0,
|
||||
"tn": 0,
|
||||
|
|
@ -9021,15 +9249,24 @@
|
|||
"recall": 1.0,
|
||||
"f1": 1.0
|
||||
},
|
||||
"safe": {
|
||||
"tp": 0,
|
||||
"resource": {
|
||||
"tp": 1,
|
||||
"fp": 0,
|
||||
"fn_": 0,
|
||||
"tn": 247,
|
||||
"tn": 0,
|
||||
"precision": 1.0,
|
||||
"recall": 1.0,
|
||||
"f1": 1.0
|
||||
},
|
||||
"safe": {
|
||||
"tp": 0,
|
||||
"fp": 1,
|
||||
"fn_": 0,
|
||||
"tn": 254,
|
||||
"precision": 0.0,
|
||||
"recall": 1.0,
|
||||
"f1": 0.0
|
||||
},
|
||||
"secrets": {
|
||||
"tp": 1,
|
||||
"fp": 0,
|
||||
|
|
@ -9078,31 +9315,31 @@
|
|||
},
|
||||
"by_confidence": {
|
||||
">=High": {
|
||||
"tp": 74,
|
||||
"fp": 108,
|
||||
"fn_": 170,
|
||||
"tn": 139,
|
||||
"precision": 0.4065934065934066,
|
||||
"recall": 0.30327868852459017,
|
||||
"f1": 0.3474178403755868
|
||||
"tp": 78,
|
||||
"fp": 107,
|
||||
"fn_": 171,
|
||||
"tn": 148,
|
||||
"precision": 0.42162162162162165,
|
||||
"recall": 0.3132530120481928,
|
||||
"f1": 0.359447004608295
|
||||
},
|
||||
">=Low": {
|
||||
"tp": 75,
|
||||
"fp": 129,
|
||||
"fn_": 169,
|
||||
"tn": 118,
|
||||
"precision": 0.36764705882352944,
|
||||
"recall": 0.3073770491803279,
|
||||
"f1": 0.3348214285714286
|
||||
"tp": 82,
|
||||
"fp": 126,
|
||||
"fn_": 167,
|
||||
"tn": 129,
|
||||
"precision": 0.3942307692307692,
|
||||
"recall": 0.3293172690763052,
|
||||
"f1": 0.35886214442013126
|
||||
},
|
||||
">=Medium": {
|
||||
"tp": 75,
|
||||
"fp": 124,
|
||||
"fn_": 169,
|
||||
"tn": 123,
|
||||
"precision": 0.3768844221105528,
|
||||
"recall": 0.3073770491803279,
|
||||
"f1": 0.33860045146726864
|
||||
"tp": 82,
|
||||
"fp": 121,
|
||||
"fn_": 167,
|
||||
"tn": 134,
|
||||
"precision": 0.4039408866995074,
|
||||
"recall": 0.3293172690763052,
|
||||
"f1": 0.3628318584070796
|
||||
}
|
||||
}
|
||||
}
|
||||
Loading…
Add table
Add a link
Reference in a new issue