Skip to content

Harden auth and add CI merge gates#3

Merged
fakechris merged 3 commits intomainfrom
harden-auth-ci-hygiene
Apr 4, 2026
Merged

Harden auth and add CI merge gates#3
fakechris merged 3 commits intomainfrom
harden-auth-ci-hygiene

Conversation

@fakechris
Copy link
Copy Markdown
Owner

@fakechris fakechris commented Apr 4, 2026

Summary

  • replace raw viewer id/email impersonation with signed viewer assertions and wire CLI/web/server to the new flow
  • add GraphQL issue-list performance guardrails plus CI checks for typecheck, lint, test, and build
  • clean repo hygiene with ignore rules, docs, and branch protection on main

Verification

  • pnpm typecheck
  • pnpm lint
  • pnpm test --run
  • pnpm build

Open with Devin

Summary by CodeRabbit

  • New Features

    • Introduced signed viewer assertion authentication system, replacing legacy header-based authentication with secure token-based approach
    • Added automated CI/CD pipeline for continuous testing and verification across all pull requests and main branch changes
  • Documentation

    • Added comprehensive project README with setup instructions, local development workflows, CLI usage examples, and import/export documentation
  • Tests

    • Extended test coverage for viewer authentication and validation
    • Added performance testing infrastructure for query optimization monitoring
  • Chores

    • Updated build and dependency configurations across all packages
    • Enhanced .gitignore to exclude compiled and generated artifacts

@coderabbitai
Copy link
Copy Markdown

coderabbitai bot commented Apr 4, 2026

📝 Walkthrough

Walkthrough

This PR introduces a signed viewer assertion token system to replace legacy x-involute-user-id and x-involute-user-email headers. It implements HMAC-SHA256-based assertion generation and validation in the shared package, updates the CLI to create assertions, modifies server auth to verify them, and reconfigures the web app to propagate assertions. Adds CI workflow and root documentation.

Changes

Cohort / File(s) Summary
Infrastructure & Configuration
.env.example, .gitignore, README.md
Added VIEWER_ASSERTION_SECRET environment variable, gitignore rules for compiled outputs, and comprehensive project documentation with setup/run instructions and CLI examples.
CI/CD Pipeline
.github/workflows/ci.yml
Introduced GitHub Actions CI workflow triggering on PRs and pushes to main, with PostgreSQL 16 service container and gates for typecheck, lint, test, and build.
Shared Package - Assertion Utilities
packages/shared/src/viewer-assertion.ts, packages/shared/src/index.ts, packages/shared/src/index.test.ts, packages/shared/package.json
Implemented HMAC-SHA256 signed viewer assertions with createViewerAssertion and verifyViewerAssertion, including expiration validation and constant-time signature comparison. Exported new types ViewerAssertionClaims and ViewerAssertionSubjectType, plus header constant. Added test coverage validating creation, verification, and security (expiration, tampering).
CLI - Viewer Assertion Integration
packages/cli/src/index.ts, packages/cli/package.json
Replaced email/ID config keys with single viewer-assertion key. Added auth viewer-assertion create command supporting --ttl and --secret options to mint signed assertions. Updated config schema and GraphQL client to use VIEWER_ASSERTION_HEADER. Added workspace dependency on @involute/shared.
Server - Auth & Environment
packages/server/src/auth.ts, packages/server/src/environment.ts, packages/server/src/index.ts
Extended GraphQLContextOptions and ServerEnvironment to accept viewerAssertionSecret. Replaced legacy header lookups with assertion verification via verifyViewerAssertion, determining viewer lookup (by id or email) based on assertion's subType.
Server - Build Configuration
packages/server/package.json
Updated build scripts to prepend @involute/shared build step, establishing explicit workspace dependency chain.
Server - Query Optimization
packages/server/src/schema.ts, packages/server/src/schema.performance.test.ts
Enhanced buildIssueListInclude to conditionally fetch children and comments relations based on GraphQL selection set. Added performance test verifying query count remains stable as dataset size increases.
Server - Test Updates
packages/server/src/auth.test.ts, packages/server/prisma/seed.test.ts, packages/server/src/son-validation-restore.test.ts
Updated auth tests to validate signed assertion verification instead of header-based lookup. Fixed label assertion ordering using locale-aware comparison. Modified SON validation test to use temporary export fixtures and adjusted expected dataset counts.
Web - Viewer Assertion Propagation
packages/web/src/lib/apollo.tsx, packages/web/src/App.drag-utils.test.tsx, packages/web/package.json
Removed getConfiguredViewerId/Email functions, replaced with getConfiguredViewerAssertion reading from VITE_INVOLUTE_VIEWER_ASSERTION env and localStorage keys. Updated Apollo client to set VIEWER_ASSERTION_HEADER. Added workspace dependency on @involute/shared and test coverage for assertion resolution.

