feat(showcase): build the static gallery generator (#300) - #308
Conversation
Architectural decision: integrated static generator (Python, matching the existing sync.py pattern) that compiles catalog TOML metadata and README files into static mdBook pages alongside the docs build. Zero backend, zero database, 100% static output in a single build step. Generator (cougr-site/generate-showcase.py): - Reads examples/catalog.toml for structured metadata (category, maturity, cougr_features, optional screenshot/testnet_contract) - Resolves and parses each example's README.md for descriptions and full content without manual duplication - Consumes design tokens directly from packages/tokens/tokens.json (single source of truth per docs/BRAND.md) as CSS custom properties Output: - Gallery index page with client-side filtering by category and maturity (vanilla JS, no dependencies) - One detail page per example with badges, feature tags, README content (links rewritten to GitHub URLs), and preview image - Preview images copied when present; <img> gracefully omitted when absent - Cougr Verified badge rendered when catalog provides verified field CI integration: - Generator runs after sync.py verification, before mdbook build - Workflow triggers on catalog.toml, example READMEs, preview SVGs, and tokens.json changes in addition to existing paths Closes salazarsebas#300
|
@sanusishafii989 Great news! 🎉 Based on an automated assessment of this PR, the linked Wave issue(s) no longer count against your application limits. You can now already apply to more issues while waiting for a review of this PR. Keep up the great work! 🚀 |
salazarsebas
left a comment
There was a problem hiding this comment.
Really nice generator overall — the HTML escaping is consistent everywhere I checked (titles, descriptions, badges, features), and the design-token consumption is a good call.
One real issue: copy_previews() and check_preview() join the preview / screenshot fields from catalog.toml straight into a filesystem path with os.path.join(EXAMPLES_DIR, name, preview), with no validation. os.path.join discards earlier segments when the last one is absolute, and ../ segments aren't blocked either. A catalog.toml entry like preview = "/etc/passwd" or preview = "../../../../some/secret" would get copied into the publicly built site during CI. Since catalog.toml is exactly the file external contributors touch when adding a new example, this is reachable, not theoretical.
Suggested fix: reject any preview/screenshot value that's absolute, contains .., or whose resolved real path falls outside EXAMPLES_DIR/<name>. Happy to re-review once that's in.
catalog.toml screenshot/preview fields are contributor-editable and were joined directly into filesystem paths without validation. A malicious entry like screenshot = /etc/passwd or ../../../some/secret would get copied into the publicly built site during CI. Add validate_preview_path() which rejects absolute paths, paths containing .. segments, and any path whose resolved real path falls outside EXAMPLES_DIR/<name>. Applied in both check_preview() and copy_previews().
sanusishafii989
left a comment
There was a problem hiding this comment.
Thanks for the review! Fixed the path traversal issue — added validate_preview_path() that rejects absolute paths, .. segments, and any resolved path outside the example directory. Applied in both check_preview() and copy_previews(). Pushed in the latest commit on this PR.
salazarsebas
left a comment
There was a problem hiding this comment.
Thanks for the quick turnaround — validate_preview_path() is solid: os.path.isabs + rejecting .. segments + os.path.realpath prefix-checked against EXAMPLES_DIR/name correctly closes the preview/screenshot vector I flagged, symlinks included.
One gap remains, same class of bug:
- The
namefield itself is never validated. It comes straight frommeta.get("name", key)inmain()and feedsos.path.join(EXAMPLES_DIR, name)for reads, and — more seriously —os.path.join(SHOWCASE_SRC, f"{name}.md")for the generated output file, completely outsidevalidate_preview_path(). Because that function computes its own "safe root" fromEXAMPLES_DIR/name, a maliciousname(e.g.../../../../tmp/evil) moves the safe root along with it and the check still passes — plus the unguardedf"{name}.md"write path is a separate arbitrary-file-write, independent of the function entirely.catalog.tomlis exactly as contributor-editable aspreview/screenshot, sonameneeds the same treatment (e.g. restrict to^[a-zA-Z0-9_-]+$, or run the same realpath-prefix check againstEXAMPLES_DIRitself).
Everything else here looks good — generator architecture, filters, detail pages, CI ordering, and token consumption all match #300. This is close.
|
Addressed the remaining review point: catalog |
Architectural decision: integrated static generator (Python, matching the existing sync.py pattern) that compiles catalog TOML metadata and README files into static mdBook pages alongside the docs build. Zero backend, zero database, 100% static output in a single build step.
Generator (cougr-site/generate-showcase.py):
Output:
CI integration:
Closes #300