Skip to content

fix!: wire up the Homebrew tap and close three classifier bypasses - #5

Merged
SHSharkar merged 2 commits into
mainfrom
sazzad/release-readiness-audit
Sep 13, 2026
Merged

SHSharkar merged 2 commits into
mainfrom
sazzad/release-readiness-audit

Conversation

@SHSharkar

Copy link
Copy Markdown
Contributor

Summary

Two fixes from a pre-v1 release-readiness pass. The first wires up the Homebrew tap publish job
that never existed. The second closes three ways a caller with ordinary write or DDL scope could
bypass the SQL classifier entirely, found independently by three separate audit passes run against
this branch.

Motivation

[workspace.metadata.dist] declared a Homebrew tap but never enabled its publish job, and
release.yml only built artifacts, never published them, so devops-infinity/homebrew-tap would
have stayed empty through a real release.

Separately, authorize(), the function that checks a statement's relations against the configured
schema scope, was called from exactly one place in the entire crate: the read path. DO/CALL
bodies and CREATE FUNCTION/CREATE PROCEDURE bodies are opaque string literals to the SQL parser,
so the classifier found nothing destructive inside one and let it run with no confirmation, no
schema check, and no denied-function check, using only ordinary write scope.

Changes

  • Added publish-jobs = ["homebrew"] to Cargo.toml and regenerated release.yml with
    dist generate --mode ci, adding the host, publish-homebrew-formula, and announce jobs.
    Every action is pinned back to a commit SHA and the three installer scripts are back to
    fetch-then-verify-then-run, matching this repository's own supply-chain rules.
  • classify() now always sets a destructive reason for DoStmt, CallStmt, and
    CreateFunctionStmt, so all three need confirm: true or the confirmation prompt.
  • routine.rs now accepts only sql and plpgsql as a function or procedure language, refusing
    plpython3u, plperlu, and similar languages that reach the filesystem or the network directly.
  • write::execute now calls authorize() on the final rendered statement before running it,
    closing the schema-scope gap for every write and DDL builder tool at once (a pg_delete filter
    or a DDL WHERE/USING/CHECK fragment containing a subquery against a foreign schema is now
    refused).
  • types.rs refuses CREATE EXTENSION for a short list of extensions that grant OS-level or
    arbitrary-network access: the untrusted procedural languages, dblink, postgres_fdw,
    file_fdw, adminpack.
  • Fixed a genuine false positive surfaced while testing the above: a
    CREATE FUNCTION ... SECURITY DEFINER SET search_path clause was being refused as a standalone
    SET statement, because PostgreSQL represents a function's SET option using the same node type
    as a real SET command, and the classifier's tree walk collected it as one. It now recognizes
    that specific shape and does not descend into it.

Type of Change

  • Bug fix (fix)
  • Breaking change (append ! to type)

Testing

Test type:

  • Unit tests added/updated
  • Integration tests added/updated
  • Manual testing performed

Steps to verify:

  1. cargo nextest run --workspace --all-features --locked with OWNPG_TEST_DSN set: 302 tests
    pass, including three new live tests that prove the closed exploit paths: a pg_delete filter
    reaching a foreign schema is refused, a DO block always needs confirmation, and the
    security-definer search_path case no longer false-positives.
  2. cargo fmt --all -- --check and cargo clippy --workspace --all-targets --all-features --locked -- -D warnings: clean.
  3. dist plan --output-format=json confirms ownpg.rb is now planned as a real release artifact.

Breaking Changes

What breaks: DO, CALL, CREATE FUNCTION, and CREATE PROCEDURE now need confirm: true
or the confirmation prompt, the same as any other statement the classifier cannot fully see into.
CREATE FUNCTION/CREATE PROCEDURE now refuses a language other than sql or plpgsql.
Migration: pass confirm: true (or accept the confirmation prompt) for these statement kinds;
rewrite a routine using a refused language in sql or plpgsql, or contact the maintainer if a
legitimate use case needs a different language allowed.

