Skip to content

Commit 81b02b1

Browse files
committed
Harden repository security posture and CI supply chain
Add the governance and security files a public standards repository is expected to carry, and close two live weaknesses in the version-sync workflow. SECURITY.md publishes a vulnerability disclosure policy with scope, response SLAs, coordinated-disclosure timeline, and safe harbor. Private vulnerability reporting was already enabled but nothing pointed researchers at it. An issue-template config now routes security reports away from public issues. CODEOWNERS requires review from admins and maintainers, with the specification, CI, and licensing paths called out. All nine handles were verified to hold write access, since GitHub fails invalid entries silently. dependabot.yml adds version updates for GitHub Actions and uv, each with a seven-day cooldown. A compromised release is most dangerous in the hours after publication, and registry takeovers are usually yanked within days. Workflow hardening in sync_version.yml: - Pin all three actions to commit SHAs. Mutable tags meant a compromised tag would execute with contents:write. All three were also majors behind: checkout v4 to v7, setup-uv v4 to v9, create-pull-request v5 to v8. - Pin uv to 0.9.9 instead of resolving "latest" at run time. - Close a script-injection path. The contents of version.txt were interpolated into a shell command through ${{ }}. The value is now validated against a semver pattern and passed through the environment. - Default to permissions:{} at the workflow level and grant contents and pull-requests write only on the job that needs them. - Add a concurrency group and enable commit signing on generated PRs. Secret-scanning and gitleaks allowlists cover the signed agent-card examples in the A2A hook documentation. A full history scan across 250 commits found 12 findings, all from one commit, all documentation JWTs that authenticate nothing. Without the allowlists, push protection blocks anyone editing the A2A specification and contributors learn to click through the warning. Signed-off-by: rocklambros <rock@rockcyber.com>
1 parent 9c75cd4 commit 81b02b1

8 files changed

Lines changed: 296 additions & 41 deletions

File tree

.github/CODEOWNERS

Lines changed: 28 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,28 @@
1+
# Code owners for the Agent Control Standard.
2+
#
3+
# Owners listed here are required reviewers on the paths they own, enforced by
4+
# the ruleset on main. Everyone below has write access, which GitHub requires
5+
# for CODEOWNERS entries to take effect.
6+
#
7+
# Keep this file in sync with repository collaborator roles. A handle that
8+
# loses write access stops being a valid owner and GitHub fails the entry
9+
# silently.
10+
11+
# Default owners for anything not matched below.
12+
* @rocklambros @fewdisc @GangGreenTemperTatum @mamicidal @sclintonowasp @afogel @stefanoamorelli @almogbhl @bar-capsule
13+
14+
# The specification is the highest-blast-radius surface in the repository.
15+
# A schema change propagates to every downstream implementer.
16+
/specification/ @rocklambros @fewdisc @GangGreenTemperTatum @mamicidal @sclintonowasp @afogel @stefanoamorelli @almogbhl @bar-capsule
17+
/docs/spec/ @rocklambros @fewdisc @GangGreenTemperTatum @mamicidal @sclintonowasp @afogel @stefanoamorelli @almogbhl @bar-capsule
18+
19+
# CI runs with write access to the repository. Changes here are a
20+
# privilege-escalation surface and warrant admin review.
21+
/.github/ @rocklambros @fewdisc @GangGreenTemperTatum @mamicidal @sclintonowasp
22+
23+
# Licensing and security policy changes are governance decisions.
24+
/LICENSE @rocklambros @fewdisc @GangGreenTemperTatum @mamicidal @sclintonowasp
25+
/LICENSE-DOCS @rocklambros @fewdisc @GangGreenTemperTatum @mamicidal @sclintonowasp
26+
/LICENSING.md @rocklambros @fewdisc @GangGreenTemperTatum @mamicidal @sclintonowasp
27+
/NOTICE @rocklambros @fewdisc @GangGreenTemperTatum @mamicidal @sclintonowasp
28+
/SECURITY.md @rocklambros @fewdisc @GangGreenTemperTatum @mamicidal @sclintonowasp

