Skip to content

Add WebApp.API ruleset for detecting exposed HTTP/REST APIs - #651

Draft
Giulia Stocco (gfs) wants to merge 5 commits into
mainfrom
gfs-upgraded-fishstick
Draft

Giulia Stocco (gfs) wants to merge 5 commits into
mainfrom
gfs-upgraded-fishstick

Conversation

@gfs

@gfs Giulia Stocco (gfs) commented Aug 1, 2026 •

Copy link
Copy Markdown
Contributor

Closes #645. Related to #563 / #578.

Stacked on #653 — that PR fixes the build-file tag filter this ruleset depends on. Review #653 first; this diff shows only the ruleset once it's targeted correctly.

Supersedes draft PR #646, which I reviewed empirically before writing this. See "Relationship to #646" below.

What this adds

A WebApp.API.* ruleset (AppInspector/rules/default/webapp/api.json, 34 rules, AI090000–AI090801) that answers the question in #645: does this repository expose an API?

Stack Detected
Python FastAPI, Flask, Django REST, aiohttp, Tornado, Sanic, Falcon, Starlette, Bottle, CherryPy, Pyramid
JS/TS Express, NestJS, Koa, Fastify, Restify, Hapi, Hono, Node/Bun/Deno HTTP servers, Next.js route handlers
.NET ASP.NET controllers, Minimal APIs, Azure Functions HTTP triggers (C# attribute + function.json binding), ASP.NET gRPC, Swashbuckle/AddOpenApi
JVM Spring, JAX-RS, Ktor, Micronaut, Vert.x, Javalin, Spark, JDK HttpServer
Go net/http, Gin, Echo, gorilla/mux, chi, Fiber, gRPC
Ruby Sinatra, Rails ActionController::API, Grape
PHP Laravel, Symfony, Slim
Rust axum, actix-web, Rocket, warp
Specs OpenAPI/Swagger documents (JSON + YAML), GraphQL servers (Apollo, Yoga, Graphene, Strawberry)

Tags are hierarchical — WebApp.API.<Stack>.<Framework> — so a consumer answering "does this expose an API?" prefix-matches WebApp.API., and one asking "which framework?" reads the full tag. No duplicated umbrella tag is emitted; no existing rule in the repo emits both a parent and a child tag, and adding one here would have broken that convention.

Avoiding false positives

Six rules match patterns that are inherently low precision in isolation — framework-agnostic route registration like @x.get(, app.get(, .MapGet(, $app->get(. Each is split into its own rule gated behind a rule-level conditions entry with search_in: "same-file" requiring a matching framework import:

"patterns": [ { "pattern": "@\\w+\\.(get|post|put|delete|patch|head|options)\\(", ... } ],
"conditions": [ {
  "pattern": { "pattern": "fastapi", "type": "regexword", "modifiers": ["i"] },
  "search_in": "same-file",
  "negate_finding": false
} ]

So @app.get("/items") in a file that imports FastAPI is a finding; @mock.patch("pkg.send") in a test file is not. The gated rules are AI090001 (FastAPI), AI090003 (Flask), AI090101 (Express), AI090201 (Minimal API), AI090501 (Sinatra), AI090602 (Slim).

AI090800 (OpenAPI) additionally requires an info key in the same file and a 2./3. version prefix, so a package.json that merely depends on a swagger package is not mistaken for a spec document.

Validation

Built the CLI and ran the ruleset against two synthetic corpora.

False-positive corpus (11 files: Python unittest.mock decorators, client-side router.get('key')/app.delete('entry'), a Mapper class calling source.MapPost(y), RSpec get '/spec/fixture' do, a package.json depending on swagger-ui/express-rate-limit/koa-body, a queueTrigger function.json, an Azure Pipelines yaml, Go/Java/C#/PHP HTTP client code): 0 findings.

True-positive corpus (28 files across every stack above): 29 of 29 tags detected.

Scanning this repository itself produces 0 WebApp.API hits, which is correct — Application Inspector is a CLI tool, not a service.

Two supporting changes

tagreportgroups.json — added a ^WebApp\.API\..*$ / "Exposed web API" entry to the Select Features group. Without it the new tags are in the JSON/SARIF output but absent from HTML reports. Verified the group renders in a generated report.

languages.json — added .kt to the kotlin entry. It previously listed only .kts, so ordinary Kotlin source files were skipped entirely. This blocked the Ktor rule and, more importantly, silently disabled every existing rule that targets kotlin for real Kotlin code. This is a pre-existing bug that this ruleset happened to surface.

Why this depends on #653

Two rules read from build-type files, and both are cases where the config file is the only place the fact is stated:

  • AI090202 — Azure Functions function.json. In the in-process model there is no C# attribute at all; the binding declaration is the API.
  • AI090800 — openapi.yaml / swagger.json, which enhanced API detection engine #645 names explicitly.

Before #653, RuleProcessor discarded every non-Metadata tag from json/yaml files, so these rules could not report anything unless the user passed -A. I initially worked around it by naming the tags WebApp.API.Metadata.*, but that was contorting the taxonomy to fit a bug — investigating it turned up 22 existing rules that were silently dead for the same reason. #653 fixes the filter properly, and this PR now uses the natural tag names.

Relationship to #646

Draft PR #646 proposed a 12-rule version of this ruleset. I built it and ran it against the same corpora before writing this one. It produced four false-positive classes, all at Medium confidence and therefore visible under the default Medium,High filter:

Rule Pattern Matches
AI090000 @\w+\.(get|post|...)\( @mock.patch(...), @responses.get(, @httpretty.post( — near-universal in Python test suites
AI090100 \b(app|router)\.(get|post|...)\( client-side app.get('config'), router.delete('item')
AI090200 \.Map(Get|Post|...)\( any source.MapPost(y)
AI090500 ^\s*(get|post|...)\s+['"]/ RSpec/Cucumber get '/spec/fixture' do

The first is the most damaging for #645's stated use case — it mislabels any Python repo containing mocks as exposing a REST API.

It also missed NestJS, Next.js, Ktor, Micronaut, Vert.x, Javalin, chi, Fiber, Bun, Deno, Hono, Grape, Slim, and gRPC, and did not update tagreportgroups.json.

This PR keeps #646's rule-ID range and file location so the two are directly comparable.

Possible follow-up

Now that #653 unblocks build-file rules, there are further API declarations that live only in yaml/json and aren't covered here: AWS SAM / CloudFormation (AWS::Serverless::Api, AWS::ApiGateway::RestApi), serverless.yml (events: - http:), Kubernetes kind: Ingress / Gateway / HTTPRoute, API gateway configs (Kong, Traefik, Envoy), asyncapi: documents, and launchSettings.json applicationUrl. For a serverless repo the yaml is the API — there's no route decorator anywhere in the source. Happy to add these here or in a follow-up, whichever you prefer.

Allow explicitly targeted rules to report build-file findings while preserving suppression for universal rules.

Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com>
Copilot-Session: f0f9dbe0-e8ba-472b-b645-eda10038e683
Adds 34 rules under WebApp.API.* that identify when a repository exposes
an HTTP, gRPC, or GraphQL API, covering Python, JavaScript/TypeScript,
.NET, JVM, Go, Ruby, PHP, Rust, and OpenAPI/Swagger documents.

Six rules whose patterns are inherently low precision (framework-agnostic
route registration such as `@x.get(`, `app.get(`, `.MapGet(`) are gated
behind a rule-level `same-file` condition requiring a matching framework
import, so they only fire in files that actually use the framework.

Also:
- Adds an "Exposed web API" entry to the HTML report's Select Features
  group so WebApp.API findings are surfaced in generated reports.
- Adds `.kt` to the kotlin entry in languages.json. Only `.kts` was
  listed, so ordinary Kotlin source files were never scanned.

Builds on the parent commit, which stopped RuleProcessor from suppressing
tags emitted by rules that explicitly target build-type files. Two rules
here depend on that: AI090202 reads Azure Functions bindings from
function.json, and AI090800 reads OpenAPI documents from json and yaml.

Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com>
Copilot-Session: cac8997c-55eb-4b89-aebd-6592d0525759
@gfs
Giulia Stocco (gfs) changed the base branch from main to gfs-fix-build-file-tag-filter August 1, 2026 18:50
Base automatically changed from gfs-fix-build-file-tag-filter to main August 3, 2026 17:49
@gfs
Giulia Stocco (gfs) requested a balanced review from Copilot September 10, 2026 20:59

Copilot AI left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Copilot review overview

🟡 Changes recommended

Several rules misclassify client-only code as exposed APIs, while valid Next.js handlers are missed.

Once you've addressed the issues Copilot identified, you can request another Copilot review.

Review tier: Balanced
Findings: 5 Medium severity

New issues introduced by this change (5)
Severity Finding
Medium severity AppInspector/​rules/​default/​webapp/​api.json — Restrict the Express condition to Express
Medium severity AppInspector/​rules/​default/​webapp/​api.json — Match root Next.js App Router handlers
Medium severity AppInspector/​rules/​default/​webapp/​api.json — Accept valid named Pages API handlers
Medium severity AppInspector/​rules/​default/​webapp/​api.json — Exclude JAX-RS client-only imports
Medium severity AppInspector/​rules/​default/​webapp/​api.json — Do not treat Graphene schemas as exposed servers
What changed in this PR

Adds cross-language rules for detecting repositories that expose HTTP, REST, gRPC, GraphQL, or specification-based APIs.

Changes:

  • Adds 34 WebApp.API.* detection rules.
  • Refines build-file tag filtering and regression coverage.
  • Adds HTML report grouping and Kotlin .kt recognition.
File Description
AppInspector/​rules/​default/​webapp/​api.json Adds API detection rules.
AppInspector.RulesEngine/​Rule.cs Defines universal-rule classification.
AppInspector.RulesEngine/​AbstractRuleSet.cs Reuses universal-rule classification.
AppInspector.RulesEngine/​RuleProcessor.cs Adjusts build-file filtering.
AppInspector/​Commands/​AnalyzeCommand.cs Updates option documentation.
AppInspector.CLI/​CLICmdOptions.cs Clarifies CLI help text.
AppInspector.Tests/​RuleProcessor/​BuildFileRuleTests.cs Tests build-file filtering.
AppInspector.Tests/​RuleProcessor/​XmlAndJsonTests.cs Removes obsolete filter overrides.
AppInspector.RulesEngine/​Resources/​languages.json Recognizes Kotlin .kt files.
AppInspector.CLI/​preferences/​tagreportgroups.json Displays API findings in HTML reports.

💡 Add a code-review agent skill or configure MCP servers for context-aware, tailored reviews. Learn more in the docs.

Comment thread AppInspector/rules/default/webapp/api.json Outdated
Comment thread AppInspector/rules/default/webapp/api.json Outdated
Comment thread AppInspector/rules/default/webapp/api.json Outdated
Comment thread AppInspector/rules/default/webapp/api.json Outdated
Comment thread AppInspector/rules/default/webapp/api.json Outdated
Co-authored-by: gfs <98900+gfs@users.noreply.github.com>

Copilot AI left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Copilot review overview

🟡 Changes recommended

Several patterns still classify client-only or non-HTTP framework usage as exposed APIs.

Once you've addressed the issues Copilot identified, you can request another Copilot review.

Review tier: Balanced
Findings: 1 Medium severity

New issues introduced by this change (1)
Severity Finding
Medium severity AppInspector/​rules/​default/​webapp/​api.json — Exclude annotation-based JAX-RS clients
Issues resolved since last review (5)
Severity Finding
Medium severity AppInspector/​rules/​default/​webapp/​api.json — Do not treat Graphene schemas as exposed servers View resolved comment
Medium severity AppInspector/​rules/​default/​webapp/​api.json — Exclude JAX-RS client-only imports View resolved comment
Medium severity AppInspector/​rules/​default/​webapp/​api.json — Accept valid named Pages API handlers View resolved comment
Medium severity AppInspector/​rules/​default/​webapp/​api.json — Match root Next.js App Router handlers View resolved comment
Medium severity AppInspector/​rules/​default/​webapp/​api.json — Restrict the Express condition to Express View resolved comment
Previously missed findings (4)

In code that hasn't changed since last review

AppInspector/rules/default/webapp/api.json:210

  • This generic rest_framework match reports an exposed API for code that only uses DRF serializers; the rule’s own from rest_framework import serializers must-match demonstrates that false positive. Restrict this branch to server constructs such as API views, viewsets, or router registrations, while retaining the existing @api_view detection.
    AppInspector/rules/default/webapp/api.json:506
  • @nestjs/common is imported by non-HTTP NestJS applications for providers, modules, lifecycle hooks, and microservices, so an Injectable-only file is currently labeled as exposing a web API. The controller decorator below is the relevant HTTP exposure signal; remove the generic import alternative and its positive self-test.
    AppInspector/rules/default/webapp/api.json:897
  • Spring OpenFeign client interfaces commonly use @GetMapping/@RequestMapping, so this alternative labels outbound-only clients as exposed APIs even when no @RestController exists. Gate mapping annotations on server-controller context or explicitly exclude @FeignClient declarations; add that client form as a negative self-test.
    AppInspector.RulesEngine/Resources/languages.json:111
  • The new .kt mapping fixes a previously skipped source type, but no language-detection test asserts that an ordinary .kt filename resolves to kotlin. Add a case in LanguagesTests so this behavior cannot regress independently of the Ktor rule self-tests, which do not exercise filename-to-language resolution.

Comment on lines +925 to +929
"pattern": "(javax|jakarta)\\.ws\\.rs\\.(ApplicationPath|Path|GET|POST|PUT|DELETE|PATCH|HEAD|OPTIONS|Produces|Consumes|BeanParam|PathParam|QueryParam|FormParam|HeaderParam|Context)\\b|ResourceConfig\\s*\\(",
"type": "regex",
"scopes": [
"code"
],
* Initial plan

* Address remaining API review findings

Co-authored-by: gfs <98900+gfs@users.noreply.github.com>

* Update expression generation expectation for pattern conditions

Co-authored-by: gfs <98900+gfs@users.noreply.github.com>

---------

Co-authored-by: copilot-swe-agent[bot] <198982749+Copilot@users.noreply.github.com>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

enhanced API detection engine

4 participants