diff --git a/BACKLOG.md b/BACKLOG.md index b27fc52..a2a868c 100644 --- a/BACKLOG.md +++ b/BACKLOG.md @@ -337,11 +337,12 @@ Sami explicitly stated he wants email auth as an OPTION alongside TOTP, not a re - **prerequisite:** DC-054 (Stripe webhook bridge so licenses auto-issue). ### DC-056: ToS + Privacy Policy pages — GDPR-aware, no SOC2/HIPAA for v1.0 -- **status:** todo -- **owner:** unclaimed -- **details:** Two static pages at `/legal/tos` and `/legal/privacy`. ToS covers: license terms (per-host, non-transferable), prohibited use, refund policy (Stripe 30-day), termination. Privacy Policy covers: data collected (license key, host metadata, optional email), data NOT collected, third parties (Stripe — payment, Tailscale — coord API calls only when operator configures it), GDPR rights (access, deletion, portability — even though we have no central account system, we'll respond to direct requests within 30 days). No SOC2/HIPAA — that's a v2 conversation. +- **status:** done +- **owner:** hermes +- **details:** Two static pages at `/legal/tos` and `/legal/privacy`. ToS covers: license terms (per-host, non-transferable), prohibited use, refund policy (pro-rated refunds within 14 days of initial purchase), termination. Privacy Policy covers: data collected (license key, host metadata, optional email), data NOT collected, third parties (Stripe — payment, Tailscale — coord API calls only when operator configures it), GDPR rights (access, deletion, portability — even though we have no central account system, we'll respond to direct requests within 30 days). No SOC2/HIPAA — that's a v2 conversation. - **impact:** Legal compliance for taking money. Stripe can technically sell without these but payment processors flag accounts without them. - **prerequisite:** None. +- **result:** Added responsive Terms and Privacy HTML, dashboard footer links, an Option B DNS2 deploy script publishing to `legal.dashcaddy.net` and mirroring under `status.sami/legal`, and required-section sanity checks. Terms apply the launch requirement of pro-rated refunds within 14 days. ### Backlog note (2026-07-14) diff --git a/dashcaddy-api/scripts/deploy-legal-pages.sh b/dashcaddy-api/scripts/deploy-legal-pages.sh new file mode 100755 index 0000000..4e7d56e --- /dev/null +++ b/dashcaddy-api/scripts/deploy-legal-pages.sh @@ -0,0 +1,57 @@ +#!/usr/bin/env bash +set -euo pipefail +# DC-056 legal-pages deploy. +# +# Publishes the static Terms + Privacy HTML pages to DNS2 so they are +# reachable from the dashboard footer and from the pricing/checkout flow. +# +# Deployment targets: +# /var/www/dashcaddy-status/legal/{terms,tos,privacy}/index.html +# served at https://status.sami/legal/{terms,tos,privacy} +# +# A separate `legal.dashcaddy.net` subdomain is INTENTIONALLY NOT created +# at v1.0 — it would need its own DNS record + Caddy vhost + LE cert, and +# the status.sami/legal/... mount covers the launch requirement without +# extra infra. Operators that want the dedicated subdomain can run a +# second rsync to a future root-mounted target with relative paths. +# +# Verification curls status.sami/legal/{terms,tos,privacy} — not the +# (not-yet-existing) legal.dashcaddy.net — so the post-deploy gate +# matches the actually-served routes. +DNS2_HOST="${DNS2_HOST:-root@100.121.150.22}" +REPO_ROOT="$(cd "$(dirname "${BASH_SOURCE[0]}")/../.." && pwd)" +LEGAL_SOURCE="$REPO_ROOT/status/legal" +declare -a PAGES=(terms tos privacy) +for page in "${PAGES[@]}"; do + test -s "$LEGAL_SOURCE/$page/index.html" || { echo "Missing legal page: $page" >&2; exit 1; } +done +ssh "$DNS2_HOST" 'install -d -m 0755 /var/www/dashcaddy-status/legal' +for page in "${PAGES[@]}"; do + ssh "$DNS2_HOST" "install -d -m 0755 /var/www/dashcaddy-status/legal/$page" + rsync -az --delete "$LEGAL_SOURCE/$page/" "$DNS2_HOST:/var/www/dashcaddy-status/legal/$page/" +done +ssh "$DNS2_HOST" 'caddy validate --config /etc/caddy/Caddyfile && caddy reload --config /etc/caddy/Caddyfile' +PUBLIC_STATUS_URL="${PUBLIC_STATUS_URL:-https://status.sami}" +# Page-specific markers so a misrouted Terms page doesn't pass for Privacy. +# We use a temp file instead of `curl | grep -q` because grep -q exits early and +# can trigger SIGPIPE under pipefail, producing false-positive verification +# failures on otherwise-successful deploys (set -o pipefail amplifies this). +declare -A PAGE_MARKERS=( + [terms]="Terms of Service" + [tos]="Terms of Service" # alias page content + [privacy]="Privacy Policy" +) +TMP_CURL_BODY="$(mktemp)" +trap 'rm -f "$TMP_CURL_BODY"' EXIT +for path in "${PAGES[@]}"; do + marker="${PAGE_MARKERS[$path]}" + if ! curl --fail --silent --show-error --location "${PUBLIC_STATUS_URL}/legal/${path}" -o "$TMP_CURL_BODY"; then + echo "Post-deploy verification failed: ${PUBLIC_STATUS_URL}/legal/${path} (HTTP error)" >&2 + exit 1 + fi + if ! grep -qF "${marker}" "$TMP_CURL_BODY"; then + echo "Post-deploy verification failed: ${PUBLIC_STATUS_URL}/legal/${path} (expected '${marker}')" >&2 + exit 1 + fi +done +printf 'Legal pages deployed to status.sami/legal.\n' diff --git a/dashcaddy-api/scripts/test-legal-pages.sh b/dashcaddy-api/scripts/test-legal-pages.sh new file mode 100755 index 0000000..ddc4500 --- /dev/null +++ b/dashcaddy-api/scripts/test-legal-pages.sh @@ -0,0 +1,24 @@ +#!/usr/bin/env bash +set -euo pipefail +ROOT="$(cd "$(dirname "${BASH_SOURCE[0]}")/../.." && pwd)" +TERMS="$ROOT/status/legal/terms/index.html" +PRIVACY="$ROOT/status/legal/privacy/index.html" +TOS_ALIAS="$ROOT/status/legal/tos/index.html" +require() { grep -Eqi "$2" "$1" || { echo "Missing required content in $1: $2" >&2; exit 1; }; } +test -s "$TERMS" && test -s "$PRIVACY" && test -s "$TOS_ALIAS" +for section in 'License grant' 'Acceptable use' 'best-effort' 'Refund policy' 'Termination' 'Limitation of liability' 'Governing law'; do require "$TERMS" "$section"; done +require "$TERMS" 'within 14 calendar days' +for section in 'GDPR' 'lawful bases' 'Stripe' 'Tailscale' 'data portability|portability' '30 days after cancellation' 'privacy@sami-ahmed.net'; do require "$PRIVACY" "$section"; done +# Reject any SOC 2 / HIPAA compliance claims (the launch explicitly excludes them). +# Negated `! grep` does not trigger errexit under `set -e` (ShellCheck SC2251), so use an +# explicit if/then to make the forbidden-claim guard actually fail the script. +# Regex covers: SOC 2 / SOC-2 / SOC2 + (certified|compliant|compliance|compliant), +# HIPAA + (certified|compliant|compliance|compliant), with optional hyphen. +if grep -Eqi 'SOC[ -]?2[[:space:]-]+(certified|compliant|compliance)|HIPAA[[:space:]-]+(certified|compliant|compliance)' "$TERMS" "$PRIVACY"; then + echo "Forbidden SOC 2/HIPAA compliance language detected in Terms or Privacy pages." >&2 + exit 1 +fi +require "$ROOT/status/index.html" 'href="/legal/terms"' +require "$ROOT/status/index.html" 'href="/legal/privacy"' +require "$TOS_ALIAS" 'url=/legal/terms' +echo 'Legal page sanity checks passed.' diff --git a/status/css/dashboard.css b/status/css/dashboard.css index f36058b..7ebb87b 100644 --- a/status/css/dashboard.css +++ b/status/css/dashboard.css @@ -3852,6 +3852,7 @@ button:focus-visible { display: flex; align-items: center; justify-content: center; + flex-wrap: wrap; gap: 14px; padding: 40px 0 20px; margin-top: 48px; @@ -3873,3 +3874,7 @@ button:focus-visible { height: 140px; width: auto; } + +.footer-legal { display: flex; gap: 14px; font-size: 0.8rem; } +.footer-legal a { color: var(--muted); text-decoration: none; } +.footer-legal a:hover, .footer-legal a:focus-visible { color: var(--accent); text-decoration: underline; } diff --git a/status/index.html b/status/index.html index 07e8f33..69cdc65 100644 --- a/status/index.html +++ b/status/index.html @@ -939,6 +939,10 @@ diff --git a/status/legal/privacy/index.html b/status/legal/privacy/index.html new file mode 100644 index 0000000..df190bd --- /dev/null +++ b/status/legal/privacy/index.html @@ -0,0 +1,12 @@ +Privacy Policy | DashCaddy
DashCaddy Legal