Deployment Notes

  • Environment variable added: HOMEBREW_TAP_TOKEN is required (a token with write access to
    devops-infinity/homebrew-tap) for the publish-homebrew-formula job to succeed on a real
    release. It does not exist yet as a repository secret.

Checklist

  • Code follows project conventions
  • Self-review completed
  • Tests added/updated
  • Documentation updated (if applicable)
  • No new warnings or errors
  • Build passes locally
  • PR title follows Conventional Commits

Why
[workspace.metadata.dist] declared a Homebrew tap and listed homebrew
as an installer, but never set publish-jobs, so cargo-dist never
generated a job that actually pushes Formula/ownpg.rb anywhere.
release.yml itself only built artifacts (plan, build-local-artifacts,
build-global-artifacts); it never created a GitHub Release or ran any
publish step, so devops-infinity/homebrew-tap would have stayed empty
through a real release.

What changed
Added publish-jobs = ["homebrew"] and regenerated release.yml with
`dist generate --mode ci`, which added the host, publish-homebrew-
formula, and announce jobs cargo-dist needs to create a GitHub Release
and push the formula to the tap. Every action in the regenerated file
is pinned back to a commit SHA, and the three curl-pipe-sh installer
steps (cargo-dist, rustup, cargo-cyclonedx) are back to fetch-then-
verify-then-run, matching this repository's own supply-chain rules;
`dist generate` does not produce either on its own.

Risk
The publish-homebrew-formula job needs a HOMEBREW_TAP_TOKEN repository
secret (a token with write access to devops-infinity/homebrew-tap)
that does not exist yet. Without it, the job will fail at the push
step on the first real release. `dist plan` confirms the rest of the
pipeline, including ownpg.rb, is now correctly planned.
Why
Three independent audit passes (the code-quality gate, a bug-bounty
pass, and a general security review) all converged on the same
finding: the classifier, the mechanism every OwnPG tool depends on to
refuse a dangerous statement, had gaps a caller with ordinary write or
DDL scope could reach with no elevated privilege.

DO and CALL bodies are opaque string literals to the SQL parser, so
classify() found no relations, no denied functions, and no destructive
reason inside one. write.rs accepted the resulting Procedure class
next to Write with nothing further checked, so a DO block reached any
schema, called any function, and ran unconfirmed. CREATE FUNCTION and
CREATE PROCEDURE bodies have the same blind spot, and the language
argument accepted anything syntactically valid, including plpython3u
and plperlu, both of which reach the filesystem and the network
directly. Separately, authorize(), the one function that checks a
statement's relations against the configured schema scope, was called
from exactly one place in the whole crate: the read path. Every write
and DDL builder tool only checked statement kind and denied-function
refusals, never relations, so a filter or WHERE fragment containing a
subquery against a foreign schema passed straight through.

What changed
classify() now always sets a destructive reason for DoStmt, CallStmt,
and CreateFunctionStmt, so all three need confirm: true or the
confirmation prompt, the same as any other statement the classifier
cannot fully see into. routine.rs now accepts only sql and plpgsql as
a function or procedure language. write::execute now calls authorize()
on the final rendered statement before running it, closing the
schema-scope gap for every write and DDL tool at once. types.rs
refuses CREATE EXTENSION for a short list of extensions that grant
OS-level or arbitrary-network access (the untrusted procedural
languages, dblink, postgres_fdw, file_fdw, adminpack).

Fixing this also surfaced a real, unrelated false positive: a
CREATE FUNCTION ... SECURITY DEFINER SET search_path clause was being
refused as if it were a standalone SET statement, because walk()
collects every node shaped like a statement anywhere in the tree, and
PostgreSQL represents a function's SET option using the same
VariableSetStmt node type. walk() now recognizes that specific shape
(a DefElem with defname set or reset) and does not descend into it.

