Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
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
2 changes: 1 addition & 1 deletion .github/CODEOWNERS
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,6 @@
# Security, licensing, and the public-mirror publish/scan tooling.
/docs/SECURITY.md @wshallwshall
/docs/Secure_Development_Standards.md @wshallwshall
/scripts/publish/ @wshallwshall
/scripts/security/ @wshallwshall
/CLA.md @wshallwshall
/LICENSE @wshallwshall
91 changes: 91 additions & 0 deletions .github/dependabot.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,91 @@
# Dependabot: surface vulnerable / outdated dependencies and CI actions as PRs (CI-1 / DEP-1).
# Until a committed lockfile lands, this is the primary signal for a known-CVE dependency.
version: 2
updates:
# Python deps via the native "uv" ecosystem (was "pip"). The uv ecosystem resolves against
# pyproject.toml + uv.lock and REGENERATES uv.lock IN its PRs (version updates GA 2025-03,
# security updates GA 2025-12) — the old "pip" ecosystem updated requirements/pyproject but NOT
# uv.lock. It still does not re-derive the EXPORTED locks (requirements.lock + docker/locks/* +
# constraints.lock), which the DEP-1 gate in security.yml byte-diffs;
# .github/workflows/dependabot-lock-resync.yml re-exports those on the Dependabot branch so the
# gate stays green.
- package-ecosystem: "uv"
directory: "/"
schedule:
interval: "weekly"
open-pull-requests-limit: 10
# Supply-chain cooldown: age a fresh release before opening a ROUTINE update PR, to dodge a
# package compromised shortly after publish. Security updates ignore cooldown (Dependabot
# behavior), so a real advisory fix still arrives immediately. Pairs with the auto-merge
# workflow: routine patches auto-merge AFTER aging; security patches auto-merge now.
cooldown:
# ~5-day aging window (DEPENDENCY-POSTURE-REVIEW.md) lengthens the malicious-fresh-publish
# dodge on the routine VERSION track; the SECURITY track still bypasses cooldown (Dependabot
# design), now backstopped by the published-GHSA gate in dependabot-auto-merge.yml (SEC-007 #2).
default-days: 5
semver-major-days: 7
groups:
# Version-update grouping (applies-to defaults to version-updates).
python-deps:
patterns: ["*"]
# Security-update grouping: collapse multiple vulnerable-dep fixes into ONE PR so a single
# review/merge clears them — faster remediation when several CVEs land together.
python-security:
applies-to: security-updates
patterns: ["*"]

- package-ecosystem: "github-actions"
directory: "/"
schedule:
interval: "weekly"
groups:
# One weekly PR for all action bumps instead of one PR per action (CI cost: every extra PR
# buys a full required-check pass + a push-to-main run when it auto-merges). Mirrors the
# python-deps/ide-deps grouping above. A patch batched with a major rides the same PR —
# latency-only, auto-merge still gates on the full CI pass. Security updates keep their own
# group so an advisory fix is never held behind a routine batch.
actions-deps:
patterns: ["*"]
actions-security:
applies-to: security-updates
patterns: ["*"]