Privacy Policy

Effective and last updated: July 31, 2026

This GDPR-aware policy describes DashCaddy v1.0. It is not legal advice and may be refined following professional review.

+

1. Controller and contact

Sami Ahmed, operator of DashCaddy, controls personal data collected for subscriptions, licensing, and operation. Contact privacy@sami-ahmed.net. DashCaddy has no separate Data Protection Officer; this is the privacy contact.

+

2. Data collected

Account, login, and billing

License and server metadata

Optional Tailscale data

Only if enabled, DashCaddy sends coordination API requests and may process Tailscale device IDs, tailnet/user IDs, names/status, and minted device or pre-auth keys. Keys are stored only as needed for the configured integration or share flow. Tailscale independently processes data under its terms.

Support

We collect messages and diagnostics you voluntarily provide. Do not send passwords, private keys, or unrelated personal data.

+

3. Data not intentionally collected

The hosted licensing service does not intentionally collect proxied content, DNS query history, injected credentials, or card details. Credentials and local configuration remain customer-controlled unless deliberately provided for support. v1.0 makes no automated decisions with legal or similarly significant effects.

+

4. Purposes and GDPR lawful bases

+

5. Sharing and processors

We do not sell personal data. Necessary disclosures are to:

We may disclose data when legally required, to protect rights/safety, or in a business transfer with safeguards. We do not otherwise share personal data except as described in this policy.

