mirror of
https://github.com/elicpeter/nyx.git
synced 2026-06-24 20:28:06 +02:00
[pitboss] phase 22: Track F.2 + F.3 — Cross-language framework probes + data store / external service / dangerous-local detection
This commit is contained in:
parent
c03326a658
commit
2395446655
43 changed files with 5213 additions and 82 deletions
13
tests/dynamic_fixtures/surface/go_gin/main.go
Normal file
13
tests/dynamic_fixtures/surface/go_gin/main.go
Normal file
|
|
@ -0,0 +1,13 @@
|
|||
package main
|
||||
|
||||
import "github.com/gin-gonic/gin"
|
||||
|
||||
func main() {
|
||||
r := gin.Default()
|
||||
r.GET("/users", listUsers)
|
||||
r.Run()
|
||||
}
|
||||
|
||||
func listUsers(c *gin.Context) {
|
||||
c.JSON(200, []string{})
|
||||
}
|
||||
12
tests/dynamic_fixtures/surface/go_http/main.go
Normal file
12
tests/dynamic_fixtures/surface/go_http/main.go
Normal file
|
|
@ -0,0 +1,12 @@
|
|||
package main
|
||||
|
||||
import "net/http"
|
||||
|
||||
func main() {
|
||||
http.HandleFunc("/users", listUsers)
|
||||
http.ListenAndServe(":8080", nil)
|
||||
}
|
||||
|
||||
func listUsers(w http.ResponseWriter, r *http.Request) {
|
||||
w.Write([]byte("[]"))
|
||||
}
|
||||
|
|
@ -0,0 +1,17 @@
|
|||
package com.example;
|
||||
|
||||
import io.quarkus.runtime.Quarkus;
|
||||
import jakarta.enterprise.context.ApplicationScoped;
|
||||
import jakarta.ws.rs.GET;
|
||||
import jakarta.ws.rs.Path;
|
||||
|
||||
@ApplicationScoped
|
||||
@Path("/api")
|
||||
public class GreetResource {
|
||||
|
||||
@GET
|
||||
@Path("/hello")
|
||||
public String hello() {
|
||||
return "hi";
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,14 @@
|
|||
package com.example;
|
||||
|
||||
import jakarta.ws.rs.GET;
|
||||
import jakarta.ws.rs.Path;
|
||||
|
||||
@Path("/users")
|
||||
public class UserResource {
|
||||
|
||||
@GET
|
||||
@Path("/{id}")
|
||||
public String get() {
|
||||
return "{}";
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,11 @@
|
|||
package com.example;
|
||||
|
||||
@RestController
|
||||
@RequestMapping("/api")
|
||||
public class UserController {
|
||||
|
||||
@GetMapping("/users")
|
||||
public String list() {
|
||||
return "[]";
|
||||
}
|
||||
}
|
||||
8
tests/dynamic_fixtures/surface/js_express/server.js
Normal file
8
tests/dynamic_fixtures/surface/js_express/server.js
Normal file
|
|
@ -0,0 +1,8 @@
|
|||
const express = require("express");
|
||||
const app = express();
|
||||
|
||||
app.get("/users", (req, res) => {
|
||||
res.send("ok");
|
||||
});
|
||||
|
||||
app.listen(3000);
|
||||
8
tests/dynamic_fixtures/surface/js_koa/server.js
Normal file
8
tests/dynamic_fixtures/surface/js_koa/server.js
Normal file
|
|
@ -0,0 +1,8 @@
|
|||
const Router = require("@koa/router");
|
||||
const router = new Router();
|
||||
|
||||
router.get("/users", async (ctx) => {
|
||||
ctx.body = [];
|
||||
});
|
||||
|
||||
module.exports = router;
|
||||
3
tests/dynamic_fixtures/surface/php_laravel/routes.php
Normal file
3
tests/dynamic_fixtures/surface/php_laravel/routes.php
Normal file
|
|
@ -0,0 +1,3 @@
|
|||
<?php
|
||||
|
||||
Route::get('/users', 'UserController@index');
|
||||
3
tests/dynamic_fixtures/surface/php_slim/routes.php
Normal file
3
tests/dynamic_fixtures/surface/php_slim/routes.php
Normal file
|
|
@ -0,0 +1,3 @@
|
|||
<?php
|
||||
|
||||
$app->get('/users', 'UsersController:list');
|
||||
10
tests/dynamic_fixtures/surface/python_django/urls.py
Normal file
10
tests/dynamic_fixtures/surface/python_django/urls.py
Normal file
|
|
@ -0,0 +1,10 @@
|
|||
from django.urls import path
|
||||
|
||||
|
||||
def admin_view(request):
|
||||
return None
|
||||
|
||||
|
||||
urlpatterns = [
|
||||
path("admin/", admin_view),
|
||||
]
|
||||
8
tests/dynamic_fixtures/surface/python_fastapi/api.py
Normal file
8
tests/dynamic_fixtures/surface/python_fastapi/api.py
Normal file
|
|
@ -0,0 +1,8 @@
|
|||
from fastapi import FastAPI
|
||||
|
||||
app = FastAPI()
|
||||
|
||||
|
||||
@app.get("/items")
|
||||
def list_items():
|
||||
return []
|
||||
8
tests/dynamic_fixtures/surface/python_flask/app.py
Normal file
8
tests/dynamic_fixtures/surface/python_flask/app.py
Normal file
|
|
@ -0,0 +1,8 @@
|
|||
from flask import Flask
|
||||
|
||||
app = Flask(__name__)
|
||||
|
||||
|
||||
@app.get("/users")
|
||||
def list_users():
|
||||
return "ok"
|
||||
|
|
@ -0,0 +1,9 @@
|
|||
class UsersController < ApplicationController
|
||||
def index
|
||||
render json: []
|
||||
end
|
||||
|
||||
def show
|
||||
render json: {}
|
||||
end
|
||||
end
|
||||
5
tests/dynamic_fixtures/surface/ruby_sinatra/app.rb
Normal file
5
tests/dynamic_fixtures/surface/ruby_sinatra/app.rb
Normal file
|
|
@ -0,0 +1,5 @@
|
|||
require 'sinatra'
|
||||
|
||||
get '/users' do
|
||||
'[]'
|
||||
end
|
||||
6
tests/dynamic_fixtures/surface/rust_actix/main.rs
Normal file
6
tests/dynamic_fixtures/surface/rust_actix/main.rs
Normal file
|
|
@ -0,0 +1,6 @@
|
|||
use actix_web::{get, HttpResponse};
|
||||
|
||||
#[get("/users")]
|
||||
async fn list_users() -> HttpResponse {
|
||||
HttpResponse::Ok().finish()
|
||||
}
|
||||
9
tests/dynamic_fixtures/surface/rust_axum/main.rs
Normal file
9
tests/dynamic_fixtures/surface/rust_axum/main.rs
Normal file
|
|
@ -0,0 +1,9 @@
|
|||
use axum::{routing::get, Router};
|
||||
|
||||
async fn list_users() -> &'static str {
|
||||
"[]"
|
||||
}
|
||||
|
||||
fn app() -> Router {
|
||||
Router::new().route("/users", get(list_users))
|
||||
}
|
||||
|
|
@ -0,0 +1,3 @@
|
|||
export async function GET(req: Request): Promise<Response> {
|
||||
return new Response("ok");
|
||||
}
|
||||
208
tests/surface_cross_lang.rs
Normal file
208
tests/surface_cross_lang.rs
Normal file
|
|
@ -0,0 +1,208 @@
|
|||
//! Phase 22 — cross-language `SurfaceMap` framework probes.
|
||||
//!
|
||||
//! One fixture per (language, framework) pair under
|
||||
//! `tests/dynamic_fixtures/surface/<probe>/`. Each probe is exercised
|
||||
//! through the public [`build_surface_map`] entry point and asserted
|
||||
//! on:
|
||||
//!
|
||||
//! 1. At least one [`SurfaceNode::EntryPoint`] is emitted.
|
||||
//! 2. The recognised entry-point carries the expected [`Framework`]
|
||||
//! tag.
|
||||
//! 3. The recognised entry-point's `route` field contains the expected
|
||||
//! substring (the path declared in the fixture).
|
||||
|
||||
use nyx_scanner::callgraph::CallGraph;
|
||||
use nyx_scanner::summary::GlobalSummaries;
|
||||
use nyx_scanner::surface::{
|
||||
Framework, SurfaceMap, SurfaceNode,
|
||||
build::{build_surface_map, SurfaceBuildInputs},
|
||||
};
|
||||
use nyx_scanner::utils::config::Config;
|
||||
use std::path::{Path, PathBuf};
|
||||
|
||||
const FIXTURE_ROOT: &str = "tests/dynamic_fixtures/surface";
|
||||
|
||||
fn empty_call_graph() -> CallGraph {
|
||||
CallGraph {
|
||||
graph: petgraph::graph::DiGraph::new(),
|
||||
index: Default::default(),
|
||||
unresolved_not_found: vec![],
|
||||
unresolved_ambiguous: vec![],
|
||||
}
|
||||
}
|
||||
|
||||
fn build(fixture_dir: &str) -> SurfaceMap {
|
||||
let dir = Path::new(FIXTURE_ROOT).join(fixture_dir);
|
||||
let mut files: Vec<PathBuf> = Vec::new();
|
||||
walk(&dir, &mut files);
|
||||
let cfg = Config::default();
|
||||
let gs = GlobalSummaries::new();
|
||||
let cg = empty_call_graph();
|
||||
let inputs = SurfaceBuildInputs {
|
||||
files: &files,
|
||||
scan_root: Some(&dir),
|
||||
global_summaries: &gs,
|
||||
call_graph: &cg,
|
||||
config: &cfg,
|
||||
};
|
||||
build_surface_map(&inputs)
|
||||
}
|
||||
|
||||
fn walk(dir: &Path, out: &mut Vec<PathBuf>) {
|
||||
let entries = match std::fs::read_dir(dir) {
|
||||
Ok(e) => e,
|
||||
Err(_) => return,
|
||||
};
|
||||
for entry in entries.flatten() {
|
||||
let path = entry.path();
|
||||
if path.is_dir() {
|
||||
walk(&path, out);
|
||||
} else {
|
||||
out.push(path);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fn assert_entry(map: &SurfaceMap, framework: Framework, route_substr: &str) {
|
||||
let routes: Vec<String> = map
|
||||
.nodes
|
||||
.iter()
|
||||
.filter_map(|n| match n {
|
||||
SurfaceNode::EntryPoint(ep) if ep.framework == framework => Some(ep.route.clone()),
|
||||
_ => None,
|
||||
})
|
||||
.collect();
|
||||
assert!(
|
||||
!routes.is_empty(),
|
||||
"no entry-point with framework {:?} found in map = {:#?}",
|
||||
framework,
|
||||
map.nodes
|
||||
);
|
||||
assert!(
|
||||
routes.iter().any(|r| r.contains(route_substr)),
|
||||
"expected a route containing {route_substr:?}; got {routes:?}",
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn python_flask_fixture() {
|
||||
let map = build("python_flask");
|
||||
assert_entry(&map, Framework::Flask, "/users");
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn python_fastapi_fixture() {
|
||||
let map = build("python_fastapi");
|
||||
assert_entry(&map, Framework::FastApi, "/items");
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn python_django_fixture() {
|
||||
let map = build("python_django");
|
||||
assert_entry(&map, Framework::Django, "admin");
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn js_express_fixture() {
|
||||
let map = build("js_express");
|
||||
assert_entry(&map, Framework::Express, "/users");
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn js_koa_fixture() {
|
||||
let map = build("js_koa");
|
||||
// koa probe currently emits the Express variant tag because the
|
||||
// SurfaceMap framework taxonomy folds koa-router under the
|
||||
// generic "node http microframework" bucket. See
|
||||
// [`nyx_scanner::surface::lang::js_koa`] doc comment.
|
||||
assert_entry(&map, Framework::Express, "/users");
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn ts_next_fixture() {
|
||||
let map = build("ts_next");
|
||||
assert_entry(&map, Framework::NextAppRouter, "users");
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn java_spring_fixture() {
|
||||
let map = build("java_spring");
|
||||
assert_entry(&map, Framework::Spring, "/api/users");
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn java_servlet_fixture() {
|
||||
let map = build("java_servlet");
|
||||
assert_entry(&map, Framework::JaxRs, "/users");
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn java_quarkus_fixture() {
|
||||
let map = build("java_quarkus");
|
||||
assert_entry(&map, Framework::JaxRs, "/api/hello");
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn go_http_fixture() {
|
||||
let map = build("go_http");
|
||||
assert_entry(&map, Framework::NetHttp, "/users");
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn go_gin_fixture() {
|
||||
let map = build("go_gin");
|
||||
assert_entry(&map, Framework::Gin, "/users");
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn php_laravel_fixture() {
|
||||
let map = build("php_laravel");
|
||||
// Laravel folds into the generic Sinatra-like framework bucket
|
||||
// because the SurfaceMap framework taxonomy is method-call shaped
|
||||
// rather than per-stack. See `surface::lang::php_laravel`.
|
||||
assert_entry(&map, Framework::Sinatra, "/users");
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn php_slim_fixture() {
|
||||
let map = build("php_slim");
|
||||
assert_entry(&map, Framework::Sinatra, "/users");
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn ruby_sinatra_fixture() {
|
||||
let map = build("ruby_sinatra");
|
||||
assert_entry(&map, Framework::Sinatra, "/users");
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn ruby_rails_fixture() {
|
||||
let map = build("ruby_rails");
|
||||
// Controller actions have empty routes because the route table
|
||||
// lives in `config/routes.rb` (separate file). Assert on the
|
||||
// handler name surfacing instead.
|
||||
let handlers: Vec<String> = map
|
||||
.nodes
|
||||
.iter()
|
||||
.filter_map(|n| match n {
|
||||
SurfaceNode::EntryPoint(ep) if ep.framework == Framework::Rails => {
|
||||
Some(ep.handler_name.clone())
|
||||
}
|
||||
_ => None,
|
||||
})
|
||||
.collect();
|
||||
assert!(handlers.contains(&"index".to_string()));
|
||||
assert!(handlers.contains(&"show".to_string()));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn rust_actix_fixture() {
|
||||
let map = build("rust_actix");
|
||||
assert_entry(&map, Framework::Actix, "/users");
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn rust_axum_fixture() {
|
||||
let map = build("rust_axum");
|
||||
assert_entry(&map, Framework::Axum, "/users");
|
||||
}
|
||||
Loading…
Add table
Add a link
Reference in a new issue