Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
54 changes: 34 additions & 20 deletions .claude/commands/audit-security.md
Original file line number Diff line number Diff line change
Expand Up @@ -25,11 +25,14 @@ component that can be released but has no SBOM definition is a release that
ships without an inventory.

```bash
node --test scripts/generate-sbom.test.mjs
node --test scripts/generate-sbom.test.mjs scripts/audit-security.test.mjs

SECURITY_AUDIT_ROOT=$(mktemp -d)
export SECURITY_AUDIT_ROOT
Comment thread
hyochan marked this conversation as resolved.
for c in $(node -e 'import("./scripts/generate-sbom.mjs").then(m=>console.log(m.listComponentIds().join(" ")))'); do
printf "%-14s " "$c"
node scripts/generate-sbom.mjs "$c" --output-dir /tmp/sbom-audit || echo "FAILED"
node scripts/generate-sbom.mjs "$c" \
--output-dir "$SECURITY_AUDIT_ROOT/core-a" || echo "FAILED"
done
```

Expand All @@ -39,7 +42,7 @@ declaration shape the reader does not model — fix the reader, never silence it
## 2. Schema validity

```bash
for f in /tmp/sbom-audit/*.cdx.json; do
for f in "$SECURITY_AUDIT_ROOT"/core-a/*.cdx.json; do
cyclonedx validate --input-file "$f" --input-format json \
--input-version v1_6 --fail-on-errors
done
Expand All @@ -54,9 +57,14 @@ are the baseline OpenSSF recommends measuring against. Check author, timestamp,
and per-component name, version, purl, supplier, and dependency relationships:

```bash
for c in $(node -e 'import("./scripts/generate-sbom.mjs").then(m=>console.log(m.listComponentIds().join(" ")))'); do
node scripts/generate-sbom.mjs "$c" --with-licenses \
--output-dir "$SECURITY_AUDIT_ROOT/enriched"
done

