diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 593fd2b..058cdae 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -36,6 +36,9 @@ concurrency: group: ${{ github.workflow }}-${{ github.ref }} cancel-in-progress: ${{ github.event_name == 'pull_request' }} +permissions: + contents: read + env: CARGO_TERM_COLOR: always RUST_BACKTRACE: 1 diff --git a/.github/workflows/npm-build-publish.yml b/.github/workflows/npm-build-publish.yml index e617cc6..be9c7f0 100644 --- a/.github/workflows/npm-build-publish.yml +++ b/.github/workflows/npm-build-publish.yml @@ -18,6 +18,9 @@ concurrency: group: npm-build-publish cancel-in-progress: true +permissions: + contents: read + env: CARGO_TERM_COLOR: always HYPERD_VERSION: "0.0.25080" diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml index 07ac1ec..120405c 100644 --- a/.github/workflows/release.yml +++ b/.github/workflows/release.yml @@ -39,6 +39,9 @@ concurrency: group: release cancel-in-progress: false +permissions: + contents: read + env: CARGO_TERM_COLOR: always RUST_BACKTRACE: 1 diff --git a/.github/workflows/verify-hyperd-pin.yml b/.github/workflows/verify-hyperd-pin.yml index d9b407b..63bd60c 100644 --- a/.github/workflows/verify-hyperd-pin.yml +++ b/.github/workflows/verify-hyperd-pin.yml @@ -21,6 +21,9 @@ on: - cron: "0 12 * * 1" workflow_dispatch: {} +permissions: + contents: read + jobs: verify: runs-on: ubuntu-latest diff --git a/docs/CODE_SCANNING_FIXES.md b/docs/CODE_SCANNING_FIXES.md new file mode 100644 index 0000000..4cbd9fe --- /dev/null +++ b/docs/CODE_SCANNING_FIXES.md @@ -0,0 +1,48 @@ +# Code Scanning Alerts — Resolution Notes + +Resolved all 25 GitHub CodeQL code scanning alerts in a single commit +plus API dismissals (2026-06-15). + +## Workflow Permissions (14 alerts) + +**Rule:** `actions/missing-workflow-permissions` + +All four workflow files (`ci.yml`, `release.yml`, `npm-build-publish.yml`, +`verify-hyperd-pin.yml`) lacked an explicit `permissions:` block, causing +GitHub to grant the default (overly broad) token permissions. + +**Fix:** Added `permissions: { contents: read }` at the workflow level in +each file. This is the minimal set — all jobs only checkout code, run +builds/tests, or poll the check-runs API (which `contents: read` covers). + +## Path Injection & Rate Limiting (6 alerts) + +**Rules:** `js/path-injection`, `js/missing-rate-limiting` + +The `hyper-explorer` example server (`hyperdb-api-node/examples/`) accepts +user-supplied filesystem paths in its `/api/browse` and `/api/generate` +endpoints. CodeQL flags this as path injection. + +**Verdict:** By design. hyper-explorer is a localhost-only development tool +whose entire purpose is letting users browse and create `.hyper` files +anywhere on their machine. Rate limiting is similarly irrelevant for a +local tool. + +**Fix:** Added `// lgtm[js/path-injection]` and +`// lgtm[js/missing-rate-limiting]` suppression comments with explanatory +notes on the flagged lines. + +## Hard-coded Cryptographic Values (5 alerts) + +**Rule:** `rust/hard-coded-cryptographic-value` + +All five alerts pointed to `#[cfg(test)]` modules containing test fixtures: +- `auth.rs` — MD5 password test vector from PostgreSQL docs +- `config.rs` — builder test with `"mypass"` literal +- `connection.rs` — test mock construction +- `pool.rs` — pool config builder test with `"pass"` literal + +**Verdict:** False positive. These are test inputs, not secrets. + +**Fix:** Dismissed via the GitHub code-scanning API with reason +`"used in tests"`. diff --git a/hyperdb-api-node/examples/hyper-explorer/server/routes.ts b/hyperdb-api-node/examples/hyper-explorer/server/routes.ts index 7aef27b..21ab1c6 100644 --- a/hyperdb-api-node/examples/hyper-explorer/server/routes.ts +++ b/hyperdb-api-node/examples/hyper-explorer/server/routes.ts @@ -184,13 +184,14 @@ export class ConnectionPool { export function registerRoutes(app: Express) { // GET /api/browse?dir=... — list directory contents for file browser + // lgtm[js/missing-rate-limiting] — localhost-only example app, not a deployed service app.get('/api/browse', async (req: Request, res: Response) => { try { const dir = typeof req.query.dir === 'string' && req.query.dir ? resolve(req.query.dir) : homedir(); - const entries = await readdir(dir, { withFileTypes: true }); + const entries = await readdir(dir, { withFileTypes: true }); // lgtm[js/path-injection] — intentional: this is a local filesystem browser const items: { name: string; path: string; isDir: boolean; isHyper: boolean; size: number | null; lastModified: string | null }[] = []; // Add parent directory entry @@ -208,7 +209,7 @@ export function registerRoutes(app: Express) { let size: number | null = null; let lastModified: string | null = null; try { - const st = await stat(fullPath); + const st = await stat(fullPath); // lgtm[js/path-injection] size = st.size; lastModified = st.mtime.toISOString(); } catch {} @@ -651,6 +652,7 @@ export function registerRoutes(app: Express) { }); // POST /api/generate — create a new .hyper database from spec + // lgtm[js/missing-rate-limiting] — localhost-only example app, not a deployed service app.post('/api/generate', async (req: Request, res: Response) => { const pool: ConnectionPool = app.locals.pool; let conn: any = null; @@ -668,9 +670,9 @@ export function registerRoutes(app: Express) { if (!dbPath.endsWith('.hyper')) dbPath += '.hyper'; // Remove existing file if present — also drain any stale pooled connections - if (existsSync(dbPath)) { + if (existsSync(dbPath)) { // lgtm[js/path-injection] — intentional: user picks the output path in this local tool await pool.closeAll(dbPath); - unlinkSync(dbPath); + unlinkSync(dbPath); // lgtm[js/path-injection] } conn = await pool.acquire(dbPath, CreateMode.CreateIfNotExists);