Risk
This is a breaking change for anyone already relying on DO, CALL,
CREATE FUNCTION, or CREATE PROCEDURE running without confirm: true, or
on a non-sql/plpgsql function language. Every fix has a live test
proving the exploit path is closed and the legitimate case still
works: pg_delete with a cross-schema filter subquery, a DO block
reaching another schema, and the security-definer search_path case.
296 pre-existing tests plus 6 new ones pass; fmt and clippy are clean.
@SHSharkar SHSharkar added the bug Something isn't working label Sep 13, 2026
@SHSharkar SHSharkar self-assigned this Sep 13, 2026
@SHSharkar
SHSharkar merged commit 9f56232 into main Sep 13, 2026
12 of 24 checks passed
@SHSharkar
SHSharkar deleted the sazzad/release-readiness-audit branch September 13, 2026 09:47
@qodo-code-review

Copy link
Copy Markdown

PR Summary by Qodo

Publish Homebrew releases and close SQL classifier bypasses

🐞 Bug fix 🧪 Tests ⚙️ Configuration changes 🕐 40+ Minutes

Grey Divider

AI Description

• Publishes release artifacts and Homebrew formulae through regenerated cargo-dist jobs.
• Enforces schema authorization, confirmation, safe routine languages, and extension restrictions
 across write paths.
• Adds unit and live regressions for classifier bypasses and safe PostgreSQL syntax.
Diagram

graph TD
  subgraph Runtime["SQL safety path"]
    WT["Write tools"] --> CL["SQL classifier"] --> AZ["Scope authorization"] --> CG["Confirmation gate"] --> DB[("PostgreSQL")]
  end
  subgraph Release["Release path"]
    DC["Dist config"] --> RW["Release workflow"] --> PT["Publish targets"]
  end
Loading
High-Level Assessment

The following are alternative approaches to this PR:

1. Parse routine bodies by language
  • ➕ Could enforce relation scope inside straightforward SQL and PL/pgSQL bodies.
  • ➕ Could avoid confirmation for demonstrably safe procedural statements.
  • ➖ Requires language-specific parsers and still cannot reliably inspect dynamic SQL.
  • ➖ Does not generalize to untrusted procedural languages or extension-provided languages.
2. Rely on database role sandboxing
  • ➕ PostgreSQL privileges provide enforcement independent of classifier correctness.
  • ➕ Can block unauthorized schemas, filesystems, and networks at execution time.
  • ➖ Requires more complex deployment and role provisioning.
  • ➖ Does not replace application-level confirmation or explain refusals before execution.
3. Allowlist all extensions
  • ➕ Fails closed for unknown extensions and future capability-expanding additions.
  • ➕ Provides a stronger default than maintaining a dangerous-extension denylist.
  • ➖ Blocks legitimate extensions until explicitly reviewed and released.
  • ➖ Creates ongoing compatibility and maintenance work across PostgreSQL installations.

Recommendation: Keep the PR’s layered approach: centralized write authorization closes the broad scope gap, conservative confirmation handles opaque bodies, and routine-language restrictions address capabilities the parser cannot inspect. Database least-privilege controls should remain a complementary boundary; consider an extension allowlist only if the pre-v1 compatibility policy can tolerate a stricter default.

Files changed (8) +464 / -119

Bug fix (4) +137 / -5
classify.rsFlag opaque procedural bodies without misclassifying routine SET options +44/-0

Flag opaque procedural bodies without misclassifying routine SET options

• Marks 'DO', 'CALL', and routine creation statements as destructive because their bodies are opaque to the classifier. The AST walker now skips function-level 'SET' and 'RESET' definition elements, preventing valid security-definer search-path clauses from being treated as standalone refused statements.

crates/ownpg-core/src/classify.rs

routine.rsRestrict routine creation to safe procedural languages +34/-3

Restrict routine creation to safe procedural languages

• Validates and normalizes routine languages, permitting only 'sql' and 'plpgsql'. Adds unit coverage for accepted casing and rejected interpreter-backed languages.

crates/ownpg-core/src/tools/ddl/routine.rs

types.rsRefuse extensions with host or network capabilities +50/-1

Refuse extensions with host or network capabilities

• Blocks creation of untrusted language, foreign-access, filesystem, network, and administrative extensions from a fixed denylist. Adds case-insensitive tests covering denied and commonly safe extensions.

crates/ownpg-core/src/tools/ddl/types.rs

write.rsAuthorize every rendered write statement before execution +9/-1