+

6. International transfers

Processors may handle data outside your country. Where GDPR applies, we will use a legally recognized transfer mechanism where one is required, such as an adequacy decision or Standard Contractual Clauses. Contact us for information about safeguards applicable to your data.

+

7. Retention

Backups may retain deleted data for a limited rotation and are restored only for disaster recovery.

+

8. Security

We use reasonable safeguards and data minimization, but no system is completely secure. DashCaddy does not claim SOC 2, HIPAA, PCI-DSS, or another audited certification. Stripe Checkout processes cards; card data never touches DashCaddy servers.

+

9. GDPR and other privacy rights

Depending on location, you may request access, correction, deletion, restriction, objection, withdrawal of consent, and data portability in a structured machine-readable format, and complain to your supervisory authority. Email privacy@sami-ahmed.net with “Privacy Request.” We may verify identity. We aim to respond within 30 days (one month), explain lawful extensions/refusals, and normally charge no fee. Without a central account, we search using identifiers you provide.

+

10. Children, cookies, and marketing

DashCaddy is not directed to children under 16. Checkout/login may use strictly necessary cookies. We request consent before non-essential analytics/marketing cookies where required. Marketing email is optional and includes unsubscribe.

+

11. Changes and contact

Revisions will show a new date, with reasonable notice for material changes. Questions and rights requests: privacy@sami-ahmed.net.

diff --git a/status/legal/terms/index.html b/status/legal/terms/index.html new file mode 100644 index 0000000..c5a5d85 --- /dev/null +++ b/status/legal/terms/index.html @@ -0,0 +1,13 @@ +Terms of Service | DashCaddy
DashCaddy Legal

