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
38 changes: 31 additions & 7 deletions .github/audit/application-security.md
Original file line number Diff line number Diff line change
Expand Up @@ -12,10 +12,16 @@ This is a code-and-specs audit of the product's own boundaries — the remote
control stack, and the local application. You need no GitHub API access and no
PAT — do not use one.

For Hosted accounts, read `docs/specs/hosted.md`, `hosted/server/`, the packed
core/auth modules in `vendor/`, and `hosted/src/`. Verify the archive hashes
against `vendor/build.json`. Distinguish tested code from pending production
configuration; do not treat local provider simulations as live OAuth acceptance.
For Hosted accounts, read `docs/specs/hosted.md`, `hosted/server/`,
`hosted/src/`, `hosted/scripts/`, `hosted/wrangler.jsonc`, the packed core/auth
modules in `vendor/`, and `.github/workflows/hosted-preview.yml` and
`.github/workflows/hosted-production.yml` — `docs/specs/security-hosted.md`'s
Deployment boundary quantifies over the preview and production paths, which
live in those scripts and workflows rather than in the Worker. Verify the
archive hashes against `vendor/build.json`. Distinguish tested code from
pending production configuration; do not treat local provider simulations as
live OAuth acceptance, and treat a checked-in placeholder as no evidence about
an external control.

Read, at minimum: `docs/specs/remote-security-model.md` **and its paired
`docs/specs/remote-security-model.rationale.md`**, `docs/specs/relay.md`,
Expand Down Expand Up @@ -146,6 +152,23 @@ Be adversarial, and go past the `FAIL IF` list. Ask specifically:
reconstruct and relay only the registered pane-level shapes, never relay a
nested document's location, and target only that origin plus the validated
app origin—never a wildcard or foreign origin.
- **Is the Hosted origin the only one that can drive Hosted?** Trace a request
from `hosted/server/worker.ts` through `workerApp`'s origin gate and
`secureHeaders`: a foreign `Host`, a preview hostname, a misconfigured
deployment's error path, and the SPA fallback must each answer without
credentialed CORS, without a cacheable shell, and without inline script.
Check that authentication cookies stay `__Host-`, Secure, HttpOnly, `Path=/`
and Domain-less, and that no session token reaches browser JSON or storage.
- **Can a Hosted login become terminal access, or an account become someone
else's?** `authPolicy` must keep explicit linking and independent logins; a
callback whose initiating login was revoked must fail; an unused or unknown
provider credential must enable nothing. No Hosted endpoint may mint a Burrow
ACL grant or stand in for the encrypted pairing and presence proof.
- **Does anything from the test or preview build reach production?** The
production Worker must not export the captured-email inbox, the deterministic
clock, or the testing injection module; preview must not copy production
routes, bindings, or credentials, must not call real mail or OAuth, and its
cleanup must check out the base branch rather than the closed PR's.
- Does the shipped code still match what the specs and this section claim? Spec
drift is a finding; say which side is wrong. The newest sections are the ones
most likely to have drifted: `remote-security-model.md`'s Presence proofs,
Expand Down Expand Up @@ -174,9 +197,10 @@ as a subtraction rather than as two named subdirectories, which is the shape
to prefer when you find the next one.

Today the remainder is `lib/`, `relay/`, `remote-lib-common/`, `standalone/`,
`vscode-ext/`, `dor/`, `dor-lib-common/`, `canopy/`, `deploy/`, `docs/`,
`.impeccable/`, and the root files — but treat that as a description of the
current tree, not as your scope. Your scope is the remainder.
`vscode-ext/`, `dor/`, `dor-lib-common/`, `hosted/`, `vendor/`, `canopy/`,
`deploy/`, `docs/`, `.impeccable/`, and the root files — but treat that as a
description of the current tree, not as your scope. Your scope is the
remainder.

Remote control is where the depth goes; the rest is a sweep for anything that
would be a security hole in a terminal that runs local shells — command
Expand Down
18 changes: 18 additions & 0 deletions .github/audit/supply-chain.md
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,24 @@

**Output file:** `audit-supply-chain.md`

The default `$GH_TOKEN` in this environment is a workflow `GITHUB_TOKEN` and
does **not** have admin scope. GitHub omits `security_and_analysis` for a
non-admin token and answers 403 rather than 204 on the Dependabot alert
endpoint, so the secret-scanning and Dependabot checks read as absent when they
are on. Prefix those `gh api` calls with `GH_TOKEN=$AUDIT_PAT`:

