always-further/runseal

GitHub: always-further/runseal

Stars: 6 | Forks: 1

Runseal logo

From the creator of Sigstore
The standard for secure software attestation, used by PyPI, npm, brew, and Maven Central

Join Discord We're hiring

Runseal was built to solve the problem of software supply chain attacks that are often triggered from GitHub Actions-based exploits. By using [nono's](https://github.com/always-further/nono) strong kernel-enforced sandboxing, runseal can protect secrets/tokens, sensitive files, and network access from untrusted or malicious code, while still allowing necessary software engineering operations through a flexible policy system. The project is developed by the engineers behind [sigstore](https://sigstore.dev) and [nono](https://nono.sh). ## What Runseal Does - Replace raw secrets within a workflow with phantom credentials that are useless if leaked - Protects sensitive files and secrets from exfiltration by untrusted code in CI - L7 network filtering to lock down network access by HTTP method and path - Uses `nono` TLS interception so HTTPS requests can be filtered by method/path - Blocks network by default unless policy explicitly allows a host or credential route - Restricts filesystem reads and writes to declared paths - Cryptographic audit captured outside of the sandbox for all network requests, credential injections, and filesystem access ## Quick Start name: Publish on: workflow_dispatch: jobs: publish: runs-on: ubuntu-latest steps: - uses: actions/checkout@v4 - uses: always-further/runseal@v1 with: run: npm publish policy: | fs: read: ["."] write: [] network: mode: filtered access: npm: secret: NPM_TOKEN url: https://registry.npmjs.org allow: - PUT /** env: NPM_TOKEN: ${{ secrets.NPM_TOKEN }} In this example, `npm publish` can read the repository, cannot write to paths not listed in `fs.write`, cannot use general network access, and can only use `NPM_TOKEN` through the Runseal/nono proxy for allowed HTTPS requests to `registry.npmjs.org`. ## Policy Format Runseal policy is YAML passed through the `policy` input. fs: read: - "." - "$HOME/.cache/my-tool" write: - "./dist" network: mode: filtered allow: - api.github.com access: deploy: secret: DEPLOY_TOKEN url: https://api.example.com allow: - POST /v1/deployments - GET /v1/deployments/* ### Filesystem Access `fs.read` lists paths the command can read. `fs.write` lists paths the command can write. Keep these narrow. For example, a deploy step often only needs to read `./dist` and a config file, and may not need write access at all. fs: read: ["./dist", "./fly.toml"] write: [] ### Network Access Runseal expects `network.mode: blocked` or `network.mode: filtered`. Add `network.allow` only for unauthenticated hosts the command must reach. Hosts used by access grants are added to the generated `nono` profile automatically. network: mode: filtered allow: - api.github.com ### Access Grants Each key under `access` is a named grant. `secret` is the environment variable containing the real secret, `url` is the service base URL, and `allow` lists the HTTP routes where the secret may be injected. Runseal masks the secret in logs, writes it to a private file, removes it from the child environment, and configures `nono` to inject it through the local proxy. access: fly: secret: FLY_API_TOKEN url: https://api.machines.dev allow: - POST /v1/apps/*/machines The sandboxed command receives a phantom credential for SDK compatibility. The real secret remains outside the sandbox and is only inserted by the proxy when the host and endpoint policy match. ### HTTPS Endpoint Filtering `allow` restricts access use by HTTP method and path. Matching is allow-list based. allow: - POST /v1/apps/*/releases - GET /v1/apps/*/status Runseal relies on `nono` TLS interception for this. The `nono` proxy creates an ephemeral trust bundle and injects standard CA environment variables into the sandboxed process, so common HTTPS clients can connect through the proxy while still allowing L7 policy enforcement. ## Common Recipes ### Run Tests With No Network - uses: always-further/runseal@v1 with: run: npm test policy: | fs: read: [".", "./node_modules"] write: ["./coverage"] network: mode: blocked ### Build With Package Registry Access - uses: always-further/runseal@v1 with: run: npm ci policy: | fs: read: ["."] write: ["./node_modules"] network: mode: filtered allow: - registry.npmjs.org ### Deploy With A Sealed Token - uses: always-further/runseal@v1 with: run: ./scripts/deploy.sh policy: | fs: read: ["./dist", "./deploy.yaml"] write: [] network: mode: filtered access: deploy: secret: DEPLOY_TOKEN url: https://deploy.example.com allow: - POST /v1/releases - GET /v1/releases/* env: DEPLOY_TOKEN: ${{ secrets.DEPLOY_TOKEN }} ## Inputs | Input | Required | Default | Description | | --- | --- | --- | --- | | `run` | Yes | none | Command to execute inside the sandbox. | | `policy` | No | empty | Runseal policy YAML. Prefer this for new workflows. | | `fs-read` | No | empty | Comma-separated read paths when `policy` is not set. | | `fs-write` | No | empty | Comma-separated write paths when `policy` is not set. | | `network` | No | `blocked` | Network policy when `policy` is not set: `blocked` or comma-separated domains. | | `runseal-version` | No | `latest` | Runseal release version to install. Accepts `v0.1.0` or `0.1.0`. | | `nono-version` | No | `latest` | nono release version to install. Accepts `v0.1.0` or `0.1.0`. | | `verify-attestations` | No | `true` | Verify GitHub artifact attestations for downloaded release assets. | | `audit` | No | `false` | Set to `artifact` or `true` to upload nono audit evidence as a GitHub Actions artifact. | ## Audit Evidence Runseal can export the nono audit session for a sandboxed command: - uses: always-further/runseal@v1 with: run: npm rebuild audit: artifact policy: | fs: read: [".", "./node_modules"] write: ["./node_modules"] network: mode: blocked When enabled, Runseal captures the new nono audit session after the command finishes and uploads a `runseal-audit` artifact containing: - `summary.md` - one JSON file per detected nono audit session Audit export runs before Runseal returns the sandboxed command's exit status, so failed or denied commands can still produce audit evidence. ## Supply Chain Verification - SHA-256 checksum from the release `SHA256SUMS` file - GitHub artifact attestation proving the asset was built by the expected repository and tag, for both `always-further/runseal` and `always-further/nono` Attestation verification uses `gh attestation verify` and is enabled by default. Set `verify-attestations: false` only for local testing or emergency fallback. ## Requirements - Linux x86_64 GitHub-hosted runner - `gh` CLI available on the runner for attestation verification - Published release assets for both Runseal and `nono` Release assets are expected to use this naming scheme: - `runseal-v-x86_64-unknown-linux-gnu.tar.gz` - `nono-v-x86_64-unknown-linux-gnu.tar.gz` - `SHA256SUMS` ## Development make ci `make ci` runs `make lint` and `make test` — the same Rust checks as the [CI workflow](.github/workflows/ci.yml). make lint # clippy + fmt check make test # unit tests only make fmt # format code make audit # cargo audit (run make audit-install first)
标签:通知系统