Skip to content

feat: production GCP Cloud Run foundation + hardened, optimized front end (keyless IaC, WCAG 2.2 AA, GEO/structured-data, CWV-budgeted CI)#713

Open
adammerrill wants to merge 33 commits into
arthelokyo:mainfrom
adammerrill:main
Open

feat: production GCP Cloud Run foundation + hardened, optimized front end (keyless IaC, WCAG 2.2 AA, GEO/structured-data, CWV-budgeted CI)#713
adammerrill wants to merge 33 commits into
arthelokyo:mainfrom
adammerrill:main

Conversation

@adammerrill

Copy link
Copy Markdown

Summary

This contribution extends AstroWind into a production-ready, zero-secrets GCP Cloud Run starter: a complete keyless Terraform/CI foundation, plus a hardened, accessibility- and discoverability-optimized front end, and a fully refreshed documentation set.

🔗 Live preview (deployed to Google Cloud Run from this branch):
https://website-kah2tr5ppq-uc.a.run.app/

Note: scale-to-zero — the first request after idle may cold-start for ~1–2s.

What's included

☁️ GCP Cloud Run deployment foundation (new)

  • Terraform for the entire architecture, layered: bootstrap (projects, GCS state bucket, automation SA) → shared (Workload Identity Federation, deploy SA, Artifact Registry) → per-environment (dev/staging/prod). Reusable modules/ for iam, cloud_run, artifact_registry, monitoring, logging, secret_manager, and cost_governance.
  • Keyless deploys — GitHub Actions → GCP via Workload Identity Federation (OIDC), scoped to the repository. No service-account JSON keys anywhere.
  • Scale-to-zero cost profilemin-instances = 0, capped autoscaling, smallest sensible CPU/memory, and a billing budget with alerts (≈ $0/month within the free tier).
  • Container — multi-stage Dockerfile building the static site and serving it with nginx on port 8080 as a non-root user (~25 MB image).
  • CI/CDci.yml (astro check + ESLint + Prettier, Docker build, terraform validate across all envs, Lighthouse Core Web Vitals budget) and app-deploy-only deploy-*.yml workflows (build → Artifact Registry → Cloud Run revision → smoke test).

🎨 Design-system hardening

  • Token-driven styling verified WCAG 2.2 AA in both light and dark themes (exact contrast computed for every color token; the one dark-mode failure fixed).
  • prefers-reduced-motion fallback (WCAG SC 2.3.3) and a skip-to-content link (SC 2.4.1).
  • Semantic landmarks, labeled forms, required alt, and minimal hydration confirmed.

⚡ Performance & 🔎 discoverability (GEO/SEO)

  • Core Web Vitals enforced in CI via a Lighthouse budget (LCP/CLS hard-gated).
  • Config-driven schema.org JSON-LD (src/utils/schema.ts + StructuredData.astro): Organization, WebSite, BlogPosting, BreadcrumbList, FAQPage.
  • public/llms.txt (llmstxt.org) and an explicit AI-crawler policy in robots.txt (GPTBot, ClaudeBot, PerplexityBot, Google-Extended, Applebot-Extended).
  • Definition-first, fact-dense content patterns for better AI retrieval.

📈 Conversion & measurement (privacy-first)

  • GA4 + Consent Mode v2 — storage denied by default (cookieless until opt-in), IP anonymization on, off unless a Measurement ID is set. No-PII conversion events (cta_*, outbound_click, form_submit, scroll_depth).

📚 Documentation (refreshed + new)

  • Rewritten README.md and AGENTS.md, plus a full docs/ set: Fresh User Walkthrough (zero → live URL), Setup Playbook, IaC Governance Rules, a repo-vs-reality Gap Register, and five plain-language optimization guides (performance/crawler, GEO/structured-data, styling/accessibility, component accessibility, conversion/measurement) — each with authoritative sources.

Security posture

  • Zero secrets in git — only .example placeholders committed; real tfvars/backend/state gitignored; keyless identity (WIF). Verified clean across files, history, and CI logs.

Compatibility & adoption

  • The front-end theme, components, and config system remain AstroWind-compatible; the front-end optimizations are non-breaking and opt-in/default-inert.
  • The GCP/Terraform/CI layer is additive — users who don't deploy to Cloud Run can ignore terraform/, the Dockerfile, and the deploy-* workflows.

Testing / validation

  • npm run check green; CI green (check + Docker + terraform validate + Lighthouse).
  • JSON-LD validated well-formed and against schema.org; contrast computed for both themes.
  • Deployed and smoke-tested live on Cloud Run (preview URL above).

Notes for maintainers

This is a large, opinionated addition (a deployment + hardening foundation, not just a theme tweak). If a single PR is too broad, it can be split into focused PRs — (1) front-end a11y, (2) GEO/structured-data, (3) privacy analytics, (4) the GCP/Terraform/CI foundation — or adopted as a companion "AstroWind + GCP Cloud Run" template variant. Happy to reshape to fit the project's direction.

