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,