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
49 changes: 49 additions & 0 deletions .github/workflows/evm-key-scan.yml
Original file line number Diff line number Diff line change
Expand Up @@ -41,3 +41,52 @@ jobs:
--range "$BASE..$HEAD" \
--config "$GITHUB_WORKSPACE/policy/.gitleaks.toml" \
--gitleaks "$RUNNER_TEMP/gitleaks/gitleaks"

semantic-liveness:
name: semantic liveness (advisory)
runs-on: ubuntu-latest
timeout-minutes: 15
steps:
- name: Check out the pull request
uses: actions/checkout@d23441a48e516b6c34aea4fa41551a30e30af803 # v6
with:
path: source
fetch-depth: 0
persist-credentials: false

- name: Check out the trusted scanner
uses: actions/checkout@d23441a48e516b6c34aea4fa41551a30e30af803 # v6
with:
repository: ${{ job.workflow_repository }}
ref: ${{ job.workflow_sha }}
path: policy
fetch-depth: 1
persist-credentials: false

- name: Set up trusted Node runtime
uses: actions/setup-node@0a44ba7841725637a19e28fa30b79a866c81b0a6 # v4.0.4
with:
node-version: 22

- name: Install verified Gitleaks
run: policy/scripts/install-gitleaks.sh "$RUNNER_TEMP/gitleaks"

- name: Install pinned liveness dependencies
run: npm ci --ignore-scripts --prefix policy/scripts/evm-key-liveness

- name: Check advisory EVM key liveness
env:
BASE: ${{ github.event.pull_request.base.sha }}
HEAD: ${{ github.event.pull_request.head.sha }}
# Public endpoints only. The central policy checkout, never the
# caller checkout or package lifecycle, reads this environment.
EVM_KEY_LIVENESS_RPC_URLS: vana=https://rpc.vana.org,moksha=https://rpc.moksha.vana.org
run: |
policy/scripts/materialize-commit-range.sh \
--repo "$GITHUB_WORKSPACE/source" \
--range "$BASE..$HEAD" \
--output "$RUNNER_TEMP/evm-key-snapshots"
node policy/scripts/evm-key-liveness/scan.mjs \
--snapshots "$RUNNER_TEMP/evm-key-snapshots" \
--config "$GITHUB_WORKSPACE/policy/.gitleaks.toml" \
--gitleaks "$RUNNER_TEMP/gitleaks/gitleaks"
12 changes: 11 additions & 1 deletion .github/workflows/policy-tests.yml
Original file line number Diff line number Diff line change
Expand Up @@ -21,7 +21,17 @@ jobs:
- name: Install verified Gitleaks
run: scripts/install-gitleaks.sh "$RUNNER_TEMP/gitleaks"

- name: Set up trusted Node runtime
uses: actions/setup-node@0a44ba7841725637a19e28fa30b79a866c81b0a6 # v4.0.4
with:
node-version: 22

- name: Install pinned liveness dependencies
run: npm ci --ignore-scripts --prefix scripts/evm-key-liveness

- name: Run scanner regression tests
env:
GITLEAKS_BIN: ${{ runner.temp }}/gitleaks/gitleaks
run: tests/run.sh
run: |
tests/run.sh
node --test scripts/evm-key-liveness/scan.test.mjs
1 change: 1 addition & 0 deletions .gitignore
Original file line number Diff line number Diff line change
@@ -1 +1,2 @@
.tools/
node_modules/
73 changes: 51 additions & 22 deletions README.md
Original file line number Diff line number Diff line change
@@ -1,8 +1,8 @@
# vana-com shared GitHub configuration

This repository contains the single trusted implementation of Vana's raw EVM
private-key check. It is deliberately a thin, pinned wrapper around Gitleaks,
not a second secret scanner.
This repository contains Vana's trusted EVM private-key policy. Gitleaks is the
authoritative primary scanner. A separate advisory job enriches only Gitleaks
findings with EVM address derivation and public-chain liveness checks.