node -e '
const fs = require("fs");
const dir = "/tmp/sbom-audit";
const dir = `${process.env.SECURITY_AUDIT_ROOT}/enriched`;
let tot = 0, sup = 0, lic = 0, purl = 0, auth = 0, files = 0;
for (const f of fs.readdirSync(dir)) {
const j = JSON.parse(fs.readFileSync(`${dir}/${f}`, "utf8"));
Expand All @@ -77,23 +85,24 @@ console.log(`component license: ${lic}/${tot}`);
```

Regenerate with `--with-licenses` when auditing supplier and license coverage;
without it those fields are intentionally absent so local runs stay offline.
without it those fields are intentionally absent. Maven and NuGet inventories
still read their published dependency descriptors.

Known structural gaps, which are **not** findings: pub.dev exposes neither
license nor supplier in package metadata, and some NuGet packages carry only a
non-SPDX license URL.

Optionally score the result with
[`sbomqs`](https://github.com/interlynk-io/sbomqs):
`sbomqs score /tmp/sbom-audit/*.cdx.json`.
`sbomqs score "$SECURITY_AUDIT_ROOT"/core-a/*.cdx.json`.

## 4. No leaked paths or secrets

A published SBOM is a public document about a private filesystem.

```bash
grep -rlE '/Users/|/home/[a-z]|/tmp/|ghp_|npm_[A-Za-z0-9]|BEGIN [A-Z ]*PRIVATE KEY' \
/tmp/sbom-audit/ && echo "LEAK" || echo "clean"
"$SECURITY_AUDIT_ROOT/core-a" && echo "LEAK" || echo "clean"
```

## 5. Core determinism
Expand All @@ -103,22 +112,31 @@ generator commit, and resolver input. Do not pass `--with-licenses` here: live
registry license and supplier metadata is point-in-time enrichment.

```bash
node scripts/generate-sbom.mjs google --output-dir /tmp/sbom-audit-2
diff /tmp/sbom-audit/openiap-google-*.cdx.json /tmp/sbom-audit-2/openiap-google-*.cdx.json
node scripts/generate-sbom.mjs google \
--output-dir "$SECURITY_AUDIT_ROOT/core-b"
diff "$SECURITY_AUDIT_ROOT"/core-a/openiap-google-*.cdx.json \
"$SECURITY_AUDIT_ROOT"/core-b/openiap-google-*.cdx.json
```

## 6. Workflow permissions and injection

Least privilege, and no untrusted value interpolated into a shell command:

```bash
# Any ${{ }} inside a run: block is a potential injection point
for f in .github/workflows/*.yml; do
awk '/^\s+run:/{r=1} /^\s+- name:|^\s+uses:/{r=0} r && /\$\{\{/ {print FILENAME": "$0}' "$f"
done
# Any ${{ }} inside a run: block is a potential injection point. The parser has
# fault tests, so an empty result cannot come from unsupported awk syntax.
node scripts/audit-security.mjs workflows \
$(rg --files .github/workflows -g '*.yml')

# Workflows that write must say so explicitly
grep -L "^permissions:" .github/workflows/*.yml

# Mutable action references in privileged workflow code
rg -n 'uses:\s+[^#]+@(v[0-9]+|main|master)$' .github/workflows

# The repository dependency graph endpoint is currently unavailable (HTTP 404)
gh api repos/hyodotdev/openiap/dependency-graph/sbom || \
echo "Dependency graph SBOM endpoint unavailable"
```

Pass values through `env:` instead of interpolating them. OpenSSF Scorecard's
Expand Down Expand Up @@ -147,13 +165,9 @@ import("./scripts/generate-sbom.mjs").then((m) => {
'

# External references must resolve
grep -rhoE "https?://[^)\" ]+" security/*.md security/vex/*.md \
packages/docs/src/pages/docs/security/*.tsx |
sed 's/[.,)"]*$//' | sort -u |
while read -r u; do
code=$(curl -sS -o /dev/null -w "%{http_code}" -L --max-time 20 "$u")
[ "$code" = "200" ] || echo "$code $u"
done
node scripts/audit-security.mjs urls \
$(rg --files security packages/docs/src/pages/docs/security \
-g '*.md' -g '*.tsx')
```

Also check for **hardcoded counts** — "nine workflows", "43 of 47
Expand Down
1 change: 1 addition & 0 deletions .github/workflows/ci.yml
Original file line number Diff line number Diff line change
Expand Up @@ -35,6 +35,7 @@ jobs:
scripts/npm-publish-authorization.test.mjs
scripts/verify-npm-release-provenance.test.mjs
scripts/generate-sbom.test.mjs
scripts/audit-security.test.mjs

- name: Test Gradle network retry helper
run: node --test scripts/ci/retry-gradle.test.mjs
Expand Down
10 changes: 10 additions & 0 deletions .github/workflows/release-apple.yml
Original file line number Diff line number Diff line change
Expand Up @@ -82,6 +82,9 @@ jobs:

release:
needs: [validate-ios]
permissions:
actions: write
contents: write
runs-on: macos-15

steps:
Expand Down Expand Up @@ -460,3 +463,10 @@ jobs:
$PRERELEASE_FLAG \
--notes-file /tmp/release-notes.md
fi

- name: Dispatch SBOM
if: inputs.publish_spm == true
env:
GH_TOKEN: ${{ secrets.GITHUB_TOKEN }}
RELEASE_TAG: ${{ steps.version.outputs.version }}
run: gh workflow run sbom.yml --ref main -f tag="$RELEASE_TAG"
7 changes: 7 additions & 0 deletions .github/workflows/release-conformance.yml
Original file line number Diff line number Diff line change
Expand Up @@ -97,6 +97,7 @@ jobs:
name: Bump, tag, and release
needs: [validate]
permissions:
actions: write
contents: write
runs-on: ubuntu-latest
defaults:
Expand Down Expand Up @@ -311,6 +312,12 @@ jobs:
prerelease: ${{ steps.bump.outputs.is_prerelease }}
body_path: /tmp/release-notes.md

- name: Dispatch SBOM
env:
GH_TOKEN: ${{ secrets.GITHUB_TOKEN }}
RELEASE_TAG: openiap-conformance-${{ steps.bump.outputs.version }}
run: gh workflow run sbom.yml --ref main -f tag="$RELEASE_TAG"

# npm provenance reads the immutable workflow event GITHUB_SHA. Dispatch
# this same trusted-publisher workflow on the tag so the event SHA, npm
# package gitHead, and release tag all identify the same source commit.
Expand Down
7 changes: 7 additions & 0 deletions .github/workflows/release-expo.yml
Original file line number Diff line number Diff line change
Expand Up @@ -161,6 +161,7 @@ jobs:
deploy:
needs: [validate-android, validate-ios]
permissions:
actions: write
contents: write
runs-on: ubuntu-latest
defaults:
Expand Down Expand Up @@ -429,6 +430,12 @@ jobs:
prerelease: ${{ steps.bump.outputs.is_prerelease }}
body_path: /tmp/release-notes.md

- name: Dispatch SBOM
env:
GH_TOKEN: ${{ secrets.GITHUB_TOKEN }}
RELEASE_TAG: expo-iap-${{ steps.bump.outputs.version }}
run: gh workflow run sbom.yml --ref main -f tag="$RELEASE_TAG"

# npm provenance reads the immutable workflow event GITHUB_SHA. Dispatch
# this same trusted-publisher workflow on the tag so the event SHA, npm
# package gitHead, and release tag all identify the same source commit.
Expand Down
7 changes: 7 additions & 0 deletions .github/workflows/release-flutter.yml
Original file line number Diff line number Diff line change
Expand Up @@ -141,6 +141,7 @@ jobs:
deploy:
needs: [validate-android, validate-ios, validate-apple-swiftpm]
permissions:
actions: write
contents: write
runs-on: ubuntu-latest
defaults:
Expand Down Expand Up @@ -547,3 +548,9 @@ jobs:
draft: false
prerelease: ${{ steps.bump.outputs.is_prerelease }}
body_path: /tmp/release-notes.md

- name: Dispatch SBOM
env:
GH_TOKEN: ${{ secrets.GITHUB_TOKEN }}
RELEASE_TAG: flutter-iap-${{ steps.bump.outputs.version }}
run: gh workflow run sbom.yml --ref main -f tag="$RELEASE_TAG"
9 changes: 9 additions & 0 deletions .github/workflows/release-godot.yml
Original file line number Diff line number Diff line change
Expand Up @@ -119,6 +119,9 @@ jobs:

deploy:
needs: [validate-android, validate-ios]
permissions:
actions: write
contents: write
runs-on: macos-15
defaults:
run:
Expand Down Expand Up @@ -524,3 +527,9 @@ jobs:
draft: false
prerelease: ${{ steps.version.outputs.is_prerelease }}
body_path: /tmp/release-notes.md

- name: Dispatch SBOM
env:
GH_TOKEN: ${{ secrets.GITHUB_TOKEN }}
RELEASE_TAG: godot-iap-${{ steps.version.outputs.VERSION }}
run: gh workflow run sbom.yml --ref main -f tag="$RELEASE_TAG"
33 changes: 21 additions & 12 deletions .github/workflows/release-google.yml
Original file line number Diff line number Diff line change
Expand Up @@ -79,6 +79,9 @@ jobs:

release:
needs: [validate-android]
permissions:
actions: write
contents: write
runs-on: ubuntu-latest
env:
# Central Portal can take 10-30 minutes to finish publishing.
Expand Down Expand Up @@ -336,11 +339,11 @@ jobs:
ORG_GRADLE_PROJECT_signingInMemoryKeyPassword: ${{ secrets.SIGNING_PASSWORD }}
run: |
if [ -z "$ORG_GRADLE_PROJECT_mavenCentralUsername" ]; then
echo "⚠️ Maven Central credentials not set. Skipping publish."
else
./gradlew :openiap:publishAndReleaseToMavenCentral --no-daemon --no-parallel --stacktrace
echo "✅ Published openiap-google-horizon (Horizon flavor) to Maven Central"
echo "::error::Maven Central credentials are required to publish the Horizon flavor."
exit 1
fi
./gradlew :openiap:publishAndReleaseToMavenCentral --no-daemon --no-parallel --stacktrace
echo "✅ Published openiap-google-horizon (Horizon flavor) to Maven Central"

- name: Check if Fire OS flavor already published
id: check_amazon
Expand Down Expand Up @@ -378,11 +381,11 @@ jobs:
ORG_GRADLE_PROJECT_signingInMemoryKeyPassword: ${{ secrets.SIGNING_PASSWORD }}
run: |
if [ -z "$ORG_GRADLE_PROJECT_mavenCentralUsername" ]; then
echo "⚠️ Maven Central credentials not set. Skipping publish."
else
./gradlew :openiap:publishAndReleaseToMavenCentral --no-daemon --no-parallel --stacktrace
echo "✅ Published openiap-google-amazon (Fire OS flavor) to Maven Central"
echo "::error::Maven Central credentials are required to publish the Fire OS flavor."
exit 1
fi
./gradlew :openiap:publishAndReleaseToMavenCentral --no-daemon --no-parallel --stacktrace
echo "✅ Published openiap-google-amazon (Fire OS flavor) to Maven Central"

- name: Check if Play flavor already published
id: check_play
Expand Down Expand Up @@ -420,11 +423,11 @@ jobs:
ORG_GRADLE_PROJECT_signingInMemoryKeyPassword: ${{ secrets.SIGNING_PASSWORD }}
run: |
if [ -z "$ORG_GRADLE_PROJECT_mavenCentralUsername" ]; then
echo "⚠️ Maven Central credentials not set. Skipping publish."
else
./gradlew :openiap:publishAndReleaseToMavenCentral --no-daemon --no-parallel --stacktrace
echo "✅ Published openiap-google (Play flavor) to Maven Central"
echo "::error::Maven Central credentials are required to publish the Play flavor."
exit 1
fi
./gradlew :openiap:publishAndReleaseToMavenCentral --no-daemon --no-parallel --stacktrace
echo "✅ Published openiap-google (Play flavor) to Maven Central"

- name: Build release artifacts
working-directory: packages/google
Expand Down Expand Up @@ -602,3 +605,9 @@ jobs:
$PRERELEASE_FLAG \
--notes-file /tmp/release-notes.md
fi

- name: Dispatch SBOM
env:
GH_TOKEN: ${{ secrets.GITHUB_TOKEN }}
RELEASE_TAG: google-${{ steps.version.outputs.version }}
run: gh workflow run sbom.yml --ref main -f tag="$RELEASE_TAG"
Comment thread
hyochan marked this conversation as resolved.
9 changes: 9 additions & 0 deletions .github/workflows/release-kmp.yml
Original file line number Diff line number Diff line change
Expand Up @@ -118,6 +118,9 @@ jobs:

publish:
needs: [validate-android, validate-ios]
permissions:
actions: write
contents: write
runs-on: macos-15
defaults:
run:
Expand Down Expand Up @@ -423,3 +426,9 @@ jobs:
prerelease: ${{ steps.version.outputs.is_prerelease }}
body_path: /tmp/release-notes.md
files: libraries/kmp-iap/release-artifacts.zip

- name: Dispatch SBOM
env:
GH_TOKEN: ${{ secrets.GITHUB_TOKEN }}
RELEASE_TAG: kmp-iap-${{ steps.version.outputs.VERSION }}
run: gh workflow run sbom.yml --ref main -f tag="$RELEASE_TAG"
9 changes: 9 additions & 0 deletions .github/workflows/release-maui.yml
Original file line number Diff line number Diff line change
Expand Up @@ -125,6 +125,9 @@ jobs:

publish:
needs: [validate, validate-multitarget]
permissions:
actions: write
contents: write
# Rebuild the exact native sidecar packed into NuGet with an App Store
# submission toolchain. Xcode 27 remains validation-only until Apple
# accepts its SDK for App Store uploads.
Expand Down Expand Up @@ -461,3 +464,9 @@ jobs:
prerelease: ${{ steps.version.outputs.is_prerelease }}
body_path: /tmp/release-notes.md
files: ./nupkgs/*.nupkg

- name: Dispatch SBOM
env:
GH_TOKEN: ${{ secrets.GITHUB_TOKEN }}
RELEASE_TAG: maui-iap-${{ steps.version.outputs.version }}
run: gh workflow run sbom.yml --ref main -f tag="$RELEASE_TAG"
7 changes: 7 additions & 0 deletions .github/workflows/release-react-native.yml
Original file line number Diff line number Diff line change
Expand Up @@ -157,6 +157,7 @@ jobs:
deploy:
needs: [validate-android, validate-ios]
permissions:
actions: write
contents: write
runs-on: ubuntu-latest
defaults:
Expand Down Expand Up @@ -430,6 +431,12 @@ jobs:
prerelease: ${{ steps.bump.outputs.is_prerelease }}
body_path: /tmp/release-notes.md

- name: Dispatch SBOM
env:
GH_TOKEN: ${{ secrets.GITHUB_TOKEN }}
RELEASE_TAG: react-native-iap-${{ steps.bump.outputs.version }}
run: gh workflow run sbom.yml --ref main -f tag="$RELEASE_TAG"

# npm provenance reads the immutable workflow event GITHUB_SHA. Dispatch
# this same trusted-publisher workflow on the tag so the event SHA, npm
# package gitHead, and release tag all identify the same source commit.
Expand Down
12 changes: 9 additions & 3 deletions .github/workflows/release.yml
Original file line number Diff line number Diff line change
Expand Up @@ -15,12 +15,12 @@ concurrency:
group: ${{ github.workflow }}
cancel-in-progress: false

permissions:
contents: write

jobs:
release:
runs-on: ubuntu-latest
permissions:
actions: write
contents: write
steps:
- name: Checkout repository
uses: actions/checkout@v7
Expand Down Expand Up @@ -76,3 +76,9 @@ jobs:
### Documentation
- [Documentation](https://openiap.dev)
- [GitHub Repository](https://github.com/hyodotdev/openiap)

- name: Dispatch SBOM
env:
GH_TOKEN: ${{ secrets.GITHUB_TOKEN }}
RELEASE_TAG: docs-${{ steps.version.outputs.version }}
run: gh workflow run sbom.yml --ref main -f tag="$RELEASE_TAG"
Loading
Loading