fix(security): move hardcoded HMAC secret to environment variable

The benchmark submission HMAC signing secret was hardcoded in source
code (CWE-798), allowing anyone reading the open-source repository to
extract it and forge benchmark submissions to benchmark.projectnomad.us.

- Read BENCHMARK_HMAC_SECRET from env instead of embedding it in code
- Register the variable in the AdonisJS env schema (optional)
- Add a guard in submitToRepository() that rejects submissions when
  the secret is not configured
- Document the new variable in .env.example

The benchmark server operator must now inject the real secret via the
BENCHMARK_HMAC_SECRET environment variable (e.g. in docker-compose or
a .env file).  The previously committed secret should be rotated
server-side.
This commit is contained in:
Sebastion 2026-03-25 08:00:43 +00:00
parent efe6af9b24
commit bc06965ec3
No known key found for this signature in database
3 changed files with 22 additions and 6 deletions

View File

@ -15,4 +15,7 @@ REDIS_PORT=6379
# Storage path for NOMAD content (ZIM files, maps, etc.) # Storage path for NOMAD content (ZIM files, maps, etc.)
# On Windows dev, use an absolute path like: C:/nomad-storage # On Windows dev, use an absolute path like: C:/nomad-storage
# On Linux production, use: /opt/project-nomad/storage # On Linux production, use: /opt/project-nomad/storage
NOMAD_STORAGE_PATH=/opt/project-nomad/storage NOMAD_STORAGE_PATH=/opt/project-nomad/storage
# HMAC secret used to sign benchmark submissions to benchmark.projectnomad.us
# Generate with: openssl rand -hex 24
# BENCHMARK_HMAC_SECRET=

View File

@ -23,16 +23,17 @@ import type {
RepositoryStats, RepositoryStats,
} from '../../types/benchmark.js' } from '../../types/benchmark.js'
import { randomUUID, createHmac } from 'node:crypto' import { randomUUID, createHmac } from 'node:crypto'
import env from '#start/env'
import { DockerService } from './docker_service.js' import { DockerService } from './docker_service.js'
import { SERVICE_NAMES } from '../../constants/service_names.js' import { SERVICE_NAMES } from '../../constants/service_names.js'
import { BROADCAST_CHANNELS } from '../../constants/broadcast.js' import { BROADCAST_CHANNELS } from '../../constants/broadcast.js'
import Dockerode from 'dockerode' import Dockerode from 'dockerode'
// HMAC secret for signing submissions to the benchmark repository // HMAC secret for signing submissions to the benchmark repository.
// This provides basic protection against casual API abuse. // Must be provided via the BENCHMARK_HMAC_SECRET environment variable.
// Note: Since NOMAD is open source, a determined attacker could extract this. // The benchmark server uses this to verify that submissions originate from
// For stronger protection, see challenge-response authentication. // a genuine NOMAD instance. Never commit the real secret to source control.
const BENCHMARK_HMAC_SECRET = '778ba65d0bc0e23119e5ffce4b3716648a7d071f0a47ec3f' const BENCHMARK_HMAC_SECRET = env.get('BENCHMARK_HMAC_SECRET')
// Re-export default weights for use in service // Re-export default weights for use in service
const SCORE_WEIGHTS = { const SCORE_WEIGHTS = {
@ -157,6 +158,11 @@ export class BenchmarkService {
} }
try { try {
// Refuse to submit if the signing secret is not configured
if (!BENCHMARK_HMAC_SECRET) {
throw new Error('Benchmark submission signing secret is not configured. Set the BENCHMARK_HMAC_SECRET environment variable.')
}
// Generate HMAC signature for submission verification // Generate HMAC signature for submission verification
const timestamp = Date.now().toString() const timestamp = Date.now().toString()
const payload = timestamp + JSON.stringify(submission) const payload = timestamp + JSON.stringify(submission)

View File

@ -60,4 +60,11 @@ export default await Env.create(new URL('../', import.meta.url), {
|---------------------------------------------------------- |----------------------------------------------------------
*/ */
NOMAD_API_URL: Env.schema.string.optional(), NOMAD_API_URL: Env.schema.string.optional(),
/*
|----------------------------------------------------------
| Variables for configuring the benchmark submission secret
|----------------------------------------------------------
*/
BENCHMARK_HMAC_SECRET: Env.schema.string.optional(),
}) })