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
34 changes: 29 additions & 5 deletions .github/workflows/l1-traceability.yml
Original file line number Diff line number Diff line change
Expand Up @@ -30,11 +30,16 @@ jobs:
- name: Check for Closes #N in commits
if: env.IS_BOT != 'true'
id: check-commits
env:
EVENT_NAME: ${{ github.event_name }}
BASE_REF: ${{ github.base_ref }}
PR_HEAD_SHA: ${{ github.event.pull_request.head.sha }}
PR_HEAD_REF: ${{ github.event.pull_request.head.ref }}
run: |
set -e

# Get the base branch (master or main)
BASE_BRANCH="${{ github.event_name == 'pull_request' && github.base_ref || 'master' }}"
BASE_BRANCH="${BASE_REF:-master}"
if ! git rev-parse --verify origin/main >/dev/null 2>&1; then
BASE_BRANCH="master"
fi
Expand Down Expand Up @@ -97,16 +102,35 @@ jobs:
- name: Check L2 GENERATION (gen/ edits forbidden)
if: env.IS_BOT != 'true'
id: check-generation
env:
EVENT_NAME: ${{ github.event_name }}
BASE_REF: ${{ github.base_ref }}
PR_HEAD_SHA: ${{ github.event.pull_request.head.sha }}
PR_HEAD_REF: ${{ github.event.pull_request.head.ref }}
run: |
set -e

# Get the base branch
BASE_BRANCH="${{ github.event_name == 'pull_request' && github.base_ref || 'master' }}"
BASE_BRANCH="${BASE_REF:-master}"

# For PR, use the PR head SHA directly instead of HEAD (which is a merge commit)
if [ "${{ github.event_name }}" = "pull_request" ]; then
HEAD_SHA="${{ github.event.pull_request.head.sha }}"
git fetch origin "${{ github.event.pull_request.head.ref }}:$HEAD_SHA" 2>/dev/null || true
if [ "${EVENT_NAME:-}" = "pull_request" ]; then
HEAD_SHA="${PR_HEAD_SHA:-}"
case "$HEAD_SHA" in
*[!0-9a-f]*|"") echo "unexpected head sha; skipping fetch"; HEAD_SHA="HEAD" ;;
*)
# PR_HEAD_REF is the fork's branch name and is chosen by whoever
# opened the PR. git permits $ ( ) ` ; | & and quotes in a
# refname, so it is validated before use, and rejected if it
# could be read as an option rather than a ref.
if git check-ref-format --branch "${PR_HEAD_REF:-}" >/dev/null 2>&1 \
&& case "${PR_HEAD_REF:-}" in -*) false ;; *) true ;; esac; then
git fetch origin "${PR_HEAD_REF}:${HEAD_SHA}" 2>/dev/null || true
else
echo "refusing to fetch a ref name that does not validate: skipping"
fi
;;
esac
else
HEAD_SHA="HEAD"
fi
Expand Down
134 changes: 74 additions & 60 deletions .github/workflows/notebook-sync.yml
Original file line number Diff line number Diff line change
Expand Up @@ -40,76 +40,81 @@ jobs:
extract-issue:
if: github.event_name != 'workflow_dispatch'
runs-on: ubuntu-latest
# This job reads untrusted event data. It gets read-only scope so that a
# future mistake here cannot write to the repository.
permissions:
contents: read
outputs:
issue_number: ${{ steps.issue.outputs.number }}
issue_title: ${{ steps.issue.outputs.title }}
event_type: ${{ steps.event_type.outputs.type }}
# issue_title was published here and consumed by nobody. It is not
# reinstated in a sanitised form: untrusted text that no step reads is
# best not carried at all.
steps:
- name: Checkout repo
uses: actions/checkout@v6

- name: Extract issue info
id: issue
# Every value arrives through env:, which the runner sets, so no
# character in it can be parsed as shell. Nothing from the event is
# interpolated into the script text.
env:
EVENT_NAME: ${{ github.event_name }}
ISSUE_NUMBER: ${{ github.event.issue.number }}
PR_NUMBER: ${{ github.event.pull_request.number }}
REF_NAME: ${{ github.ref_name }}
run: |
ISSUE_NUM=""
ISSUE_TITLE=""
set -euo pipefail

