Commitf674b01unknown_key
security: address GateTest findings — heredoc injection + ci hardening
security: address GateTest findings — heredoc injection + ci hardening
Critical fix: the AI root-cause analysis step in hetzner-deploy.yml was
using an unquoted heredoc to build a JSON request body that embedded
${{ github.event.head_commit.message }}. Because the heredoc was not
quoted, bash expanded $(...) and `...` inside it — a commit message
containing a backtick command substitution could have executed arbitrary
commands on the runner with access to ANTHROPIC_API_KEY and the SSH key.
Swapped to a python3 heredoc with json.dumps over os.environ so every
untrusted input is properly JSON-encoded and never reaches bash.
CI hardening: added top-level `permissions: read-all` to fly-deploy,
hetzner-deploy, and stripe-bootstrap. The GITHUB_TOKEN was defaulting
to the broad write scope it inherits at the repo level; none of these
workflows need write access via the token (deploys use SSH/Fly API/
Stripe API keys, not the GITHUB_TOKEN).
Repo metadata: added `license`, `repository`, and `engines.node` to
package.json so the npm registry and supply-chain scanners can resolve
the project metadata.
Other GateTest findings reviewed and not actioned because they were
intentional with code comments explaining why:
- httpOnly:false on csrf double-submit cookie (JS must read it)
- httpOnly:false on theme cookie (pre-paint script must read it)
- new Function(onRunDone) in sse-client (dev-supplied JS, not user input)
- eval() / new Function() matches in intelligence.ts (regex patterns
for detecting eval in user code, not actually calling eval)
https://claude.ai/code/session_01QFLWDxWw65DX6enMcS5Lwe4 files changed+41−11f674b01f4b781ebc19b6a750a44df9b9d3ad14d4
4 changed files+41−11
Modified.github/workflows/fly-deploy.yml+4−0View fileUnifiedSplit
@@ -1,5 +1,9 @@
11name: Fly Deploy
22
3# Default-deny: scope GITHUB_TOKEN to read-only. Individual jobs can
4# escalate via their own `permissions:` block if they need write access.
5permissions: read-all
6
37on:
48 push:
59 branches:
Modified.github/workflows/hetzner-deploy.yml+24−11View fileUnifiedSplit
@@ -1,5 +1,10 @@
11name: Hetzner Deploy (gluecron.com)
22
3# Default-deny: scope GITHUB_TOKEN to read-only. The deploy uses an SSH key
4# (HETZNER_SSH_KEY) to push to the box, not the GitHub token, so read-only
5# is sufficient. Individual jobs may escalate if they need write access.
6permissions: read-all
7
38# Triggered on every push to main and on manual dispatch.
49# Steps:
510# 1. Capture the current SHA on the box (for rollback)
@@ -576,18 +581,26 @@ jobs:
576581 COMMIT_MSG: ${{ github.event.head_commit.message }}
577582 run: |
578583 set +e
579 # Build the prompt
580 cat > /tmp/prompt.json <<EOF
581 {
582 "model": "claude-haiku-4-5-20251001",
583 "max_tokens": 600,
584 "system": "You are a senior SRE diagnosing a failed deploy. Read the systemd status, journal, and curl output. In 1 short paragraph (under 100 words), identify the most likely root cause and the single fastest fix. Be direct, no preamble.",
585 "messages": [{
586 "role": "user",
587 "content": "Commit: $COMMIT_SHA\nMessage: $COMMIT_MSG\n\nDeploy logs:\n$DEPLOY_LOGS"
588 }]
584 # Build the prompt. Use python3 to JSON-encode every untrusted input
585 # (commit message, deploy logs, SHA) so backticks, $(…), quotes, or
586 # newlines in a commit message cannot break out of the JSON string
587 # or execute on the runner. The previous unquoted heredoc allowed
588 # arbitrary command execution via crafted commit messages.
589 python3 - <<'PY' > /tmp/prompt.json
590 import json, os
591 payload = {
592 "model": "claude-haiku-4-5-20251001",
593 "max_tokens": 600,
594 "system": "You are a senior SRE diagnosing a failed deploy. Read the systemd status, journal, and curl output. In 1 short paragraph (under 100 words), identify the most likely root cause and the single fastest fix. Be direct, no preamble.",
595 "messages": [{
596 "role": "user",
597 "content": "Commit: " + os.environ.get("COMMIT_SHA", "") +
598 "\nMessage: " + os.environ.get("COMMIT_MSG", "") +
599 "\n\nDeploy logs:\n" + os.environ.get("DEPLOY_LOGS", ""),
600 }],
589601 }
590 EOF
602 print(json.dumps(payload))
603 PY
591604 response=$(curl -s https://api.anthropic.com/v1/messages \
592605 -H "x-api-key: $ANTHROPIC_API_KEY" \
593606 -H "anthropic-version: 2023-06-01" \
Modified.github/workflows/stripe-bootstrap.yml+5−0View fileUnifiedSplit
@@ -1,5 +1,10 @@
11name: Stripe Bootstrap
22
3# Default-deny: scope GITHUB_TOKEN to read-only. The bootstrap script
4# authenticates with STRIPE_SECRET_KEY and FLY_API_TOKEN secrets, not the
5# GitHub token, so read-only is sufficient.
6permissions: read-all
7
38# Idempotent one-shot: creates Stripe products + prices + webhook endpoint
49# matching gluecron's billing schema. Re-runnable. Propagates the
510# STRIPE_SECRET_KEY and STRIPE_WEBHOOK_SECRET into Fly so the live site
Modifiedpackage.json+8−0View fileUnifiedSplit
@@ -2,6 +2,14 @@
22 "name": "gluecron",
33 "version": "0.1.0",
44 "description": "AI-native code intelligence platform — git hosting, automated CI, and green ecosystem enforcement",
5 "license": "MIT",
6 "repository": {
7 "type": "git",
8 "url": "https://gluecron.com/ccantynz/Gluecron.com.git"
9 },
10 "engines": {
11 "node": ">=20"
12 },
513 "type": "module",
614 "scripts": {
715 "dev": "bun run --hot src/index.ts",
816