Gleam projects
SAFE analyses Gleam projects that target the Erlang VM. Gleam compiles to
Erlang, so SAFE runs its full set of BEAM security checks over your Gleam codebase and reports
every finding against the .gleam file it came from.
Gleam support is new in SAFE 1.6.0 and is free while it is in beta. SAFE needs a gleam.toml that
names the project and applications the Gleam compiler built. A build tree that mixes Gleam with
Erlang or Elixir applications needs a license of its own.
Analysing a project
You need the SAFE CLI on your PATH (Download) and a project built for the
Erlang target — SAFE reads compiled BEAM code, so the JavaScript target has nothing for it to
read.
gleam build # produces build/dev/erlang, the directory SAFE reads
safe analyse # run from the directory holding gleam.toml
safe sca # scans manifest.toml
✓ No SAFE license required — analysing this Gleam project under the free beta.
✓ SAFE license is valid until 2027-03-31.
Info: 6 months, 24 days left in the Gleam free beta (ends 2027-03-31).
SAFE reads build/dev/erlang, not your .gleam sources. Without a fresh gleam build it analyses
the previous build — so put the build immediately before safe analyse in CI:
- run: |
gleam build
safe analyse
safe sca
Full example: Setting up in CI/CD.
What SAFE analyses
gleam build compiles src/, dev/ and test/ into a single ebin, and no build profile leaves
the last two out. Only src/ is production code, so that is what SAFE analyses.
To pull an excluded module or a dependency back in, name it in additional_includes
(Configuration). Scope is decided per application from the
build output, not from the project type, so a Gleam package inside an Erlang or Elixir build tree
gets the same treatment while the applications around it are handled as usual. Lines of code, which
the fingerprint and the license check use, are counted on the sources you wrote, not on the
generated Erlang.
The checks themselves are the same ones SAFE applies to Erlang and Elixir: atom exhaustion, unsafe deserialisation, command and SQL injection, dynamic code evaluation, weak cryptography, and the rest.
Findings anchor to the function, not the call
Gleam's compiler records one source position per function, not per expression, so SAFE reports the function declaration line and attaches the whole function body as the snippet:
38 pub fn with_name(barnacle: Barnacle(error), name: String) -> Barnacle(error) {
39 Barnacle(..barnacle, name: Some(name |> atom.create))
40 }
The finding reports line 38 with a snippet covering 38–40, though atom.create on line 39 is
the actual problem. vulnerable_fun names the Erlang function the compiled code calls, so
atom.create/1 is reported as erlang:binary_to_atom/1. Both facts matter when suppressing a
finding: the ignore comment goes above the function
declaration and names the Erlang function. Full field breakdown:
Vulnerability Report.
Reading a snippet to find the call, and translating Gleam names into Erlang ones before you can suppress a finding, is more work than it should be. It reflects what the compiled output currently tells us, not where we intend to leave it: Gleam support ships as a beta, and upcoming releases will bring both analysis and UX improvements here. Feedback on what gets in your way is welcome at safe@erlang-solutions.com.
When you need a config file
Running with no config covers the common case. Generate a .safe/config.json with
safe setup gleam when you want a report file, SARIF or
Checkmarx output; additional_includes; to run SAFE from
another directory via --config-path; or to keep analysing after the beta ends.
Commit .safe/config.json; do not commit fingerprint.json. Every flag is on the
CLI page, the generated config is
documented here, and Gleam failure modes are in
Troubleshooting.