yak(sobelow-in-the-lens-pass-vr40): sobelow in the Lens pass

This commit is contained in:
Willem van den Ende
2026-10-28 23:22:07 +00:00
parent e292b4ebfb
commit 09da15c1df
8 changed files with 204 additions and 8 deletions
+30
View File
@@ -0,0 +1,30 @@
# Sobelow per-finding skips. Enabled by `--skip` in `app/Makefile`.
#
# Rule: every entry is preceded by a rationale comment saying what the check
# cannot see and why the protection is real (or why the finding is a false
# positive). An unexplained skip is a defect; `scripts/check-lens-sensors.sh`
# enforces this. Fingerprints are sobelow's own; a code change that moves a
# skip's line invalidates it and the sensor fails loudly rather than staying
# quiet.
#
# Baseline 2026-10-28, sobelow 0.17.0: 3 findings (2 High, 1 Low), all skipped.
# Config.CSP - High - lib/firehose_web/router.ex:13 (pipeline :browser)
# No CSP exists. put_secure_browser_headers does not set one and the root
# layout has an inline theme script with no nonce, so choosing a policy is a
# design decision rather than a mechanical fix.
# Follow-up yak: app: add a Content-Security-Policy (sobelow Config.CSP)
Config.CSP: Missing Content-Security-Policy,lib/firehose_web/router.ex:13,486EC13
# Config.HTTPS - High - config/prod.exs
# No force_ssl: production TLS is terminated by the Dokku proxy. Whether the
# endpoint should also force SSL/HSTS (and trust X-Forwarded-Proto) is a
# deployment decision, not a mechanical fix.
# Follow-up yak: app: enforce HTTPS in production (sobelow Config.HTTPS)
Config.HTTPS: HTTPS Not Enabled,config/prod.exs:0,2B5C077
# Traversal.FileModule - Low - lib/firehose_web/live/microprints_live.ex:331
# False positive: the client path is only read after Enum.find matches it
# against the server-scanned microprint list, and source_dir comes from
# @source_dirs. Both Path.join inputs are server-computed, never client text.
Traversal.FileModule: Directory Traversal in `File.read`,lib/firehose_web/live/microprints_live.ex:331,7ED2FE5
+16 -2
View File
@@ -3,7 +3,7 @@
MISE_BIN ?= mise
MISE_EXEC = $(MISE_BIN) exec --
.PHONY: check precommit deps assets compile test test.coverage coverage crap format format.check credo verify
.PHONY: check precommit deps assets compile test test.coverage sobelow coverage crap format format.check credo verify
# Run all static analysis checks (no database required). `format.check` only
# reports: a gate that rewrites the files it judges can never fail. Fix
@@ -159,6 +159,10 @@ credo:
# VERIFY_SENSORS — that variable is the single source of truth for what this
# target reports as wired.
# - sobelow (yak: sobelow in the Lens pass)
# WIRED below (sobelow, ~1s). Fails loudly on findings and
# when the task is missing — no fallback of any kind.
# Per-finding skips exist only in .sobelow-skips, each with a
# written rationale (pinned by scripts/check-lens-sensors.sh).
# - coverage (yak: coverage floors + framework-glue exclusions policy)
# WIRED below (test.coverage + coverage).
# - crap (yak: CRAP scores with ex_crap (both apps))
@@ -171,7 +175,17 @@ credo:
#
# The suite runs FIRST, because it is what produces the coverage export the
# sensors read; sensors still run cheapest-first among themselves.
VERIFY_SENSORS := coverage crap
VERIFY_SENSORS := sobelow coverage crap
# Sobelow security scan. `--exit` is load-bearing: without it sobelow prints
# findings and exits 0 (verified 2026-10-28, sobelow 0.17.0: 3 findings -> 0,
# `--exit` -> 1; it defaults to the `low` confidence threshold). `--skip`
# applies only the individually rationalised entries in .sobelow-skips.
# There is deliberately no guard or fallback: if the dep is absent, `mix
# sobelow` is a hard Mix error ("The task could not be found") and the
# verify loop's `|| exit 1` fails the pass.
sobelow:
$(MISE_EXEC) mix sobelow --skip --exit
verify:
@echo "==> verify(app): suite (coverage exported), then sensors [$(VERIFY_SENSORS)]"
+2 -1
View File
@@ -82,7 +82,8 @@ defmodule Firehose.MixProject do
git: "https://gitea.apps.sustainabledelivery.com/QWAN/microprints-phoenix.git",
branch: "main"},
{:credo, "~> 1.7", only: [:dev, :test], runtime: false},
{:ex_crap, "~> 0.1.1", only: [:dev, :test], runtime: false}
{:ex_crap, "~> 0.1.1", only: [:dev, :test], runtime: false},
{:sobelow, "~> 0.16", only: [:dev, :test], runtime: false}
]
end
+1
View File
@@ -53,6 +53,7 @@
"req": {:hex, :req, "0.7.5", "0c3293cbf7717aba20b1b1b3ea13fe8d3fdf6f1557490f390c3f1ef6dd9db955", [:mix], [{:brotli, "~> 0.3.1", [hex: :brotli, repo: "hexpm", optional: true]}, {:finch, "~> 0.21", [hex: :finch, repo: "hexpm", optional: false]}, {:jason, "~> 1.0", [hex: :jason, repo: "hexpm", optional: false]}, {:mime, "~> 2.0.6 or ~> 2.1", [hex: :mime, repo: "hexpm", optional: false]}, {:nimble_csv, "~> 1.0", [hex: :nimble_csv, repo: "hexpm", optional: true]}, {:plug, "~> 1.0", [hex: :plug, repo: "hexpm", optional: true]}], "hexpm", "576853c4bf403faa3c1fa5d093e0759227083b78f8a6864c67d62c0a9c45514d"},
"rustler": {:hex, :rustler, "0.38.0", "7a8906998ff0d28e3021c0a73264abcda719bda344b2e58307c6805b0f87c9b4", [:mix], [{:jason, "~> 1.0", [hex: :jason, repo: "hexpm", optional: false]}], "hexpm", "704c03c1bf66be12b031c5a389347b91c81c5cb819a24b068b0de36fe4a5652a"},
"rustler_precompiled": {:hex, :rustler_precompiled, "0.10.0", "8f07ff0865cb09a6a0f75e11701e9a46e331e05f737da4e5b01374dcd25b68c0", [:mix], [{:rustler, "~> 0.28", [hex: :rustler, repo: "hexpm", optional: true]}], "hexpm", "c68c9dc732c12acfdf4a7fa06f4e6f48a641179e6be49a474c8a60809929c430"},
"sobelow": {:hex, :sobelow, "0.17.0", "57436898f5b5eac05244ddb301927b1e6521d7de5df3023929d0fe2818d11055", [:mix], [{:jason, "~> 1.0", [hex: :jason, repo: "hexpm", optional: false]}], "hexpm", "02914e7cdc5bd9bdcb1a2be7f08d793fd765f8c238c924f29857d3996d435928"},
"swoosh": {:hex, :swoosh, "1.29.0", "b938791267d8a55415344018aa0421d214f9bc36c93cbe27305d3284a853785c", [:mix], [{:bandit, ">= 1.0.0", [hex: :bandit, repo: "hexpm", optional: true]}, {:cowboy, "~> 1.1 or ~> 2.4", [hex: :cowboy, repo: "hexpm", optional: true]}, {:ex_aws, "~> 2.1", [hex: :ex_aws, repo: "hexpm", optional: true]}, {:finch, "~> 0.6", [hex: :finch, repo: "hexpm", optional: true]}, {:gen_smtp, "~> 0.13 or ~> 1.0", [hex: :gen_smtp, repo: "hexpm", optional: true]}, {:hackney, ">= 1.9.0 and < 5.0.0", [hex: :hackney, repo: "hexpm", optional: true]}, {:idna, ">= 6.0.0 and < 8.0.0", [hex: :idna, repo: "hexpm", optional: false]}, {:jason, "~> 1.0", [hex: :jason, repo: "hexpm", optional: false]}, {:mail, "~> 0.2", [hex: :mail, repo: "hexpm", optional: true]}, {:mime, "~> 1.1 or ~> 2.0", [hex: :mime, repo: "hexpm", optional: false]}, {:mua, "~> 0.2.3", [hex: :mua, repo: "hexpm", optional: true]}, {:multipart, "~> 0.4", [hex: :multipart, repo: "hexpm", optional: true]}, {:plug, "~> 1.9", [hex: :plug, repo: "hexpm", optional: true]}, {:plug_cowboy, ">= 1.0.0", [hex: :plug_cowboy, repo: "hexpm", optional: true]}, {:req, "~> 0.5.10 or ~> 0.6 or ~> 1.0", [hex: :req, repo: "hexpm", optional: true]}, {:telemetry, "~> 0.4.2 or ~> 1.0", [hex: :telemetry, repo: "hexpm", optional: false]}], "hexpm", "d23ea419c6c67604628c0e93a8d5c6d13bcc442137870671e1b05325b2956a13"},
"tailwind": {:hex, :tailwind, "0.5.1", "35435b13158c90d37da11e1cfc808755fca1d7b6c5ab87b1b19c5de87e2f0a10", [:mix], [], "hexpm", "c4e26302a59fec72abc5610ecb6ad2116d9aa31f31aab2d4b8eb6e95d25a689c"},
"telemetry": {:hex, :telemetry, "1.4.2", "a0cb522801dffb1c49fe6e30561badffc7b6d0e180db1300df759faa22062855", [:rebar3], [], "hexpm", "928f6495066506077862c0d1646609eed891a4326bee3126ba54b60af61febb1"},