mirror of
https://github.com/rcourtman/Pulse.git
synced 2026-11-01 00:12:16 +00:00
0c08f9b4b3
Preserve exact Delivery candidate identity, current public frontend receipts and additive canonical validation corrections. Change-source: pulse-maintainer
471 lines
22 KiB
YAML
471 lines
22 KiB
YAML
name: Security Scan
|
|
|
|
# Build and Test audits dependencies on every push, but a quiet week with no
|
|
# pushes means no run — and newly disclosed vulnerabilities or delivery drift
|
|
# land against unchanged code. The six-hour schedule cheaply verifies that the
|
|
# advertised stable release remains locked and bound to its activation marker.
|
|
# The weekly schedule re-scans every dependency and performs the full public
|
|
# delivery verification. A failed scheduled run emails the repository owner.
|
|
on:
|
|
schedule:
|
|
- cron: '17 */6 * * *' # six-hour stable release lock and activation watch
|
|
- cron: '30 5 * * 1' # weekly, Monday 05:30 UTC, before the 06:00 triage run
|
|
workflow_dispatch:
|
|
workflow_run:
|
|
workflows: [Release Convergence]
|
|
types: [completed]
|
|
branches: [main]
|
|
|
|
permissions:
|
|
contents: read
|
|
|
|
jobs:
|
|
release-continuity:
|
|
name: Latest stable release continuity
|
|
# Stable convergence gets an immediate independent read-back. Prerelease
|
|
# convergence is excluded because this job intentionally follows the
|
|
# advertised stable channel. The six-hour schedule stops after release and
|
|
# activation identity resolution; weekly, manual, and convergence runs
|
|
# continue through every artifact and customer-facing delivery surface.
|
|
if: ${{ github.event_name != 'workflow_run' || !contains(github.event.workflow_run.display_title, '-') }}
|
|
runs-on: ubuntu-24.04
|
|
timeout-minutes: 60
|
|
steps:
|
|
- name: Checkout release verification controls
|
|
uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1
|
|
with:
|
|
persist-credentials: false
|
|
|
|
- name: Set up Go
|
|
if: ${{ github.event.schedule != '17 */6 * * *' }}
|
|
uses: actions/setup-go@b7ad1dad31e06c5925ef5d2fc7ad053ef454303e # v7.0.0
|
|
with:
|
|
go-version-file: go.mod
|
|
|
|
- name: Set up Helm
|
|
if: ${{ github.event.schedule != '17 */6 * * *' }}
|
|
uses: azure/setup-helm@9bc31f4ebc9c6b171d7bfbaa5d006ae7abdb4310 # v5.0.1
|
|
with:
|
|
version: v3.15.2
|
|
|
|
- name: Set up Docker Buildx
|
|
if: ${{ github.event.schedule != '17 */6 * * *' }}
|
|
uses: docker/setup-buildx-action@37fe631027851001ddb9b187196cc803df7f5f0e # v4.3.0
|
|
|
|
- name: Resolve the advertised stable release
|
|
id: release
|
|
env:
|
|
GH_TOKEN: ${{ github.token }}
|
|
REPOSITORY: ${{ github.repository }}
|
|
run: |
|
|
set -euo pipefail
|
|
mkdir -p release-continuity-evidence
|
|
release_json=release-continuity-evidence/release.json
|
|
|
|
if ! gh api \
|
|
-H 'Accept: application/vnd.github+json' \
|
|
-H 'X-GitHub-Api-Version: 2026-03-10' \
|
|
"repos/${REPOSITORY}/releases/latest" > "${release_json}"; then
|
|
python3 scripts/release_control/release_continuity.py release \
|
|
--release-json "${release_json}" \
|
|
--diagnostic release-continuity-evidence/release-diagnostic.json \
|
|
--github-output "${GITHUB_OUTPUT}" || true
|
|
exit 1
|
|
fi
|
|
|
|
python3 scripts/release_control/release_continuity.py release \
|
|
--release-json "${release_json}" \
|
|
--diagnostic release-continuity-evidence/release-diagnostic.json \
|
|
--github-output "${GITHUB_OUTPUT}"
|
|
|
|
- name: Reject orphaned stable publication tags
|
|
id: frontier
|
|
if: ${{ !cancelled() && steps.release.outputs.referenceable == 'true' }}
|
|
env:
|
|
GH_TOKEN: ${{ github.token }}
|
|
REPOSITORY: ${{ github.repository }}
|
|
run: |
|
|
set -euo pipefail
|
|
evidence=release-continuity-evidence
|
|
gh api --paginate --slurp \
|
|
-H 'Accept: application/vnd.github+json' \
|
|
-H 'X-GitHub-Api-Version: 2026-03-10' \
|
|
"repos/${REPOSITORY}/git/matching-refs/tags/v?per_page=100" \
|
|
> "${evidence}/stable-refs.json"
|
|
gh api --paginate --slurp \
|
|
-H 'Accept: application/vnd.github+json' \
|
|
-H 'X-GitHub-Api-Version: 2026-03-10' \
|
|
"repos/${REPOSITORY}/releases?per_page=100" \
|
|
> "${evidence}/releases.json"
|
|
|
|
fetch_registry_tags() {
|
|
local registry_name="$1" auth_url="$2" registry_url="$3" image="$4" output="$5"
|
|
local token
|
|
token="$(curl -fsSL --retry 4 --retry-delay 2 --retry-all-errors "${auth_url}")"
|
|
token="$(jq -er '.token | select(type == "string" and length > 0)' <<<"${token}")"
|
|
curl -fsSL --retry 4 --retry-delay 2 --retry-all-errors \
|
|
-H "Authorization: Bearer ${token}" \
|
|
"${registry_url}/v2/${image}/tags/list?n=10000" \
|
|
| jq --arg name "${registry_name}/${image}" '.name = $name' > "${output}"
|
|
}
|
|
owner="${GITHUB_REPOSITORY_OWNER,,}"
|
|
for image in pulse pulse-control-plane; do
|
|
file_image="${image//-/_}"
|
|
fetch_registry_tags \
|
|
ghcr.io \
|
|
"https://ghcr.io/token?service=ghcr.io&scope=repository:${owner}/${image}:pull" \
|
|
https://ghcr.io "${owner}/${image}" \
|
|
"${evidence}/ghcr-${file_image}-tags.json"
|
|
done
|
|
# The server image is public on both registries. The control-plane
|
|
# image is publicly enumerable only on GHCR, so an unauthenticated
|
|
# continuity audit must not depend on Docker Hub credentials.
|
|
fetch_registry_tags \
|
|
docker.io \
|
|
"https://auth.docker.io/token?service=registry.docker.io&scope=repository:rcourtman/pulse:pull" \
|
|
https://registry-1.docker.io rcourtman/pulse \
|
|
"${evidence}/docker-pulse-tags.json"
|
|
python3 scripts/release_control/release_continuity.py frontier \
|
|
--release-json "${evidence}/release.json" \
|
|
--stable-refs-json "${evidence}/stable-refs.json" \
|
|
--releases-json "${evidence}/releases.json" \
|
|
--registry-tags-json "${evidence}/ghcr-pulse-tags.json" \
|
|
--registry-tags-json "${evidence}/docker-pulse-tags.json" \
|
|
--registry-tags-json "${evidence}/ghcr-pulse_control_plane-tags.json" \
|
|
--diagnostic "${evidence}/frontier-diagnostic.json"
|
|
|
|
- name: Bind the release activation marker
|
|
id: activation
|
|
# A structurally valid tag/id/SHA remains safe lookup data even when
|
|
# release trust fails. Inspect its marker so one defect cannot hide a
|
|
# second; all public-surface verification below still requires both
|
|
# release and activation admission to succeed.
|
|
if: ${{ !cancelled() && steps.release.outputs.referenceable == 'true' }}
|
|
env:
|
|
GH_TOKEN: ${{ github.token }}
|
|
REPOSITORY: ${{ github.repository }}
|
|
TAG: ${{ steps.release.outputs.tag }}
|
|
run: |
|
|
set -euo pipefail
|
|
release_json=release-continuity-evidence/release.json
|
|
marker=release-continuity-evidence/release-activation.json
|
|
|
|
if ! gh release download "${TAG}" \
|
|
--repo "${REPOSITORY}" \
|
|
--pattern release-activation.json \
|
|
--dir release-continuity-evidence; then
|
|
python3 scripts/release_control/release_continuity.py activation \
|
|
--release-json "${release_json}" \
|
|
--activation-json "${marker}" \
|
|
--diagnostic release-continuity-evidence/activation-diagnostic.json \
|
|
--github-output "${GITHUB_OUTPUT}" || true
|
|
exit 1
|
|
fi
|
|
|
|
python3 scripts/release_control/release_continuity.py activation \
|
|
--release-json "${release_json}" \
|
|
--activation-json "${marker}" \
|
|
--diagnostic release-continuity-evidence/activation-diagnostic.json \
|
|
--github-output "${GITHUB_OUTPUT}"
|
|
|
|
- name: Verify immutable release and build provenance
|
|
id: packet
|
|
if: ${{ always() && steps.release.outcome == 'success' && steps.frontier.outcome == 'success' && steps.activation.outcome == 'success' && github.event.schedule != '17 */6 * * *' }}
|
|
env:
|
|
GH_TOKEN: ${{ github.token }}
|
|
REPOSITORY: ${{ github.repository }}
|
|
TAG: ${{ steps.release.outputs.tag }}
|
|
RELEASE_ID: ${{ steps.release.outputs.release_id }}
|
|
SOURCE_SHA: ${{ steps.release.outputs.source_sha }}
|
|
run: >-
|
|
./scripts/verify-github-release-integrity.sh
|
|
"${TAG}" "${REPOSITORY}" "${RELEASE_ID}" "${SOURCE_SHA}"
|
|
|
|
- name: Authenticate every published release asset
|
|
id: assets
|
|
if: ${{ always() && steps.release.outcome == 'success' && steps.frontier.outcome == 'success' && steps.activation.outcome == 'success' && github.event.schedule != '17 */6 * * *' }}
|
|
env:
|
|
PULSE_UPDATE_SIGNING_PUBLIC_KEY: ${{ vars.PULSE_UPDATE_SIGNING_PUBLIC_KEY }}
|
|
REPOSITORY: ${{ github.repository }}
|
|
TAG: ${{ steps.release.outputs.tag }}
|
|
run: ./scripts/validate-published-release.sh "${TAG}" "${REPOSITORY}"
|
|
|
|
- name: Verify exact-version container identities
|
|
id: containers
|
|
if: ${{ always() && steps.release.outcome == 'success' && steps.frontier.outcome == 'success' && steps.activation.outcome == 'success' && github.event.schedule != '17 */6 * * *' }}
|
|
env:
|
|
GH_TOKEN: ${{ github.token }}
|
|
REPOSITORY: ${{ github.repository }}
|
|
TAG: ${{ steps.release.outputs.tag }}
|
|
SOURCE_SHA: ${{ steps.release.outputs.source_sha }}
|
|
EXPECTED_SERVER_DIGEST: ${{ steps.activation.outputs.server_image_digest }}
|
|
EXPECTED_CONTROL_PLANE_DIGEST: ${{ steps.activation.outputs.control_plane_image_digest }}
|
|
run: |
|
|
set -euo pipefail
|
|
proof="$(./scripts/verify-release-container-images.sh "${TAG}" "${SOURCE_SHA}" "${REPOSITORY}")"
|
|
printf '%s\n' "${proof}"
|
|
server_digest="$(awk -F= '$1 == "server_digest" {print $2}' <<<"${proof}")"
|
|
control_plane_digest="$(awk -F= '$1 == "control_plane_digest" {print $2}' <<<"${proof}")"
|
|
if [ "${server_digest}" != "${EXPECTED_SERVER_DIGEST}" ] || \
|
|
[ "${control_plane_digest}" != "${EXPECTED_CONTROL_PLANE_DIGEST}" ]; then
|
|
echo "::error::Exact-version container identities moved from the committed activation marker."
|
|
exit 1
|
|
fi
|
|
|
|
- name: Verify stable container discovery aliases
|
|
id: aliases
|
|
if: ${{ always() && steps.release.outcome == 'success' && steps.frontier.outcome == 'success' && steps.activation.outcome == 'success' && github.event.schedule != '17 */6 * * *' }}
|
|
env:
|
|
TAG: ${{ steps.release.outputs.tag }}
|
|
EXPECTED_SERVER_DIGEST: ${{ steps.activation.outputs.server_image_digest }}
|
|
EXPECTED_CONTROL_PLANE_DIGEST: ${{ steps.activation.outputs.control_plane_image_digest }}
|
|
REGISTRY_OWNER: ${{ github.repository_owner }}
|
|
run: >-
|
|
./scripts/verify-stable-container-aliases.sh
|
|
"${TAG}" "${EXPECTED_SERVER_DIGEST}" "${EXPECTED_CONTROL_PLANE_DIGEST}"
|
|
"${REGISTRY_OWNER}"
|
|
|
|
- name: Verify exact-version Helm identity
|
|
id: helm
|
|
if: ${{ always() && steps.release.outcome == 'success' && steps.frontier.outcome == 'success' && steps.activation.outcome == 'success' && github.event.schedule != '17 */6 * * *' }}
|
|
env:
|
|
GH_TOKEN: ${{ github.token }}
|
|
REPOSITORY: ${{ github.repository }}
|
|
TAG: ${{ steps.release.outputs.tag }}
|
|
SOURCE_SHA: ${{ steps.release.outputs.source_sha }}
|
|
EXPECTED_HELM_DIGEST: ${{ steps.activation.outputs.helm_chart_digest }}
|
|
run: >-
|
|
./scripts/verify-release-helm-chart.sh
|
|
"${TAG}" "${SOURCE_SHA}" "${REPOSITORY}" "${EXPECTED_HELM_DIGEST}"
|
|
|
|
- name: Record continuity evidence
|
|
if: ${{ always() }}
|
|
env:
|
|
REPOSITORY: ${{ github.repository }}
|
|
RUN_ID: ${{ github.run_id }}
|
|
RUN_ATTEMPT: ${{ github.run_attempt }}
|
|
TRIGGER_EVENT: ${{ github.event_name }}
|
|
TRIGGER_SCHEDULE: ${{ github.event.schedule }}
|
|
CONVERGENCE_RUN_ID: ${{ github.event.workflow_run.id }}
|
|
CONVERGENCE_RUN_ATTEMPT: ${{ github.event.workflow_run.run_attempt }}
|
|
CONVERGENCE_CONCLUSION: ${{ github.event.workflow_run.conclusion }}
|
|
CONVERGENCE_DISPLAY_TITLE: ${{ github.event.workflow_run.display_title }}
|
|
TAG: ${{ steps.release.outputs.tag }}
|
|
RELEASE_ID: ${{ steps.release.outputs.release_id }}
|
|
SOURCE_SHA: ${{ steps.release.outputs.source_sha }}
|
|
ACTIVATION_SHA256: ${{ steps.activation.outputs.activation_sha256 }}
|
|
SERVER_IMAGE_DIGEST: ${{ steps.activation.outputs.server_image_digest }}
|
|
CONTROL_PLANE_IMAGE_DIGEST: ${{ steps.activation.outputs.control_plane_image_digest }}
|
|
HELM_CHART_DIGEST: ${{ steps.activation.outputs.helm_chart_digest }}
|
|
RELEASE_RESULT: ${{ steps.release.outcome }}
|
|
FRONTIER_RESULT: ${{ steps.frontier.outcome }}
|
|
ACTIVATION_RESULT: ${{ steps.activation.outcome }}
|
|
PACKET_RESULT: ${{ steps.packet.outcome }}
|
|
ASSET_RESULT: ${{ steps.assets.outcome }}
|
|
CONTAINER_RESULT: ${{ steps.containers.outcome }}
|
|
ALIAS_RESULT: ${{ steps.aliases.outcome }}
|
|
HELM_RESULT: ${{ steps.helm.outcome }}
|
|
run: |
|
|
set -euo pipefail
|
|
mkdir -p release-continuity-evidence
|
|
checked_at="$(date -u +%Y-%m-%dT%H:%M:%SZ)"
|
|
mode=full
|
|
if [ "${TRIGGER_SCHEDULE}" = '17 */6 * * *' ]; then
|
|
mode=release_lock
|
|
fi
|
|
for diagnostic in release frontier activation; do
|
|
path="release-continuity-evidence/${diagnostic}-diagnostic.json"
|
|
if [ ! -s "${path}" ]; then
|
|
jq -n \
|
|
--arg check "${diagnostic}" \
|
|
'{schema_version: 1, check: $check, status: "not_run", identity: {}, violations: []}' \
|
|
> "${path}"
|
|
fi
|
|
done
|
|
jq -n \
|
|
--slurpfile release_diagnostic release-continuity-evidence/release-diagnostic.json \
|
|
--slurpfile frontier_diagnostic release-continuity-evidence/frontier-diagnostic.json \
|
|
--slurpfile activation_diagnostic release-continuity-evidence/activation-diagnostic.json \
|
|
--arg schema_version "1" \
|
|
--arg checked_at "${checked_at}" \
|
|
--arg repository "${REPOSITORY}" \
|
|
--arg run_id "${RUN_ID}" \
|
|
--arg run_attempt "${RUN_ATTEMPT}" \
|
|
--arg trigger_event "${TRIGGER_EVENT}" \
|
|
--arg trigger_schedule "${TRIGGER_SCHEDULE}" \
|
|
--arg mode "${mode}" \
|
|
--arg convergence_run_id "${CONVERGENCE_RUN_ID}" \
|
|
--arg convergence_run_attempt "${CONVERGENCE_RUN_ATTEMPT}" \
|
|
--arg convergence_conclusion "${CONVERGENCE_CONCLUSION}" \
|
|
--arg convergence_display_title "${CONVERGENCE_DISPLAY_TITLE}" \
|
|
--arg tag "${TAG}" \
|
|
--arg release_id "${RELEASE_ID}" \
|
|
--arg source_sha "${SOURCE_SHA}" \
|
|
--arg activation_sha256 "${ACTIVATION_SHA256}" \
|
|
--arg server_image_digest "${SERVER_IMAGE_DIGEST}" \
|
|
--arg control_plane_image_digest "${CONTROL_PLANE_IMAGE_DIGEST}" \
|
|
--arg helm_chart_digest "${HELM_CHART_DIGEST}" \
|
|
--arg release_result "${RELEASE_RESULT}" \
|
|
--arg frontier_result "${FRONTIER_RESULT}" \
|
|
--arg activation_result "${ACTIVATION_RESULT}" \
|
|
--arg packet_result "${PACKET_RESULT}" \
|
|
--arg asset_result "${ASSET_RESULT}" \
|
|
--arg container_result "${CONTAINER_RESULT}" \
|
|
--arg alias_result "${ALIAS_RESULT}" \
|
|
--arg helm_result "${HELM_RESULT}" \
|
|
'{
|
|
schema_version: ($schema_version | tonumber),
|
|
checked_at: $checked_at,
|
|
repository: $repository,
|
|
workflow_run: {id: $run_id, attempt: $run_attempt},
|
|
trigger: {
|
|
event: $trigger_event,
|
|
schedule: $trigger_schedule,
|
|
mode: $mode,
|
|
release_convergence_run: {
|
|
id: $convergence_run_id,
|
|
attempt: $convergence_run_attempt,
|
|
conclusion: $convergence_conclusion,
|
|
display_title: $convergence_display_title
|
|
}
|
|
},
|
|
release: {
|
|
tag: $tag,
|
|
id: $release_id,
|
|
source_sha: $source_sha,
|
|
activation_sha256: $activation_sha256,
|
|
server_image_digest: $server_image_digest,
|
|
control_plane_image_digest: $control_plane_image_digest,
|
|
helm_chart_digest: $helm_chart_digest
|
|
},
|
|
diagnostics: {
|
|
release_identity: $release_diagnostic[0],
|
|
stable_publication_frontier: $frontier_diagnostic[0],
|
|
activation_binding: $activation_diagnostic[0]
|
|
},
|
|
checks: {
|
|
release_resolution: $release_result,
|
|
stable_publication_frontier: $frontier_result,
|
|
activation_binding: $activation_result,
|
|
immutable_packet_and_provenance: $packet_result,
|
|
authenticated_assets: $asset_result,
|
|
container_identities: $container_result,
|
|
stable_container_aliases: $alias_result,
|
|
helm_identity: $helm_result
|
|
}
|
|
}' > release-continuity-evidence/continuity-evidence.json
|
|
cat release-continuity-evidence/continuity-evidence.json >> "${GITHUB_STEP_SUMMARY}"
|
|
|
|
- name: Retain continuity evidence
|
|
if: ${{ always() }}
|
|
uses: actions/upload-artifact@cf430e030ddbb5b0abf93d22962f4752f3646cd9 # v7.0.2
|
|
with:
|
|
name: release-continuity-${{ steps.release.outputs.tag || 'unresolved' }}-${{ github.run_id }}-${{ github.run_attempt }}
|
|
path: release-continuity-evidence
|
|
if-no-files-found: error
|
|
retention-days: 90
|
|
|
|
container-lifecycle:
|
|
name: Container trust and support window
|
|
if: ${{ github.event_name != 'workflow_run' && github.event.schedule != '17 */6 * * *' }}
|
|
runs-on: ubuntu-24.04
|
|
timeout-minutes: 30
|
|
steps:
|
|
- name: Checkout repository
|
|
uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1
|
|
with:
|
|
persist-credentials: false
|
|
|
|
- name: Set up Go
|
|
uses: actions/setup-go@b7ad1dad31e06c5925ef5d2fc7ad053ef454303e # v7.0.0
|
|
with:
|
|
go-version-file: go.mod
|
|
|
|
- name: Check governed image pins and lifecycle runway
|
|
run: >-
|
|
go test ./scripts/installtests
|
|
-run '^(TestGovernedContainerBaseSupportWindow|TestEveryDockerfilePinsExternalBases|TestNodeToolchainParity|TestIntegrationContainersUseGovernedImmutableBases|TestDockerBuildUsesCanonicalReleaseLdflags|TestProviderMSPControlPlaneDockerfileBuildsReleaseLicenseBinary)$'
|
|
|
|
govulncheck:
|
|
name: Go Vulnerability Scan (${{ matrix.directory }})
|
|
if: ${{ github.event_name != 'workflow_run' && github.event.schedule != '17 */6 * * *' }}
|
|
runs-on: ubuntu-24.04
|
|
timeout-minutes: 45
|
|
strategy:
|
|
fail-fast: false
|
|
matrix:
|
|
directory:
|
|
- '.'
|
|
- 'tests/integration/mock-github-server'
|
|
steps:
|
|
- name: Checkout repository
|
|
uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1
|
|
with:
|
|
persist-credentials: false
|
|
|
|
- name: Set up Go
|
|
uses: actions/setup-go@b7ad1dad31e06c5925ef5d2fc7ad053ef454303e # v7.0.0
|
|
with:
|
|
go-version-file: go.mod
|
|
|
|
- name: Install govulncheck
|
|
run: go install golang.org/x/vuln/cmd/govulncheck@v1.7.0
|
|
|
|
- name: Scan Go module for reachable vulnerabilities
|
|
working-directory: ${{ matrix.directory }}
|
|
run: govulncheck ./...
|
|
|
|
npm-audit:
|
|
name: npm Dependency Audit (${{ matrix.name }})
|
|
if: ${{ github.event_name != 'workflow_run' && github.event.schedule != '17 */6 * * *' }}
|
|
runs-on: ubuntu-24.04
|
|
timeout-minutes: 20
|
|
strategy:
|
|
fail-fast: false
|
|
matrix:
|
|
include:
|
|
- name: root tooling
|
|
directory: '.'
|
|
- name: issue metadata tooling
|
|
directory: '.github/scripts'
|
|
- name: product frontend
|
|
directory: 'frontend-modern'
|
|
- name: account frontend
|
|
directory: 'internal/cloudcp/portal/frontend'
|
|
- name: integration tests
|
|
directory: 'tests/integration'
|
|
steps:
|
|
- name: Checkout repository
|
|
uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1
|
|
with:
|
|
persist-credentials: false
|
|
|
|
- name: Set up Node.js
|
|
uses: actions/setup-node@949feb2413d6458794dcd2491c4babbbce0c15c1 # v7.1.0
|
|
with:
|
|
node-version: '24'
|
|
|
|
- name: Audit complete dependency graph
|
|
id: audit-complete
|
|
continue-on-error: true
|
|
working-directory: ${{ matrix.directory }}
|
|
run: bash "$GITHUB_WORKSPACE/scripts/npm-audit-retry.sh" all --package-lock-only
|
|
|
|
- name: Audit production dependencies
|
|
id: audit-production
|
|
continue-on-error: true
|
|
working-directory: ${{ matrix.directory }}
|
|
run: bash "$GITHUB_WORKSPACE/scripts/npm-audit-retry.sh" production --package-lock-only
|
|
|
|
- name: Require dependency audits
|
|
if: ${{ !cancelled() }}
|
|
env:
|
|
COMPLETE_AUDIT_RESULT: ${{ steps.audit-complete.outcome }}
|
|
PRODUCTION_AUDIT_RESULT: ${{ steps.audit-production.outcome }}
|
|
run: |
|
|
if [ "${COMPLETE_AUDIT_RESULT}" != success ] || \
|
|
[ "${PRODUCTION_AUDIT_RESULT}" != success ]; then
|
|
echo "::error::One or more dependency audits failed."
|
|
exit 1
|
|
fi
|