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
6 changes: 6 additions & 0 deletions .github/dependabot.yml
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,12 @@ updates:
interval: weekly
cooldown:
default-days: 7
ignore:
# @cloudflare/vitest-plugin declares a peer of vitest ^4. A major Vitest
# bump cannot install until the plugin supports it; the plugin's own
# update will say when.
- dependency-name: vitest
update-types: ["version-update:semver-major"]
groups:
# Wrangler, the Vitest plugin, workers-types and Vitest move together;
# updating one alone is how the lockfile ended up unresolvable before.
Expand Down
4 changes: 3 additions & 1 deletion .github/workflows/scpe-seal.yml
Original file line number Diff line number Diff line change
Expand Up @@ -40,7 +40,9 @@ jobs:
name: seal
# A failed verify is usually a missing disclosure, which is exactly when the
# explanation is needed. If verify failed before uploading, the download fails.
if: ${{ github.event.workflow_run.conclusion == 'success' || github.event.workflow_run.conclusion == 'failure' }}
# Dependabot is exempt from the disclosure in scpe.yml, so there is nothing to
# post on its pull requests.
if: ${{ (github.event.workflow_run.conclusion == 'success' || github.event.workflow_run.conclusion == 'failure') && github.event.workflow_run.actor.login != 'dependabot[bot]' }}
runs-on: ubuntu-latest
timeout-minutes: 5
# A job-level permissions block REPLACES the workflow default (it does not merge),
Expand Down
2 changes: 2 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -57,6 +57,8 @@ What "public API" means for devcard, since it is not a library:
pull requests, and CodeQL for TypeScript, Python and the workflows. Dependabot
version updates for npm and Actions, with a seven-day cooldown.
- npm dependency install scripts are disabled for the Worker toolchain.
- The capture hook's `git commit` detector no longer backtracks exponentially on
repeated flags (CodeQL `py/redos`); a crafted command could stall the hook.

### Added

Expand Down
5 changes: 4 additions & 1 deletion hook/devcard_lib.py
Original file line number Diff line number Diff line change
Expand Up @@ -254,7 +254,10 @@ def count_lines(text):
_SHELL_SPLIT = re.compile(r"&&|\|\||[;\n|]")
# A segment that actually *invokes* `git commit`, tolerating a path-qualified
# binary and leading flags (`git -C /repo commit`, `git --no-pager commit`).
_GIT_COMMIT = re.compile(r"^\s*(?:\S*[/\\])?git(?:\.exe)?\s+(?:-\S+\s+\S+\s+|-\S+\s+)*commit\b")
# A flag's value may not itself start with `-`. Without that rule the flag group
# could split `-a -b` two ways, and a command like `git -x -x -x … x` backtracked
# exponentially — enough to stall the capture hook (CodeQL py/redos).
_GIT_COMMIT = re.compile(r"^\s*(?:\S*[/\\])?git(?:\.exe)?\s+(?:-\S+\s+(?:[^-\s]\S*\s+)?)*commit\b")


def counts_as_commit(command):
Expand Down
13 changes: 13 additions & 0 deletions hook/test_devcard_lib.py
Original file line number Diff line number Diff line change
@@ -1,5 +1,7 @@
import os
import shutil
import subprocess
import sys
import tempfile
import unittest
from unittest import mock
Expand Down Expand Up @@ -146,6 +148,17 @@ def test_unrelated_command(self):
self.assertFalse(lib.counts_as_commit("git status"))
self.assertFalse(lib.counts_as_commit(""))

def test_a_pathological_command_cannot_stall_the_hook(self):
# The flag group used to backtrack exponentially on repeated flags. Run
# in a child process so a regression fails on the timeout instead of
# hanging the whole suite.
code = "import devcard_lib as lib; print(lib.counts_as_commit('git ' + '-! ' * 200 + 'x'))"
out = subprocess.run(
[sys.executable, "-S", "-c", code], cwd=os.path.dirname(os.path.abspath(__file__)),
capture_output=True, text=True, timeout=10,
)
self.assertEqual(out.stdout.strip(), "False", out.stderr)


class TestToolFailed(unittest.TestCase):
def test_missing_response_is_not_a_failure(self):
Expand Down