case "${{ github.event_name }}" in
issues)
ISSUE_NUM="${{ github.event.issue.number }}"
ISSUE_TITLE="${{ github.event.issue.title }}"
echo "type=issue" >> $GITHUB_OUTPUT
;;
issue_comment)
ISSUE_NUM="${{ github.event.issue.number }}"
ISSUE_TITLE="${{ github.event.issue.title }}"
echo "type=comment" >> $GITHUB_OUTPUT
;;
pull_request)
ISSUE_NUM="${{ github.event.pull_request.number }}"
ISSUE_TITLE="${{ github.event.pull_request.title }}"
echo "type=pr" >> $GITHUB_OUTPUT
;;
pull_request_review)
ISSUE_NUM="${{ github.event.pull_request.number }}"
ISSUE_TITLE="${{ github.event.pull_request.title }}"
echo "type=pr" >> $GITHUB_OUTPUT
;;
ISSUE_NUM=""
case "$EVENT_NAME" in
issues|issue_comment) ISSUE_NUM="${ISSUE_NUMBER:-}" ;;
pull_request|pull_request_review) ISSUE_NUM="${PR_NUMBER:-}" ;;
push)
# Extract from branch name: feature/issue-357 -> 357
BRANCH="${{ github.ref_name }}"
ISSUE_NUM=$(echo "$BRANCH" | grep -oE '(issue-|#)?[0-9]+' | head -1 | tr -d 'issue-#' || echo "")
echo "type=push" >> $GITHUB_OUTPUT
# feature/issue-357 -> 357
ISSUE_NUM="$(printf '%s' "${REF_NAME:-}" \
| grep -oE '[0-9]+' | head -1 || true)"
;;
esac

echo "number=$ISSUE_NUM" >> $GITHUB_OUTPUT
echo "title=$ISSUE_TITLE" >> $GITHUB_OUTPUT
# Accept only decimal digits. Downstream this number is spliced into a
# command line, and GITHUB_OUTPUT is a newline-delimited file: a value
# containing a newline defines additional outputs of its own choosing.
case "$ISSUE_NUM" in
''|*[!0-9]*) ISSUE_NUM="" ;;
esac

printf 'number=%s\n' "$ISSUE_NUM" >> "$GITHUB_OUTPUT"

- name: Determine event type
id: event_type
env:
EVENT_NAME: ${{ github.event_name }}
EVENT_ACTION: ${{ github.event.action }}
run: |
EVENT_TYPE=""
set -euo pipefail

# The action name is a closed set, so it is checked against that set
# rather than trusted to be one of them. The check costs one case
# statement and removes this value from the argument that has to be
# made about the whole file.
ACTION="unknown"
case "${EVENT_ACTION:-}" in
opened|edited|labeled|closed|created|synchronize|submitted)
ACTION="$EVENT_ACTION" ;;
esac

case "${{ github.event_name }}" in
issues)
EVENT_TYPE="issue_${{ github.event.action }}"
;;
issue_comment)
EVENT_TYPE="comment"
;;
pull_request)
EVENT_TYPE="pr_${{ github.event.action }}"
;;
pull_request_review)
EVENT_TYPE="review"
;;
push)
EVENT_TYPE="push"
;;
EVENT_TYPE=""
case "$EVENT_NAME" in
issues) EVENT_TYPE="issue_${ACTION}" ;;
issue_comment) EVENT_TYPE="comment" ;;
pull_request) EVENT_TYPE="pr_${ACTION}" ;;
pull_request_review) EVENT_TYPE="review" ;;
push) EVENT_TYPE="push" ;;
esac

echo "type=$EVENT_TYPE" >> $GITHUB_OUTPUT
printf 'type=%s\n' "$EVENT_TYPE" >> "$GITHUB_OUTPUT"

# Sync to NotebookLM
sync-notebook:
Expand Down Expand Up @@ -140,34 +145,37 @@ jobs:
run: |
cd contrib/backend/notebooklm

SYNC_ARGS="--issue $ISSUE_NUM"
# An array, not a string: a string is re-split by the shell on
# whatever IFS happens to be, which is a second way for a value to
# become more than one argument.
SYNC_ARGS=(--issue "$ISSUE_NUM")

# Determine sync type
case "$EVENT_TYPE" in
issue_opened|issue_edited|pr_opened|pr_synchronize|push)
SYNC_ARGS="$SYNC_ARGS --event push"
SYNC_ARGS+=(--event push)
echo "🔄 Syncing push/PR event for issue #$ISSUE_NUM"
;;
issue_closed)
SYNC_ARGS="$SYNC_ARGS --event merge"
SYNC_ARGS+=(--event merge)
echo "✨ Syncing merge (issue closed) for issue #$ISSUE_NUM"
;;
comment)
SYNC_ARGS="$SYNC_ARGS --event push --trigger comment"
SYNC_ARGS+=(--event push --trigger comment)
echo "💬 Syncing new comment for issue #$ISSUE_NUM"
;;
pr_closed)
SYNC_ARGS="$SYNC_ARGS --event merge --trigger merged"
SYNC_ARGS+=(--event merge --trigger merged)
echo "🔀 Syncing PR merge for #$ISSUE_NUM"
;;
*)
echo "ℹ️ Event type $EVENT_TYPE - using default sync"
SYNC_ARGS="$SYNC_ARGS --event push"
SYNC_ARGS+=(--event push)
;;
esac

