Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
19 commits
Select commit Hold shift + click to select a range
e99ae96
feat(security): add exec-safe argv-only process helpers
fraware Jul 16, 2026
c304613
feat(security): require bearer auth on metrics routes
fraware Jul 16, 2026
95e8350
fix(security): fail closed when redis budget is unavailable
fraware Jul 16, 2026
f9c599b
chore(security): require redis auth in local compose
fraware Jul 16, 2026
fd8457d
feat(patching): enforce healing patch path deny policy
fraware Jul 16, 2026
5f117ca
feat(testing): harden test and gate activity execution
fraware Jul 16, 2026
1071e3e
feat(freestyle): require all-pass quorum for success
fraware Jul 16, 2026
beb719b
feat(testing): add real static-analysis analyzer runners
fraware Jul 16, 2026
47de6de
feat(testing): add differential fuzzing runners
fraware Jul 16, 2026
bc1ecf5
feat(security): add attestation store and policy modes
fraware Jul 16, 2026
2d095f7
feat(proofs): reject lean sorry and add proof discovery
fraware Jul 16, 2026
35e0dc2
feat(morph): unify http adapter and patch apply path
fraware Jul 16, 2026
b35854d
feat(temporal): harden core heal loop and patched sha tracking
fraware Jul 16, 2026
bfffd5c
feat(temporal): add phase 3 gates and observability hooks
fraware Jul 16, 2026
6be9356
feat(github-app): tighten host binding and temporal client
fraware Jul 16, 2026
b9e9acf
feat(claude): cap tokens and support triage-first diagnosis
fraware Jul 16, 2026
92f96e7
test(temporal): add workflow environment and soak harnesses
fraware Jul 16, 2026
f4f6b58
chore(deps): expand ci matrix and pin vulnerable transitive deps
fraware Jul 16, 2026
adf3855
docs: document security hardening and phase gates
fraware Jul 16, 2026
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
83 changes: 76 additions & 7 deletions .env.example
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@
# --- GitHub App (required for apps/github-app and worker GitHub API calls) ---
GITHUB_APP_ID=
# PEM contents with literal \n newlines, or path-style single line
# Accepts RSA, PKCS#8, EC, or OpenSSH PEM formats
GITHUB_PRIVATE_KEY=
GITHUB_WEBHOOK_SECRET=

Expand All @@ -16,22 +17,41 @@ TEMPORAL_TASK_QUEUE=self-healing-ci
TEMPORAL_TLS_ENABLED=false
TEMPORAL_CONNECTION_TIMEOUT=10000
TEMPORAL_MAX_RETRIES=3
# WorkflowEnvironment tests (opt-in). Install CLI: node scripts/install-temporal-cli.js
# TEMPORAL_CLI_PATH=
# SELF_HEALING_RUN_TEMPORAL_TESTS=true
# Under corp TLS interception for npm/GitHub downloads:
# NODE_EXTRA_CA_CERTS=/path/to/corp-root-ca.pem
# Never set NODE_TLS_REJECT_UNAUTHORIZED=0 in CI.

# --- Redis (deduplication, workflow state; optional — falls back if unavailable) ---
REDIS_URL=redis://127.0.0.1:6379
# --- Redis (required for dedup, budget, rate limit in production) ---
# docker-compose uses REDIS_PASSWORD; URL must include the password.
REDIS_PASSWORD=change-me-local-only
REDIS_URL=redis://:change-me-local-only@127.0.0.1:6379
# Local/dev only: allow healing when Redis is down (NEVER in production)
SELF_HEALING_ALLOW_DEGRADED=false

