Compare commits
1 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
| b068dc7698 |
@@ -0,0 +1,7 @@
|
||||
---
|
||||
description: Workflow and container YAML stays declarative; operational shell belongs in separate scripts
|
||||
---
|
||||
|
||||
Do not embed multiline operational scripts in workflow, Compose, or container
|
||||
YAML. Keep YAML declarative and move shell logic into a small, named `.sh` file
|
||||
that can be tested directly.
|
||||
+1
-8
@@ -27,14 +27,7 @@ runs:
|
||||
env:
|
||||
ACTION_PATH: ${{ gitea.action_path }}
|
||||
RUNNER_TEMP: ${{ runner.temp }}
|
||||
run: |
|
||||
path=$(mktemp -d "${RUNNER_TEMP}/bot-agents-source-XXXXXX")
|
||||
mkdir "${path}/src"
|
||||
cp -a "${ACTION_PATH}"/src/*.ts "${path}/src/"
|
||||
cp -a "${ACTION_PATH}/profiles.yml" "${ACTION_PATH}/prompt.md" \
|
||||
"${path}/"
|
||||
chmod -R go-w "${path}"
|
||||
echo "path=${path}" >> "${GITHUB_OUTPUT}"
|
||||
run: bash "${{ gitea.action_path }}/scripts/protect-source.sh"
|
||||
- name: Allow isolated profiles to edit the checkout
|
||||
shell: bash
|
||||
run: chmod -R a+rwX "${GITHUB_WORKSPACE}"
|
||||
|
||||
Executable
+9
@@ -0,0 +1,9 @@
|
||||
#!/usr/bin/env bash
|
||||
set -euo pipefail
|
||||
|
||||
path=$(mktemp -d "${RUNNER_TEMP}/bot-agents-source-XXXXXX")
|
||||
mkdir "${path}/src"
|
||||
cp -a "${ACTION_PATH}"/src/*.ts "${path}/src/"
|
||||
cp -a "${ACTION_PATH}/profiles.yml" "${ACTION_PATH}/prompt.md" "${path}/"
|
||||
chmod -R go-w "${path}"
|
||||
printf 'path=%s\n' "${path}" >> "${GITHUB_OUTPUT}"
|
||||
@@ -41,6 +41,11 @@ Deno.test("the action exposes only the master key and scopes it to preparation",
|
||||
const action = await yaml("action.yml");
|
||||
assertEquals(Object.keys(mapping(action.inputs, "inputs")), ["master-key"]);
|
||||
const steps = sequence(mapping(action.runs, "runs").steps, "runs.steps");
|
||||
for (const step of steps) {
|
||||
if (typeof step.run === "string") {
|
||||
assertEquals(step.run.includes("\n"), false, String(step.name));
|
||||
}
|
||||
}
|
||||
const checkout = steps.find((step) => step.uses === "actions/checkout@v4");
|
||||
const prepare = steps.find((step) => step.id === "prepare");
|
||||
const agent = steps.find((step) => step.id === "agent");
|
||||
@@ -67,6 +72,40 @@ Deno.test("the action exposes only the master key and scopes it to preparation",
|
||||
}
|
||||
});
|
||||
|
||||
Deno.test("the source protection script stages immutable action files", async () => {
|
||||
const temp = await Deno.makeTempDir();
|
||||
const outputFile = `${temp}/output`;
|
||||
try {
|
||||
const output = await new Deno.Command("/bin/bash", {
|
||||
args: ["scripts/protect-source.sh"],
|
||||
env: {
|
||||
ACTION_PATH: Deno.cwd(),
|
||||
GITHUB_OUTPUT: outputFile,
|
||||
RUNNER_TEMP: temp,
|
||||
},
|
||||
stderr: "piped",
|
||||
}).output();
|
||||
assert(output.success, new TextDecoder().decode(output.stderr));
|
||||
|
||||
const path = (await Deno.readTextFile(outputFile)).trim().slice(5);
|
||||
for (
|
||||
const file of [
|
||||
"src/prepare.ts",
|
||||
"src/run.ts",
|
||||
"src/post.ts",
|
||||
"profiles.yml",
|
||||
"prompt.md",
|
||||
]
|
||||
) {
|
||||
const stat = await Deno.stat(`${path}/${file}`);
|
||||
assert(stat.isFile, file);
|
||||
assertEquals(stat.mode! & 0o022, 0, file);
|
||||
}
|
||||
} finally {
|
||||
await Deno.remove(temp, { recursive: true });
|
||||
}
|
||||
});
|
||||
|
||||
Deno.test("the repository uses its trusted main action", async () => {
|
||||
const workflow = await yaml(".gitea/workflows/agents.yml");
|
||||
const job = mapping(mapping(workflow.jobs, "jobs").agent, "jobs.agent");
|
||||
|
||||
Reference in New Issue
Block a user