Sequence Diagram(s)

sequenceDiagram
    participant Client
    participant Web as Web App<br/>(Apollo)
    participant Server as Server<br/>(Auth)
    participant DB as Database

    Client->>Web: Request with viewer context
    Web->>Web: Read assertion from env/<br/>localStorage
    Web->>Server: GraphQL query with<br/>x-involute-viewer-assertion header
    Server->>Server: Extract assertion &<br/>VIEWER_ASSERTION_SECRET
    Server->>Server: verifyViewerAssertion()<br/>validate signature & expiry
    alt Assertion Valid
        Server->>Server: Decode claims<br/>(sub, subType)
        alt subType === 'email'
            Server->>DB: Query user by email
        else subType === 'id'
            Server->>DB: Query user by id
        end
        DB-->>Server: User record
        Server->>Server: Create GraphQLContext<br/>with viewer
    else Assertion Invalid/Missing
        Server->>Server: Default to admin viewer
    end
    Server-->>Web: GraphQL response
    Web-->>Client: Rendered UI
Loading

Estimated code review effort

🎯 4 (Complex) | ⏱️ ~50 minutes

Possibly related PRs

Poem

🐰 A signature so fine, with HMAC's sign—
No more headers split, now assertions align!
From CLI to server, a token takes flight,
Web reads it with care, the viewer shines bright! ✨

🚥 Pre-merge checks | ✅ 2 | ❌ 1