# --- Self-healing controls ---
SELF_HEALING_ENABLED=true
SELF_HEALING_DRY_RUN=false
SELF_HEALING_MAX_RUNS_PER_DAY=20
SELF_HEALING_AUTO_MERGE=false
# Max extra diagnose→patch→test loops after a non-infra test failure (default 1)
SELF_HEALING_MAX_DIAGNOSIS_RETRIES=1
# When true, skipped proofs block auto-merge. Default: true if Lean mode is enabled, else false.
# SELF_HEALING_REQUIRE_PROOFS=true
# Comma-separated substrings; workflow name must contain one (case-insensitive). Empty = default (ci,test,build,lint)
SELF_HEALING_WORKFLOW_ALLOWLIST=
PATCH_BACKEND=github
# run-tests activity: command and wall-clock timeout
SELF_HEALING_TEST_COMMAND=pnpm test
# Patch path policy (JSON string arrays). Default deny includes workflows, secrets, .env, keys.
# SELF_HEALING_PATCH_DENY_GLOBS=["**/custom-deny/**"]
# SELF_HEALING_PATCH_ALLOW_GLOBS=[]
# run-tests activity: prefer JSON argv array, e.g. ["pnpm","test"]
# String form allows only: pnpm, npm, yarn, cargo, pytest, go, make, node (no shell metacharacters)
SELF_HEALING_TEST_COMMAND=["pnpm","test"]
SELF_HEALING_TEST_TIMEOUT_MS=600000
# auto | http | docker | local | disabled — see README for backend selection
# Production recommendation: container isolation (docker/http), not local shell
SELF_HEALING_TEST_EXECUTION_MODE=auto
# Absolute path to a repo checkout (local shell tests, and default host mount for Docker Freestyle)
SELF_HEALING_TEST_WORKDIR=
Expand All @@ -45,35 +65,84 @@ FREESTYLE_CONTAINER_WORKSPACE=/workspace
FREESTYLE_CONTAINER_WORKDIR=/workspace
FREESTYLE_DOCKER_SOCKET=
FREESTYLE_RETRY_COUNT=2
# Deterministic Freestyle success: all retries must pass (n/n). Set to a number <= retry count for quorum.
FREESTYLE_SUCCESS_QUORUM=all
# Freestyle Docker integration tests (opt-in; CI freestyle-docker job sets REQUIRE):
# SELF_HEALING_REQUIRE_DOCKER=true
# SELF_HEALING_RUN_DOCKER_TESTS=true
# DOCKER_SOCKET=/var/run/docker.sock

# --- Optional: Morph API (when PATCH_BACKEND=morph) ---
MORPH_API_KEY=
MORPH_API_URL=https://api.morph.dev
# Apply path (default /apply; some deployments use /apply-patch)
MORPH_APPLY_PATH=/apply
MORPH_TIMEOUT_MS=120000

# --- Optional: Freestyle HTTP API (POST /v1/test-runs) ---
FREESTYLE_API_KEY=
FREESTYLE_API_URL=https://api.freestyle.dev

# --- Optional: Lean proofs (HTTP or local @self-healing-ci/lean) ---
# disabled | http | local | auto — empty proofFiles → skipped (never vacuous success)
LEAN_PROOFS_EXECUTION_MODE=auto
LEAN_LOCAL_WORKSPACE=/tmp/lean-proofs
LEAN_LOCAL_TIMEOUT_MS=120000
# Dev only: allow sorry/admit to count as validated. Never enable in production.
LEAN_ALLOW_SORRY=false
LEAN_API_KEY=
LEAN_API_URL=https://api.lean.dev
# Optional override path in-repo for proof file list (JSON: { "proofFiles": ["..."] })
# SELF_HEALING_PROOFS_MANIFEST=.self-healing/proofs.json

# --- Optional workflow gates (default OFF) ---
# Static analysis on healing tip; blocks merge on high+ findings
SELF_HEALING_STATIC_ANALYSIS=false
# SELF_HEALING_STATIC_ANALYSIS_TOOLS=eslint,ruff
# Time-boxed differential fuzz smoke; blocks on crash/regression/divergence
SELF_HEALING_FUZZ=false
# SELF_HEALING_FUZZ_DURATION_MS=60000
# SELF_HEALING_FUZZ_TOOLS=cargo-fuzz
# SELF_HEALING_FUZZ_SUBMIT_ISSUES=false
# SLSA/cosign attestation for healing commit; blocks if verify fails
SELF_HEALING_ATTESTATION=false
# COSIGN_KEY_PATH=
# COSIGN_PASSWORD=
# ATTESTATION_STORE=local
# ATTESTATION_REGISTRY_URL=
# ATTESTATION_POLICY_PATH=

# --- Cost controls (fail-closed when exceeded) ---
SELF_HEALING_MAX_LOG_CHARS=100000
SELF_HEALING_MAX_CLAUDE_TOKENS=8192
SELF_HEALING_MAX_FAILED_JOB_LOGS=5
SELF_HEALING_MAX_COST_TOKENS_PER_RUN=50000
# Optional model overrides
# SELF_HEALING_CLAUDE_MODEL=claude-3-5-haiku-20241022
# Cheaper first-pass triage before full diagnose (set both to enable)
# SELF_HEALING_TRIAGE_FIRST=true
# SELF_HEALING_TRIAGE_MODEL=claude-3-5-haiku-20241022

