mirror of
https://github.com/pezkuwichain/pwap.git
synced 2026-06-12 09:51:01 +00:00
ci(security): Faz 3 + ekstra — runner consolidation, auto-rollback, cosign, SRI, dep cleanup
* Faz 3.1 — All CI jobs moved to self-hosted pwap-runner (DEV VPS).
No more dependency on GitHub-hosted runners — supply-chain attack
surface from GHA runner image compromise eliminated.
* Faz 3.3 — Automatic rollback on health-check fail. Each deploy stamps
/.deploy-sha into the artifact. On health-check failure, the deploy
job reads the previous SHA from the live site, pulls that image, and
redeploys. Telegram notification differentiates: rolled-back-OK,
rollback-also-failed, no-prev-available, manual-rollback-needed.
* E.3 — cosign keyless image signing. build-image signs the GHCR
manifest via Sigstore Fulcio (OIDC, no long-lived keys). deploy-app
and deploy-pex verify the signature before extracting /dist —
unsigned or tampered images cannot deploy. Identity-pinned to this
workflow file.
* E.5 — Subresource Integrity (SRI). vite-plugin-subresource-integrity
injects sha384 integrity= into <script>/<link> tags at build time.
CDN/proxy compromise cannot inject tampered JS — browser blocks on
hash mismatch.
* E.2 — Dependabot triage. 14 alerts: 7 high + 4 moderate cleared via
npm audit fix + npm overrides (elliptic, create-ecdh). 6 low
(transitive in vite-plugin-node-polyfills chain) accepted; the
upstream fix proposes a semver-major DOWNGRADE which makes no sense.
* E.1 — Branch protection on main: CI Gate ✅ required, 1 review
required, force-push and deletion blocked.
This commit is contained in:
@@ -21,7 +21,7 @@ concurrency:
|
||||
jobs:
|
||||
analyze:
|
||||
name: Analyze ${{ matrix.language }}
|
||||
runs-on: ubuntu-latest
|
||||
runs-on: pwap-runner
|
||||
strategy:
|
||||
fail-fast: false
|
||||
matrix:
|
||||
|
||||
@@ -32,7 +32,7 @@ jobs:
|
||||
# ========================================
|
||||
web:
|
||||
name: Web App
|
||||
runs-on: ubuntu-latest
|
||||
runs-on: pwap-runner
|
||||
|
||||
steps:
|
||||
- name: Checkout code
|
||||
@@ -92,7 +92,7 @@ jobs:
|
||||
# ========================================
|
||||
build-image:
|
||||
name: Build & Push Image
|
||||
runs-on: ubuntu-latest
|
||||
runs-on: pwap-runner
|
||||
needs: [web, telegram-gate]
|
||||
if: |
|
||||
github.ref == 'refs/heads/main' &&
|
||||
@@ -101,14 +101,21 @@ jobs:
|
||||
permissions:
|
||||
contents: read
|
||||
packages: write
|
||||
id-token: write # cosign keyless signing via Sigstore OIDC
|
||||
outputs:
|
||||
image_sha: ${{ steps.meta.outputs.image_sha }}
|
||||
image_digest: ${{ steps.build.outputs.digest }}
|
||||
|
||||
steps:
|
||||
- uses: actions/checkout@v4
|
||||
|
||||
- uses: docker/setup-buildx-action@v3
|
||||
|
||||
- name: Install cosign
|
||||
uses: sigstore/cosign-installer@v3
|
||||
with:
|
||||
cosign-release: 'v2.4.1'
|
||||
|
||||
- name: Log in to GHCR
|
||||
uses: docker/login-action@v3
|
||||
with:
|
||||
@@ -125,6 +132,7 @@ jobs:
|
||||
echo "image=${{ env.REGISTRY }}/${{ env.IMAGE_NAME }}" >> $GITHUB_OUTPUT
|
||||
|
||||
- name: Build and push
|
||||
id: build
|
||||
uses: docker/build-push-action@v6
|
||||
with:
|
||||
context: ./
|
||||
@@ -146,6 +154,15 @@ jobs:
|
||||
cache-to: type=registry,ref=${{ steps.meta.outputs.image }}:cache,mode=max
|
||||
provenance: false
|
||||
|
||||
- name: Sign image with cosign (keyless, Sigstore Fulcio)
|
||||
env:
|
||||
COSIGN_EXPERIMENTAL: '1'
|
||||
run: |
|
||||
IMAGE_DIGEST="${{ steps.meta.outputs.image }}@${{ steps.build.outputs.digest }}"
|
||||
echo "Signing $IMAGE_DIGEST"
|
||||
cosign sign --yes "$IMAGE_DIGEST"
|
||||
echo "✅ Image signed (transparency log: rekor.sigstore.dev)"
|
||||
|
||||
# ========================================
|
||||
# TELEGRAM CEO APPROVAL GATE
|
||||
# Runs on self-hosted pwap-runner (DEV VPS) where pexsec-bot.service
|
||||
@@ -215,7 +232,7 @@ jobs:
|
||||
# ========================================
|
||||
bump-version:
|
||||
name: Bump Version
|
||||
runs-on: ubuntu-latest
|
||||
runs-on: pwap-runner
|
||||
needs: [web, security-audit, telegram-gate, build-image]
|
||||
# Skip on rollback (workflow_dispatch with rollback_to set)
|
||||
if: |
|
||||
@@ -261,7 +278,7 @@ jobs:
|
||||
# ========================================
|
||||
deploy-app:
|
||||
name: Deploy app.pezkuwichain.io
|
||||
runs-on: ubuntu-latest
|
||||
runs-on: pwap-runner
|
||||
needs: [telegram-gate, bump-version, build-image]
|
||||
if: |
|
||||
always() &&
|
||||
@@ -286,6 +303,14 @@ jobs:
|
||||
echo "sha=${{ needs.build-image.outputs.image_sha }}" >> $GITHUB_OUTPUT
|
||||
fi
|
||||
|
||||
- name: Capture currently-live SHA (for auto-rollback)
|
||||
id: prev
|
||||
run: |
|
||||
# /.deploy-sha is written into every deploy; read what's live now
|
||||
PREV=$(curl -sf --max-time 5 "https://${{ env.DOMAIN }}/.deploy-sha" | head -c 40 | tr -dc 'a-f0-9' || echo "")
|
||||
echo "Previous live SHA: ${PREV:-unknown}"
|
||||
echo "prev=$PREV" >> $GITHUB_OUTPUT
|
||||
|
||||
- name: Log in to GHCR
|
||||
uses: docker/login-action@v3
|
||||
with:
|
||||
@@ -293,6 +318,24 @@ jobs:
|
||||
username: ${{ github.actor }}
|
||||
password: ${{ secrets.GITHUB_TOKEN }}
|
||||
|
||||
- name: Install cosign (for verify)
|
||||
uses: sigstore/cosign-installer@v3
|
||||
with:
|
||||
cosign-release: 'v2.4.1'
|
||||
|
||||
- name: Verify image signature (cosign keyless)
|
||||
env:
|
||||
COSIGN_EXPERIMENTAL: '1'
|
||||
run: |
|
||||
IMAGE="${{ env.REGISTRY }}/${{ env.IMAGE_NAME }}:${{ steps.sha.outputs.sha }}"
|
||||
echo "Verifying signature for $IMAGE"
|
||||
# Identity = workflow that built this image (build-image job in this repo)
|
||||
cosign verify "$IMAGE" \
|
||||
--certificate-identity-regexp "^https://github.com/pezkuwichain/pwap/.github/workflows/quality-gate.yml@" \
|
||||
--certificate-oidc-issuer "https://token.actions.githubusercontent.com" \
|
||||
> /dev/null
|
||||
echo "✅ Signature valid — image was built by trusted CI"
|
||||
|
||||
- name: Extract /dist from image
|
||||
run: |
|
||||
IMAGE="${{ env.REGISTRY }}/${{ env.IMAGE_NAME }}:${{ steps.sha.outputs.sha }}"
|
||||
@@ -301,7 +344,8 @@ jobs:
|
||||
mkdir -p dist
|
||||
docker cp "$CID:/dist/." dist/
|
||||
docker rm "$CID" >/dev/null
|
||||
echo "Extracted dist/ contents:"
|
||||
# Stamp this build's SHA into dist so future deploys can read PREV
|
||||
echo "${{ steps.sha.outputs.sha }}" > dist/.deploy-sha
|
||||
ls -la dist/ | head -10
|
||||
|
||||
- name: Deploy to DEV VPS
|
||||
@@ -329,6 +373,48 @@ jobs:
|
||||
echo "❌ Health check failed for ${{ env.DOMAIN }}"
|
||||
exit 1
|
||||
|
||||
# ── Automatic rollback: pull PREV SHA image, redeploy, recheck ──
|
||||
- name: Auto-rollback to previous SHA
|
||||
id: rollback
|
||||
if: failure() && steps.healthcheck.conclusion == 'failure' && steps.prev.outputs.prev != ''
|
||||
run: |
|
||||
PREV="${{ steps.prev.outputs.prev }}"
|
||||
echo "🔄 Rolling back ${{ env.DOMAIN }} to $PREV"
|
||||
IMAGE="${{ env.REGISTRY }}/${{ env.IMAGE_NAME }}:$PREV"
|
||||
docker pull "$IMAGE"
|
||||
CID=$(docker create "$IMAGE")
|
||||
rm -rf dist && mkdir dist
|
||||
docker cp "$CID:/dist/." dist/
|
||||
docker rm "$CID" >/dev/null
|
||||
echo "$PREV" > dist/.deploy-sha
|
||||
echo "rollback_sha=$PREV" >> $GITHUB_OUTPUT
|
||||
|
||||
- name: SCP rollback artifact
|
||||
if: steps.rollback.outcome == 'success'
|
||||
uses: appleboy/scp-action@v1.0.0
|
||||
with:
|
||||
host: ${{ secrets.VPS_HOST }}
|
||||
username: ${{ secrets.VPS_USER }}
|
||||
key: ${{ secrets.VPS_SSH_KEY }}
|
||||
port: ${{ secrets.VPS_SSH_PORT || 2222 }}
|
||||
source: 'dist/*'
|
||||
target: '/var/www/subdomains/app'
|
||||
strip_components: 1
|
||||
|
||||
- name: Re-health-check after rollback
|
||||
if: steps.rollback.outcome == 'success'
|
||||
id: healthcheck_rb
|
||||
run: |
|
||||
for i in 1 2 3 4 5 6; do
|
||||
if curl -sf --max-time 10 "https://${{ env.DOMAIN }}/" >/dev/null; then
|
||||
echo "✅ Rolled back successfully — ${{ env.DOMAIN }} healthy on ${{ steps.rollback.outputs.rollback_sha }}"
|
||||
exit 0
|
||||
fi
|
||||
sleep 10
|
||||
done
|
||||
echo "❌ Rollback also failed!"
|
||||
exit 1
|
||||
|
||||
- name: Post-deploy notification
|
||||
if: success()
|
||||
run: |
|
||||
@@ -339,17 +425,29 @@ jobs:
|
||||
env:
|
||||
BOT_TOKEN: ${{ secrets.PEXSEC_BOT_TOKEN }}
|
||||
CEO_CHAT_ID: ${{ secrets.TELEGRAM_CEO_CHAT_ID }}
|
||||
NEW_SHA: ${{ steps.sha.outputs.sha }}
|
||||
PREV_SHA: ${{ steps.prev.outputs.prev }}
|
||||
ROLLBACK_OUTCOME: ${{ steps.rollback.outcome }}
|
||||
RECHECK_OUTCOME: ${{ steps.healthcheck_rb.outcome }}
|
||||
run: |
|
||||
if [ "$RECHECK_OUTCOME" = "success" ]; then
|
||||
MSG="⚠️ pwap/web ${{ env.DOMAIN }}: deploy ($NEW_SHA) failed health check, AUTO-ROLLED-BACK to $PREV_SHA. Site healthy."
|
||||
elif [ "$ROLLBACK_OUTCOME" = "success" ]; then
|
||||
MSG="🚨 pwap/web ${{ env.DOMAIN }}: deploy ($NEW_SHA) failed AND rollback to $PREV_SHA also failed. Manual intervention needed."
|
||||
elif [ -z "$PREV_SHA" ]; then
|
||||
MSG="❌ pwap/web ${{ env.DOMAIN }}: deploy ($NEW_SHA) failed. No previous SHA available (first deploy?). Manual rollback: gh workflow run quality-gate.yml -f rollback_to=<sha>"
|
||||
else
|
||||
MSG="❌ pwap/web ${{ env.DOMAIN }}: deploy ($NEW_SHA) failed. Auto-rollback was not attempted. Manual: gh workflow run quality-gate.yml -f rollback_to=$PREV_SHA"
|
||||
fi
|
||||
curl -s -X POST "https://api.telegram.org/bot${BOT_TOKEN}/sendMessage" \
|
||||
-d "chat_id=${CEO_CHAT_ID}" \
|
||||
-d "text=❌ pwap/web deploy FAILED: ${{ env.DOMAIN }} (sha ${{ steps.sha.outputs.sha }}). Health check did not pass after deploy. Manual rollback needed: gh workflow run quality-gate.yml -f rollback_to=<previous-sha>"
|
||||
-d "chat_id=${CEO_CHAT_ID}" --data-urlencode "text=$MSG"
|
||||
|
||||
# ========================================
|
||||
# DEPLOY TO pex.mom (VPS3 — geo-redundant mirror)
|
||||
# ========================================
|
||||
deploy-pex:
|
||||
name: Deploy pex.mom
|
||||
runs-on: ubuntu-latest
|
||||
runs-on: pwap-runner
|
||||
needs: [telegram-gate, bump-version, build-image]
|
||||
if: |
|
||||
always() &&
|
||||
@@ -373,6 +471,13 @@ jobs:
|
||||
echo "sha=${{ needs.build-image.outputs.image_sha }}" >> $GITHUB_OUTPUT
|
||||
fi
|
||||
|
||||
- name: Capture currently-live SHA (for auto-rollback)
|
||||
id: prev
|
||||
run: |
|
||||
PREV=$(curl -sf --max-time 5 "https://${{ env.DOMAIN }}/.deploy-sha" | head -c 40 | tr -dc 'a-f0-9' || echo "")
|
||||
echo "Previous live SHA: ${PREV:-unknown}"
|
||||
echo "prev=$PREV" >> $GITHUB_OUTPUT
|
||||
|
||||
- name: Log in to GHCR
|
||||
uses: docker/login-action@v3
|
||||
with:
|
||||
@@ -380,6 +485,23 @@ jobs:
|
||||
username: ${{ github.actor }}
|
||||
password: ${{ secrets.GITHUB_TOKEN }}
|
||||
|
||||
- name: Install cosign (for verify)
|
||||
uses: sigstore/cosign-installer@v3
|
||||
with:
|
||||
cosign-release: 'v2.4.1'
|
||||
|
||||
- name: Verify image signature (cosign keyless)
|
||||
env:
|
||||
COSIGN_EXPERIMENTAL: '1'
|
||||
run: |
|
||||
IMAGE="${{ env.REGISTRY }}/${{ env.IMAGE_NAME }}:${{ steps.sha.outputs.sha }}"
|
||||
echo "Verifying signature for $IMAGE"
|
||||
cosign verify "$IMAGE" \
|
||||
--certificate-identity-regexp "^https://github.com/pezkuwichain/pwap/.github/workflows/quality-gate.yml@" \
|
||||
--certificate-oidc-issuer "https://token.actions.githubusercontent.com" \
|
||||
> /dev/null
|
||||
echo "✅ Signature valid — image was built by trusted CI"
|
||||
|
||||
- name: Extract /dist from image
|
||||
run: |
|
||||
IMAGE="${{ env.REGISTRY }}/${{ env.IMAGE_NAME }}:${{ steps.sha.outputs.sha }}"
|
||||
@@ -388,6 +510,7 @@ jobs:
|
||||
mkdir -p dist
|
||||
docker cp "$CID:/dist/." dist/
|
||||
docker rm "$CID" >/dev/null
|
||||
echo "${{ steps.sha.outputs.sha }}" > dist/.deploy-sha
|
||||
|
||||
- name: Deploy to VPS3
|
||||
uses: appleboy/scp-action@v1.0.0
|
||||
@@ -401,6 +524,7 @@ jobs:
|
||||
strip_components: 1
|
||||
|
||||
- name: Health check (60s window)
|
||||
id: healthcheck
|
||||
run: |
|
||||
for i in 1 2 3 4 5 6; do
|
||||
if curl -sf --max-time 10 "https://${{ env.DOMAIN }}/" >/dev/null; then
|
||||
@@ -413,6 +537,47 @@ jobs:
|
||||
echo "❌ Health check failed for ${{ env.DOMAIN }}"
|
||||
exit 1
|
||||
|
||||
- name: Auto-rollback to previous SHA
|
||||
id: rollback
|
||||
if: failure() && steps.healthcheck.conclusion == 'failure' && steps.prev.outputs.prev != ''
|
||||
run: |
|
||||
PREV="${{ steps.prev.outputs.prev }}"
|
||||
echo "🔄 Rolling back ${{ env.DOMAIN }} to $PREV"
|
||||
IMAGE="${{ env.REGISTRY }}/${{ env.IMAGE_NAME }}:$PREV"
|
||||
docker pull "$IMAGE"
|
||||
CID=$(docker create "$IMAGE")
|
||||
rm -rf dist && mkdir dist
|
||||
docker cp "$CID:/dist/." dist/
|
||||
docker rm "$CID" >/dev/null
|
||||
echo "$PREV" > dist/.deploy-sha
|
||||
echo "rollback_sha=$PREV" >> $GITHUB_OUTPUT
|
||||
|
||||
- name: SCP rollback artifact
|
||||
if: steps.rollback.outcome == 'success'
|
||||
uses: appleboy/scp-action@v1.0.0
|
||||
with:
|
||||
host: ${{ secrets.VPS_PEX_HOST }}
|
||||
username: ${{ secrets.VPS_PEX_USER }}
|
||||
key: ${{ secrets.VPS_PEX_SSH_KEY }}
|
||||
port: ${{ secrets.VPS_PEX_SSH_PORT || 22 }}
|
||||
source: 'dist/*'
|
||||
target: '/var/www/pex.mom'
|
||||
strip_components: 1
|
||||
|
||||
- name: Re-health-check after rollback
|
||||
if: steps.rollback.outcome == 'success'
|
||||
id: healthcheck_rb
|
||||
run: |
|
||||
for i in 1 2 3 4 5 6; do
|
||||
if curl -sf --max-time 10 "https://${{ env.DOMAIN }}/" >/dev/null; then
|
||||
echo "✅ Rolled back successfully — ${{ env.DOMAIN }} healthy on ${{ steps.rollback.outputs.rollback_sha }}"
|
||||
exit 0
|
||||
fi
|
||||
sleep 10
|
||||
done
|
||||
echo "❌ Rollback also failed!"
|
||||
exit 1
|
||||
|
||||
- name: Post-deploy notification
|
||||
if: success()
|
||||
run: |
|
||||
@@ -423,10 +588,22 @@ jobs:
|
||||
env:
|
||||
BOT_TOKEN: ${{ secrets.PEXSEC_BOT_TOKEN }}
|
||||
CEO_CHAT_ID: ${{ secrets.TELEGRAM_CEO_CHAT_ID }}
|
||||
NEW_SHA: ${{ steps.sha.outputs.sha }}
|
||||
PREV_SHA: ${{ steps.prev.outputs.prev }}
|
||||
ROLLBACK_OUTCOME: ${{ steps.rollback.outcome }}
|
||||
RECHECK_OUTCOME: ${{ steps.healthcheck_rb.outcome }}
|
||||
run: |
|
||||
if [ "$RECHECK_OUTCOME" = "success" ]; then
|
||||
MSG="⚠️ pwap/web ${{ env.DOMAIN }}: deploy ($NEW_SHA) failed health check, AUTO-ROLLED-BACK to $PREV_SHA. Site healthy."
|
||||
elif [ "$ROLLBACK_OUTCOME" = "success" ]; then
|
||||
MSG="🚨 pwap/web ${{ env.DOMAIN }}: deploy ($NEW_SHA) failed AND rollback to $PREV_SHA also failed. Manual intervention needed."
|
||||
elif [ -z "$PREV_SHA" ]; then
|
||||
MSG="❌ pwap/web ${{ env.DOMAIN }}: deploy ($NEW_SHA) failed. No previous SHA available (first deploy?). Manual rollback: gh workflow run quality-gate.yml -f rollback_to=<sha>"
|
||||
else
|
||||
MSG="❌ pwap/web ${{ env.DOMAIN }}: deploy ($NEW_SHA) failed. Auto-rollback was not attempted. Manual: gh workflow run quality-gate.yml -f rollback_to=$PREV_SHA"
|
||||
fi
|
||||
curl -s -X POST "https://api.telegram.org/bot${BOT_TOKEN}/sendMessage" \
|
||||
-d "chat_id=${CEO_CHAT_ID}" \
|
||||
-d "text=❌ pwap/web deploy FAILED: ${{ env.DOMAIN }} (sha ${{ steps.sha.outputs.sha }}). Health check did not pass. Rollback: gh workflow run quality-gate.yml -f rollback_to=<previous-sha>"
|
||||
-d "chat_id=${CEO_CHAT_ID}" --data-urlencode "text=$MSG"
|
||||
|
||||
# ========================================
|
||||
# SECURITY CHECKS (BLOCKING)
|
||||
@@ -434,7 +611,7 @@ jobs:
|
||||
# ========================================
|
||||
security-audit:
|
||||
name: Security Audit
|
||||
runs-on: ubuntu-latest
|
||||
runs-on: pwap-runner
|
||||
needs: [web]
|
||||
|
||||
steps:
|
||||
@@ -476,7 +653,7 @@ jobs:
|
||||
# ========================================
|
||||
ci-gate:
|
||||
name: CI Gate ✅
|
||||
runs-on: ubuntu-latest
|
||||
runs-on: pwap-runner
|
||||
needs: [web, security-audit]
|
||||
if: always()
|
||||
|
||||
|
||||
Generated
+8
@@ -109,6 +109,7 @@
|
||||
"typescript-eslint": "^8.0.1",
|
||||
"vite": "^7.3.1",
|
||||
"vite-plugin-node-polyfills": "^0.25.0",
|
||||
"vite-plugin-subresource-integrity": "^0.0.12",
|
||||
"vitest": "^4.0.10"
|
||||
}
|
||||
},
|
||||
@@ -13259,6 +13260,13 @@
|
||||
"vite": "^2.0.0 || ^3.0.0 || ^4.0.0 || ^5.0.0 || ^6.0.0 || ^7.0.0"
|
||||
}
|
||||
},
|
||||
"node_modules/vite-plugin-subresource-integrity": {
|
||||
"version": "0.0.12",
|
||||
"resolved": "https://registry.npmjs.org/vite-plugin-subresource-integrity/-/vite-plugin-subresource-integrity-0.0.12.tgz",
|
||||
"integrity": "sha512-geKEo1KgGA56G8CciaoKA3Yf7ckpR23zSuSW802xrisW6vnH+dAYjKXZygEcmFKfsOe0+r3uG7Oz9eFEwhdjxg==",
|
||||
"dev": true,
|
||||
"license": "MIT"
|
||||
},
|
||||
"node_modules/vite/node_modules/fdir": {
|
||||
"version": "6.5.0",
|
||||
"resolved": "https://registry.npmjs.org/fdir/-/fdir-6.5.0.tgz",
|
||||
|
||||
+4
-1
@@ -120,7 +120,9 @@
|
||||
"@pezkuwi/x-textdecoder": "^14.0.25",
|
||||
"@pezkuwi/x-textencoder": "^14.0.25",
|
||||
"@pezkuwi/x-ws": "^14.0.25",
|
||||
"@pezkuwi/networks": "^14.0.25"
|
||||
"@pezkuwi/networks": "^14.0.25",
|
||||
"elliptic": "^6.6.1",
|
||||
"create-ecdh": "^5.0.1"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@eslint/js": "^9.9.0",
|
||||
@@ -147,6 +149,7 @@
|
||||
"typescript-eslint": "^8.0.1",
|
||||
"vite": "^7.3.1",
|
||||
"vite-plugin-node-polyfills": "^0.25.0",
|
||||
"vite-plugin-subresource-integrity": "^0.0.12",
|
||||
"vitest": "^4.0.10"
|
||||
}
|
||||
}
|
||||
|
||||
@@ -3,6 +3,7 @@ import { defineConfig } from "vitest/config";
|
||||
import react from "@vitejs/plugin-react-swc";
|
||||
import path from "path";
|
||||
import { nodePolyfills } from 'vite-plugin-node-polyfills';
|
||||
import subresourceIntegrity from 'vite-plugin-subresource-integrity';
|
||||
|
||||
// https://vitejs.dev/config/
|
||||
export default defineConfig(({ command }) => ({
|
||||
@@ -38,6 +39,10 @@ export default defineConfig(({ command }) => ({
|
||||
},
|
||||
protocolImports: true,
|
||||
}),
|
||||
// SRI: production build sırasında <script>/<link> tag'lerine
|
||||
// sha384 integrity hash ekle. CDN/proxy compromise olsa bile
|
||||
// tampered asset browser tarafından load edilmez.
|
||||
command === 'build' ? subresourceIntegrity({ algorithm: 'sha384' }) : null,
|
||||
].filter(Boolean),
|
||||
resolve: {
|
||||
mainFields: ['browser', 'module', 'main', 'exports'],
|
||||
|
||||
Reference in New Issue
Block a user