Skip to content
Closed
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
150 changes: 150 additions & 0 deletions .github/workflows/downstream-semgrep.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,150 @@
name: Downstream Semgrep candidate

on:
pull_request:
paths:
- 'scripts/downstream_semgrep.py'
- 'scripts/verify_downstream_semgrep.py'
- 'ci/downstream*'
- 'tools/downstream_semgrep/**'
- '.github/workflows/downstream-semgrep.yml'
- 'tests/unit/scripts/test_downstream_semgrep*.py'
- 'uv.lock'
- 'tools/semgrep/sast.yml'
- 'tools/semgrep/sast-baseline.json'
- 'scripts/semgrep_sast_gate.py'
- 'tests/unit/scripts/test_native_report_schema.py'
workflow_dispatch:

permissions:
contents: read

jobs:
matrix:
runs-on: ubuntu-24.04
outputs:
cells: ${{ steps.matrix.outputs.cells }}
steps:
- uses: actions/checkout@df4cb1c069e1874edd31b4311f1884172cec0e10
with:
persist-credentials: false
- uses: actions/setup-python@a309ff8b426b58ec0e2a45f0f869d46889d02405
with:
python-version: '3.13'
- id: matrix
shell: bash
run: |
cells="$(python scripts/verify_downstream_semgrep.py matrix)"
echo "cells=$cells" >> "$GITHUB_OUTPUT"

candidate:
needs: matrix
strategy:
fail-fast: false
matrix: ${{ fromJSON(needs.matrix.outputs.cells) }}
runs-on: ${{ matrix.runner }}
timeout-minutes: 60
steps:
- name: Preserve committed input bytes on Windows
if: runner.os == 'Windows'
shell: bash
run: git config --global core.autocrlf false
- uses: actions/checkout@df4cb1c069e1874edd31b4311f1884172cec0e10
with:
persist-credentials: false
- uses: actions/setup-python@a309ff8b426b58ec0e2a45f0f869d46889d02405
with:
python-version: ${{ matrix.python_version }}
- uses: astral-sh/setup-uv@d0cc045d04ccac9d8b7881df0226f9e82c39688e
with:
enable-cache: false
version: '0.12.19'
- name: Execute native candidate cell
if: matrix.container_image == ''
shell: bash
env:
CELL_PLATFORM: ${{ matrix.platform }}
CELL_PYTHON: ${{ matrix.python_version }}
run: |
python scripts/verify_downstream_semgrep.py cell --platform "$CELL_PLATFORM" --python-version "$CELL_PYTHON" --output "$RUNNER_TEMP/downstream-cell"
- name: Execute native musl candidate cell
if: matrix.container_image != ''
shell: bash
env:
CELL_PLATFORM: ${{ matrix.platform }}
CELL_PYTHON: ${{ matrix.python_version }}
CELL_IMAGE: ${{ matrix.container_image }}
run: |
case "$CELL_IMAGE" in python:*@sha256:*) ;; *) echo 'Unpinned musl image' >&2; exit 1 ;; esac
mkdir -p "$RUNNER_TEMP/downstream-musl"
status=0
docker run --rm \
--mount "type=bind,src=$GITHUB_WORKSPACE,dst=/workspace,readonly" \
--mount "type=bind,src=$RUNNER_TEMP/downstream-musl,dst=/evidence" \
-e CELL_PLATFORM -e CELL_PYTHON \
-w /workspace "$CELL_IMAGE" sh -euc '
apk add --no-cache git build-base libffi-dev openssl-dev cargo
git config --global --add safe.directory /workspace
python scripts/verify_downstream_semgrep.py uv-requirements > /evidence/uv-bootstrap.txt
python -m pip install --require-hashes --only-binary=:all: --index-url https://pypi.org/simple -r /evidence/uv-bootstrap.txt
python scripts/verify_downstream_semgrep.py cell --platform "$CELL_PLATFORM" --python-version "$CELL_PYTHON" --output /evidence/downstream-cell
' || status=$?
exit "$status"
- name: Retain candidate evidence on success or failure
if: always()
uses: actions/upload-artifact@043fb46d1a93c77aae656e7c1c64a875d1fc6a0a
with:
name: downstream-${{ matrix.platform }}-${{ matrix.python_version }}
path: |
${{ runner.temp }}/downstream-cell
${{ runner.temp }}/downstream-musl/downstream-cell
!${{ runner.temp }}/**/*-env/**
!${{ runner.temp }}/**/build-two/**
!${{ runner.temp }}/**/semgrep-settings.yml
if-no-files-found: error
retention-days: 14