# Run sync
python3.10 sync.py $SYNC_ARGS || {
python3.10 sync.py "${SYNC_ARGS[@]}" || {
echo "⚠️ Sync completed with warnings"
exit 0
}
Expand Down Expand Up @@ -287,21 +295,27 @@ jobs:
run: |
cd contrib/backend/notebooklm

SYNC_ARGS="--issue $ISSUE_NUM"
# An array, not a string: a string is re-split by the shell on
# whatever IFS happens to be, which is a second way for a value to
# become more than one argument.
SYNC_ARGS=(--issue "$ISSUE_NUM")

case "$SYNC_TYPE" in
comment)
SYNC_ARGS="$SYNC_ARGS --event push --trigger comment"
SYNC_ARGS+=(--event push --trigger comment)
;;
activity)
SYNC_ARGS="--activity"
# Replaces the array rather than appending to it: --activity is
# documented as standalone, and the original assignment here
# discarded --issue too.
SYNC_ARGS=(--activity)
;;
*)
SYNC_ARGS="$SYNC_ARGS --event push"
SYNC_ARGS+=(--event push)
;;
esac

python3.10 sync.py $SYNC_ARGS
python3.10 sync.py "${SYNC_ARGS[@]}"

- name: Comment on issue (optional)
if: inputs.sync_type == 'activity'
Expand Down
45 changes: 45 additions & 0 deletions .github/workflows/untrusted-input-gate.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,45 @@
name: Untrusted Input Gate

# Two checks, deliberately separate:
#
# check_untrusted_shell_interp.py -- no workflow interpolates untrusted event
# data into a run: block
# test_untrusted_payloads.py -- the two shell forms behave as claimed, on
# payloads whose effect is declared up front
# check_untrusted_javascript_interp.py -- no github.* expression enters a
# github-script source block
# test_untrusted_javascript_payloads.py -- JavaScript source injection fails
# while process.env preserves data
#
# The first is a property of this repository and can be fixed. The second is a
# property of bash and cannot; it exists so that the first check's reason is
# reproducible rather than asserted.
#
# No `branches:` filter: a gate that filters pull_request by branch does not run
# on a stacked PR and reads as green (see #2167).

on:
pull_request:
push:
branches: [master]

permissions:
contents: read

jobs:
untrusted-input:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v6

- name: No untrusted event data interpolated into shell
run: python3 scripts/ci/check_untrusted_shell_interp.py

- name: Payload behaviour matches what is claimed about it
run: python3 scripts/ci/test_untrusted_payloads.py

- name: No event data interpolated into github-script source
run: python3 scripts/ci/check_untrusted_javascript_interp.py

- name: JavaScript payload behaviour matches what is claimed about it
run: python3 scripts/ci/test_untrusted_javascript_payloads.py
3 changes: 3 additions & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -91,3 +91,6 @@ docker/Xilinx_Unified_*.bin
# the specification it claims to be about. Do not commit.
fpga/formal/mvp_classifier_dut.v
lean4_bridge/.lake/

# Evidential binaries: sealed by docs/evidence/seal_*.json, not stored in git.
docs/evidence/bin/
16 changes: 16 additions & 0 deletions docs/NOW.md
Original file line number Diff line number Diff line change
Expand Up @@ -470,6 +470,22 @@ Last updated: 2026-08-18
- **It earned its keep immediately**: it caught two places in the `.tex` that the first pass of this very change had missed
- Unrelated and worth stating: `docs/SILICON_TRAINING_METHODOLOGY.md` was audited for the same defect class and is **clean**. It distinguishes a loose from a tight constraint, uses `create_clock -period 50`, attributes the 21 -> 29 MHz change to a specific design edit, and keeps twelve ruled-out hypotheses. The papers were the problem; the engineering notes were not

# NOW -- a pull request title is untrusted input, and it was being run (2026-08-14)

Last updated: 2026-08-14

## ci: stop executing event data, and seal the binaries that carry evidence (Closes #2171)