.github/ISSUE_TEMPLATE/config.yml

Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,16 @@
1+
# Routes people away from filing security reports as public issues, which is
2+
# the single most common way a coordinated disclosure gets blown.
3+
4+
blank_issues_enabled: true
5+
contact_links:
6+
- name: Report a security vulnerability
7+
url: https://github.com/GenAI-Security-Project/agent-control-standard/security/advisories/new
8+
about: Do not open a public issue. Report privately here. See SECURITY.md for scope, response times, and safe harbor.
9+
10+
- name: Propose a specification change
11+
url: https://github.com/GenAI-Security-Project/agent-control-standard/discussions
12+
about: Schema, hook, and event changes affect every downstream implementer. Start a Discussion before opening a PR.
13+
14+
- name: Ask a question
15+
url: https://github.com/GenAI-Security-Project/agent-control-standard/discussions
16+
about: Questions about using or implementing ACS belong in Discussions.

.github/dependabot.yml

Lines changed: 56 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,56 @@
1+
# Dependabot version updates.
2+
#
3+
# Security updates are enabled separately in repository settings and fire on
4+
# advisories regardless of this file. This config covers routine version
5+
# drift, so a dependency is current before it becomes a CVE.
6+
#
7+
# Every ecosystem sets a cooldown. A compromised release is most dangerous in
8+
# the hours after publication, and registry takeovers are usually caught and
9+
# yanked within days. Waiting before proposing a bump costs nothing here and
10+
# keeps a poisoned version from landing in a PR that looks routine.
11+
12+
version: 2
13+
updates:
14+
# GitHub Actions run with write access to the repository. Actions are pinned
15+
# to commit SHAs in the workflows, and Dependabot understands SHA pins and
16+
# updates them along with the version comment.
17+
- package-ecosystem: "github-actions"
18+
directory: "/"
19+
schedule:
20+
interval: "weekly"
21+
day: "monday"
22+
cooldown:
23+
default-days: 7
24+
open-pull-requests-limit: 5
25+
commit-message:
26+
prefix: "ci"
27+
labels:
28+
- "dependencies"
29+
- "github-actions"
30+
31+
# Python dependencies resolved through uv.lock. The only runtime surface is
32+
# the documentation build and the version-sync script.
33+
- package-ecosystem: "uv"
34+
directory: "/"
35+
schedule:
36+
interval: "weekly"
37+
day: "monday"
38+
cooldown:
39+
default-days: 7
40+
open-pull-requests-limit: 5
41+
commit-message:
42+
prefix: "deps"
43+
labels:
44+
- "dependencies"
45+
- "python"
46+
groups:
47+
# Patch and minor bumps for the docs toolchain land as one PR. Majors
48+
# stay separate, since mkdocs-material majors have broken builds before.
49+
docs-toolchain:
50+
patterns:
51+
- "mkdocs*"
52+
- "mike"
53+
- "pymdown-extensions"
54+
update-types:
55+
- "minor"
56+
- "patch"

.github/pull_request_template.md

