Skip to content

security challenge expiration validation#326

Merged
0xNgoo merged 1 commit into
0xNgoo:mainfrom
gracekenn:codex/security-challenge-expiration-validation
Jul 1, 2026
Merged

security challenge expiration validation#326
0xNgoo merged 1 commit into
0xNgoo:mainfrom
gracekenn:codex/security-challenge-expiration-validation

Conversation

@gracekenn

Copy link
Copy Markdown
Contributor

What does this PR do?

Adds validation for challengeExpirationSeconds in SecurityConfigSchema.

  • Rejects non-numeric, non-finite, and non-positive values
  • Keeps the existing authTokenLifetimeSeconds validation unchanged
  • Adds focused tests for valid and invalid challenge expiration values

How to test?

  • bun test tests/utils/validation.test.ts
  • bun run typecheck
  • bun run lint
  • bun run format:check
  • bun run build
  • bun run scripts/check-fallow-health.mjs

Checklist

  • My code follows the code style of this project.
  • I have added tests for my changes.
  • I have updated the documentation accordingly.
  • I have run bun run test and bun run lint locally.

Issue Reference

Closes #252

@drips-wave

drips-wave Bot commented Jun 28, 2026

Copy link
Copy Markdown

@gracekenn Great news! 🎉 Based on an automated assessment of this PR, the linked Wave issue(s) no longer count against your application limits.

You can now already apply to more issues while waiting for a review of this PR. Keep up the great work! 🚀

Learn more about application limits

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

Copy link
Copy Markdown
Contributor

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 refactors the configuration validation and HTTP routing logic by extracting default configuration merging, validation helpers, and Express router request handling into separate files (config-defaults.ts, validation-helpers.ts, and express-router-impl.ts). It also refactors several tests to use deterministic deferred promises. Feedback on these changes highlights a critical bug in express-router-impl.ts where setTimebounds is called with incorrect arguments, a high-severity idempotency issue where failed transactions can leave empty placeholder records in the database, and a loss of integration test coverage in malformed-bearer.test.ts due to testing a local helper instead of the actual middleware.

Important

The consumer version of Gemini Code Assist on GitHub is being sunset. Starting June 18, 2026, new organization installations will be blocked, and all code review activity will officially cease on July 17, 2026.
For more details on the timeline and next steps, please review the Help Documentation.

source: account,
}),
)
.setTimebounds(now, expiresAtUnix)

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

critical

In @stellar/stellar-sdk, TransactionBuilder.prototype.setTimebounds expects a single TimeBounds object (e.g., { minTime: number | string, maxTime: number | string }) rather than two separate arguments. Passing now, expiresAtUnix directly will result in invalid timebounds or a runtime error during transaction building.

Suggested change
.setTimebounds(now, expiresAtUnix)
.setTimebounds({ minTime: now, maxTime: expiresAtUnix })

Comment on lines +535 to +546
if (typeof idempotencyKey === 'string' && idempotencyKey.length > 0) {
const idempotencyId = randomUUID();
const idempotencyRecord = await context.database.insertOrGetIdempotencyRecord({
id: idempotencyId,
scope,
idempotencyKey,
requestHash,
statusCode: 201,
responseBody: '{}',
});

if (idempotencyRecord.id === idempotencyId) {

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

high

There is a critical correctness issue with the idempotency implementation. If insertInteractiveTransaction (or any subsequent step) fails and throws an error, the placeholder idempotency record remains in the database with statusCode: 201 and responseBody: '{}'.

A subsequent request with the same idempotency key will match the existing record, bypass transaction creation, and replay the empty 201 response. This makes the client believe the transaction was successfully created when it was actually aborted.

Consider wrapping the transaction creation in a try-catch block to delete or mark the idempotency record as failed if the transaction cannot be created.

Comment on lines +3 to 10
function isAcceptedBearerHeader(authHeader: unknown): boolean {
if (!authHeader || typeof authHeader !== 'string' || !authHeader.startsWith('Bearer ')) {
return false;
}

if (req.url === '/protected') {
const auth = req.headers['authorization'] || '';
if (!auth || typeof auth !== 'string' || !auth.startsWith('Bearer ')) {
res.statusCode = 401;
res.end('Unauthorized');
return;
}

const token = auth.slice('Bearer '.length).trim();
// Very small validation: JWTs have three dot-separated parts
if (token.split('.').length !== 3) {
res.statusCode = 401;
res.end('Unauthorized');
return;
}

res.statusCode = 200;
res.end('OK');
return;
}

res.statusCode = 404;
res.end();
});

return new Promise<http.Server>((resolve, reject) => {
server.listen(0, () => resolve(server));
server.on('error', reject);
});
}

function httpRequest(
port: number,
opts: { method?: string; path?: string; headers?: Record<string, string> },
) {
return new Promise<{ statusCode: number; body: string }>((resolve, reject) => {
const request = http.request(
{ port, method: opts.method || 'GET', path: opts.path || '/', headers: opts.headers },
(res) => {
const chunks: Buffer[] = [];
res.on('data', (c) => chunks.push(Buffer.from(c)));
res.on('end', () =>
resolve({ statusCode: res.statusCode || 0, body: Buffer.concat(chunks).toString() }),
);
},
);

request.on('error', reject);
request.end();
});
const token = authHeader.slice('Bearer '.length).trim();
return token.split('.').length === 3;
}

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

medium

This test has been refactored to test a local helper function isAcceptedBearerHeader defined entirely within the test file itself. It no longer starts a test server or exercises the actual router middleware/authentication logic in express-router-impl.ts (which uses jwt.verify). This results in a complete loss of integration test coverage for bearer token validation.

@0xNgoo 0xNgoo force-pushed the codex/security-challenge-expiration-validation branch from 5f80412 to 32c6113 Compare July 1, 2026 14:52
@0xNgoo 0xNgoo merged commit 3d127c7 into 0xNgoo:main Jul 1, 2026
1 check passed
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.

Validate challengeExpirationSeconds in SecurityConfigSchema

2 participants