```sh
GH_TOKEN=$AUDIT_PAT gh api repos/$GITHUB_REPOSITORY --jq .security_and_analysis
GH_TOKEN=$AUDIT_PAT gh api repos/$GITHUB_REPOSITORY/vulnerability-alerts
```

`$AUDIT_PAT` is a fine-grained, read-only PAT covering Administration +
Secrets + Environments, guaranteed present by an earlier step. If a prefixed
call still returns 403, record FAIL with the note "PAT scope drifted from
docs/specs/security-audit.md". When run by `scripts/security-audit-local.sh`
without `AUDIT_PAT`, use the operator's existing `gh` authentication without a
`GH_TOKEN=` override, and report an inaccessible check as `UNVERIFIABLE`.

The workspace is installed by an earlier workflow step, so try the
generate-deps check directly; if it errors on a missing module, run
`pnpm install --frozen-lockfile` first. The check requires a clean working tree
Expand Down
23 changes: 11 additions & 12 deletions .github/workflows/security-audit.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -299,21 +299,20 @@ jobs:
# `security-audit-failure`: an audit that produced no verdict
# must not let the release gate in `release.yml` pass, and a
# later PASS should auto-close it like any other failure.

# What the *file* said, parsed once and never mutated. Every note in
# the issue body is written about a condition rather than about a
# branch, and "the audit never wrote a verdict" is one of those
# conditions — it cannot be recovered from `STATUS` afterwards,
# because the escalation below can raise that same MISSING to FAIL.
if [ -s audit-status.txt ]; then
STATUS=$(cat audit-status.txt)
FILE_STATUS=$(cat audit-status.txt)
fi
case "${STATUS:-}" in
case "${FILE_STATUS:-}" in
PASS|FAIL) ;;
*) STATUS=MISSING ;;
*) FILE_STATUS=MISSING ;;
esac

# What the *file* said, kept separate from `STATUS`, which the guards
# below mutate. Every note in the issue body is written about a
# condition rather than about a branch, and "the audit never wrote a
# verdict" is one of those conditions — it cannot be recovered from
# `STATUS` afterwards, because a dissent can raise that same MISSING
# to FAIL.
if [ "$STATUS" = "MISSING" ]; then STATUS_FILE_VERDICT=none; else STATUS_FILE_VERDICT="$STATUS"; fi
STATUS="$FILE_STATUS"

# A PASS is only as good as the evidence behind it. Each domain
# writes its own fragment before returning a verdict, so a
Expand Down Expand Up @@ -488,7 +487,7 @@ jobs:
if [ -n "${INCONCLUSIVE_DOMAINS:-}" ]; then
echo "- **A domain could not determine every check.** $INCONCLUSIVE_DOMAINS returned \`VERDICT: INCONCLUSIVE\`; read its \`UNVERIFIABLE\` checks. Those checks do not count as passing." >> "$NOTES"
fi
if [ "$STATUS_FILE_VERDICT" = "none" ]; then
if [ "$FILE_STATUS" = "MISSING" ]; then
echo "- **The audit wrote no verdict.** \`audit-status.txt\` was absent, empty, or not \`PASS\`/\`FAIL\`, so the run ended without deciding. Start at the report's own markers: \`UNVERIFIABLE\` is a check the agent could not determine, \`_Incomplete …_\` heads a domain that was cut off mid-report, and a domain section reading \`_No report …_\` is one that never reported — which is what an expired wait deadline looks like." >> "$NOTES"
fi

Expand Down
11 changes: 4 additions & 7 deletions SECURITY.md
Original file line number Diff line number Diff line change
Expand Up @@ -13,13 +13,10 @@ intend to do about it; there is no bounty.
security spec, [`docs/specs/security.md`](docs/specs/security.md), published at
<https://dormouse.sh/docs/security> — whole, but with the guarantees table and
the two lists narrowed there to that page's audience, so the spec itself is
where every row appears together. It names the five audited
checklists beside it — [local](docs/specs/security-local.md),
[remote control](docs/specs/security-remote.md),
[supply chain](docs/specs/security-supply-chain.md),
[CI and releases](docs/specs/security-ci.md), and
[the audit itself](docs/specs/security-audit.md) — whose `FAIL IF` lines a
nightly audit executes and every VS Code release is gated on. A failure files a
where every row appears together. Its
[Domains table](docs/specs/security.md#how-the-guarantees-are-checked) names
every audited checklist beside it, whose `FAIL IF` lines a nightly audit
executes and every VS Code release is gated on. A failure files a
public issue labeled
[`security-audit-failure`](https://github.com/diffplug/dormouse/issues?q=is%3Aissue+label%3Asecurity-audit-failure);
open ones are live, closed ones are the record.
Loading
Loading