Lines changed: 34 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,34 @@
1+
## What changed
2+
3+
<!-- One or two sentences. What does this PR do and why? -->
4+
5+
## Type of change
6+
7+
- [ ] Specification change (schema, hooks, events, AgBOM)
8+
- [ ] Documentation
9+
- [ ] Tooling or CI
10+
- [ ] Governance (licensing, security policy, contributor docs)
11+
12+
## Specification changes
13+
14+
<!-- Delete this section if you touched nothing under specification/ or docs/spec/. -->
15+
16+
- [ ] I opened a [Discussion](https://github.com/GenAI-Security-Project/agent-control-standard/discussions) before this PR
17+
- [ ] Schema changes validate against the JSON Schema spec
18+
- [ ] I described the impact on downstream implementers below
19+
20+
**Breaking for implementers?** <!-- yes or no, and what breaks -->
21+
22+
## Checklist
23+
24+
- [ ] Commits are signed off with `git commit -s` (required by the DCO)
25+
- [ ] Prose follows [STYLE.md](../STYLE.md)
26+
- [ ] `uv run mkdocs build --strict` passes
27+
- [ ] No secrets, tokens, or internal URLs in the diff
28+
29+
## Security
30+
31+
- [ ] This change has no security impact
32+
33+
<!-- If it does, describe it. Do not open a PR for an unreported vulnerability.
34+
Report it privately first: see SECURITY.md. -->

.github/secret_scanning.yml

Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,16 @@
1+
# Paths excluded from GitHub secret scanning.
2+
#
3+
# The A2A hook documentation contains signed agent-card examples. Those JWTs
4+
# illustrate the JWS structure that the specification requires. They are not
5+
# credentials, they authenticate nothing, and their payloads decode to sample
6+
# agent metadata such as {"name": "Content generator", "provider": ...}.
7+
#
8+
# Without this exclusion, secret scanning flags twelve of them and push
9+
# protection blocks anyone editing the A2A specification. Contributors then
10+
# learn to click through the warning, which is worse than having no push
11+
# protection at all.
12+
#
13+
# Scope this narrowly. Do not add paths that hold real configuration.
14+
15+
paths-ignore:
16+
- "docs/spec/instrument/a2a/hooks/**"

.github/workflows/sync_version.yml

Lines changed: 53 additions & 41 deletions
Original file line numberDiff line numberDiff line change
@@ -1,82 +1,94 @@
1-
# This workflow synchronizes the version from the version.txt file to all project files
2-
# It can be triggered manually or on changes to the version.txt file
1+
# Synchronizes the version in version.txt across the project files.
2+
# Triggers on a push to version.txt on main, or manually.
33
name: Sync Version
44

55
on:
6-
# Trigger on changes to version.txt file
76
push:
87
paths:
98
- 'version.txt'
109
branches: ["main"]
11-
12-
# Allow manual trigger
1310
workflow_dispatch:
1411

15-
permissions:
16-
contents: write
17-
pull-requests: write
12+
# Deny by default. The one job below grants itself only what it needs.
13+
permissions: {}
14+
15+
# A second push to version.txt while a run is in flight would race the first
16+
# on the same branch name.
17+
concurrency:
18+
group: sync-version-${{ github.ref }}
19+
cancel-in-progress: false
1820

1921
jobs:
2022
sync-version:
2123
runs-on: ubuntu-latest
24+
permissions:
25+
contents: write # create the version-sync branch
26+
pull-requests: write # open the PR
2227
steps:
23-
- uses: actions/checkout@v4
24-
with:
25-
token: ${{ secrets.GITHUB_TOKEN }}
26-
28+
- name: Check out the repository
29+
uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1
30+
2731
- name: Install uv
28-
uses: astral-sh/setup-uv@v4
32+
uses: astral-sh/setup-uv@c771a70e6277c0a99b617c7a806ffedaca235ff9 # v9.0.0
2933
with:
30-
version: "latest"
31-
32-
- name: Configure Git
33-
run: |
34-
git config --global user.name "github-actions[bot]"
35-
git config --global user.email "github-actions[bot]@users.noreply.github.com"
36-
37-
- name: Read version from version.txt file
34+
version: "0.9.9"
35+
36+
- name: Read and validate version
3837
id: version
3938
run: |
40-
VERSION=$(cat version.txt)
41-
echo "VERSION=$VERSION" >> $GITHUB_ENV
42-
echo "version=$VERSION" >> $GITHUB_OUTPUT
43-
echo "Current version: $VERSION"
44-
39+
set -euo pipefail
40+
VERSION="$(tr -d '[:space:]' < version.txt)"
41+
# version.txt is attacker-controlled by anyone with write access.
42+
# Anything not matching semver stops here rather than reaching a
43+
# shell, a git ref, or PR body markdown downstream.
44+
if ! printf '%s' "$VERSION" | grep -Eq '^[0-9]+\.[0-9]+\.[0-9]+([-+][0-9A-Za-z.-]+)?$'; then
45+
echo "::error file=version.txt::Not a valid semantic version"
46+
exit 1
47+
fi
48+
echo "version=${VERSION}" >> "$GITHUB_OUTPUT"
49+
echo "Validated version: ${VERSION}"
50+
4551
- name: Sync versions across files
52+
env:
53+
# Passed through the environment, never interpolated into the shell
54+
# command. A ${{ }} expansion inside run: would execute as code.
55+
VERSION: ${{ steps.version.outputs.version }}
4656
run: |
47-
uv run .github/workflows/sync_version.py "${{ env.VERSION }}"
48-
57+
set -euo pipefail
58+
uv run .github/workflows/sync_version.py "$VERSION"
59+
4960
- name: Check for changes
5061
id: changes
5162
run: |
63+
set -euo pipefail
5264
if git diff --quiet; then
53-
echo "has_changes=false" >> $GITHUB_OUTPUT
65+
echo "has_changes=false" >> "$GITHUB_OUTPUT"
5466
echo "No changes detected"
5567
else
56-
echo "has_changes=true" >> $GITHUB_OUTPUT
68+
echo "has_changes=true" >> "$GITHUB_OUTPUT"
5769
echo "Changes detected"
5870
fi
59-
60-
- name: Create Pull Request
71+
72+
- name: Create pull request
6173
if: steps.changes.outputs.has_changes == 'true'
62-
uses: peter-evans/create-pull-request@v5
74+
uses: peter-evans/create-pull-request@5f6978faf089d4d20b00c7766989d076bb2fc7f1 # v8.1.1
6375
with:
6476
token: ${{ secrets.GITHUB_TOKEN }}
65-
commit-message: "Automated: sync version to ${{ env.VERSION }}"
66-
title: "Sync version to ${{ env.VERSION }}"
77+
commit-message: "Automated: sync version to ${{ steps.version.outputs.version }}"
78+
title: "Sync version to ${{ steps.version.outputs.version }}"
6779
body: |
68-
This PR synchronizes the version across all project files to `${{ env.VERSION }}`.
69-
80+
Synchronizes the version across project files to `${{ steps.version.outputs.version }}`.
81+
7082
Updated files:
7183
- `pyproject.toml`
7284
- `docs/spec/instrument/specification.md`
7385
- `specification/ACS/acs_schema.json`
74-
75-
The version was read from the `version.txt` file.
76-
branch: version-sync-${{ env.VERSION }}
86+
87+
The version was read from `version.txt` and validated as semver.
88+
branch: version-sync-${{ steps.version.outputs.version }}
7789
delete-branch: true
90+
sign-commits: true
7891
add-paths: |
7992
pyproject.toml
8093
docs/spec/instrument/specification.md
8194
specification/ACS/acs_schema.json
82-

.gitleaksignore

Lines changed: 27 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,27 @@
1+
# Gitleaks allowlist.
2+
#
3+
# Every entry below is a signed agent-card example in the A2A hook
4+
# documentation. The specification requires a JWS structure, so the examples
5+
# have to show a real-shaped JWT. These authenticate nothing. Payloads decode
6+
# to sample metadata such as:
7+
# {"name": "Content generator", "provider": {"name": "OpenAI", ...}}
8+
#
9+
# Verified 2026-08-11 across all 250 commits: 12 findings, all from commit
10+
# d24eab0, all rule "jwt", all documentation. No real credentials in history.
11+
#
12+
# Add an entry only after decoding the token and confirming it is an example.
13+
# A fingerprint is commit:path:rule:line, so an entry stops matching if the
14+
# content moves. That is intentional. Moved content gets re-reviewed.
15+
16+
d24eab0343460c27d9622910250b027b80fca9bd:docs/spec/instrument/a2a/hooks/cancel_task_request.md:jwt:64
17+
d24eab0343460c27d9622910250b027b80fca9bd:docs/spec/instrument/a2a/hooks/cancel_task_request.md:jwt:151
18+
d24eab0343460c27d9622910250b027b80fca9bd:docs/spec/instrument/a2a/hooks/get_task_push_notification_config_request.md:jwt:63
19+
d24eab0343460c27d9622910250b027b80fca9bd:docs/spec/instrument/a2a/hooks/get_task_push_notification_config_request.md:jwt:149
20+
d24eab0343460c27d9622910250b027b80fca9bd:docs/spec/instrument/a2a/hooks/get_task_request.md:jwt:68
21+
d24eab0343460c27d9622910250b027b80fca9bd:docs/spec/instrument/a2a/hooks/get_task_request.md:jwt:159
22+
d24eab0343460c27d9622910250b027b80fca9bd:docs/spec/instrument/a2a/hooks/resubscribe_to_task_request.md:jwt:64
23+
d24eab0343460c27d9622910250b027b80fca9bd:docs/spec/instrument/a2a/hooks/resubscribe_to_task_request.md:jwt:151
24+
d24eab0343460c27d9622910250b027b80fca9bd:docs/spec/instrument/a2a/hooks/set_task_push_notification_config_request.md:jwt:71
25+
d24eab0343460c27d9622910250b027b80fca9bd:docs/spec/instrument/a2a/hooks/set_task_push_notification_config_request.md:jwt:165
26+
d24eab0343460c27d9622910250b027b80fca9bd:docs/spec/instrument/a2a/hooks/stream_message_request.md:jwt:110
27+
d24eab0343460c27d9622910250b027b80fca9bd:docs/spec/instrument/a2a/hooks/stream_message_request.md:jwt:243

SECURITY.md

Lines changed: 66 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,66 @@
1+
# Security Policy
2+
3+
ACS is a specification. Most of what lives here is prose and JSON Schema, so the security surface is narrower than a typical software project. Report anything you find anyway. A flaw in the specification propagates into every implementation that follows it.
4+
5+
## Report a vulnerability
6+
7+
**Do not open a public issue.**
8+
9+
Use GitHub's [private vulnerability reporting](https://github.com/GenAI-Security-Project/agent-control-standard/security/advisories/new). Reports land with the maintainers and stay private until we publish an advisory together.
10+
11+
Include what you have:
12+
13+
- What you found and where, with a file path or a link to the specific line
14+
- How to reproduce it, or the reasoning chain if the flaw is in the specification rather than in code
15+
- What an attacker gains
16+
- Any suggested fix
17+
18+
Partial reports are welcome. We would rather triage something incomplete than never hear about it.
19+
20+
## What is in scope
21+
22+
| In scope | Out of scope |
23+
| --- | --- |
24+
| Flaws in the ACS specification that lead implementers into insecure designs | The documentation site at agentcontrolstandard.ai, which is built from a separate repository |
25+
| Errors in the JSON Schemas under `specification/` | Findings against third-party agent frameworks that happen to implement ACS |
26+
| The GitHub Actions workflows in `.github/workflows/` | Automated scanner output with no demonstrated impact |
27+
| Hook or event definitions that leak sensitive data by design | Missing security headers on sites we do not operate |
28+
| Supply-chain issues in this repository's dependencies | Social engineering of maintainers or contributors |
29+
30+
A specification flaw counts. If a hook definition forces implementers to log secrets, or an event schema makes an authorization bypass easy to write, that is a finding even though no code here executes.
31+
32+
## Response commitments
33+
34+
| Stage | Target |
35+
| --- | --- |
36+
| Acknowledge your report | 3 business days |
37+
| Initial triage and severity assessment | 10 business days |
38+
| Fix or documented mitigation for high and critical findings | 90 days from triage |
39+
| Public advisory | Coordinated with you, at or before day 90 |
40+
41+
We use [CVSS v3.1](https://www.first.org/cvss/calculator/3.1) for severity. Specification flaws get scored against a reference implementation, since the specification itself has no runtime.
42+
43+
## Coordinated disclosure
44+
45+
We publish an advisory once a fix ships, or at 90 days from triage, whichever comes first. If a fix needs longer, we tell you why and agree a new date rather than letting the clock run out quietly.
46+
47+
Tell us how you want to be credited, including if you prefer not to be. We credit reporters in the advisory by default.
48+
49+
If you believe a finding is being actively exploited, say so in the report. That moves it to the front of the queue and shortens every window above.
50+
51+
## Safe harbor
52+
53+
We will not pursue or support legal action against research conducted under this policy, provided you:
54+
55+
- Report through the private channel above and give us a chance to fix the issue before disclosing it
56+
- Avoid privacy violations, data destruction, and interruption of any service
57+
- Access only the minimum data needed to demonstrate the finding, and delete it once you have reported
58+
- Do not exploit a finding beyond what proving it requires
59+
60+
Work in good faith under these terms and we treat your research as authorized. If a third party brings action against you for research that followed this policy, we will make that authorization clear.
61+
62+
## Signing and provenance
63+
64+
Contributors sign off commits under the Developer Certificate of Origin. See [CONTRIBUTING.md](./CONTRIBUTING.md).
65+
66+
Report suspected compromise of a maintainer account or a release artifact through the private channel above, marked urgent.

0 commit comments

Comments
 (0)