Skip to content

Commit e1fba6c

Browse files
committed
fix(fleet): safely repair retired descriptile policy references
1 parent 9e6abd2 commit e1fba6c

4 files changed

Lines changed: 71 additions & 2 deletions

File tree

.github/workflows/e2e.yml

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -21,3 +21,5 @@ jobs:
2121
run: pip install pyyaml --quiet
2222
- name: Run E2E tests
2323
run: bash tests/e2e.sh
24+
- name: Verify canonical descriptile policy repair
25+
run: bash tests/retired-descriptile-policy-test.sh
Lines changed: 36 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,36 @@
1+
#!/usr/bin/env bash
2+
# SPDX-License-Identifier: MPL-2.0
3+
# Repair only a finding's policy file, after its canonical targets exist.
4+
set -euo pipefail
5+
REPO_PATH="${1:?Usage: $0 <repo-path> <finding-json>}"
6+
FINDING_JSON="${2:?Missing finding JSON file}"
7+
ruby - "$REPO_PATH" "$FINDING_JSON" <<'RUBY'
8+
require 'json'
9+
require 'pathname'
10+
11+
root = File.realpath(ARGV.fetch(0))
12+
finding = JSON.parse(File.read(ARGV.fetch(1)))
13+
relative = finding['file'] || finding.dig('location', 'file')
14+
abort 'Finding must name a policy file' unless relative.is_a?(String) && !relative.empty?
15+
path = Pathname.new(relative)
16+
abort 'Finding path must be repository-relative' if path.absolute? || path.each_filename.include?('..')
17+
allowed = %w[Justfile justfile].include?(relative) ||
18+
(path.dirname.to_s == '.github/workflows' && %w[.yml .yaml].include?(path.extname)) ||
19+
(%w[scripts .githooks].include?(path.dirname.to_s) && path.extname == '.sh')
20+
target = File.join(root, relative)
21+
unless allowed && !File.symlink?(target) && File.realpath(target).start_with?(root + '/')
22+
abort 'Refusing a path outside supported repository policy files'
23+
end
24+
pattern = %r{\.machine_readable/(?:6a2/)?(STATE|META|ECOSYSTEM|AGENTIC|NEUROSYM|PLAYBOOK|ANCHOR)\.a2ml}
25+
content = File.read(target)
26+
names = content.scan(pattern).flatten.uniq
27+
names.each do |name|
28+
canonical = File.join(root, '.machine_readable/descriptiles', name + '.a2ml')
29+
unless !File.symlink?(canonical) && File.file?(canonical) && File.realpath(canonical).start_with?(root + '/')
30+
abort "Canonical descriptile missing or unsafe: #{name}; reconcile files before repairing policy"
31+
end
32+
end
33+
updated = content.gsub(pattern) { ".machine_readable/descriptiles/#{$1}.a2ml" }
34+
File.write(target, updated) unless updated == content
35+
puts "#{relative}: #{names.length} descriptile reference target(s) reconciled"
36+
RUBY

scripts/fix-script-registry.json

Lines changed: 4 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -48,7 +48,8 @@
4848
"TrackedNpmLockfile": "fix-tracked-package-lock.sh",
4949
"LicensePMPLDrift": "fix-pmpl-drift.sh",
5050
"ActionsPolicyTooRestrictive": "fix-actions-policy.sh",
51-
"SessionDetritus": "fix-session-detritus.sh"
51+
"SessionDetritus": "fix-session-detritus.sh",
52+
"RetiredDescriptilePolicy": "fix-retired-descriptile-policy.sh"
5253
},
5354
"by_recipe": {
5455
"recipe-actions-allow-all": "fix-actions-policy.sh",
@@ -109,7 +110,8 @@
109110
"chapel-replace-chpl-about-with-version": "fix-chapel-replace-chpl-about.sh",
110111
"reusable_workflow_sha_bump_needs_propagation": "propagate-sha-bump.sh",
111112
"recipe-fix-pmpl-drift": "fix-pmpl-drift.sh",
112-
"recipe-archive-session-detritus": "fix-session-detritus.sh"
113+
"recipe-archive-session-detritus": "fix-session-detritus.sh",
114+
"recipe-retired-descriptile-policy": "fix-retired-descriptile-policy.sh"
113115
}
114116
}
115117
}
Lines changed: 29 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,29 @@
1+
#!/usr/bin/env bash
2+
# SPDX-License-Identifier: MPL-2.0
3+
set -euo pipefail
4+
ROOT="$(cd "$(dirname "$0")/.." && pwd)"
5+
TEMP_DIR=$(mktemp -d)
6+
trap 'rm -rf "$TEMP_DIR"' EXIT
7+
mkdir -p "$TEMP_DIR/repo/.github/workflows" "$TEMP_DIR/repo/.machine_readable/descriptiles"
8+
printf '%s\n' 'test -f .machine_readable/STATE.a2ml' > "$TEMP_DIR/repo/.github/workflows/check.yml"
9+
printf '%s\n' '{"file":".github/workflows/check.yml"}' > "$TEMP_DIR/finding.json"
10+
FIXER="$ROOT/scripts/fix-retired-descriptile-policy.sh"
11+
# Missing canonical data must not be papered over with a reference rewrite.
12+
if bash "$FIXER" "$TEMP_DIR/repo" "$TEMP_DIR/finding.json"; then
13+
echo 'FAIL: repaired policy without its canonical target' >&2; exit 1
14+
fi
15+
grep -Fxq 'test -f .machine_readable/STATE.a2ml' "$TEMP_DIR/repo/.github/workflows/check.yml"
16+
printf '%s\n' '[metadata]' 'name = "fixture"' > "$TEMP_DIR/repo/.machine_readable/descriptiles/STATE.a2ml"
17+
bash "$FIXER" "$TEMP_DIR/repo" "$TEMP_DIR/finding.json"
18+
grep -Fxq 'test -f .machine_readable/descriptiles/STATE.a2ml' "$TEMP_DIR/repo/.github/workflows/check.yml"
19+
bash "$FIXER" "$TEMP_DIR/repo" "$TEMP_DIR/finding.json"
20+
printf '%s\n' '{"file":"../outside"}' > "$TEMP_DIR/finding.json"
21+
if bash "$FIXER" "$TEMP_DIR/repo" "$TEMP_DIR/finding.json"; then
22+
echo 'FAIL: accepted path traversal' >&2; exit 1
23+
fi
24+
printf '%s\n' '{"file":".github/workflows/link.yml"}' > "$TEMP_DIR/finding.json"
25+
ln -s check.yml "$TEMP_DIR/repo/.github/workflows/link.yml"
26+
if bash "$FIXER" "$TEMP_DIR/repo" "$TEMP_DIR/finding.json"; then
27+
echo 'FAIL: accepted a symlink' >&2; exit 1
28+
fi
29+
echo 'PASS: canonical prerequisite, repair, idempotence, traversal and symlink rejection'

0 commit comments

Comments
 (0)