Skip to content
Merged
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
4 changes: 2 additions & 2 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,7 @@
<br />
<a href="https://sentinel-command.com"><strong>► Try the live app</strong></a>
&nbsp;·&nbsp;
<a href="https://sentinel-command.com/docs">Documentation</a>
<a href="https://app.sentinel-command.com/docs">Documentation</a>
&nbsp;·&nbsp;
<a href="https://github.com/SourceBox-LLC/Sentinel-CameraNode">CameraNode</a>
</p>
Expand Down Expand Up @@ -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/) |
Expand Down
12 changes: 5 additions & 7 deletions SECURITY.md
Original file line number Diff line number Diff line change
@@ -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

Expand All @@ -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
Expand Down Expand Up @@ -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

Expand Down
22 changes: 18 additions & 4 deletions backend/app/api/well_known.py
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down
25 changes: 19 additions & 6 deletions backend/tests/test_security_txt.py
Original file line number Diff line number Diff line change
Expand Up @@ -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 ────────────────────────────────
Expand Down
4 changes: 2 additions & 2 deletions docs/LAUNCH_HANDOFF.md
Original file line number Diff line number Diff line change
Expand Up @@ -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.
Expand Down Expand 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:
```
Expand Down
2 changes: 1 addition & 1 deletion docs/runbooks/DISASTER_RECOVERY.md
Original file line number Diff line number Diff line change
Expand Up @@ -268,7 +268,7 @@ bash /app/scripts/restore_db.sh /data/backups/sentinel-<stamp>.dump
# 4. Start the app and verify BEFORE deleting the pre-restore dump.
exit
fly machine start <machine-id> -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
Expand Down
6 changes: 3 additions & 3 deletions docs/runbooks/ON_CALL.md
Original file line number Diff line number Diff line change
Expand Up @@ -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 |
Expand Down Expand Up @@ -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,
Expand Down