# --- Ops / observability ---
LOG_LEVEL=info
METRICS_PORT=9090
# Metrics/alert routes require: Authorization: Bearer ${METRICS_AUTH_TOKEN}
METRICS_AUTH_TOKEN=change-me-metrics-token
# Default bind is 127.0.0.1; set true only when intentionally exposing metrics
METRICS_BIND_PUBLIC=false
METRICS_ENABLED=true
JAEGER_ENDPOINT=http://localhost:14268/api/traces
OPSGENIE_API_KEY=
# Optional: POST CloudEvents 1.0 JSON to this URL (Bearer token optional)
# When unset, events are logged-only (metric: self_healing_cloudevents_logged_only_total)
CLOUDEVENTS_INGEST_URL=
CLOUDEVENTS_INGEST_TOKEN=

# --- AWS (deduplication DynamoDB fallback) ---
# --- AWS (optional DynamoDB dedup fallback; set DYNAMODB_ENABLED=true to use) ---
AWS_REGION=us-east-1
DYNAMODB_ENABLED=false
DYNAMODB_TABLE=self-healing-ci-dedup

# --- App servers ---
# --- App servers (default loopback; set HOST=0.0.0.0 only behind a trusted reverse proxy) ---
PORT=3000
HOST=0.0.0.0
HOST=127.0.0.1
95 changes: 84 additions & 11 deletions .github/workflows/ci.yml
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,7 @@ on:
jobs:
validate:
runs-on: ubuntu-latest
timeout-minutes: 20
timeout-minutes: 45

steps:
- uses: actions/checkout@v4
Expand All @@ -25,29 +25,102 @@ jobs:
- name: Install dependencies
run: pnpm install --frozen-lockfile

- name: Build Claude service (types for worker)
run: pnpm --filter @self-healing-ci/claude run build

- name: Build Freestyle and Lean (worker optional backends)
- name: Build all workspace packages
run: |
pnpm --filter @self-healing-ci/exec-safe run build
pnpm --filter @self-healing-ci/claude run build
pnpm --filter @self-healing-ci/freestyle run build
pnpm --filter @self-healing-ci/lean run build
pnpm --filter @self-healing-ci/morph run build
pnpm --filter @self-healing-ci/static-analysis run build
pnpm --filter @self-healing-ci/fuzzing run build
pnpm --filter @self-healing-ci/attestation run build

- name: Typecheck
- name: Typecheck apps
run: |
pnpm --filter @self-healing-ci/temporal-worker exec tsc --noEmit
pnpm --filter @self-healing-ci/github-app exec tsc --noEmit

- name: Lint
- name: Lint apps
run: |
pnpm --filter @self-healing-ci/temporal-worker run lint
pnpm --filter @self-healing-ci/github-app run lint

- name: Test
- name: Install Temporal CLI (for WorkflowEnvironment tests)
run: |
# Install from GitHub Releases (avoids temporal.download MITM / TLS flakes).
# Never set NODE_TLS_REJECT_UNAUTHORIZED=0 here.
node scripts/install-temporal-cli.js --version=1.7.2 --dir="$HOME/.temporalio/bin"
echo "$HOME/.temporalio/bin" >> "$GITHUB_PATH"
temporal --version

- name: Test all workspace packages
env:
# Fail closed: real @temporalio/testing WorkflowEnvironment assertions.
SELF_HEALING_RUN_TEMPORAL_TESTS: 'true'
SELF_HEALING_REQUIRE_TEMPORAL_TESTS: 'true'
TEMPORAL_CLI_PATH: /home/runner/.temporalio/bin/temporal
run: |
pnpm --filter @self-healing-ci/exec-safe test
pnpm --filter @self-healing-ci/claude test
pnpm --filter @self-healing-ci/freestyle test
pnpm --filter @self-healing-ci/lean test
pnpm --filter @self-healing-ci/morph test
pnpm --filter @self-healing-ci/static-analysis test
pnpm --filter @self-healing-ci/fuzzing test
pnpm --filter @self-healing-ci/attestation test
pnpm --filter @self-healing-ci/temporal-worker test
pnpm --filter @self-healing-ci/github-app test

- name: Audit (informational)
run: pnpm audit --audit-level=high
continue-on-error: true
- name: Soak and cost harnesses
run: |
node scripts/soak-harness.js
node scripts/soak-worker-restart.js
node scripts/cost-harness.js

