Compare commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
7c69368443 | ||
|
|
40ecebae5c | ||
|
|
5c21fc7771 | ||
|
|
83134656fc | ||
|
|
11fe60b146 | ||
|
|
435049d688 | ||
|
|
f7262f97cb | ||
|
|
fdf5ed0296 | ||
|
|
08b5ac84a7 | ||
|
|
ff31f36ddb | ||
|
|
e375cf3b07 |
No files matched your search
@@ -0,0 +1,12 @@
|
||||
# Plain-language categories for native GitHub release notes.
|
||||
changelog:
|
||||
exclude:
|
||||
labels:
|
||||
- "autorelease: pending"
|
||||
- "autorelease: tagged"
|
||||
- "autorelease: failed"
|
||||
categories:
|
||||
- title: Breaking changes
|
||||
labels: [breaking]
|
||||
- title: Changes
|
||||
labels: ["*"]
|
||||
@@ -93,6 +93,16 @@ jobs:
|
||||
shell: pwsh
|
||||
run: ./tools/Test-RepoConformance.ps1 -SelfTest
|
||||
|
||||
- name: Conformance parser fuzzing
|
||||
shell: pwsh
|
||||
env:
|
||||
CI_EVENT_NAME: ${{ github.event_name }}
|
||||
CI_REF_TYPE: ${{ github.ref_type }}
|
||||
run: |
|
||||
$iterations = if ($env:CI_EVENT_NAME -eq 'schedule' -or $env:CI_REF_TYPE -eq 'tag') { 10000 } else { 1000 }
|
||||
./tools/Test-RepoConformance.Fuzz.ps1 -Seed 22023 -Iterations $iterations
|
||||
./tools/Test-RepoConformance.Fuzz.ps1 -Seed 99173 -Iterations $iterations
|
||||
|
||||
- name: Standards package tests
|
||||
shell: pwsh
|
||||
run: ./tools/Test-StandardsPackage.ps1
|
||||
@@ -125,7 +135,24 @@ jobs:
|
||||
permission-contents: write
|
||||
permission-pull-requests: write
|
||||
|
||||
# Version files merge before publication; wait for the publisher's label.
|
||||
- name: Check for unpublished merged releases
|
||||
id: pending
|
||||
env:
|
||||
GH_TOKEN: ${{ steps.app.outputs.token }}
|
||||
GH_REPO: ${{ github.repository }}
|
||||
run: |
|
||||
set -euo pipefail
|
||||
pending="$(gh api --paginate "repos/$GH_REPO/pulls?state=closed&per_page=100" --jq '.[] | select(.merged_at != null and any(.labels[]; .name == "autorelease: pending")) | .number')"
|
||||
if [[ -n "$pending" ]]; then
|
||||
echo 'can-propose=false' >> "$GITHUB_OUTPUT"
|
||||
echo "Waiting for publication of merged release PR(s): $pending"
|
||||
else
|
||||
echo 'can-propose=true' >> "$GITHUB_OUTPUT"
|
||||
fi
|
||||
|
||||
- name: Propose a semantic release
|
||||
if: steps.pending.outputs.can-propose == 'true'
|
||||
uses: googleapis/release-please-action@45996ed1f6d02564a971a2fa1b5860e934307cf7 # v5.0.0
|
||||
with:
|
||||
token: ${{ steps.app.outputs.token }}
|
||||
|
||||
@@ -52,6 +52,7 @@ jobs:
|
||||
- name: PR title is a Conventional Commit header
|
||||
env:
|
||||
PR_TITLE: ${{ github.event.pull_request.title }}
|
||||
PR_BODY: ${{ github.event.pull_request.body }}
|
||||
run: |
|
||||
# The title becomes the squash commit subject on main (K22-REPO-22), so it
|
||||
# must be a valid Conventional Commits 1.0 header of at most 100 characters.
|
||||
@@ -65,6 +66,15 @@ jobs:
|
||||
printf 'Title was: %s\n' "$PR_TITLE"
|
||||
exit 1
|
||||
fi
|
||||
node - <<'NODE'
|
||||
const emoji = /\p{Emoji_Presentation}|[\u2600-\u27BF]|\p{Extended_Pictographic}\uFE0F|[#*0-9]\uFE0F?\u20E3/u;
|
||||
for (const [field, text] of [['title', process.env.PR_TITLE], ['description', process.env.PR_BODY || '']]) {
|
||||
if (emoji.test(text)) {
|
||||
console.error(`::error title=Unprofessional pull request::The ${field} contains an emoji; use professional text.`);
|
||||
process.exit(1);
|
||||
}
|
||||
}
|
||||
NODE
|
||||
echo "PR title OK"
|
||||
|
||||
dco:
|
||||
|
||||
@@ -2,6 +2,10 @@ name: release-standards
|
||||
|
||||
on:
|
||||
workflow_call:
|
||||
secrets:
|
||||
RELEASE_APP_PRIVATE_KEY:
|
||||
description: App key resolved from the protected release environment.
|
||||
required: true
|
||||
|
||||
permissions: {}
|
||||
|
||||
@@ -51,11 +55,13 @@ jobs:
|
||||
}
|
||||
echo "pr=$pr" >> "$GITHUB_OUTPUT"
|
||||
# Preflight is approved before tagging; shipping checks follow publication.
|
||||
checklist=''
|
||||
annotation="$(git for-each-ref --format='%(contents)' "refs/tags/$TAG")"
|
||||
checklist="$(printf '%s\n' "$annotation" | sed -n 's/^Release checklist: //p' | head -n 1)"
|
||||
if [[ "$version" == *.0 ]]; then
|
||||
annotation="$(git for-each-ref --format='%(contents)' "refs/tags/$TAG")"
|
||||
[[ -n "$checklist" ]]
|
||||
fi
|
||||
if [[ -n "$checklist" ]]; then
|
||||
prefix="https://github.com/$GH_REPO/issues/"
|
||||
checklist="$(printf '%s\n' "$annotation" | grep -F 'Release checklist: ' | head -n 1 | sed 's/^Release checklist: //')"
|
||||
[[ "$checklist" == "$prefix"* ]]
|
||||
issue="${checklist#"$prefix"}"
|
||||
[[ "$issue" =~ ^[1-9][0-9]*$ ]]
|
||||
@@ -70,6 +76,7 @@ jobs:
|
||||
uses: ./.github/workflows/ci.yml
|
||||
permissions:
|
||||
contents: read
|
||||
pull-requests: read # required by ci.yml's nested PR caller even on tag events
|
||||
security-events: write
|
||||
|
||||
build:
|
||||
|
||||
@@ -336,9 +336,29 @@ jobs:
|
||||
tenant-id: ${{ vars.AZURE_TENANT_ID }}
|
||||
subscription-id: ${{ vars.AZURE_SUBSCRIPTION_ID }}
|
||||
|
||||
# Signs the app's own executables and DLLs (and the CLI) before packing, so the
|
||||
# binaries inside the installer, the portable zip, and the update packages are
|
||||
# all signed (K22-REL-05).
|
||||
# Signs the executables and DLLs under publish that carry no valid signature
|
||||
# yet (the app's own binaries and unsigned third-party ones), so the binaries
|
||||
# inside the installer, the portable zip and the update packages are all
|
||||
# signed (K22-REL-05). Already-signed files (the .NET runtime, Windows App SDK)
|
||||
# keep their publisher's signature and are not re-signed: a self-contained
|
||||
# publish holds ~300 of them at ~2 s each, which is how the first real run
|
||||
# (gclo v1.0.2-beta.2, #26) hit the signing action's 300 s default timeout.
|
||||
- name: Select the binaries to sign
|
||||
if: inputs.sign
|
||||
id: sign-list
|
||||
shell: pwsh
|
||||
env:
|
||||
K22_MAIN_EXE: ${{ inputs.main-exe }}
|
||||
K22_CLI_PROJECT: ${{ inputs.cli-project }}
|
||||
run: |
|
||||
$all = Get-ChildItem publish -Recurse -Include *.exe, *.dll
|
||||
$unsigned = @($all | Where-Object { (Get-AuthenticodeSignature -LiteralPath $_.FullName).Status -ne 'Valid' })
|
||||
$names = $unsigned | ForEach-Object { $_.Name }
|
||||
if ($names -notcontains $env:K22_MAIN_EXE) { throw "publish/app/$env:K22_MAIN_EXE is missing or already signed by someone else." }
|
||||
if ($env:K22_CLI_PROJECT -and -not ($unsigned | Where-Object { $_.Extension -eq '.exe' -and $_.FullName -like '*\publish\cli\*' })) { throw 'publish/cli has no unsigned exe to sign.' }
|
||||
Write-Output "Signing $($unsigned.Count) of $($all.Count) binaries; $($all.Count - $unsigned.Count) already carry a valid signature."
|
||||
"files<<EOF`n$(($unsigned.FullName) -join "`n")`nEOF" | Add-Content -LiteralPath $env:GITHUB_OUTPUT
|
||||
|
||||
- name: Sign published binaries (Azure Trusted Signing)
|
||||
if: inputs.sign
|
||||
uses: azure/artifact-signing-action@c7ab2a863ab5f9a846ddb8265964877ef296ee82 # v2.0.0
|
||||
@@ -346,12 +366,11 @@ jobs:
|
||||
endpoint: ${{ vars.TRUSTED_SIGNING_ENDPOINT }}
|
||||
signing-account-name: ${{ vars.TRUSTED_SIGNING_ACCOUNT }}
|
||||
certificate-profile-name: ${{ vars.TRUSTED_SIGNING_PROFILE }}
|
||||
files-folder: ${{ github.workspace }}\publish
|
||||
files-folder-filter: exe,dll
|
||||
files-folder-recurse: true
|
||||
files: ${{ steps.sign-list.outputs.files }}
|
||||
file-digest: SHA256
|
||||
timestamp-rfc3161: http://timestamp.acs.microsoft.com
|
||||
timestamp-digest: SHA256
|
||||
timeout: 1800
|
||||
cache-dependencies: false
|
||||
exclude-environment-credential: true
|
||||
exclude-workload-identity-credential: true
|
||||
@@ -363,9 +382,13 @@ jobs:
|
||||
exclude-azure-developer-cli-credential: true
|
||||
exclude-interactive-browser-credential: true
|
||||
|
||||
# vpk signs what it adds (Setup.exe and the update stub) with the same identity,
|
||||
# reading this metadata file and the Azure CLI login above.
|
||||
- name: Write signing metadata for vpk
|
||||
# vpk signs what it adds (Setup.exe, Update.exe, the execution stub) with the
|
||||
# same identity. It calls this wrapper once per file (--signTemplate) instead
|
||||
# of its built-in Azure path (--azureTrustedSignFile), which hung for over ten
|
||||
# minutes on nine files in testing; the wrapper uses the SignTool and the
|
||||
# Artifact Signing client the signing step above already installed, with the
|
||||
# Azure CLI login, and skips files that already carry a valid signature.
|
||||
- name: Write the signing wrapper for vpk
|
||||
if: inputs.sign
|
||||
shell: pwsh
|
||||
env:
|
||||
@@ -378,6 +401,18 @@ jobs:
|
||||
CodeSigningAccountName = $env:TRUSTED_SIGNING_ACCOUNT
|
||||
CertificateProfileName = $env:TRUSTED_SIGNING_PROFILE
|
||||
} | ConvertTo-Json | Set-Content -LiteralPath "$env:RUNNER_TEMP\trusted-signing.json" -Encoding utf8
|
||||
$signtool = Get-ChildItem "${env:ProgramFiles(x86)}\Windows Kits\10\bin" -Recurse -Filter signtool.exe |
|
||||
Where-Object { $_.FullName -match '\\x64\\' } | Sort-Object FullName -Descending | Select-Object -First 1
|
||||
$dlib = Get-ChildItem "$env:LOCALAPPDATA\ArtifactSigning" -Recurse -Filter Azure.CodeSigning.Dlib.dll |
|
||||
Where-Object { $_.FullName -match '\\x64\\' } | Sort-Object FullName -Descending | Select-Object -First 1
|
||||
if (-not $signtool -or -not $dlib) { throw "signtool.exe ($signtool) or Azure.CodeSigning.Dlib.dll ($dlib) not found." }
|
||||
@"
|
||||
param([Parameter(Mandatory)][string] `$File)
|
||||
`$ErrorActionPreference = 'Stop'
|
||||
if ((Get-AuthenticodeSignature -LiteralPath `$File).Status -eq 'Valid') { exit 0 }
|
||||
& '$($signtool.FullName)' sign /fd SHA256 /tr http://timestamp.acs.microsoft.com /td SHA256 /dlib '$($dlib.FullName)' /dmdf '$env:RUNNER_TEMP\trusted-signing.json' `$File | Out-Null
|
||||
if (`$LASTEXITCODE -ne 0) { throw "signtool failed (`$LASTEXITCODE) on `$File" }
|
||||
"@ | Set-Content -LiteralPath "$env:RUNNER_TEMP\sign-one.ps1" -Encoding utf8
|
||||
|
||||
# The vpk version matches the Velopack NuGet package the apps reference (AppKit).
|
||||
- name: Install vpk
|
||||
@@ -401,10 +436,30 @@ jobs:
|
||||
shell: pwsh
|
||||
run: |
|
||||
$sign = @()
|
||||
if ($env:K22_SIGN -eq 'true') { $sign = @('--azureTrustedSignFile', "$env:RUNNER_TEMP\trusted-signing.json") }
|
||||
if ($env:K22_SIGN -eq 'true') { $sign = @('--signTemplate', "pwsh -NoProfile -NonInteractive -File `"$env:RUNNER_TEMP\sign-one.ps1`" `"{{file}}`"") }
|
||||
vpk pack --packId $env:K22_APP_ID --packVersion $env:K22_VERSION --packDir publish/app --mainExe $env:K22_MAIN_EXE --channel $env:K22_CHANNEL --icon $env:K22_ICON --outputDir Releases @sign
|
||||
if ($LASTEXITCODE -ne 0) { exit $LASTEXITCODE }
|
||||
|
||||
# Nothing unsigned leaves this job: Setup.exe and every executable inside the
|
||||
# portable zip (the app, Update.exe, the execution stub) must verify before the
|
||||
# release is even drafted (K22-REL-05).
|
||||
- name: Verify signatures before packaging further
|
||||
if: inputs.sign
|
||||
shell: pwsh
|
||||
run: |
|
||||
$signtool = Get-ChildItem "${env:ProgramFiles(x86)}\Windows Kits\10\bin" -Recurse -Filter signtool.exe |
|
||||
Where-Object { $_.FullName -match '\\x64\\' } | Sort-Object FullName -Descending | Select-Object -First 1
|
||||
Expand-Archive "Releases/$env:K22_APP_ID-$env:K22_CHANNEL-Portable.zip" -DestinationPath "$env:RUNNER_TEMP\portable" -Force
|
||||
$files = @(Get-Item "Releases/$env:K22_APP_ID-$env:K22_CHANNEL-Setup.exe") + @(Get-ChildItem "$env:RUNNER_TEMP\portable" -Recurse -Include *.exe, *.dll)
|
||||
$failed = @()
|
||||
foreach ($f in $files)
|
||||
{
|
||||
& $signtool.FullName verify /pa /all $f.FullName | Out-Null
|
||||
if ($LASTEXITCODE -ne 0) { $failed += $f.FullName }
|
||||
}
|
||||
if ($failed) { Write-Output "::error::Unsigned or invalid after packing:`n$($failed -join "`n")"; exit 1 }
|
||||
Write-Output "All $($files.Count) packaged executables and libraries verify with signtool."
|
||||
|
||||
# Keeps only what this build produced, as `vpk upload` does: the files listed in
|
||||
# assets.<channel>.json, plus a releases.<channel>.json feed trimmed to this build's
|
||||
# packages (the local feed may also list the downloaded baseline).
|
||||
|
||||
@@ -15,6 +15,9 @@ jobs:
|
||||
release:
|
||||
if: startsWith(github.ref, 'refs/tags/v')
|
||||
uses: ./.github/workflows/release-standards.yml
|
||||
# Required even when the value exists only in the called job's environment.
|
||||
secrets:
|
||||
RELEASE_APP_PRIVATE_KEY: ${{ secrets.RELEASE_APP_PRIVATE_KEY }}
|
||||
permissions:
|
||||
contents: write
|
||||
issues: read
|
||||
|
||||
@@ -1 +1 @@
|
||||
{".":"0.1.0"}
|
||||
{".":"1.0.0"}
|
||||
+42
-3
@@ -1,9 +1,48 @@
|
||||
# Changelog
|
||||
|
||||
<!-- Generated by Release Please; permit its section spacing. -->
|
||||
<!-- markdownlint-disable MD012 -->
|
||||
<!-- Generated by Release Please and GitHub; permit native spacing, links and repeated categories. -->
|
||||
<!-- markdownlint-disable MD012 MD022 MD024 MD032 MD034 -->
|
||||
|
||||
## 0.1.0 (2026-10-10)
|
||||
## 1.0.0 (2026-10-10)
|
||||
|
||||
<!-- Release notes generated using configuration in .github/release.yml at main -->
|
||||
|
||||
## What's Changed
|
||||
### Breaking changes
|
||||
* feat(assurance)!: require applicable fuzzing and badge participation by @KofTwentyTwo in https://github.com/KofTwentyTwo/standards/pull/19
|
||||
### Changes
|
||||
* fix(releases): use professional repository and automation text by @KofTwentyTwo in https://github.com/KofTwentyTwo/standards/pull/23
|
||||
* fix(releases): allow native formatting in generated changelog by @KofTwentyTwo in https://github.com/KofTwentyTwo/standards/pull/24
|
||||
* docs(assurance): record OpenSSF passing badge by @KofTwentyTwo in https://github.com/KofTwentyTwo/standards/pull/25
|
||||
* docs(exceptions): scope temporary frontend audit exception by @KofTwentyTwo in https://github.com/KofTwentyTwo/standards/pull/27
|
||||
* ci(release-velopack): sign only unsigned files; verify before packing by @KofTwentyTwo in https://github.com/KofTwentyTwo/standards/pull/29
|
||||
|
||||
|
||||
**Full Changelog**: https://github.com/KofTwentyTwo/standards/compare/v0.1.2...v1.0.0
|
||||
|
||||
## [0.1.2](https://github.com/KofTwentyTwo/standards/compare/v0.1.1...v0.1.2) (2026-10-10)
|
||||
|
||||
First published standards bundle: policies, language profiles, architectures,
|
||||
configs, reusable workflows, templates, and tooling. The earlier `0.1.0` and
|
||||
`0.1.1` versions were never published; their signed tags remain reserved after
|
||||
the workflow failures documented below.
|
||||
|
||||
|
||||
### Bug Fixes
|
||||
|
||||
* **releases:** declare the protected environment secret contract ([#15](https://github.com/KofTwentyTwo/standards/issues/15)) ([08b5ac8](https://github.com/KofTwentyTwo/standards/commit/08b5ac84a7b4d0a06d0b4c5f3ba9e643799084ad))
|
||||
|
||||
## 0.1.1 (2026-10-10) — unpublished
|
||||
|
||||
The bundle passed CI, checksums, and all five provenance checks, but publication
|
||||
stopped before draft creation because the reusable caller omitted its environment
|
||||
secret contract. This signed tag and the `0.1.0` baseline remain reserved.
|
||||
|
||||
### Bug Fixes
|
||||
|
||||
* **releases:** carry PR read permission through release gates ([#13](https://github.com/KofTwentyTwo/standards/issues/13)) ([e375cf3](https://github.com/KofTwentyTwo/standards/commit/e375cf3b07f6b209324fe9e0be77844bbd0397a2))
|
||||
|
||||
## 0.1.0 (2026-10-10) — unpublished
|
||||
|
||||
|
||||
### Features
|
||||
|
||||
@@ -22,6 +22,8 @@ themselves: everything below comes from the [SDLC policy](policies/sdlc.md).
|
||||
|
||||
## What a good change looks like
|
||||
|
||||
- Use professional wording in code comments, documentation, commit/PR messages,
|
||||
release notes and automation output. No emojis, joke copy or bot sound effects.
|
||||
- A new or changed requirement follows the format in
|
||||
[policies/README.md](policies/README.md#requirement-ids): an ID that has never been used,
|
||||
a level, the rule, *Why*, and *Verified by*. A requirement nobody can verify is not
|
||||
@@ -41,6 +43,7 @@ Documentation and package checks:
|
||||
npx markdownlint-cli2 "**/*.md"
|
||||
lychee --offline --include-fragments --no-progress './**/*.md'
|
||||
pwsh tools/Test-RepoConformance.ps1 -SelfTest
|
||||
pwsh tools/Test-RepoConformance.Fuzz.ps1 -Seed 22023 -Iterations 1000
|
||||
pwsh tools/Test-StandardsPackage.ps1
|
||||
pwsh tools/Test-RepoConformance.ps1 -LocalPath . -StaticOnly
|
||||
```
|
||||
@@ -59,6 +62,9 @@ pwsh tools/Test-RepoConformance.ps1 -Repository KofTwentyTwo/standards -LocalPat
|
||||
```
|
||||
|
||||
The same checks run in CI on every pull request and must pass before merging.
|
||||
Parser changes must pass both CI seeds and retain any discovered failure as a
|
||||
deterministic regression test. See [fuzzing](docs/security/fuzzing.md) for extended
|
||||
campaigns and [badge assessment](docs/security/best-practices.md) for enrollment evidence.
|
||||
|
||||
## License of contributions
|
||||
|
||||
|
||||
@@ -7,23 +7,23 @@ reusable GitHub Actions workflows, repository templates, and a conformance check
|
||||
|
||||
[](LICENSE-docs)
|
||||
[](LICENSE)
|
||||
[-orange)](#status)
|
||||
[](https://www.bestpractices.dev/projects/15366)
|
||||
[](#status)
|
||||
|
||||
## Status
|
||||
|
||||
> **Draft standards, before `v1.0.0`.** The requirements are written and the
|
||||
> enforcement tooling exists, but no KofTwentyTwo repository has been verified against
|
||||
> them yet; adoption starts with this repository, then
|
||||
> [AppKit](https://github.com/KofTwentyTwo/AppKit) and
|
||||
> [gclo](https://github.com/KofTwentyTwo/gclo). The compliance crosswalks therefore list
|
||||
> controls as **Defined**, not met, and the response times in the security program are
|
||||
> targets until `v1.0.0`. See [how these standards work](policies/README.md).
|
||||
> **Versioned standards with repository-specific adoption.** Immutable releases
|
||||
> identify each published baseline. This repository has completed repository and CI
|
||||
> conformance checks; other repositories record their own checks and exceptions.
|
||||
> Compliance crosswalks describe requirements and evidence for individual controls.
|
||||
> See [how these standards work](policies/README.md) and the
|
||||
> [release checklists](https://github.com/KofTwentyTwo/standards/issues?q=is%3Aissue+%22Release+v%22).
|
||||
|
||||
---
|
||||
|
||||
**Tier:** Product ([scope](policies/README.md#scope)). **Conformance:** this repository is
|
||||
the first to adopt the standards; it will state *Conforms to KofTwentyTwo standards
|
||||
v1.0* once the checker passes on it.
|
||||
**Tier:** Product ([scope](policies/README.md#scope)). **Conformance:** repository and
|
||||
CI checks reviewed on 2026-10-10: zero FAIL or UNKNOWN, with manual review items
|
||||
resolved in the release checklist and approved [exceptions](exceptions/register.md).
|
||||
|
||||
## How to use
|
||||
|
||||
@@ -33,6 +33,11 @@ v1.0* once the checker passes on it.
|
||||
the [reusable workflows](#use-the-reusable-workflows), and keep it green with the
|
||||
[conformance checker](#check-a-repositorys-conformance).
|
||||
|
||||
Security assurance is recorded in the [Best Practices assessment](docs/security/best-practices.md)
|
||||
and [parser fuzzing guide](docs/security/fuzzing.md). The repository earned the
|
||||
[OpenSSF Best Practices passing badge](https://www.bestpractices.dev/projects/15366)
|
||||
on 2026-10-10 through a maintainer self-assessment.
|
||||
|
||||
## Contents
|
||||
|
||||
- [How to use](#how-to-use)
|
||||
@@ -197,13 +202,18 @@ IDE does on save is what CI checks.
|
||||
`protect-release-tags` rulesets, squash-only merging, the security features
|
||||
(private vulnerability reporting, secret scanning with push protection, Dependabot,
|
||||
code scanning, immutable releases), and read-only Actions tokens.
|
||||
6. **Check.** Run the conformance checker and fix every FAIL:
|
||||
6. **Assurance.** Complete `docs/security/assurance.json` and its evidence templates.
|
||||
Public Product repositories register their own Best Practices assessment and
|
||||
link the actual badge. Identify complex untrusted-input parsers; add their fuzz
|
||||
commands to a required PR/main CI job and weekly/release campaigns, or document
|
||||
why none apply. Review scope decisions; copied placeholders do not conform.
|
||||
7. **Check.** Run the conformance checker and fix every FAIL:
|
||||
|
||||
```powershell
|
||||
pwsh ./tools/Test-RepoConformance.ps1 -Repository KofTwentyTwo/<repo> -LocalPath ../<repo>
|
||||
```
|
||||
|
||||
7. **Declare it.** Add *"Conforms to KofTwentyTwo standards vX.Y"* to the README and
|
||||
8. **Declare it.** Add *"Conforms to KofTwentyTwo standards vX.Y"* to the README and
|
||||
list the repository in [MAINTAINERS.md](MAINTAINERS.md#repositories).
|
||||
|
||||
### An existing repository
|
||||
|
||||
@@ -38,7 +38,7 @@ this crosswalk is updated when it becomes final.
|
||||
| **PW.5** Create source code by adhering to secure coding practices | [Coding standards](../standards/coding/README.md), K22-AI-30 |
|
||||
| **PW.6** Configure build processes to improve executable security | Language profiles (warnings as errors, analyzers, deterministic builds), K22-CI-30 |
|
||||
| **PW.7** Review and/or analyze human-readable code | K22-SDLC-12, K22-CI-22 |
|
||||
| **PW.8** Test executable code | [Testing standard](../standards/testing.md) |
|
||||
| **PW.8** Test executable code | [Testing standard](../standards/testing.md), including K22-TEST-25 parser fuzzing where applicable |
|
||||
| **PW.9** Configure software to have secure settings by default | K22-SEC-70, K22-SEC-71 |
|
||||
| **RV.1** Identify and confirm vulnerabilities on an ongoing basis | K22-CI-21, K22-CI-22 (weekly scans), K22-DEP-40, K22-SEC-40 |
|
||||
| **RV.2** Assess, prioritize, and remediate vulnerabilities | K22-SEC-41, K22-SEC-42 |
|
||||
@@ -72,4 +72,5 @@ Scorecard checks and the requirement that drives each:
|
||||
| Vulnerabilities | K22-SEC-41 |
|
||||
| Binary-Artifacts | K22-REPO-05 |
|
||||
| Packaging | K22-REL-11 |
|
||||
| Fuzzing | K22-TEST-23 (where applicable) |
|
||||
| Fuzzing | K22-TEST-25 (mandatory where applicable); K22-TEST-23 negative-test guidance. Detection supports selected integrations and can miss native harnesses |
|
||||
| CII-Best-Practices | K22-REPO-12 (public Product enrollment and maintained assessment); actual badge level is assigned by the provider |
|
||||
@@ -22,7 +22,7 @@ picks them up on open: *Settings → Editor → Code Style* shows **Project** se
|
||||
|
||||
## Per IDE (all projects)
|
||||
|
||||
1. **Code style:** *Settings → Editor → Code Style → ⚙ (Show Scheme Actions) → Import
|
||||
1. **Code style:** *Settings → Editor → Code Style → Show Scheme Actions → Import
|
||||
Scheme → IntelliJ IDEA code style XML* → choose `Kingsrook_Code_Style.xml`.
|
||||
2. **Reformat on save:** *Settings → Tools → Actions on Save* → enable **Reformat code**
|
||||
and **Optimize imports**.
|
||||
|
||||
+40
-10
@@ -24,10 +24,19 @@ against it before packaging.
|
||||
|
||||
Use `feat` for new guidance and `!` for new obligations or removed allowances as
|
||||
defined in [the versioning policy](../policies/README.md#versioning-of-the-standards).
|
||||
Infrastructure changes appear in release notes and can produce PATCH releases;
|
||||
`test`, `style`, and `chore` alone do not produce release notes. Review the proposed
|
||||
Infrastructure changes can produce PATCH releases;
|
||||
`test`, `style`, and `chore` alone do not trigger a version proposal. Review the proposed
|
||||
version and notes before merging. A `breaking` label alone does not change the version.
|
||||
|
||||
PR headers and footers are explicitly configured in `release-please-config.json`.
|
||||
Native GitHub release notes use the plain-language categories in `.github/release.yml`,
|
||||
keeping breaking changes visible without decorative symbols. Versions still come
|
||||
from Conventional Commits; note entries link to reviewed PRs. Maintainers review
|
||||
breaking-change descriptions before publication. Repository text and automation
|
||||
output use professional wording, without emojis, joke copy or bot sound effects.
|
||||
Only generated `CHANGELOG.md` permits native spacing, bare links and repeated
|
||||
category headings; other documentation retains the normal Markdown rules.
|
||||
|
||||
## One-time maintainer setup
|
||||
|
||||
1. Keep **Settings → General → Releases → Enable release immutability** enabled.
|
||||
@@ -46,7 +55,9 @@ version and notes before merging. A `breaking` label alone does not change the v
|
||||
`release` with a maintainer reviewer and deployment access for `v*` tags only.
|
||||
Store `RELEASE_APP_PRIVATE_KEY` separately in these **environment secrets**;
|
||||
never as a repository secret or committed file. Environments in the reusable
|
||||
publisher resolve their own secrets; caller workflows do not pass the key.
|
||||
publisher resolve their own secrets, but the caller must explicitly pass the
|
||||
secret name and the reusable workflow must declare it. The named contract does
|
||||
not expose the tag-only value to caller jobs or nested CI gates.
|
||||
Keep `protect-main` and `protect-release-tags` intact; the App does not create
|
||||
tags or bypass review. Allow the SHA-pinned Release Please action if repository
|
||||
action restrictions require an explicit allowlist entry.
|
||||
@@ -71,16 +82,18 @@ and Release Please actions are pinned and tracked by Dependabot. See the
|
||||
|
||||
```bash
|
||||
git fetch origin main
|
||||
git tag -s v0.1.0 <release-pr-merge-sha> \
|
||||
git tag -s v0.1.2 <release-pr-merge-sha> \
|
||||
-m "Release checklist: https://github.com/KofTwentyTwo/standards/issues/123"
|
||||
git push origin v0.1.0
|
||||
git push origin v0.1.2
|
||||
```
|
||||
|
||||
4. The tag workflow validates ancestry, version files, the pending release PR, and
|
||||
the checklist; reruns all repository CI gates; builds and attests the assets;
|
||||
and waits for the `release` environment approval. Publication creates a draft with
|
||||
all assets and notes, then publishes it. The release PR is marked
|
||||
`autorelease: tagged` so the next version can be proposed.
|
||||
`autorelease: tagged` so the next version can be proposed. Version proposals wait
|
||||
while a merged release PR has `autorelease: pending`; a failed, permanently
|
||||
retired version uses `autorelease: failed` instead.
|
||||
|
||||
These bundles ship no compiled software, so the compiled-artifact SBOM requirement
|
||||
does not apply. The included workflows/configs retain their upstream pins; source
|
||||
@@ -93,10 +106,10 @@ Every release includes `KofTwentyTwo-standards-<version>.zip`, `.tar.gz`,
|
||||
and license split. Download assets from a specific version, then verify:
|
||||
|
||||
```bash
|
||||
gh release download v0.1.0 --repo KofTwentyTwo/standards --dir standards-0.1.0
|
||||
cd standards-0.1.0
|
||||
gh release download v0.1.2 --repo KofTwentyTwo/standards --dir standards-0.1.2
|
||||
cd standards-0.1.2
|
||||
sha256sum --check SHA256SUMS
|
||||
gh attestation verify KofTwentyTwo-standards-0.1.0.zip --repo KofTwentyTwo/standards
|
||||
gh attestation verify KofTwentyTwo-standards-0.1.2.zip --repo KofTwentyTwo/standards
|
||||
```
|
||||
|
||||
On Windows, compare `Get-FileHash -Algorithm SHA256` to the corresponding entry in
|
||||
@@ -106,7 +119,7 @@ version comment; do not use a moving branch or tag as a workflow pin.
|
||||
To reproduce from a checkout containing the release commit:
|
||||
|
||||
```powershell
|
||||
pwsh tools/Build-StandardsPackage.ps1 -Version 0.1.0 -Ref v0.1.0 -OutputPath artifacts/rebuild-0.1.0
|
||||
pwsh tools/Build-StandardsPackage.ps1 -Version 0.1.2 -Ref v0.1.2 -OutputPath artifacts/rebuild-0.1.2
|
||||
pwsh tools/Test-StandardsPackage.ps1
|
||||
```
|
||||
|
||||
@@ -116,6 +129,12 @@ same Git/archive tool versions. The builder refuses an existing output directory
|
||||
|
||||
## Failed releases
|
||||
|
||||
The initial `v0.1.0` tag was reserved by a workflow-startup failure; no bundle was
|
||||
published for it. The `v0.1.1` publisher then stopped before draft creation because
|
||||
the caller omitted the environment secret contract. Both tags remain reserved;
|
||||
`v0.1.2` is the first published bundle. Keep these tags unchanged; use a new
|
||||
version when a tagged workflow needs a source fix.
|
||||
|
||||
Never replace published assets or move/reuse a version tag. Fix a published error
|
||||
with a new release. A failed upload or publication leaves a draft; inspect its run
|
||||
and assets, then remove **only the unpublished draft** before rerunning the tag's
|
||||
@@ -126,3 +145,14 @@ from `autorelease: pending` to `autorelease: tagged`; do not rebuild the release
|
||||
References: [GitHub immutable releases](https://docs.github.com/en/code-security/concepts/supply-chain-security/immutable-releases),
|
||||
[enabling immutability](https://docs.github.com/en/code-security/how-tos/secure-your-supply-chain/establish-provenance-and-integrity/prevent-release-changes),
|
||||
and [Release Please token behavior](https://github.com/googleapis/release-please-action#other-actions-on-release-please-prs).
|
||||
|
||||
## Known limitations
|
||||
|
||||
[Issue #22](https://github.com/KofTwentyTwo/standards/issues/22) tracks Windows App SDK
|
||||
publish resources and the missing shared launch-smoke gate in `release-velopack.yml`.
|
||||
Windows consumers must keep `WindowsAppSDKSelfContained` and `WindowsPackageType`
|
||||
consistent between build and publish, or clean intermediate output before publish.
|
||||
Run smoke tests against the published executable and test the installer on a clean
|
||||
machine before a stable application release. This remains a consumer-side check
|
||||
until the shared workflow implements it. The standards bundle contains source and
|
||||
configuration; it does not contain a compiled Windows application.
|
||||
@@ -0,0 +1,17 @@
|
||||
{
|
||||
"schemaVersion": 1,
|
||||
"badge": {
|
||||
"applicability": "required",
|
||||
"reason": "Public open-source Product repository distributing standards and executable tooling.",
|
||||
"projectUrl": "https://www.bestpractices.dev/projects/15366",
|
||||
"evidence": "docs/security/best-practices.md"
|
||||
},
|
||||
"fuzzing": {
|
||||
"applicability": "required",
|
||||
"reason": "The conformance checker parses contributor-controlled workflows and exception records; unsafe acceptance can hide failed requirements.",
|
||||
"targets": ["tools/Test-RepoConformance.ps1"],
|
||||
"command": "./tools/Test-RepoConformance.Fuzz.ps1",
|
||||
"workflow": ".github/workflows/ci.yml",
|
||||
"evidence": "docs/security/fuzzing.md"
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,73 @@
|
||||
# OpenSSF Best Practices evidence
|
||||
|
||||
## Enrollment and review
|
||||
|
||||
**Repository:** <https://github.com/KofTwentyTwo/standards>. **Owner:** @KofTwentyTwo.
|
||||
**Assessment:** [project 15366](https://www.bestpractices.dev/en/projects/15366/passing).
|
||||
**Level:** Passing. **Reviewed and earned:** 2026-10-10.
|
||||
**Next review:** 2027-01-08, or before the next MINOR or MAJOR release, whichever
|
||||
occurs first. Reassess at least every 90 days.
|
||||
|
||||
The public assessment identifies this repository and displays the passing badge.
|
||||
The maintainer completed the self-assessment, including personal attestations.
|
||||
This badge records voluntary maintainer self-certification.
|
||||
The enrollment-only exception [EX-0007](../../exceptions/register.md#ex-0007) is
|
||||
closed with the actual README badge and project URL recorded in
|
||||
[assurance.json](assurance.json).
|
||||
|
||||
## Criterion evidence matrix
|
||||
|
||||
IDs below omit the provider's `0.` level prefix. **Evidence** means source exists;
|
||||
**Review** marks ongoing operational evidence or human judgment to revalidate;
|
||||
**Proposed N/A** preserves applicability reasoning for the next review. These are
|
||||
local evidence notes; the linked public assessment holds the submitted answers.
|
||||
|
||||
| Criteria | Evidence and ongoing review | State |
|
||||
| --- | --- | --- |
|
||||
| `description_good`, `documentation_basics` | [README](../../README.md) describes standards, tooling and adoption | Evidence |
|
||||
| `interact`, `discussion`, `report_process`, `report_tracker`, `report_archive` | Public [issues](https://github.com/KofTwentyTwo/standards/issues), templates and [CONTRIBUTING](../../CONTRIBUTING.md) | Evidence |
|
||||
| `contribution`, `contribution_requirements` | CONTRIBUTING documents proposals, signed commits, DCO, review and gates | Evidence |
|
||||
| `floss_license`, `floss_license_osi`, `license_location` | Root [MIT](../../LICENSE) for code, configs and templates; [CC BY 4.0](../../LICENSE-docs) for prose; attributed Apache-2.0 material in [NOTICE](../../NOTICE) | Evidence |
|
||||
| `documentation_interface` | [Tool help and local commands](../../CONTRIBUTING.md#running-the-checks-locally), [release instructions](../releases.md); owner checks shipped tools' help completeness | Review |
|
||||
| `sites_https`, `delivery_mitm` | GitHub repository and release delivery use HTTPS; [verification](../../standards/releases.md#verifying-a-release) checks provenance | Evidence |
|
||||
| `english` | Repository documentation and contribution guidance are in English | Evidence |
|
||||
| `maintained`, `report_responses`, `enhancement_responses` | Recent PRs/issues exist; a new project has limited response history. Assess actual responsiveness, not a policy promise | Review |
|
||||
| `repo_public`, `repo_track`, `repo_interim`, `repo_distributed` | Public Git repository retains reviewed changes; signed history and protected main | Evidence |
|
||||
| `version_unique`, `version_semver`, `version_tags`, `release_notes` | Immutable [v0.1.2](https://github.com/KofTwentyTwo/standards/releases/tag/v0.1.2), committed version and [changelog](../../CHANGELOG.md); failed unpublished tags are reserved | Evidence |
|
||||
| `release_notes_vulns` | [Release standard](../../standards/releases.md) requires security fix notes; no published vulnerability fix history to attest to yet | Review |
|
||||
| `vulnerability_report_process`, `vulnerability_report_private` | [SECURITY](../../SECURITY.md) links private GitHub reporting and supported versions | Evidence |
|
||||
| `vulnerability_report_response` | Maintainer confirmed actual response compliance on 2026-10-10 in the public assessment; revalidate report history, not just SECURITY targets | Review |
|
||||
| `build`, `build_common_tools`, `build_floss_tools` | [Bundle builder](../../tools/Build-StandardsPackage.ps1) uses Git archives and PowerShell; prerequisites and release steps are documented | Evidence |
|
||||
| `test`, `test_invocation`, `test_policy`, `tests_are_added`, `tests_documented_added` | CONTRIBUTING requires behavioral regression tests and documents checker, package and fuzz commands | Evidence |
|
||||
| `test_continuous_integration` | [CI](../../.github/workflows/ci.yml) runs tests on PR/main, schedules and tag publication gates | Evidence |
|
||||
| `test_most` | Parser properties, helper self-tests and package tests exist; measured whole-tool coverage is not established | Suggested: Unmet |
|
||||
| `warnings`, `warnings_fixed` | Markdown/workflow gates reject findings; revalidate applicable PowerShell warning handling | Review |
|
||||
| `warnings_strict` | Maximal PowerShell warning/linter coverage across shipped tooling is not established by Actions CodeQL | Suggested: Unmet |
|
||||
| `know_secure_design`, `know_common_errors` | Primary maintainer personally attested on 2026-10-10; supporting [threat model](threat-model.md), [security policy](../../policies/security-program.md) and parser regressions remain review evidence | Review |
|
||||
| `crypto_published`, `crypto_call`, `crypto_floss`, `crypto_keylength`, `crypto_working`, `crypto_weaknesses` | No custom product cryptography. GitHub TLS, SSH signing and attestations are delegated mechanisms; assess whether delegation meets each criterion or permits N/A | Review |
|
||||
| `crypto_pfs` | No application server or product-controlled TLS configuration; document the hosted-service boundary if N/A is permitted | Proposed N/A |
|
||||
| `crypto_password_storage` | No application passwords, password database or credential-storage implementation | Proposed N/A |
|
||||
| `crypto_random` | The seeded fuzz PRNG generates only synthetic tests, never security keys/nonces. Cryptographic randomness is delegated to signing/hosting providers | Review |
|
||||
| `delivery_unsigned` | Release assets have checksums, verified source, build provenance and GitHub immutable-release attestation; see [verification commands](../releases.md) | Evidence |
|
||||
| `vulnerabilities_fixed_60_days`, `vulnerabilities_critical_fixed` | [Security program](../../policies/security-program.md#vulnerability-management) defines deadlines; maintainer must check current findings and known unpatched issues before answering | Review |
|
||||
| `no_leaked_credentials` | Required [gitleaks and secret scans](../../.github/workflows/security.yml); scans support, but cannot prove, a universal absence claim | Review |
|
||||
| `static_analysis`, `static_analysis_common_vulnerabilities`, `static_analysis_often` | [CodeQL Actions](../../.github/workflows/codeql.yml), zizmor and actionlint in required security gates; document PowerShell analysis coverage accurately | Evidence |
|
||||
| `static_analysis_fixed` | Gates reject applicable findings; review unresolved alerts and [Scorecard dispositions](scorecard-dispositions.md) before claiming all important findings fixed | Review |
|
||||
| `dynamic_analysis`, `dynamic_analysis_enable_assertions` | Executed [fuzz properties](fuzzing.md), deterministic helper and isolated package tests; assertions fail the job | Evidence |
|
||||
| `dynamic_analysis_unsafe` | Shipped tooling is managed PowerShell, not unsafe native-language implementation; owner validates applicability | Proposed N/A |
|
||||
| `dynamic_analysis_fixed` | Generated-input findings gained deterministic regressions; review all future campaign failures before assessment updates | Review |
|
||||
|
||||
## Maintaining the assessment
|
||||
|
||||
1. Review the public answers against current criteria and repository evidence by the
|
||||
next review date and before each MINOR or MAJOR release.
|
||||
2. Reconfirm human knowledge, unresolved findings and actual response history with
|
||||
the maintainer; update answers when evidence or applicability changes.
|
||||
3. Keep the provider's live badge, project URL and this review record synchronized.
|
||||
Downstream repositories need their own assessment and badge.
|
||||
4. Improve the unmet suggested criteria `test_most` and `warnings_strict`; passing
|
||||
does not establish whole-tool coverage or maximal warning coverage. Track the
|
||||
shared release smoke-test gap in [issue #22](https://github.com/KofTwentyTwo/standards/issues/22).
|
||||
|
||||
Downstream repositories use the [assessment template](../../templates/repo/docs/security/best-practices.md)
|
||||
and their own evidence and project ID; EX-0007 does not apply to them.
|
||||
@@ -0,0 +1,50 @@
|
||||
# Conformance parser fuzzing
|
||||
|
||||
## Scope and properties
|
||||
|
||||
K22-TEST-25 applies to shipped tooling even though most repository content is prose.
|
||||
`tools/Test-RepoConformance.Fuzz.ps1` generates synthetic workflow and exception input
|
||||
for the pure helpers in `tools/Test-RepoConformance.ps1`. It varies quotes, indentation,
|
||||
comments, LF/CRLF, scalar indicators, action references and attacker expressions.
|
||||
Independent construction supplies the expected security decision: only full SHA
|
||||
pins pass; credential persistence and untrusted expressions are detected; expressions
|
||||
in environment fields remain outside script bodies; exceptions match their exact
|
||||
scope and must be open and unexpired. Bounded malformed ASCII inputs also exercise
|
||||
parser robustness. These properties test unsafe acceptance, not only crashes.
|
||||
|
||||
This is native generated-input fuzzing, not coverage-guided instrumentation or a
|
||||
complete YAML validator. Zizmor remains the authoritative workflow security gate.
|
||||
Scorecard currently detects selected integrations; it may continue reporting zero
|
||||
for this PowerShell harness. Do not claim OSS-Fuzz or ClusterFuzzLite enrollment.
|
||||
|
||||
## Running and replaying
|
||||
|
||||
PowerShell 7.4 or later is the only dependency; no network or profile is used.
|
||||
|
||||
```powershell
|
||||
pwsh -NoProfile -File tools/Test-RepoConformance.Fuzz.ps1 -Seed 22023 -Iterations 1000
|
||||
pwsh -NoProfile -File tools/Test-RepoConformance.Fuzz.ps1 -Seed 99173 -Iterations 1000
|
||||
# Weekly and pre-MINOR/MAJOR campaigns: run both seeds at 10000 iterations.
|
||||
pwsh -NoProfile -File tools/Test-RepoConformance.Fuzz.ps1 -Seed 22023 -Iterations 10000
|
||||
pwsh -NoProfile -File tools/Test-RepoConformance.Fuzz.ps1 -Seed 99173 -Iterations 10000
|
||||
```
|
||||
|
||||
Failures print synthetic input, property, iteration and seed as JSON. Replay with
|
||||
that seed and at least that iteration count against the same checkout and PowerShell
|
||||
runtime. Minimize the input into a named `-SelfTest` regression, then fix the parser
|
||||
and rerun both seeds. Report suspected vulnerabilities through [SECURITY.md](../../SECURITY.md).
|
||||
|
||||
Inputs are bounded to small workflow fragments and fewer than 128 random ASCII
|
||||
characters; iterations are limited to 100000. Generated scripts are never executed.
|
||||
The harness does not write files, launch tools, or request repository credentials.
|
||||
The required `ci / conformance` job runs both seeds on PRs and `main`, and 10000
|
||||
iterations per seed on weekly schedules and release-tag validation, within its
|
||||
10-minute timeout. Future failing seeds must be retained alongside regression cases.
|
||||
|
||||
## Review record
|
||||
|
||||
- **Owner:** @KofTwentyTwo; **reviewed:** 2026-10-10; **next review:** 2027-01-08.
|
||||
- Seed 22023 exposed quoted-checkout and explicit-indentation block-scalar gaps;
|
||||
both now have deterministic self-test regressions. This is not a coverage claim.
|
||||
- Reassess targets when parser boundaries change; generated data alone does not
|
||||
establish completeness or substitute for ordinary package and checker tests.
|
||||
@@ -14,8 +14,14 @@ changes or predict future maintenance.
|
||||
|
||||
Keep the accepted risk visible; dismissal is not a claim that the missing second
|
||||
maintainer exists. Do not disable Scorecard or weaken protections. Reopen and
|
||||
reassess these dispositions when their assumptions change. The LOW best-practices
|
||||
badge and MEDIUM fuzzing recommendations remain open for follow-up.
|
||||
reassess these dispositions when their assumptions change. The
|
||||
[badge assessment](best-practices.md) records the passing badge for
|
||||
[project 15366](https://www.bestpractices.dev/projects/15366), earned on 2026-10-10;
|
||||
EX-0007 is closed. Let the next Scorecard scan reassess the badge finding rather
|
||||
than claiming a new score before it runs. The MEDIUM fuzzing recommendation remains
|
||||
visible: [native parser fuzzing](fuzzing.md) supplies generated security properties and
|
||||
regressions. Scorecard may not recognize this PowerShell harness; do not label a
|
||||
score of zero as proof that tests did not run or claim a supported integration.
|
||||
|
||||
No OpenVEX statement is fabricated for these process findings: no vulnerable shipped
|
||||
component or exploit was identified. Any actual vulnerability present but not
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
# Threat model: standards distribution
|
||||
|
||||
- **Version reviewed:** v0.1.0 release-bundle implementation
|
||||
- **Version reviewed:** v1.0.0 standards baseline
|
||||
- **Last updated:** 2026-10-10
|
||||
- **Status:** Implementation reviewed; provisioning is verified in the release checklist.
|
||||
|
||||
@@ -71,6 +71,27 @@ trust boundaries change.
|
||||
|
||||
## Review log
|
||||
|
||||
- **2026-10-10, v1.0.0:** Reviewed changes since v0.1.2: mandatory applicable badge
|
||||
participation and parser fuzzing, completed passing self-assessment, professional
|
||||
release text, scoped frontend audit exception, and unsigned-file-only Windows
|
||||
signing with verification before packing. Distribution credentials, channels,
|
||||
environment restrictions and provenance boundaries remain unchanged. Badge
|
||||
metadata adds a public provider link; fuzzing executes synthetic local inputs.
|
||||
Signing continues to use the Windows consumer's existing Azure OIDC boundary.
|
||||
The most likely release interruption remains a failed gate or unpublished draft;
|
||||
recovery preserves tags and immutable assets. The highest-impact distribution
|
||||
threat remains a compromised credential or pinned Action, constrained by the
|
||||
existing scoped tokens, protected environments and required checks. Windows
|
||||
publish/resource and launch-smoke limitations remain tracked in
|
||||
[issue #22](https://github.com/KofTwentyTwo/standards/issues/22); consumer workarounds
|
||||
and rehearsal expectations are recorded in [release guidance](../releases.md#known-limitations).
|
||||
|
||||
- **2026-10-10, v0.1.2:** Reviewed the explicit reusable-workflow secret contract
|
||||
after the unpublished v0.1.1 publisher failed. Only the named App key is passed;
|
||||
its value remains restricted to the reviewer-protected tag environment. Nested
|
||||
CI receives no secrets, and permissions, interfaces, and recovery boundaries
|
||||
remain unchanged. Failed version tags stay reserved.
|
||||
|
||||
- **2026-10-10, v0.1.0:** Reviewed source-only packaging, release-PR validation,
|
||||
preflight approval, App token scopes, environment restrictions, provenance, and
|
||||
immutable publication. Main and tag protections remain in place. Most likely
|
||||
|
||||
@@ -11,6 +11,8 @@ Open and closed exceptions to the KofTwentyTwo standards. The process is in
|
||||
| [EX-0004](#ex-0004) | Open | K22-CI-30, K22-REL-04 (shared release workflow) | gclo | When the shared reusable release workflow ships, at the latest 2027-04-09 |
|
||||
| [EX-0005](#ex-0005) | Open | K22-AI-10 (agents via PR only), K22-AI-11 (in part: unconfirmed pushes) | second-brain, nix, Praetor, Jellyfin | Reviewed yearly, next on 2027-10-10 |
|
||||
| [EX-0006](#ex-0006) | Open | K22-CI-16 (tag-only environment secrets) | standards | 2027-04-10; review 2027-01-10 |
|
||||
| [EX-0007](#ex-0007) | Closed | K22-REPO-12 (enrollment) | KofTwentyTwo/standards | Closed 2026-10-10 |
|
||||
| [EX-0008](#ex-0008) | Open | K22-CI-21 (HIGH dependency finding) | Website-Frontend, development-only braces 3.0.3 | 2026-11-10; review 2026-10-17 |
|
||||
|
||||
## EX-0001
|
||||
|
||||
@@ -169,3 +171,57 @@ Open and closed exceptions to the KofTwentyTwo standards. The process is in
|
||||
- **Expires:** 2027-04-10, or earlier when version proposals use credential federation.
|
||||
- **Owner:** the maintainer.
|
||||
- **Review date:** 2027-01-10.
|
||||
|
||||
## EX-0007
|
||||
|
||||
**Best Practices enrollment temporarily blocked by provider anti-spam**
|
||||
|
||||
- **Requirements:** `K22-REPO-12` enrollment and the actual README badge only.
|
||||
- **Scope:** `KofTwentyTwo/standards`; no downstream repository is covered.
|
||||
- **Reason:** on 2026-10-10 the maintainer could not sign in because of the provider's
|
||||
anti-spam restriction and explicitly deferred enrollment. No project ID existed
|
||||
when this exception was approved.
|
||||
- **Risk:** consumers could not inspect a registered provider assessment during the
|
||||
enrollment deferral.
|
||||
- **Compensating controls (while open):** publish the [criterion evidence](../docs/security/best-practices.md)
|
||||
and pending answers; retain existing security/PR gates and parser fuzzing. Do not
|
||||
claim participation or a passing badge. Retry normal login once the restriction
|
||||
clears, register the exact repository URL, and close this exception through a PR.
|
||||
- **Expires:** 2026-11-10 (UTC), or successful enrollment and README badge publication,
|
||||
whichever occurs first. **Review:** 2026-10-17.
|
||||
- **Owner:** @KofTwentyTwo. Approval is recorded by the normal exception-register PR;
|
||||
this entry does not authorize bypassing CI or publishing an unreviewed assessment.
|
||||
- **Closed:** 2026-10-10. [Project 15366](https://www.bestpractices.dev/projects/15366)
|
||||
identifies this repository and has earned the passing badge. The actual linked
|
||||
badge is in [README](../README.md), the project URL is in
|
||||
[assurance.json](../docs/security/assurance.json), and the
|
||||
[assessment record](../docs/security/best-practices.md) sets the next review date.
|
||||
|
||||
## EX-0008
|
||||
|
||||
**Temporary development dependency audit exception for Website-Frontend**
|
||||
|
||||
- **Requirements:** `K22-CI-21` blocks HIGH and CRITICAL findings in the resolved
|
||||
dependency graph.
|
||||
- **Scope:** `KofTwentyTwo/Website-Frontend` only; development-only `braces` 3.0.3,
|
||||
[GHSA-vfj7-8cjw-p6xm](https://github.com/advisories/GHSA-vfj7-8cjw-p6xm)
|
||||
/ CVE-2026-93687 and npm's propagated findings for that same advisory. No other
|
||||
advisory, runtime dependency or CRITICAL finding is covered.
|
||||
- **Reason:** the Next ESLint tooling depends on the affected package and no patched
|
||||
npm release exists as of 2026-10-10. npm suggests an unrelated parent downgrade;
|
||||
that does not establish a patched braces release.
|
||||
- **Risk:** a deliberately deeply nested caller-supplied brace pattern can exhaust
|
||||
the stack and stop a lint/build process. Website requests and article Markdown do
|
||||
not control the lint glob configuration. The dependency remains vulnerable.
|
||||
- **Compensating controls:** the complete audit still runs. A separate production
|
||||
audit must have zero HIGH/CRITICAL findings. The exception checker validates the
|
||||
advisory ID, package, locked version, development-only package paths and expiry;
|
||||
rejects other HIGH/CRITICAL findings, malformed reports and audit errors; and
|
||||
requires reassessment if the registry reports a new braces release. Lint, tests,
|
||||
build, secret scanning, SAST and Trivy remain enabled. No global scanner exclusion
|
||||
is permitted. Website-Frontend configuration cites this ID.
|
||||
- **Expires:** 2026-11-10 at 00:00 UTC, or a patched upstream release, whichever
|
||||
occurs first. A newer upstream release blocks acceptance pending reassessment.
|
||||
- **Owner:** James Maes, repository maintainer.
|
||||
- **Review date:** 2026-10-17. Remove the consumer exception and close this entry once
|
||||
the affected dependency is replaced or patched.
|
||||
+7
-11
@@ -1,16 +1,12 @@
|
||||
# How these standards work
|
||||
|
||||
> **Status: draft, pre-v1.0.0.** The requirements are written but have not been
|
||||
> released. Until the first release (`v1.0.0`):
|
||||
>
|
||||
> - the automated checks and conformance tooling are still being built, so no
|
||||
> requirement is yet *Enforced* or *Verified* (see [compliance](../compliance/README.md));
|
||||
> - no KofTwentyTwo repository claims conformance; adoption is in progress, starting
|
||||
> with this repository, [AppKit](https://github.com/KofTwentyTwo/AppKit), and
|
||||
> [gclo](https://github.com/KofTwentyTwo/gclo);
|
||||
> - commitments to people outside the project (vulnerability response and fix times in
|
||||
> the [security program](security-program.md#vulnerability-management)) are targets
|
||||
> that the maintainer works to, and become binding commitments at `v1.0.0`.
|
||||
> **Status: versioned baseline.** Published tags identify immutable requirements.
|
||||
> Each repository adopts a specific version and records its own conformance evidence
|
||||
> and approved exceptions. [Compliance](../compliance/README.md) labels describe the
|
||||
> evidence for each mapped control; publication alone does not verify every consumer.
|
||||
> External vulnerability response and fix deadlines in the
|
||||
> [security program](security-program.md#vulnerability-management) become binding
|
||||
> commitments with adoption of the stable `v1.0.0` baseline.
|
||||
|
||||
This repository is the single source of truth for how KofTwentyTwo builds, secures,
|
||||
and ships software. It has three layers, each more specific than the last:
|
||||
|
||||
@@ -2,18 +2,11 @@
|
||||
"$schema": "https://raw.githubusercontent.com/googleapis/release-please/main/schemas/config.json",
|
||||
"release-type": "simple",
|
||||
"include-component-in-tag": false,
|
||||
"bump-minor-pre-major": false,
|
||||
"changelog-type": "github",
|
||||
"pull-request-header": "Release proposal generated from reviewed changes on main. Review the version, changes, and validation before merging.",
|
||||
"pull-request-footer": "Generated by [Release Please](https://github.com/googleapis/release-please) using this repository's release configuration.",
|
||||
"initial-version": "0.1.0",
|
||||
"changelog-sections": [
|
||||
{ "type": "security", "section": "Security", "hidden": false },
|
||||
{ "type": "feat", "section": "Features", "hidden": false },
|
||||
{ "type": "fix", "section": "Bug Fixes", "hidden": false },
|
||||
{ "type": "docs", "section": "Documentation", "hidden": false },
|
||||
{ "type": "perf", "section": "Performance", "hidden": false },
|
||||
{ "type": "refactor", "section": "Refactoring", "hidden": false },
|
||||
{ "type": "build", "section": "Build", "hidden": false },
|
||||
{ "type": "ci", "section": "Automation", "hidden": false },
|
||||
{ "type": "revert", "section": "Reverts", "hidden": false }
|
||||
],
|
||||
"packages": {
|
||||
".": { "version-file": "version.txt" }
|
||||
}
|
||||
|
||||
@@ -117,7 +117,10 @@ the pull request description discloses it, per
|
||||
|
||||
**K22-AGENT-41 (SHOULD)** An agent reports in concise plain language: what changed,
|
||||
why, what was verified, and what limitations remain. Document length scales with the
|
||||
task; decoration (emojis, filler praise) is omitted unless asked for.
|
||||
task. Repository text, including code comments, documentation, commits, PRs, release
|
||||
notes and generated automation messages, uses professional wording. Omit emojis,
|
||||
joke copy, filler praise and bot sound effects. Configure third-party automation to
|
||||
follow the same rule; its default templates are not an exception.
|
||||
*Why:* the report is the maintainer's primary review input. *Verified by:* review.
|
||||
|
||||
## Context economy
|
||||
|
||||
@@ -88,6 +88,20 @@ released software, links to the latest release and the verification instructions
|
||||
[releases](releases.md#verifying-a-release).
|
||||
*Why:* users can see the security posture before they install. *Verified by:* review.
|
||||
|
||||
**K22-REPO-12 (MUST)** Public, open-source Product repositories enroll in the
|
||||
[OpenSSF Best Practices program](https://www.bestpractices.dev/en), link their actual
|
||||
project badge from the README, and maintain an evidence-backed assessment in
|
||||
`docs/security/best-practices.md`. Reassess at least every 90 days and before each
|
||||
MINOR or MAJOR release. Record applicability, evidence path, and the actual project
|
||||
URL in `docs/security/assurance.json` using the [starter record](../templates/repo/docs/security/assurance.json).
|
||||
Private repositories record their scope and reason instead; Internal repositories
|
||||
follow the tier rules above. Registration obstacles require a dated exception, not
|
||||
a placeholder badge or a claim of passing. A passing badge is the adoption target;
|
||||
enrollment alone does not demonstrate that every criterion is met.
|
||||
*Why:* a public assessment makes security practice gaps visible and accountable.
|
||||
*Verified by:* conformance checker (record, evidence and badge link); maintainer
|
||||
review of the provider's project page, repository identity, and criterion answers.
|
||||
|
||||
## Branch and tag protection
|
||||
|
||||
Protection is configured with **repository rulesets**, never with legacy branch
|
||||
|
||||
@@ -80,3 +80,29 @@ release checklist.
|
||||
applications expose a data-directory override (for example an environment variable)
|
||||
that tests use.
|
||||
*Why:* running tests must be safe on any machine. *Verified by:* review.
|
||||
|
||||
## Parser fuzzing
|
||||
|
||||
**K22-TEST-25 (MUST)** Repositories with complex parsers of untrusted input (including
|
||||
workflow, configuration, protocol, archive, and document parsers) maintain fuzz
|
||||
targets for those boundaries. Use coverage-guided fuzzing where supported; otherwise
|
||||
use generated or mutated inputs with independent security properties. A crash-only
|
||||
smoke test is insufficient when acceptance of unsafe input is the relevant failure.
|
||||
|
||||
Run bounded campaigns in a merge-gating CI job on every PR and push to `main`, and
|
||||
longer campaigns at least weekly and before each MINOR or MAJOR release. Document
|
||||
targets, input limits, invariants, a local replay command, seeds or corpus, CI job,
|
||||
and failure handling in `docs/security/fuzzing.md`; declare the evidence, targets,
|
||||
command and workflow in `docs/security/assurance.json`. Preserve minimized failures
|
||||
as regression tests and fix or triage them before merging. Never execute generated
|
||||
attacker scripts, use real secrets, or write to a user's profile.
|
||||
|
||||
Repositories without a complex untrusted-input parser document that decision and
|
||||
its rationale in the same files; review applicability when input boundaries change
|
||||
and at least every 90 days. Documentation-only status does not exempt shipped tooling.
|
||||
*Why:* malformed inputs can cause unsafe acceptance as well as crashes; repeatable
|
||||
fuzzing exercises combinations ordinary examples miss. *Verified by:* conformance
|
||||
checker (scope record, target paths, command and CI reference); required CI execution
|
||||
and review of properties, corpus and applicability. See the
|
||||
[native PowerShell example](../docs/security/fuzzing.md). Scorecard recognition is
|
||||
useful evidence, but is not a substitute for running appropriate targets.
|
||||
@@ -1,5 +1,8 @@
|
||||
# Contributing to {{PROJECT_NAME}}
|
||||
|
||||
Use professional wording in code comments, documentation, commit/PR messages,
|
||||
release notes and automation output. No emojis, joke copy or bot sound effects.
|
||||
|
||||
Thanks for helping. This project follows the
|
||||
[KofTwentyTwo standards](https://github.com/KofTwentyTwo/standards); this page is the
|
||||
short version of what they ask of a contribution.
|
||||
|
||||
@@ -51,6 +51,11 @@ sha256sum --check SHA256SUMS
|
||||
## Security
|
||||
|
||||
Report vulnerabilities privately; see [SECURITY.md](SECURITY.md).
|
||||
Maintain the [Best Practices assessment](docs/security/best-practices.md) and
|
||||
[parser fuzzing assessment](docs/security/fuzzing.md), with their actual scope and
|
||||
configuration in [assurance.json](docs/security/assurance.json). After enrollment,
|
||||
insert the provider's actual linked badge here; placeholders do not demonstrate
|
||||
participation.
|
||||
|
||||
## Contributing
|
||||
|
||||
|
||||
@@ -0,0 +1,17 @@
|
||||
{
|
||||
"schemaVersion": 1,
|
||||
"badge": {
|
||||
"applicability": "required",
|
||||
"reason": "Replace with this repository's public Product scope, or a reviewed private/Internal rationale.",
|
||||
"projectUrl": null,
|
||||
"evidence": "docs/security/best-practices.md"
|
||||
},
|
||||
"fuzzing": {
|
||||
"applicability": "required",
|
||||
"reason": "Replace with actual untrusted-input boundaries, or a reviewed not-applicable rationale.",
|
||||
"targets": [],
|
||||
"command": "",
|
||||
"workflow": "",
|
||||
"evidence": "docs/security/fuzzing.md"
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,23 @@
|
||||
# OpenSSF Best Practices assessment
|
||||
|
||||
## Project and ownership
|
||||
|
||||
For a public open-source Product repository, register its exact canonical URL at
|
||||
<https://www.bestpractices.dev/en>. Replace `badge.projectUrl` in `assurance.json`
|
||||
with the actual `https://www.bestpractices.dev/projects/<numeric-id>` URL and add the
|
||||
provider's real linked badge to the README. Record the owner, review date and next
|
||||
review within 90 days. Private/Internal scope decisions need a documented rationale.
|
||||
|
||||
## Criterion evidence
|
||||
|
||||
Use the current [passing criteria](https://www.bestpractices.dev/en/criteria/0).
|
||||
For each answer, link actual source, policy, test or observed operational evidence.
|
||||
Separate implemented practice from intentions, pending human attestations, and
|
||||
justified not-applicable answers. Passing is the target, not an automatic claim
|
||||
made by a copied template. Record remaining gaps with owners and follow-up dates.
|
||||
|
||||
## Maintenance
|
||||
|
||||
Reassess at least every 90 days and before each MINOR or MAJOR release. Keep the
|
||||
provider's answers consistent with this evidence. A registration restriction needs
|
||||
a dated approved exception; never use another repository's badge or invent an ID.
|
||||
@@ -0,0 +1,29 @@
|
||||
# Parser fuzzing assessment
|
||||
|
||||
## Applicability
|
||||
|
||||
Record the reviewed complex untrusted-input boundaries and their owner. If none
|
||||
exist, explain why shipped tooling, importers, configuration and protocol handling
|
||||
do not introduce one. Set `fuzzing.applicability` in `assurance.json` to `required`
|
||||
or `not-applicable` with the actual rationale. Unfilled templates do not conform.
|
||||
|
||||
## Targets and properties
|
||||
|
||||
List each target, synthetic input limits, security invariants and corpus or seeds.
|
||||
Use the language's native coverage-guided fuzzer where practical. Generated-input
|
||||
property tests are an alternative when instrumentation is unavailable; assertions
|
||||
must detect unsafe acceptance as well as crashes.
|
||||
|
||||
## Run and replay
|
||||
|
||||
Provide exact local commands, prerequisites, bounded PR/main campaign budgets,
|
||||
weekly and pre-MINOR/MAJOR budgets, and the required CI job. Reference the target
|
||||
paths, command and workflow in `assurance.json`; ensure the command actually runs
|
||||
there rather than merely appearing in a comment. Explain how to replay and minimize
|
||||
failures into regression tests. Never execute generated attacker scripts.
|
||||
|
||||
## Review
|
||||
|
||||
Name the owner, review date and next review (within 90 days). Reassess input
|
||||
boundaries when they change. Do not claim a Scorecard-supported integration unless
|
||||
it is genuinely configured and running; a score alone is not a test result.
|
||||
@@ -44,7 +44,9 @@ if($committedVersion -ne $Version -or $releaseManifest['.'] -ne $Version)
|
||||
|
||||
$changelog = (git -C $repository show "${commit}:CHANGELOG.md") -join "`n"
|
||||
$escapedVersion = [regex]::Escape($Version)
|
||||
$section = [regex]::Match($changelog, "(?ms)^## (?:\[$escapedVersion\](?:\([^\r\n]*\))?|$escapedVersion)(?=\s|$)[^\r\n]*\r?\n.*?(?=^## |\z)")
|
||||
# Native release-note categories also use H2; only a version heading ends a release.
|
||||
$releaseHeading = '^## (?:\[\d+\.\d+\.\d+(?:[-+][0-9A-Za-z.-]+)?\](?:\([^\r\n]*\))?|\d+\.\d+\.\d+(?:[-+][0-9A-Za-z.-]+)?)(?=\s|$)'
|
||||
$section = [regex]::Match($changelog, "(?ms)^## (?:\[$escapedVersion\](?:\([^\r\n]*\))?|$escapedVersion)(?=\s|$)[^\r\n]*\r?\n.*?(?=$releaseHeading|\z)")
|
||||
if(-not $section.Success)
|
||||
{
|
||||
throw "CHANGELOG.md has no release notes for $Version at $commit."
|
||||
|
||||
@@ -0,0 +1,135 @@
|
||||
#Requires -Version 7.4
|
||||
<#
|
||||
.SYNOPSIS
|
||||
Runs bounded, reproducible property tests against the conformance parsers.
|
||||
.DESCRIPTION
|
||||
Constructs workflows with independently known security properties, varying quotes,
|
||||
indentation, comments, line endings, block scalars, and attacker expressions. Inputs
|
||||
are data only: no generated script is executed and no GitHub access is needed.
|
||||
A failure prints the seed, iteration, property, and synthetic input for replay.
|
||||
.PARAMETER Seed
|
||||
Seed for a deterministic sequence of generated cases.
|
||||
.PARAMETER Iterations
|
||||
Number of generated cases; bounded for pull-request CI.
|
||||
#>
|
||||
[CmdletBinding()]
|
||||
param(
|
||||
[int] $Seed = 22023,
|
||||
[ValidateRange(1, 100000)]
|
||||
[int] $Iterations = 1000
|
||||
)
|
||||
|
||||
Set-StrictMode -Version Latest
|
||||
$ErrorActionPreference = 'Stop'
|
||||
. (Join-Path $PSScriptRoot 'Test-RepoConformance.ps1') -LoadHelpers
|
||||
$random = [System.Random]::new($Seed)
|
||||
$assertions = 0
|
||||
$caseNumber = 0
|
||||
|
||||
<#
|
||||
.SYNOPSIS
|
||||
Fail with replay data when an independently generated security property is violated.
|
||||
#>
|
||||
function Assert-Property
|
||||
{
|
||||
param([bool] $Condition, [string] $Property, [string] $InputText)
|
||||
if(-not $Condition)
|
||||
{
|
||||
$replay = [ordered]@{ seed = $Seed; iteration = $caseNumber; property = $Property; input = $InputText }
|
||||
throw ($replay | ConvertTo-Json -Compress)
|
||||
}
|
||||
$script:assertions++
|
||||
}
|
||||
|
||||
<#
|
||||
.SYNOPSIS
|
||||
Generate synthetic hexadecimal action refs using the injected deterministic PRNG.
|
||||
#>
|
||||
function Get-HexString
|
||||
{
|
||||
param([int] $Length)
|
||||
$characters = for($position = 0; $position -lt $Length; $position++)
|
||||
{
|
||||
'0123456789abcdef'[$random.Next(16)]
|
||||
}
|
||||
return -join $characters
|
||||
}
|
||||
|
||||
for($caseNumber = 1; $caseNumber -le $Iterations; $caseNumber++)
|
||||
{
|
||||
$newline = @("`n", "`r`n")[$random.Next(2)]
|
||||
$indent = ' ' * $random.Next(2, 5)
|
||||
$quote = @('', "'", '"')[$random.Next(3)]
|
||||
$sha = Get-HexString 40
|
||||
$uses = 'actions/checkout@' + $sha
|
||||
$pinnedWorkflow = @(
|
||||
'name: generated'
|
||||
'on: pull_request'
|
||||
'permissions: {}'
|
||||
'jobs:'
|
||||
($indent + 'check:')
|
||||
($indent * 2 + 'runs-on: ubuntu-24.04')
|
||||
($indent * 2 + 'steps:')
|
||||
($indent * 3 + '- uses: ' + $quote + $uses + $quote + ' # pinned')
|
||||
($indent * 3 + ' with:')
|
||||
($indent * 3 + ' persist-credentials: false')
|
||||
) -join $newline
|
||||
$parsedUses = @(Get-WorkflowUses $pinnedWorkflow)
|
||||
Assert-Property -Condition ($parsedUses.Count -eq 1) -Property 'one generated action is found' -InputText $pinnedWorkflow
|
||||
Assert-Property -Condition ((Get-UsesPinState $parsedUses[0].Uses) -eq 'PINNED') -Property 'quoted full SHA remains pinned' -InputText $pinnedWorkflow
|
||||
Assert-Property -Condition (@(Find-PersistedCheckout $pinnedWorkflow).Count -eq 0) -Property 'explicit credential disabling is found' -InputText $pinnedWorkflow
|
||||
$unsafeCheckout = $pinnedWorkflow.Replace('persist-credentials: false', 'persist-credentials: true')
|
||||
Assert-Property -Condition (@(Find-PersistedCheckout $unsafeCheckout).Count -eq 1) -Property 'quoted unsafe checkout is never missed' -InputText $unsafeCheckout
|
||||
|
||||
$badRef = @('v7', 'main', (Get-HexString 39), (Get-HexString 41))[$random.Next(4)]
|
||||
Assert-Property -Condition ((Get-UsesPinState ('actions/checkout@' + $badRef)) -ne 'PINNED') -Property 'non-SHA refs never pass pinning' -InputText $badRef
|
||||
|
||||
$expression = @('github.event.pull_request.title', 'github.head_ref', 'inputs.payload')[$random.Next(3)]
|
||||
$indicator = @('|', '|-', '|+', '>', '>-', '>+', '|2', '|2-', '>2+')[$random.Next(9)]
|
||||
$expressionText = '${{ ' + $expression + ' }}'
|
||||
$injectionWorkflow = @(
|
||||
'permissions: {}'
|
||||
'jobs:'
|
||||
($indent + 'check:')
|
||||
($indent * 2 + 'steps:')
|
||||
($indent * 3 + '- run: ' + $indicator + ' # generated scalar')
|
||||
($indent * 3 + ' echo "' + $expressionText + '"')
|
||||
) -join $newline
|
||||
$findings = @(Find-TemplateInjection $injectionWorkflow -IncludeInputs)
|
||||
Assert-Property -Condition ($findings.Count -eq 1) -Property 'untrusted expression in a block scalar is found' -InputText $injectionWorkflow
|
||||
Assert-Property -Condition ($findings[0].Expression -eq $expression) -Property 'finding preserves the controlled expression' -InputText $injectionWorkflow
|
||||
$safeWorkflow = $injectionWorkflow.Replace($expressionText, '${{ github.sha }}')
|
||||
Assert-Property -Condition (@(Find-TemplateInjection $safeWorkflow -IncludeInputs).Count -eq 0) -Property 'trusted expression is not reported' -InputText $safeWorkflow
|
||||
$envWorkflow = @(
|
||||
'jobs:'
|
||||
($indent + 'check:')
|
||||
($indent * 2 + 'steps:')
|
||||
($indent * 3 + '- env:')
|
||||
($indent * 3 + ' TITLE: ' + $expressionText)
|
||||
($indent * 3 + ' run: echo "$TITLE"')
|
||||
) -join $newline
|
||||
Assert-Property -Condition (@(Find-TemplateInjection $envWorkflow -IncludeInputs).Count -eq 0) -Property 'expressions in env stay outside run bodies' -InputText $envWorkflow
|
||||
|
||||
$shortName = 'generated-' + $random.Next(100000).ToString([Globalization.CultureInfo]::InvariantCulture)
|
||||
$requirement = 'K22-CI-' + $random.Next(10, 25).ToString('00', [Globalization.CultureInfo]::InvariantCulture)
|
||||
$exception = '| [EX-0099](#ex-0099) | Open | ' + $requirement + ' | KofTwentyTwo/' + $shortName + ' | 2099-01-01 |'
|
||||
$register = @(Read-ExceptionRegister $exception)
|
||||
Assert-Property -Condition ($register.Count -eq 1) -Property 'generated exception row is parsed' -InputText $exception
|
||||
Assert-Property -Condition ($null -ne (Find-Exception $register $requirement ('KofTwentyTwo/' + $shortName))) -Property 'exact exception scope matches' -InputText $exception
|
||||
Assert-Property -Condition ($null -eq (Find-Exception $register $requirement ('KofTwentyTwo/' + $shortName + '-other'))) -Property 'partial repository names cannot gain an exception' -InputText $exception
|
||||
Assert-Property -Condition ($null -eq (Find-Exception $register $requirement ('AnotherOwner/' + $shortName))) -Property 'explicit scope cannot authorize another owner' -InputText $exception
|
||||
$closed = @(Read-ExceptionRegister $exception.Replace('| Open |', '| Closed |'))
|
||||
Assert-Property -Condition ($null -eq (Find-Exception $closed $requirement ('KofTwentyTwo/' + $shortName))) -Property 'closed exceptions cannot authorize a failure' -InputText $exception
|
||||
$expired = @(Read-ExceptionRegister $exception.Replace('2099-01-01', '2026-11-10'))
|
||||
Assert-Property -Condition ($null -eq (Find-Exception $expired $requirement ('KofTwentyTwo/' + $shortName) -Today ([datetime] '2026-11-10'))) -Property 'expired exceptions cannot authorize a failure' -InputText $exception
|
||||
|
||||
$noise = -join (1..$random.Next(1, 128) | ForEach-Object { [char] $random.Next(32, 127) })
|
||||
foreach($parser in 'Get-WorkflowUses', 'Get-TopLevelPermissions', 'Get-RunBlocks', 'Find-TemplateInjection', 'Find-PersistedCheckout', 'Read-ExceptionRegister')
|
||||
{
|
||||
& $parser $noise | Out-Null
|
||||
}
|
||||
Get-AssuranceCheck $noise @() @{} '' | Out-Null
|
||||
$assertions++
|
||||
}
|
||||
|
||||
Write-Output "PASS: conformance fuzz seed=$Seed iterations=$Iterations assertions=$assertions"
|
||||
@@ -4,8 +4,9 @@
|
||||
Checks a repository against the KofTwentyTwo repository and CI/CD standards.
|
||||
|
||||
.DESCRIPTION
|
||||
Evaluates every automatable requirement in standards/repository.md (K22-REPO-*)
|
||||
and standards/ci-cd.md (K22-CI-*) and prints one row per requirement:
|
||||
Evaluates automatable requirements in standards/repository.md (K22-REPO-*),
|
||||
standards/ci-cd.md (K22-CI-*), and the K22-TEST-25 fuzzing scope record, printing
|
||||
one row per requirement:
|
||||
|
||||
PASS the requirement is met
|
||||
FAIL a MUST is not met (or a SHOULD, reported as WARN instead)
|
||||
@@ -45,6 +46,9 @@
|
||||
.PARAMETER SelfTest
|
||||
Run the checker's own unit tests and exit.
|
||||
|
||||
.PARAMETER LoadHelpers
|
||||
Load pure parser helpers for the fuzz harness without checking a repository.
|
||||
|
||||
.EXAMPLE
|
||||
./tools/Test-RepoConformance.ps1 -Repository KofTwentyTwo/AppKit -LocalPath ../AppKit
|
||||
|
||||
@@ -58,7 +62,8 @@ param(
|
||||
[switch] $StaticOnly,
|
||||
[string] $StandardsPath = (Split-Path -Parent $PSScriptRoot),
|
||||
[switch] $Json,
|
||||
[switch] $SelfTest
|
||||
[switch] $SelfTest,
|
||||
[switch] $LoadHelpers
|
||||
)
|
||||
|
||||
Set-StrictMode -Version Latest
|
||||
@@ -72,7 +77,8 @@ $script:Levels = [ordered]@{
|
||||
'K22-REPO-01' = 'MUST'; 'K22-REPO-02' = 'MUST'; 'K22-REPO-03' = 'MUST'
|
||||
'K22-REPO-04' = 'MUST'; 'K22-REPO-05' = 'MUST'; 'K22-REPO-06' = 'MUST'
|
||||
'K22-REPO-07' = 'MUST'; 'K22-REPO-08' = 'MUST'; 'K22-REPO-09' = 'MUST'
|
||||
'K22-REPO-10' = 'SHOULD'; 'K22-REPO-11' = 'SHOULD'; 'K22-REPO-20' = 'MUST'
|
||||
'K22-REPO-10' = 'SHOULD'; 'K22-REPO-11' = 'SHOULD'; 'K22-REPO-12' = 'MUST'
|
||||
'K22-TEST-25' = 'MUST'; 'K22-REPO-20' = 'MUST'
|
||||
'K22-REPO-21' = 'MUST'; 'K22-REPO-22' = 'MUST'; 'K22-REPO-30' = 'MUST'
|
||||
'K22-REPO-31' = 'MUST'; 'K22-REPO-32' = 'SHOULD'; 'K22-REPO-40' = 'MUST'
|
||||
'K22-REPO-41' = 'SHOULD'; 'K22-REPO-42' = 'MUST'
|
||||
@@ -169,7 +175,7 @@ function Get-RunBlocks
|
||||
{
|
||||
$indent = $Matches[1].Length
|
||||
$rest = $Matches[2]
|
||||
if ($rest -and $rest -notmatch '^[|>][+-]?\s*$')
|
||||
if ($rest -and $rest -notmatch '^[|>](?:[1-9][+-]?|[+-][1-9]?|[+-]?)\s*(?:#.*)?$')
|
||||
{
|
||||
[pscustomobject]@{ Line = $i + 1; Body = $rest }
|
||||
continue
|
||||
@@ -226,7 +232,7 @@ function Find-PersistedCheckout
|
||||
$lines = $Text -split "`r?`n"
|
||||
for ($i = 0; $i -lt $lines.Count; $i++)
|
||||
{
|
||||
if ($lines[$i] -match '^(\s*)(-\s+)?uses:\s*actions/checkout@')
|
||||
if ($lines[$i] -match '^(\s*)(-\s+)?uses:\s*["'']?actions/checkout@')
|
||||
{
|
||||
$stepIndent = $Matches[1].Length
|
||||
$ok = $false
|
||||
@@ -263,6 +269,7 @@ function Read-ExceptionRegister
|
||||
Requirements = @($qualifiers.Keys)
|
||||
Qualifiers = $qualifiers
|
||||
Scope = $cells[3]
|
||||
Expires = if ($cells.Count -ge 5) { $cells[4] } else { '' }
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -270,10 +277,18 @@ function Read-ExceptionRegister
|
||||
# Returns the open exception covering a requirement for a repository, or $null.
|
||||
function Find-Exception
|
||||
{
|
||||
param([object[]] $Register, [string] $RequirementId, [string] $RepositoryName, [string[]] $Aspects = @())
|
||||
param([object[]] $Register, [string] $RequirementId, [string] $RepositoryName, [string[]] $Aspects = @(), [datetime] $Today = [datetime]::UtcNow.Date)
|
||||
foreach ($ex in $Register)
|
||||
{
|
||||
if ($ex.Status -notmatch '^Open$') { continue }
|
||||
# Review dates are not expiry dates. Enforce an explicit ISO expiry or an
|
||||
# "at the latest" deadline; condition-only expiry still needs human review.
|
||||
if ($ex.Expires -match '(?i)(?:^|at (?:the )?latest\s+)(\d{4}-\d{2}-\d{2})')
|
||||
{
|
||||
$deadline = [datetime]::MinValue
|
||||
if (-not [datetime]::TryParseExact($Matches[1], 'yyyy-MM-dd', [Globalization.CultureInfo]::InvariantCulture, [Globalization.DateTimeStyles]::None, [ref] $deadline)) { continue }
|
||||
if ($Today.Date -ge $deadline.Date) { continue }
|
||||
}
|
||||
if ($ex.Requirements -notcontains $RequirementId) { continue }
|
||||
# A qualified exception covers a failure only when every failing aspect is
|
||||
# the qualified one.
|
||||
@@ -285,7 +300,8 @@ function Find-Exception
|
||||
}
|
||||
$short = if ($RepositoryName) { ($RepositoryName -split '/')[-1] } else { '' }
|
||||
if ($ex.Scope -match '(?i)^all repositories' -or
|
||||
($short -and $ex.Scope -match "(?i)(^|[\s/,(])$([regex]::Escape($short))($|[\s,)])"))
|
||||
($RepositoryName -and $ex.Scope -match "(?i)(^|[\s,(])$([regex]::Escape($RepositoryName))($|[\s,)])") -or
|
||||
($short -and $ex.Scope -match "(?i)(^|[\s,(])$([regex]::Escape($short))($|[\s,)])"))
|
||||
{
|
||||
return $ex
|
||||
}
|
||||
@@ -293,6 +309,78 @@ function Find-Exception
|
||||
return $null
|
||||
}
|
||||
|
||||
<#
|
||||
.SYNOPSIS
|
||||
Validate declared assurance evidence without executing commands or claiming certification.
|
||||
#>
|
||||
function Get-AssuranceCheck
|
||||
{
|
||||
param([string] $Text, [string[]] $Paths, [hashtable] $Workflows, [string] $Readme)
|
||||
try
|
||||
{
|
||||
$record = ConvertFrom-Json -InputObject $Text -AsHashtable -ErrorAction Stop
|
||||
if ($record -isnot [System.Collections.IDictionary] -or $record['schemaVersion'] -isnot [long] -or $record['schemaVersion'] -ne 1) { throw 'unsupported schema' }
|
||||
}
|
||||
catch
|
||||
{
|
||||
foreach ($id in 'K22-REPO-12', 'K22-TEST-25')
|
||||
{
|
||||
[pscustomobject]@{ Id = $id; Status = 'FAIL'; Detail = 'missing or invalid docs/security/assurance.json (schemaVersion 1)'; Aspects = @('record') }
|
||||
}
|
||||
return
|
||||
}
|
||||
foreach ($pair in @(@('badge', 'K22-REPO-12'), @('fuzzing', 'K22-TEST-25')))
|
||||
{
|
||||
$kind, $id = $pair
|
||||
$entry = $record[$kind]
|
||||
$status, $detail, $aspect = 'FAIL', 'invalid assurance scope, rationale or evidence path', 'record'
|
||||
if ($entry -is [System.Collections.IDictionary] -and
|
||||
$entry['applicability'] -in @('required', 'not-applicable') -and
|
||||
$entry['reason'] -is [string] -and -not [string]::IsNullOrWhiteSpace($entry['reason']) -and
|
||||
$entry['evidence'] -is [string] -and $entry['evidence'] -cmatch '^docs/security/[A-Za-z0-9._/-]+\.md$' -and
|
||||
$entry['evidence'] -notmatch '(^|/)\.\.?(/|$)' -and $Paths -ccontains $entry['evidence'])
|
||||
{
|
||||
if ($entry['applicability'] -eq 'not-applicable')
|
||||
{
|
||||
$status, $detail = 'REVIEW', "$kind scope declared not applicable; verify rationale and input/public-Product scope in $($entry.evidence)"
|
||||
}
|
||||
elseif ($kind -eq 'badge')
|
||||
{
|
||||
$aspect = 'enrollment'
|
||||
$detail = 'badge enrollment missing; register this repository or obtain a dated enrollment exception'
|
||||
if ($entry['projectUrl'] -is [string] -and $entry['projectUrl'] -cmatch '^https://www\.bestpractices\.dev/(?:en/)?projects/([1-9][0-9]*)/?$')
|
||||
{
|
||||
$projectId = $Matches[1]
|
||||
$badgePattern = '\[!\[[^\]]*\]\(https://www\.bestpractices\.dev/projects/' + $projectId + '/badge\)\]\(https://www\.bestpractices\.dev/(?:en/)?projects/' + $projectId + '/?\)'
|
||||
if ($Readme -cmatch $badgePattern)
|
||||
{
|
||||
$status, $detail = 'REVIEW', "badge linked; verify $($entry.projectUrl) belongs to this repository, current answers and 90-day review in $($entry.evidence)"
|
||||
}
|
||||
else { $detail = 'README lacks the actual linked badge matching the declared project ID' }
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
$aspect = 'fuzzing'
|
||||
$targets = @($entry['targets'])
|
||||
$validTargets = $targets.Count -gt 0 -and @($targets | Where-Object {
|
||||
$_ -isnot [string] -or $_ -match '(^[/\\]|^[A-Za-z]:|(^|[/\\])\.\.?([/\\]|$))' -or $Paths -cnotcontains $_
|
||||
}).Count -eq 0
|
||||
$validWorkflow = $entry['workflow'] -is [string] -and $entry['workflow'] -cmatch '^\.github/workflows/[^/]+\.ya?ml$' -and $Workflows.ContainsKey($entry['workflow'])
|
||||
$validCommand = $entry['command'] -is [string] -and -not [string]::IsNullOrWhiteSpace($entry['command']) -and $entry['command'] -notmatch '[\r\n]'
|
||||
$runs = if ($validWorkflow) { @(Get-RunBlocks $Workflows[$entry['workflow']] | ForEach-Object Body) -join "`n" } else { '' }
|
||||
$commandInRun = $validCommand -and $runs -cmatch ('(?m)^\s*' + [regex]::Escape($entry['command'].Trim()) + '(?:\s|$)')
|
||||
if ($validTargets -and $validWorkflow -and $commandInRun)
|
||||
{
|
||||
$status, $detail = 'REVIEW', "fuzz targets and run command found; verify required PR/main gate, weekly/release budgets, properties and replay in $($entry.evidence)"
|
||||
}
|
||||
else { $detail = 'declared fuzz targets must exist and the command must begin a run line in the declared workflow' }
|
||||
}
|
||||
}
|
||||
[pscustomobject]@{ Id = $id; Status = $status; Detail = $detail; Aspects = @($aspect) }
|
||||
}
|
||||
}
|
||||
|
||||
# Lock files expected for each manifest found (K22-REPO-09).
|
||||
$script:LockRules = @(
|
||||
@{ Manifest = '(^|/)[^/]+\.(cs|fs|vb)proj$|(^|/)Directory\.Packages\.props$'; Locks = 'packages\.lock\.json$'; Name = 'NuGet (packages.lock.json)' }
|
||||
@@ -395,6 +483,8 @@ jobs:
|
||||
Assert ($inj[1].Expression -eq 'github.head_ref') 'injection: head_ref'
|
||||
$persisted = @(Find-PersistedCheckout $wf)
|
||||
Assert ($persisted.Count -eq 1 -and $persisted[0] -eq 9) "checkout: persisted ($($persisted -join ','))"
|
||||
Assert (@(Find-PersistedCheckout "jobs:`n check:`n steps:`n - uses: 'actions/checkout@v7'`n with:`n persist-credentials: true").Count -eq 1) 'checkout: quoted unsafe action'
|
||||
Assert (@(Find-TemplateInjection "jobs:`n check:`n steps:`n - run: |2 # explicit indentation`n echo `${{ github.head_ref }}").Count -eq 1) 'injection: block scalar indentation and comment'
|
||||
|
||||
$register = @"
|
||||
| ID | Status | Requirements | Scope | Expires |
|
||||
@@ -416,6 +506,24 @@ jobs:
|
||||
Assert ($null -eq (Find-Exception $parsed 'K22-REPO-05' 'KofTwentyTwo/AppKit')) 'exception: closed'
|
||||
Assert ((Find-Exception $parsed 'K22-CODE-CS-03' 'KofTwentyTwo/AppKit').Id -eq 'EX-0004') 'exception: short name scope'
|
||||
Assert ($null -eq (Find-Exception $parsed 'K22-CODE-CS-03' 'KofTwentyTwo/AppKitExtras')) 'exception: no partial name match'
|
||||
$expired = @(Read-ExceptionRegister '| [EX-0099](#ex-0099) | Open | K22-REPO-12 | standards | 2026-11-10; review 2026-10-17 |')
|
||||
Assert ($null -ne (Find-Exception $expired 'K22-REPO-12' 'standards' -Today ([datetime] '2026-11-09'))) 'exception: valid before deadline'
|
||||
Assert ($null -eq (Find-Exception $expired 'K22-REPO-12' 'standards' -Today ([datetime] '2026-11-10'))) 'exception: expiry cannot silently authorize failure'
|
||||
$exactScope = @(Read-ExceptionRegister '| [EX-0099](#ex-0099) | Open | K22-REPO-12 | KofTwentyTwo/standards | 2099-01-01 |')
|
||||
Assert ($null -eq (Find-Exception $exactScope 'K22-REPO-12' 'AnotherOwner/standards')) 'exception: explicit owner scope cannot match another owner'
|
||||
Assert (@(Get-AssuranceCheck '{}' @() @{} '').Count -eq 2) 'assurance: missing schema returns both failures'
|
||||
Assert (@(Get-AssuranceCheck '{"schemaVersion":true}' @() @{} '' | Where-Object Status -eq 'FAIL').Count -eq 2) 'assurance: boolean cannot masquerade as schema version 1'
|
||||
Assert (@(Get-AssuranceCheck '{"schemaVersion":1,"badge":{},"fuzzing":{}}' @() @{} '' | Where-Object Status -eq 'FAIL').Count -eq 2) 'assurance: incomplete entries fail without crashing'
|
||||
$assurance = '{"schemaVersion":1,"badge":{"applicability":"required","reason":"public Product","projectUrl":"https://www.bestpractices.dev/projects/123","evidence":"docs/security/best-practices.md"},"fuzzing":{"applicability":"required","reason":"parser","targets":["tools/parser.ps1"],"command":"./tools/fuzz.ps1","workflow":".github/workflows/ci.yml","evidence":"docs/security/fuzzing.md"}}'
|
||||
$assurancePaths = @('tools/parser.ps1', 'docs/security/best-practices.md', 'docs/security/fuzzing.md')
|
||||
$assuranceWorkflow = @{ '.github/workflows/ci.yml' = "jobs:`n tests:`n steps:`n - run: ./tools/fuzz.ps1 -Iterations 10" }
|
||||
$assuranceReadme = '[](https://www.bestpractices.dev/projects/123)'
|
||||
$assuranceRows = @(Get-AssuranceCheck $assurance $assurancePaths $assuranceWorkflow $assuranceReadme)
|
||||
Assert (@($assuranceRows | Where-Object Status -ne 'REVIEW').Count -eq 0) 'assurance: evidence needs human verification, never fabricated certification'
|
||||
Assert (@(Get-AssuranceCheck $assurance $assurancePaths $assuranceWorkflow '' | Where-Object { $_.Id -eq 'K22-REPO-12' -and $_.Status -eq 'FAIL' }).Count -eq 1) 'assurance: missing badge fails'
|
||||
$assuranceWorkflow['.github/workflows/ci.yml'] = "jobs:`n tests:`n steps:`n - run: |`n # ./tools/fuzz.ps1"
|
||||
Assert (@(Get-AssuranceCheck $assurance $assurancePaths $assuranceWorkflow $assuranceReadme | Where-Object { $_.Id -eq 'K22-TEST-25' -and $_.Status -eq 'FAIL' }).Count -eq 1) 'assurance: comment does not execute a fuzz command'
|
||||
Assert (@(Get-AssuranceCheck ($assurance.Replace('tools/parser.ps1', '../parser.ps1')) $assurancePaths $assuranceWorkflow $assuranceReadme | Where-Object { $_.Id -eq 'K22-TEST-25' -and $_.Status -eq 'FAIL' }).Count -eq 1) 'assurance: traversal paths cannot satisfy targets'
|
||||
|
||||
Assert (@(Get-MissingLocks @('src/a/a.csproj', 'README.md')) -contains 'NuGet (packages.lock.json)') 'locks: missing nuget'
|
||||
Assert (@(Get-MissingLocks @('src/a/a.csproj', 'src/a/packages.lock.json')).Count -eq 0) 'locks: nuget ok'
|
||||
@@ -450,6 +558,7 @@ jobs:
|
||||
exit 0
|
||||
}
|
||||
|
||||
if ($LoadHelpers) { return }
|
||||
if ($SelfTest) { Invoke-SelfTest }
|
||||
|
||||
# =================================================================================
|
||||
@@ -534,6 +643,13 @@ function Find-FirstPath
|
||||
}
|
||||
|
||||
$repoName = if ($Repository) { $Repository } else { Split-Path -Leaf $script:LocalRoot }
|
||||
if (-not $Repository)
|
||||
{
|
||||
# Local static checks can identify exact exception scope without contacting
|
||||
# GitHub. A clone with no canonical GitHub origin keeps its directory name.
|
||||
$origin = & git -C $script:LocalRoot remote get-url origin 2>$null
|
||||
if ($LASTEXITCODE -eq 0 -and $origin -match '^(?:https://github\.com/|git@github\.com:|ssh://git@(?:github\.com(?::22)?|ssh\.github\.com:443)/)([^/]+/[^/]+?)(?:\.git)?$') { $repoName = $Matches[1] }
|
||||
}
|
||||
|
||||
# =================================================================================
|
||||
# Static checks: required files (K22-REPO-01 .. 11)
|
||||
@@ -662,6 +778,15 @@ $workflows = @{}
|
||||
foreach ($p in $workflowPaths) { $workflows[$p] = Get-FileText $p }
|
||||
$allWorkflowText = ($workflows.Values -join "`n")
|
||||
|
||||
# Assurance records fail closed when required evidence is absent. A positive static
|
||||
# check is REVIEW: identity, applicability and actual CI execution need verification.
|
||||
$assuranceReadme = if ($readmePath) { Get-FileText $readmePath } else { '' }
|
||||
if ($assuranceReadme -match '(?im)\bTier(?:\*\*)?\s*[:|]\s*(?:\*\*)?\s*Internal\b') { $script:Levels['K22-REPO-12'] = 'SHOULD' }
|
||||
foreach ($check in Get-AssuranceCheck (Get-FileText 'docs/security/assurance.json') $script:Paths $workflows $assuranceReadme)
|
||||
{
|
||||
Add-Result $check.Id $check.Status $check.Detail $check.Aspects
|
||||
}
|
||||
|
||||
function Test-Calls
|
||||
{
|
||||
# True when any workflow calls the named standards reusable workflow (by remote
|
||||
|
||||
@@ -63,6 +63,14 @@ Future notes.
|
||||
|
||||
Current notes.
|
||||
|
||||
## Changes
|
||||
|
||||
Native category notes.
|
||||
|
||||
## Breaking changes
|
||||
|
||||
Current breaking details.
|
||||
|
||||
## 1.2.2
|
||||
|
||||
Older notes.
|
||||
@@ -100,6 +108,7 @@ $manifest = Get-Content -LiteralPath (Join-Path $first "$prefix.manifest.json")
|
||||
Test-Condition ($manifest.version -eq '1.2.3' -and $manifest.commit -eq (git -C $source rev-parse HEAD)) 'manifest identifies version and exact source commit'
|
||||
$notes = Get-Content -LiteralPath (Join-Path $first "$prefix.notes.md") -Raw
|
||||
Test-Condition ($notes -match 'Current notes' -and $notes -notmatch 'Older notes|Future notes' -and $notes.Contains($checklist)) 'notes contain only this release and its checklist'
|
||||
Test-Condition ($notes -match 'Native category notes' -and $notes -match 'Current breaking details') 'notes preserve native H2 categories within a release'
|
||||
foreach($file in (Get-ChildItem -LiteralPath $first -File))
|
||||
{
|
||||
Test-Condition ((Get-FileHash -LiteralPath $file.FullName).Hash -eq (Get-FileHash -LiteralPath (Join-Path $second $file.Name)).Hash) "$($file.Name) is reproducible"
|
||||
@@ -115,6 +124,14 @@ Test-Exception { & $builder -Version '1.2.4' -RepositoryPath $source -OutputPath
|
||||
Test-Exception { & $builder -Version '01.2.3' -RepositoryPath $source } 'ValidatePattern|validation|pattern'
|
||||
Test-Exception { & $builder -Version '1.2.3' -RepositoryPath $source -OutputPath $first } 'already exists'
|
||||
Test-Exception { & $builder -Version '1.2.3' -RepositoryPath $source -OutputPath (Join-Path $fixtureRoot 'invalid-url') -ChecklistUrl 'http://example.com/123' } 'GitHub issue URL'
|
||||
$nativeChangelog = (Get-Content -LiteralPath (Join-Path $source 'CHANGELOG.md') -Raw).Replace('## [1.2.3](https://example.com/current)', '## 1.2.3')
|
||||
Set-Content -LiteralPath (Join-Path $source 'CHANGELOG.md') -Value $nativeChangelog
|
||||
git -C $source add -- CHANGELOG.md
|
||||
git -C $source -c user.name=Fixture -c user.email=fixture@example.invalid -c commit.gpgsign=false commit --quiet -m 'test: native release heading fixture'
|
||||
$nativeOutput = Join-Path $fixtureRoot 'native-headings'
|
||||
& $builder -Version '1.2.3' -RepositoryPath $source -OutputPath $nativeOutput
|
||||
$nativeNotes = Get-Content -LiteralPath (Join-Path $nativeOutput "$prefix.notes.md") -Raw
|
||||
Test-Condition ($nativeNotes -match 'Native category notes' -and $nativeNotes -match 'Current breaking details' -and $nativeNotes -notmatch 'Older notes|Future notes') 'native unlinked version heading retains only its release categories'
|
||||
Set-Content -LiteralPath (Join-Path $source 'CHANGELOG.md') -Value '# Changelog without release notes'
|
||||
git -C $source add -- CHANGELOG.md
|
||||
git -C $source -c user.name=Fixture -c user.email=fixture@example.invalid -c commit.gpgsign=false commit --quiet -m 'test: missing release notes fixture'
|
||||
|
||||
+1
-1
@@ -1 +1 @@
|
||||
0.1.0
|
||||
1.0.0
|
||||
Reference in new issue
Block a user