210 lines
7.8 KiB
Bash
Executable File
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."
|