diff --git a/README.md b/README.md
index b300afd..d8727a5 100644
--- a/README.md
+++ b/README.md
@@ -8,7 +8,7 @@
► Try the live app
·
- Documentation
+ Documentation
·
CameraNode
@@ -93,7 +93,7 @@ CameraNode captures and encodes video on your network, then pushes it **outbound
| If you want to… | Go to |
|-----------------|-------|
-| **Use Sentinel** — set up cameras, recording, notifications, integrations | The in-app [Documentation](https://sentinel-command.com/docs) |
+| **Use Sentinel** — set up cameras, recording, notifications, integrations | The in-app [Documentation](https://app.sentinel-command.com/docs) |
| **See how the whole system fits together** — every repo, every deployed service, the paths between them | [docs/ARCHITECTURE.md](docs/ARCHITECTURE.md) |
| **Understand the code** — architecture, API, data models, configuration | [AGENTS.md](AGENTS.md) |
| **Operate it** — decision records, runbooks, legal templates | [docs/](docs/) |
diff --git a/SECURITY.md b/SECURITY.md
index a49878c..301af40 100644
--- a/SECURITY.md
+++ b/SECURITY.md
@@ -1,12 +1,10 @@
# Security Policy
-SourceBox Sentry is a security-focused application and we take vulnerabilities seriously.
+Sentinel by SourceBox is a security-focused product and we take vulnerabilities seriously.
-The full policy — scope, response timelines, safe-harbour terms, and the standard machine-readable [`security.txt`](https://app.sentinel-command.com/.well-known/security.txt) — lives at:
+**This file is the policy.** Scope, response timelines, and safe-harbour terms are all below, and the machine-readable [`security.txt`](https://app.sentinel-command.com/.well-known/security.txt) points here.
-**https://sentinel-command.com/security#vulnerability-disclosure**
-
-This file is the GitHub-standard summary; the deployed page above is canonical when the two disagree.
+It previously deferred to a page at `sentinel-command.com/security` and called that page canonical. That page does not exist and never has — so the canonical policy was a 404, and `security.txt` sent researchers there. Corrected 2026-09-09. If a hosted policy page is published later, point `security.txt` at it and say so here.
## Reporting a vulnerability
@@ -24,7 +22,7 @@ Two channels, use whichever you prefer:
- Description of the issue and its impact
- Steps to reproduce (URLs, payloads, screenshots)
-- Version / commit you tested against — surfaced by `GET /api/health`
+- Version you tested against — `GET /api/health` returns it (e.g. `{"version": "2.1.2"}`)
- Optional suggested fix or mitigation
### Response timeline
@@ -67,7 +65,7 @@ If you make a good-faith effort to comply with this policy:
## Bug bounty
-There is no monetary bug bounty today — SourceBox Sentry is pre-PMF. We're upfront about that so you can decide whether to invest the time. If we ever launch one, prior reporters will be at the front of the line.
+There is no monetary bug bounty today — Sentinel is pre-PMF. We're upfront about that so you can decide whether to invest the time. If we ever launch one, prior reporters will be at the front of the line.
## Security updates
diff --git a/backend/app/api/well_known.py b/backend/app/api/well_known.py
index 93e04f3..de028e9 100644
--- a/backend/app/api/well_known.py
+++ b/backend/app/api/well_known.py
@@ -73,10 +73,24 @@ def _build_security_txt() -> str:
datetime.now(tz=UTC) + timedelta(days=_EXPIRY_DAYS)
).strftime("%Y-%m-%dT%H:%M:%SZ")
- # The security policy page now lives on the standalone website
- # (sentinel-command.com), not this app's frontend. The Policy: URL
- # must point there so researchers land on the actual disclosure page.
- policy_url = "https://sentinel-command.com/security#vulnerability-disclosure"
+ # Policy: MUST resolve. This is machine-read (RFC 9116) by scanners
+ # and by researchers deciding whether they are covered by safe
+ # harbour before they touch anything — a 404 here means no published
+ # scope and no published authorisation.
+ #
+ # It pointed at https://sentinel-command.com/security#vulnerability-disclosure
+ # on the assumption the page had moved to the standalone site. It had
+ # not: that URL, and every plausible variant of it, returns 404
+ # (checked 2026-09-09). SECURITY.md in this repository is the only
+ # place the full policy — scope, timelines, safe harbour — actually
+ # exists, so it is what we point at.
+ #
+ # If the standalone site ever publishes the page, move this back and
+ # update the matching assertion in tests/test_security_txt.py.
+ policy_url = (
+ "https://github.com/SourceBox-LLC/Sentinel-Command"
+ "/blob/master/SECURITY.md"
+ )
# Order follows RFC 9116 §2.5 examples for readability. Comments
# at the top help human readers; scanners ignore them. Contact
diff --git a/backend/tests/test_security_txt.py b/backend/tests/test_security_txt.py
index 22d63e3..4758eb8 100644
--- a/backend/tests/test_security_txt.py
+++ b/backend/tests/test_security_txt.py
@@ -168,16 +168,29 @@ def test_security_txt_has_canonical_and_policy(unauthenticated_client):
assert any(line.startswith("Policy:") for line in body.splitlines())
-def test_security_txt_policy_anchor_matches_security_page(unauthenticated_client):
- """Pin the anchor — the security page on sentinel-command.com renders an
- ``id="vulnerability-disclosure"`` section that this URL deep-links to.
- A regression that renames the section would leave every scanner+researcher
- landing on the page header instead of the policy text."""
+def test_security_txt_policy_points_at_a_document_that_exists(
+ unauthenticated_client,
+):
+ """Pin the Policy target to the document that actually holds the policy.
+
+ This previously asserted ``sentinel-command.com/security#vulnerability-
+ disclosure``, and its docstring claimed that page rendered an
+ ``id="vulnerability-disclosure"`` section. It never did — that URL and
+ every plausible variant returned 404 (checked 2026-09-09), so the test
+ was pinning a fiction while researchers following RFC 9116 found no
+ scope and no safe-harbour terms.
+
+ Asserting *presence* of a Policy line, as the test above does, is not
+ enough: a well-formed pointer at nothing still passes. This pins the
+ specific target so moving it is a deliberate act with a test to update.
+ """
body = unauthenticated_client.get("/.well-known/security.txt").text
policy_line = [
line for line in body.splitlines() if line.startswith("Policy:")
][0]
- assert "sentinel-command.com/security#vulnerability-disclosure" in policy_line
+ assert "github.com/SourceBox-LLC/Sentinel-Command/blob/master/SECURITY.md" in (
+ policy_line
+ )
# ── Public + cacheable for scanners ────────────────────────────────
diff --git a/docs/LAUNCH_HANDOFF.md b/docs/LAUNCH_HANDOFF.md
index 6e52e71..d3aa594 100644
--- a/docs/LAUNCH_HANDOFF.md
+++ b/docs/LAUNCH_HANDOFF.md
@@ -47,7 +47,7 @@ charges don't post, the dev-mode badge shows in the UI, and the
-a sentinel-command
```
4. Verify the Clerk webhook endpoint
- `https://sentinel-command.com/api/webhooks/clerk` is
+ `https://app.sentinel-command.com/api/webhooks/clerk` is
registered in the production Clerk app and signing secret is set
(`CLERK_WEBHOOK_SECRET`). Test by upgrading a test org and
confirming the `Setting(org_plan="pro")` row shows up.
@@ -96,7 +96,7 @@ the answer for those.
the pre-rename brand and is *not* a verified sending domain — setting
`EMAIL_FROM_ADDRESS` to it would have failed every send.
3. Configure a webhook in Resend → endpoint
- `https://sentinel-command.com/api/webhooks/resend`. Copy the
+ `https://app.sentinel-command.com/api/webhooks/resend`. Copy the
signing secret (starts with `whsec_`).
4. Set the four Fly secrets:
```
diff --git a/docs/runbooks/DISASTER_RECOVERY.md b/docs/runbooks/DISASTER_RECOVERY.md
index 620c9e6..33341c5 100644
--- a/docs/runbooks/DISASTER_RECOVERY.md
+++ b/docs/runbooks/DISASTER_RECOVERY.md
@@ -268,7 +268,7 @@ bash /app/scripts/restore_db.sh /data/backups/sentinel-.dump
# 4. Start the app and verify BEFORE deleting the pre-restore dump.
exit
fly machine start -a sentinel-command
-curl -fsS https://sentinel-command.com/api/health/ready
+curl -fsS https://app.sentinel-command.com/api/health/ready
```
Then sanity-check in the dashboard: an org loads, cameras list, a known
diff --git a/docs/runbooks/ON_CALL.md b/docs/runbooks/ON_CALL.md
index d505246..f3de7e4 100644
--- a/docs/runbooks/ON_CALL.md
+++ b/docs/runbooks/ON_CALL.md
@@ -39,8 +39,8 @@ That last row matters: this runbook is for "the service is broken." If the *data
| Tool / link | Why |
|---|---|
-| https://sentinel-command.com/api/health | Liveness — is the process up? |
-| https://sentinel-command.com/api/health/detailed | DB ping latency, cache + queue depths |
+| https://app.sentinel-command.com/api/health | Liveness — is the process up? |
+| https://app.sentinel-command.com/api/health/detailed | DB ping latency, cache + queue depths |
| `fly logs -a sentinel-command` | Application stderr/stdout |
| `fly status -a sentinel-command` | Machine health + last deploy |
| `fly ssh console -a sentinel-command` | Shell into the live machine |
@@ -243,7 +243,7 @@ self-hosted section applies to them, not this scenario.
Check the "events" timeline for recent restarts.
2. **`fly status -a sentinel-postgres`** — is the *database* up? This is
the check that did not exist before the migration.
-3. `curl https://sentinel-command.com/api/health/detailed` —
+3. `curl https://app.sentinel-command.com/api/health/detailed` —
look at:
- `checks.database.status` and `latency_ms`
- `checks.disk.percent_used` and `checks.disk.status` (segments now,