circle-ir 3.165.0 → 3.166.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -20148,9 +20148,28 @@ function methodIsSupertypeLifecycleEntryPoint(method, enclosingType) {
20148
20148
  return false;
20149
20149
  }
20150
20150
  function classifyEntryPointTier(method, enclosingType, ctx) {
20151
- const language = (ctx.language ?? "").toLowerCase();
20152
- if (language !== "java") return "TIER_UNKNOWN";
20153
20151
  if (!method) return "TIER_UNKNOWN";
20152
+ const language = (ctx.language ?? "").toLowerCase();
20153
+ switch (language) {
20154
+ case "java":
20155
+ return classifyJavaEntryPoint(method, enclosingType);
20156
+ case "python":
20157
+ return classifyPythonEntryPoint(method, enclosingType, ctx);
20158
+ case "javascript":
20159
+ case "typescript":
20160
+ case "tsx":
20161
+ case "jsx":
20162
+ return classifyJsTsEntryPoint(method, enclosingType, ctx);
20163
+ case "go":
20164
+ return classifyGoEntryPoint(method, enclosingType, ctx);
20165
+ case "bash":
20166
+ case "shell":
20167
+ return classifyBashEntryPoint(method, enclosingType, ctx);
20168
+ default:
20169
+ return "TIER_UNKNOWN";
20170
+ }
20171
+ }
20172
+ function classifyJavaEntryPoint(method, enclosingType) {
20154
20173
  if (classShapeIsLibraryFacade(enclosingType)) {
20155
20174
  return "TIER_3_LIBRARY_API";
20156
20175
  }
@@ -20174,6 +20193,413 @@ function shouldGateInterproceduralParam(sourceType, enclosingMethod, enclosingTy
20174
20193
  const tier = classifyEntryPointTier(enclosingMethod, enclosingType, ctx);
20175
20194
  return tier === "TIER_3_LIBRARY_API";
20176
20195
  }
20196
+ var POLYGLOT_LIBRARY_PATH_FRAGMENTS = [
20197
+ "/lib/",
20198
+ "/libapi/",
20199
+ "/libs/",
20200
+ "/utils/",
20201
+ "/util/",
20202
+ "/helpers/",
20203
+ "/helper/",
20204
+ "/interop/",
20205
+ "/vendor/",
20206
+ "/vendored/",
20207
+ "/node_modules/",
20208
+ "/dist/",
20209
+ "/build/",
20210
+ "/_internal/",
20211
+ "/__tests__/",
20212
+ "/tests/",
20213
+ "/test/",
20214
+ "/testing/",
20215
+ "/spec/",
20216
+ "/specs/",
20217
+ "/fixtures/",
20218
+ "/mocks/",
20219
+ "/__mocks__/"
20220
+ ];
20221
+ var POLYGLOT_TEST_FILE_MARKERS = [
20222
+ ".test.",
20223
+ ".spec.",
20224
+ "_test.",
20225
+ "_spec.",
20226
+ ".tests."
20227
+ ];
20228
+ function pathLooksLikeLibraryOrTest(filePath) {
20229
+ if (!filePath) return false;
20230
+ const normalized = filePath.replace(/\\/g, "/").toLowerCase();
20231
+ const padded = `/${normalized}/`;
20232
+ for (const frag of POLYGLOT_LIBRARY_PATH_FRAGMENTS) {
20233
+ if (padded.includes(frag)) return true;
20234
+ }
20235
+ const slash = normalized.lastIndexOf("/");
20236
+ const base = slash >= 0 ? normalized.slice(slash + 1) : normalized;
20237
+ for (const marker of POLYGLOT_TEST_FILE_MARKERS) {
20238
+ if (base.includes(marker)) return true;
20239
+ }
20240
+ return false;
20241
+ }
20242
+ var PYTHON_TIER_1_DECORATOR_NAMES = /* @__PURE__ */ new Set([
20243
+ // Flask
20244
+ "route",
20245
+ "get",
20246
+ "post",
20247
+ "put",
20248
+ "delete",
20249
+ "patch",
20250
+ "options",
20251
+ "head",
20252
+ "before_request",
20253
+ "after_request",
20254
+ "errorhandler",
20255
+ "teardown_request",
20256
+ // FastAPI
20257
+ "websocket",
20258
+ "websocket_route",
20259
+ "api_route",
20260
+ "middleware",
20261
+ // Django
20262
+ "login_required",
20263
+ "csrf_exempt",
20264
+ "csrf_protect",
20265
+ "require_http_methods",
20266
+ "require_GET",
20267
+ "require_POST",
20268
+ "require_safe",
20269
+ "permission_required",
20270
+ "user_passes_test",
20271
+ "staff_member_required",
20272
+ "api_view",
20273
+ "action",
20274
+ "detail_route",
20275
+ "list_route",
20276
+ "renderer_classes",
20277
+ "authentication_classes",
20278
+ "permission_classes",
20279
+ // Click / Typer
20280
+ "command",
20281
+ "group",
20282
+ // Celery / RQ / dramatiq
20283
+ "task",
20284
+ "shared_task",
20285
+ "periodic_task",
20286
+ "actor",
20287
+ // aiohttp
20288
+ "view",
20289
+ // pytest fixtures ARE library callback surfaces (test framework calls
20290
+ // them), so we mark them TIER_1 too — a taint sink inside a fixture
20291
+ // is genuinely reachable when the test runs.
20292
+ "fixture"
20293
+ ]);
20294
+ function classifyPythonEntryPoint(method, enclosingType, ctx) {
20295
+ if (pathLooksLikeLibraryOrTest(ctx.filePath)) {
20296
+ return "TIER_3_LIBRARY_API";
20297
+ }
20298
+ if (pythonDecoratorLooksTier1(method.annotations)) {
20299
+ return "TIER_1_ENTRY_POINT";
20300
+ }
20301
+ if (methodIsRuntimeRegistrationHandler(method, ctx.runtimeRegistrations)) {
20302
+ return "TIER_1_ENTRY_POINT";
20303
+ }
20304
+ if (method.name === "main" && !enclosingType) {
20305
+ return "TIER_1_ENTRY_POINT";
20306
+ }
20307
+ if (method.name === "main" && enclosingType && looksLikeModuleType(enclosingType)) {
20308
+ return "TIER_1_ENTRY_POINT";
20309
+ }
20310
+ if (method.name.startsWith("_") && !method.name.startsWith("__")) {
20311
+ return "TIER_3_LIBRARY_API";
20312
+ }
20313
+ return "TIER_UNKNOWN";
20314
+ }
20315
+ function pythonDecoratorLooksTier1(annotations) {
20316
+ if (!annotations || annotations.length === 0) return false;
20317
+ for (const raw of annotations) {
20318
+ let name2 = raw.replace(/^@/, "").replace(/[<(].*$/, "").trim();
20319
+ if (!name2) continue;
20320
+ const dot = name2.lastIndexOf(".");
20321
+ if (dot >= 0) name2 = name2.slice(dot + 1);
20322
+ if (PYTHON_TIER_1_DECORATOR_NAMES.has(name2)) return true;
20323
+ }
20324
+ return false;
20325
+ }
20326
+ function looksLikeModuleType(t) {
20327
+ return !t.extends && (!t.implements || t.implements.length === 0) && (!t.annotations || t.annotations.length === 0);
20328
+ }
20329
+ var JSTS_TIER_1_METHOD_DECORATORS = /* @__PURE__ */ new Set([
20330
+ // NestJS HTTP method decorators
20331
+ "Get",
20332
+ "Post",
20333
+ "Put",
20334
+ "Delete",
20335
+ "Patch",
20336
+ "Head",
20337
+ "Options",
20338
+ "All",
20339
+ // NestJS WebSocket
20340
+ "SubscribeMessage",
20341
+ "MessageBody",
20342
+ "ConnectedSocket",
20343
+ // NestJS microservices / event
20344
+ "EventPattern",
20345
+ "MessagePattern",
20346
+ "GrpcMethod",
20347
+ "GrpcStreamMethod",
20348
+ // Angular / other DI-registered handler hooks
20349
+ "HostListener"
20350
+ ]);
20351
+ var JSTS_TIER_1_CLASS_DECORATORS = /* @__PURE__ */ new Set([
20352
+ "Controller",
20353
+ "RestController",
20354
+ "Resolver",
20355
+ "WebSocketGateway",
20356
+ "Gateway"
20357
+ ]);
20358
+ var JSTS_TIER_1_MODULE_EXPORTS = /* @__PURE__ */ new Set([
20359
+ // AWS Lambda / Google Cloud Functions / Netlify / Vercel
20360
+ "handler",
20361
+ // Next.js App Router — HTTP method exports
20362
+ "GET",
20363
+ "POST",
20364
+ "PUT",
20365
+ "DELETE",
20366
+ "PATCH",
20367
+ "HEAD",
20368
+ "OPTIONS",
20369
+ // SvelteKit / Remix
20370
+ "load",
20371
+ "action",
20372
+ // Next.js middleware
20373
+ "middleware"
20374
+ ]);
20375
+ function classifyJsTsEntryPoint(method, enclosingType, ctx) {
20376
+ if (pathLooksLikeLibraryOrTest(ctx.filePath)) {
20377
+ return "TIER_3_LIBRARY_API";
20378
+ }
20379
+ if (annotationsInclude(method.annotations, JSTS_TIER_1_METHOD_DECORATORS)) {
20380
+ return "TIER_1_ENTRY_POINT";
20381
+ }
20382
+ if (enclosingType && annotationsInclude(enclosingType.annotations, JSTS_TIER_1_CLASS_DECORATORS)) {
20383
+ return "TIER_1_ENTRY_POINT";
20384
+ }
20385
+ if (methodIsRuntimeRegistrationHandler(method, ctx.runtimeRegistrations)) {
20386
+ return "TIER_1_ENTRY_POINT";
20387
+ }
20388
+ if (JSTS_TIER_1_MODULE_EXPORTS.has(method.name) && (!enclosingType || looksLikeModuleType(enclosingType))) {
20389
+ return "TIER_1_ENTRY_POINT";
20390
+ }
20391
+ if (method.name === "main" && (!enclosingType || looksLikeModuleType(enclosingType))) {
20392
+ return "TIER_1_ENTRY_POINT";
20393
+ }
20394
+ return "TIER_UNKNOWN";
20395
+ }
20396
+ var GO_HTTP_REGISTRAR_METHODS = /* @__PURE__ */ new Set([
20397
+ // net/http
20398
+ "HandleFunc",
20399
+ "Handle",
20400
+ // gorilla/mux, chi, echo, gin
20401
+ "GET",
20402
+ "POST",
20403
+ "PUT",
20404
+ "DELETE",
20405
+ "PATCH",
20406
+ "HEAD",
20407
+ "OPTIONS",
20408
+ "Any",
20409
+ "Get",
20410
+ "Post",
20411
+ "Put",
20412
+ "Delete",
20413
+ "Patch",
20414
+ "Head",
20415
+ "Options",
20416
+ // gorilla/chi/gin sub-routers
20417
+ "Route",
20418
+ "Mount",
20419
+ "Method"
20420
+ ]);
20421
+ var GO_HTTP_REGISTRAR_RECEIVERS = [
20422
+ "http",
20423
+ // net/http.HandleFunc
20424
+ "mux",
20425
+ // gorilla/mux
20426
+ "router",
20427
+ // chi/gin/gorilla common
20428
+ "r",
20429
+ // gin/chi convention
20430
+ "e",
20431
+ // echo convention
20432
+ "g",
20433
+ // gin group convention
20434
+ "app",
20435
+ // fiber convention
20436
+ "engine",
20437
+ // gin engine convention
20438
+ "srv",
20439
+ "server"
20440
+ ];
20441
+ function classifyGoEntryPoint(method, enclosingType, ctx) {
20442
+ if (pathLooksLikeLibraryOrTest(ctx.filePath)) {
20443
+ return "TIER_3_LIBRARY_API";
20444
+ }
20445
+ if (ctx.filePath && ctx.filePath.toLowerCase().endsWith("_test.go")) {
20446
+ return "TIER_3_LIBRARY_API";
20447
+ }
20448
+ if (method.name === "main" && enclosingType?.package === "main") {
20449
+ return "TIER_1_ENTRY_POINT";
20450
+ }
20451
+ if (methodHasNetHttpHandlerSignature(method)) {
20452
+ return "TIER_1_ENTRY_POINT";
20453
+ }
20454
+ if (methodLooksLikeGrpcHandler(method, enclosingType)) {
20455
+ return "TIER_1_ENTRY_POINT";
20456
+ }
20457
+ if (methodIsRegisteredByGoHttpFramework(method, ctx.calls)) {
20458
+ return "TIER_1_ENTRY_POINT";
20459
+ }
20460
+ return "TIER_UNKNOWN";
20461
+ }
20462
+ function methodHasNetHttpHandlerSignature(method) {
20463
+ const params = method.parameters ?? [];
20464
+ if (params.length !== 2) return false;
20465
+ const p0 = normalizeGoType(params[0]?.type ?? "");
20466
+ const p1 = normalizeGoType(params[1]?.type ?? "");
20467
+ const p0IsWriter = p0.includes("http.ResponseWriter") || p0.endsWith("ResponseWriter");
20468
+ const p1IsRequest = p1.includes("http.Request") || p1.endsWith("*Request") || p1.endsWith("Request");
20469
+ return p0IsWriter && p1IsRequest;
20470
+ }
20471
+ function methodLooksLikeGrpcHandler(method, enclosingType) {
20472
+ if (!enclosingType) return false;
20473
+ const params = method.parameters ?? [];
20474
+ if (params.length < 2) return false;
20475
+ const p0 = normalizeGoType(params[0]?.type ?? "");
20476
+ if (!p0.includes("context.Context") && !p0.endsWith("Context")) return false;
20477
+ const typeName = enclosingType.name ?? "";
20478
+ if (!/(Server|Service|Handler)$/.test(typeName)) return false;
20479
+ return true;
20480
+ }
20481
+ function normalizeGoType(t) {
20482
+ return t.replace(/\s+/g, "").replace(/^\*+/, "");
20483
+ }
20484
+ function methodIsRegisteredByGoHttpFramework(method, calls) {
20485
+ if (!calls || calls.length === 0) return false;
20486
+ for (const call of calls) {
20487
+ if (!GO_HTTP_REGISTRAR_METHODS.has(call.method_name)) continue;
20488
+ const receiver = call.receiver ?? "";
20489
+ if (!goRegistrarReceiverMatches(receiver)) continue;
20490
+ const args2 = call.arguments ?? [];
20491
+ for (const arg of args2) {
20492
+ const expr = (arg.expression ?? arg.variable ?? arg.value ?? "").trim();
20493
+ if (!expr) continue;
20494
+ const short = expr.slice(expr.lastIndexOf(".") + 1);
20495
+ if (short === method.name) return true;
20496
+ }
20497
+ }
20498
+ return false;
20499
+ }
20500
+ function goRegistrarReceiverMatches(receiver) {
20501
+ const trimmed = receiver.trim();
20502
+ if (!trimmed) return false;
20503
+ for (const target of GO_HTTP_REGISTRAR_RECEIVERS) {
20504
+ if (trimmed === target) return true;
20505
+ if (trimmed.endsWith(`.${target}`)) return true;
20506
+ }
20507
+ return false;
20508
+ }
20509
+ var BASH_POSITIONAL_TOKENS = [
20510
+ "$1",
20511
+ "$2",
20512
+ "$3",
20513
+ "$4",
20514
+ "$5",
20515
+ "$6",
20516
+ "$7",
20517
+ "$8",
20518
+ "$9",
20519
+ "$@",
20520
+ "$*",
20521
+ "$#",
20522
+ "${1",
20523
+ "${2",
20524
+ "${3",
20525
+ "${@",
20526
+ "getopts"
20527
+ ];
20528
+ var BASH_LIBRARY_FILENAME_PREFIXES = [
20529
+ "benign_",
20530
+ "safe_",
20531
+ "lib_",
20532
+ "common_",
20533
+ "helpers_",
20534
+ "_"
20535
+ ];
20536
+ function classifyBashEntryPoint(method, enclosingType, ctx) {
20537
+ if (pathLooksLikeLibraryOrTest(ctx.filePath)) {
20538
+ return "TIER_3_LIBRARY_API";
20539
+ }
20540
+ if (bashFilenameLooksLikeLibrary(ctx.filePath)) {
20541
+ return "TIER_3_LIBRARY_API";
20542
+ }
20543
+ if (method.name === "main") {
20544
+ return "TIER_1_ENTRY_POINT";
20545
+ }
20546
+ if (methodConsumesPositionalArgs(method, ctx.calls)) {
20547
+ return "TIER_1_ENTRY_POINT";
20548
+ }
20549
+ if (enclosingType && looksLikeModuleType(enclosingType) && fileHasPositionalArgUse(ctx.calls)) {
20550
+ return "TIER_1_ENTRY_POINT";
20551
+ }
20552
+ return "TIER_UNKNOWN";
20553
+ }
20554
+ function bashFilenameLooksLikeLibrary(filePath) {
20555
+ if (!filePath) return false;
20556
+ const normalized = filePath.replace(/\\/g, "/");
20557
+ const slash = normalized.lastIndexOf("/");
20558
+ const base = (slash >= 0 ? normalized.slice(slash + 1) : normalized).toLowerCase();
20559
+ for (const prefix of BASH_LIBRARY_FILENAME_PREFIXES) {
20560
+ if (base.startsWith(prefix)) return true;
20561
+ }
20562
+ if (base.includes(".test.") || base.includes("_test.")) return true;
20563
+ return false;
20564
+ }
20565
+ function methodConsumesPositionalArgs(method, calls) {
20566
+ if (!calls || calls.length === 0) return false;
20567
+ for (const call of calls) {
20568
+ if (call.location.line < method.start_line) continue;
20569
+ if (call.location.line > method.end_line) continue;
20570
+ if (callHasPositionalToken(call)) return true;
20571
+ }
20572
+ return false;
20573
+ }
20574
+ function fileHasPositionalArgUse(calls) {
20575
+ if (!calls || calls.length === 0) return false;
20576
+ for (const call of calls) {
20577
+ if (callHasPositionalToken(call)) return true;
20578
+ }
20579
+ return false;
20580
+ }
20581
+ function callHasPositionalToken(call) {
20582
+ for (const arg of call.arguments ?? []) {
20583
+ const expr = arg.expression ?? arg.variable ?? arg.value ?? "";
20584
+ for (const tok of BASH_POSITIONAL_TOKENS) {
20585
+ if (expr.includes(tok)) return true;
20586
+ }
20587
+ }
20588
+ if (call.method_name === "getopts") return true;
20589
+ return false;
20590
+ }
20591
+ function methodIsRuntimeRegistrationHandler(method, regs) {
20592
+ if (!regs || regs.length === 0) return false;
20593
+ for (const reg of regs) {
20594
+ if (reg.kind !== "http_route" && reg.kind !== "decorator" && reg.kind !== "event_listener" && reg.kind !== "middleware") {
20595
+ continue;
20596
+ }
20597
+ const handlerName = reg.handler?.name;
20598
+ if (!handlerName) continue;
20599
+ if (handlerName === method.name) return true;
20600
+ }
20601
+ return false;
20602
+ }
20177
20603
 
20178
20604
  // src/analysis/project-profile-transform.ts
20179
20605
  var DOWNGRADE_ELIGIBLE_RULE_IDS = /* @__PURE__ */ new Set([
@@ -33145,7 +33571,13 @@ var InterproceduralPass = class {
33145
33571
  source.type,
33146
33572
  enclosing?.method,
33147
33573
  enclosing?.type,
33148
- { language, types: graph.ir.types }
33574
+ {
33575
+ language,
33576
+ types: graph.ir.types,
33577
+ filePath: graph.ir.meta.file,
33578
+ calls: graph.ir.calls,
33579
+ runtimeRegistrations: graph.ir.runtime_registrations ?? null
33580
+ }
33149
33581
  )) {
33150
33582
  continue;
33151
33583
  }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "circle-ir",
3
- "version": "3.165.0",
3
+ "version": "3.166.0",
4
4
  "description": "High-performance Static Application Security Testing (SAST) library for detecting security vulnerabilities through taint analysis",
5
5
  "main": "dist/index.js",
6
6
  "module": "dist/index.js",