Skip to content

Commit 3dcd7b7

Browse files
Address bounded release audit review findings
Handle root homes, bounded blob caching, non-fetching partial clones, explicit LFS review, and classic branch protection without expanding into generalized parsing.
1 parent 18cdea1 commit 3dcd7b7

3 files changed

Lines changed: 106 additions & 14 deletions

File tree

public-source-release-audit/SKILL.md

Lines changed: 10 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -31,7 +31,9 @@ The supported categories are deliberately small:
3131

3232
Stage all intended release changes before relying on the candidate result.
3333
Ignored files and submodule contents are outside this check. The checker does
34-
not inspect Git history.
34+
not inspect Git history. Git LFS pointers fail the check because their external
35+
objects require separate review. Missing index blobs, including unavailable
36+
partial-clone objects, fail without network fetching.
3537

3638
## Semantic Review
3739

@@ -52,14 +54,17 @@ gh api repos/OWNER/REPO --jq '{visibility,security_and_analysis}'
5254
gh ruleset check --default --repo OWNER/REPO
5355
gh ruleset list --repo OWNER/REPO --parents --limit 100
5456
gh ruleset view RULESET-ID --repo OWNER/REPO
57+
gh api repos/OWNER/REPO/branches/DEFAULT-BRANCH/protection
5558
gh api 'repos/OWNER/REPO/actions/runners?per_page=100' \
5659
--jq '{total_count,runners:[.runners[] | {name,status,busy}]}'
5760
```
5861

59-
Inspect applicable rulesets when necessary to confirm the required hosted
60-
check, update strictness, and bypass actors. If authorization cannot expose a
61-
setting, report it as unconfirmed. Do not weaken or mutate settings unless the
62-
user separately authorizes that action.
62+
Inspect applicable rulesets and classic branch protection when necessary to
63+
confirm the required hosted check, update strictness, and bypass actors. A 404
64+
from the classic endpoint means no classic rule is configured; it does not
65+
invalidate an applicable ruleset. If authorization cannot expose a setting,
66+
report it as unconfirmed. Do not weaken or mutate settings unless the user
67+
separately authorizes that action.
6368

6469
## Result
6570

public-source-release-audit/scripts/check_current_source.rb

Lines changed: 29 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -7,7 +7,7 @@
77
RULES = [
88
[
99
"machine-local home path",
10-
%r{(?:\A|[\s"'`=:(,])/(?:Users|home)/[A-Za-z0-9._-]+(?:/|\b)|\b[A-Za-z]:[\\/]Users[\\/][^\\/\r\n]+(?:[\\/]|$)}
10+
%r{(?:\A|[\s"'`=:(,])(?:/(?:Users|home)/[A-Za-z0-9._-]+(?:/|\b)|/root(?:/|(?![A-Za-z0-9._-])))|\b[A-Za-z]:[\\/]Users[\\/][^\\/\r\n]+(?:[\\/]|$)}
1111
],
1212
["AWS access key", /\b(?:AKIA|ASIA)[0-9A-Z]{16}\b/],
1313
["bearer credential", /Authorization\s*:\s*Bearer\s+[A-Za-z0-9._~+\/=:-]{16,}/i],
@@ -23,11 +23,13 @@
2323
].freeze
2424

2525
INDEX_BLOB_MODES = %w[100644 100755 120000].freeze
26+
LFS_POINTER = %r{\Aversion https://git-lfs\.github\.com/spec/v1(?:\r?\n|\z)}.freeze
2627
GIT_ENVIRONMENT = {
2728
"GIT_ALTERNATE_OBJECT_DIRECTORIES" => nil,
2829
"GIT_COMMON_DIR" => nil,
2930
"GIT_DIR" => nil,
3031
"GIT_INDEX_FILE" => nil,
32+
"GIT_NO_LAZY_FETCH" => "1",
3133
"GIT_NO_REPLACE_OBJECTS" => "1",
3234
"GIT_OBJECT_DIRECTORY" => nil,
3335
"GIT_WORK_TREE" => nil
@@ -43,16 +45,27 @@ def git_capture(repo, *arguments)
4345
Open3.capture3(GIT_ENVIRONMENT, "git", "-C", repo, *arguments)
4446
end
4547

46-
def scan_source(content, relative_path, findings, sensitive_paths = nil)
48+
def matching_labels(content)
4749
source = content.b
48-
RULES.each do |label, pattern|
49-
next unless pattern.match?(source)
50+
RULES.filter_map { |label, pattern| label if pattern.match?(source) }
51+
end
5052

53+
def record_findings(labels, relative_path, findings, sensitive_paths = nil)
54+
labels.each do |label|
5155
findings.add([relative_path, label])
5256
sensitive_paths&.add(relative_path)
5357
end
5458
end
5559

60+
def scan_source(content, relative_path, findings, sensitive_paths = nil)
61+
record_findings(
62+
matching_labels(content),
63+
relative_path,
64+
findings,
65+
sensitive_paths
66+
)
67+
end
68+
5669
def display_path(relative_path, sensitive_paths)
5770
sensitive_paths.include?(relative_path) ? "<redacted path>" : relative_path.dump
5871
end
@@ -72,7 +85,7 @@ def display_path(relative_path, sensitive_paths)
7285
errors = Set.new
7386
findings = Set.new
7487
sensitive_paths = Set.new
75-
blob_cache = {}
88+
blob_result_cache = {}
7689
index_entries = index_output.split("\0").reject(&:empty?).filter_map do |record|
7790
metadata, relative_path = record.split("\t", 2)
7891
mode, object_id, stage = metadata&.split(" ", 3)
@@ -100,8 +113,8 @@ def display_path(relative_path, sensitive_paths)
100113
end
101114

102115
object_id = entry.fetch(:object_id)
103-
content = blob_cache[object_id]
104-
unless content
116+
result = blob_result_cache[object_id]
117+
unless result
105118
content, _blob_error, blob_status = git_capture(
106119
repo,
107120
"cat-file",
@@ -112,9 +125,16 @@ def display_path(relative_path, sensitive_paths)
112125
errors.add([relative_path, "unable to read Git index blob"])
113126
next
114127
end
115-
blob_cache[object_id] = content
128+
result = {
129+
labels: matching_labels(content),
130+
lfs_pointer: LFS_POINTER.match?(content.b)
131+
}
132+
blob_result_cache[object_id] = result
133+
end
134+
if result.fetch(:lfs_pointer)
135+
errors.add([relative_path, "Git LFS object requires separate review"])
116136
end
117-
scan_source(content, relative_path, findings)
137+
record_findings(result.fetch(:labels), relative_path, findings)
118138
end
119139

120140
worktree_output, _worktree_error, worktree_status = git_capture(

public-source-release-audit/tests/test_check_current_source.rb

Lines changed: 67 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -123,6 +123,7 @@ def test_supported_high_confidence_formats_are_detected_and_redacted
123123
"openai.txt" => "s" + "k-proj-" + ("D" * 24),
124124
"private-key.txt" => "-----BEGIN " + "OPENSSH PRIVATE KEY-----",
125125
"posix-home.txt" => "/" + "Users/example/private.txt",
126+
"root-home.txt" => "/" + "root/.ssh/id_ed25519",
126127
"windows-home.txt" => "C:" + "\\Users\\example\\private.txt"
127128
}
128129
fixtures.each { |path, content| stage(repo, path, content) }
@@ -186,6 +187,72 @@ def test_unrelated_home_directory_and_web_route_do_not_match_user_homes
186187
end
187188
end
188189

190+
def test_duplicate_index_blobs_report_each_candidate_path
191+
with_repo do |repo|
192+
token = github_token
193+
stage(repo, "first.txt", token)
194+
stage(repo, "second.txt", token)
195+
196+
_stdout, stderr, status = run_checker(repo)
197+
198+
refute status.success?
199+
assert_includes stderr, "first.txt"
200+
assert_includes stderr, "second.txt"
201+
assert_includes stderr, "GitHub token"
202+
refute_includes stderr, token
203+
end
204+
end
205+
206+
def test_git_commands_disable_lazy_object_fetching
207+
with_repo do |repo|
208+
stage(repo, "README.md", "Public documentation without credentials.\n")
209+
real_git = ENV.fetch("PATH").split(File::PATH_SEPARATOR).filter_map do |dir|
210+
candidate = File.join(dir, "git")
211+
candidate if File.executable?(candidate)
212+
end.first
213+
refute_nil real_git
214+
215+
Dir.mktmpdir("public-source-check-bin-") do |bin_dir|
216+
fake_git = File.join(bin_dir, "git")
217+
File.write(fake_git, <<~RUBY)
218+
#!/usr/bin/env ruby
219+
abort "lazy fetching was not disabled" unless ENV["GIT_NO_LAZY_FETCH"] == "1"
220+
exec #{real_git.dump}, *ARGV
221+
RUBY
222+
FileUtils.chmod(0o755, fake_git)
223+
224+
stdout, stderr, status = run_checker(
225+
repo,
226+
{
227+
"GIT_NO_LAZY_FETCH" => "0",
228+
"PATH" => [bin_dir, ENV.fetch("PATH")].join(File::PATH_SEPARATOR)
229+
}
230+
)
231+
232+
assert status.success?, stderr
233+
assert_includes stdout, "current public-source check passed"
234+
end
235+
end
236+
end
237+
238+
def test_git_lfs_pointer_requires_separate_review
239+
with_repo do |repo|
240+
pointer = [
241+
"version https://git-lfs.github.com/spec/v1",
242+
"oid sha256:#{'a' * 64}",
243+
"size 123"
244+
].join("\n") + "\n"
245+
stage(repo, "large.dat", pointer)
246+
247+
_stdout, stderr, status = run_checker(repo)
248+
249+
refute status.success?
250+
assert_includes stderr, "large.dat"
251+
assert_includes stderr, "Git LFS object requires separate review"
252+
refute_includes stderr, "sha256:"
253+
end
254+
end
255+
189256
def test_unresolved_index_entries_fail_closed
190257
with_repo do |repo|
191258
object_ids = %w[base ours theirs].map do |content|

0 commit comments

Comments
 (0)