Authorize every rendered write statement before execution

• Applies classifier authorization to the final rendered write or DDL statement using the configured schema, mode, and pooled-connection qualification policy. Authorization now runs before dry-run responses, confirmation, or database execution.

crates/ownpg-core/src/tools/write.rs

Tests (2) +46 / -2
ddl.rsConfirm routine creation in end-to-end DDL coverage +4/-2

Confirm routine creation in end-to-end DDL coverage

• Updates routine creation scenarios to provide explicit confirmation now that opaque routine bodies are always considered destructive.

crates/ownpg-core/tests/live/ddl.rs

write.rsCover write scope escape and procedural confirmation regressions +42/-0

Cover write scope escape and procedural confirmation regressions

• Adds live tests proving a write-builder filter cannot reference a foreign schema and an unconfirmed procedural block cannot execute. The assertions verify stable refusal and confirmation error codes.

crates/ownpg-core/tests/live/write.rs

Other (2) +281 / -112
release.ymlAdd generated hosting and Homebrew publishing pipeline +280/-112

Add generated hosting and Homebrew publishing pipeline

• Regenerates the cargo-dist workflow to plan, build, host, and attach artifacts to GitHub Releases before publishing formulae to the Homebrew tap. It retains commit-pinned actions and checksum-verified installer scripts, and authenticates tap pushes through 'HOMEBREW_TAP_TOKEN'.

.github/workflows/release.yml

Cargo.tomlEnable cargo-dist Homebrew publishing +1/-0

Enable cargo-dist Homebrew publishing

• Adds Homebrew to 'publish-jobs', causing cargo-dist to generate the tap publication stage for 'devops-infinity/homebrew-tap'.

Cargo.toml

@qodo-code-review

Copy link
Copy Markdown

Code Review by Qodo

🐞 Bugs (5) 📘 Rule violations (0) 📜 Skill insights (0)

Grey Divider


Action required

1. Untrusted blocks can execute host code 🐞 Bug ⛨ Security
Description
classify assigns every DoStmt only a destructive reason, while the hard safe_language
allowlist is confined to the routine builder. When an untrusted language is already installed,
pg_run_write accepts a DO LANGUAGE ... block as a procedure and confirm: true sends its opaque
body to PostgreSQL, including code with interpreter-level filesystem or network capabilities.
Code

crates/ownpg-core/src/classify.rs[R309-312]