aggregate:
permissions:
contents: read
actions: read
if: always()
needs: [matrix, candidate]
runs-on: ubuntu-24.04
steps:
- uses: actions/checkout@df4cb1c069e1874edd31b4311f1884172cec0e10
with:
persist-credentials: false
- uses: actions/setup-python@a309ff8b426b58ec0e2a45f0f869d46889d02405
with:
python-version: '3.13'
- name: Download immutable job artifacts
env:
GH_TOKEN: ${{ github.token }}
GH_REPO: ${{ github.repository }}
RUN_ID: ${{ github.run_id }}
shell: bash
run: |
gh run download "$RUN_ID" --pattern 'downstream-*' --dir "$RUNNER_TEMP/downstream-reports"
- name: Require complete successful candidate matrix
env:
MATRIX_RESULT: ${{ needs.matrix.result }}
CANDIDATE_RESULT: ${{ needs.candidate.result }}
shell: bash
run: |
test "$MATRIX_RESULT" = success
test "$CANDIDATE_RESULT" = success
python scripts/verify_downstream_semgrep.py aggregate --reports "$RUNNER_TEMP/downstream-reports"

protected-producer:
if: github.event_name == 'workflow_dispatch' && github.ref == 'refs/heads/dev'
needs: aggregate
runs-on: ubuntu-24.04
permissions:
contents: read
steps:
- name: Refuse production bootstrap until separately approved roots exist
shell: bash
run: |
echo 'Candidate evidence only. Production producer bootstrap is unconfigured; no signing, attestation or publication is permitted.' >&2
exit 1
4 changes: 4 additions & 0 deletions ci/downstream-semgrep-admissions.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@
{
"schema_version": 1,
"admissions": []
}
105 changes: 105 additions & 0 deletions ci/downstream-semgrep-recipe.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,105 @@
{
"schema_version": 1,
"package": "semgrep",
"upstream_version": "1.179.0",
"downstream_version": "1.179.0+specfact.1",
"mcp_from": "1.29.0",
"mcp_to": "1.30.0",
"python_versions": [
"3.11",
"3.12",
"3.13"
],
"build_runtime_policy": {
"implementation": "CPython",
"python_versions": [
"3.11",
"3.12",
"3.13"
],
"wheel_compression": "ZIP_STORED",
"description": "Runtime-policy digest; actual executable and environment identities are bound separately by each cell report."
},
"build_runtime_digest": "31432b8918f5a5aef72fbdb8f5aefe74090f5002917c33e7f4f78478998c0f51",
"wheels": [
{
"filename": "semgrep-1.179.0-cp310.cp311.cp312.cp313.cp314.py310.py311.py312.py313.py314-none-macosx_10_14_x86_64.whl",
"platform_tag": "macosx_10_14_x86_64",
"upstream_sha256": "34b5e801e3f4325c93014124ccfab6da96857f74aae68458d2a07defd4727b0f",
"upstream_url": "https://files.pythonhosted.org/packages/85/4b/4cbaa0c3c124ae7d522e688c01f06013038ff59cc818c21dfa44c96380b3/semgrep-1.179.0-cp310.cp311.cp312.cp313.cp314.py310.py311.py312.py313.py314-none-macosx_10_14_x86_64.whl",
"runner": "macos-15-intel",
"containers": {}
},
{
"filename": "semgrep-1.179.0-cp310.cp311.cp312.cp313.cp314.py310.py311.py312.py313.py314-none-macosx_11_0_arm64.whl",
"platform_tag": "macosx_11_0_arm64",
"upstream_sha256": "9b57026f587a4bc7833bca2b6e4d6778072a79dcf0b9f2e5e01d3d5df39c7759",
"upstream_url": "https://files.pythonhosted.org/packages/bd/77/b573a0faa5a5b0848dc884a0f8595fcb5c550f55fa5f455a233b29a52c0c/semgrep-1.179.0-cp310.cp311.cp312.cp313.cp314.py310.py311.py312.py313.py314-none-macosx_11_0_arm64.whl",
"runner": "macos-15",
"containers": {}
},
{
"filename": "semgrep-1.179.0-cp310.cp311.cp312.cp313.cp314.py310.py311.py312.py313.py314-none-manylinux_2_34_x86_64.whl",
"platform_tag": "manylinux_2_34_x86_64",
"upstream_sha256": "b529c4db63f293918989b84ce4640f12f1b1e4680d9e7fcab5f3554f9113dfc5",
"upstream_url": "https://files.pythonhosted.org/packages/cc/6c/621944b4843c7484aeaefd5b232d93780bdf95259567c10a9a8129f08dc6/semgrep-1.179.0-cp310.cp311.cp312.cp313.cp314.py310.py311.py312.py313.py314-none-manylinux_2_34_x86_64.whl",
"runner": "ubuntu-24.04",
"containers": {}
},
{
"filename": "semgrep-1.179.0-cp310.cp311.cp312.cp313.cp314.py310.py311.py312.py313.py314-none-manylinux_2_34_aarch64.whl",
"platform_tag": "manylinux_2_34_aarch64",
"upstream_sha256": "c23d79c4839540372acc39b59aad167c1c7f3dc21bacacb27f19aa2edd79d271",
"upstream_url": "https://files.pythonhosted.org/packages/e3/ba/b9f1afbed852a21b6f7e72c064343161c5ab249954c46db1796b59314290/semgrep-1.179.0-cp310.cp311.cp312.cp313.cp314.py310.py311.py312.py313.py314-none-manylinux_2_34_aarch64.whl",
"runner": "ubuntu-24.04-arm",
"containers": {}
},
{
"filename": "semgrep-1.179.0-cp310.cp311.cp312.cp313.cp314.py310.py311.py312.py313.py314-none-musllinux_1_2_x86_64.whl",
"platform_tag": "musllinux_1_2_x86_64",
"upstream_sha256": "69bce348296374df6c51e72758801ce06dd18d6dfb307bc73d24b9de53bbe5f1",
"upstream_url": "https://files.pythonhosted.org/packages/ee/c1/59096c02408630b79be19c5b97d145786141e254782ab0e62939c1eea52d/semgrep-1.179.0-cp310.cp311.cp312.cp313.cp314.py310.py311.py312.py313.py314-none-musllinux_1_2_x86_64.whl",
"runner": "ubuntu-24.04",
"containers": {
"3.11": "python:3.11-alpine3.22@sha256:a4fc589b32e824f3f02ed9d7e7be19518aa47e105b80416336af9f202275a489",
"3.12": "python:3.12-alpine3.22@sha256:a190708a2dec1bd18b1decb539f8e8f5407abaa9bf39cacda583f7f8c11db322",
"3.13": "python:3.13-alpine3.22@sha256:e81548ac35b07a3bd4805f275107592ef458b1e893c0e04d45aedaa19416cca5"
}
},
{
"filename": "semgrep-1.179.0-cp310.cp311.cp312.cp313.cp314.py310.py311.py312.py313.py314-none-musllinux_1_2_aarch64.whl",
"platform_tag": "musllinux_1_2_aarch64",
"upstream_sha256": "dc6317fbfad49b1dbe396df7da7d9702cffc5ea9ea62cb3b4b2209824496b425",
"upstream_url": "https://files.pythonhosted.org/packages/1d/41/dc51c4e2f2cd730c1512739db792f70f7e9c665f6a0fb40fef62ff4b8eb3/semgrep-1.179.0-cp310.cp311.cp312.cp313.cp314.py310.py311.py312.py313.py314-none-musllinux_1_2_aarch64.whl",
"runner": "ubuntu-24.04-arm",
"containers": {
"3.11": "python:3.11-alpine3.22@sha256:a4fc589b32e824f3f02ed9d7e7be19518aa47e105b80416336af9f202275a489",
"3.12": "python:3.12-alpine3.22@sha256:a190708a2dec1bd18b1decb539f8e8f5407abaa9bf39cacda583f7f8c11db322",
"3.13": "python:3.13-alpine3.22@sha256:e81548ac35b07a3bd4805f275107592ef458b1e893c0e04d45aedaa19416cca5"
}
},
{
"filename": "semgrep-1.179.0-cp310.cp311.cp312.cp313.cp314.py310.py311.py312.py313.py314-none-win_amd64.whl",
"platform_tag": "win_amd64",
"upstream_sha256": "6c6cf104ad0959c0b11de56d15c5d31f56949c620b4bd130c5d1fb899f2bb5a3",
"upstream_url": "https://files.pythonhosted.org/packages/ec/3e/dbf54b028afb807f26dc1e1c94489faac5926a8fa745b41bf505b4c53a7e/semgrep-1.179.0-cp310.cp311.cp312.cp313.cp314.py310.py311.py312.py313.py314-none-win_amd64.whl",
"runner": "windows-2022",
"containers": {}
}
],
"uv_bootstrap": {
"version": "0.12.19",
"wheels": [
{
"filename": "uv-0.12.19-py3-none-manylinux_2_17_aarch64.manylinux2014_aarch64.musllinux_1_1_aarch64.whl",
"sha256": "b466eb0f74645883df52446d54474905e8515d75fdf0b313bf099dedc3237896",
"url": "https://files.pythonhosted.org/packages/49/5c/4690490a19726b44d8fd01cdbdc56061fdebf99bb3e466215f276eefbd5d/uv-0.12.19-py3-none-manylinux_2_17_aarch64.manylinux2014_aarch64.musllinux_1_1_aarch64.whl"
},
{
"filename": "uv-0.12.19-py3-none-musllinux_1_1_x86_64.whl",
"sha256": "a6512549e6bf12013190639bd5d9ef780f197d7adf4770a628c5e681577543d8",
"url": "https://files.pythonhosted.org/packages/28/cd/e57952eef1e24d52403a73c25c73d5697791eb72debe4a2facf9006fcba5/uv-0.12.19-py3-none-musllinux_1_1_x86_64.whl"
}
]
}
}
11 changes: 10 additions & 1 deletion openspec/CHANGE_ORDER.md
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,15 @@ This document is the **single source of truth for active work** in this
repository. It lists what is in flight, what is paused, and the order in which
active changes should be implemented.

## Downstream Semgrep admission (#772, 2026-10-16)

The owner authorized `downstream-semgrep-admission` as a separate implementation
PR under #747. Build metadata-only downstream wheels with fixed MCP, execute the
seven-platform / three-Python matrix, and require exact protected provenance
before production adoption. Candidate tests do not authorize their own artifacts
or replace the currently blocked frozen audit. Preserve every existing gate,
provider pin and signature boundary. No merge or main promotion is authorized.

## Authorized #747 dependency stack (2026-10-10)

The owner approved `dev <- #744 <- #727 <- #752 <- #754 <- #753`, retaining
Expand Down Expand Up @@ -73,7 +82,7 @@ Recovered from uncommitted proposal-only feature worktrees into local `dev`; rem

| Bucket | Count | Location |
|---|---:|---|
| **Active-tree entries** | 41 | [`openspec/changes/`](changes/) |
| **Active-tree entries** | 47 | [`openspec/changes/`](changes/) |
| **Parking-lot entries** | 21 | [`openspec/parking-lot/`](parking-lot/) |
| **Archived** | 120 | [`openspec/changes/archive/`](changes/archive/) |

Expand Down
79 changes: 79 additions & 0 deletions openspec/changes/downstream-semgrep-admission/REVIEW_EXCEPTIONS.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,79 @@
# Preinstallation review exceptions

