Skip to content
Open
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
17 changes: 10 additions & 7 deletions .github/workflows/deploy-cloudflare-bridge.yml
Original file line number Diff line number Diff line change
Expand Up @@ -78,7 +78,13 @@ jobs:
tools \
"$BRIDGE_DIRECTORY/"

printf '/*\n X-Robots-Tag: noindex, nofollow\n' > "$BRIDGE_DIRECTORY/_headers"
printf '%s\n' \
'https://secure-tools-web-bridge.pages.dev/*' \
' X-Robots-Tag: noindex, nofollow' \
'' \
'https://:version.secure-tools-web-bridge.pages.dev/*' \
' X-Robots-Tag: noindex, nofollow' \
> "$BRIDGE_DIRECTORY/_headers"

[[ ! -e "$BRIDGE_DIRECTORY/CNAME" ]]
[[ ! -e "$BRIDGE_DIRECTORY/_redirects" ]]
Expand Down Expand Up @@ -133,13 +139,10 @@ jobs:
- name: Validate deployed bridge
env:
DEPLOYMENT_URL: ${{ steps.deploy.outputs.deployment-url }}
run: node tests/deployment-smoke.mjs "$DEPLOYMENT_URL"
run: node tests/deployment-smoke.mjs "$DEPLOYMENT_URL" noindex

- name: Validate stable bridge alias
run: node tests/deployment-smoke.mjs https://secure-tools-web-bridge.pages.dev
run: node tests/deployment-smoke.mjs https://secure-tools-web-bridge.pages.dev noindex

- name: Validate tools custom domain
run: node tests/deployment-smoke.mjs https://tools.securetools.app

- name: Validate existing GitHub Pages production
run: node tests/deployment-smoke.mjs https://securetools.app production
run: node tests/deployment-smoke.mjs https://tools.securetools.app indexable
8 changes: 4 additions & 4 deletions about/index.html
Original file line number Diff line number Diff line change
Expand Up @@ -9,21 +9,21 @@
<link rel="apple-touch-icon" sizes="180x180" href="/assets/icons/apple-touch-icon.png">
<meta http-equiv="Content-Security-Policy" content="default-src 'self'; script-src 'self'; style-src 'self'; img-src 'self' blob: data:; connect-src 'none'; object-src 'none'; frame-src 'none'; base-uri 'self'; form-action 'self'">
<meta name="description" content="Why Secure Tools builds open, local-first file utilities that can be inspected instead of simply trusted.">
<link rel="canonical" href="https://securetools.app/about/">
<link rel="canonical" href="https://tools.securetools.app/about/">
<meta property="og:site_name" content="Secure Tools">
<meta property="og:url" content="https://securetools.app/about/">
<meta property="og:url" content="https://tools.securetools.app/about/">
<meta name="theme-color" content="#f7f7f5">
<meta property="og:type" content="website">
<meta property="og:title" content="About — Secure Tools">
<meta property="og:description" content="Why Secure Tools builds open, local-first file utilities that can be inspected instead of simply trusted.">
<meta property="og:image" content="https://securetools.app/assets/images/og-image.png">
<meta property="og:image" content="https://tools.securetools.app/assets/images/og-image.png">
<meta property="og:image:width" content="1200">
<meta property="og:image:height" content="630">
<meta property="og:image:alt" content="Secure Tools — Local-first browser utilities for PDF, Image, and Privacy">
<meta name="twitter:card" content="summary_large_image">
<meta name="twitter:title" content="About — Secure Tools">
<meta name="twitter:description" content="Why Secure Tools builds open, local-first file utilities that can be inspected instead of simply trusted.">
<meta name="twitter:image" content="https://securetools.app/assets/images/og-image.png">
<meta name="twitter:image" content="https://tools.securetools.app/assets/images/og-image.png">
<meta name="twitter:image:alt" content="Secure Tools — Local-first browser utilities for PDF, Image, and Privacy">
<title>About — Secure Tools</title>
<script src="../js/theme-bootstrap.js"></script>
Expand Down
2 changes: 1 addition & 1 deletion docs/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,7 @@ The root [README](../README.md) introduces Secure Tools. This directory owns det
| [Dependencies](./dependencies.md) | Production runtime inventory, versions, vendoring, licenses, and integrity ownership |
| [Tool status](./tool-status.md) | Production and planned surfaces, supported formats, behavior, and resource boundaries |
| [Search discovery and metadata](./seo.md) | Canonical routes, crawler files, metadata policy, maintenance, and submission steps |
| [Cloudflare Pages migration bridge](./cloudflare-pages-bridge.md) | H3.2 Direct Upload provenance plus H3.3 parallel-domain activation, validation, SEO isolation, and rollback contracts |
| [Cloudflare Pages migration bridge](./cloudflare-pages-bridge.md) | H3.2/H3.3 provenance plus the prepared H3.5 hostname-specific indexing, activation, validation, and rollback contracts |
| [Image Metadata privacy](./image-metadata-privacy.md) | Format-specific inspection, cleaning, preservation, and verification semantics |
| [UX consistency audit](./ux-consistency-audit.md) | Shared interaction, accessibility, responsive, theme, and historical browser-QA findings |
| [i18n copy review](./i18n-copy-review.md) | Localization coverage and editorial review record |
Expand Down
125 changes: 46 additions & 79 deletions docs/cloudflare-pages-bridge.md
Original file line number Diff line number Diff line change
@@ -1,124 +1,91 @@
# Cloudflare Pages migration bridge

