Skip to content

Commit 182b7b1

Browse files
hyperpolymathclaude
andcommitted
fix(governance)!: banned_language ban is total — no exceptions
Org policy 2026-05-18: the banned-language ban (incl. Python) is now absolute, with no SaltStack-style carve-outs. - ScannerSuppression.suppressed?/4 gains a hard short-circuit clause: cicd_rules/banned_language_file is NEVER suppressed — not by .hypatia-ignore, built-in default exemptions, universal excludes, or training-corpus paths. (Added the canonical bodiless default head so the guard clause coexists with the optional opts arg.) - cicd_rules :python_detected loses its `exception: "SaltStack"` field. - 4 new tests pin the invariant (unsuppressible via every vector; unrelated rules on the same path remain suppressible). Compiles clean; 24/24 scanner_suppression tests pass. Companion to #279 (which removed hypatia's own Python + revoked its exemption records). Note: the #272 `unwrap_without_check` false positive is NOT a rule defect — the source regex /\.unwrap\(\)/ does not match `unwrap_or` (verified); it was a stale compiled escript, already addressed by #278's escript rebuild. No rule change there. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
1 parent 65f0912 commit 182b7b1

3 files changed

Lines changed: 63 additions & 3 deletions

File tree

lib/hypatia/scanner_suppression.ex

Lines changed: 14 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -78,8 +78,21 @@ defmodule Hypatia.ScannerSuppression do
7878
7979
`file` is matched as a normalised relative path. Absolute paths are
8080
normalised by stripping the leading repo_path when supplied.
81+
82+
## Unsuppressible findings
83+
84+
The banned-language ban (`cicd_rules/banned_language_file`) is **total
85+
with no exceptions** as of org policy 2026-05-18 — including the former
86+
SaltStack carve-out. No `.hypatia-ignore` entry, built-in default
87+
exemption, universal exclude, or training-corpus path may suppress it;
88+
this clause short-circuits every suppression vector for that rule so the
89+
gate cannot be silenced repo-side.
8190
"""
82-
def suppressed?(file, rule_module, rule_type, opts \\ []) do
91+
def suppressed?(file, rule_module, rule_type, opts \\ [])
92+
93+
def suppressed?(_file, "cicd_rules", "banned_language_file", _opts), do: false
94+
95+
def suppressed?(file, rule_module, rule_type, opts) do
8396
repo_path = Keyword.get(opts, :repo_path, nil)
8497
rel = relative(file, repo_path)
8598

lib/rules/cicd_rules.ex

Lines changed: 4 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -65,8 +65,10 @@ defmodule Hypatia.Rules.CicdRules do
6565
%{id: :typescript_detected, glob: "*.ts", reason: "TypeScript banned -- use ReScript"},
6666
%{id: :nodejs_detected, glob: "package-lock.json", reason: "Node.js banned -- use Deno"},
6767
%{id: :golang_detected, glob: "*.go", reason: "Go banned -- use Rust"},
68-
%{id: :python_detected, glob: "*.py", reason: "Python banned -- use Julia/Rust",
69-
exception: "SaltStack"},
68+
# Python ban is total — no exceptions (the former SaltStack carve-out
69+
# was removed by org policy 2026-05-18). ScannerSuppression also
70+
# hard-refuses to suppress cicd_rules/banned_language_file.
71+
%{id: :python_detected, glob: "*.py", reason: "Python banned -- use Julia/Rust"},
7072
%{id: :makefile_detected, glob: "Makefile", reason: "Makefiles banned -- use justfile"},
7173
%{id: :unpinned_action,
7274
pattern: ~r/uses:\s+[a-zA-Z0-9_.-]+\/[a-zA-Z0-9_.\/-]+@(v[0-9][a-zA-Z0-9.-]*|main|master)/,

test/scanner_suppression_test.exs

Lines changed: 45 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -68,6 +68,51 @@ defmodule Hypatia.ScannerSuppressionTest do
6868
end
6969
end
7070

71+
describe "suppressed?/4 — banned_language_file is total, no exceptions" do
72+
test "never suppressed even on a universal-exclude path" do
73+
refute ScannerSuppression.suppressed?(
74+
"node_modules/tool/helper.py",
75+
"cicd_rules",
76+
"banned_language_file"
77+
)
78+
end
79+
80+
test "never suppressed even for a training-corpus path" do
81+
refute ScannerSuppression.suppressed?(
82+
".audittraining/security-errors/sample.py",
83+
"cicd_rules",
84+
"banned_language_file"
85+
)
86+
end
87+
88+
test "never suppressed even with a matching .hypatia-ignore entry" do
89+
tmp = Path.join(System.tmp_dir!(), "hyp-ban-#{System.unique_integer([:positive])}")
90+
File.mkdir_p!(Path.join(tmp, "scripts"))
91+
92+
File.write!(
93+
Path.join(tmp, ".hypatia-ignore"),
94+
"cicd_rules/banned_language_file:scripts/legacy.py\n"
95+
)
96+
97+
refute ScannerSuppression.suppressed?(
98+
"scripts/legacy.py",
99+
"cicd_rules",
100+
"banned_language_file",
101+
repo_path: tmp
102+
)
103+
104+
File.rm_rf!(tmp)
105+
end
106+
107+
test "an unrelated rule on the same path is still suppressible" do
108+
assert ScannerSuppression.suppressed?(
109+
"node_modules/foo/index.js",
110+
"security_errors",
111+
"secret_detected"
112+
)
113+
end
114+
end
115+
71116
describe "context_safe_line?/2 — line-level exemptions for secret_detected" do
72117
test "GitHub Actions secrets reference is not a leak" do
73118
assert ScannerSuppression.context_safe_line?(

0 commit comments

Comments
 (0)