The retained predecessor SpecFact review reports nine `MISSING_ICONTRACT` warnings and one
`kiss.parameter-count.warning`. These are explicit, scoped exceptions under
`docs/agent-rules/50-quality-gates-and-review.md`. The review scorer, thresholds,
configuration and contract checker remain unchanged.

## Standard-library preinstallation boundary

The nine affected entry points are `build_candidate`, `verify_candidate`,
`required_matrix`, `aggregate_candidate_reports`, `admit_artifact`,
`CandidateWheel.candidate_audit_input`, `AdmittedWheel.audit_input`,
`BoundAuditInput.assert_complete_result`, and the verification driver's `main`.

These scripts run before an environment or a downstream artifact is trusted.
Importing icontract or beartype here would require a third-party installation
before the boundary can check it. A fallback decorator would falsely imply an
enforced contract. Instead, the core validates its external JSON, paths,
identities, archive members, signatures and audit inventory with explicit
fail-closed checks. The driver validates its arguments and invokes the same
boundary. The forty mapped regressions exercise observable success and rejection
behavior. The independent final security review accepted this limited rationale.

This exception applies only to these preinstallation entry points. It does not
permit application APIs to omit contracts, or permit a caller to bypass admission.
Reassess it if this code moves into an authenticated installed environment.

