
Generate deterministic semantic fingerprints for Go functions. Detect xz-style backdoors and verify semantic equivalence using Static Single Assignment (SSA) analysis and Scalar Evolution.
sfw is a static analyzer for Go source. It builds SSA with golang.org/x/tools/go/ssa, rewrites each function into a canonical IR, and fingerprints the function as the SHA-256 of that IR. On top of the fingerprint:
sfw diff compares two files function by function. It matches renamed functions by topology and scores each change by the capabilities it newly reaches and the guards, checks and security settings it removes.sfw deps diff compares the third-party code two trees of a module compile in: changed functions, exported functions that newly reach a capability, and changed files the analysis cannot see.sfw audit sends a diff's high-risk changes, their source and the commit message to an LLM, which reads the code (through tools, with a Claude model) and decides whether the message describes the change.sfw index and sfw scan match function topologies against a signature store (PebbleDB or JSON).The analysis never executes the code under test.
[!CAUTION] This tool is provided for defensive security research and authorized testing only. Do not use it to create, distribute, or deploy malware. Users are responsible for compliance with applicable laws and organizational policies. The author assumes no liability for misuse.
go install github.com/BlackVectorOps/semantic_firewall/v7/cmd/[email protected]
go.mod declares go 1.27.2. sfw version prints the build:
Semantic Firewall CLI
Build: v7.1.1 (Pebble/Gob+PackedIdx)
Flags must come before positional arguments. The CLI parses each command with Go's flag package, which stops at the first non-flag argument; flags placed after a file name are silently ignored.
| Command | Flags (default) | Arguments | Sandboxed |
|---|---|---|---|
check | --strict (false), --scan (false), --db (""), --no-sandbox (false) | <file.go|dir> | yes |
diff | --no-sandbox (false), --batch ("") | <old.go> <new.go>, or none with --batch <pairs-file> | yes |
deps diff | --no-sandbox (false) | <old-module-dir> <new-module-dir> | yes |
audit | --model (claude-opus-5-5), --api-key (""), --api-base (""), --no-sandbox (false) | <old.go> <new.go> "<commit message>" | yes (the diff step) |
index | --name (required), --severity (HIGH), --category (malware), --db ("") | <file.go|dir> | no |
scan | --db (""), --threshold (0.75), --exact (false), --deps (false), --deps-depth (direct), --no-sandbox (false) | <file.go|dir> | yes |
migrate | --from (required), --to (required) | none | no |
stats | --db ("") | none | no |
version | none | none | no |
sfw <command> -h lists a command's flags. --severity is stored as given, without validation. Any --deps-depth value other than transitive is treated as direct.
Exit codes:
| Command | Exit 0 | Exit 1 |
|---|---|---|
check | analysis ran, even when a file failed to load (reported in that file's error field) | --strict and any file errored; fatal errors |
diff | diff produced, regardless of risk | fatal errors |
diff --batch | every pair diffed, regardless of risk | any pair failed (after printing all results); fatal errors |
deps diff | diff produced, regardless of risk | either tree failed to load; fatal errors |
scan | scan ran, regardless of alerts | fatal errors (e.g. no database) |
audit | verdict MATCH | verdict LIE, SUSPICIOUS or ERROR |
diff treats a missing input file as an empty file.
Prints a JSON array with one entry per file. A directory is walked recursively, skipping vendor/, hidden directories and _test.go files. Function names are package-qualified: a file loaded on its own belongs to command-line-arguments.
sfw check --no-sandbox new.go
[
{
"file": "new.go",
"functions": [
{ "function": "command-line-arguments.Handle", "fingerprint": "4acdccda3895dd4cec3af2d55ad43489f91860cd226f82091eaf0a2429849e2f", "file": "/work/new.go", "line": 5 },
{ "function": "command-line-arguments.Handle$1", "fingerprint": "1777c5c3ae9a69e395f6bbe8f35a1e447d25534afec7bb907eacf873e7d4917c", "file": "/work/new.go", "line": 7 },
{ "function": "command-line-arguments.Sum", "fingerprint": "7cc1efa6ef6ca643863de5dcb0a68f7c80cd6dae8c530697706f40ce737030ae", "file": "/work/new.go", "line": 10 },
{ "function": "command-line-arguments.init", "fingerprint": "825a2d206f4eb4ea4413c050e4ca9e4b8e439c2067219829a8fef891031a1850", "file": "" }
]
}
]
The output above was captured from a real run, with the absolute path shortened to /work and each object put on one line. new.go is the file in the sfw diff example.
--strict: an unhandled SSA instruction makes the canonicalizer fail instead of emitting UnhandledInstr<T>, and any per-file error makes the command exit 1.--scan: each function's topology is also matched against the signature database (--db, or the default resolution) at a fixed threshold of 0.75. The matches go in a scan_results field. A missing database is a fatal error.Input:
// old.go
package p
import "fmt"
func Handle(cmd string) { fmt.Println(cmd) }
func Add(a, b int) int { return a + b }
// new.go
package p
import "net"
func Handle(cmd string) {
net.Dial("tcp", cmd)
go func() {}()
}
func Sum(a, b int) int { return a + b }
sfw diff --no-sandbox old.go new.go prints the following. This is the full output, except that the init entry is abbreviated to its status and delta.