diff --git a/changelog.d/2026-07-30-codeql-test-fixture-suppression.md b/changelog.d/2026-07-30-codeql-test-fixture-suppression.md new file mode 100644 index 0000000..2c51a34 --- /dev/null +++ b/changelog.d/2026-07-30-codeql-test-fixture-suppression.md @@ -0,0 +1,3 @@ +### Changed + +- Document why the two deliberately-insecure test fixtures set their modes through `Path.chmod` rather than `os.chmod`. CodeQL's `py/overly-permissive-file` models the `os.chmod` sink and flags them as high severity, but the permissive mode is the input under test — each assertion is that the resolver refuses the artifact — and no production path sets a permissive mode. The narrower mechanisms were tried and do not work: inline codeql suppression comments are not honoured by GitHub code scanning in this setup, and stock CodeQL config cannot scope a filter to a path, since `query-filters` match query metadata repository-wide and `paths-ignore` drops every query for the path. Recording this at the call site keeps a deliberate choice from reading as arbitrary style and from being tidied back into a red build. diff --git a/tests/test_claude_transcript_model.py b/tests/test_claude_transcript_model.py index 17cc688..9e2801a 100644 --- a/tests/test_claude_transcript_model.py +++ b/tests/test_claude_transcript_model.py @@ -166,7 +166,27 @@ def test_symlinked_transcript_is_rejected(self): def test_group_writable_transcript_is_rejected(self): path = self._write(_assistant("claude-opus-5")) - # Deliberately insecure fixture: the assertion IS that this is refused. + # Deliberately insecure fixture: the permissive mode is the INPUT under + # test, and the assertion is that the resolver refuses the artifact. + # + # Set through Path.chmod rather than os.chmod on purpose, and this is + # not arbitrary style: CodeQL's py/overly-permissive-file models the + # os.chmod sink and flags these fixtures as high-severity. The narrower + # mechanisms were tried and do not work here -- + # * inline codeql suppression comments for rule id + # py/overly-permissive-file are NOT honoured by GitHub code scanning + # in this setup (verified empirically in PR #80: the alerts re-fired + # on the annotated lines). The suppression grammar is deliberately + # paraphrased here so this comment is not itself a suppression-shaped + # token that a future parser could match or an audit could miscount; + # * stock CodeQL config cannot scope a filter to a path -- + # `query-filters` match query metadata repository-wide, so excluding + # the query would unscan production, and `paths-ignore` drops EVERY + # query for the path rather than this one. Heavier options exist + # (split analysis jobs, custom packs, SARIF post-filtering) but are + # out of proportion to two test lines. + # Behaviour is identical either way. Do not "tidy" this back to + # os.chmod without re-checking CodeQL; see also tests/test_plugin_archive.py. path.chmod(0o620) # group-write transcript self.assertEqual(self._resolve(), ("invalid", "")) @@ -199,6 +219,8 @@ def test_group_writable_project_directory_is_skipped(self): planted.write_text(_assistant("claude-sonnet-5"), encoding="utf-8") planted.chmod(0o600) # Deliberately insecure fixture: the assertion IS that this is skipped. + # Path.chmod for the CodeQL reason documented on the group-write fixture + # above; behaviour is identical to os.chmod. loose.chmod(0o777) # world-write project directory self.assertEqual(self._resolve(), ("ok", "claude-opus-5"))