## External MCP factory signature

`_FixedManagerFactory.__call__` declares the five MCP 1.30.0 constructor arguments
used by the tests. The SDK also has optional arguments outside that tested call
projection. The reported sixth parameter is protocol `self`. The regression
checks the supported SDK parameters before casting to that protocol; its `app`
annotation is deliberately wider than the SDK annotation. Removing a call
argument or erasing the signature would lose a tested control or misrepresent
the call. This exception applies only to that external protocol declaration.

No other current review finding is covered by these exceptions. Real native
platform proof and protected producer provenance remain separate obligations.

## Credential fixture readability

The FULL review also reports `ai-bloat.loc-vs-complexity` on the 53-line private
`_authenticated_fixture`. Manual inspection retains that fixture. The same
function defines ephemeral signed credentials, their signature verifier, and the
server's audience policy. Splitting these single-use pieces into more helpers
would spread the security assumptions across callers without removing logic.
The HTTP test itself now contains only the ownership request sequence. This is
an explicit retained readability advisory, not a change to the clean-code gate.

## Matrix boundary readability

Manual inspection retains `ai-bloat.loc-vs-complexity` on the 41-line
`aggregate_candidate_reports`. Each guard validates a distinct external evidence
obligation, including source cleanliness, audit controls and platform coverage.
The audit-field inventory is already a single constant. No standard-library
operation replaces those checks. Splitting them across one-caller helpers would
hide the admission conditions while retaining the same logic. This disposition
retains explicit boundary checks and does not exempt further growth.

## Retained scope in successor PR #775

This record is carried forward from published predecessor commit `7a1abb75`.
Its entry-point and external-signature scope is unchanged. It does not cover
the new archive mutation helper's complexity findings; those are corrected in
code. No scorer, threshold, rule configuration or trusted gate is changed.
The successor preserves these existing dispositions for independent review
without presenting them as a new owner authority grant or production admission.

## Additional scoped entry point in PR #778

The current review adds one `MISSING_ICONTRACT` warning for
`AdmittedWheel.materialize` to the nine predecessor warnings above. The
standard-library preinstallation disposition covers only this additional
method. It yields the retained authenticated bytes through the scoped context;
no third-party package may be required before that boundary runs. Byte identity,
normal/error cleanup and native private-storage checks remain mandatory. This
does not cover another entry point, change the scanner, or grant protected
authority or merge permission.
Loading
Loading