We ran our release checks on a Flutter app and a Rust service. Most of the alarms were ours.
Our benchmark is a set of open-source projects we scan every time the rules change. Well-run projects are in it to catch false alarms: a person reads every high-severity finding on them, and a rule that is wrong gets fixed or stops blocking releases. Until this week the set had no Flutter app and no Rust web service, so we added two: LocalSend, a Flutter app with a Rust core for sending files between devices on the same network, and Lemmy, a Rust forum server on Actix and Postgres.
The first scan flagged both of them hard. After reading every finding, most turned out to be our mistakes, so this post is mainly about what we got wrong, because the same patterns are in plenty of other codebases.
"delete" in an error message is not SQL
The Rust SQL injection check looked for format! strings containing words like SELECT, UPDATE or DELETE. That caught these:
format!("Invalid identity file: {} (delete it to generate a new identity)", path.display())
format!("{}internal/delete?alias={}", pictrs_config.url, alias)
format!("Failed to update {} hot_ranks: {}", table_name, e)The check now asks for the shape of a query (SELECT … FROM, INSERT INTO, UPDATE … SET, DELETE FROM) plus an interpolation, and it reads raw strings like r#"..."#, which the old version skipped entirely. That second part matters: Lemmy does build some SQL with format!, in raw strings the old check never looked at.
sql_query(format!(
r#"WITH batch AS (SELECT a.id FROM {table_name} a WHERE ... AND ({where_clause}) ...)"#
))Here the table name and clause come from Lemmy's own code, so nothing a user sends reaches the string today. We still report it, because the function signature does not stop a future caller from passing user input, and a reviewer should know the query is assembled by hand.
Accepting a self-signed certificate is fine if you pin it yourself
LocalSend's Rust client turns off normal certificate checks:
reqwest::Client::builder()
.use_rustls_tls()
.danger_accept_invalid_certs(true)
.tls_info(true)On its own that line means anyone on the network can impersonate the other side. LocalSend devices use self-signed certificates, so a public CA could not vouch for them anyway. Instead the client reads the peer certificate (tls_info(true)) and checks its fingerprint against the one it expects. That is certificate pinning done by hand, and it is the right design for devices on a LAN. The check now skips clients that read and verify the peer certificate in the same file.
The Flutter side had a similar case: a Dart badCertificateCallback that returns true, in a helper that only ever connects to 127.0.0.1, meaning the app talking to itself. There is no network path to intercept, so loopback-only clients are no longer reported.
A password called "password" in CI is not a leak
Lemmy's CI runs Postgres as a service container:
LEMMY_DATABASE_URL: postgres://lemmy:password@database:5432/lemmyWe already skipped localhost URLs, but this host is the CI service name, and the dev script builds the host from a shell variable. Neither is a credential anyone could use. Literal placeholder passwords (password, changeme,example and similar) and hosts taken from a variable are now skipped. A real-looking password on a real hostname is still reported.
A PEM header is not a private key
LocalSend has tests that check the format of a generated key:
assert!(key_pem.starts_with("-----BEGIN PRIVATE KEY-----"));The secret check matched the header and reported a committed private key. It now needs key material after the header before it calls something a key.
USER $UNAME is a non-root user
Lemmy's Dockerfile creates a user from build arguments and switches to it:
RUN groupadd -g ${GID} -o ${UNAME} && \
useradd -m -u ${UID} -g ${GID} -o -s /bin/bash ${UNAME}
USER $UNAMETwo of our container checks only accepted a literal name after USER, so they reported the image as running as root. Both now accept USER $NAME and USER ${NAME}.
What was worth a look
LocalSend's Android manifest sets usesCleartextTraffic="true". For an app that talks to other devices on the local network and has a plain HTTP mode, that is a deliberate trade-off, but it applies to every request the app makes, and a network security config could limit it to the cases that need it. Its server Dockerfile also has no USER line, so that container does run as root.
Neither project had the kind of problem the first scan suggested. Both are careful codebases, and the rule fixes they led to apply to every Rust and Dart project we scan.
How we decide what counts
A finding on a benchmark project is either confirmed by reading the code, or the rule is changed. We do not count a project as clean by hiding findings on it. Every rule change ships with a test that keeps the real case reported and the false one quiet, and the scan of the vulnerable apps in the benchmark has to find the same flaws afterwards. The changes from this round are in the changelog.
Check your own repo
Public repositories scan free. Paste a GitHub URL on the dashboard and every finding comes with the file, the line and a fix.
Scan a repository free