# The VS Code extension's npm tree (build-time toolchain — the shipped artifact is the esbuild
# bundle). Surfaces a vulnerable/outdated npm dep as a PR, the same DEP-1 surveillance the uv
# ecosystem gets; the blocking npm-audit job in security.yml is the fail-closed companion.
- package-ecosystem: "npm"
directory: "/ide"
schedule:
interval: "weekly"
open-pull-requests-limit: 10
cooldown:
default-days: 3
semver-major-days: 7
# Three dev-deps are pinned to the extension's runtime contract and must NOT float to the newest
# published version (grouping them into a routine bump broke `ide build` in PR #649 and PR #877):
# * @types/node tracks the Node runtime inside VS Code's Electron, not the latest stubs. The
# 20 -> 26 jump made the Node globals/builtins unresolvable at typecheck (TS2591/TS2304). A
# major is a deliberate moduleResolution (node16/nodenext) migration on its own PR, not an
# auto-bump — so hold the 20.x line (patches still flow).
# * @types/vscode must never exceed the engines.vscode floor (^1.95). Typing against a newer
# API surface lets the extension compile against methods absent from the editors it claims to
# support, failing at runtime on an older VS Code rather than in CI. Raise engines.vscode and
# this pin together, as one deliberate change, when dropping support for older VS Code.
# * typescript majors are a compiler migration, not a routine bump. The 5 -> 6 jump broke the
# typecheck outright (TS2591/TS2304/TS2694 — @types/node globals and the NodeJS namespace no
# longer resolve under the new lib/moduleResolution defaults). #875 deliberately left it
# un-ignored so CI could adjudicate the major on its own PR; CI failed it (#877), so hold the
# 5.x line and adopt TS 6 as its own deliberate migration (tsconfig lib/module review).
ignore:
- dependency-name: "@types/node"
versions: [">=21.0.0"]
- dependency-name: "@types/vscode"
versions: [">=1.96.0"]
- dependency-name: "typescript"
versions: [">=6.0.0"]
groups:
ide-deps:
patterns: ["*"]
ide-security:
applies-to: security-updates
patterns: ["*"]
99 changes: 0 additions & 99 deletions .github/workflows/backlog-hygiene.yml

This file was deleted.

44 changes: 0 additions & 44 deletions .github/workflows/release-sync-check.yml

This file was deleted.