❌ Failed checks (1 warning)

Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 0.00% which is insufficient. The required threshold is 80.00%. Write docstrings for the functions missing them to satisfy the coverage threshold.
✅ Passed checks (2 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check ✅ Passed The title accurately captures the two main objectives: hardening authentication with signed viewer assertions and adding CI/merge gate infrastructure.

✏️ Tip: You can configure your own custom pre-merge checks in the settings.

✨ Finishing Touches
📝 Generate docstrings
  • Create stacked PR
  • Commit on current branch
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Commit unit tests in branch harden-auth-ci-hygiene

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands and usage tips.

Copy link
Copy Markdown

@gemini-code-assist gemini-code-assist bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Code Review

This pull request replaces raw user headers with a signed 'viewer assertion' mechanism using HMAC for trusted impersonation across the CLI, server, and web packages. It also optimizes the server's GraphQL issues query by conditionally including related data based on the requested fields and introduces a performance test suite. I have no feedback to provide.

devin-ai-integration[bot]

This comment was marked as resolved.

Copy link
Copy Markdown

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 4

Caution

Some comments are outside the diff and can’t be posted inline due to platform limitations.

⚠️ Outside diff range comments (1)
packages/server/src/environment.ts (1)

31-39: ⚠️ Potential issue | 🟠 Major

Fail fast when VIEWER_ASSERTION_SECRET is missing.

Line 38 collapses a missing/blank secret to null, and packages/server/src/auth.ts then treats that the same as an invalid assertion and falls back to DEFAULT_ADMIN_EMAIL. A typoed env var therefore silently disables signed viewer assertions while authorized requests still resolve as the default admin.

🔧 Suggested hardening
 export interface ServerEnvironment {
   databaseUrl: string;
   authToken: string;
   port: number;
-  viewerAssertionSecret: string | null;
+  viewerAssertionSecret: string;
 }
 
 export function getServerEnvironment(env: NodeJS.ProcessEnv = process.env): ServerEnvironment {
   const port = Number(env.PORT ?? DEFAULT_PORT);
+  const viewerAssertionSecret = env.VIEWER_ASSERTION_SECRET?.trim();
+
+  if (!viewerAssertionSecret) {
+    throw new Error('VIEWER_ASSERTION_SECRET is required');
+  }
 
   return {
     databaseUrl: env.DATABASE_URL ?? '',
     authToken: env.AUTH_TOKEN ?? '',
     port: Number.isFinite(port) && port > 0 ? port : DEFAULT_PORT,
-    viewerAssertionSecret: env.VIEWER_ASSERTION_SECRET?.trim() || null,
+    viewerAssertionSecret,
   };
 }
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@packages/server/src/environment.ts` around lines 31 - 39, The function
getServerEnvironment should fail fast when VIEWER_ASSERTION_SECRET is missing or
blank: in getServerEnvironment validate env.VIEWER_ASSERTION_SECRET (trimmed)
and if it's undefined/empty throw a clear Error instead of returning null for
viewerAssertionSecret; update the returned ServerEnvironment to expect a
non-null viewerAssertionSecret and ensure callers rely on that invariant
(references: getServerEnvironment, viewerAssertionSecret, ServerEnvironment,
VIEWER_ASSERTION_SECRET).
🧹 Nitpick comments (3)
packages/server/src/schema.performance.test.ts (1)

87-88: Avoid hard-coding fixture totals in this perf test.

19 and 38 tightly couple this to current seed contents. Prefer asserting the delta added by this test fixture.

Suggested assertion tweak
-    expect(baseline.issueCount).toBe(19);
-    expect(expanded.issueCount).toBe(38);
+    expect(expanded.issueCount - baseline.issueCount).toBe(19);
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@packages/server/src/schema.performance.test.ts` around lines 87 - 88, The
test currently hard-codes seed totals using baseline.issueCount and
expanded.issueCount; change it to assert the delta introduced by this test
fixture instead—compute the difference (expanded.issueCount -
baseline.issueCount) and assert that equals the expected number of issues your
fixture adds (use a named constant like expectedAddedIssues for clarity) rather
than comparing to literal totals.
.github/workflows/ci.yml (1)

1-15: Declare least-privilege workflow permissions explicitly.

This job only checks out code and runs local install/test/build steps, so inheriting the repo default GITHUB_TOKEN permissions is broader than necessary for a merge gate. Adding an explicit read-only permissions block makes that boundary unambiguous.

🔒 Suggested change
 name: CI
+
+permissions:
+  contents: read
+
 on:
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In @.github/workflows/ci.yml around lines 1 - 15, The workflow currently
inherits default GITHUB_TOKEN permissions; add an explicit least-privilege
permissions block at the top of the workflow (e.g., under the existing "name:
CI" or before "concurrency") to limit token scope to read-only repository
contents — for example set permissions: contents: read — so the verify job (and
the overall workflow) only has the minimal required access for checkout and
local test/build steps.
packages/cli/src/index.ts (1)

373-373: Consider documenting the security implications of --secret.

Passing secrets via command-line flags exposes them in shell history and process listings (e.g., ps aux). While the environment variable INVOLUTE_VIEWER_ASSERTION_SECRET is provided as the primary method, users might still use --secret for convenience.

Consider adding a note to the command description or documentation warning users to prefer the environment variable approach, or to clear shell history after use.

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@packages/cli/src/index.ts` at line 373, Update the CLI option description for
the '--secret <secret>' flag in packages/cli/src/index.ts to warn about security
implications: explicitly note that passing secrets via the command line exposes
them to shell history and process listings and recommend using the environment
variable INVOLUTE_VIEWER_ASSERTION_SECRET instead (or clearing shell
history/avoiding process lists) so users see the preferred, safer approach when
they run or view help for the command.
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Inline comments:
In `@packages/server/src/schema.performance.test.ts`:
- Around line 12-72: The hardcoded counts and threshold in the schema
performance test are brittle; replace assertions that use fixed numbers (e.g.,
the toBe(19)/toBe(38) checks and the toBeLessThanOrEqual(20) query threshold)
with dynamic values derived from the seeded data or parameters so they adapt to
seed changes: compute expected counts by querying the prisma fixtures after
seedDatabase()/seedBoardFixture() (using prisma.issue.count(),
prisma.comment.count() or capturing returned totals from seedBoardFixture) and
then assert against those computed totals and a relative/per-item query budget
(e.g., queries per issue) instead of fixed integers; update references in this
file to executedQueries, seedDatabase, seedBoardFixture, and any count
assertions to use the derived values.

In `@packages/server/src/son-validation-restore.test.ts`:
- Around line 33-35: Tests reassign exportDir in beforeEach but only remove the
last directory in afterAll; change cleanup to run afterEach so each created temp
directory is removed: add an afterEach that deletes the directory created by
exportDir (guarding against undefined and using recursive removal) and remove
the existing afterAll cleanup block; reference the beforeEach that calls mkdtemp
and writeSonExportFixture, the exportDir variable, and the afterAll cleanup to
locate where to update.

In `@packages/shared/src/viewer-assertion.ts`:
- Around line 57-60: The verification currently accepts empty or whitespace-only
subjects; update the verification in the function handling ViewerAssertionClaims
(the code that uses the parsedPayload -> const claims = parsedPayload as
ViewerAssertionClaims) to reject blank subjects by checking that claims.sub is a
string and claims.sub.trim().length > 0 (and return null if not). Ensure this
check is applied alongside the existing exp validation so blank/whitespace-only
claims.sub values do not proceed to { id } / { email } lookups.

In `@README.md`:
- Around line 114-122: Update the local validation snippet so it matches CI by
replacing the current commands `pnpm typecheck`, `pnpm test`, `pnpm lint` with
the CI-equivalent sequence: include `pnpm build` and run tests with `pnpm test
--run` (i.e., `pnpm typecheck`, `pnpm test --run`, `pnpm lint`, `pnpm build`) so
following the README executes the same gates as GitHub Actions.

---

Outside diff comments:
In `@packages/server/src/environment.ts`:
- Around line 31-39: The function getServerEnvironment should fail fast when
VIEWER_ASSERTION_SECRET is missing or blank: in getServerEnvironment validate
env.VIEWER_ASSERTION_SECRET (trimmed) and if it's undefined/empty throw a clear
Error instead of returning null for viewerAssertionSecret; update the returned
ServerEnvironment to expect a non-null viewerAssertionSecret and ensure callers
rely on that invariant (references: getServerEnvironment, viewerAssertionSecret,
ServerEnvironment, VIEWER_ASSERTION_SECRET).

---

Nitpick comments:
In @.github/workflows/ci.yml:
- Around line 1-15: The workflow currently inherits default GITHUB_TOKEN
permissions; add an explicit least-privilege permissions block at the top of the
workflow (e.g., under the existing "name: CI" or before "concurrency") to limit
token scope to read-only repository contents — for example set permissions:
contents: read — so the verify job (and the overall workflow) only has the
minimal required access for checkout and local test/build steps.

In `@packages/cli/src/index.ts`:
- Line 373: Update the CLI option description for the '--secret <secret>' flag
in packages/cli/src/index.ts to warn about security implications: explicitly
note that passing secrets via the command line exposes them to shell history and
process listings and recommend using the environment variable
INVOLUTE_VIEWER_ASSERTION_SECRET instead (or clearing shell history/avoiding
process lists) so users see the preferred, safer approach when they run or view
help for the command.

In `@packages/server/src/schema.performance.test.ts`:
- Around line 87-88: The test currently hard-codes seed totals using
baseline.issueCount and expanded.issueCount; change it to assert the delta
introduced by this test fixture instead—compute the difference
(expanded.issueCount - baseline.issueCount) and assert that equals the expected
number of issues your fixture adds (use a named constant like
expectedAddedIssues for clarity) rather than comparing to literal totals.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: defaults

Review profile: CHILL

Plan: Pro

Run ID: c3d08802-78f7-4f76-8b21-fac026e98ebb

📥 Commits

Reviewing files that changed from the base of the PR and between 4981875 and 6a29eb9.

⛔ Files ignored due to path filters (1)
  • pnpm-lock.yaml is excluded by !**/pnpm-lock.yaml
📒 Files selected for processing (22)
  • .env.example
  • .github/workflows/ci.yml
  • .gitignore
  • README.md
  • packages/cli/package.json
  • packages/cli/src/index.ts
  • packages/server/package.json
  • packages/server/prisma/seed.test.ts
  • packages/server/src/auth.test.ts
  • packages/server/src/auth.ts
  • packages/server/src/environment.ts
  • packages/server/src/index.ts
  • packages/server/src/schema.performance.test.ts
  • packages/server/src/schema.ts
  • packages/server/src/son-validation-restore.test.ts
  • packages/shared/package.json
  • packages/shared/src/index.test.ts
  • packages/shared/src/index.ts
  • packages/shared/src/viewer-assertion.ts
  • packages/web/package.json
  • packages/web/src/App.drag-utils.test.tsx
  • packages/web/src/lib/apollo.tsx

Comment on lines +12 to +72
describe('schema query performance', () => {
const prisma = new PrismaClientConstructor({
log: [{ emit: 'event', level: 'query' }],
});
const executedQueries: string[] = [];
let server: StartedServer;
let adminId: string;
let defaultTeamId: string;
let readyStateId: string;

beforeAll(async () => {
prisma.$on('query', (event) => {
executedQueries.push(event.query);
});

await prisma.$connect();
server = await startServer({
authToken: TEST_AUTH_TOKEN,
port: 0,
prisma,
});
});

afterAll(async () => {
await server.stop();
await prisma.$disconnect();
});

beforeEach(async () => {
executedQueries.length = 0;

await prisma.comment.deleteMany();
await prisma.issue.deleteMany();
await prisma.workflowState.deleteMany();
await prisma.team.deleteMany();
await prisma.issueLabel.deleteMany();
await prisma.user.deleteMany();
await prisma.legacyLinearMapping.deleteMany();

await seedDatabase(prisma);

const admin = await prisma.user.findFirstOrThrow();
const team = await prisma.team.findFirstOrThrow();
const readyState = await prisma.workflowState.findFirstOrThrow({
where: {
name: 'Ready',
teamId: team.id,
},
});

adminId = admin.id;
defaultTeamId = team.id;
readyStateId = readyState.id;

await seedBoardFixture(prisma, {
adminId,
readyStateId,
teamId: defaultTeamId,
});
executedQueries.length = 0;
});
Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟡 Minor

🧩 Analysis chain

🏁 Script executed:

#!/bin/bash
set -euo pipefail

echo "== Vitest/Vite config files =="
fd -HI 'vitest(\.workspace)?\.(config\.)?(ts|js|mts|cts|mjs|cjs)$|vite\.config\.(ts|js|mts|cts|mjs|cjs)$'

echo
echo "== Parallelism/isolation-related config =="
rg -n -C2 'threads|pool|maxWorkers|minWorkers|isolate|fileParallelism|sequence|test\s*:' \
  $(fd -HI 'vitest(\.workspace)?\.(config\.)?(ts|js|mts|cts|mjs|cjs)$|vite\.config\.(ts|js|mts|cts|mjs|cjs)$') || true

echo
echo "== Other DB-mutating tests =="
rg -n -C1 'deleteMany\(|seedDatabase\(|startServer\(' --iglob '*test*.ts'

Repository: fakechris/Involute

Length of output: 21696


🏁 Script executed:

cat packages/server/vitest.config.ts

Repository: fakechris/Involute

Length of output: 237


🏁 Script executed:

sed -n '72,$p' packages/server/src/schema.performance.test.ts | head -50

Repository: fakechris/Involute

Length of output: 1655


Make count assertions resilient to seed data evolution.

The test uses absolute issue counts (toBe(19), toBe(38)) and query threshold (toBeLessThanOrEqual(20)) that depend on the exact output of seedDatabase() and seedBoardFixture(). If seed data changes in the future, these brittle assertions will fail even though the underlying query performance may be sound. Consider either:

  • Capturing baseline/expanded counts dynamically from seed output rather than hardcoding
  • Parameterizing the fixture totals so they adapt to seed changes

Note: File parallelism is already disabled in packages/server/vitest.config.ts, so cross-test interference is not a concern.

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@packages/server/src/schema.performance.test.ts` around lines 12 - 72, The
hardcoded counts and threshold in the schema performance test are brittle;
replace assertions that use fixed numbers (e.g., the toBe(19)/toBe(38) checks
and the toBeLessThanOrEqual(20) query threshold) with dynamic values derived
from the seeded data or parameters so they adapt to seed changes: compute
expected counts by querying the prisma fixtures after
seedDatabase()/seedBoardFixture() (using prisma.issue.count(),
prisma.comment.count() or capturing returned totals from seedBoardFixture) and
then assert against those computed totals and a relative/per-item query budget
(e.g., queries per issue) instead of fixed integers; update references in this
file to executedQueries, seedDatabase, seedBoardFixture, and any count
assertions to use the derived values.

Comment on lines +33 to +35

exportDir = await mkdtemp(join(tmpdir(), 'involute-son-restore-'));
await writeSonExportFixture(exportDir);
Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟡 Minor

Temp directory cleanup only removes the last created directory.

exportDir is reassigned in each beforeEach run, but cleanup in afterAll only removes the final value. If there are multiple test cases (currently 2), only the last temp directory gets cleaned up.

Consider using afterEach for cleanup instead:

Proposed fix
   beforeEach(async () => {
     await prisma.legacyLinearMapping.deleteMany();
     await prisma.comment.deleteMany();
     await prisma.issue.deleteMany();
     await prisma.workflowState.deleteMany();
     await prisma.issueLabel.deleteMany();
     await prisma.user.deleteMany();
     await prisma.team.deleteMany();

     exportDir = await mkdtemp(join(tmpdir(), 'involute-son-restore-'));
     await writeSonExportFixture(exportDir);
   });
+
+  afterEach(async () => {
+    if (exportDir) {
+      await rm(exportDir, { force: true, recursive: true }).catch(() => {});
+    }
+  });

And remove the afterAll cleanup block at lines 71-73.

Also applies to: 71-73

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@packages/server/src/son-validation-restore.test.ts` around lines 33 - 35,
Tests reassign exportDir in beforeEach but only remove the last directory in
afterAll; change cleanup to run afterEach so each created temp directory is
removed: add an afterEach that deletes the directory created by exportDir
(guarding against undefined and using recursive removal) and remove the existing
afterAll cleanup block; reference the beforeEach that calls mkdtemp and
writeSonExportFixture, the exportDir variable, and the afterAll cleanup to
locate where to update.

Comment on lines +57 to +60
const claims = parsedPayload as ViewerAssertionClaims;

if (!Number.isInteger(claims.exp) || claims.exp <= Math.floor(now.getTime() / 1000)) {
return null;
Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟡 Minor

Reject blank sub values during verification.

Right now any string passes, so '' and whitespace-only subjects are accepted and forwarded into { id } / { email } lookups. Those assertions should fail validation instead of producing meaningless unique queries.

🔧 Suggested validation tweak
   const claims = parsedPayload as ViewerAssertionClaims;
 
+  if (!claims.sub.trim()) {
+    return null;
+  }
+
   if (!Number.isInteger(claims.exp) || claims.exp <= Math.floor(now.getTime() / 1000)) {
     return null;
   }
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
const claims = parsedPayload as ViewerAssertionClaims;
if (!Number.isInteger(claims.exp) || claims.exp <= Math.floor(now.getTime() / 1000)) {
return null;
const claims = parsedPayload as ViewerAssertionClaims;
if (!claims.sub.trim()) {
return null;
}
if (!Number.isInteger(claims.exp) || claims.exp <= Math.floor(now.getTime() / 1000)) {
return null;
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@packages/shared/src/viewer-assertion.ts` around lines 57 - 60, The
verification currently accepts empty or whitespace-only subjects; update the
verification in the function handling ViewerAssertionClaims (the code that uses
the parsedPayload -> const claims = parsedPayload as ViewerAssertionClaims) to
reject blank subjects by checking that claims.sub is a string and
claims.sub.trim().length > 0 (and return null if not). Ensure this check is
applied alongside the existing exp validation so blank/whitespace-only
claims.sub values do not proceed to { id } / { email } lookups.

Comment thread README.md
Comment on lines +114 to +122
Run workspace checks from the repository root:

```bash
pnpm typecheck
pnpm test
pnpm lint
```

GitHub Actions now runs the same `typecheck`, `lint`, `test`, and `build` gates on pull requests and pushes to `main`.
Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟡 Minor

Make the local validation snippet match CI.

This section omits pnpm build and uses pnpm test instead of pnpm test --run, so following the README does not exercise the same merge gates and may leave Vitest in watch mode.

📝 Suggested docs update
 pnpm typecheck
-pnpm test
 pnpm lint
+pnpm test --run
+pnpm build
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
Run workspace checks from the repository root:
```bash
pnpm typecheck
pnpm test
pnpm lint
```
GitHub Actions now runs the same `typecheck`, `lint`, `test`, and `build` gates on pull requests and pushes to `main`.
pnpm typecheck
pnpm lint
pnpm test --run
pnpm build
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@README.md` around lines 114 - 122, Update the local validation snippet so it
matches CI by replacing the current commands `pnpm typecheck`, `pnpm test`,
`pnpm lint` with the CI-equivalent sequence: include `pnpm build` and run tests
with `pnpm test --run` (i.e., `pnpm typecheck`, `pnpm test --run`, `pnpm lint`,
`pnpm build`) so following the README executes the same gates as GitHub Actions.

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