From d3e22af5a3a332de049e2a7cf494b31039430eb1 Mon Sep 17 00:00:00 2001 From: thetechnologist1911 Date: Tue, 15 Sep 2026 21:55:43 -0400 Subject: [PATCH 1/3] docs: add SECURITY.md The repo is public, security-positioned, and aimed at people who run govulncheck on what they install. Two advisories currently show up against the Docker client library, and until now there was no stated position on them, so the first person to find them would have had nothing to read. Documents the disclosure channel (GitHub private vulnerability reporting, now enabled), supported versions, the guarantees the agent actually enforces in code, and the assessment of the two Moby advisories with a concrete trigger for revisiting them. Every guarantee listed was checked against the source rather than copied from design notes: the restic subcommand type is closed and unexported, the sandbox teardown is deferred so it survives panics, containers are force-removed with volumes, and scrub/redaction runs before strings are logged or transmitted. --- SECURITY.md | 98 +++++++++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 98 insertions(+) create mode 100644 SECURITY.md diff --git a/SECURITY.md b/SECURITY.md new file mode 100644 index 0000000..7127656 --- /dev/null +++ b/SECURITY.md @@ -0,0 +1,98 @@ +# Security + +Restorable runs against your backup repository and your restored data. That +puts it in a position of trust, so this document states what it guarantees, +what it does not, and how to report a problem. + +## Reporting a vulnerability + +Use GitHub's private vulnerability reporting: +[**Report a vulnerability**](https://github.com/restorable-dev/restorable/security/advisories/new). + +That opens a private advisory visible only to the maintainers. Please do not +open a public issue for anything exploitable. + +You can expect an acknowledgement within a week. This is a small project, so +a fix may take longer than that, and the advisory will say where things stand +rather than going quiet. + +If you would rather not use GitHub, open a public issue asking for a contact +address and leave out the details. + +## Supported versions + +| Version | Supported | +| ------- | --------- | +| 0.1.x | Yes | +| < 0.1.1 | No | + +Releases before v0.1.1 were built with a Go toolchain carrying known standard +library vulnerabilities and have been removed from the releases page. There is +no auto-update mechanism by design, so upgrading is a manual reinstall. + +## What the agent guarantees + +These are enforced in code, not by convention, and each has tests: + +- **Read-only against your repository.** The restic wrapper exposes a closed, + unexported subcommand type whose only values are `version`, `snapshots`, + `ls`, `restore`, `check`, `dump` and `cat`. There is no code path that + reaches `forget`, `prune` or `backup`; it will not compile. +- **Sandboxes are destroyed.** Teardown is deferred, so it runs on every path + out of a verification run, including a panic in recipe code. Containers are + force-removed along with their volumes on the same guarantee. +- **Disk pre-flight.** Free space is checked before a restore begins, so an + oversized snapshot fails with a clear error instead of filling the disk. +- **Backup contents stay on your machine.** When connected to the hosted + control plane, only pass/fail status, timings, snapshot IDs, recipe names + and error strings are transmitted. File contents are never uploaded. +- **Credentials and paths are scrubbed.** Strings are passed through a scrub + and redaction step before they are logged or transmitted, including error + messages from cleanup failures. Repository URLs containing credentials are + stripped before they can enter a report. +- **Outbound only.** The agent polls over HTTPS. It opens no inbound ports. + +Cloud connectivity is optional. `restorable test --config agent.yaml` runs the +full local loop with no account. + +## Known advisories + +`govulncheck` currently reports two advisories against the agent, both through +the Docker client library: + +| Advisory | CVE | Severity | +| --- | --- | --- | +| GO-2026-4887 | CVE-2026-34040 | High | +| GO-2026-4883 | CVE-2026-33997 | Medium | + +**Assessment: not exploitable through this agent, and not currently fixable.** + +Both describe daemon-side behavior, an AuthZ plugin bypass on oversized request +bodies and an off-by-one in plugin privilege validation. The agent is a client. +It creates, execs into, and removes containers over the local Docker socket. It +does not install plugins and does not use an AuthZ plugin, so it does not drive +the affected code paths. Your exposure to these is a function of your Docker +daemon version, not of this agent; update Docker to address them. + +They are also not fixable here today. The patches exist only in +`github.com/moby/moby/v2`, which has published nothing but beta releases. Moving +this project onto a beta dependency to silence two advisories it does not +exercise would trade a real risk for a cosmetic one. + +**Revisit trigger:** when `github.com/moby/moby/v2` publishes a stable release, +the agent migrates to it and this section goes away. + +Development dependencies of the web control plane may carry their own +advisories. Those are not shipped to users; `npm audit --omit=dev` on the +production tree is the number that matters, and it is expected to be zero. + +## Out of scope + +- Vulnerabilities in restic itself. Report those to + [restic](https://github.com/restic/restic). +- Vulnerabilities in the Docker daemon, including the two above. +- Anything requiring an attacker who already has write access to your agent + configuration or recipe files. Those are trusted input, equivalent to + local code execution. +- Results from automated scanners without an accompanying explanation of how + the issue is reachable in this codebase. From 73ea6f4849ef4d681a84740aae7971454e613a9c Mon Sep 17 00:00:00 2001 From: thetechnologist1911 Date: Tue, 15 Sep 2026 21:58:40 -0400 Subject: [PATCH 2/3] docs: rewrite SECURITY.md in a plainer voice The first draft read as generated: six consecutive bullets opening with a bolded noun phrase, a balanced tricolon in the intro, and an antithetical closer on the Moby assessment. Same content, first person, varied sentence length, no listicle scaffolding. --- SECURITY.md | 138 +++++++++++++++++++++++++--------------------------- 1 file changed, 66 insertions(+), 72 deletions(-) diff --git a/SECURITY.md b/SECURITY.md index 7127656..370a68b 100644 --- a/SECURITY.md +++ b/SECURITY.md @@ -1,23 +1,20 @@ # Security -Restorable runs against your backup repository and your restored data. That -puts it in a position of trust, so this document states what it guarantees, -what it does not, and how to report a problem. +Restorable reads your backup repository and handles your restored data, so +here's what it does and doesn't promise, and how to tell me if something's +wrong with it. ## Reporting a vulnerability -Use GitHub's private vulnerability reporting: -[**Report a vulnerability**](https://github.com/restorable-dev/restorable/security/advisories/new). +Use [GitHub's private reporting](https://github.com/restorable-dev/restorable/security/advisories/new). +That opens an advisory only the maintainers can see. Please don't file a public +issue for anything exploitable. -That opens a private advisory visible only to the maintainers. Please do not -open a public issue for anything exploitable. +I'll acknowledge within a week. Fixes may take longer, it's a small project, but +the advisory will say where things stand instead of going quiet. -You can expect an acknowledgement within a week. This is a small project, so -a fix may take longer than that, and the advisory will say where things stand -rather than going quiet. - -If you would rather not use GitHub, open a public issue asking for a contact -address and leave out the details. +If you'd rather not use GitHub, open a public issue asking for a contact address +and leave the details out. ## Supported versions @@ -26,73 +23,70 @@ address and leave out the details. | 0.1.x | Yes | | < 0.1.1 | No | -Releases before v0.1.1 were built with a Go toolchain carrying known standard -library vulnerabilities and have been removed from the releases page. There is -no auto-update mechanism by design, so upgrading is a manual reinstall. - -## What the agent guarantees - -These are enforced in code, not by convention, and each has tests: - -- **Read-only against your repository.** The restic wrapper exposes a closed, - unexported subcommand type whose only values are `version`, `snapshots`, - `ls`, `restore`, `check`, `dump` and `cat`. There is no code path that - reaches `forget`, `prune` or `backup`; it will not compile. -- **Sandboxes are destroyed.** Teardown is deferred, so it runs on every path - out of a verification run, including a panic in recipe code. Containers are - force-removed along with their volumes on the same guarantee. -- **Disk pre-flight.** Free space is checked before a restore begins, so an - oversized snapshot fails with a clear error instead of filling the disk. -- **Backup contents stay on your machine.** When connected to the hosted - control plane, only pass/fail status, timings, snapshot IDs, recipe names - and error strings are transmitted. File contents are never uploaded. -- **Credentials and paths are scrubbed.** Strings are passed through a scrub - and redaction step before they are logged or transmitted, including error - messages from cleanup failures. Repository URLs containing credentials are - stripped before they can enter a report. -- **Outbound only.** The agent polls over HTTPS. It opens no inbound ports. - -Cloud connectivity is optional. `restorable test --config agent.yaml` runs the -full local loop with no account. +Anything before v0.1.1 was built with a Go toolchain carrying known stdlib +vulnerabilities, and those releases have been pulled. There's no auto-update by +design, so upgrading means reinstalling. -## Known advisories +## What the agent actually enforces + +Not design intentions, these are structural and have tests: + +The restic wrapper takes a closed, unexported subcommand type. Its only values +are `version`, `snapshots`, `ls`, `restore`, `check`, `dump` and `cat`. There's +no path to `forget`, `prune` or `backup` because it wouldn't compile. + +Sandbox teardown is deferred, so it runs on every exit from a verification run, +panics included. Containers get force-removed with their volumes on the same +guarantee. + +Free space is checked before a restore starts. An oversized snapshot fails with +a clear error rather than filling your disk. -`govulncheck` currently reports two advisories against the agent, both through -the Docker client library: +Backup contents never leave your machine. Connected to the hosted control plane, +it sends pass/fail, timings, snapshot IDs, recipe names and error strings. Not +file contents. -| Advisory | CVE | Severity | -| --- | --- | --- | -| GO-2026-4887 | CVE-2026-34040 | High | -| GO-2026-4883 | CVE-2026-33997 | Medium | +Strings go through scrubbing and redaction before they're logged or transmitted, +including errors from cleanup failures. Repository URLs with credentials in them +get stripped before anything can reach a report. -**Assessment: not exploitable through this agent, and not currently fixable.** +The agent polls outbound over HTTPS and opens no inbound ports. -Both describe daemon-side behavior, an AuthZ plugin bypass on oversized request -bodies and an off-by-one in plugin privilege validation. The agent is a client. -It creates, execs into, and removes containers over the local Docker socket. It -does not install plugins and does not use an AuthZ plugin, so it does not drive -the affected code paths. Your exposure to these is a function of your Docker -daemon version, not of this agent; update Docker to address them. +Cloud is optional either way. `restorable test --config agent.yaml` runs the +whole local loop without an account. -They are also not fixable here today. The patches exist only in -`github.com/moby/moby/v2`, which has published nothing but beta releases. Moving -this project onto a beta dependency to silence two advisories it does not -exercise would trade a real risk for a cosmetic one. +## Known advisories + +`govulncheck` flags two against the agent, both through the Docker client +library: GO-2026-4887 (CVE-2026-34040, high) and GO-2026-4883 (CVE-2026-33997, +medium). + +Neither is exploitable here, and neither is fixable yet. -**Revisit trigger:** when `github.com/moby/moby/v2` publishes a stable release, -the agent migrates to it and this section goes away. +Both are daemon-side: an AuthZ plugin bypass on oversized request bodies, and an +off-by-one in plugin privilege validation. This agent is a client. It creates +containers, execs into them, and removes them over the local Docker socket. It +doesn't install plugins or use an AuthZ plugin, so it never drives the affected +paths. If these worry you, your Docker daemon version is what determines your +exposure, so update Docker. -Development dependencies of the web control plane may carry their own -advisories. Those are not shipped to users; `npm audit --omit=dev` on the -production tree is the number that matters, and it is expected to be zero. +As for fixing it here: the patches only exist in `github.com/moby/moby/v2`, +which has published nothing but betas so far. I'm not moving this onto a beta +dependency over two advisories it doesn't exercise. When moby/moby/v2 goes +stable the agent migrates and this section goes away. + +Dev dependencies of the web control plane may carry their own advisories. Those +don't ship. `npm audit --omit=dev` on the production tree is the number that +matters and it should read zero. ## Out of scope -- Vulnerabilities in restic itself. Report those to - [restic](https://github.com/restic/restic). -- Vulnerabilities in the Docker daemon, including the two above. -- Anything requiring an attacker who already has write access to your agent - configuration or recipe files. Those are trusted input, equivalent to - local code execution. -- Results from automated scanners without an accompanying explanation of how - the issue is reachable in this codebase. +Bugs in restic itself go to [restic](https://github.com/restic/restic). Same for +the Docker daemon, including the two above. + +Anything that needs an attacker who already has write access to your agent +config or recipe files. That's trusted input, roughly equivalent to local code +execution. + +Raw scanner output with no explanation of how the issue is reachable in this +codebase. From a4e6ca480a0d616d02278b553558dd46ac99101f Mon Sep 17 00:00:00 2001 From: thetechnologist1911 Date: Thu, 17 Sep 2026 18:53:04 -0400 Subject: [PATCH 3/3] docs: be precise about what reaches the control plane The disclosure said "pass/fail, timings, snapshot IDs, recipe names and error strings", which undersells it. The repository label is the repo string with credentials stripped, so it still carries host and path, and it goes into the title of every alert reaching Telegram, Discord, ntfy and email. Check messages name paths from inside the snapshot; a checksum mismatch says which file differed. Someone deciding whether to connect the cloud at all should be able to see that from this page. Also reconciles the recipe trust boundary with the code. Saying recipes are "trusted input" contradicted files.go, which treats them as a boundary and validates their paths. Both halves are true and neither alone is: paths are contained, and a recipe still names the container images its checks run. --- SECURITY.md | 30 +++++++++++++++++++++--------- 1 file changed, 21 insertions(+), 9 deletions(-) diff --git a/SECURITY.md b/SECURITY.md index 370a68b..59ac129 100644 --- a/SECURITY.md +++ b/SECURITY.md @@ -42,13 +42,23 @@ guarantee. Free space is checked before a restore starts. An oversized snapshot fails with a clear error rather than filling your disk. -Backup contents never leave your machine. Connected to the hosted control plane, -it sends pass/fail, timings, snapshot IDs, recipe names and error strings. Not -file contents. - -Strings go through scrubbing and redaction before they're logged or transmitted, -including errors from cleanup failures. Repository URLs with credentials in them -get stripped before anything can reach a report. +The contents of your files never leave your machine. Connected to the hosted +control plane, the agent sends pass/fail, timings, snapshot IDs, recipe and +check names, check messages, error strings, and a label for the repository. + +Be aware of what those last three carry, because it is more than "metadata" +suggests. The repository label is your repo string with credentials stripped, +so it still contains the host and path, and it appears in the title of every +alert sent to whatever channels you connect. Check messages can name paths from +inside the snapshot: a checksum mismatch says which file differed. If either is +more than you want reaching a third-party chat service, run the agent +standalone and skip the cloud entirely. + +Strings go through scrubbing and redaction before they're logged or +transmitted, including errors from cleanup failures. Credentials embedded in +repository URLs are stripped before anything can reach a report, and output +from database checks is stripped of the segments that echo row values, since a +failed load quotes the data that failed. The agent polls outbound over HTTPS and opens no inbound ports. @@ -85,8 +95,10 @@ Bugs in restic itself go to [restic](https://github.com/restic/restic). Same for the Docker daemon, including the two above. Anything that needs an attacker who already has write access to your agent -config or recipe files. That's trusted input, roughly equivalent to local code -execution. +config or recipe files. Paths inside a recipe are validated and contained, so a +recipe cannot read outside the restored snapshot, but a recipe also names the +container images its database and app checks run. Treat one you did not write +the way you would treat a docker-compose file from the same source. Raw scanner output with no explanation of how the issue is reachable in this codebase.