8Validate the linter's ``no_hardcoded_secrets`` rule (§6b Secret Hygiene).
12- Confirm that hardcoded secrets in direct assignments are caught (Pattern A).
13- Confirm that hardcoded secrets in annotated assignments are caught (Pattern A).
14- Confirm that hardcoded secrets in dict literals are caught (Pattern B).
15- Confirm that hardcoded secrets in function defaults are caught (Pattern C).
16- Confirm that safe patterns (os.getenv, os.environ, function calls,
17 subscripts) pass without violations.
18- Confirm that non-secret variable names with string literals pass.
19- Confirm profile behaviour (strict/compact: blocking; proto: not checked).
30Inputs: inline source strings
31Outputs: pass/fail assertions
42 """String literal assigned to a secret-like variable name is caught."""
43 source =
'password = "hunter2"\n'
44 ok, msg = oct_lint.check_no_hardcoded_secrets(source)
46 assert "password" in msg
47 assert "string literal" in msg
51 """Multiple secret assignments produce multiple violation messages."""
53 'API_KEY = "sk-abc123"\n'
56 ok, msg = oct_lint.check_no_hardcoded_secrets(source)
58 assert "API_KEY" in msg
or "api_key" in msg.lower()
63 """Annotated assignment (name: str = "literal") is caught."""
64 source =
'password: str = "hunter2"\n'
65 ok, msg = oct_lint.check_no_hardcoded_secrets(source)
67 assert "password" in msg
71 """Class-level secret attribute is caught (AST walker visits class bodies)."""
74 ' API_KEY = "sk-abc"\n'
76 ok, msg = oct_lint.check_no_hardcoded_secrets(source)
78 assert "API_KEY" in msg
or "api_key" in msg.lower()
86 """Dict with secret-like key mapped to a string literal is caught."""
87 source =
'config = {"password": "secret"}\n'
88 ok, msg = oct_lint.check_no_hardcoded_secrets(source)
90 assert "password" in msg
94 """Dict with multiple secret keys produces multiple violations."""
95 source =
'settings = {"api_key": "sk-123", "db_url": "postgres://x"}\n'
96 ok, msg = oct_lint.check_no_hardcoded_secrets(source)
98 assert "api_key" in msg
99 assert "db_url" in msg
103 """Dict with non-secret key and string value passes."""
104 source =
'config = {"max_retries": "3"}\n'
105 ok, msg = oct_lint.check_no_hardcoded_secrets(source)
110 """Dict with secret key mapped to a function call passes."""
113 'config = {"password": os.getenv("DB_PASS")}\n'
115 ok, msg = oct_lint.check_no_hardcoded_secrets(source)
124 """Function with secret-like parameter defaulting to string literal is caught."""
125 source =
'def connect(password="default"):\n pass\n'
126 ok, msg = oct_lint.check_no_hardcoded_secrets(source)
128 assert "password" in msg
132 """Keyword-only parameter with secret default is caught."""
133 source =
'def connect(*, token="default"):\n pass\n'
134 ok, msg = oct_lint.check_no_hardcoded_secrets(source)
136 assert "token" in msg
140 """Function with non-secret parameter defaulting to string passes."""
141 source =
'def connect(host="localhost"):\n pass\n'
142 ok, msg = oct_lint.check_no_hardcoded_secrets(source)
151 """os.getenv() is a function call, not a literal — passes."""
154 'password = os.getenv("PASSWORD")\n'
156 ok, msg = oct_lint.check_no_hardcoded_secrets(source)
161 """os.environ["KEY"] is a subscript, not a literal — passes."""
164 'API_KEY = os.environ["API_KEY"]\n'
166 ok, msg = oct_lint.check_no_hardcoded_secrets(source)
171 """Assigning a function call result to a secret name passes."""
172 source =
'password = get_password()\n'
173 ok, msg = oct_lint.check_no_hardcoded_secrets(source)
178 """Subscript on a non-environ object passes (known limitation)."""
180 'config = load_config("settings.json")\n'
181 'API_KEY = config["api_key"]\n'
183 ok, msg = oct_lint.check_no_hardcoded_secrets(source)
188 """Variable with non-secret name assigned string literal passes."""
189 source =
'max_retries = "3"\n'
190 ok, msg = oct_lint.check_no_hardcoded_secrets(source)
195 """f-string assigned to secret-named variable is flagged (OI-408)."""
196 source =
'password = f"prefix-{var}"\n'
197 ok, msg = oct_lint.check_no_hardcoded_secrets(source)
199 assert "f-string" in msg
203 """String concatenation assigned to secret-named variable is flagged (OI-408)."""
204 source =
'api_key = "sk-" + "abc123"\n'
205 ok, msg = oct_lint.check_no_hardcoded_secrets(source)
207 assert "concatenation" in msg
211 """Concatenation where one side is a literal is flagged (OI-408)."""
212 source =
'token = prefix + "static_suffix"\n'
213 ok, msg = oct_lint.check_no_hardcoded_secrets(source)
215 assert "concatenation" in msg
219 """f-string assigned to a non-secret variable name is fine (OI-408)."""
220 source =
'greeting = f"Hello {name}"\n'
221 ok, msg = oct_lint.check_no_hardcoded_secrets(source)
226 """Empty file passes."""
227 ok, msg = oct_lint.check_no_hardcoded_secrets(
"")
232 """Unparseable source passes (same behaviour as other AST-based checks)."""
233 ok, msg = oct_lint.check_no_hardcoded_secrets(
"def (\n")
242 """Every name in _SECRET_NAME_PATTERNS is caught when assigned a literal."""
243 for name
in oct_lint._SECRET_NAME_PATTERNS:
244 source = f
'{name} = "test_value"\n'
245 ok, msg = oct_lint.check_no_hardcoded_secrets(source)
246 assert not ok, f
"Expected violation for '{name}' but check passed"
250 """Secret name matching is case-insensitive."""
251 source =
'PASSWORD = "hunter2"\n'
252 ok, msg = oct_lint.check_no_hardcoded_secrets(source)
254 assert "PASSWORD" in msg
262 """Compound variable names containing secret patterns are caught (OI-402)."""
263 source =
'db_password = "hunter2"\n'
264 ok, msg = oct_lint.check_no_hardcoded_secrets(source)
266 assert "db_password" in msg
270 """Prefixed secret name like stripe_api_key is caught (OI-402)."""
271 source =
'stripe_api_key = "sk_live_abc123"\n'
272 ok, msg = oct_lint.check_no_hardcoded_secrets(source)
277 """Suffixed secret name like refresh_token_value is caught (OI-402)."""
278 source =
'refresh_token_value = "tok-xyz"\n'
279 ok, msg = oct_lint.check_no_hardcoded_secrets(source)
284 """Compound name that does not contain a secret pattern passes."""
285 source =
'max_retry_count = "3"\n'
286 ok, msg = oct_lint.check_no_hardcoded_secrets(source)
295 """parse_inline_waivers sets reason=None when no reason is given (OI-404)."""
296 source =
'# OCT-LINT: disable=no_hardcoded_secrets\npassword = "hunter2"\n'
297 waivers = oct_lint.parse_inline_waivers(source)
298 assert len(waivers) == 1
299 w = list(waivers.values())[0]
300 assert w[
"reason"]
is None
304 """parse_inline_waivers captures reason when provided (OI-404)."""
305 source =
'# OCT-LINT: disable=no_hardcoded_secrets reason="test fixture"\n'
306 waivers = oct_lint.parse_inline_waivers(source)
307 w = list(waivers.values())[0]
308 assert w[
"reason"]
is not None
309 assert "test fixture" in w[
"reason"]
313 """The waived_rules set only includes waivers with a reason (OI-404)."""
315 '# OCT-LINT: disable=rule_a\n'
316 '# OCT-LINT: disable=rule_b reason="justified"\n'
318 waivers = oct_lint.parse_inline_waivers(source)
319 waived_rules = {w[
"rule"]
for w
in waivers.values()
if w[
"reason"]
is not None}
320 assert "rule_a" not in waived_rules,
"reason-less waiver should not suppress check"
321 assert "rule_b" in waived_rules,
"waiver with reason should suppress check"
test_non_secret_compound_passes()
test_dict_secret_key_call_value_passes()
test_function_default_violation()
test_waiver_with_reason_parsed_correctly()
test_waiver_filtering_excludes_reasonless()
test_direct_assignment_violation()
test_annotated_assignment_violation()
test_waiver_without_reason_parsed_correctly()
test_fstring_non_secret_name_passes()
test_os_environ_subscript_passes()
test_prefixed_api_key_violation()
test_dict_multiple_secret_keys()
test_compound_name_violation()
test_case_insensitive_matching()
test_subscript_on_non_environ_passes()
test_concatenation_caught()
test_function_default_non_secret_passes()
test_non_secret_name_with_string_passes()
test_dict_literal_violation()
test_direct_assignment_multiple_violations()
test_syntax_error_passes()
test_kwonly_default_violation()
test_class_attribute_violation()
test_concat_with_variable_caught()
test_all_secret_patterns_caught()
test_suffixed_token_violation()
test_function_call_passes()
test_dict_non_secret_key_passes()