- name: Audit (blocking high/critical)
run: node scripts/pnpm-audit-gate.js
# Uses npm bulk advisory API (pnpm 8's `pnpm audit` hits a retired 410 endpoint).
# Allowlist temporary exceptions in .pnpm-audit-ignore (reviewed, time-boxed).
# Do not use continue-on-error for dependency CVEs.
# Never set NODE_TLS_REJECT_UNAUTHORIZED=0 here — fix the trust store instead.

freestyle-docker:
runs-on: ubuntu-latest
timeout-minutes: 20
# GitHub-hosted runners always expose Docker; gate keeps the job honest if
# that ever changes (forks / self-hosted without a daemon).
if: ${{ vars.SELF_HEALING_SKIP_DOCKER != 'true' }}
steps:
- uses: actions/checkout@v4

- uses: pnpm/action-setup@v4
with:
version: 8

- uses: actions/setup-node@v4
with:
node-version: '20'
cache: 'pnpm'

- name: Verify Docker daemon
run: |
docker version
docker info
test -S /var/run/docker.sock

- name: Install dependencies
run: pnpm install --frozen-lockfile

- name: Build Freestyle
run: pnpm --filter @self-healing-ci/freestyle run build

- name: Pull alpine fixture image
run: docker pull alpine:3.19

- name: Freestyle Docker integration tests
run: pnpm --filter @self-healing-ci/freestyle test -- freestyle-client.docker.test.ts
env:
SELF_HEALING_REQUIRE_DOCKER: 'true'
SELF_HEALING_TEST_EXECUTION_MODE: docker
DOCKER_SOCKET: /var/run/docker.sock
44 changes: 44 additions & 0 deletions .pnpm-audit-ignore
Original file line number Diff line number Diff line change
@@ -0,0 +1,44 @@
# Documented dependency audit allowlist for `node scripts/pnpm-audit-gate.js`.
# Format: one advisory ID (numeric npm id or GHSA-...) per line, with justification.
#
# Residual risk (reviewed 2026-07-16):
# - Runtime heal path: Temporal SDK 1.20.3 (@grpc/grpc-js 1.14.4, protobufjs 7.6.5)
# and owned Fastify 5.10.0 — prior Temporal/Fastify high/critical GHSAs cleared.
# - Remaining allowlist is **dev/tooling only** (semantic-release / eslint / jest
# transitive). Not loaded by the Temporal worker heal path at runtime.
# Residual: compromised CI tooling.
#
# Cleared this pass (do not re-allow without new evidence):
# GHSA-5375-pq7m-f5r2, GHSA-99f4-grh7-6pcq (@grpc/grpc-js >=1.13.5 / 1.14.4)
# GHSA-66ff-xgx4-vchm (+ related protobufjs) — protobufjs >=7.5.6 / 7.6.5
# GHSA-jx2c-rxcm-jvmq — Fastify >=5.7.2 / 5.10.0
# GHSA-v39h-62p7-jpjc, GHSA-q3j6-qgpj-74h6 — fast-uri >=3.1.2 / 3.1.3
#
# review-by: 2026-08-15 owner: platform

# Dev/tooling transitive — not in runtime heal path
GHSA-25h7-pfq9-p65f # flatted; review-by: 2026-08-15
GHSA-rf6f-7fwh-wjgh # flatted; review-by: 2026-08-15
GHSA-hmw2-7cc7-3qxx # form-data; review-by: 2026-08-15
GHSA-3mfm-83xf-c92r # handlebars (dev); review-by: 2026-08-15
GHSA-2w6w-674q-4c4q # handlebars critical (dev); review-by: 2026-08-15
GHSA-xhpv-hc6g-r9c6 # handlebars; review-by: 2026-08-15
GHSA-9cx6-37pm-9jff # handlebars; review-by: 2026-08-15
GHSA-xjpj-3mr7-gcpf # handlebars; review-by: 2026-08-15
GHSA-r5fr-rjxr-66jc # lodash template; review-by: 2026-08-15
GHSA-j3q9-mxjg-w52f # path-to-regexp; review-by: 2026-08-15
GHSA-37ch-88jc-xwx2 # path-to-regexp; review-by: 2026-08-15

# Numeric npm advisory ids currently emitted by bulk API for the same issues
1114526
1115357
1120743
1115538
1115539
1115693
1115694
1117465
1115805
1115806
1115573
1115527
Loading
Loading