Status: H3.2 is deployed and validated. H3.3 preparation targets a parallel `tools.securetools.app` custom domain, but the preparation pull request must remain unmerged until the activation gate below is satisfied. H3.3 does not authorize an apex migration.
Status: H3.3 is complete. `tools.securetools.app` is active on the Direct Upload Pages project and remains non-indexable under the live H3.3 deployment. H3.4A prepares, but does not activate, the later H3.5 indexing and canonical cutover.

## Deployment identity

| Item | Value |
| --- | --- |
| Cloudflare Pages project | `secure-tools-web-bridge` |
| Production branch | `main` |
| Custom domain | `https://tools.securetools.app` |
| Stable validation URL | `https://secure-tools-web-bridge.pages.dev` |
| Deployment mechanism | GitHub Actions Direct Upload through Wrangler |
| Current custom domains | None before H3.3 activation |
| H3.3 target custom domain | `https://tools.securetools.app` |

The stable Pages hostname is a validated application endpoint and must remain available throughout H3.3 as the rollback and comparison endpoint. `securetools.app` and `www.securetools.app` remain outside the Pages project.

The H3.3 domain contract is:
The H3.3 architecture remains live while the H3.4A pull request is open:

```text
securetools.app
→ existing GitHub Pages Web Utilities production

tools.securetools.app
→ Cloudflare Pages project secure-tools-web-bridge
→ Cloudflare Pages parallel endpoint
→ X-Robots-Tag: noindex, nofollow
→ canonical securetools.app

secure-tools-web-bridge.pages.dev
→ retained validation and rollback endpoint

securetools.app
→ unchanged GitHub Pages production deployment
→ validation and rollback endpoint
→ X-Robots-Tag: noindex, nofollow
```

## Provenance and isolation
No H3.4A feature-branch push deploys to Pages because the workflow deploys only on `main` or explicit manual dispatch.

The bridge workflow is `.github/workflows/deploy-cloudflare-bridge.yml`:
## Provenance and artifact isolation

```text
Secure_Tools main
→ GitHub Actions validation
→ temporary bridge artifact
→ temporary bridge artifact without repository CNAME
→ GitHub Deployment
→ Cloudflare Pages Direct Upload
```

The existing GitHub Pages production path remains independent:
The required `CLOUDFLARE_API_TOKEN` and `CLOUDFLARE_ACCOUNT_ID` values remain GitHub Actions secrets. The workflow validates the intended project, `main` production branch, custom domain, Direct Upload source, and analytics-disabled state without logging secret values.

```text
Secure_Tools main repository root + CNAME
→ GitHub Pages
→ https://securetools.app
```
The source repository has no `_headers`, `_redirects`, `_worker.js`, or `functions/` deployment behavior. CI creates the Cloudflare-only `_headers` file inside the runner's temporary artifact.

The workflow copies only application files to `${{ runner.temp }}/secure-tools-web-bridge`. It excludes the repository `CNAME` and injects this bridge-only file into that temporary directory:
## Prepared H3.5 indexing split

Cloudflare Pages `_headers` supports absolute hostname patterns. H3.4A therefore prepares:

```text
/*
https://secure-tools-web-bridge.pages.dev/*
X-Robots-Tag: noindex, nofollow
```

The source artifact therefore retains its current canonical, Open Graph, sitemap, robots, and GitHub Pages behavior. The Pages bridge and H3.3 custom domain remain accessible for QA while their Cloudflare static responses instruct crawlers not to index or follow them.

## Credentials and project contract

The required GitHub Actions secret names are:

- `CLOUDFLARE_API_TOKEN`
- `CLOUDFLARE_ACCOUNT_ID`

Their values remain secret and must never enter source, logs, pull-request text, or untrusted workflows. The token remains limited to the intended Cloudflare account and required Pages permissions.

After H3.3 activation, every deployment queries the authenticated Pages project state and requires the expected name, `main` production branch, stable Pages subdomain, exactly the `tools.securetools.app` custom domain, no Git integration, and no Cloudflare Web Analytics configuration.

## H3.3 manual browser QA gate

Do not attach the custom domain until a human has completed all of these checks against `https://secure-tools-web-bridge.pages.dev`:

- one representative PDF operation;
- one representative image operation;
- one representative metadata operation; and
- browser Network-panel confirmation that selected user files are not uploaded.

Static and contract tests do not satisfy this manual gate. Record the human result on the H3.3 issue before activation.

## Custom-domain activation