adammerrill and others added 30 commits June 15, 2026 16:13
Implementation of AstroWind v3 architecture:

- Transition CI/CD from Cloud Build to native GitHub Actions.

- Add feature-gated multi-project Terraform foundation.

- Integrate SCC, CAI, Audit Logs, and IAM Analyzer.

- Establish SLIs, SLOs, error budgets, and synthetic monitoring.

- Enforce cost governance via Terraform and strengthen DR capabilities.
Convert to Reusable GCP Starter Template
The attribute_condition was scoped to the GitHub owner (any repo under
adammerrill/ could federate). The SA binding used the same owner-wide
principalSet. Both are now scoped to attribute.repository ==
'adammerrill/astro-gcp-cloudrun-starter', so no other repository under
the same account can assume the deploy identity.

Adds github_repo variable wired through shared env → iam module.
Template users must set github_repo in their tfvars; the .example file
is updated with the new placeholder.

https://claude.ai/code/session_019m7zNSare8J2meL24HDW5K
security(wif): scope WIF identity to this repo only, not entire owner
SA impersonation (used by all downstream environment providers) requires
iamcredentials.googleapis.com to generate access tokens. Without it the
shared/providers.tf data source fails with 400 badRequest on first plan.

https://claude.ai/code/session_019m7zNSare8J2meL24HDW5K
The google_service_account_access_token data source passes scopes directly
to the generateAccessToken API, which requires full URIs. Short-form names
like 'cloud-platform' are invalid arguments and cause a 400 error. Changed
all four environment providers.tf files to use full URIs, matching the
format already used in the impersonation provider block above.

