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
82 changes: 82 additions & 0 deletions .github/workflows/ci.yml
Original file line number Diff line number Diff line change
Expand Up @@ -296,6 +296,88 @@ jobs:
fi
exit "$fail"

assets-manifest-gate:
# Vendored-asset provenance invariant. Every file under `assets/`
# (except MANIFEST.toml itself) MUST be listed in `assets/MANIFEST.toml`
# with a permissive (non-copyleft) SPDX license. This is the STRUCTURAL
# gate; `tests/assets_manifest.rs` mirrors the constraint and adds
# SHA-256 verification for fast local signal. Shape mirrors
# `fixture-manifest-gate`. No untrusted GitHub event interpolation in
# any `run:` block.
name: Asset-provenance gate (every asset listed in MANIFEST.toml)
runs-on: ubuntu-latest
timeout-minutes: 5
steps:
- uses: actions/checkout@v4
- name: Every assets/* file must be listed in MANIFEST.toml
Comment on lines +299 to +312

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟠 Major | ⚡ Quick win

🧩 Analysis chain

🏁 Script executed:

#!/bin/bash
set -euo pipefail

# Verify the new job block has explicit permissions and hardened checkout settings.
awk '
  /^  assets-manifest-gate:/ {in_job=1}
  in_job {print}
  /^  resource-ref-lint:/ {in_job=0}
' .github/workflows/ci.yml > /tmp/assets-manifest-gate.yml

echo "== uses entries =="
rg -n 'uses:' /tmp/assets-manifest-gate.yml

echo "== permissions block =="
rg -n '^    permissions:|^      contents:' /tmp/assets-manifest-gate.yml || true

echo "== checkout credential persistence =="
rg -n 'persist-credentials' /tmp/assets-manifest-gate.yml || true

Repository: breezy-bays-labs/cute-dbt

Length of output: 187


🏁 Script executed:

#!/bin/bash
set -euo pipefail

file=".github/workflows/ci.yml"

echo "== workflow top-level permissions (if any) =="
rg -n "^(permissions:|default:|on:)" "$file" || true
head -n 120 "$file" | nl -ba | sed -n '1,120p'

echo
echo "== assets-manifest-gate job block (context) =="
nl -ba "$file" | sed -n '250,360p'

echo
echo "== uses entries within assets-manifest-gate =="
awk '
  /^  assets-manifest-gate:/ {in_job=1}
  in_job {print}
  /^  [a-zA-Z0-9_-]+:/ { if (seen && !/^  assets-manifest-gate:/) exit }
  BEGIN{seen=0}
  /^  assets-manifest-gate:/ {seen=1}
' "$file" | rg -n "uses:|with:|permissions:|persist-credentials" || true

echo
echo "== checkout occurrences elsewhere (to see existing hardening pattern) =="
rg -n "uses:\s*actions/checkout@" "$file" -n

Repository: breezy-bays-labs/cute-dbt

Length of output: 182


🏁 Script executed:

#!/bin/bash
set -euo pipefail

file=".github/workflows/ci.yml"

echo "== first 140 lines (for top-level permissions/default) =="
sed -n '1,140p' "$file" | cat -n

echo
echo "== assets-manifest-gate job block (around 250-380) =="
sed -n '250,380p' "$file" | cat -n

echo
echo "== assets-manifest-gate: uses/with/permissions/persist-credentials =="
awk '
  /^  assets-manifest-gate:/ {in_job=1}
  in_job {print}
  /^  [a-zA-Z0-9_-]+:/ { if (in_job && !/^  assets-manifest-gate:/) {exit} }
' "$file" | rg -n 'uses:|with:|permissions:|persist-credentials|checkout@'

Repository: breezy-bays-labs/cute-dbt

Length of output: 12115


🏁 Script executed:

#!/bin/bash
set -euo pipefail

file=".github/workflows/ci.yml"

echo "== all permissions: occurrences =="
rg -n "^[[:space:]]*permissions:" "$file" || true

echo
echo "== all persist-credentials occurrences =="
rg -n "persist-credentials" "$file" || true

Repository: breezy-bays-labs/cute-dbt

Length of output: 147


Harden assets-manifest-gate permissions and checkout action pinning.

  • assets-manifest-gate uses actions/checkout@v4 without pinning to a full commit SHA.
  • No persist-credentials: false is set for checkout.
  • The workflow contains no permissions: block anywhere, so this job inherits broader token defaults.
Suggested patch
   assets-manifest-gate:
@@
     runs-on: ubuntu-latest
     timeout-minutes: 5
+    permissions:
+      contents: read
     steps:
-      - uses: actions/checkout@v4
+      - uses: actions/checkout@<FULL_LENGTH_COMMIT_SHA>
+        with:
+          persist-credentials: false
🧰 Tools
🪛 zizmor (1.25.2)

[warning] 311-311: credential persistence through GitHub Actions artifacts (artipacked): does not set persist-credentials: false

(artipacked)


[warning] 299-379: overly broad permissions (excessive-permissions): default permissions used due to no permissions: block

(excessive-permissions)


[error] 311-311: unpinned action reference (unpinned-uses): action is not pinned to a hash (required by blanket policy)

(unpinned-uses)

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In @.github/workflows/ci.yml around lines 299 - 312, The assets-manifest-gate
job is using actions/checkout@v4 without a pinned commit, not setting
persist-credentials: false, and the workflow lacks an explicit least-privilege
permissions block; update the job so the checkout step references a full commit
SHA for actions/checkout, add persist-credentials: false to that checkout step,
and add a minimal permissions block (e.g., contents: read and any other exact
scopes needed) at the job (or top-level) to restrict the token; target the job
named assets-manifest-gate and the checkout step in the steps list when making
these changes.

run: |
set -euo pipefail
manifest="assets/MANIFEST.toml"
if [ ! -f "$manifest" ]; then
echo "::error::assets-manifest-gate: $manifest missing"
exit 1
fi

# Extract every `path = "..."` value inside an [[asset]] table.
# Track whether we are inside an `[[asset]]` block so a `path =`
# in some other table cannot pollute the listed set.
listed=$(awk '
/^[[:space:]]*\[\[asset\]\][[:space:]]*$/ { in_asset=1; next }
/^[[:space:]]*\[/ { in_asset=0 }
in_asset && /^[[:space:]]*path[[:space:]]*=/ {
line=$0
sub(/^[^"]*"/, "", line)
sub(/".*$/, "", line)
print line
}
' "$manifest" | sort -u)

# Walk every regular file under assets/ except the manifest.
# `find -printf` is GNU-only; this job pins ubuntu-latest, so it
# is the right tool here (mirrors fixture-manifest-gate).
committed=$(find assets -type f \
! -name MANIFEST.toml \
-printf '%P\n' 2>/dev/null \
| sort -u || true)

# Any committed file absent from `listed` violates provenance.
unlisted=$(comm -23 <(printf '%s\n' "$committed") <(printf '%s\n' "$listed") | sed '/^$/d')
# Any listed path not present on disk is MANIFEST drift.
missing=$(comm -13 <(printf '%s\n' "$committed") <(printf '%s\n' "$listed") | sed '/^$/d')

fail=0
if [ -n "$unlisted" ]; then
echo "::error::assets-manifest-gate: files under assets/ are NOT listed in MANIFEST.toml (provenance invariant):"
printf ' - %s\n' $unlisted
fail=1
fi
if [ -n "$missing" ]; then
echo "::error::assets-manifest-gate: MANIFEST.toml lists paths that are not present on disk:"
printf ' - %s\n' $missing
fail=1
fi

# Every [[asset]] license must be permissive (no copyleft).
bad_license=$(awk '
/^[[:space:]]*\[\[asset\]\][[:space:]]*$/ { in_asset=1; next }
/^[[:space:]]*\[/ { in_asset=0 }
in_asset && /^[[:space:]]*license[[:space:]]*=/ {
line=$0
sub(/^[^"]*"/, "", line)
sub(/".*$/, "", line)
if (line != "MIT" && line != "BSD-2-Clause" \
&& line != "BSD-3-Clause" && line != "Apache-2.0" \
&& line != "ISC" && line != "0BSD")
print line
}
' "$manifest" | sort -u)
if [ -n "$bad_license" ]; then
echo "::error::assets-manifest-gate: non-permissive asset license(s) — only MIT / BSD-2-Clause / BSD-3-Clause / Apache-2.0 / ISC / 0BSD allowed:"
printf ' - %s\n' $bad_license
fail=1
fi
exit "$fail"

resource-ref-lint:
# tracked: breezy-bays-labs/cute-dbt#12 — activate when a
# generated report.html exists (PR 8b). Until then this job no-ops; the
Expand Down
17 changes: 10 additions & 7 deletions ARCHITECTURE.md
Original file line number Diff line number Diff line change
Expand Up @@ -212,9 +212,10 @@ file.

### Build constructs

- **Embedding:** `include_str!` (text — Sakura CSS, jQuery, DataTables
JS/CSS, the Mermaid UMD bundle) / `include_bytes!` (the `data:` favicon
source) at compile time. Asset bytes land in the binary's `.rodata`
- **Embedding:** every v0.1 asset is text (Sakura CSS, jQuery, DataTables
JS/CSS, the Mermaid UMD bundle), so each is embedded with `include_str!`
at compile time — the bundle carries no binary asset and no
`include_bytes!` user. Asset bytes land in the binary's `.rodata`
section; **there is no runtime asset directory and no code path that
fetches them.** The askama template interpolates them inside
`<style>` / `<script>` blocks with the `|safe` filter.
Expand All @@ -232,17 +233,19 @@ file.
The system-font stack suppresses Mermaid's default Google Fonts fetch
(proven empirically in the R1 spike); without it the report would emit
a network request when opened in a browser with networking allowed.
- **Favicon:** an embedded `data:` URI favicon (or an empty
`<link rel="icon">`) so the browser's automatic favicon request never
leaves the document. Reinforces the "literally zero requests" story.
- **Favicon:** an empty `data:` URI favicon, emitted as
`<link rel="icon" href="data:,">`, so the browser's automatic favicon
request resolves in-document and never leaves it. Reinforces the
"literally zero requests" story.

### Vendored-asset provenance

`assets/MANIFEST.toml` records every vendored asset with:

- `name` — the asset's library identifier (e.g. `mermaid`)
- `version` — the pinned upstream version
- `url` — the upstream source URL the bytes were fetched from
- `path` — the asset's filename within `assets/`
- `source` — the canonical upstream URL the bytes were fetched from
- `sha256` — the SHA-256 of the vendored file
- `license` — SPDX identifier (all MIT/BSD/Apache-compatible)

Expand Down
79 changes: 60 additions & 19 deletions assets/MANIFEST.toml
Original file line number Diff line number Diff line change
@@ -1,26 +1,67 @@
# Vendored frontend asset provenance.
#
# Every asset inlined into the generated `report.html` is listed here with
# its pinned version, source URL, SHA-256, and SPDX license. This file is
# the supply-chain artifact any auditor can read for the *frontend bundle*
# (paired with `Cargo.toml` + `Cargo.lock` + `deny.toml` for the Rust
# crate graph).
# Every asset inlined into cute-dbt's generated HTML report is listed here
# with its pinned version, canonical source URL, SHA-256 of the embedded
# bytes, and SPDX license. Paired with `Cargo.toml` + `Cargo.lock` +
# `deny.toml` (the Rust crate graph), this file is the complete
# supply-chain artifact an auditor reads for the frontend bundle.
#
# Skeleton at bootstrap; entries populated in PR 8a (#TBD) when the
# `include_str!` / `include_bytes!` embedding infrastructure lands.
# Integrity is enforced two ways, both of which fail the PR on drift:
# - `tests/assets_manifest.rs` — a `cargo test` asserting every listed
# asset exists on disk, its SHA-256 matches, its license is permissive,
# and no file under `assets/` is unlisted.
# - the `assets-manifest-gate` CI job — a structural grep mirroring
# `fixture-manifest-gate`.
#
# Update flow for a version bump: re-download from `source`, replace the
# file, update `version` + `sha256` here; the PR 9 headless-zero-egress
# test then re-validates that the new bundle still issues zero requests.
#
# Schema (each entry):
# [[asset]]
# name = "..." # human-readable name
# version = "..." # pinned version
# source = "https://..." # canonical download URL
# sha256 = "..." # hex SHA-256 of the embedded bytes
# license = "MIT" | "BSD-3-Clause" | ... # SPDX identifier
#
# Expected v0.1 entries (placeholder until PR 8a):
# - sakura ~1.5 (CSS, classless)
# - jquery 3.7.1 (JS, DataTables dep)
# - datatables 2.1.8 (JS + CSS, sort/filter/search)
# - mermaid 11.x (UMD bundle, securityLevel:'strict')
# name = "..." # library identifier (e.g. "mermaid")
# version = "..." # pinned upstream version
# path = "..." # filename within assets/
# source = "https://..." # canonical download URL
# sha256 = "..." # hex SHA-256 of the embedded bytes
# license = "MIT" | ... # SPDX identifier (permissive only)

[[asset]]
name = "sakura"
version = "1.5.0"
path = "sakura-1.5.0.css"
source = "https://unpkg.com/sakura.css@1.5.0/css/sakura.css"
sha256 = "c997043b8e58936644e2bd47acb561c2f997a1167daf5706ab6ad2ca4fe9629c"
license = "MIT"

[[asset]]
name = "jquery"
version = "3.7.1"
path = "jquery-3.7.1.min.js"
source = "https://code.jquery.com/jquery-3.7.1.min.js"
sha256 = "fc9a93dd241f6b045cbff0481cf4e1901becd0e12fb45166a8f17f95823f0b1a"
license = "MIT"

[[asset]]
name = "datatables"
version = "2.1.8"
path = "datatables-2.1.8.min.js"
source = "https://cdn.datatables.net/2.1.8/js/dataTables.min.js"
sha256 = "69306f7b554a5a8ce68e8f4d6f613bdd1bcfe2df313962e7fc83cf5f6be5e085"
license = "MIT"

[[asset]]
name = "datatables"
version = "2.1.8"
path = "datatables-2.1.8.min.css"
source = "https://cdn.datatables.net/2.1.8/css/dataTables.dataTables.min.css"
sha256 = "10587c5eacd75926baec92990840987da4a8fb66b91c8c43ac955e7b174f21b0"
license = "MIT"

asset = []
[[asset]]
name = "mermaid"
version = "11.15.0"
path = "mermaid-11.15.0.umd.min.js"
source = "https://cdn.jsdelivr.net/npm/mermaid@11.15.0/dist/mermaid.min.js"
sha256 = "70137e77bb273bb2ef972b86e8b0400cca8be53cb25bfc45911a186dc98665de"
license = "MIT"
1 change: 1 addition & 0 deletions assets/datatables-2.1.8.min.css

Large diffs are not rendered by default.

4 changes: 4 additions & 0 deletions assets/datatables-2.1.8.min.js

Large diffs are not rendered by default.

2 changes: 2 additions & 0 deletions assets/jquery-3.7.1.min.js

Large diffs are not rendered by default.

3,405 changes: 3,405 additions & 0 deletions assets/mermaid-11.15.0.umd.min.js

Large diffs are not rendered by default.

Loading
Loading