mirror of
https://github.com/elicpeter/nyx.git
synced 2026-06-21 20:18:06 +02:00
Phase 1 (#33)
* chore: Exclude CLAUDE.md from Cargo.toml * feat: add callgraph module and integrate into main analysis flow * feat: enhance CLI with new severity filtering and analysis modes * feat: update CHANGELOG with recent enhancements and fixes to severity filtering and output handling * feat: implement state-model dataflow analysis for resource lifecycle and auth state * feat: enhance diagnostic output formatting and add evidence structure * feat: implement attack surface ranking for diagnostics with scoring and sorting * feat: add comprehensive documentation for installation, usage, and rules reference * feat: add multiple language support for command execution and evaluation endpoints * feat: implement inline suppression for findings using `nyx:ignore` comments * feat: add confidence levels to AST patterns and update output structure * feat: implement low-noise prioritization system with category filtering, rollup grouping, and configurable budgets * feat: bump version to 0.4.0 and update changelog with new features and improvements * feat: add dead code allowances to various functions in mod.rs and real_world_tests.rs
This commit is contained in:
parent
19b578c5c4
commit
1bbe4b1cfb
456 changed files with 25628 additions and 1228 deletions
24
tests/fixtures/patterns/c/negative.c
vendored
Normal file
24
tests/fixtures/patterns/c/negative.c
vendored
Normal file
|
|
@ -0,0 +1,24 @@
|
|||
/* Negative fixture: none of these should trigger security patterns. */
|
||||
#include <stdio.h>
|
||||
#include <stdlib.h>
|
||||
#include <string.h>
|
||||
|
||||
void safe_snprintf(const char *name) {
|
||||
char buf[128];
|
||||
snprintf(buf, sizeof(buf), "Hello %s", name);
|
||||
}
|
||||
|
||||
void safe_strncpy(const char *src) {
|
||||
char dst[32];
|
||||
strncpy(dst, src, sizeof(dst) - 1);
|
||||
dst[sizeof(dst) - 1] = '\0';
|
||||
}
|
||||
|
||||
void safe_fgets() {
|
||||
char buf[64];
|
||||
fgets(buf, sizeof(buf), stdin);
|
||||
}
|
||||
|
||||
void safe_printf_literal() {
|
||||
printf("Hello %s\n", "world");
|
||||
}
|
||||
50
tests/fixtures/patterns/c/positive.c
vendored
Normal file
50
tests/fixtures/patterns/c/positive.c
vendored
Normal file
|
|
@ -0,0 +1,50 @@
|
|||
/* Positive fixture: each snippet should trigger the named pattern. */
|
||||
#include <stdio.h>
|
||||
#include <stdlib.h>
|
||||
#include <string.h>
|
||||
|
||||
/* c.memory.gets */
|
||||
void trigger_gets() {
|
||||
char buf[64];
|
||||
gets(buf);
|
||||
}
|
||||
|
||||
/* c.memory.strcpy */
|
||||
void trigger_strcpy(char *src) {
|
||||
char dst[32];
|
||||
strcpy(dst, src);
|
||||
}
|
||||
|
||||
/* c.memory.strcat */
|
||||
void trigger_strcat(char *extra) {
|
||||
char buf[64] = "prefix";
|
||||
strcat(buf, extra);
|
||||
}
|
||||
|
||||
/* c.memory.sprintf */
|
||||
void trigger_sprintf(const char *name) {
|
||||
char buf[128];
|
||||
sprintf(buf, "Hello %s", name);
|
||||
}
|
||||
|
||||
/* c.memory.scanf_percent_s */
|
||||
void trigger_scanf() {
|
||||
char name[32];
|
||||
scanf("%s", name);
|
||||
}
|
||||
|
||||
/* c.cmdi.system */
|
||||
void trigger_system(const char *cmd) {
|
||||
system(cmd);
|
||||
}
|
||||
|
||||
/* c.cmdi.popen */
|
||||
void trigger_popen(const char *cmd) {
|
||||
FILE *f = popen(cmd, "r");
|
||||
pclose(f);
|
||||
}
|
||||
|
||||
/* c.memory.printf_no_fmt */
|
||||
void trigger_printf_no_fmt(char *user_data) {
|
||||
printf(user_data);
|
||||
}
|
||||
24
tests/fixtures/patterns/cpp/negative.cpp
vendored
Normal file
24
tests/fixtures/patterns/cpp/negative.cpp
vendored
Normal file
|
|
@ -0,0 +1,24 @@
|
|||
// Negative fixture: none of these should trigger security patterns.
|
||||
#include <cstdio>
|
||||
#include <cstring>
|
||||
#include <string>
|
||||
|
||||
void safe_string_ops() {
|
||||
std::string s = "hello";
|
||||
std::string copy = s;
|
||||
auto len = s.length();
|
||||
}
|
||||
|
||||
void safe_cast() {
|
||||
double d = 3.14;
|
||||
int i = static_cast<int>(d);
|
||||
}
|
||||
|
||||
void safe_snprintf(const char *name) {
|
||||
char buf[128];
|
||||
snprintf(buf, sizeof(buf), "Hello %s", name);
|
||||
}
|
||||
|
||||
void safe_printf_literal() {
|
||||
printf("Hello %s\n", "world");
|
||||
}
|
||||
49
tests/fixtures/patterns/cpp/positive.cpp
vendored
Normal file
49
tests/fixtures/patterns/cpp/positive.cpp
vendored
Normal file
|
|
@ -0,0 +1,49 @@
|
|||
// Positive fixture: each snippet should trigger the named pattern.
|
||||
#include <cstdlib>
|
||||
#include <cstring>
|
||||
#include <cstdio>
|
||||
|
||||
// cpp.memory.gets
|
||||
void trigger_gets() {
|
||||
char buf[64];
|
||||
gets(buf);
|
||||
}
|
||||
|
||||
// cpp.memory.strcpy
|
||||
void trigger_strcpy(const char *src) {
|
||||
char dst[32];
|
||||
strcpy(dst, src);
|
||||
}
|
||||
|
||||
// cpp.memory.strcat
|
||||
void trigger_strcat(const char *extra) {
|
||||
char buf[64] = "prefix";
|
||||
strcat(buf, extra);
|
||||
}
|
||||
|
||||
// cpp.memory.sprintf
|
||||
void trigger_sprintf(const char *name) {
|
||||
char buf[128];
|
||||
sprintf(buf, "Hello %s", name);
|
||||
}
|
||||
|
||||
// cpp.cmdi.system
|
||||
void trigger_system(const char *cmd) {
|
||||
system(cmd);
|
||||
}
|
||||
|
||||
// cpp.memory.reinterpret_cast
|
||||
void trigger_reinterpret_cast() {
|
||||
int x = 42;
|
||||
float *fp = reinterpret_cast<float*>(&x);
|
||||
}
|
||||
|
||||
// cpp.memory.const_cast
|
||||
void trigger_const_cast(const int *p) {
|
||||
int *q = const_cast<int*>(p);
|
||||
}
|
||||
|
||||
// cpp.memory.printf_no_fmt
|
||||
void trigger_printf_no_fmt(char *user_data) {
|
||||
printf(user_data);
|
||||
}
|
||||
23
tests/fixtures/patterns/go/negative.go
vendored
Normal file
23
tests/fixtures/patterns/go/negative.go
vendored
Normal file
|
|
@ -0,0 +1,23 @@
|
|||
package main
|
||||
|
||||
import (
|
||||
"crypto/sha256"
|
||||
"database/sql"
|
||||
)
|
||||
|
||||
func safeHash(data []byte) {
|
||||
sha256.Sum256(data)
|
||||
}
|
||||
|
||||
func safeParamQuery(db *sql.DB, user string) {
|
||||
db.Query("SELECT * FROM users WHERE name = $1", user)
|
||||
}
|
||||
|
||||
func safeLiteralQuery(db *sql.DB) {
|
||||
db.Query("SELECT COUNT(*) FROM users")
|
||||
}
|
||||
|
||||
func safeStringOps() {
|
||||
x := "hello"
|
||||
_ = len(x)
|
||||
}
|
||||
55
tests/fixtures/patterns/go/positive.go
vendored
Normal file
55
tests/fixtures/patterns/go/positive.go
vendored
Normal file
|
|
@ -0,0 +1,55 @@
|
|||
package main
|
||||
|
||||
import (
|
||||
"crypto/md5"
|
||||
"crypto/sha1"
|
||||
"database/sql"
|
||||
"encoding/gob"
|
||||
"os"
|
||||
"os/exec"
|
||||
"unsafe"
|
||||
)
|
||||
|
||||
// go.cmdi.exec_command
|
||||
func triggerExecCommand(cmd string) {
|
||||
exec.Command("bash", "-c", cmd)
|
||||
}
|
||||
|
||||
// go.memory.unsafe_pointer
|
||||
func triggerUnsafePointer() {
|
||||
x := 42
|
||||
p := unsafe.Pointer(&x)
|
||||
_ = p
|
||||
}
|
||||
|
||||
// go.transport.insecure_skip_verify
|
||||
func triggerInsecureSkipVerify() {
|
||||
_ = struct{ InsecureSkipVerify bool }{InsecureSkipVerify: true}
|
||||
}
|
||||
|
||||
// go.crypto.md5
|
||||
func triggerMD5(data []byte) {
|
||||
md5.Sum(data)
|
||||
}
|
||||
|
||||
// go.crypto.sha1
|
||||
func triggerSHA1(data []byte) {
|
||||
sha1.Sum(data)
|
||||
}
|
||||
|
||||
// go.sqli.query_concat
|
||||
func triggerSQLConcat(db *sql.DB, user string) {
|
||||
db.Query("SELECT * FROM users WHERE name = '" + user + "'")
|
||||
}
|
||||
|
||||
// go.secrets.hardcoded_key
|
||||
func triggerHardcodedSecret() {
|
||||
password := "super_secret_password_12345"
|
||||
_ = password
|
||||
}
|
||||
|
||||
// go.deser.gob_decode
|
||||
func triggerGobDecode(f *os.File) {
|
||||
dec := gob.NewDecoder(f)
|
||||
_ = dec
|
||||
}
|
||||
22
tests/fixtures/patterns/java/negative.java
vendored
Normal file
22
tests/fixtures/patterns/java/negative.java
vendored
Normal file
|
|
@ -0,0 +1,22 @@
|
|||
import java.sql.*;
|
||||
import java.security.SecureRandom;
|
||||
|
||||
class Negative {
|
||||
// Safe: parameterized query
|
||||
void safeQuery(Connection conn, String user) throws Exception {
|
||||
PreparedStatement ps = conn.prepareStatement("SELECT * FROM users WHERE name = ?");
|
||||
ps.setString(1, user);
|
||||
ResultSet rs = ps.executeQuery();
|
||||
}
|
||||
|
||||
// Safe: SecureRandom instead of Random
|
||||
void safeRandom() {
|
||||
SecureRandom sr = new SecureRandom();
|
||||
int token = sr.nextInt();
|
||||
}
|
||||
|
||||
// Safe: no concatenation in SQL
|
||||
void safeLiteralQuery(Statement stmt) throws Exception {
|
||||
stmt.executeQuery("SELECT COUNT(*) FROM users");
|
||||
}
|
||||
}
|
||||
48
tests/fixtures/patterns/java/positive.java
vendored
Normal file
48
tests/fixtures/patterns/java/positive.java
vendored
Normal file
|
|
@ -0,0 +1,48 @@
|
|||
import java.io.*;
|
||||
import java.util.Random;
|
||||
import java.security.MessageDigest;
|
||||
|
||||
class Positive {
|
||||
// java.deser.readobject
|
||||
void triggerDeser(InputStream is) throws Exception {
|
||||
ObjectInputStream ois = new ObjectInputStream(is);
|
||||
Object obj = ois.readObject();
|
||||
}
|
||||
|
||||
// java.cmdi.runtime_exec
|
||||
void triggerRuntimeExec(String cmd) throws Exception {
|
||||
Runtime.getRuntime().exec(cmd);
|
||||
}
|
||||
|
||||
// java.reflection.class_forname
|
||||
void triggerClassForName(String name) throws Exception {
|
||||
Class.forName(name);
|
||||
}
|
||||
|
||||
// java.reflection.method_invoke
|
||||
void triggerMethodInvoke(Object target) throws Exception {
|
||||
java.lang.reflect.Method m = target.getClass().getMethod("run");
|
||||
m.invoke(target);
|
||||
}
|
||||
|
||||
// java.sqli.execute_concat
|
||||
void triggerSqlConcat(java.sql.Statement stmt, String user) throws Exception {
|
||||
stmt.executeQuery("SELECT * FROM users WHERE name = '" + user + "'");
|
||||
}
|
||||
|
||||
// java.crypto.insecure_random
|
||||
void triggerInsecureRandom() {
|
||||
Random r = new Random();
|
||||
int token = r.nextInt();
|
||||
}
|
||||
|
||||
// java.crypto.weak_digest
|
||||
void triggerWeakDigest() throws Exception {
|
||||
MessageDigest md = MessageDigest.getInstance("MD5");
|
||||
}
|
||||
|
||||
// java.xss.getwriter_print
|
||||
void triggerGetWriterPrint(javax.servlet.http.HttpServletResponse resp) throws Exception {
|
||||
resp.getWriter().println("<html>" + "data" + "</html>");
|
||||
}
|
||||
}
|
||||
25
tests/fixtures/patterns/javascript/negative.js
vendored
Normal file
25
tests/fixtures/patterns/javascript/negative.js
vendored
Normal file
|
|
@ -0,0 +1,25 @@
|
|||
// Negative fixture: none of these should trigger security patterns.
|
||||
|
||||
function safeStringOps() {
|
||||
var x = "hello";
|
||||
var y = x.toUpperCase();
|
||||
var z = JSON.stringify({ key: "value" });
|
||||
}
|
||||
|
||||
function safeTimeout(fn) {
|
||||
// Function reference, not string
|
||||
setTimeout(fn, 1000);
|
||||
}
|
||||
|
||||
function safeDomManipulation(el) {
|
||||
el.textContent = "safe text";
|
||||
el.setAttribute("class", "active");
|
||||
}
|
||||
|
||||
function safeRandomness() {
|
||||
var buf = crypto.getRandomValues(new Uint8Array(16));
|
||||
}
|
||||
|
||||
function safeCopy(src) {
|
||||
var copy = Object.assign({}, src);
|
||||
}
|
||||
51
tests/fixtures/patterns/javascript/positive.js
vendored
Normal file
51
tests/fixtures/patterns/javascript/positive.js
vendored
Normal file
|
|
@ -0,0 +1,51 @@
|
|||
// Positive fixture: each snippet should trigger the named pattern.
|
||||
|
||||
// js.code_exec.eval
|
||||
function triggerEval(code) {
|
||||
eval(code);
|
||||
}
|
||||
|
||||
// js.code_exec.new_function
|
||||
function triggerNewFunction(body) {
|
||||
var fn = new Function(body);
|
||||
}
|
||||
|
||||
// js.code_exec.settimeout_string
|
||||
function triggerSetTimeout() {
|
||||
setTimeout("alert(1)", 1000);
|
||||
}
|
||||
|
||||
// js.xss.document_write
|
||||
function triggerDocumentWrite(data) {
|
||||
document.write(data);
|
||||
}
|
||||
|
||||
// js.xss.outer_html
|
||||
function triggerOuterHtml(el, data) {
|
||||
el.outerHTML = data;
|
||||
}
|
||||
|
||||
// js.xss.insert_adjacent_html
|
||||
function triggerInsertAdjacentHtml(el, data) {
|
||||
el.insertAdjacentHTML("beforeend", data);
|
||||
}
|
||||
|
||||
// js.prototype.proto_assignment
|
||||
function triggerProtoAssignment(obj) {
|
||||
obj.__proto__ = { malicious: true };
|
||||
}
|
||||
|
||||
// js.xss.location_assign
|
||||
function triggerLocationAssign(url) {
|
||||
window.location = url;
|
||||
}
|
||||
|
||||
// js.xss.cookie_write
|
||||
function triggerCookieWrite(sid) {
|
||||
document.cookie = "session=" + sid;
|
||||
}
|
||||
|
||||
// js.crypto.math_random
|
||||
function triggerMathRandom() {
|
||||
var token = Math.random();
|
||||
}
|
||||
25
tests/fixtures/patterns/php/negative.php
vendored
Normal file
25
tests/fixtures/patterns/php/negative.php
vendored
Normal file
|
|
@ -0,0 +1,25 @@
|
|||
<?php
|
||||
// Negative fixture: none of these should trigger security patterns.
|
||||
|
||||
function safe_query($pdo, $user) {
|
||||
$stmt = $pdo->prepare("SELECT * FROM users WHERE name = ?");
|
||||
$stmt->execute([$user]);
|
||||
}
|
||||
|
||||
function safe_hash($data) {
|
||||
return hash("sha256", $data);
|
||||
}
|
||||
|
||||
function safe_random() {
|
||||
return random_int(1, 100);
|
||||
}
|
||||
|
||||
function safe_include() {
|
||||
include "config.php";
|
||||
}
|
||||
|
||||
function safe_string_ops() {
|
||||
$x = "hello";
|
||||
$y = strtoupper($x);
|
||||
$z = strlen($y);
|
||||
}
|
||||
57
tests/fixtures/patterns/php/positive.php
vendored
Normal file
57
tests/fixtures/patterns/php/positive.php
vendored
Normal file
|
|
@ -0,0 +1,57 @@
|
|||
<?php
|
||||
// Positive fixture: each snippet should trigger the named pattern.
|
||||
|
||||
// php.code_exec.eval
|
||||
function trigger_eval($code) {
|
||||
eval($code);
|
||||
}
|
||||
|
||||
// php.code_exec.create_function
|
||||
function trigger_create_function() {
|
||||
$fn = create_function('$a', 'return $a * 2;');
|
||||
}
|
||||
|
||||
// php.code_exec.preg_replace_e
|
||||
function trigger_preg_replace_e($input) {
|
||||
preg_replace('/test/e', 'strtoupper("$1")', $input);
|
||||
}
|
||||
|
||||
// php.code_exec.assert_string
|
||||
function trigger_assert($code) {
|
||||
assert("strlen('$code') > 0");
|
||||
}
|
||||
|
||||
// php.cmdi.system
|
||||
function trigger_system($cmd) {
|
||||
system($cmd);
|
||||
}
|
||||
|
||||
// php.deser.unserialize
|
||||
function trigger_unserialize($data) {
|
||||
unserialize($data);
|
||||
}
|
||||
|
||||
// php.sqli.query_concat
|
||||
function trigger_sql_concat($user) {
|
||||
mysql_query("SELECT * FROM users WHERE name = '" . $user . "'");
|
||||
}
|
||||
|
||||
// php.path.include_variable
|
||||
function trigger_include($path) {
|
||||
include $path;
|
||||
}
|
||||
|
||||
// php.crypto.md5
|
||||
function trigger_md5($data) {
|
||||
md5($data);
|
||||
}
|
||||
|
||||
// php.crypto.sha1
|
||||
function trigger_sha1($data) {
|
||||
sha1($data);
|
||||
}
|
||||
|
||||
// php.crypto.rand
|
||||
function trigger_rand() {
|
||||
$r = rand();
|
||||
}
|
||||
23
tests/fixtures/patterns/python/negative.py
vendored
Normal file
23
tests/fixtures/patterns/python/negative.py
vendored
Normal file
|
|
@ -0,0 +1,23 @@
|
|||
# Negative fixture: none of these should trigger security patterns.
|
||||
|
||||
import subprocess
|
||||
import hashlib
|
||||
|
||||
def safe_subprocess():
|
||||
# No shell=True
|
||||
subprocess.run(["ls", "-la"])
|
||||
|
||||
def safe_hash():
|
||||
hashlib.sha256(b"data")
|
||||
|
||||
def safe_literal_query(cursor):
|
||||
cursor.execute("SELECT COUNT(*) FROM users")
|
||||
|
||||
def safe_yaml_load(data):
|
||||
import yaml
|
||||
yaml.safe_load(data)
|
||||
|
||||
def safe_string_ops():
|
||||
x = "hello"
|
||||
y = x.upper()
|
||||
z = len(y)
|
||||
51
tests/fixtures/patterns/python/positive.py
vendored
Normal file
51
tests/fixtures/patterns/python/positive.py
vendored
Normal file
|
|
@ -0,0 +1,51 @@
|
|||
# Positive fixture: each snippet should trigger the named pattern.
|
||||
|
||||
import os
|
||||
import subprocess
|
||||
import pickle
|
||||
import yaml
|
||||
import hashlib
|
||||
|
||||
# py.code_exec.eval
|
||||
def trigger_eval(data):
|
||||
result = eval(data)
|
||||
|
||||
# py.code_exec.exec
|
||||
def trigger_exec(code):
|
||||
exec(code)
|
||||
|
||||
# py.code_exec.compile
|
||||
def trigger_compile(code):
|
||||
co = compile(code, "<string>", "exec")
|
||||
|
||||
# py.cmdi.os_system
|
||||
def trigger_os_system(cmd):
|
||||
os.system(cmd)
|
||||
|
||||
# py.cmdi.os_popen
|
||||
def trigger_os_popen(cmd):
|
||||
os.popen(cmd)
|
||||
|
||||
# py.cmdi.subprocess_shell
|
||||
def trigger_subprocess_shell(cmd):
|
||||
subprocess.run(cmd, shell=True)
|
||||
|
||||
# py.deser.pickle_loads
|
||||
def trigger_pickle(data):
|
||||
obj = pickle.loads(data)
|
||||
|
||||
# py.deser.yaml_load
|
||||
def trigger_yaml(data):
|
||||
obj = yaml.load(data)
|
||||
|
||||
# py.sqli.execute_format
|
||||
def trigger_sql_concat(cursor, user):
|
||||
cursor.execute("SELECT * FROM users WHERE name = '" + user + "'")
|
||||
|
||||
# py.crypto.md5
|
||||
def trigger_md5(data):
|
||||
hashlib.md5(data)
|
||||
|
||||
# py.crypto.sha1
|
||||
def trigger_sha1(data):
|
||||
hashlib.sha1(data)
|
||||
25
tests/fixtures/patterns/ruby/negative.rb
vendored
Normal file
25
tests/fixtures/patterns/ruby/negative.rb
vendored
Normal file
|
|
@ -0,0 +1,25 @@
|
|||
# Negative fixture: none of these should trigger security patterns.
|
||||
|
||||
def safe_yaml(data)
|
||||
YAML.safe_load(data)
|
||||
end
|
||||
|
||||
def safe_system
|
||||
Dir.entries(".")
|
||||
end
|
||||
|
||||
def safe_send(obj)
|
||||
obj.send(:to_s)
|
||||
end
|
||||
|
||||
def safe_open
|
||||
File.open("config.yml", "r") do |f|
|
||||
f.read
|
||||
end
|
||||
end
|
||||
|
||||
def safe_string_ops
|
||||
x = "hello"
|
||||
y = x.upcase
|
||||
z = y.length
|
||||
end
|
||||
51
tests/fixtures/patterns/ruby/positive.rb
vendored
Normal file
51
tests/fixtures/patterns/ruby/positive.rb
vendored
Normal file
|
|
@ -0,0 +1,51 @@
|
|||
# Positive fixture: each snippet should trigger the named pattern.
|
||||
|
||||
# rb.code_exec.eval
|
||||
def trigger_eval(code)
|
||||
eval(code)
|
||||
end
|
||||
|
||||
# rb.code_exec.instance_eval
|
||||
def trigger_instance_eval(obj, code)
|
||||
obj.instance_eval(code)
|
||||
end
|
||||
|
||||
# rb.code_exec.class_eval
|
||||
def trigger_class_eval(klass, code)
|
||||
klass.class_eval(code)
|
||||
end
|
||||
|
||||
# rb.cmdi.backtick
|
||||
def trigger_backtick
|
||||
`uname -a`
|
||||
end
|
||||
|
||||
# rb.cmdi.system_interp
|
||||
def trigger_system_interp(cmd)
|
||||
system("run #{cmd}")
|
||||
end
|
||||
|
||||
# rb.deser.yaml_load
|
||||
def trigger_yaml_load(data)
|
||||
YAML.load(data)
|
||||
end
|
||||
|
||||
# rb.deser.marshal_load
|
||||
def trigger_marshal_load(data)
|
||||
Marshal.load(data)
|
||||
end
|
||||
|
||||
# rb.reflection.send_dynamic
|
||||
def trigger_send_dynamic(obj, method_name)
|
||||
obj.send(method_name)
|
||||
end
|
||||
|
||||
# rb.reflection.constantize
|
||||
def trigger_constantize(name)
|
||||
name.constantize
|
||||
end
|
||||
|
||||
# rb.ssrf.open_uri
|
||||
def trigger_open_uri
|
||||
open("https://example.com/api")
|
||||
end
|
||||
36
tests/fixtures/patterns/rust/negative.rs
vendored
Normal file
36
tests/fixtures/patterns/rust/negative.rs
vendored
Normal file
|
|
@ -0,0 +1,36 @@
|
|||
// Negative fixture: none of the security-relevant patterns should fire here.
|
||||
|
||||
fn safe_option_handling() {
|
||||
let x: Option<i32> = Some(1);
|
||||
// Using match instead of unwrap
|
||||
match x {
|
||||
Some(v) => println!("{}", v),
|
||||
None => println!("none"),
|
||||
}
|
||||
}
|
||||
|
||||
fn safe_result_handling() -> Result<(), String> {
|
||||
let x: Result<i32, String> = Ok(1);
|
||||
// Using ? instead of unwrap
|
||||
let _v = x?;
|
||||
Ok(())
|
||||
}
|
||||
|
||||
fn safe_copy() {
|
||||
let src = vec![1, 2, 3];
|
||||
let mut dst = vec![0; 3];
|
||||
// Safe copy via clone
|
||||
dst.clone_from(&src);
|
||||
}
|
||||
|
||||
fn safe_cast() {
|
||||
let x: u32 = 42;
|
||||
// Widening cast is fine
|
||||
let _ = x as u64;
|
||||
}
|
||||
|
||||
fn safe_string_ops() {
|
||||
let s = String::from("hello");
|
||||
let _ = s.len();
|
||||
let _ = s.is_empty();
|
||||
}
|
||||
78
tests/fixtures/patterns/rust/positive.rs
vendored
Normal file
78
tests/fixtures/patterns/rust/positive.rs
vendored
Normal file
|
|
@ -0,0 +1,78 @@
|
|||
// Positive fixture: each snippet should trigger the named pattern.
|
||||
|
||||
use std::mem;
|
||||
use std::ptr;
|
||||
|
||||
// rs.memory.transmute
|
||||
fn trigger_transmute() {
|
||||
let x: u32 = unsafe { mem::transmute(1.0f32) };
|
||||
let _ = x;
|
||||
}
|
||||
|
||||
// rs.memory.copy_nonoverlapping
|
||||
fn trigger_copy_nonoverlapping() {
|
||||
let src = [1u8; 4];
|
||||
let mut dst = [0u8; 4];
|
||||
unsafe { ptr::copy_nonoverlapping(src.as_ptr(), dst.as_mut_ptr(), 4) };
|
||||
}
|
||||
|
||||
// rs.memory.get_unchecked
|
||||
fn trigger_get_unchecked() {
|
||||
let v = vec![1, 2, 3];
|
||||
let _ = unsafe { v.get_unchecked(0) };
|
||||
}
|
||||
|
||||
// rs.memory.mem_zeroed
|
||||
fn trigger_mem_zeroed() {
|
||||
let _: u64 = unsafe { mem::zeroed() };
|
||||
}
|
||||
|
||||
// rs.memory.ptr_read
|
||||
fn trigger_ptr_read() {
|
||||
let x = 42u32;
|
||||
let _ = unsafe { ptr::read(&x) };
|
||||
}
|
||||
|
||||
// rs.quality.unsafe_block
|
||||
fn trigger_unsafe_block() {
|
||||
unsafe {
|
||||
let _ = 1;
|
||||
}
|
||||
}
|
||||
|
||||
// rs.quality.unsafe_fn
|
||||
unsafe fn trigger_unsafe_fn() {}
|
||||
|
||||
// rs.quality.unwrap
|
||||
fn trigger_unwrap() {
|
||||
let x: Option<i32> = Some(1);
|
||||
let _ = x.unwrap();
|
||||
}
|
||||
|
||||
// rs.quality.expect
|
||||
fn trigger_expect() {
|
||||
let x: Option<i32> = Some(1);
|
||||
let _ = x.expect("should exist");
|
||||
}
|
||||
|
||||
// rs.quality.panic_macro
|
||||
fn trigger_panic() {
|
||||
panic!("boom");
|
||||
}
|
||||
|
||||
// rs.quality.todo
|
||||
fn trigger_todo() {
|
||||
todo!();
|
||||
}
|
||||
|
||||
// rs.memory.narrow_cast
|
||||
fn trigger_narrow_cast() {
|
||||
let big: u32 = 1000;
|
||||
let _ = big as u8;
|
||||
}
|
||||
|
||||
// rs.memory.mem_forget
|
||||
fn trigger_mem_forget() {
|
||||
let v = vec![1, 2, 3];
|
||||
mem::forget(v);
|
||||
}
|
||||
25
tests/fixtures/patterns/typescript/negative.ts
vendored
Normal file
25
tests/fixtures/patterns/typescript/negative.ts
vendored
Normal file
|
|
@ -0,0 +1,25 @@
|
|||
// Negative fixture: none of the security-relevant patterns should fire here.
|
||||
|
||||
function safeStringOps(): string {
|
||||
const x: string = "hello";
|
||||
return x.toUpperCase();
|
||||
}
|
||||
|
||||
function safeTimeout(fn: () => void): void {
|
||||
setTimeout(fn, 1000);
|
||||
}
|
||||
|
||||
function safeDomManipulation(el: Element): void {
|
||||
el.textContent = "safe text";
|
||||
}
|
||||
|
||||
function safeTypedParam(x: number): number {
|
||||
return x + 1;
|
||||
}
|
||||
|
||||
function safeUnknownHandling(x: unknown): string {
|
||||
if (typeof x === "string") {
|
||||
return x;
|
||||
}
|
||||
return String(x);
|
||||
}
|
||||
56
tests/fixtures/patterns/typescript/positive.ts
vendored
Normal file
56
tests/fixtures/patterns/typescript/positive.ts
vendored
Normal file
|
|
@ -0,0 +1,56 @@
|
|||
// Positive fixture: each snippet should trigger the named pattern.
|
||||
|
||||
// ts.code_exec.eval
|
||||
function triggerEval(code: string): void {
|
||||
eval(code);
|
||||
}
|
||||
|
||||
// ts.code_exec.new_function
|
||||
function triggerNewFunction(body: string): void {
|
||||
const fn = new Function(body);
|
||||
}
|
||||
|
||||
// ts.code_exec.settimeout_string
|
||||
function triggerSetTimeout(): void {
|
||||
setTimeout("alert(1)", 1000);
|
||||
}
|
||||
|
||||
// ts.xss.document_write
|
||||
function triggerDocumentWrite(data: string): void {
|
||||
document.write(data);
|
||||
}
|
||||
|
||||
// ts.xss.outer_html
|
||||
function triggerOuterHtml(el: Element, data: string): void {
|
||||
el.outerHTML = data;
|
||||
}
|
||||
|
||||
// ts.xss.insert_adjacent_html
|
||||
function triggerInsertAdjacentHtml(el: Element, data: string): void {
|
||||
el.insertAdjacentHTML("beforeend", data);
|
||||
}
|
||||
|
||||
// ts.quality.any_annotation
|
||||
function triggerAnyAnnotation(x: any): void {
|
||||
console.log(x);
|
||||
}
|
||||
|
||||
// ts.quality.as_any
|
||||
function triggerAsAny(x: unknown): void {
|
||||
const y = x as any;
|
||||
}
|
||||
|
||||
// ts.prototype.proto_assignment
|
||||
function triggerProtoAssignment(obj: Record<string, unknown>): void {
|
||||
obj.__proto__ = { malicious: true };
|
||||
}
|
||||
|
||||
// ts.xss.location_assign
|
||||
function triggerLocationAssign(url: string): void {
|
||||
window.location = url;
|
||||
}
|
||||
|
||||
// ts.xss.cookie_write
|
||||
function triggerCookieWrite(sid: string): void {
|
||||
document.cookie = "session=" + sid;
|
||||
}
|
||||
Loading…
Add table
Add a link
Reference in a new issue