https://claude.ai/code/session_019m7zNSare8J2meL24HDW5K
fix: use full OAuth2 URIs for SA impersonation scopes + add iamcredentials API to bootstrap
…s, drop duplicate state bucket (#4)

* fix(bootstrap): add missing seed APIs needed by downstream environments

Previously only 4 APIs were enabled on seed projects. Downstream
Terraform environments were failing with 403/disabled errors as each
missing API was encountered. Added the full set needed before any
environment apply can succeed:
- cloudbilling.googleapis.com (project_factory billing config)
- iamcredentials.googleapis.com (SA impersonation token generation)
- artifactregistry.googleapis.com (shared AR repo creation)
- run.googleapis.com (Cloud Run service creation)
- secretmanager.googleapis.com (Secret Manager resources)

https://claude.ai/code/session_019m7zNSare8J2meL24HDW5K

* fix: grant admin SA WIF+SA-admin roles; drop redundant state bucket in shared

Two issues surfaced applying the shared environment:

1. The admin SA (editor + projectIamAdmin) cannot create Workload
   Identity Pools or set IAM policy on service accounts. Added
   roles/iam.workloadIdentityPoolAdmin and roles/iam.serviceAccountAdmin
   to the bootstrap admin_roles so downstream WIF/SA-binding resources
   can be created.

2. The shared environment's storage module created a bucket with the
   same name as the bootstrap-managed TF state bucket, causing a 409
   conflict and dual-ownership across two state files. Removed the
   storage module from the shared environment; bootstrap owns the state
   bucket and shared referenced none of the storage outputs.

https://claude.ai/code/session_019m7zNSare8J2meL24HDW5K

---------

Co-authored-by: Claude <noreply@anthropic.com>
Validated by a full local terraform apply of the dev environment.

- deploy-dev.yml: pass TF_VAR_* env vars and -backend-config so CI uses
  correct values and a gitignored backend
- bootstrap admin_roles: add run.admin, logging.configWriter,
  monitoring.admin, secretmanager.admin (needed by env modules)
- storage module: create_state_bucket flag + count-indexed state buckets
  and outputs so env layers don't duplicate the bootstrap state bucket
- dev/main.tf: create_state_bucket = false

https://claude.ai/code/session_019m7zNSare8J2meL24HDW5K

Co-authored-by: Claude <noreply@anthropic.com>
The deploy pipeline ran a full terraform apply, which broke under keyless
WIF in two ways:
- The env providers.tf impersonate sa-terraform-admin and require
  var.terraform_service_account, which CI never set, so apply hung at an
  interactive prompt until timeout.
- backend.tf is gitignored, so -backend-config fell back to local state
  and would have tried to recreate all already-provisioned resources.

Infrastructure is owned by Terraform run deliberately from a controlled
environment. CI should only build the app image and deploy a new Cloud
Run revision to the existing service. Removed the Setup Terraform and
Terraform Apply steps; kept build/push -> deploy-cloudrun -> smoke test.

Requires the deploy SA to have roles/run.developer on the env project and
roles/iam.serviceAccountUser on the Cloud Run runtime SA.

https://claude.ai/code/session_019m7zNSare8J2meL24HDW5K

Co-authored-by: Claude <noreply@anthropic.com>
When the container image lives in the shared project's Artifact Registry
but the service runs in an env project, the puller is the env project's
Cloud Run Service Agent (service-PROJECTNUMBER@serverless-robot-prod),
not the runtime SA. Added a data.google_project lookup to construct that
agent identity and grant it artifactregistry.reader on the shared
project. Without this, deploys fail with a 403 downloadArtifacts denied.

https://claude.ai/code/session_019m7zNSare8J2meL24HDW5K

Co-authored-by: Claude <noreply@anthropic.com>
… + fix docs) (#9)

* docs(audit): gap register — repo vs. actual deploy reality

https://claude.ai/code/session_019m7zNSare8J2meL24HDW5K

* feat(terraform): codify deploy-SA roles; default create_project=false; service_name=website

- iam module grants the GitHub deploy SA run.developer + serviceAccountUser on
  the runtime SA (was a manual gcloud step) when deploy_service_account_email is
  set; dev wires it deterministically from shared_project_id.
- create_project now defaults to false in env layers (bootstrap owns projects);
  true caused a billing precondition error.
- dev service_name default is now 'website' to match the SERVICE_NAME CI var.

https://claude.ai/code/session_019m7zNSare8J2meL24HDW5K

* feat(template): complete .example files for required config

- shared tfvars.example: add project_id, project_name, create_project
- dev tfvars.example: add project_name, create_project, service_name (+ coupling note)
- add bootstrap/backend.tf.example so state migration is a copy, not a hand-edit

https://claude.ai/code/session_019m7zNSare8J2meL24HDW5K

* fix(ci,template): remove unused /app scaffold; staging/prod app-deploy-only

- /app placeholder server is unused by the build (root Dockerfile builds the
  Astro site and serves via nginx:8080); removed to stop misleading users.
- staging/prod workflows drop terraform-apply-in-CI to match the validated dev
  pipeline (keyless, least-privilege, no impersonation hang).

https://claude.ai/code/session_019m7zNSare8J2meL24HDW5K

* docs(setup): corrected ordered setup + fresh-user walkthrough

- FRESH_USER_WALKTHROUGH.md: single authoritative zero-to-deployed path incl.
  GitHub Variables and verify-URL steps.
- SETUP_PLAYBOOK: copy backend.tf.example (not hand-edit main.tf); apply shared
  before dev; create_project note; project-quota callout.
- TEMPLATE_SETUP: correct build path (Astro + nginx:8080, no /app); add CI step.
- README: fix 9-vs-8 governance rule count.

https://claude.ai/code/session_019m7zNSare8J2meL24HDW5K

---------

Co-authored-by: Claude <noreply@anthropic.com>
Adds a 'Run workflow' button to the dev/staging/prod deploy workflows so the
current main (or a chosen ref) can be rebuilt and redeployed on demand without
an empty commit — useful for forcing a clean rebuild or after rotating GCP-side
config.

https://claude.ai/code/session_019m7zNSare8J2meL24HDW5K

Co-authored-by: Claude <noreply@anthropic.com>
…og, URLs) (#11)

* fix(content): repoint stale arthelokyo/astrowind URLs to the new repo

Replaces github.com/arthelokyo/astrowind (and the arthelokyo profile +
stars badge + astrowind.vercel.app canonical hosts) with
github.com/adammerrill/astro-gcp-cloudrun-starter across pages, layouts,
landing variants, and legal pages.

https://claude.ai/code/session_019m7zNSare8J2meL24HDW5K

* feat(content): rebrand homepage, nav, config, and blog to the GCP Cloud Run story

- Homepage rewritten around the real value: keyless WIF, Terraform layers,
  scale-to-zero cost, hardening, app-deploy-only CI, and what ships in the box.
- config.yaml metadata/description/handle updated; stale Google verification
  + vercel site URL replaced.
- Footer/nav: 'Use this template' CTA, GCP-themed links, new footnote.
- Blog: two lorem-ipsum posts replaced with real guides (deploy-to-Cloud-Run,
  template-in-depth); customization post retitled.

https://claude.ai/code/session_019m7zNSare8J2meL24HDW5K

* content: rebrand secondary pages (homes demos, legal, alt text)

Removes remaining AstroWind/Astrowind brand references across the
alternate-homepage demos (saas, startup, mobile-app), legal pages, the
blog list page, and image alt text. Awkward grammar from the brand swap
was cleaned up; the startup hero now leads with the Astro + Cloud Run
story. Legal-entity placeholder de-branded.

https://claude.ai/code/session_019m7zNSare8J2meL24HDW5K

* content: clear final brand mentions (announcement, landing post, Image comment, slug)

Removes the remaining user-visible AstroWind references from the
announcement star-bar and the landing blog post, de-brands an internal
component comment, and renames the customization post file so the old
brand no longer appears in its URL slug.

https://claude.ai/code/session_019m7zNSare8J2meL24HDW5K

* style: apply prettier formatting to rebranded files

Fixes the check:prettier CI step — formats the homepage, navigation,
the two new docs, and the saas/startup demo pages to Prettier style.

https://claude.ai/code/session_019m7zNSare8J2meL24HDW5K

---------

Co-authored-by: Claude <noreply@anthropic.com>
…ess (#12)

* feat(crawler-access): explicit AI-crawler allow policy in robots.txt

Explicitly welcomes GPTBot/OAI-SearchBot, ClaudeBot/Claude-User,
PerplexityBot, Google-Extended, and Applebot-Extended so the site can be
found and cited by AI search engines. They were already allowed via
'User-agent: *'; this makes the stance intentional with a per-bot opt-out.
The sitemap reference is still auto-appended at build time.

Source: web.dev/articles/vitals; vendor robots.txt integration.

https://claude.ai/code/session_019m7zNSare8J2meL24HDW5K

* ci(lighthouse): add Core Web Vitals performance budget

Runs Lighthouse CI on every PR and fails on CWV regressions:
LCP>2.5s, CLS>0.1, TBT>300ms (TBT = lab proxy for INP, which is
field-only). Performance/a11y/SEO category scores warn at <0.9 until
stable real numbers are observed, then tighten to error.

Lab is diagnostic; CrUX field data is the true scoreboard once live.
Source: web.dev/articles/vitals, web.dev/articles/inp, Google Search Central.

https://claude.ai/code/session_019m7zNSare8J2meL24HDW5K

* docs: phase 1 performance & crawler-access guide (plain-language + sources)

https://claude.ai/code/session_019m7zNSare8J2meL24HDW5K

* ci(lighthouse): median of 3 runs; hard-gate LCP/CLS, advisory TBT

A single Lighthouse run on a shared CI runner reported an inflated
1183ms TBT despite the page shipping only ~16KB JS. Switched to 3 runs +
median aggregation to de-noise. Hard-fail on lab-stable LCP/CLS at the
official thresholds; treat TBT as advisory (warn) because INP is
field-only and lab TBT is noisy — true INP is monitored via CrUX.

Source: web.dev/articles/inp, web.dev/articles/lab-and-field-data-differences.

https://claude.ai/code/session_019m7zNSare8J2meL24HDW5K

---------

Co-authored-by: Claude <noreply@anthropic.com>
…#13)

Adds config-driven structured data so AI/search engines can extract and
cite the site:
- src/utils/schema.ts: central, config-driven schema.org builders
- StructuredData.astro: renders JSON-LD; Layout injects Organization +
  WebSite sitewide, pages append their own
- Homepage: SoftwareApplication + FAQPage (single source of truth shared
  with the on-page FAQ) + BreadcrumbList
- Blog posts: BlogPosting + BreadcrumbList
- public/llms.txt: curated LLM index (llmstxt.org spec)
- docs/GEO_STRUCTURED_DATA.md: plain-language guide + sources

Only real, verifiable facts (no fabricated ratings). Locally validated:
homepage 5 JSON-LD blocks, blog post 4, all valid JSON. Verify with
Google Rich Results Test post-deploy.

Sources: schema.org, Google structured-data docs, CMU GEO (KDD 2024), llmstxt.org.

https://claude.ai/code/session_019m7zNSare8J2meL24HDW5K

Co-authored-by: Claude <noreply@anthropic.com>
…14)

- Adds a global prefers-reduced-motion fallback (WCAG 2.2 SC 2.3.3) that
  neutralizes animations/transitions/smooth-scroll for users who request
  reduced motion; functional state changes still work.
- Computed exact WCAG contrast for every color token in BOTH themes.
  Light passes everywhere; the only normal-text dark failure was the
  Button link-hover (primary on dark = 3.8:1). Fixed with
  dark:hover:text-blue-300 (11.1:1, verified). accent is never used as
  dark text; icon/large uses of primary meet the 3.0 UI threshold.
- Tokenized an introduced hardcoded text-blue-600 -> text-primary (+ dark).
- docs/STYLING_ACCESSIBILITY.md: contrast table, sources, and the
  browser-only manual checklist (responsive overflow, full tokenization).

Sources: w3.org/WAI/WCAG22, web.dev/prefers-reduced-motion.

https://claude.ai/code/session_019m7zNSare8J2meL24HDW5K

Co-authored-by: Claude <noreply@anthropic.com>
…it (#15)

- Adds a 'Skip to main content' link as the first focusable element,
  targeting <main id="main">, so keyboard/screen-reader users can bypass
  the nav (WCAG 2.2 SC 2.4.1 Bypass Blocks).
- Audited and verified already-correct: landmarks (header/nav/main/footer),
  heading order (one h1), button-vs-link semantics, associated form labels,
  focus visibility (focus:ring replacements), required alt text, html lang.
- Confirmed minimal hydration: ZERO client:* islands; only the ~16KB View
  Transitions module ships site-wide (optimal for INP).
- docs/COMPONENT_ACCESSIBILITY.md: audit results + manual keyboard/SR checklist.

Source: w3.org WCAG 2.2 Bypass Blocks / Focus Visible.

https://claude.ai/code/session_019m7zNSare8J2meL24HDW5K

Co-authored-by: Claude <noreply@anthropic.com>
…ents (#16)

- Analytics.astro upgraded to a privacy-first GA4 setup: Consent Mode v2
  defaults all storage to denied (cookieless until a CMP/banner grants),
  IP anonymization on, OFF unless a Measurement ID is configured.
- Tracks key conversion events with no PII: cta_use_template,
  outbound_click, form_submit, scroll_depth (25/50/75/100%).
- docs/CONVERSION_MEASUREMENT.md: what is measured + how to read GA4, how
  to isolate AI-referral traffic (the GEO payoff), how to enable it, where
  REAL trust signals come from (no fabrication), and a no-dark-patterns
  commitment.

Build-verified: GA OFF by default → inert guarded script, no gtag loaded.
Sources: developers.google.com/tag-platform/security/guides/consent.

https://claude.ai/code/session_019m7zNSare8J2meL24HDW5K

Co-authored-by: Claude <noreply@anthropic.com>
#17)

* docs: refresh README and AGENTS for the GCP starter + 5-phase features

- README rewritten as a GCP Cloud Run starter front door: definition-first
  intro, 'why this template' pillars, quickstart pointing to the walkthrough,
  a complete docs index (deploy/infra + the 5 optimization guides), accurate
  project structure, and config notes. Removed stale AstroWind marketing and
  Netlify/Vercel/PandaStack/CodeSandbox/StackBlitz deploy buttons.
- AGENTS.md: overview now reflects the GCP/keyless/Terraform reality; added
  SEO/GEO/Analytics layer, Infrastructure & CI/CD, and Documentation sections;
  expanded the verification checklist with the CI gate and optimization
  invariants (JSON-LD, WCAG AA, skip link, CWV budget).

https://claude.ai/code/session_019m7zNSare8J2meL24HDW5K

* docs(readme): restore all original sections, updated (additive, nothing removed)

Audited the prior rewrite and restored every section it had dropped, with
corrected/current details:
- Table of Contents, Demo, TL;DR, Getting started, full Project structure
  tree (updated to include terraform/, docs/, .github/workflows/, Dockerfile,
  schema.ts, Analytics/StructuredData, lighthouserc.json, navigation.ts).
- Configuration (config.yaml example refreshed to current values),
  Customize Design, and the full Deploy section: production-manual, Netlify,
  Vercel (link fixed to this repo), PandaStack, and GCP (primary).
- CodeSandbox/StackBlitz buttons, front-end feature bullets, badges, FAQ
  (filled in real Q&A), Acknowledgements (credits AstroWind/Arthelokyo).
Kept the new GCP/optimization intro, pillars, and the full docs index.

Principle: documentation is only updated to reflect current state, never removed.

https://claude.ai/code/session_019m7zNSare8J2meL24HDW5K

* docs(playbook): restore the inline GCS backend HCL block (additive)

The audit replaced the inline backend{} snippet with a 'copy
backend.tf.example' instruction; this restores the actual HCL block as a
reference alongside the cp step, so no useful content is lost. Other
modified docs (AGENTS, TEMPLATE_SETUP, IAC_RULES) were verified additive —
their changes only updated content to the current state.

https://claude.ai/code/session_019m7zNSare8J2meL24HDW5K

---------

Co-authored-by: Claude <noreply@anthropic.com>
…I + push-protection docs) (#18)

Layered secret scanning for this public repo: Gitleaks pre-commit hook, TruffleHog CI verification, and GitHub Advanced Security backstop, plus Dependabot version-updates config.

- .gitleaks.toml allowlist (public GA4 ID, .example placeholders, keyless-WIF SA emails/provider path, dist/node_modules/CDN hosts)
- .pre-commit-config.yaml + .githooks/pre-commit (native) gate the staged diff
- .github/workflows/secret-scan.yml: Gitleaks SARIF + TruffleHog --only-verified (PR diff + weekly full-history sweep)
- .github/dependabot.yml: weekly grouped updates (npm, github-actions, docker)
- .gitignore hardened with SA/OAuth JSON key patterns, broader .env.*/secrets.*/PEM
- SECURITY.md: three-layer runbook, allowlist rationale, rotate-first response

Baseline scan: 0 verified / 0 unverified secrets in full history. No rotation or history rewrite required; CI gate ships fail-closed.
Bumps the npm-minor-patch group with 13 updates:

| Package | From | To |
| --- | --- | --- |
| [astro](https://github.com/withastro/astro/tree/HEAD/packages/astro) | `6.4.2` | `6.4.7` |
| [@astrojs/mdx](https://github.com/withastro/astro/tree/HEAD/packages/integrations/mdx) | `6.0.1` | `6.0.3` |
| [@tailwindcss/typography](https://github.com/tailwindlabs/tailwindcss-typography) | `0.5.19` | `0.5.20` |
| [@tailwindcss/vite](https://github.com/tailwindlabs/tailwindcss/tree/HEAD/packages/@tailwindcss-vite) | `4.3.0` | `4.3.1` |
| [@types/mdx](https://github.com/DefinitelyTyped/DefinitelyTyped/tree/HEAD/types/mdx) | `2.0.13` | `2.0.14` |
| [@typescript-eslint/eslint-plugin](https://github.com/typescript-eslint/typescript-eslint/tree/HEAD/packages/eslint-plugin) | `8.60.0` | `8.61.1` |
| [@typescript-eslint/parser](https://github.com/typescript-eslint/typescript-eslint/tree/HEAD/packages/parser) | `8.60.0` | `8.61.1` |
| [eslint](https://github.com/eslint/eslint) | `10.4.0` | `10.5.0` |
| [js-yaml](https://github.com/nodeca/js-yaml) | `4.1.1` | `4.2.0` |
| [prettier](https://github.com/prettier/prettier) | `3.8.3` | `3.8.4` |
| [sharp](https://github.com/lovell/sharp) | `0.34.5` | `0.35.1` |
| [tailwindcss](https://github.com/tailwindlabs/tailwindcss/tree/HEAD/packages/tailwindcss) | `4.3.0` | `4.3.1` |
| [typescript-eslint](https://github.com/typescript-eslint/typescript-eslint/tree/HEAD/packages/typescript-eslint) | `8.60.0` | `8.61.1` |


Updates `astro` from 6.4.2 to 6.4.7
- [Release notes](https://github.com/withastro/astro/releases)
- [Changelog](https://github.com/withastro/astro/blob/main/packages/astro/CHANGELOG.md)
- [Commits](https://github.com/withastro/astro/commits/astro@6.4.7/packages/astro)

Updates `@astrojs/mdx` from 6.0.1 to 6.0.3
- [Release notes](https://github.com/withastro/astro/releases)
- [Changelog](https://github.com/withastro/astro/blob/main/packages/integrations/mdx/CHANGELOG.md)
- [Commits](https://github.com/withastro/astro/commits/@astrojs/mdx@6.0.3/packages/integrations/mdx)

Updates `@tailwindcss/typography` from 0.5.19 to 0.5.20
- [Release notes](https://github.com/tailwindlabs/tailwindcss-typography/releases)
- [Changelog](https://github.com/tailwindlabs/tailwindcss-typography/blob/main/CHANGELOG.md)
- [Commits](tailwindlabs/tailwindcss-typography@v0.5.19...v0.5.20)

Updates `@tailwindcss/vite` from 4.3.0 to 4.3.1
- [Release notes](https://github.com/tailwindlabs/tailwindcss/releases)
- [Changelog](https://github.com/tailwindlabs/tailwindcss/blob/main/CHANGELOG.md)
- [Commits](https://github.com/tailwindlabs/tailwindcss/commits/v4.3.1/packages/@tailwindcss-vite)

Updates `@types/mdx` from 2.0.13 to 2.0.14
- [Release notes](https://github.com/DefinitelyTyped/DefinitelyTyped/releases)
- [Commits](https://github.com/DefinitelyTyped/DefinitelyTyped/commits/HEAD/types/mdx)

Updates `@typescript-eslint/eslint-plugin` from 8.60.0 to 8.61.1
- [Release notes](https://github.com/typescript-eslint/typescript-eslint/releases)
- [Changelog](https://github.com/typescript-eslint/typescript-eslint/blob/main/packages/eslint-plugin/CHANGELOG.md)
- [Commits](https://github.com/typescript-eslint/typescript-eslint/commits/v8.61.1/packages/eslint-plugin)

Updates `@typescript-eslint/parser` from 8.60.0 to 8.61.1
- [Release notes](https://github.com/typescript-eslint/typescript-eslint/releases)
- [Changelog](https://github.com/typescript-eslint/typescript-eslint/blob/main/packages/parser/CHANGELOG.md)
- [Commits](https://github.com/typescript-eslint/typescript-eslint/commits/v8.61.1/packages/parser)

Updates `eslint` from 10.4.0 to 10.5.0
- [Release notes](https://github.com/eslint/eslint/releases)
- [Commits](eslint/eslint@v10.4.0...v10.5.0)

Updates `js-yaml` from 4.1.1 to 4.2.0
- [Changelog](https://github.com/nodeca/js-yaml/blob/master/CHANGELOG.md)
- [Commits](https://github.com/nodeca/js-yaml/commits)

Updates `prettier` from 3.8.3 to 3.8.4
- [Release notes](https://github.com/prettier/prettier/releases)
- [Changelog](https://github.com/prettier/prettier/blob/main/CHANGELOG.md)
- [Commits](prettier/prettier@3.8.3...3.8.4)

Updates `sharp` from 0.34.5 to 0.35.1
- [Release notes](https://github.com/lovell/sharp/releases)
- [Commits](lovell/sharp@v0.34.5...v0.35.1)

Updates `tailwindcss` from 4.3.0 to 4.3.1
- [Release notes](https://github.com/tailwindlabs/tailwindcss/releases)
- [Changelog](https://github.com/tailwindlabs/tailwindcss/blob/main/CHANGELOG.md)
- [Commits](https://github.com/tailwindlabs/tailwindcss/commits/v4.3.1/packages/tailwindcss)

Updates `typescript-eslint` from 8.60.0 to 8.61.1
- [Release notes](https://github.com/typescript-eslint/typescript-eslint/releases)
- [Changelog](https://github.com/typescript-eslint/typescript-eslint/blob/main/packages/typescript-eslint/CHANGELOG.md)
- [Commits](https://github.com/typescript-eslint/typescript-eslint/commits/v8.61.1/packages/typescript-eslint)

---
updated-dependencies:
- dependency-name: astro
  dependency-version: 6.4.7
  dependency-type: direct:production
  update-type: version-update:semver-patch
  dependency-group: npm-minor-patch
- dependency-name: "@astrojs/mdx"
  dependency-version: 6.0.3
  dependency-type: direct:development
  update-type: version-update:semver-patch
  dependency-group: npm-minor-patch
- dependency-name: "@tailwindcss/typography"
  dependency-version: 0.5.20
  dependency-type: direct:development
  update-type: version-update:semver-patch
  dependency-group: npm-minor-patch
- dependency-name: "@tailwindcss/vite"
  dependency-version: 4.3.1
  dependency-type: direct:development
  update-type: version-update:semver-patch
  dependency-group: npm-minor-patch
- dependency-name: "@types/mdx"
  dependency-version: 2.0.14
  dependency-type: direct:development
  update-type: version-update:semver-patch
  dependency-group: npm-minor-patch
- dependency-name: "@typescript-eslint/eslint-plugin"
  dependency-version: 8.61.1
  dependency-type: direct:development
  update-type: version-update:semver-minor
  dependency-group: npm-minor-patch
- dependency-name: "@typescript-eslint/parser"
  dependency-version: 8.61.1
  dependency-type: direct:development
  update-type: version-update:semver-minor
  dependency-group: npm-minor-patch
- dependency-name: eslint
  dependency-version: 10.5.0
  dependency-type: direct:development
  update-type: version-update:semver-minor
  dependency-group: npm-minor-patch
- dependency-name: js-yaml
  dependency-version: 4.2.0
  dependency-type: direct:development
  update-type: version-update:semver-minor
  dependency-group: npm-minor-patch
- dependency-name: prettier
  dependency-version: 3.8.4
  dependency-type: direct:development
  update-type: version-update:semver-patch
  dependency-group: npm-minor-patch
- dependency-name: sharp
  dependency-version: 0.35.1
  dependency-type: direct:development
  update-type: version-update:semver-minor
  dependency-group: npm-minor-patch
- dependency-name: tailwindcss
  dependency-version: 4.3.1
  dependency-type: direct:development
  update-type: version-update:semver-patch
  dependency-group: npm-minor-patch
- dependency-name: typescript-eslint
  dependency-version: 8.61.1
  dependency-type: direct:development
  update-type: version-update:semver-minor
  dependency-group: npm-minor-patch
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
Bumps the docker group with 2 updates: node and nginx.


Updates `node` from 22-alpine to 26-alpine

Updates `nginx` from 1.27-alpine to 1.31-alpine

---
updated-dependencies:
- dependency-name: node
  dependency-version: 26-alpine
  dependency-type: direct:production
  dependency-group: docker
- dependency-name: nginx
  dependency-version: 1.31-alpine
  dependency-type: direct:production
  dependency-group: docker
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
Bumps the github-actions group with 9 updates:

| Package | From | To |
| --- | --- | --- |
| [actions/checkout](https://github.com/actions/checkout) | `4` | `6` |
| [actions/setup-node](https://github.com/actions/setup-node) | `4` | `6` |
| [docker/setup-buildx-action](https://github.com/docker/setup-buildx-action) | `3` | `4` |
| [docker/build-push-action](https://github.com/docker/build-push-action) | `5` | `7` |
| [hashicorp/setup-terraform](https://github.com/hashicorp/setup-terraform) | `3` | `4` |
| [google-github-actions/auth](https://github.com/google-github-actions/auth) | `2` | `3` |
| [google-github-actions/setup-gcloud](https://github.com/google-github-actions/setup-gcloud) | `2` | `3` |
| [google-github-actions/deploy-cloudrun](https://github.com/google-github-actions/deploy-cloudrun) | `2` | `3` |
| [gitleaks/gitleaks-action](https://github.com/gitleaks/gitleaks-action) | `2` | `3` |


Updates `actions/checkout` from 4 to 6
- [Release notes](https://github.com/actions/checkout/releases)
- [Changelog](https://github.com/actions/checkout/blob/main/CHANGELOG.md)
- [Commits](actions/checkout@v4...v6)

Updates `actions/setup-node` from 4 to 6
- [Release notes](https://github.com/actions/setup-node/releases)
- [Commits](actions/setup-node@v4...v6)

Updates `docker/setup-buildx-action` from 3 to 4
- [Release notes](https://github.com/docker/setup-buildx-action/releases)
- [Commits](docker/setup-buildx-action@v3...v4)

Updates `docker/build-push-action` from 5 to 7
- [Release notes](https://github.com/docker/build-push-action/releases)
- [Commits](docker/build-push-action@v5...v7)

Updates `hashicorp/setup-terraform` from 3 to 4
- [Release notes](https://github.com/hashicorp/setup-terraform/releases)
- [Changelog](https://github.com/hashicorp/setup-terraform/blob/main/CHANGELOG.md)
- [Commits](hashicorp/setup-terraform@v3...v4)

Updates `google-github-actions/auth` from 2 to 3
- [Release notes](https://github.com/google-github-actions/auth/releases)
- [Changelog](https://github.com/google-github-actions/auth/blob/main/CHANGELOG.md)
- [Commits](google-github-actions/auth@v2...v3)

Updates `google-github-actions/setup-gcloud` from 2 to 3
- [Release notes](https://github.com/google-github-actions/setup-gcloud/releases)
- [Changelog](https://github.com/google-github-actions/setup-gcloud/blob/main/CHANGELOG.md)
- [Commits](google-github-actions/setup-gcloud@v2...v3)

Updates `google-github-actions/deploy-cloudrun` from 2 to 3
- [Release notes](https://github.com/google-github-actions/deploy-cloudrun/releases)
- [Changelog](https://github.com/google-github-actions/deploy-cloudrun/blob/main/CHANGELOG.md)
- [Commits](google-github-actions/deploy-cloudrun@v2...v3)

Updates `gitleaks/gitleaks-action` from 2 to 3
- [Release notes](https://github.com/gitleaks/gitleaks-action/releases)
- [Commits](gitleaks/gitleaks-action@v2...v3)

---
updated-dependencies:
- dependency-name: actions/checkout
  dependency-version: '6'
  dependency-type: direct:production
  update-type: version-update:semver-major
  dependency-group: github-actions
- dependency-name: actions/setup-node
  dependency-version: '6'
  dependency-type: direct:production
  update-type: version-update:semver-major
  dependency-group: github-actions
- dependency-name: docker/setup-buildx-action
  dependency-version: '4'
  dependency-type: direct:production
  update-type: version-update:semver-major
  dependency-group: github-actions
- dependency-name: docker/build-push-action
  dependency-version: '7'
  dependency-type: direct:production
  update-type: version-update:semver-major
  dependency-group: github-actions
- dependency-name: hashicorp/setup-terraform
  dependency-version: '4'
  dependency-type: direct:production
  update-type: version-update:semver-major
  dependency-group: github-actions
- dependency-name: google-github-actions/auth
  dependency-version: '3'
  dependency-type: direct:production
  update-type: version-update:semver-major
  dependency-group: github-actions
- dependency-name: google-github-actions/setup-gcloud
  dependency-version: '3'
  dependency-type: direct:production
  update-type: version-update:semver-major
  dependency-group: github-actions
- dependency-name: google-github-actions/deploy-cloudrun
  dependency-version: '3'
  dependency-type: direct:production
  update-type: version-update:semver-major
  dependency-group: github-actions
- dependency-name: gitleaks/gitleaks-action
  dependency-version: '3'
  dependency-type: direct:production
  update-type: version-update:semver-major
  dependency-group: github-actions
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
Bumps [tar](https://github.com/isaacs/node-tar) from 7.5.15 to 7.5.16.
- [Release notes](https://github.com/isaacs/node-tar/releases)
- [Changelog](https://github.com/isaacs/node-tar/blob/main/CHANGELOG.md)
- [Commits](isaacs/node-tar@v7.5.15...v7.5.16)

---
updated-dependencies:
- dependency-name: tar
  dependency-version: 7.5.16
  dependency-type: indirect
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
Bump typescript 5.9.3 -> 6.0.3 (major). Verified: astro check (0/0/0 across 95 files), eslint, prettier, and the production build all pass. tsconfig extends astro/tsconfigs/base; TS 6.0 strict-by-default surfaced no new errors. No source changes required.
…eme check (#26)

Resolve 5 CodeQL code-scanning alerts on main:

- ci.yml: add least-privilege top-level `permissions: contents: read`
  (4x "Workflow does not contain permissions"). Jobs only read the repo.
- permalinks.ts: make getPermalink's absolute/special-link detection
  case-insensitive and cover all script-capable pseudo-schemes
  (javascript:/data:/vbscript:) so the URL scheme check is complete and
  non-bypassable (High: "Incomplete URL scheme check"). Behavior unchanged.

Verified: astro check 0/0/0, eslint, prettier, docker build, lighthouse,
CodeQL all green.
Resolves Dependabot alert "vite: server.fs.deny bypass on Windows alternate
paths" (CVE-2026-53571), patched in vite 7.3.5 for the 7.x line. Within the
existing ^7 override; lockfile-only, esbuild unchanged. astro check / eslint /
prettier / docker build / lighthouse / CodeQL all green.
… triage (#28)

Additive docs for this session's security work: AGENTS.md stack TS 5.9->6.0 +
Security automation subsection; SECURITY.md least-privilege workflow permissions
note + new "Dependency security & vulnerability triage" policy (in-range fixes,
execution-path triage, dismiss "not used"). No content removed.
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants