Skip to content

Move the security claims into the compiler and CI - #68

Merged
sotashimozono merged 1 commit into
mainfrom
security-in-ci
Sep 14, 2026
Merged

sotashimozono merged 1 commit into
mainfrom
security-in-ci

Conversation

@sotashimozono

Copy link
Copy Markdown
Member

Stacked on #67 — merge that first.

The supply-chain layer was already here: cargo-deny, cargo-audit with a cron and a tracking issue, CodeQL, dependabot, pinned action SHAs, least-privilege tokens. What nothing checked was this product's own claims.

"Nothing is written to your remote" is now a type. wire::Verb wraps a byte nothing outside that module can construct, and there are six: OPEN, CLOSE, READ, OPENDIR, READDIR, REALPATH. SSH_FXP_WRITE, SETSTAT, REMOVE, MKDIR, RMDIR, RENAME, SYMLINK are not missing by policy — there is no value of that type that means them, so the code that would send one does not compile. Tx::queue takes a Verb, and that is the only way out of the process. Unused LSTAT is gone.

unsafe_code = "forbid" across the workspace. There is none today, and forbid cannot be lifted by an allow further down.

extension/permissions.json pins what the extension can reach and CI fails on any disagreement with the manifest. Verified by adding tabs:

the extension's reach changed and extension/permissions.json does not say so

zizmor lints the workflows, failing rather than advising. First run:

47 findings: 0 informational, 1 low, 17 medium, 0 high

Seventeen actions/checkout steps persisting credentials into .git/config, where every later step can read them and any artifact upload can carry them off the runner. release-plz.yml already set persist-credentials: false; the rest had not caught up. All fixed; the remaining low finding is suppressed inline with a reason, because it is on the one workflow whose first real run is a release.

No findings to report. Good job! (1 ignored, 29 suppressed)

npm audit --audit-level=high on the extension — esbuild and typescript run on the machine that builds what gets signed.

218 Rust tests, 63 e2e checks against a real SSH host, fmt/clippy/tsc clean.

The supply-chain layer was already here — cargo-deny, cargo-audit with a cron
and a tracking issue, CodeQL, dependabot, pinned action SHAs, least-privilege
tokens. What nothing checked was this product's own claims.

"Nothing is written to your remote" is now a type. `wire::Verb` wraps a byte
nothing outside that module can construct, and there are six of them: OPEN,
CLOSE, READ, OPENDIR, READDIR, REALPATH. `SSH_FXP_WRITE`, SETSTAT, REMOVE,
MKDIR, RMDIR, RENAME and SYMLINK are not missing by policy — there is no value
of that type that means them, so the code that would send one does not compile.
`Tx::queue` takes a `Verb` rather than a `u8`, which is the only way out. A
seventh is a `pub const` somebody reads in a diff, and a test names the six and
the eight write opcodes that are not among them. Unused `LSTAT` is gone.

`unsafe_code = "forbid"` across the workspace. There is none today; `forbid`
cannot be lifted by an `allow` further down, so there is none tomorrow without
a build failure.

`extension/permissions.json` pins what the extension can reach — permissions,
host permissions, content scripts, web-accessible resources, externally
connectable, CSP — and CI rebuilds it from the manifest and fails on a
disagreement. This is the surface a reviewer asks about and the one that widens
by accident, because a key added while doing something else looks like nothing
in a diff full of TypeScript. Verified by adding `tabs` and watching it go red.

zizmor lints the workflows themselves, failing rather than advising. Its first
run found seventeen checkouts persisting credentials into `.git/config`, where
every later step can read them and any artifact upload can carry them off the
runner — release-plz already knew this and the rest had not caught up. All
seventeen fixed; one remaining finding is suppressed inline with a reason.
Pinned to the version this was verified against, and run through `pipx`, which
is on the runner, so the linter needs no fourth-party SHA of its own.

`npm audit --audit-level=high` on the extension. esbuild and typescript run on
the machine that builds what gets signed, so a compromised one is a compromised
build.

218 Rust tests, 63 e2e checks against a real host, zizmor clean.

Signed-off-by: Souta <shimozono-sota631@g.ecc.u-tokyo.ac.jp>
@codecov

codecov Bot commented Sep 14, 2026

Copy link
Copy Markdown

Codecov Report

❌ Patch coverage is 89.47368% with 2 lines in your changes missing coverage. Please review.

Files with missing lines Patch % Lines
crates/ssh-browser/src/fs/sftp.rs 66.66% 1 Missing ⚠️
crates/ssh-browser/src/sftp/mod.rs 66.66% 1 Missing ⚠️

📢 Thoughts on this report? Let us know!

@sotashimozono
sotashimozono changed the base branch from index-at-the-suffix to main September 14, 2026 11:22
@sotashimozono
sotashimozono merged commit 2dc2693 into main Sep 14, 2026
24 checks passed
@sotashimozono
sotashimozono deleted the security-in-ci branch September 14, 2026 11:23
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant