Compare commits

...

6 Commits

Author SHA1 Message Date
Countess von Containerheim 9dc5fd673d fix(ci): inline CI workflow, remove reusable .github dependency (PRI-1630)
Promotion Gate / promotion-gate (pull_request) Failing after 0s
CI / ci (pull_request) Successful in 50s
CI / ci (push) Successful in 46s
2026-05-20 10:45:01 +00:00
privilegedescalation-engineer[bot] 125b06734a Merge pull request #164 from privilegedescalation/uat
Promote uat to main
2026-05-14 03:16:38 +00:00
privilegedescalation-engineer[bot] 61582d7534 fix: remove stale package-lock.json causing npm install failures
The project declares pnpm@10.32.1 as packageManager but had a committed
package-lock.json. Running npm install produced a broken node_modules
layout. Delete the stale lockfile and add it to .gitignore.

Note: tests were failing before this change due to a missing tsconfig
for vitest.setup.ts — tracked separately as pre-existing issue.

Co-authored-by: Chris Farhood <chris@farhood.org>
Co-authored-by: Claude Opus 4.7 <noreply@anthropic.com>
2026-05-14 00:15:30 +00:00
privilegedescalation-engineer[bot] f6a296df1b fix: override fast-uri to patched version to resolve 2 high severity CVEs (#159)
Upgraded @kinvolk/headlamp-plugin from ^0.13.0 to ^0.14.0 and added
fast-uri >=3.1.2 to pnpm overrides to address:
- GHSA-q3j6-qgpj-74h6 (fast-uri path traversal, patched in >=3.1.1)
- GHSA-v39h-62p7-jpjc (fast-uri host confusion, patched in >=3.1.2)

Remaining 6 vulnerabilities (1 low, 5 moderate) are in transitive deps
without direct override paths and do not affect production runtime.

Co-authored-by: Chris Farhood <chris@farhood.org>
Co-authored-by: Paperclip <noreply@paperclip.ing>
2026-05-13 17:43:20 +00:00
privilegedescalation-engineer[bot] 5744d9083f chore(ci): add audit-ci allowlist for inherited @kinvolk/headlamp-plugin CVEs (PRI-855)
QA reviewed and approved. Adds audit-ci.jsonc with 3 CVE allowlist entries for dev-only dependencies.
2026-05-12 22:22:41 +00:00
privilegedescalation-ceo[bot] 34ea111776 Update CI and approval workflows for three-branch SDLC (#158)
CI triggers on dev/uat/main. Promotion gate replaces dual-approval.

Co-authored-by: Chris Farhood <chris@farhood.org>
Co-authored-by: Paperclip <noreply@paperclip.ing>
2026-05-11 21:40:07 +00:00
6 changed files with 244 additions and 18694 deletions
+201 -3
View File
@@ -2,12 +2,210 @@ name: CI
on:
push:
branches: [main, dev, uat]
branches: ['**']
pull_request:
branches: [main, dev, uat]
workflow_dispatch:
workflow_call:
permissions:
contents: read
jobs:
ci:
uses: privilegedescalation/.github/.github/workflows/plugin-ci.yaml@main
runs-on: ubuntu-latest
timeout-minutes: 10
container: node:22-slim
steps:
- name: Checkout
uses: actions/checkout@v6
- name: Install Python
run: apt-get update && apt-get install -y --no-install-recommends python3 python3-yaml
- name: Validate artifacthub-pkg.yml
run: |
python3 - <<'EOF'
import sys, re
try:
import yaml
except ImportError:
print("::warning::PyYAML not available, skipping artifacthub-pkg.yml validation")
sys.exit(0)
try:
with open("artifacthub-pkg.yml") as f:
pkg = yaml.safe_load(f)
except FileNotFoundError:
print("::error::artifacthub-pkg.yml not found")
sys.exit(1)
except yaml.YAMLError as e:
print(f"::error::artifacthub-pkg.yml is invalid YAML: {e}")
sys.exit(1)
errors = []
for field in ["version", "name", "description", "homeURL"]:
if not pkg.get(field):
errors.append(f"Missing required field: {field}")
version = pkg.get("version", "")
if version and not re.match(r'^\d+\.\d+\.\d+$', str(version)):
errors.append(f"version '{version}' is not SemVer (expected X.Y.Z)")
annotations = pkg.get("annotations", {}) or {}
archive_url = annotations.get("headlamp/plugin/archive-url", "")
archive_checksum = annotations.get("headlamp/plugin/archive-checksum", "")
if not archive_url:
errors.append("Missing annotation: headlamp/plugin/archive-url")
if not archive_checksum:
errors.append("Missing annotation: headlamp/plugin/archive-checksum")
elif not re.match(r'^sha256:[0-9a-f]{64}$', str(archive_checksum)):
errors.append(f"archive-checksum has unexpected format: '{archive_checksum}' (expected sha256:<64 hex chars>)")
if errors:
for e in errors:
print(f"::error::{e}")
sys.exit(1)
print(f"artifacthub-pkg.yml valid: name={pkg['name']} version={pkg['version']}")
EOF
- name: Detect package manager
id: pkg-manager
run: |
if [ -f "pnpm-lock.yaml" ]; then
echo "manager=pnpm" >> $GITHUB_OUTPUT
PM=$(python3 -c "import json,sys; d=json.load(open('package.json')); print('true' if d.get('packageManager','').startswith('pnpm@') else 'false')" 2>/dev/null || echo "false")
echo "has_package_manager=$PM" >> $GITHUB_OUTPUT
else
echo "manager=npm" >> $GITHUB_OUTPUT
echo "has_package_manager=false" >> $GITHUB_OUTPUT
fi
- name: Setup Node
uses: actions/setup-node@v6
with:
node-version: '22'
cache: ${{ steps.pkg-manager.outputs.manager == 'npm' && 'npm' || '' }}
- name: Setup pnpm (via Corepack, reads version from packageManager field)
if: steps.pkg-manager.outputs.manager == 'pnpm' && steps.pkg-manager.outputs.has_package_manager == 'true'
run: |
npm install -g corepack
corepack enable pnpm
corepack install
- name: Setup pnpm (version latest)
if: steps.pkg-manager.outputs.manager == 'pnpm' && steps.pkg-manager.outputs.has_package_manager == 'false'
uses: pnpm/action-setup@v5
with:
run_install: false
version: latest
- name: Get pnpm store directory
id: pnpm-store
if: steps.pkg-manager.outputs.manager == 'pnpm'
run: echo "dir=$(pnpm store path --silent)" >> $GITHUB_OUTPUT
- name: Cache pnpm store
if: steps.pkg-manager.outputs.manager == 'pnpm'
uses: actions/cache@v5
with:
path: ${{ steps.pnpm-store.outputs.dir }}
key: ${{ runner.os }}-pnpm-${{ hashFiles('**/pnpm-lock.yaml') }}
restore-keys: |
${{ runner.os }}-pnpm-
- name: Validate pnpm lockfile freshness
if: steps.pkg-manager.outputs.manager == 'pnpm'
run: |
if [ ! -f "pnpm-lock.yaml" ]; then
echo "No pnpm-lock.yaml found, skipping lockfile freshness check"
exit 0
fi
if ! grep -q 'overrides:' pnpm-lock.yaml 2>/dev/null; then
echo "No overrides section in pnpm-lock.yaml, skipping lockfile freshness check"
exit 0
fi
echo "Detected pnpm-lock.yaml with overrides section. Checking lockfile freshness..."
ERR_FILE=$(mktemp)
if pnpm install --frozen-lockfile 2>&1 | tee "$ERR_FILE"; then
echo "Lockfile is fresh."
else
if grep -q "CONFIG_MISMATCH\|EBADLOCKFILE\|ERR_PNPM_LOCKFILE" "$ERR_FILE"; then
echo ""
echo "::error::pnpm-lock.yaml is out of sync with package.json overrides."
echo "::error::Run 'pnpm install' to regenerate the lockfile and commit the updated pnpm-lock.yaml."
rm -f "$ERR_FILE"
exit 1
fi
rm -f "$ERR_FILE"
echo "::warning::Install failed with a different error. Will retry in the Install dependencies step."
fi
- name: Install dependencies
run: |
max_attempts=3
attempt=1
while [ $attempt -le $max_attempts ]; do
echo "Attempt $attempt of $max_attempts"
if [ "${{ steps.pkg-manager.outputs.manager }}" = "pnpm" ]; then
pnpm install --frozen-lockfile && break
else
npm ci && break
fi
if [ $attempt -lt $max_attempts ]; then
echo "::warning::Install step failed on attempt $attempt. Retrying in 5 seconds..."
sleep 5
fi
attempt=$((attempt + 1))
done
if [ $attempt -gt $max_attempts ]; then
echo "::error::Install step failed after $max_attempts attempts."
exit 1
fi
- name: Build plugin
run: npx @kinvolk/headlamp-plugin build
- name: Lint
run: |
if [ "${{ steps.pkg-manager.outputs.manager }}" = "pnpm" ]; then
pnpm run lint
else
npm run lint
fi
- name: Type-check
run: |
if [ "${{ steps.pkg-manager.outputs.manager }}" = "pnpm" ]; then
pnpm run tsc
else
npm run tsc
fi
- name: Format check
run: |
if [ "${{ steps.pkg-manager.outputs.manager }}" = "pnpm" ]; then
pnpm run format:check
else
npm run format:check
fi
- name: Run tests
run: |
if [ "${{ steps.pkg-manager.outputs.manager }}" = "pnpm" ]; then
pnpm test
else
npm test
fi
- name: Security audit
run: |
if [ "${{ steps.pkg-manager.outputs.manager }}" = "pnpm" ]; then
npx audit-ci --pnpm --audit-level=high --config ./audit-ci.jsonc
else
npx audit-ci --npm --audit-level=high --config ./audit-ci.jsonc
fi
+8 -7
View File
@@ -1,20 +1,21 @@
name: Dual Approval (CTO + QA)
name: Promotion Gate
# Calls the shared dual-approval-check workflow.
# Passes when both privilegedescalation-cto and privilegedescalation-qa
# have approved the PR. Add "Dual Approval (CTO + QA)" to required_status_checks
# in branch protection to enforce this gate.
# Calls the shared promotion gate workflow.
# dev PRs: no gate (engineer self-merges).
# uat PRs: QA approval required.
# main PRs: UAT approval required (uat→main promotions).
on:
pull_request_review:
types: [submitted, dismissed]
pull_request:
branches: [main]
branches: [uat, main]
types: [opened, reopened, synchronize]
jobs:
dual-approval:
promotion-gate:
uses: privilegedescalation/.github/.github/workflows/dual-approval-check.yaml@main
secrets: inherit
with:
pr_number: ${{ github.event.pull_request.number }}
+1
View File
@@ -5,3 +5,4 @@ dist/
.env
.env.local
.eslintcache
package-lock.json
-18642
View File
File diff suppressed because it is too large Load Diff
+3 -2
View File
@@ -37,11 +37,12 @@
"lodash": ">=4.18.0",
"picomatch": ">=4.0.4",
"vite": ">=6.4.2",
"elliptic": ">=6.6.1"
"elliptic": ">=6.6.1",
"fast-uri": ">=3.1.2"
}
},
"devDependencies": {
"@kinvolk/headlamp-plugin": "^0.13.0",
"@kinvolk/headlamp-plugin": "^0.14.0",
"@mui/material": "^5.15.14",
"@testing-library/jest-dom": "^6.4.8",
"@testing-library/react": "^16.0.0",
+31 -40
View File
@@ -12,6 +12,7 @@ overrides:
picomatch: '>=4.0.4'
vite: '>=6.4.2'
elliptic: '>=6.6.1'
fast-uri: '>=3.1.2'
importers:
@@ -21,8 +22,8 @@ importers:
specifier: ^0.6.0
version: 0.6.0(@typescript-eslint/eslint-plugin@8.56.1(@typescript-eslint/parser@8.56.1(eslint@8.57.1)(typescript@5.6.2))(eslint@8.57.1)(typescript@5.6.2))(eslint-config-prettier@9.1.2(eslint@8.57.1))(eslint-plugin-import@2.32.0(@typescript-eslint/parser@8.56.1(eslint@8.57.1)(typescript@5.6.2))(eslint@8.57.1))(eslint-plugin-jsx-a11y@6.10.2(eslint@8.57.1))(eslint-plugin-react-hooks@4.6.2(eslint@8.57.1))(eslint-plugin-react@7.35.0(eslint@8.57.1))(eslint-plugin-simple-import-sort@12.1.1(eslint@8.57.1))(eslint-plugin-unused-imports@4.4.1(@typescript-eslint/eslint-plugin@8.56.1(@typescript-eslint/parser@8.56.1(eslint@8.57.1)(typescript@5.6.2))(eslint@8.57.1)(typescript@5.6.2))(eslint@8.57.1))(eslint@8.57.1)
'@kinvolk/headlamp-plugin':
specifier: ^0.13.0
version: 0.13.1(@swc/core@1.15.18)(@types/debug@4.1.12)(@typescript-eslint/parser@8.56.1(eslint@8.57.1)(typescript@5.6.2))(csstype@3.2.3)(esbuild@0.25.12)(immer@11.1.4)(openapi-types@12.1.3)(redux@5.0.1)(rollup@4.59.0)(terser@5.46.0)(webpack@5.105.4(@swc/core@1.15.18)(esbuild@0.25.12))
specifier: ^0.14.0
version: 0.14.0(@swc/core@1.15.18)(@types/debug@4.1.12)(@typescript-eslint/parser@8.56.1(eslint@8.57.1)(typescript@5.6.2))(csstype@3.2.3)(esbuild@0.25.12)(immer@11.1.4)(openapi-types@12.1.3)(redux@5.0.1)(rollup@4.59.0)(terser@5.46.0)(webpack@5.105.4(@swc/core@1.15.18)(esbuild@0.25.12))
'@mui/material':
specifier: ^5.15.14
version: 5.18.0(@emotion/react@11.14.0(@types/react@19.2.14)(react@18.3.1))(@emotion/styled@11.14.1(@emotion/react@11.14.0(@types/react@19.2.14)(react@18.3.1))(@types/react@19.2.14)(react@18.3.1))(@types/react@19.2.14)(react-dom@18.3.1(react@18.3.1))(react@18.3.1)
@@ -603,8 +604,8 @@ packages:
peerDependencies:
jsep: ^0.4.0||^1.0.0
'@kinvolk/headlamp-plugin@0.13.1':
resolution: {integrity: sha512-aoAGs5w8HIS43p3YBcjzkIWZZlh18b/e02d+r/rr6+99vc48vOd9tKAIBZMVg4j+cVzbPtL1+t1tDE/UdeHcWQ==}
'@kinvolk/headlamp-plugin@0.14.0':
resolution: {integrity: sha512-oVIqpSzf2zZfZG44gwrGI8xTLImCIKupUJ26k7ZhVrFSUBY9Ga+R66tfCdN4Q/ShYha/8J+qlpy5ac9PjRq2KA==}
hasBin: true
'@mdx-js/react@3.1.1':
@@ -2939,8 +2940,8 @@ packages:
fast-levenshtein@2.0.6:
resolution: {integrity: sha512-DCXu6Ifhqcks7TZKY3Hxp3y6qphY5SJZmrWMDrKcERSOXWQdMhU9Ig/PYrzyw/ul9jOIyh0N4M0tbC5hodg8dw==}
fast-uri@3.1.0:
resolution: {integrity: sha512-iPeeDKJSWf4IEOasVVrknXpaBV0IApz/gp7S2bb7Z4Lljbl2MGJRqInZiUrQwV16cpzw/D3S5j5Julj/gT52AA==}
fast-uri@3.1.2:
resolution: {integrity: sha512-rVjf7ArG3LTk+FS6Yw81V1DLuZl1bRbNrev6Tmd/9RaroeeRRJhAt7jg/6YFxbvAQXUCavSoZhPPj6oOx+5KjQ==}
fastq@1.20.1:
resolution: {integrity: sha512-GGToxJ/w1x32s/D2EKND7kTil4n8OVk/9mycTc4VDza13lOvpUZTGX3mFSCtV9ksdGBVzvsyAVLM6mHFThxXxw==}
@@ -4284,10 +4285,6 @@ packages:
resolution: {integrity: sha512-qif0+jGGZoLWdHey3UFHHWP0H7Gbmsk8T5VEqyYFbWqPr1XqvLGBbk/sl8V5exGmcYJklJOhOQq1pV9IcsiFag==}
engines: {node: ^10 || ^12 || >=14}
postcss@8.5.8:
resolution: {integrity: sha512-OW/rX8O/jXnm82Ey1k44pObPtdblfiuWnrd8X7GJ7emImCOstunGbXUpp7HdBrFQX6rJzn3sPT397Wp5aCwCHg==}
engines: {node: ^10 || ^12 || >=14}
prelude-ls@1.2.1:
resolution: {integrity: sha512-vkcDPrRZo1QZLbn5RLGPpg/WmIQ65qoWWhcGKf/b5eplkkarX0m9z8ppCat4mlOqUsWpyNuYgO3VRyrYHSzX5g==}
engines: {node: '>= 0.8.0'}
@@ -6093,7 +6090,7 @@ snapshots:
dependencies:
jsep: 1.4.0
'@kinvolk/headlamp-plugin@0.13.1(@swc/core@1.15.18)(@types/debug@4.1.12)(@typescript-eslint/parser@8.56.1(eslint@8.57.1)(typescript@5.6.2))(csstype@3.2.3)(esbuild@0.25.12)(immer@11.1.4)(openapi-types@12.1.3)(redux@5.0.1)(rollup@4.59.0)(terser@5.46.0)(webpack@5.105.4(@swc/core@1.15.18)(esbuild@0.25.12))':
'@kinvolk/headlamp-plugin@0.14.0(@swc/core@1.15.18)(@types/debug@4.1.12)(@typescript-eslint/parser@8.56.1(eslint@8.57.1)(typescript@5.6.2))(csstype@3.2.3)(esbuild@0.25.12)(immer@11.1.4)(openapi-types@12.1.3)(redux@5.0.1)(rollup@4.59.0)(terser@5.46.0)(webpack@5.105.4(@swc/core@1.15.18)(esbuild@0.25.12))':
dependencies:
'@apidevtools/swagger-parser': 10.1.1(openapi-types@12.1.3)
'@emotion/react': 11.14.0(@types/react@18.3.28)(react@18.3.1)
@@ -7686,7 +7683,7 @@ snapshots:
ajv@8.18.0:
dependencies:
fast-deep-equal: 3.1.3
fast-uri: 3.1.0
fast-uri: 3.1.2
json-schema-traverse: 1.0.0
require-from-string: 2.0.2
@@ -8225,12 +8222,12 @@ snapshots:
css-loader@6.11.0(webpack@5.105.4(@swc/core@1.15.18)(esbuild@0.25.12)):
dependencies:
icss-utils: 5.1.0(postcss@8.5.8)
postcss: 8.5.8
postcss-modules-extract-imports: 3.1.0(postcss@8.5.8)
postcss-modules-local-by-default: 4.2.0(postcss@8.5.8)
postcss-modules-scope: 3.2.1(postcss@8.5.8)
postcss-modules-values: 4.0.0(postcss@8.5.8)
icss-utils: 5.1.0(postcss@8.5.13)
postcss: 8.5.13
postcss-modules-extract-imports: 3.1.0(postcss@8.5.13)
postcss-modules-local-by-default: 4.2.0(postcss@8.5.13)
postcss-modules-scope: 3.2.1(postcss@8.5.13)
postcss-modules-values: 4.0.0(postcss@8.5.13)
postcss-value-parser: 4.2.0
semver: 7.7.4
optionalDependencies:
@@ -8934,7 +8931,7 @@ snapshots:
fast-levenshtein@2.0.6: {}
fast-uri@3.1.0: {}
fast-uri@3.1.2: {}
fastq@1.20.1:
dependencies:
@@ -9392,9 +9389,9 @@ snapshots:
dependencies:
safer-buffer: 2.1.2
icss-utils@5.1.0(postcss@8.5.8):
icss-utils@5.1.0(postcss@8.5.13):
dependencies:
postcss: 8.5.8
postcss: 8.5.13
ieee754@1.2.1: {}
@@ -9647,7 +9644,7 @@ snapshots:
jest-worker@27.5.1:
dependencies:
'@types/node': 20.19.37
'@types/node': 22.19.15
merge-stream: 2.0.0
supports-color: 8.1.1
@@ -9885,7 +9882,7 @@ snapshots:
md5.js@1.3.5:
dependencies:
hash-base: 3.0.5
hash-base: 3.1.2
inherits: 2.0.4
safe-buffer: 5.2.1
@@ -10501,26 +10498,26 @@ snapshots:
possible-typed-array-names@1.1.0: {}
postcss-modules-extract-imports@3.1.0(postcss@8.5.8):
postcss-modules-extract-imports@3.1.0(postcss@8.5.13):
dependencies:
postcss: 8.5.8
postcss: 8.5.13
postcss-modules-local-by-default@4.2.0(postcss@8.5.8):
postcss-modules-local-by-default@4.2.0(postcss@8.5.13):
dependencies:
icss-utils: 5.1.0(postcss@8.5.8)
postcss: 8.5.8
icss-utils: 5.1.0(postcss@8.5.13)
postcss: 8.5.13
postcss-selector-parser: 7.1.1
postcss-value-parser: 4.2.0
postcss-modules-scope@3.2.1(postcss@8.5.8):
postcss-modules-scope@3.2.1(postcss@8.5.13):
dependencies:
postcss: 8.5.8
postcss: 8.5.13
postcss-selector-parser: 7.1.1
postcss-modules-values@4.0.0(postcss@8.5.8):
postcss-modules-values@4.0.0(postcss@8.5.13):
dependencies:
icss-utils: 5.1.0(postcss@8.5.8)
postcss: 8.5.8
icss-utils: 5.1.0(postcss@8.5.13)
postcss: 8.5.13
postcss-selector-parser@7.1.1:
dependencies:
@@ -10535,12 +10532,6 @@ snapshots:
picocolors: 1.1.1
source-map-js: 1.2.1
postcss@8.5.8:
dependencies:
nanoid: 3.3.11
picocolors: 1.1.1
source-map-js: 1.2.1
prelude-ls@1.2.1: {}
prettier@2.8.8: {}
@@ -11811,7 +11802,7 @@ snapshots:
chokidar: 3.6.0
p-map: 7.0.4
picocolors: 1.1.1
tinyglobby: 0.2.15
tinyglobby: 0.2.16
vite: 8.0.10(@types/node@20.19.37)(esbuild@0.25.12)(terser@5.46.0)(yaml@2.8.2)
vite-plugin-svgr@4.5.0(rollup@4.59.0)(typescript@5.6.2)(vite@8.0.10(@types/node@20.19.37)(esbuild@0.25.12)(terser@5.46.0)(yaml@2.8.2)):