Skip to content
Open
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
15 changes: 8 additions & 7 deletions .github/workflows/add-community-bundle.lock.yml

Large diffs are not rendered by default.

16 changes: 15 additions & 1 deletion .github/workflows/add-community-bundle.md
Original file line number Diff line number Diff line change
Expand Up @@ -10,12 +10,19 @@ on:

tools:
edit:
bash: ["echo", "grep", "sort", "python3", "jq", "date"]
bash: ["echo", "grep", "sort", "python3", "jq", "date", "curl"]
github:
toolsets: [issues, repos]
min-integrity: none
web-fetch:

network:
allowed:
- defaults
- github.com
- codeload.github.com
- release-assets.githubusercontent.com

permissions:
contents: read
issues: read
Expand Down Expand Up @@ -143,6 +150,13 @@ Run every check and collect all failures before deciding the outcome.
- Confirm the asset name is versioned and consistent with the submitted bundle
ID and version.

Use `curl` for binary downloads, follow HTTPS redirects with
`--location --proto '=https' --proto-redir '=https'`, and bound the request with
`--max-time 60`. Save the archive under `/tmp/gh-aw/` and inspect the final
HTTP status with `--write-out '%{http_code}'`. A blocked or failed download
must not count as a passed check; repository/release metadata is not a
substitute for fetching the archive. Never execute downloaded content.

Do not fetch arbitrary user-provided URLs. Do not claim the artifact was
executed or audited; rely on the required submission attestations for build and
installation evidence.
Expand Down
15 changes: 8 additions & 7 deletions .github/workflows/add-community-extension.lock.yml

Large diffs are not rendered by default.

16 changes: 15 additions & 1 deletion .github/workflows/add-community-extension.md
Original file line number Diff line number Diff line change
Expand Up @@ -10,12 +10,19 @@ on:

tools:
edit:
bash: ["echo", "cat", "head", "tail", "grep", "wc", "sort", "python3", "jq", "date"]
bash: ["echo", "cat", "head", "tail", "grep", "wc", "sort", "python3", "jq", "date", "curl"]
github:
toolsets: [issues, repos]
min-integrity: none
web-fetch:

network:
allowed:
- defaults
- github.com
- codeload.github.com
- release-assets.githubusercontent.com

permissions:
contents: read
issues: read
Expand Down Expand Up @@ -135,6 +142,13 @@ deciding pass/fail:
check when the field is absent.
- Verify a GitHub release exists for that tag.

Use `curl` for binary downloads, follow HTTPS redirects with
`--location --proto '=https' --proto-redir '=https'`, and bound the request with
`--max-time 60`. Save the archive under `/tmp/gh-aw/` and inspect the final
HTTP status with `--write-out '%{http_code}'`. A blocked or failed download
must not count as a passed check; repository/release metadata is not a
substitute for fetching the archive. Never execute downloaded content.

### 2e. Submission checklists
- Confirm that all required checkboxes in the Testing Checklist and Submission
Requirements sections are checked (`[x]`)
Expand Down
15 changes: 8 additions & 7 deletions .github/workflows/add-community-preset.lock.yml

Large diffs are not rendered by default.

16 changes: 15 additions & 1 deletion .github/workflows/add-community-preset.md
Original file line number Diff line number Diff line change
Expand Up @@ -10,12 +10,19 @@ on:

tools:
edit:
bash: ["echo", "cat", "head", "tail", "grep", "wc", "sort", "python3", "jq", "date"]
bash: ["echo", "cat", "head", "tail", "grep", "wc", "sort", "python3", "jq", "date", "curl"]
github:
toolsets: [issues, repos]
min-integrity: none
web-fetch:

network:
allowed:
- defaults
- github.com
- codeload.github.com
- release-assets.githubusercontent.com

permissions:
contents: read
issues: read
Expand Down Expand Up @@ -186,6 +193,13 @@ preset** — not just any file named `README.md`, and not a product/framework pi
check when the field is absent.
- Verify a GitHub release exists for that tag.

Use `curl` for binary downloads, follow HTTPS redirects with
`--location --proto '=https' --proto-redir '=https'`, and bound the request with
`--max-time 60`. Save the archive under `/tmp/gh-aw/` and inspect the final
HTTP status with `--write-out '%{http_code}'`. A blocked or failed download
must not count as a passed check; repository/release metadata is not a
substitute for fetching the archive. Never execute downloaded content.

### 2f. Submission checklists
- Confirm that all required checkboxes in the Testing Checklist and Submission
Requirements sections are checked (`[x]`)
Expand Down
63 changes: 63 additions & 0 deletions tests/test_github_workflows.py
Original file line number Diff line number Diff line change
Expand Up @@ -286,6 +286,69 @@ def _frontmatter(source_text: str) -> dict:
return yaml.safe_load(frontmatter)


def _community_submission_agent_run(workflow: str) -> str:
compiled = WORKFLOWS_DIR / f"add-community-{workflow}.lock.yml"
steps = yaml.safe_load(compiled.read_text(encoding="utf-8"))["jobs"]["agent"][
"steps"
]
return next(
step["run"]
for step in steps
if step["name"] == "Execute GitHub Copilot CLI"
)


def test_community_submission_archive_fetch_tool_is_allowed():
"""Archive checks must not require an interactive curl permission grant."""
for workflow, *_ in COMMUNITY_SUBMISSION_WORKFLOWS:
source = WORKFLOWS_DIR / f"add-community-{workflow}.md"
bash_tools = _frontmatter(source.read_text(encoding="utf-8"))["tools"]["bash"]

assert "curl" in bash_tools, f"{workflow} cannot fetch binary archives"
assert "*" not in bash_tools
agent_run = _community_submission_agent_run(workflow)
assert "shell(curl:*)" in agent_run
assert "--allow-all-tools" not in agent_run


def test_community_submission_archive_redirect_hosts_are_allowed():
"""Both accepted ZIP URL patterns must work through the restricted firewall."""
download_hosts = {
"github.com",
"codeload.github.com",
"release-assets.githubusercontent.com",
}
Comment on lines +316 to +320
for workflow, *_ in COMMUNITY_SUBMISSION_WORKFLOWS:
source = WORKFLOWS_DIR / f"add-community-{workflow}.md"
config = _frontmatter(source.read_text(encoding="utf-8"))

assert set(config.get("network", {}).get("allowed", [])) == {
"defaults",
*download_hosts,
}, f"{workflow} must allow only the required download hosts plus defaults"
agent_run = _community_submission_agent_run(workflow)
for host in download_hosts:
assert f'\\"{host}\\"' in agent_run


def test_community_submission_archive_fetch_requires_direct_evidence():
for workflow, *_ in COMMUNITY_SUBMISSION_WORKFLOWS:
source_text = (WORKFLOWS_DIR / f"add-community-{workflow}.md").read_text(
encoding="utf-8"
)

assert "Use `curl` for binary downloads" in source_text
assert "--proto '=https' --proto-redir '=https'" in source_text
assert "`--max-time 60`" in source_text
assert "`--write-out '%{http_code}'`" in source_text
assert (
"A blocked or failed download\n"
"must not count as a passed check; repository/release metadata is not a\n"
"substitute for fetching the archive."
) in source_text
assert "Never execute downloaded content." in source_text


def test_community_submission_threat_detection_is_fail_closed():
for workflow, *_ in COMMUNITY_SUBMISSION_WORKFLOWS:
source = WORKFLOWS_DIR / f"add-community-{workflow}.md"
Expand Down