Repository navigation
Add WebApp.API ruleset for detecting exposed HTTP/REST APIs - #651
Giulia Stocco (gfs) wants to merge 5 commits into
Conversation
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
98deafb to
a8283b1
Compare
There was a problem hiding this comment.
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
New issues introduced by this change (5)
| Severity | Finding |
|---|---|
AppInspector/rules/default/webapp/api.json — Restrict the Express condition to Express |
|
AppInspector/rules/default/webapp/api.json — Match root Next.js App Router handlers |
|
AppInspector/rules/default/webapp/api.json — Accept valid named Pages API handlers |
|
AppInspector/rules/default/webapp/api.json — Exclude JAX-RS client-only imports |
|
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
.ktrecognition.
| 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.
Co-authored-by: gfs <98900+gfs@users.noreply.github.com>
There was a problem hiding this comment.
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
New issues introduced by this change (1)
| Severity | Finding |
|---|---|
AppInspector/rules/default/webapp/api.json — Exclude annotation-based JAX-RS clients |
Issues resolved since last review (5)
| Severity | Finding |
|---|---|
AppInspector/rules/default/webapp/api.json — Do not treat Graphene schemas as exposed servers View resolved comment |
|
AppInspector/rules/default/webapp/api.json — Exclude JAX-RS client-only imports View resolved comment |
|
AppInspector/rules/default/webapp/api.json — Accept valid named Pages API handlers View resolved comment |
|
AppInspector/rules/default/webapp/api.json — Match root Next.js App Router handlers View resolved comment |
|
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_frameworkmatch reports an exposed API for code that only uses DRF serializers; the rule’s ownfrom rest_framework import serializersmust-match demonstrates that false positive. Restrict this branch to server constructs such as API views, viewsets, or router registrations, while retaining the existing@api_viewdetection.
AppInspector/rules/default/webapp/api.json:506 @nestjs/commonis imported by non-HTTP NestJS applications for providers, modules, lifecycle hooks, and microservices, so anInjectable-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@RestControllerexists. Gate mapping annotations on server-controller context or explicitly exclude@FeignClientdeclarations; add that client form as a negative self-test.
AppInspector.RulesEngine/Resources/languages.json:111 - The new
.ktmapping fixes a previously skipped source type, but no language-detection test asserts that an ordinary.ktfilename resolves tokotlin. Add a case inLanguagesTestsso this behavior cannot regress independently of the Ktor rule self-tests, which do not exercise filename-to-language resolution.
| "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>

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?function.jsonbinding), ASP.NET gRPC, Swashbuckle/AddOpenApiHttpServernet/http, Gin, Echo, gorilla/mux, chi, Fiber, gRPCActionController::API, GrapeTags are hierarchical —
WebApp.API.<Stack>.<Framework>— so a consumer answering "does this expose an API?" prefix-matchesWebApp.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-levelconditionsentry withsearch_in: "same-file"requiring a matching framework import: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 areAI090001(FastAPI),AI090003(Flask),AI090101(Express),AI090201(Minimal API),AI090501(Sinatra),AI090602(Slim).AI090800(OpenAPI) additionally requires aninfokey in the same file and a2./3.version prefix, so apackage.jsonthat 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.mockdecorators, client-siderouter.get('key')/app.delete('entry'), aMapperclass callingsource.MapPost(y), RSpecget '/spec/fixture' do, apackage.jsondepending onswagger-ui/express-rate-limit/koa-body, aqueueTriggerfunction.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.APIhits, which is correct — Application Inspector is a CLI tool, not a service.verifyrules: all 34 rulesStatus: Truedotnet test: 351 passed, 0 failed (matching the Allow explicitly targeted rules in build files #653 baseline)Two supporting changes
tagreportgroups.json— added a^WebApp\.API\..*$/ "Exposed web API" entry to theSelect Featuresgroup. 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.ktto thekotlinentry. 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 targetskotlinfor 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 Functionsfunction.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,
RuleProcessordiscarded every non-Metadatatag fromjson/yamlfiles, so these rules could not report anything unless the user passed-A. I initially worked around it by naming the tagsWebApp.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,Highfilter:AI090000@\w+\.(get|post|...)\(@mock.patch(...),@responses.get(,@httpretty.post(— near-universal in Python test suitesAI090100\b(app|router)\.(get|post|...)\(app.get('config'),router.delete('item')AI090200\.Map(Get|Post|...)\(source.MapPost(y)AI090500^\s*(get|post|...)\s+['"]/get '/spec/fixture' doThe 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:), Kuberneteskind: Ingress/Gateway/HTTPRoute, API gateway configs (Kong, Traefik, Envoy),asyncapi:documents, andlaunchSettings.jsonapplicationUrl. 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.