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
20 changes: 20 additions & 0 deletions .github/workflows/cli-ci.yml
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,26 @@ permissions:
contents: read

jobs:
secret-scan:
runs-on: ubuntu-latest
steps:
- name: Checkout (full history)
uses: actions/checkout@v5
with:
# Full history so gitleaks scans every commit, not just the tip.
fetch-depth: 0

- name: Run gitleaks
env:
# Pinned release; bump deliberately. Run the binary directly rather than
# gitleaks/gitleaks-action@v2, which requires a paid GITLEAKS_LICENSE for
# organization-owned repos.
GITLEAKS_VERSION: 8.30.1
run: |
curl -sSfL "https://github.com/gitleaks/gitleaks/releases/download/v${GITLEAKS_VERSION}/gitleaks_${GITLEAKS_VERSION}_linux_x64.tar.gz" \
| tar -xz gitleaks
./gitleaks git . --redact --verbose --no-banner

lint-and-test:
runs-on: ubuntu-latest

Expand Down
20 changes: 20 additions & 0 deletions .gitleaks.toml
Original file line number Diff line number Diff line change
@@ -0,0 +1,20 @@
# Gitleaks configuration — extends the default ruleset.
#
# The only allowlisted secrets are the two PUBLIC Supabase anon keys committed
# in src/config/environments.ts. Those JWTs are anon-role keys, designed to be
# embedded in client code and gated by RLS (see the doc comment in that file) —
# they are intentionally not secret.
#
# They are allowlisted by EXACT VALUE, deliberately not by file path or by the
# whole `jwt` rule: a Supabase service_role key is also a JWT, so a path/rule
# allowlist would let a genuinely sensitive key pasted into the same file slip
# through. Matching exact values keeps that detection intact.
[extend]
useDefault = true

[allowlist]
description = "Public Supabase anon keys (safe to commit, gated by RLS)"
regexes = [
'''eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9\.eyJpc3MiOiJzdXBhYmFzZSIsInJlZiI6InBneWRucGhiaW1ldGluc2dma2JvIiwicm9sZSI6ImFub24iLCJpYXQiOjE3MDc1OTQzNDYsImV4cCI6MjAyMzE3MDM0Nn0\.hAYOMFxxwX1exkQkY9xyQJGC_GhGnyogkj2N-kBkMI8''',
'''eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9\.eyJpc3MiOiJzdXBhYmFzZSIsInJlZiI6ImxibXNvd2VodGp3bnFsdXJwZW1iIiwicm9sZSI6ImFub24iLCJpYXQiOjE3MDkyMTg0ODcsImV4cCI6MjAyNDc5NDQ4N30\.zeLTMAuZ_WwYvGdeP0kdvL_Zrs-RQee5APPyxmWq7qQ''',
]
16 changes: 16 additions & 0 deletions .husky/pre-commit
Original file line number Diff line number Diff line change
@@ -0,0 +1,16 @@
#!/usr/bin/env sh
# Scan staged changes for secrets before they ever enter git history.
# Mirrors the gitleaks CI gate (.github/workflows/cli-ci.yml) and shares the
# same allowlist (.gitleaks.toml), so the public Supabase anon keys won't trip.
#
# gitleaks is NOT a dependency of this package — contributors install the binary
# themselves. If it's missing we skip (exit 0) rather than block: CI is the
# real enforcement backstop, this hook is just the fast local warning.
if ! command -v gitleaks >/dev/null 2>&1; then
echo "gitleaks not found — skipping local secret scan."
echo " Install it to catch secrets before committing: https://github.com/gitleaks/gitleaks#installing"
echo " (CI still scans on push, so nothing slips through.)"
exit 0
fi

gitleaks git --staged --redact --no-banner
23 changes: 23 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -34,3 +34,26 @@ $ dcd cloud --apiKey <apiKey> <appFile> .myFlows/
See full documentation: [Docs](https://docs.devicecloud.dev)


## Development

Requires Node 22+ and [pnpm](https://pnpm.io). `pnpm install` builds the CLI and installs the git hooks automatically.

```sh-session
$ pnpm install # install deps, build, set up git hooks
$ pnpm dcd <args> # run the CLI from source
$ pnpm lint # ESLint
$ pnpm typecheck # strict tsc, no emit
$ pnpm test # build + boot mock API + integration/unit tests
```

### Secret scanning

A [gitleaks](https://github.com/gitleaks/gitleaks) scan runs in two places, both sharing the allowlist in `.gitleaks.toml`:

- **pre-commit hook** (via husky) — scans your staged changes and blocks the commit if a secret is found. Install the binary so it can run; without it the hook skips with a warning:
```sh-session
$ brew install gitleaks # or see github.com/gitleaks/gitleaks#installing
```
- **CI** — the `secret-scan` job scans the full history on every push and pull request, and is the enforced backstop regardless of local setup.


3 changes: 2 additions & 1 deletion package.json
Original file line number Diff line number Diff line change
Expand Up @@ -31,6 +31,7 @@
"eslint-config-prettier": "^10.1.8",
"eslint-plugin-import": "^2.32.0",
"eslint-plugin-unicorn": "^64.0.0",
"husky": "^9.1.7",
"mocha": "^11.7.5",
"prettier": "^3.3.3",
"shx": "^0.4.0",
Expand Down Expand Up @@ -61,7 +62,7 @@
"build": "shx rm -rf dist && tsc -b && shx chmod +x dist/index.js",
"build:binaries": "node scripts/build-binaries.mjs",
"lint": "eslint src test --ext .ts",
"prepare": "pnpm build",
"prepare": "pnpm build && husky",
"test": "node scripts/test-runner.mjs",
"typecheck": "tsc --noEmit -p tsconfig.test.json"
},
Expand Down
10 changes: 10 additions & 0 deletions pnpm-lock.yaml

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

Loading