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
9 changes: 6 additions & 3 deletions .github/workflows/ci.yml
Original file line number Diff line number Diff line change
Expand Up @@ -495,15 +495,18 @@ jobs:
serve console-other 8083 https://other.example:8443/fruitback/

policy() { curl -s -D - -o /dev/null "http://127.0.0.1:$1/w/any/sites" | tr -d '\r' | grep -i '^content-security-policy:'; }
# WARNING: never `! grep`. Under `bash -e` a command that `!` inverts does not stop the script,
# so the step stayed green with the very thing it looks for. `absent` exits by itself.
absent() { if grep -q -- "$1" "$2"; then echo "found « $1 » in $2, where it must not be"; exit 1; fi; }
policy 8082 | tee /tmp/acme
policy 8083 | tee /tmp/other
grep -q "connect-src 'self' https://api.acme.example;" /tmp/acme
grep -q "connect-src 'self' https://other.example:8443;" /tmp/other
! grep -q "other.example" /tmp/acme
! grep -q "acme.example" /tmp/other
absent "other.example" /tmp/acme
absent "acme.example" /tmp/other
for file in /tmp/acme /tmp/other; do
grep -q "'sha256-" "$file"
! grep -q "script-src[^;]*unsafe-inline" "$file"
absent "script-src[^;]*unsafe-inline" "$file"
grep -q "frame-ancestors 'none'" "$file"
done
test "$(curl -s http://127.0.0.1:8082/config.js)" = 'globalThis.__FRUITBACK__ = { api: "https://api.acme.example" };'
Expand Down
6 changes: 5 additions & 1 deletion .github/workflows/release-image.yml
Original file line number Diff line number Diff line change
Expand Up @@ -322,7 +322,11 @@ jobs:
echo "$policy"
echo "$policy" | grep -q "connect-src 'self' https://api.acme.example;"
echo "$policy" | grep -q "'sha256-"
! echo "$policy" | grep -q "script-src[^;]*unsafe-inline"
# Never `! grep`: under `bash -e` a command that `!` inverts does not stop the script.
if echo "$policy" | grep -q "script-src[^;]*unsafe-inline"; then
echo "the policy allows inline scripts"
exit 1
fi
test "$(curl -s http://127.0.0.1:8082/config.js)" = 'globalThis.__FRUITBACK__ = { api: "https://api.acme.example" };'
test "$(docker exec serves id -u)" = 101
docker rm -f serves
Expand Down
17 changes: 17 additions & 0 deletions apps/worker/src/workflows.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -438,6 +438,23 @@ describe('the GitHub workflows', () => {
);
});

/**
* A check that looks for an absence must be able to fail (FRU-222). GitHub runs a step with
* `bash -e`, and bash does not stop on a command that `!` inverts: `! grep -q unsafe-inline` left
* the step green with `unsafe-inline` in the policy. The form that stops is
* `if grep …; then exit 1; fi`. Raised in review.
*/
it('negate no command with `!` in a step: under `bash -e` it never fails the step', () => {
const negated = /^\s+! \S.*$/gm;
// The control: the form that was there is found.
assert.deepEqual(' run: |\n ! grep -q unsafe-inline /tmp/policy\n'.match(negated)?.length, 1);

assert.deepEqual(
workflows().flatMap(({ file, source }) => (source.match(negated) ?? []).map((line) => `${file}: ${line.trim()}`)),
[],
);
});

it('publish nothing from a commit that did not pass CI', () => {
const files = workflows();
const writers = files.flatMap(({ source }) => [...source.matchAll(/^ {6}\w[\w-]*: write$/gm)]);
Expand Down
4 changes: 4 additions & 0 deletions docs/decisions/image.md
Original file line number Diff line number Diff line change
Expand Up @@ -52,6 +52,10 @@ immutable.
`packages: write`.
`.github/dependabot.yml` moves an existing pin, SHA and comment together. It does not pin a new step:
`workflows.test.ts` fails on any `uses:` that is not a SHA followed by its version.
- **No step negates a command with `!`** (FRU-222). GitHub runs a step with `bash -e`, and bash does
not stop on a command that `!` inverts: `! grep -q unsafe-inline` left the step green with
`unsafe-inline` in the policy of the console. A check of an absence is written
`if grep …; then exit 1; fi`, and `workflows.test.ts` fails on the other form.
- **`persist-credentials: false` on every checkout** — `actions/checkout` otherwise writes the token
into `.git/config`, where any later step reads it.
- **Only the `publish` job holds the write permissions** (FRU-70). The workflow grants
Expand Down
Loading