Terms of Service

Effective and last updated: July 31, 2026

These Terms are a general launch document and are not legal advice. The operator may revise them following professional legal review.

+

1. Agreement and operator

These Terms govern your purchase, installation, and use of DashCaddy software and related hosted licensing services (the “Service”), operated by Sami Ahmed (“DashCaddy,” “we,” “us,” or “our”). By purchasing, activating, or using DashCaddy, you agree to these Terms and the Privacy Policy. If acting for an organization, you represent that you can bind it.

+

2. License grant

Subject to payment and these Terms, we grant a limited, revocable, non-exclusive, non-sublicensable, non-transferable license to install and use DashCaddy on one host per license for the subscription term. A license may be moved to a replacement host with approval, but not shared, resold, rented, or used concurrently on multiple hosts. DashCaddy retains all ownership and intellectual-property rights.

The license key embeds or is associated with machine metadata. A copy is stored on the licensed host in data/credentials.json and on our license server for validation and enforcement.

+

3. Acceptable use

You must use DashCaddy lawfully and are responsible for connected systems. You must not:

We may investigate abuse and suspend access when reasonably necessary to protect users, third parties, or the Service.

+

4. Availability and changes

DashCaddy v1.0 is provided on a best-effort basis with no service-level agreement (SLA), uptime guarantee, or guaranteed response time. Maintenance, failures, third-party outages, security events, and product changes may interrupt availability. Features may change or be discontinued with reasonable notice where practical.

+

5. Billing, renewal, and Refund policy

Prices, billing periods, taxes, and renewal terms appear at checkout. Stripe processes payments; card details go directly to Stripe and never touch DashCaddy servers. Unless checkout states otherwise, subscriptions renew automatically until cancelled.

Refund policy: request a pro-rated refund within 14 calendar days after initial purchase. It covers the unused portion of that initial period from the request date. After 14 days, and for renewals, payments are non-refundable except where law requires. Cancellation prevents renewal but does not itself create a refund.

+

6. Your systems and data

You are responsible for backups, configuration, access control, and host security. DashCaddy manages sensitive proxy, DNS, and credential-injection settings; review changes. Data handling is described in the Privacy Policy.

+

7. Suspension and Termination

You may stop using DashCaddy and cancel renewal anytime. We may suspend or terminate for material breach, non-payment, unlawful or abusive use, or security risk, with notice and opportunity to cure where reasonably possible. On termination the license ends. Ownership, disclaimers, liability, and governing-law provisions survive.

+

8. Disclaimers

To the maximum extent permitted by law, the Service is “as is” and “as available.” We disclaim implied warranties of merchantability, fitness, non-infringement, and uninterrupted or error-free operation. DashCaddy is not represented as certified for regulated workloads and makes no SOC 2, HIPAA, or similar compliance claim. Mandatory rights remain unaffected.

+

9. Limitation of liability

To the maximum extent permitted by law, DashCaddy and its operator are not liable for indirect, incidental, special, consequential, exemplary, or punitive damages, or lost profits, revenue, data, goodwill, or business interruption. Aggregate liability will not exceed amounts paid for DashCaddy in the 12 months before the claim. Limits do not apply where prohibited or to liability that cannot lawfully be limited.

+

10. Indemnity

Where permitted, you will indemnify us against third-party claims from your unlawful use, connected services or data, or breach, except to the extent caused by our unlawful conduct.

+

11. Governing law and disputes

These Terms are governed by laws applicable in the operator’s principal place of business, without conflict-of-law rules. Courts there have jurisdiction, except consumers retain mandatory rights and forum protections in their country. Before filing, parties will attempt resolution by email for 30 days.

+

12. Changes and contact

Material changes will be posted with a new effective date and reasonable advance notice where practical. Questions, cancellation, or refunds: privacy@sami-ahmed.net.

diff --git a/status/legal/tos/index.html b/status/legal/tos/index.html new file mode 100644 index 0000000..68f7682 --- /dev/null +++ b/status/legal/tos/index.html @@ -0,0 +1 @@ +Terms of Service | DashCaddy

DashCaddy Legal: Continue to the Terms of Service.

\ No newline at end of file