- **A pull request title was interpolated into a `run:` block, so whoever wrote the title chose what the runner executed.** `notebook-sync.yml` pasted `github.event.pull_request.title` and `github.event.issue.title` straight into shell. Measured on ten payloads: five of them execute a command under that form -- `$(...)`, backticks, `;`, `${IFS}` in place of a space, and one that writes `$GITHUB_TOKEN` to a file. The same ten pass through an `env:` variable byte for byte with no side effect
- **The title that exposed this was broken by backticks, not by the parenthesis, and the earlier attribution in #2171 is corrected here.** The title of #2168 quotes code in backticks; bash opened a command substitution on them and `(` was a syntax error inside it, reproduced byte for byte against the CI log. The parenthesis is what stopped the execution rather than what caused the failure -- had the quoted text been a valid command it would have run. Titles in this repository quote code as a matter of style, so the dangerous construct is the ordinary one
- **The unsafe value was serving no purpose.** The `issue_title` output was published and read by nobody, so it is deleted rather than sanitised. Everything still needed moves to `env:`, the issue number is checked against `^[0-9]+$` before it is written, and `SYNC_ARGS` becomes an array instead of a string the shell re-splits
- **A newline needs no shell at all:** written into `GITHUB_OUTPUT`, which is a newline-delimited file, a value containing one defines further outputs of its own choosing. Demonstrated. Quoting does not help, because the value is already data and the file format is what is abused
- **Second vector, same class:** `l1-traceability.yml` fetched `github.event.pull_request.head.ref`. `git check-ref-format` accepts `$( )`, backticks, `;`, `|`, `&` and quotes in a branch name, so the ref is now validated and refused if it could read as an option
- `scripts/ci/check_untrusted_shell_interp.py` is the standing check: 5 untrusted interpolations before this change, 0 after, over 35 workflows. It runs with no `branches:` filter, since a gate that filters by branch reads as green on a stacked PR (#2167)
- **Evidential binaries no longer live only in `/tmp`.** `scripts/ci/artifact_seal.py` records commit, build commands, toolchain, profile, digests, declared inputs and test results; `verify --rebuild` rebuilds from the named commit and compares. Reproduced bit-exactly at `836e8bc4...` from `b928725`
- **That only worked once the build path was made a constant.** A debug build embeds its source path, so two builds of the same commit from differently named temporary worktrees differed in 39,830,933 bytes. Digest comparison is a usable check only from a fixed path
- **The pair behind the tick D differential is sealed with its commit field empty.** Provenance not captured at build time cannot be recovered afterwards, and writing today's `HEAD` there would manufacture it

# NOW -- BNF: the control that measures what ternary is worth (2026-08-09)

Last updated: 2026-08-09
Expand Down
52 changes: 52 additions & 0 deletions docs/evidence/seal_m2162_pair.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,52 @@
{
"schema": "trinity.artifact-seal/1",
"sealed_utc": "2026-08-14T22:03:48.093958+00:00",
"obtained_utc": "2026-08-14T20:07:00Z",
"label": "m2162-differential-pair",
"purpose": "the base/candidate pair behind the tick D full-corpus differential (PR #2151, #2162)",
"source": {
"repo": "/home/user/workspace/t27",
"remote": "https://github.com/gHashTag/t27.git",
"commit": null,
"commit_subject": "",
"branch": "",
"provenance": "unrecorded-at-build-time",
"claimed_commit_unverified": "base: origin/master b928725; candidate: w699-generic-const-decl (PR #2168). Neither was recorded at build time.",
"tree_dirty_at_seal_time": true,
"tree_dirty_note": "the working tree had uncommitted changes, so the commit above does not fully describe these artifacts"
},
"build": {
"profile": "dev",
"commands": [
"cd bootstrap && cargo build"
],
"toolchain": {
"rustc": "rustc 1.97.1 (8bab26f4f 2026-07-14)",
"cargo": "cargo 1.97.1 (c980f4866 2026-06-30)",
"python3": "Python 3.14.3",
"uname": "Linux 6.1.155+ x86_64"
}
},
"artifacts": [
{
"path": "/tmp/t27c.m2162base",
"present": true,
"sha256": "760373266c87bdd8c69e22bbe5a5a06bd62bcd6150d621d472caf21e2b327bcb",
"size_bytes": 14144800,
"mtime_utc": "2026-08-14T20:07:49.792377+00:00"
},
{
"path": "/tmp/t27c.m2162fix",
"present": true,
"sha256": "17895fa2d5a0659485474a34a93b1262d7d85edf982eaf7ba02ca9b3ea346eb5",
"size_bytes": 14150448,
"mtime_utc": "2026-08-14T20:10:40.320377+00:00"
}
],
"inputs": [],
"tests": [],
"limits": [
"verify recomputes digests; it does not prove the artifact matches the commit. Use --rebuild for that, and read its caveat.",
"a rebuild mismatch is reported as unreproduced, not as tampering: cargo release builds are not bit-reproducible by default."
]
}
Loading
Loading