5 changes: 3 additions & 2 deletions .github/workflows/release.yml
Original file line number Diff line number Diff line change
Expand Up @@ -46,7 +46,8 @@ jobs:
# asked for.
#
# The guard is INVERTED (`!=` the public slug) on purpose — DO NOT "normalize" it to
# `== 'MEFORORG/MessageFoundry'`. scripts/publish/publish.ps1 rewrites the private slug to the
# `== 'MEFORORG/MessageFoundry'`. Historically scripts/publish/publish.ps1 (retired at the MEFORORG
# cutover) rewrote the private slug to the
# public one across *.yml when it materializes the mirror, so an `==` private-slug test is rewritten
# into `== 'MEFORORG/MessageFoundry'` and becomes TRUE exactly where it must be false. This form names
# only the public slug, so the rewrite cannot touch it: true on the source repo, false on the mirror.
Expand Down Expand Up @@ -75,7 +76,7 @@ jobs:
# badges — but anonymous fetches of a private repo's Actions badge SVG 404 (broken badge
# images), and every body link 404s on click. The public mirror (MEFORORG/MessageFoundry)
# runs the same workflows and is anonymously reachable, so rewrite the slug here (the same
# rewrite scripts/publish/publish.ps1 applies to the mirror) before `python -m build` embeds
# rewrite the retired scripts/publish/publish.ps1 used to apply) before `python -m build` embeds
# the README. Ephemeral runner edit only — never committed.
sed -i 's#MEFORORG/MessageFoundry#MEFORORG/MessageFoundry#g' README.md
if grep -q 'MEFORORG/MessageFoundry' README.md; then
Expand Down
55 changes: 42 additions & 13 deletions .github/workflows/security.yml
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,11 @@ name: Security
# against an unchanged main. On-demand full scans: workflow_dispatch.
on:
pull_request:
# Post-merge re-scan (main only). A fork PR is scanned STRUCTURAL-ONLY by design -- the secret is
# unavailable to it -- so without this arm no fully-loaded scan ever sees fork-contributed content.
# Scoped to main so branch pushes do not double-run alongside their own PR.
push:
branches: [main]
schedule:
# Daily, so a CVE freshly disclosed against an UNCHANGED pinned dep (no push/PR to trigger a scan)
# is caught within ~24h instead of up to 7 days — a weekly cadence can't meet a 72h-class remediation
Expand Down Expand Up @@ -344,24 +349,48 @@ jobs:
forbidden-content:
name: forbidden-content (customer/PHI leak guard)
runs-on: ubuntu-latest
# BLOCKING: scan the PUBLISHABLE subset — every tracked file MINUS scripts/publish/publish-denylist.txt
# (the same deny-list publish.ps1 removes) — for customer/PHI-adjacent strings: partner/site names, the
# real production estate, routable host IPs. These are not *secrets* (gitleaks won't flag them) but must
# never reach the open-source mirror. This is the same scanner publish.ps1 runs as its fail-closed gate,
# brought forward to PR/push-to-main time so a token can't ride a squash-merge onto main and sit there
# until the next publish catches it. See docs/security/PUBLISHING.md.
# BLOCKING (required context): scan the WHOLE tracked tree for customer/PHI-adjacent strings --
# partner/site names, the real production estate, routable host IPs, internal worktree slugs and
# absolute home paths. These are not *secrets* (gitleaks will not flag them) but must never land on
# this public repo. The committed scanner ships only STRUCTURAL detectors plus a synthetic
# .example; the real token list is NEVER committed -- it arrives from the MEFOR_FORBIDDEN_TOKENS
# secret here, and locally from a git-ignored scripts/security/scan-tokens.local.txt.
steps:
- uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1
- uses: actions/setup-python@5fda3b95a4ea91299a34e894583c3862153e4b97 # v7.0.0
with:
python-version: "3.14"
- name: Scan the publishable subset for forbidden content
# Stdlib-only scanner (no install). Exits non-zero — failing the build — on any hit. The scanner
# lives under the deny-listed scripts/publish/, so it is ABSENT on the OSS mirror — there's
# nothing to gate there (everything is already the published subset); skip rather than error.
- name: Scan the tracked tree for forbidden content
# Stdlib-only scanner (no install).
#
# MEFOR_MIN_DETECTORS is what makes "fail-closed" mean anything. Requiring a token SOURCE only
# proves one arrived: a partially-mangled secret still loaded as few as 1 of 21 detectors and
# passed with a green tick, and losing just the final line silently disabled every site-code
# detector. The floor is PER-SECTION, not a bare total -- a total is a SUM, so growth in a cheap
# section masks collapse in an expensive one (names 7->1 alongside estate 13->19 still totals 21).
# It is a FLOOR: adding tokens needs no CI change, losing them fails the build.
#
# zizmor: the secret is never interpolated into this run: body. It arrives as the step-level env
# var (an opaque value, not an Actions-expression sink).
env:
MEFOR_FORBIDDEN_TOKENS: ${{ secrets.MEFOR_FORBIDDEN_TOKENS }}
# Whether this run CANNOT legitimately see the secret. Empty on pushes and same-repo PRs.
IS_FORK_PR: ${{ github.event.pull_request.head.repo.fork }}
run: |
if [ -f scripts/publish/scan_forbidden.py ]; then
python scripts/publish/scan_forbidden.py --published
if [ -n "$MEFOR_FORBIDDEN_TOKENS" ]; then
# Deliberately NOT written to scripts/security/scan-tokens.local.txt: the scanner resolves
# the env var first, so that file would never be read -- it would only drop the full real
# token list into the job workspace for every later step to see.
export MEFOR_REQUIRE_TOKENS=1
export MEFOR_MIN_DETECTORS=names=7,estate=13,site_prefixes=1
echo "token list loaded from the MEFOR_FORBIDDEN_TOKENS secret (fail-closed, per-section floor)."
elif [ "$IS_FORK_PR" = "true" ]; then
echo "fork PR -- the secret is unavailable BY DESIGN; structural-only scan."
else
echo "scripts/publish/scan_forbidden.py absent (OSS mirror) — skipping the publish leak-gate."
# Degrading here would be SILENT and PERMANENT: a renamed, deleted, environment-scoped or
# rotated secret takes the fork branch forever, scanning nothing while the REQUIRED context
# stays green. Absent-on-a-non-fork is a broken gate, not a mode.
echo "::error::MEFOR_FORBIDDEN_TOKENS is absent on a non-fork run. The customer-leak gate cannot run. Check the repository secret (repo-scoped, not environment-scoped)." >&2
exit 2
fi
python scripts/security/scan_forbidden.py --path .
Loading
Loading