Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 2 additions & 0 deletions .github/workflows/daily-cli-performance.lock.yml

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

2 changes: 2 additions & 0 deletions .github/workflows/daily-code-metrics.lock.yml

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

2 changes: 2 additions & 0 deletions .github/workflows/daily-community-attribution.lock.yml

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

4 changes: 3 additions & 1 deletion actions/setup/js/check_daily_aic_workflow_guardrail.cjs
Original file line number Diff line number Diff line change
Expand Up @@ -663,7 +663,9 @@ async function main(options = {}) {
const workflowName = process.env.GH_AW_WORKFLOW_NAME || process.env.GH_AW_WORKFLOW_ID || "workflow";
let actorLogin = process.env.GITHUB_TRIGGERING_ACTOR || process.env.GITHUB_ACTOR || "";
if (backend === REPO_MEMORY_BACKEND) {
if (process.env.GH_AW_ALLOW_INSECURE_REPO_MEMORY_AIC !== "true") {
const trustedLedger = process.env.GH_AW_DAILY_AIC_REPO_MEMORY_TRUSTED === "true";
const allowInsecureLedger = process.env.GH_AW_ALLOW_INSECURE_REPO_MEMORY_AIC === "true";
if (!trustedLedger && !allowInsecureLedger) {
const message = "Daily workflow AI Credits repo-memory ledger is untrusted; set GH_AW_ALLOW_INSECURE_REPO_MEMORY_AIC to true to explicitly allow it.";
core.setOutput("daily_ai_credits_guardrail_status", "structural_error");
core.setOutput("daily_ai_credits_guardrail_error", message);
Expand Down
64 changes: 64 additions & 0 deletions actions/setup/js/check_daily_aic_workflow_guardrail.test.cjs
Original file line number Diff line number Diff line change
Expand Up @@ -36,13 +36,15 @@ describe("check_daily_aic_workflow_guardrail", () => {
setFailed,
info: vi.fn(),
warning: vi.fn(),
summary: { addDetails: vi.fn(), write: vi.fn() },
};
global.github = {};
global.context = { repo: { owner: "test-owner", repo: "test-repo" } };
process.env.GH_AW_MAX_DAILY_AI_CREDITS = "100";
process.env.GH_AW_MAX_DAILY_AI_CREDITS_BACKEND = "repo-memory";
process.env.GITHUB_EVENT_NAME = "schedule";
delete process.env.GH_AW_ALLOW_INSECURE_REPO_MEMORY_AIC;
delete process.env.GH_AW_DAILY_AIC_REPO_MEMORY_TRUSTED;
vi.resetModules();
const mod = await import("./check_daily_aic_workflow_guardrail.cjs");
exports = mod.default || mod;
Expand All @@ -52,13 +54,75 @@ describe("check_daily_aic_workflow_guardrail", () => {
expect(outputs.daily_ai_credits_guardrail_status).toBe("structural_error");
expect(outputs.daily_ai_credits_guardrail_error).toContain("untrusted");
expect(setFailed).toHaveBeenCalledOnce();

process.env.GH_AW_ALLOW_INSECURE_REPO_MEMORY_AIC = "true";
await exports.main({ repoMemoryDir: scanCacheDirectory });
expect(outputs.daily_ai_credits_guardrail_status).toBe("under_budget");
expect(setFailed).toHaveBeenCalledOnce();
} finally {
delete global.core;
delete global.github;
delete global.context;
delete process.env.GH_AW_MAX_DAILY_AI_CREDITS;
delete process.env.GH_AW_MAX_DAILY_AI_CREDITS_BACKEND;
delete process.env.GH_AW_ALLOW_INSECURE_REPO_MEMORY_AIC;
delete process.env.GITHUB_EVENT_NAME;
}
});

it("accepts a prior-run ledger written by the normal writer when provenance is trusted", async () => {
const outputs = {};
const setFailed = vi.fn();
global.core = {
setOutput: (key, value) => {
outputs[key] = value;
},
setFailed,
info: vi.fn(),
warning: vi.fn(),
summary: { addDetails: vi.fn(), write: vi.fn() },
};
global.github = {};
global.context = { repo: { owner: "test-owner", repo: "test-repo" } };
const repoMemoryDir = path.join(scanCacheDirectory, "memory");
const usageRoot = path.join(scanCacheDirectory, "usage");
fs.mkdirSync(usageRoot, { recursive: true });
fs.writeFileSync(path.join(usageRoot, "agent_usage.jsonl"), `${JSON.stringify({ aic: 4 })}\n`, "utf8");
const now = Date.now();
process.env.GH_AW_MAX_DAILY_AI_CREDITS = "100";
process.env.GH_AW_MAX_DAILY_AI_CREDITS_BACKEND = "repo-memory";
process.env.GH_AW_DAILY_AIC_REPO_MEMORY_TRUSTED = "true";
process.env.GH_AW_DAILY_AIC_REPO_MEMORY_DIR = repoMemoryDir;
process.env.GH_AW_WORKFLOW_ID = "daily-code-metrics";
process.env.GITHUB_EVENT_NAME = "schedule";
process.env.GITHUB_RUN_ID = "41";
process.env.GITHUB_REPOSITORY = "test-owner/test-repo";
try {
const ledgerModule = await import("./daily_aic_repo_memory_ledger.cjs");
const ledger = ledgerModule.default || ledgerModule;
ledger.appendCurrentRunLedgerEntry({
repoMemoryDir,
usageRoot,
now: now - 60_000,
});

await exports.main({ repoMemoryDir });

expect(outputs.daily_ai_credits_guardrail_status).toBe("under_budget");
expect(outputs.daily_ai_credits_total).toBe("4");
expect(setFailed).not.toHaveBeenCalled();
} finally {
delete global.core;
delete global.github;
delete global.context;
delete process.env.GH_AW_MAX_DAILY_AI_CREDITS;
delete process.env.GH_AW_MAX_DAILY_AI_CREDITS_BACKEND;
delete process.env.GH_AW_DAILY_AIC_REPO_MEMORY_TRUSTED;
delete process.env.GH_AW_DAILY_AIC_REPO_MEMORY_DIR;
delete process.env.GH_AW_WORKFLOW_ID;
delete process.env.GITHUB_EVENT_NAME;
delete process.env.GITHUB_RUN_ID;
delete process.env.GITHUB_REPOSITORY;
}
});

Expand Down
34 changes: 27 additions & 7 deletions actions/setup/sh/clone_repo_memory_branch.sh
Original file line number Diff line number Diff line change
Expand Up @@ -76,6 +76,12 @@ harden_repo_memory_git_state() {
fi
}

write_clone_output() {
if [ -n "${GITHUB_OUTPUT:-}" ]; then
printf 'cloned=%s\n' "$1" >> "$GITHUB_OUTPUT"
fi
}

# Validate required environment variables
if [ -z "$GH_TOKEN" ]; then
echo "ERROR: GH_TOKEN environment variable is required"
Expand Down Expand Up @@ -121,15 +127,28 @@ export GIT_CONFIG_COUNT=1
export GIT_CONFIG_KEY_0="http.extraheader"
export GIT_CONFIG_VALUE_0="$AUTH_HEADER"

# Try to clone the branch (don't fail if it doesn't exist)
set +e
git clone --depth 1 --single-branch --branch "$BRANCH_NAME" "$SAFE_ORIGIN_URL" "$MEMORY_DIR" 2>/dev/null
CLONE_EXIT_CODE=$?
set -e
write_clone_output false

# Verify the branch before cloning so only a confirmed missing branch can
# fall back to an orphan. Authentication and transport failures must fail closed.
if git ls-remote --exit-code --heads "$SAFE_ORIGIN_URL" "refs/heads/$BRANCH_NAME" >/dev/null 2>&1; then
if ! git clone --depth 1 --single-branch --branch "$BRANCH_NAME" "$SAFE_ORIGIN_URL" "$MEMORY_DIR" 2>/dev/null; then
echo "ERROR: failed to clone repo-memory branch after it was confirmed to exist" >&2
exit 1
fi
CLONE_SUCCEEDED=true
else
LS_REMOTE_EXIT_CODE=$?
if [ "$LS_REMOTE_EXIT_CODE" -ne 2 ]; then
echo "ERROR: failed to verify repo-memory branch; refusing to create an orphan branch" >&2
exit 1
fi
CLONE_SUCCEEDED=false
fi
unset GIT_CONFIG_COUNT GIT_CONFIG_KEY_0 GIT_CONFIG_VALUE_0

if [ $CLONE_EXIT_CODE -ne 0 ]; then
# Clone failed - branch doesn't exist
if [ "$CLONE_SUCCEEDED" = "false" ]; then
# The remote confirmed that the branch does not exist.
if [ "$CREATE_ORPHAN" = "true" ]; then
echo "Branch $BRANCH_NAME does not exist, creating orphan branch"
mkdir -p "$MEMORY_DIR"
Expand Down Expand Up @@ -165,6 +184,7 @@ else
git remote remove origin >/dev/null 2>&1 || true
git remote add origin "$SAFE_ORIGIN_URL"
harden_repo_memory_git_state "$MEMORY_DIR" "$SAFE_ORIGIN_URL"
write_clone_output true
fi
unset AUTH_HEADER

Expand Down
78 changes: 77 additions & 1 deletion actions/setup/sh/clone_repo_memory_branch_test.sh
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,42 @@ SCRIPT="${SCRIPT_DIR}/clone_repo_memory_branch.sh"
TESTS_PASSED=0
TESTS_FAILED=0
WORKSPACE="$(mktemp -d)"
REAL_GIT="$(command -v git)"
MOCK_BIN="${WORKSPACE}/mock-bin"
SOURCE_REPO="${WORKSPACE}/source.git"
mkdir -p "${MOCK_BIN}"

cat > "${MOCK_BIN}/git" <<'EOF'
#!/usr/bin/env bash
if [ "$1" = "ls-remote" ]; then
case "${GIT_TEST_LS_REMOTE:-missing}" in
missing) exit 2 ;;
exists|clone-fail)
printf '%s\trefs/heads/%s\n' "1111111111111111111111111111111111111111" "$BRANCH_NAME"
exit 0
;;
error) exit 128 ;;
esac
fi
if [ "$1" = "clone" ] && [ "${GIT_TEST_LS_REMOTE:-missing}" != "missing" ]; then
if [ "${GIT_TEST_LS_REMOTE}" = "clone-fail" ]; then
exit 128
fi
exec "$REAL_GIT" clone --depth 1 --single-branch --branch "$BRANCH_NAME" "$GIT_TEST_SOURCE_REPO" "$MEMORY_DIR"
fi
exec "$REAL_GIT" "$@"
EOF
chmod +x "${MOCK_BIN}/git"

SOURCE_WORKTREE="${WORKSPACE}/source-worktree"
mkdir -p "${SOURCE_WORKTREE}"
"${REAL_GIT}" -C "${SOURCE_WORKTREE}" init -q -b repo-memory-test
"${REAL_GIT}" -C "${SOURCE_WORKTREE}" config user.name "Test"
"${REAL_GIT}" -C "${SOURCE_WORKTREE}" config user.email "test@example.com"
echo "ledger" > "${SOURCE_WORKTREE}/ledger.jsonl"
"${REAL_GIT}" -C "${SOURCE_WORKTREE}" add ledger.jsonl
"${REAL_GIT}" -C "${SOURCE_WORKTREE}" commit -qm "Add ledger"
"${REAL_GIT}" clone -q --bare "${SOURCE_WORKTREE}" "${SOURCE_REPO}"

cleanup() {
rm -rf "${WORKSPACE}"
Expand All @@ -29,13 +65,21 @@ assert() {

run_script() {
local memory_dir="$1"
local ls_remote_mode="${2:-missing}"
local output_file="${memory_dir}.output"
rm -f "${output_file}"
GITHUB_OUTPUT="${output_file}" \
GH_TOKEN="test-token" \
BRANCH_NAME="repo-memory-test" \
TARGET_REPO="octo/test" \
MEMORY_DIR="${memory_dir}" \
CREATE_ORPHAN="true" \
GITHUB_SERVER_URL="https://127.0.0.1:9" \
bash "${SCRIPT}" 2>&1 || true
GIT_TEST_LS_REMOTE="${ls_remote_mode}" \
GIT_TEST_SOURCE_REPO="${SOURCE_REPO}" \
REAL_GIT="${REAL_GIT}" \
PATH="${MOCK_BIN}:${PATH}" \
bash "${SCRIPT}" 2>&1
}

echo "Testing clone_repo_memory_branch.sh"
Expand All @@ -50,6 +94,7 @@ D="${WORKSPACE}/test2"
mkdir -p "${D}"
OUTPUT="$(run_script "${D}")"
assert "orphan branch message emitted" "printf '%s' \"${OUTPUT}\" | grep -q 'creating orphan branch'"
assert "missing branch is not trusted" "grep -q '^cloned=false$' '${D}.output'"
assert "git metadata exists" "[ -d '${D}/.git' ]"
assert "hooks path hardened" "[ \"\$(git -C '${D}' config --default '' core.hooksPath)\" = '/dev/null' ]"
assert "fsmonitor hardened" "[ \"\$(git -C '${D}' config --default '' core.fsmonitor)\" = 'false' ]"
Expand Down Expand Up @@ -140,6 +185,37 @@ else
fi
echo ""

echo "Test 7: Existing branch is cloned and marked trusted"
D="${WORKSPACE}/test7"
OUTPUT="$(run_script "${D}" exists)"
assert "existing branch cloned" "[ -f '${D}/ledger.jsonl' ]"
assert "successful clone is trusted" "grep -q '^cloned=true$' '${D}.output'"
echo ""

echo "Test 8: Failed clone after branch verification does not create an orphan"
D="${WORKSPACE}/test8"
if OUTPUT="$(run_script "${D}" clone-fail)"; then
CLONE_STATUS=0
else
CLONE_STATUS=$?
fi
assert "failed clone exits unsuccessfully" "[ '${CLONE_STATUS}' -ne 0 ]"
assert "failed clone does not create git metadata" "[ ! -e '${D}/.git' ]"
assert "failed clone remains untrusted" "grep -q '^cloned=false$' '${D}.output'"
echo ""

echo "Test 9: Unclassified remote failure does not create an orphan"
D="${WORKSPACE}/test9"
if OUTPUT="$(run_script "${D}" error)"; then
REMOTE_STATUS=0
else
REMOTE_STATUS=$?
fi
assert "remote verification failure exits unsuccessfully" "[ '${REMOTE_STATUS}' -ne 0 ]"
assert "remote verification failure does not create git metadata" "[ ! -e '${D}/.git' ]"
assert "remote verification failure remains untrusted" "grep -q '^cloned=false$' '${D}.output'"
echo ""

echo "Tests passed: ${TESTS_PASSED}"
echo "Tests failed: ${TESTS_FAILED}"

Expand Down
3 changes: 2 additions & 1 deletion pkg/workflow/compiler_activation_daily_aic.go
Original file line number Diff line number Diff line change
Expand Up @@ -141,7 +141,7 @@ func (c *Compiler) buildActivationDailyAICGuardrailStep(data *WorkflowData) []st
steps = append(steps, fmt.Sprintf(" %s: %q\n", maxDailyAICBackendEnvVar, data.MaxDailyAICBackend))
}
if data.MaxDailyAICBackend == maxDailyAICBackendRepoMemory {
steps = append(steps, " GH_AW_ALLOW_INSECURE_REPO_MEMORY_AIC: ${{ vars.GH_AW_ALLOW_INSECURE_REPO_MEMORY_AIC || 'false' }}\n")
steps = append(steps, " GH_AW_DAILY_AIC_REPO_MEMORY_TRUSTED: ${{ steps.daily-aic-repo-memory-clone.outputs.cloned == 'true' }}\n GH_AW_ALLOW_INSECURE_REPO_MEMORY_AIC: ${{ vars.GH_AW_ALLOW_INSECURE_REPO_MEMORY_AIC || 'false' }}\n")
}
if entry, ok := dailyAICRepoMemoryEntry(data); ok {
steps = append(steps, fmt.Sprintf(" GH_AW_DAILY_AIC_REPO_MEMORY_DIR: %s\n", dailyAICRepoMemoryDir(entry)))
Expand Down Expand Up @@ -247,6 +247,7 @@ func buildDailyAICRepoMemoryCloneStep(memory RepoMemoryEntry) []string {
memoryDir := dailyAICRepoMemoryDir(memory)
return []string{
fmt.Sprintf(" - name: Clone daily AIC %s ledger (%s)\n", memoryLabel, memory.ID),
" id: daily-aic-repo-memory-clone\n",
fmt.Sprintf(" if: %s\n", maxDailyAICreditsConfiguredIfExpr),
" env:\n",
" GH_TOKEN: ${{ github.token }}\n",
Expand Down
11 changes: 11 additions & 0 deletions pkg/workflow/daily_aic_workflow_guardrail_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -611,6 +611,17 @@ Daily AIC guardrail with repo-memory ledger`
if !strings.Contains(lockStr, "GH_AW_MAX_DAILY_AI_CREDITS_BACKEND: \"repo-memory\"") {
t.Fatal("expected activation guardrail to receive repo-memory backend env")
}
if !strings.Contains(lockStr, "id: daily-aic-repo-memory-clone") {
t.Fatal("expected repo-memory clone step to expose its clone result")
}
if !strings.Contains(lockStr, "GH_AW_DAILY_AIC_REPO_MEMORY_TRUSTED: ${{ steps.daily-aic-repo-memory-clone.outputs.cloned == 'true' }}") {
t.Fatal("expected activation guardrail to trust the ledger only after a successful clone")
}
preGuardrailCloneIdx := strings.Index(lockStr, "Clone daily AIC repo-memory ledger (default)")
guardrailCheckIdx := strings.Index(lockStr, "name: Check daily workflow token guardrail")
if preGuardrailCloneIdx < 0 || guardrailCheckIdx < 0 || preGuardrailCloneIdx >= guardrailCheckIdx {
t.Fatal("expected the trusted ledger clone to complete before the daily AIC guardrail")
}
if !strings.Contains(lockStr, "GH_AW_ALLOW_INSECURE_REPO_MEMORY_AIC: ${{ vars.GH_AW_ALLOW_INSECURE_REPO_MEMORY_AIC || 'false' }}") {
t.Fatal("expected repo-memory backend to require explicit insecure opt-in")
}
Expand Down
Loading