+    if classification.destructive_reason.is_none()
+        && matches!(
+            classification.kind.as_str(),
+            "DoStmt" | "CallStmt" | "CreateFunctionStmt"
Evidence
The new classifier branch only adds a confirmation-triggering destructive reason. Procedures are
accepted by pg_run_write, and confirmation proceeds to execution, while the rationale in the new
routine allowlist explicitly states that other procedural languages have interpreter-level
capabilities.

crates/ownpg-core/src/classify.rs[309-317]
crates/ownpg-core/src/classify.rs[387-405]
crates/ownpg-core/src/tools/write.rs[686-713]
crates/ownpg-core/src/tools/ddl/routine.rs[16-31]

Agent prompt
The issue below was found during a code review. Follow the provided context and guidance below and implement a solution

## Issue description
Raw `DO` statements can select an untrusted procedural language and execute after `confirm: true`, bypassing the new language restriction in the routine builder.

## Fix Focus Areas
- crates/ownpg-core/src/classify.rs[309-317]
- crates/ownpg-core/src/tools/ddl/routine.rs[16-31]
- crates/ownpg-core/src/tools/write.rs[686-713]

## Recommended Fix
Inspect the language carried by `DoStmt` during classification and add a hard refusal unless it is an explicitly permitted language for anonymous blocks. Add tests proving that an unsafe language remains refused even with confirmation enabled.

ⓘ Copy this prompt and use it to remediate the issue with your preferred AI generation tools


2. Cross-schema procedures remain callable 🐞 Bug ⛨ Security
Description
authorize scopes only classification.relations, although the walker stores a CALL target in
classification.functions and the new fallback merely marks it destructive. A write-capable caller
can therefore submit CALL other_schema.proc() with confirm: true, reaching a routine outside the
configured schema.
Code

crates/ownpg-core/src/classify.rs[R310-313]

+        && matches!(
+            classification.kind.as_str(),
+            "DoStmt" | "CallStmt" | "CreateFunctionStmt"
+        )
Evidence
Authorization iterates only over relations, whereas the walker records FuncCall names separately.
The added destructive marker requires confirmation but does not turn the called procedure into an
object checked against SchemaScope.

crates/ownpg-core/src/classify.rs[322-360]
crates/ownpg-core/src/classify.rs[550-565]
crates/ownpg-core/src/classify.rs[985-997]
crates/ownpg-core/src/tools/write.rs[686-713]

Agent prompt
The issue below was found during a code review. Follow the provided context and guidance below and implement a solution

## Issue description
Qualified procedure calls are recorded as functions rather than relations, so the schema authorization added around write execution does not reject calls outside the configured schema.

## Fix Focus Areas
- crates/ownpg-core/src/classify.rs[322-360]
- crates/ownpg-core/src/classify.rs[550-565]
- crates/ownpg-core/src/classify.rs[985-997]

## Recommended Fix
Capture the top-level `CALL` target as a schema-scoped object and validate it in `authorize`, including pooled-connection qualification requirements. Add tests for qualified foreign-schema calls and unqualified calls on pooled connections.

ⓘ Copy this prompt and use it to remediate the issue with your preferred AI generation tools


3. Pull requests receive release access 🐞 Bug ⛨ Security
Description
The workflow grants contents: write globally while also running on pull_request and exposing
GITHUB_TOKEN to build jobs that execute code from the checked-out revision. A same-repository pull
request can consequently run modified build inputs with a token capable of changing repository
contents or creating releases.
Code

.github/workflows/release.yml[R17-18]

permissions:
-  contents: read
+  "contents": "write"
Evidence
The workflow combines a pull-request trigger with global write permission, checks out pull-request
code, exports the GitHub token to the build, and executes dist build. The repository's ordinary CI
workflow instead limits pull requests to contents: read.

.github/workflows/release.yml[17-18]
.github/workflows/release.yml[42-45]
.github/workflows/release.yml[116-126]
.github/workflows/release.yml[151-154]
.github/workflows/ci.yml[3-10]

Agent prompt
The issue below was found during a code review. Follow the provided context and guidance below and implement a solution

## Issue description
Pull-request validation inherits the workflow-wide repository write permission even though only tag-publishing jobs require it.

## Fix Focus Areas
- .github/workflows/release.yml[17-18]
- .github/workflows/release.yml[42-45]
- .github/workflows/release.yml[247-311]

## Recommended Fix
Set workflow-level contents permission to read and grant contents write only to the tag-gated host or release job that uploads artifacts and creates the GitHub release.

ⓘ Copy this prompt and use it to remediate the issue with your preferred AI generation tools


View high (1)
4. Release archives lose attribution 🐞 Bug ≡ Correctness
Description
The regenerated local build invokes dist build without first generating THIRD-PARTY.txt, even
though that gitignored file remains configured as an archive include. A clean release checkout
therefore cannot package the required dependency attribution and may fail while resolving the
missing include or publish archives without it.
Code

.github/workflows/release.yml[R153-155]

+          # Actually do builds and make zips and whatnot
+          dist build ${{ needs.plan.outputs.tag-flag }} --print=linkage --output-format=json ${{ matrix.dist_args }} > dist-manifest.json
+          echo "dist ran successfully"
Evidence
Cargo-dist is configured to include THIRD-PARTY.txt, but the file is ignored and thus absent after
checkout. The repository's standalone release process explicitly generates and verifies it before
packaging, while the replacement workflow removed that preparation.

.github/workflows/release.yml[146-155]
Cargo.toml[121-132]
.gitignore[10-10]
tools/release.sh[393-396]

Agent prompt
The issue below was found during a code review. Follow the provided context and guidance below and implement a solution

## Issue description
The release workflow no longer creates the gitignored attribution file required by the cargo-dist include configuration before building archives.

## Fix Focus Areas
- .github/workflows/release.yml[146-155]
- Cargo.toml[121-132]
- tools/release.sh[393-396]

## Recommended Fix
Install the pinned cargo-about version and generate `THIRD-PARTY.txt` before every archive-producing `dist build`, or generate it once and download it into the workspace root in each local build job.

ⓘ Copy this prompt and use it to remediate the issue with your preferred AI generation tools



Remediation recommended

5. Published releases omit the SBOMs 🐞 Bug ≡ Correctness
Description
The artifact upload reads steps.cargo-cyclonedx.output.paths, but GitHub step values are exposed
through outputs, which is where the preceding step writes its multiline paths value. The
generated SBOM paths therefore expand to nothing and those files do not reach the host job or
published release.
Code

.github/workflows/release.yml[R243-245]

+            ${{ steps.cargo-dist.outputs.paths }}
+            ${{ steps.cargo-cyclonedx.output.paths }}
+            ${{ env.BUILD_MANIFEST_NAME }}
Evidence
The CycloneDX step writes a paths output to GITHUB_OUTPUT, but the upload step references
output.paths rather than outputs.paths. The neighboring cargo-dist expression demonstrates the
correct property name.

.github/workflows/release.yml[225-237]
.github/workflows/release.yml[238-245]
Cargo.toml[130-132]

Agent prompt
The issue below was found during a code review. Follow the provided context and guidance below and implement a solution

## Issue description
The SBOM upload path references a nonexistent singular `output` property, so generated CycloneDX files are omitted from the uploaded artifact set.

## Fix Focus Areas
- .github/workflows/release.yml[225-245]

## Recommended Fix
Change the expression to `${{ steps.cargo-cyclonedx.outputs.paths }}` and verify that the uploaded global artifact contains the generated `.cdx.xml` files.

ⓘ Copy this prompt and use it to remediate the issue with your preferred AI generation tools


Grey Divider

Context sources
Review mode: ⚖️ Balanced: Downgraded extended -> standard: change is below the extended eligibility bar (hunks 17/18, lines 583/200; both must reach the floor). Router rationale: This is a high-risk, bug-dense security and release-readiness change spanning classifier authorization, DDL capability restrictions, write-path enforcement, and privileged CI publishing, with many independent logic sites where redundant review could catch subtle bypasses or workflow defects.

Grey Divider

Tip of the day
💡 Did you know, you can start a comment with 'qodo' or '@qodo' to chat about any finding

More tips ↗ | Customize Qodo ↗ | Qodo docs ↗

Grey Divider

Qodo Logo

Comment on lines +309 to +312
if classification.destructive_reason.is_none()
&& matches!(
classification.kind.as_str(),
"DoStmt" | "CallStmt" | "CreateFunctionStmt"

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Action required

1. Untrusted blocks can execute host code 🐞 Bug ⛨ Security

classify assigns every DoStmt only a destructive reason, while the hard safe_language
allowlist is confined to the routine builder. When an untrusted language is already installed,
pg_run_write accepts a DO LANGUAGE ... block as a procedure and confirm: true sends its opaque
body to PostgreSQL, including code with interpreter-level filesystem or network capabilities.
Agent Prompt
## Issue description
Raw `DO` statements can select an untrusted procedural language and execute after `confirm: true`, bypassing the new language restriction in the routine builder.

## Fix Focus Areas
- crates/ownpg-core/src/classify.rs[309-317]
- crates/ownpg-core/src/tools/ddl/routine.rs[16-31]
- crates/ownpg-core/src/tools/write.rs[686-713]

## Recommended Fix
Inspect the language carried by `DoStmt` during classification and add a hard refusal unless it is an explicitly permitted language for anonymous blocks. Add tests proving that an unsafe language remains refused even with confirmation enabled.

ⓘ Copy this prompt and use it to remediate the issue with your preferred AI generation tools

Comment on lines +310 to +313
&& matches!(
classification.kind.as_str(),
"DoStmt" | "CallStmt" | "CreateFunctionStmt"
)

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Action required

2. Cross-schema procedures remain callable 🐞 Bug ⛨ Security

authorize scopes only classification.relations, although the walker stores a CALL target in
classification.functions and the new fallback merely marks it destructive. A write-capable caller
can therefore submit CALL other_schema.proc() with confirm: true, reaching a routine outside the
configured schema.
Agent Prompt
## Issue description
Qualified procedure calls are recorded as functions rather than relations, so the schema authorization added around write execution does not reject calls outside the configured schema.

## Fix Focus Areas
- crates/ownpg-core/src/classify.rs[322-360]
- crates/ownpg-core/src/classify.rs[550-565]
- crates/ownpg-core/src/classify.rs[985-997]

## Recommended Fix
Capture the top-level `CALL` target as a schema-scoped object and validate it in `authorize`, including pooled-connection qualification requirements. Add tests for qualified foreign-schema calls and unqualified calls on pooled connections.

ⓘ Copy this prompt and use it to remediate the issue with your preferred AI generation tools

Comment on lines 17 to +18
permissions:
contents: read
"contents": "write"

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Action required

3. Pull requests receive release access 🐞 Bug ⛨ Security

The workflow grants contents: write globally while also running on pull_request and exposing
GITHUB_TOKEN to build jobs that execute code from the checked-out revision. A same-repository pull
request can consequently run modified build inputs with a token capable of changing repository
contents or creating releases.
Agent Prompt
## Issue description
Pull-request validation inherits the workflow-wide repository write permission even though only tag-publishing jobs require it.

## Fix Focus Areas
- .github/workflows/release.yml[17-18]
- .github/workflows/release.yml[42-45]
- .github/workflows/release.yml[247-311]

## Recommended Fix
Set workflow-level contents permission to read and grant contents write only to the tag-gated host or release job that uploads artifacts and creates the GitHub release.

ⓘ Copy this prompt and use it to remediate the issue with your preferred AI generation tools

Comment on lines +153 to +155
# Actually do builds and make zips and whatnot
dist build ${{ needs.plan.outputs.tag-flag }} --print=linkage --output-format=json ${{ matrix.dist_args }} > dist-manifest.json
echo "dist ran successfully"

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Action required

4. Release archives lose attribution 🐞 Bug ≡ Correctness

The regenerated local build invokes dist build without first generating THIRD-PARTY.txt, even
though that gitignored file remains configured as an archive include. A clean release checkout
therefore cannot package the required dependency attribution and may fail while resolving the
missing include or publish archives without it.
Agent Prompt
## Issue description
The release workflow no longer creates the gitignored attribution file required by the cargo-dist include configuration before building archives.

## Fix Focus Areas
- .github/workflows/release.yml[146-155]
- Cargo.toml[121-132]
- tools/release.sh[393-396]

## Recommended Fix
Install the pinned cargo-about version and generate `THIRD-PARTY.txt` before every archive-producing `dist build`, or generate it once and download it into the workspace root in each local build job.

ⓘ Copy this prompt and use it to remediate the issue with your preferred AI generation tools

Comment on lines +243 to +245
${{ steps.cargo-dist.outputs.paths }}
${{ steps.cargo-cyclonedx.output.paths }}
${{ env.BUILD_MANIFEST_NAME }}

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Remediation recommended

5. Published releases omit the sboms 🐞 Bug ≡ Correctness

The artifact upload reads steps.cargo-cyclonedx.output.paths, but GitHub step values are exposed
through outputs, which is where the preceding step writes its multiline paths value. The
generated SBOM paths therefore expand to nothing and those files do not reach the host job or
published release.
Agent Prompt
## Issue description
The SBOM upload path references a nonexistent singular `output` property, so generated CycloneDX files are omitted from the uploaded artifact set.

## Fix Focus Areas
- .github/workflows/release.yml[225-245]

## Recommended Fix
Change the expression to `${{ steps.cargo-cyclonedx.outputs.paths }}` and verify that the uploaded global artifact contains the generated `.cdx.xml` files.

ⓘ Copy this prompt and use it to remediate the issue with your preferred AI generation tools

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

bug Something isn't working

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant