Files
HiveGrid-Wireframes/hivegrid-cloud/scripts/build-plugin-audit-package.sh
T

210 lines
7.8 KiB
Bash
Executable File

#!/usr/bin/env bash
# Build the Phase 06 plugin runtime external-audit package.
#
# Produces a single tar.gz containing:
# - the in-scope source code (a curated subset, NOT the whole repo)
# - the threat model, attack-surface inventory, internal findings
# - the RFP / sign-off template
# - the manifest of what's included (with SHA-256 per file)
#
# Operator workflow:
# 1. Run from the repo root: scripts/build-plugin-audit-package.sh
# 2. Output lands in artifacts/plugin-audit/<ISO-date>/plugin-audit-<short-sha>.tar.gz
# 3. Email the archive to the vendor per the RFP § 8.
# 4. Keep the archive AND its checksum locally for forensics — the vendor
# receives the same bytes.
#
# Dependencies: bash, tar, sha256sum (or shasum -a 256), git. macOS + Linux compatible.
set -euo pipefail
# ---- locate repo root ------------------------------------------------------
REPO_ROOT="$(git rev-parse --show-toplevel 2>/dev/null || true)"
if [ -z "${REPO_ROOT}" ] || [ ! -d "${REPO_ROOT}/.git" ]; then
echo "ERROR: must run inside the boilerplate git repo." >&2
exit 1
fi
cd "${REPO_ROOT}"
# ---- pick sha256 binary ----------------------------------------------------
if command -v sha256sum >/dev/null 2>&1; then
SHA256="sha256sum"
elif command -v shasum >/dev/null 2>&1; then
SHA256="shasum -a 256"
else
echo "ERROR: need sha256sum or shasum on PATH." >&2
exit 1
fi
# ---- destination -----------------------------------------------------------
SHORT_SHA="$(git rev-parse --short HEAD)"
DATE_ISO="$(date -u +%Y-%m-%d)"
OUT_DIR="artifacts/plugin-audit/${DATE_ISO}"
ARCHIVE_NAME="plugin-audit-${SHORT_SHA}.tar.gz"
ARCHIVE_PATH="${OUT_DIR}/${ARCHIVE_NAME}"
MANIFEST_PATH="${OUT_DIR}/MANIFEST-${SHORT_SHA}.txt"
STAGE_DIR="$(mktemp -d -t plugin-audit-XXXXXX)"
trap 'rm -rf "${STAGE_DIR}"' EXIT
mkdir -p "${OUT_DIR}"
echo "→ Building plugin audit package"
echo " repo: ${REPO_ROOT}"
echo " HEAD: ${SHORT_SHA}"
echo " out: ${ARCHIVE_PATH}"
echo " stage: ${STAGE_DIR}"
# ---- in-scope file list ----------------------------------------------------
# Append-only list. Keep aligned with docs/security/plugin-attack-surface.md § 1-6.
# Each path is relative to the repo root. Glob patterns expanded by bash.
INSCOPE_PATHS=(
# Runtime
"packages/plugin-runtime/src"
"packages/plugin-runtime/package.json"
"packages/plugin-runtime/tsconfig.json"
# SDK
"packages/plugin-sdk/src"
"packages/plugin-sdk/examples"
"packages/plugin-sdk/package.json"
"packages/plugin-sdk/tsconfig.json"
# Worker handlers
"apps/worker/src/jobs/plugin-invoke.ts"
"apps/worker/src/jobs/plugin-invoke.test.ts"
"apps/worker/src/jobs/plugin-capability-use.ts"
"apps/worker/src/jobs/plugin-capability-use.test.ts"
"apps/worker/src/jobs/plugin-audit-retention.ts"
"apps/worker/src/jobs/plugin-audit-retention.test.ts"
# Web routes + lifecycle service. `$tenant` and `$id` here are literal
# React Router path-segment names, NOT shell variables — we read array
# elements without `eval` (see staging loop below) so they stay literal.
'apps/web/app/routes/api.tenants.$tenant.plugins.tsx'
'apps/web/app/routes/api.tenants.$tenant.plugins.test.ts'
'apps/web/app/routes/app.$tenant/settings/plugins.tsx'
'apps/web/app/routes/app.$tenant/settings/plugins.test.ts'
'apps/web/app/routes/app.$tenant/settings/plugins.$id.audit.tsx'
"apps/web/app/modules/plugins"
# CLI plugin commands
"packages/cli/src/commands/plugins"
# Prisma schema for plugin tables
"prisma/schema/plugins.prisma"
# Documentation
"docs/security/plugin-threat-model.md"
"docs/security/plugin-attack-surface.md"
"docs/security/plugin-internal-findings.md"
"docs/security/plugin-audit-rfp.md"
"docs/security/plugin-runtime-signoff.md"
"docs/architecture/plugin-system.md"
"docs/architecture/plugin-hooks.md"
"docs/plugins"
"docs/improvement-plan/06-plugin-system"
# Env example (operator-visible config surface)
".env.example"
)
# ---- stage files -----------------------------------------------------------
# We copy paths preserving directory structure under ${STAGE_DIR}/code/.
PKG_ROOT="${STAGE_DIR}/plugin-audit-${SHORT_SHA}"
mkdir -p "${PKG_ROOT}/code"
MISSING=()
for path in "${INSCOPE_PATHS[@]}"; do
# No eval / re-expansion — the path is taken literally. React Router
# filenames contain literal `$tenant` / `$id` which we must NOT interpret
# as shell variables.
if [ ! -e "${path}" ]; then
MISSING+=("${path}")
continue
fi
parent_dir="$(dirname "${path}")"
mkdir -p "${PKG_ROOT}/code/${parent_dir}"
cp -R "${path}" "${PKG_ROOT}/code/${parent_dir}/"
done
# ---- include git HEAD metadata --------------------------------------------
git rev-parse HEAD > "${PKG_ROOT}/HEAD.txt"
git log -1 --format="%H%n%an <%ae>%n%aI%n%s" > "${PKG_ROOT}/HEAD-meta.txt"
git status --porcelain > "${PKG_ROOT}/WORKTREE-DIRTY.txt"
if [ -s "${PKG_ROOT}/WORKTREE-DIRTY.txt" ]; then
echo " WARNING: worktree is dirty — package reflects uncommitted state." >&2
echo " See ${PKG_ROOT}/WORKTREE-DIRTY.txt inside the archive." >&2
fi
# ---- readme inside the archive --------------------------------------------
cat > "${PKG_ROOT}/README.txt" <<'PKG_README'
Plugin Runtime External Audit Package
=====================================
This archive contains the Phase 06 plugin runtime code surface plus the
internal documentation we have produced. It is the input to the external
pen-test described in docs/security/plugin-audit-rfp.md.
What's inside
-------------
code/ Curated subset of the repo (see scripts/build-plugin-audit-package.sh
for the in-scope list).
HEAD.txt git HEAD SHA the archive was built from.
HEAD-meta.txt Committer + date + subject of HEAD.
WORKTREE-DIRTY.txt Uncommitted changes at build time (should be empty).
MANIFEST.txt SHA-256 of every file in this archive.
Where to start reading
----------------------
1. code/docs/security/plugin-audit-rfp.md — what we're asking for
2. code/docs/security/plugin-threat-model.md — what we worry about
3. code/docs/security/plugin-attack-surface.md — interface inventory
4. code/docs/security/plugin-internal-findings.md — what we've already found
5. code/packages/plugin-runtime/src — the runtime itself
6. code/apps/worker/src/jobs/plugin-*.ts — worker entry points
7. code/prisma/schema/plugins.prisma — data model
Re-test workflow
----------------
After remediation lands on `main`, re-run the build script and send the
new archive (with its delta vs. the previous SHORT_SHA).
PKG_README
# ---- per-file SHA-256 manifest --------------------------------------------
(
cd "${PKG_ROOT}"
if [ "${SHA256}" = "sha256sum" ]; then
find . -type f ! -name "MANIFEST.txt" -print0 | sort -z | xargs -0 sha256sum > MANIFEST.txt
else
# shasum -a 256 expects file args; use find -print0 + xargs
find . -type f ! -name "MANIFEST.txt" -print0 | sort -z | xargs -0 shasum -a 256 > MANIFEST.txt
fi
)
# ---- pack ------------------------------------------------------------------
tar -czf "${ARCHIVE_PATH}" -C "${STAGE_DIR}" "plugin-audit-${SHORT_SHA}"
${SHA256} "${ARCHIVE_PATH}" > "${MANIFEST_PATH}"
# ---- summary --------------------------------------------------------------
echo
echo "✔ Archive built:"
echo " ${ARCHIVE_PATH}"
echo " $(wc -c < "${ARCHIVE_PATH}") bytes"
echo
echo "✔ Outer-archive SHA-256:"
cat "${MANIFEST_PATH}"
echo
if [ ${#MISSING[@]} -gt 0 ]; then
echo "⚠ Missing in-scope paths (omitted from archive):"
for m in "${MISSING[@]}"; do echo " ${m}"; done
fi
echo
echo "Next steps:"
echo " 1. Verify the archive: tar -tzf ${ARCHIVE_PATH} | head"
echo " 2. Email the archive to the vendor per docs/security/plugin-audit-rfp.md § 8."
echo " 3. Record the archive checksum in your engagement tracker."