After the manual gate has explicit evidence, use this exact order:
https://:version.secure-tools-web-bridge.pages.dev/*
X-Robots-Tag: noindex, nofollow
```

1. In Cloudflare, open **Workers & Pages → `secure-tools-web-bridge` → Custom domains → Set up a domain**.
2. Enter `tools.securetools.app`, continue, and activate it through the Pages project.
3. Because `securetools.app` is already a Cloudflare-managed zone, allow Cloudflare Pages to create and manage the associated `tools` DNS record.
4. Wait until the Pages custom domain reports active and its TLS certificate is valid.
5. Validate HTTPS, all 19 routes, representative assets, exact `X-Robots-Tag: noindex, nofollow`, and the existing production SEO inventory with `node tests/deployment-smoke.mjs https://tools.securetools.app`.
6. Revalidate `secure-tools-web-bridge.pages.dev` and `securetools.app`, then merge the prepared pull request. The resulting `main` deployment continuously validates the immutable deployment, stable Pages alias, custom domain, and existing production isolation.
Expected H3.5 behavior after coordinated merge and deployment:

Do not manually create a Pages-target CNAME before associating the hostname with the Pages project. Do not change apex or `www` records. Cloudflare may represent the managed record internally; the invariant is that `tools.securetools.app` resolves through `secure-tools-web-bridge` while apex records remain unchanged.
```text
tools.securetools.app
→ indexable
→ canonical tools.securetools.app

## Continuous deployment validation
secure-tools-web-bridge.pages.dev
*.secure-tools-web-bridge.pages.dev
→ X-Robots-Tag: noindex, nofollow
→ canonical tools.securetools.app
```

Every `main` push and optional manual dispatch performs:
The static hostname rules are the smallest transparent solution: they preserve `pages.dev` duplicate-host protection without a Worker, Pages Function, redirect, or external zone rule. The workflow verifies all 19 routes, seven representative assets, redirect absence, indexing-header mode, and tools-host canonical/`og:url` values on the immutable URL, stable alias, and custom domain.

1. the complete repository test suite;
2. an explicit secret-name prerequisite check;
3. creation of a temporary static artifact without `CNAME`, `_redirects`, Workers, or Pages Functions;
4. injection of the bridge-only `_headers` rule;
5. authenticated verification of project identity, production branch, custom-domain contract, Direct Upload mode, and analytics isolation;
6. Direct Upload with source SHA and branch provenance;
7. the shared deployment smoke contract against the immutable deployment URL, stable Pages alias, H3.3 custom domain, and existing GitHub Pages production;
8. HTTP 200 without redirects for all 19 H3.1 routes on each endpoint;
9. representative CSS, JavaScript, icon, and vendored-library checks;
10. exact `X-Robots-Tag: noindex, nofollow` on Pages routes and assets, plus confirmation that production does not inherit that bridge-only header; and
11. canonical and `og:url` values that intentionally continue to identify `https://securetools.app/...`, while the legacy `/tools/image-to-pdf/` alias retains its existing source-level noindex and stays outside the canonical inventory.
## H3.5 activation gate

Existing static tests continue to cover representative PDF, image, metadata, privacy, local-processing, dependency-integrity, CSP, and network invariants.
The H3.4A pull request must not merge independently. H3.5 must coordinate its merge with the Hub apex cutover, legacy path redirects, Hub canonical activation, retirement of the old Secure_Tools GitHub Pages apex path, and Web Utilities indexing activation.

During H3.3, both Pages hostnames remain non-indexable while canonical and `og:url` metadata continue to identify the existing production host. `tools.securetools.app` must not be added to production sitemap files. No Search Console operation belongs to H3.3.
After deployment, require all of the following before treating the SEO cutover as complete:

## H3.3 rollback
- `tools.securetools.app` returns HTTP 200 for all 19 routes and has no response-level noindex header;
- stable and immutable `pages.dev` endpoints return exact `X-Robots-Tag: noindex, nofollow`;
- all canonical-bearing pages and `og:url` values identify the tools host;
- social image URLs, sitemap, and robots sitemap declaration use the tools host;
- the legacy alias retains page-level noindex and remains outside the sitemap; and
- the Hub apex, redirect, and Search Console steps are validated by their owning H3.5 work.

If `tools.securetools.app` is unhealthy after activation:
## Rollback

1. remove the `tools` DNS record associated with Pages if Cloudflare does not remove it as part of detachment;
2. detach `tools.securetools.app` from the Pages project's Custom domains configuration;
3. confirm `secure-tools-web-bridge.pages.dev` remains healthy and no custom-domain record remains; and
4. leave `securetools.app`, its root `CNAME`, GitHub Pages, apex and `www` DNS, Search Console, redirects, sitemap, and production metadata untouched.
If the H3.5 Web Utilities SEO deployment is unhealthy, use the immutable or stable Pages endpoint for diagnosis while keeping those hostnames non-indexable. Coordinate rollback with the Hub/apex migration owner; do not independently create DNS records, change the old apex, or publish conflicting canonical and sitemap states.

The bridge is additive, so rollback never requires a change to the existing production site. H3.3 does not move the apex, create legacy redirects, begin H3.4, or change application behavior.
H3.4A itself changes no DNS, Cloudflare custom domain, GitHub Pages configuration, Search Console property, redirect, Worker, Pages Function, or application behavior.
Loading
Loading