From c5361b0887ec34b8c5665c052894171b5ef0d7a9 Mon Sep 17 00:00:00 2001 From: Chad Bentz <1760475+felickz@users.noreply.github.com> Date: Tue, 23 Jun 2026 13:02:51 -0400 Subject: [PATCH] Add Java security queries for transport security and IDOR Adds five Java queries (each with a Markdown help doc): - CWE-319 CleartextLdapUrl: ldap:// URL on an LDAP context source - CWE-295 InsecureJdbcCertificateValidation: JDBC trustServerCertificate=true - CWE-598 CredentialsInOutboundUrl: credential getter reaching an outbound request URL - CWE-639 UserControlledRecordRetrieval: read/disclosure IDOR (path id to repository finder) - CWE-639 InsecureDirectObjectReference: modify IDOR, Java analogue of cs/web/insecure-direct-object-reference Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> --- .../InsecureJdbcCertificateValidation.md | 26 ++++ .../InsecureJdbcCertificateValidation.ql | 53 ++++++++ java/src/security/CWE-319/CleartextLdapUrl.md | 28 ++++ java/src/security/CWE-319/CleartextLdapUrl.ql | 47 +++++++ .../CWE-598/CredentialsInOutboundUrl.md | 27 ++++ .../CWE-598/CredentialsInOutboundUrl.ql | 64 +++++++++ .../CWE-639/InsecureDirectObjectReference.md | 34 +++++ .../CWE-639/InsecureDirectObjectReference.ql | 124 ++++++++++++++++++ .../CWE-639/UserControlledRecordRetrieval.md | 33 +++++ .../CWE-639/UserControlledRecordRetrieval.ql | 57 ++++++++ 10 files changed, 493 insertions(+) create mode 100644 java/src/security/CWE-295/InsecureJdbcCertificateValidation.md create mode 100644 java/src/security/CWE-295/InsecureJdbcCertificateValidation.ql create mode 100644 java/src/security/CWE-319/CleartextLdapUrl.md create mode 100644 java/src/security/CWE-319/CleartextLdapUrl.ql create mode 100644 java/src/security/CWE-598/CredentialsInOutboundUrl.md create mode 100644 java/src/security/CWE-598/CredentialsInOutboundUrl.ql create mode 100644 java/src/security/CWE-639/InsecureDirectObjectReference.md create mode 100644 java/src/security/CWE-639/InsecureDirectObjectReference.ql create mode 100644 java/src/security/CWE-639/UserControlledRecordRetrieval.md create mode 100644 java/src/security/CWE-639/UserControlledRecordRetrieval.ql diff --git a/java/src/security/CWE-295/InsecureJdbcCertificateValidation.md b/java/src/security/CWE-295/InsecureJdbcCertificateValidation.md new file mode 100644 index 00000000..14f2a989 --- /dev/null +++ b/java/src/security/CWE-295/InsecureJdbcCertificateValidation.md @@ -0,0 +1,26 @@ +# JDBC connection disables TLS certificate validation + +A JDBC URL containing `trustServerCertificate=true` makes the driver accept any server certificate. Even when the URL also sets `encrypt=true`, the channel is then encrypted but unauthenticated, so a man-in-the-middle can present its own certificate, impersonate the database, and read or modify all traffic, including credentials. + +## Recommendation +Remove `trustServerCertificate=true` and let the driver validate the server certificate against a trusted certificate authority. If the database uses a private CA, import that CA into the client trust store rather than disabling validation. + +## Example +The following example builds a SQL Server JDBC URL that disables certificate validation. + +```java +// BAD: trustServerCertificate=true accepts any certificate (MITM risk). +private static final String JDBC_URL = + "jdbc:sqlserver://db01.corp.example.com:1433;databaseName=App;" + + "encrypt=true;trustServerCertificate=true;loginTimeout=5"; + +try (Connection c = DriverManager.getConnection(JDBC_URL, user, password)) { + // ... +} +``` + +Use `encrypt=true;trustServerCertificate=false` and trust the server certificate through the client trust store. + +## References +* Microsoft: [Connecting with encryption (JDBC driver for SQL Server)](https://learn.microsoft.com/en-us/sql/connect/jdbc/connecting-with-ssl-encryption). +* Common Weakness Enumeration: [CWE-295](https://cwe.mitre.org/data/definitions/295.html). diff --git a/java/src/security/CWE-295/InsecureJdbcCertificateValidation.ql b/java/src/security/CWE-295/InsecureJdbcCertificateValidation.ql new file mode 100644 index 00000000..ab84495f --- /dev/null +++ b/java/src/security/CWE-295/InsecureJdbcCertificateValidation.ql @@ -0,0 +1,53 @@ +/** + * @name JDBC connection disables TLS certificate validation + * @description A JDBC URL containing `trustServerCertificate=true` makes the driver + * accept any server certificate. Even with `encrypt=true` the channel + * is then encrypted but unauthenticated, letting a man-in-the-middle + * impersonate the database. + * @kind problem + * @problem.severity warning + * @security-severity 7.5 + * @precision medium + * @id githubsecuritylab/java/jdbc-insecure-certificate + * @tags security + * external/cwe/cwe-295 + */ + +import java + +/** + * Holds if `e` evaluates to the constant string `v`, resolving a single field + * indirection and constant string concatenation. + */ +predicate constantStringValue(Expr e, string v) { + v = e.(CompileTimeConstantExpr).getStringValue() + or + exists(Variable var | + e = var.getAnAccess() and + v = var.getAnAssignedValue().(CompileTimeConstantExpr).getStringValue() + ) +} + +/** A call that opens or configures a JDBC connection from a URL argument. */ +class JdbcUrlSink extends MethodCall { + Expr urlArg; + + JdbcUrlSink() { + this.getMethod().hasName("getConnection") and + this.getMethod().getDeclaringType().hasQualifiedName("java.sql", "DriverManager") and + urlArg = this.getArgument(0) + or + this.getMethod().hasName(["setUrl", "setJdbcUrl"]) and + this.getQualifier().getType().(RefType).getName().matches(["%DataSource%", "%Config%"]) and + urlArg = this.getArgument(0) + } + + Expr getUrlArg() { result = urlArg } +} + +from JdbcUrlSink sink, string url +where + constantStringValue(sink.getUrlArg(), url) and + url.regexpMatch("(?i).*trustservercertificate\\s*=\\s*true.*") +select sink, + "JDBC connection uses 'trustServerCertificate=true', disabling certificate validation (MITM risk)." diff --git a/java/src/security/CWE-319/CleartextLdapUrl.md b/java/src/security/CWE-319/CleartextLdapUrl.md new file mode 100644 index 00000000..d853706d --- /dev/null +++ b/java/src/security/CWE-319/CleartextLdapUrl.md @@ -0,0 +1,28 @@ +# Cleartext LDAP URL + +Configuring an LDAP context source with an `ldap://` URL transmits bind credentials and directory data over an unencrypted channel. An attacker positioned on the network can intercept the service-account password and the contents of every directory query and response. + +## Recommendation +Use `ldaps://` (LDAP over TLS) or enable STARTTLS so that the connection to the directory server is encrypted and authenticated. Store the bind password outside source control (for example in a secret manager or environment variable). + +## Example +The following example configures a Spring `LdapContextSource` with a cleartext `ldap://` URL, so the bind credentials cross the network in the clear. + +```java +@Bean +public LdapContextSource ldapContextSource() { + LdapContextSource ctx = new LdapContextSource(); + // BAD: cleartext ldap:// transmits the bind password unencrypted. + ctx.setUrl("ldap://ldap.corp.example.com:389"); + ctx.setUserDn("cn=svc-app,ou=ServiceAccounts,dc=corp,dc=example,dc=com"); + ctx.setPassword(System.getenv("LDAP_PASSWORD")); + ctx.afterPropertiesSet(); + return ctx; +} +``` + +Use `ldaps://ldap.corp.example.com:636` instead. + +## References +* OWASP: [Transport Layer Protection Cheat Sheet](https://cheatsheetseries.owasp.org/cheatsheets/Transport_Layer_Protection_Cheat_Sheet.html). +* Common Weakness Enumeration: [CWE-319](https://cwe.mitre.org/data/definitions/319.html). diff --git a/java/src/security/CWE-319/CleartextLdapUrl.ql b/java/src/security/CWE-319/CleartextLdapUrl.ql new file mode 100644 index 00000000..a52c5807 --- /dev/null +++ b/java/src/security/CWE-319/CleartextLdapUrl.ql @@ -0,0 +1,47 @@ +/** + * @name Cleartext LDAP URL + * @description Configuring an LDAP context source with an `ldap://` URL transmits + * bind credentials and directory data over an unencrypted channel, + * allowing them to be intercepted. Use `ldaps://` or STARTTLS instead. + * @kind problem + * @problem.severity warning + * @security-severity 7.5 + * @precision medium + * @id githubsecuritylab/java/cleartext-ldap-url + * @tags security + * external/cwe/cwe-319 + */ + +import java + +/** + * Holds if `e` evaluates to the constant string `v`, resolving a single field + * indirection (e.g. a `private static final String` constant). + */ +predicate constantStringValue(Expr e, string v) { + v = e.(CompileTimeConstantExpr).getStringValue() + or + exists(Variable var | + e = var.getAnAccess() and + v = var.getAnAssignedValue().(CompileTimeConstantExpr).getStringValue() + ) +} + +/** A call that configures the URL of an LDAP/JNDI context source. */ +class LdapUrlSink extends MethodCall { + Expr urlArg; + + LdapUrlSink() { + this.getMethod().hasName(["setUrl", "setUrls", "setProviderUrl"]) and + this.getQualifier().getType().(RefType).getName().matches("%ContextSource%") and + urlArg = this.getArgument(0) + } + + Expr getUrlArg() { result = urlArg } +} + +from LdapUrlSink sink, string url +where + constantStringValue(sink.getUrlArg(), url) and + url.regexpMatch("(?i)ldap://.*") +select sink, "LDAP context configured with cleartext URL '" + url + "'; use ldaps:// or STARTTLS." diff --git a/java/src/security/CWE-598/CredentialsInOutboundUrl.md b/java/src/security/CWE-598/CredentialsInOutboundUrl.md new file mode 100644 index 00000000..4f718fff --- /dev/null +++ b/java/src/security/CWE-598/CredentialsInOutboundUrl.md @@ -0,0 +1,27 @@ +# Credentials transmitted in outbound request URL + +Embedding a password or other secret in the URL of an outbound HTTP request exposes the credential in server logs, proxy logs, and browser history. When the request is sent over `http://` the credential also crosses the network in cleartext, where it can be intercepted. + +## Recommendation +Send credentials in an `Authorization` header or a request body over a TLS-protected connection (`https://`), not as URL query parameters. Avoid logging full request URLs that contain secrets. + +## Example +The following example concatenates a password into the query string of a request issued with a Spring `RestTemplate`. + +```java +public String fetchReport(String reportName) { + // BAD: the password is placed in the request URL. + String url = props.getUrl() + + "/Render?report=" + reportName + + "&user=" + props.getUsername() + + "&password=" + props.getPassword(); + return restTemplate.getForObject(url, String.class); +} +``` + +Send the credentials in a header instead, for example via `HttpHeaders.setBasicAuth(...)` over `https://`. + +## References +* OWASP: [Information exposure through query strings in URL](https://owasp.org/www-community/vulnerabilities/Information_exposure_through_query_strings_in_url). +* Common Weakness Enumeration: [CWE-598](https://cwe.mitre.org/data/definitions/598.html). +* Common Weakness Enumeration: [CWE-319](https://cwe.mitre.org/data/definitions/319.html). diff --git a/java/src/security/CWE-598/CredentialsInOutboundUrl.ql b/java/src/security/CWE-598/CredentialsInOutboundUrl.ql new file mode 100644 index 00000000..f093f3da --- /dev/null +++ b/java/src/security/CWE-598/CredentialsInOutboundUrl.ql @@ -0,0 +1,64 @@ +/** + * @name Credentials transmitted in outbound request URL + * @description Embedding a password or secret in the URL of an outbound HTTP + * request exposes the credential in server logs, proxies and + * browser history, and over `http://` leaks it in cleartext on the + * wire. Send credentials in headers or a request body over TLS. + * @kind path-problem + * @problem.severity warning + * @security-severity 7.5 + * @precision medium + * @id githubsecuritylab/java/credentials-in-outbound-url + * @tags security + * external/cwe/cwe-598 + * external/cwe/cwe-319 + */ + +import java +import semmle.code.java.dataflow.TaintTracking +import CredentialsInUrlFlow::PathGraph + +/** A getter whose name suggests it returns a credential or secret. */ +class CredentialGetter extends MethodCall { + CredentialGetter() { + this.getMethod() + .getName() + .regexpMatch("(?i)get(pass(word|wd)?|secret|credential|apikey|api_?key|token).*") + } +} + +/** The URL argument of an outbound HTTP client request. */ +class OutboundUrlArg extends Expr { + OutboundUrlArg() { + exists(MethodCall ma | + ( + ma.getMethod() + .getDeclaringType() + .getASupertype*() + .hasQualifiedName("org.springframework.web.client", "RestOperations") + or + ma.getMethod() + .hasName([ + "getForObject", "getForEntity", "postForObject", "postForEntity", "put", "delete", + "exchange", "execute" + ]) and + ma.getQualifier().getType().(RefType).getName().matches("%RestTemplate%") + ) and + this = ma.getArgument(0) + ) + } +} + +module CredentialsInUrlConfig implements DataFlow::ConfigSig { + predicate isSource(DataFlow::Node source) { source.asExpr() instanceof CredentialGetter } + + predicate isSink(DataFlow::Node sink) { sink.asExpr() instanceof OutboundUrlArg } +} + +module CredentialsInUrlFlow = TaintTracking::Global; + +from CredentialsInUrlFlow::PathNode source, CredentialsInUrlFlow::PathNode sink +where CredentialsInUrlFlow::flowPath(source, sink) +select sink.getNode(), source, sink, + "Credential from $@ is concatenated into the URL of an outbound HTTP request.", source.getNode(), + "this getter" diff --git a/java/src/security/CWE-639/InsecureDirectObjectReference.md b/java/src/security/CWE-639/InsecureDirectObjectReference.md new file mode 100644 index 00000000..e7a80dae --- /dev/null +++ b/java/src/security/CWE-639/InsecureDirectObjectReference.md @@ -0,0 +1,34 @@ +# Insecure direct object reference + +A web action that operates on a resource identified by user input, without checking that the current user is authorized to act on that specific resource, allows an attacker to access or modify arbitrary objects by changing the identifier. + +This query is the Java analogue of the C# query `cs/web/insecure-direct-object-reference`. It flags a state-changing Spring controller action that takes an id-like parameter but performs no user or session check and carries no method-security annotation. + +## Recommendation +Add an authorization check that ties the request to the authenticated user before acting on the resource. This can be a method-security annotation such as `@PreAuthorize` or `@PostAuthorize`, an explicit ownership check against the current user or session, or a query scoped to the caller. + +## Example +The following example deletes a record identified by a path variable without verifying that the caller owns it. + +```java +@DeleteMapping("/{id}") +public void deleteStatement(@PathVariable long id) { + // BAD: no check that the current user may delete record `id`. + service.delete(id); +} +``` + +Restrict the action with an authorization check, for example: + +```java +@DeleteMapping("/{id}") +@PreAuthorize("@statementAccess.isOwner(#id, authentication.name)") +public void deleteStatement(@PathVariable long id) { + service.delete(id); +} +``` + +## References +* OWASP: [Insecure Direct Object Reference Prevention Cheat Sheet](https://cheatsheetseries.owasp.org/cheatsheets/Insecure_Direct_Object_Reference_Prevention_Cheat_Sheet.html). +* OWASP Top 10: [A01:2021 Broken Access Control](https://owasp.org/Top10/A01_2021-Broken_Access_Control/). +* Common Weakness Enumeration: [CWE-639](https://cwe.mitre.org/data/definitions/639.html). diff --git a/java/src/security/CWE-639/InsecureDirectObjectReference.ql b/java/src/security/CWE-639/InsecureDirectObjectReference.ql new file mode 100644 index 00000000..3566037f --- /dev/null +++ b/java/src/security/CWE-639/InsecureDirectObjectReference.ql @@ -0,0 +1,124 @@ +/** + * @name Insecure direct object reference + * @description A web action that operates on a resource identified by user input, + * without checking that the current user is authorized to act on that + * specific resource, allows an attacker to access or modify arbitrary + * objects. This is the Java analogue of the C# query + * `cs/web/insecure-direct-object-reference`: it flags a state-changing + * Spring controller action that takes an id-like parameter but performs + * no user/session check and carries no method-security annotation. + * @kind problem + * @problem.severity warning + * @security-severity 7.5 + * @precision medium + * @id githubsecuritylab/java/insecure-direct-object-reference + * @tags security + * external/cwe/cwe-639 + */ + +import java +import semmle.code.java.frameworks.spring.SpringController + +/** A Spring MVC controller action method (the analogue of the C# `ActionMethod`). */ +class ActionMethod extends SpringRequestMappingMethod { + /** Gets a string describing this action: method name, class name, route, or HTTP verb. */ + string getADescription() { + result = + [this.getName(), this.getDeclaringType().getName(), this.getAValue(), this.getMethodValue()] + } + + /** Holds if this action may represent a state-changing operation. */ + predicate isEdit() { + // Mapped with a state-changing HTTP verb. + this.getMethodValue() = ["POST", "PUT", "DELETE", "PATCH"] + or + this.getAnAnnotation() + .getType() + .hasName(["PostMapping", "PutMapping", "DeleteMapping", "PatchMapping"]) + or + // Or named/routed like a mutating action. + exists(string str | + str = this.getADescription().regexpReplaceAll("([a-z])([A-Z])", "$1_$2") and + str.regexpMatch("(?i).*(edit|delete|modify|change|update|save|remove).*") and + not str.regexpMatch("(?i).*(on_?change|changed).*") + ) + } + + /** Holds if this action appears to be intended for admin users. */ + predicate isAdmin() { + this.getADescription() + .regexpReplaceAll("([a-z])([A-Z])", "$1_$2") + .regexpMatch("(?i).*(admin|superuser).*") + } +} + +/** + * Holds if `m` takes a request-bound parameter that looks like a resource id, + * either by parameter name or by the explicit name in its binding annotation. + */ +predicate hasIdParameter(ActionMethod m) { + exists(SpringRequestMappingParameter p | p = m.getARequestParameter() | + p.getName().toLowerCase().matches(["%id", "%idx"]) + or + exists(Annotation a | a = p.getAnAnnotation() | + a.getType() + .hasQualifiedName("org.springframework.web.bind.annotation", + ["PathVariable", "RequestParam"]) and + a.getAStringArrayValue(["value", "name"]).toLowerCase().matches(["%id", "%idx"]) + ) + ) +} + +/** Holds if `c`'s name suggests it checks the current user / session / resource owner. */ +predicate authorizingCallable(Callable c) { + exists(string name | name = c.getName().toLowerCase() | + name.matches(["%user%", "%session%", "%principal%", "%owner%", "%current%"]) and + not name.matches("%get%by%") // exclude getXById / getXByUsername style lookups + ) +} + +private predicate calls(Callable c1, Callable c2) { c1.calls(c2) } + +private predicate callsPlus(Callable c1, Callable c2) = fastTC(calls/2)(c1, c2) + +/** Holds if `m` may, somewhere in its call graph, perform a check against the current user. */ +predicate checksUser(ActionMethod m) { + exists(Callable c | authorizingCallable(c) and callsPlus(m, c)) +} + +/** + * Holds if `m`, its declaring type, or an overridden method carries a Spring + * Security / JSR-250 method-security annotation that enforces authorization. + */ +predicate hasAuthorizationAnnotation(ActionMethod m) { + exists(Annotation a | + a.getType() + .hasQualifiedName([ + "org.springframework.security.access.prepost", + "org.springframework.security.access.annotation", "jakarta.annotation.security", + "javax.annotation.security" + ], ["PreAuthorize", "PostAuthorize", "Secured", "RolesAllowed", "DenyAll"]) + | + a = m.getAnAnnotation() or + a = m.getAnOverride().getAnAnnotation() or + a = m.getDeclaringType().getAnAnnotation() + ) +} + +/** + * Holds if `m` is a state-changing action keyed on a user-supplied id that + * neither checks the current user nor declares a method-security annotation. + */ +predicate hasInsecureDirectObjectReference(ActionMethod m) { + m.isEdit() and + not m.isAdmin() and + hasIdParameter(m) and + not checksUser(m) and + not hasAuthorizationAnnotation(m) and + exists(m.getBody()) +} + +from ActionMethod m +where hasInsecureDirectObjectReference(m) +select m, + "This action may be missing authorization checks for which users can access the resource of the provided id." diff --git a/java/src/security/CWE-639/UserControlledRecordRetrieval.md b/java/src/security/CWE-639/UserControlledRecordRetrieval.md new file mode 100644 index 00000000..90b3febb --- /dev/null +++ b/java/src/security/CWE-639/UserControlledRecordRetrieval.md @@ -0,0 +1,33 @@ +# Record retrieval using a user-controlled identifier + +A request-controlled identifier flows into a data-access lookup with no per-record ownership or authorization check, so an attacker can read another user's record simply by changing the identifier in the request. This is the read or disclosure sub-class of insecure direct object reference (IDOR). + +This query is a heuristic. Review each result to confirm that the returned record is not subject to an ownership check elsewhere. + +## Recommendation +Authorize the lookup against the authenticated caller: verify that the requested record belongs to (or is otherwise accessible by) the current user before returning it, or scope the query itself to the caller (for example `findByIdAndOwner(id, currentUser)`). + +## Example +The following example reads a statement by its path id and returns it without checking that it belongs to the caller. + +```java +@GetMapping("/{id}") +public AccountStatement getStatement(@PathVariable long id, + @RequestHeader("X-User") String currentUser) { + // BAD: currentUser is ignored; any id can be read. + return service.getStatement(id); +} + +// service -> repository +public AccountStatement getStatement(long id) { + return repository.findById(id).orElseThrow(); +} +``` + +Scope the lookup to the caller instead, for example `repository.findByIdAndOwnerUsername(id, currentUser)`. + +## References +* OWASP: [Insecure Direct Object Reference Prevention Cheat Sheet](https://cheatsheetseries.owasp.org/cheatsheets/Insecure_Direct_Object_Reference_Prevention_Cheat_Sheet.html). +* OWASP Top 10: [A01:2021 Broken Access Control](https://owasp.org/Top10/A01_2021-Broken_Access_Control/). +* Common Weakness Enumeration: [CWE-639](https://cwe.mitre.org/data/definitions/639.html). +* Common Weakness Enumeration: [CWE-863](https://cwe.mitre.org/data/definitions/863.html). diff --git a/java/src/security/CWE-639/UserControlledRecordRetrieval.ql b/java/src/security/CWE-639/UserControlledRecordRetrieval.ql new file mode 100644 index 00000000..4e383991 --- /dev/null +++ b/java/src/security/CWE-639/UserControlledRecordRetrieval.ql @@ -0,0 +1,57 @@ +/** + * @name Record retrieval using a user-controlled identifier + * @description A request-controlled identifier flows into a data-access lookup + * with no per-record ownership or authorization check, so an + * attacker may read another user's record by changing the + * identifier. This is the read/disclosure sub-class of insecure + * direct object reference (IDOR). It is a heuristic; review each + * result for an ownership check on the returned record. + * @kind path-problem + * @problem.severity warning + * @precision low + * @id githubsecuritylab/java/user-controlled-record-retrieval + * @tags security + * external/cwe/cwe-639 + * external/cwe/cwe-863 + */ + +import java +import semmle.code.java.dataflow.TaintTracking +import RecordRetrievalFlow::PathGraph + +/** A Spring MVC handler-method parameter bound from the request path. */ +class PathVariableParam extends Parameter { + PathVariableParam() { + this.getAnAnnotation() + .getType() + .hasQualifiedName("org.springframework.web.bind.annotation", "PathVariable") + } +} + +/** A call to a Spring Data style repository finder that looks a record up by id. */ +class RepositoryFindByIdCall extends MethodCall { + Expr idArg; + + RepositoryFindByIdCall() { + this.getMethod().hasName(["findById", "getById", "getOne", "findOne", "getReferenceById"]) and + idArg = this.getArgument(0) + } + + Expr getIdArg() { result = idArg } +} + +module RecordRetrievalConfig implements DataFlow::ConfigSig { + predicate isSource(DataFlow::Node source) { source.asParameter() instanceof PathVariableParam } + + predicate isSink(DataFlow::Node sink) { + exists(RepositoryFindByIdCall c | sink.asExpr() = c.getIdArg()) + } +} + +module RecordRetrievalFlow = TaintTracking::Global; + +from RecordRetrievalFlow::PathNode source, RecordRetrievalFlow::PathNode sink +where RecordRetrievalFlow::flowPath(source, sink) +select sink.getNode(), source, sink, + "Record is retrieved by request-controlled id from $@ with no per-record authorization check (possible read IDOR).", + source.getNode(), "this path variable"