## What the CI check does

Expand All @@ -17,7 +17,16 @@ The rule finds 64-hex-character EVM private-key candidates when they are within
two lines of a secret-shaped declaration or stored in a secret-named file such
as `private-key`. It intentionally does not scan arbitrary 32-byte hashes.
Findings are redacted; the workflow prints a commit ID, never the candidate
value.
value. The advisory job deduplicates the primary candidates across the whole
range before it queries RPC endpoints. It reports active-address findings and
incomplete endpoint checks as warnings; an internal scanner or inventory error
still fails that job.

The advisory scanner runs only code from the immutable policy checkout. It
materializes caller Git objects as data and does not run caller scripts, install
caller dependencies, or use a caller package lifecycle. Its implementation is
Apache-2.0 because it adapts Vana smart-contracts PR 69 by Maciej; see
[`scripts/evm-key-liveness/NOTICE`](scripts/evm-key-liveness/NOTICE).

This release does **not** detect BIP-39 mnemonics. A reliable mnemonic rule
must validate the BIP-39 checksum against its word list to avoid flagging normal
Expand All @@ -42,10 +51,11 @@ jobs:
```

Replace `<released-commit-sha>` with the 40-character commit ID of a reviewed
release. After the first successful run, make its observed scan job a required
status check. This pin is immutable, so all repositories run the reviewed
central implementation. Keep the small caller workflow code-owned, so a pull
request cannot change the central pin without the security owner's review.
release. This pin is immutable, so all repositories run the reviewed central
implementation. Keep the small caller workflow code-owned, so a pull request
cannot change the central pin without the security owner's review. The Gitleaks
scan is the authoritative job; whether it is required for merge remains a
separate, approved branch-protection decision in each caller repository.

The workflow pins Gitleaks `v8.30.1` and verifies the downloaded archive's
SHA-256 before executing it. Action references use immutable commit IDs. It
Expand All @@ -59,21 +69,33 @@ Git hooks are not policy: they can be missing, stale, or bypassed with
The hook is still useful because it stops an accidental public leak before it
leaves the workstation.

Clone this repository at the same reviewed release commit somewhere durable,
then:
Clone this repository at the same reviewed release commit somewhere durable.
From that checkout, run:

```bash
scripts/install-pre-push.sh --shared-dir /path/to/vana-dotgithub-build --repo /path/to/public-repo
scripts/install-pre-push.sh --repo /path/to/public-repo --ref <released-commit-sha>
```

The installer refuses to overwrite an existing hook. Use a hook manager or
merge the launcher deliberately when another pre-push hook already exists. It
records the selected shared checkout in the installed launcher, so no shell
environment setup is required.
The hook runs offline after its first verified Gitleaks download and sends no
source or candidate values over the network. For a new remote branch, it scans
only commits not reachable from locally fetched `refs/remotes/<remote>` tips;
run `git fetch <remote>` first if those refs may be stale.
The installer requires the selected checkout to come from `vana-com/.github`,
match the exact release SHA, and have no local changes. It installs the pinned
Gitleaks binary when the local copy is missing or fails checksum verification,
then records the selected policy checkout in a managed launcher. No shell
environment setup is required. Use `status` to inspect the launcher and
`uninstall` to remove it. To move a repository to a newer reviewed release,
uninstall with the old release SHA, then install with the new release SHA.

The installer refuses to overwrite or remove an unmanaged hook. Use a hook
manager or merge the launcher deliberately when another pre-push hook already
exists.

The hook verifies the policy checkout SHA, verifies that the checkout has no
local changes, and verifies the Gitleaks binary checksum before each push. It
does not download tools during `git push`; if the tool is missing or has the
wrong checksum, it fails closed and asks the developer to re-run the installer.
It sends no source or candidate values over the network. For a new remote
branch, it scans only commits not reachable from locally fetched
`refs/remotes/<remote>` tips; run `git fetch <remote>` first if those refs may
be stale.

## False positives and remediation

Expand Down Expand Up @@ -103,14 +125,21 @@ still be uploaded, cloned, cached, or indexed.
```bash
scripts/install-gitleaks.sh .tools/gitleaks
GITLEAKS_BIN=$PWD/.tools/gitleaks/gitleaks tests/run.sh
npm ci --ignore-scripts --prefix scripts/evm-key-liveness
GITLEAKS_BIN=$PWD/.tools/gitleaks/gitleaks node --test scripts/evm-key-liveness/scan.test.mjs
```

The test harness covers inline keys, clean hashes, add-then-remove history,
split-line declarations, path-and-value exceptions, commit messages, merge
resolutions, secret-named files, upstream-vendored-example exception bounds,
and fail-closed argument and tool failures. It
creates its own throwaway Git repository.
and fail-closed argument and tool failures. It creates its own throwaway Git
repository. The liveness tests use mocked RPC
responses for scalar validation, address derivation, active and inactive
accounts, incomplete checks, redaction, and range materialization.

## License

[MIT](LICENSE)
The repository root is [MIT licensed](LICENSE). The isolated
[`scripts/evm-key-liveness`](scripts/evm-key-liveness) adaptation is
[Apache-2.0 licensed](scripts/evm-key-liveness/LICENSE); its
[NOTICE](scripts/evm-key-liveness/NOTICE) preserves the source attribution.
28 changes: 24 additions & 4 deletions hooks/pre-push
Original file line number Diff line number Diff line change
Expand Up @@ -8,17 +8,37 @@ if [[ -z "$shared_dir" ]]; then
printf 'VANA_SECRET_SCAN_HOME is unset; skipping optional EVM key scan.\n' >&2
exit 0
fi
expected_sha=${VANA_SECRET_SCAN_EXPECTED_SHA:-}
if [[ -z "$expected_sha" || ! "$expected_sha" =~ ^[0-9a-f]{40}$ ]]; then
printf 'VANA_SECRET_SCAN_EXPECTED_SHA is unset or invalid; refusing to scan with unpinned policy.\n' >&2
exit 2
fi

scanner="$shared_dir/scripts/scan-commit-range.sh"
installer="$shared_dir/scripts/install-gitleaks.sh"
[[ -x "$scanner" && -x "$installer" ]] || {
verifier="$shared_dir/scripts/verify-gitleaks.sh"
[[ -x "$scanner" && -x "$verifier" ]] || {
printf 'VANA_SECRET_SCAN_HOME is not a valid shared scanner checkout.\n' >&2
exit 2
}
actual_sha=$(git -C "$shared_dir" rev-parse HEAD)
if [[ "$actual_sha" != "$expected_sha" ]]; then
printf 'Vana scanner checkout is at %s, expected %s.\n' "$actual_sha" "$expected_sha" >&2
exit 2
fi
origin_url=$(git -C "$shared_dir" config --get remote.origin.url || true)
case "$origin_url" in
git@github.com:vana-com/.github|git@github.com:vana-com/.github.git|https://github.com/vana-com/.github|https://github.com/vana-com/.github.git) ;;
*) printf 'Vana scanner checkout origin is not vana-com/.github: %s\n' "${origin_url:-<unset>}" >&2; exit 2 ;;
esac
git -C "$shared_dir" diff --quiet -- . && git -C "$shared_dir" diff --cached --quiet -- . || {
printf 'Vana scanner checkout has local changes: %s\n' "$shared_dir" >&2
exit 2
}

tool_dir="$shared_dir/.tools/gitleaks"
if [[ ! -x "$tool_dir/gitleaks" ]]; then
"$installer" "$tool_dir"
if ! "$verifier" "$tool_dir" >/dev/null 2>&1; then
printf 'Gitleaks is not installed for the Vana pre-push hook. Re-run scripts/install-pre-push.sh from the pinned policy checkout.\n' >&2
exit 2
fi

remote_name=${1:-origin}
Expand Down
Loading
Loading