mirror of
https://github.com/mauriceboe/TREK.git
synced 2026-06-19 21:31:46 +00:00
security: address second-pass audit findings
- CI-C1 false positive: actions/{checkout,setup-node,upload-artifact}
@v6 do exist (v6.0.0 releases published Oct-Dec 2025). Restore the
@v6 refs — the earlier batch-1 commit downgraded them unnecessarily.
- Widen idempotency_keys primary key to (key, user_id, method, path)
via new migration. Batch 1 widened the middleware lookup but left
the table PK at (key, user_id), so `INSERT OR IGNORE` silently
skipped the second endpoint that reused a key — the cache was
never populated for it and a replay re-ran the handler. The
migration rebuilds the table preserving existing rows (the old
narrower PK guarantees no conflicts against the new looser key).
- HSTS: keep `includeSubDomains` OFF by default. Enabling it for
every NODE_ENV=production install would break apex-domain setups
where siblings still serve HTTP. Operators who want the stricter
policy opt in with HSTS_INCLUDE_SUBDOMAINS=true.
- Extend the idempotency unit tests to cover the (method, path)
dimension — same user+key on different path no longer replays.
This commit is contained in:
+7
-3
@@ -79,10 +79,14 @@ export function createApp(): express.Application {
|
||||
// Caddy / Cloudflare Tunnel typically leave FORCE_HTTPS unset (the
|
||||
// proxy handles the redirect for them), and the previous "HSTS off by
|
||||
// default" meant those instances never advertised HSTS at all.
|
||||
// `HSTS_INCLUDE_SUBDOMAINS=false` lets operators with sibling
|
||||
// subdomains on the same apex opt back out.
|
||||
//
|
||||
// `includeSubDomains` stays OFF by default on purpose: an instance
|
||||
// running on an apex domain would otherwise force HTTPS on every
|
||||
// sibling subdomain the same operator may still be running over plain
|
||||
// HTTP. Operators who want the stricter policy opt in with
|
||||
// `HSTS_INCLUDE_SUBDOMAINS=true`.
|
||||
const hstsActive = shouldForceHttps || process.env.NODE_ENV === 'production';
|
||||
const hstsIncludeSubdomains = process.env.HSTS_INCLUDE_SUBDOMAINS !== 'false';
|
||||
const hstsIncludeSubdomains = process.env.HSTS_INCLUDE_SUBDOMAINS === 'true';
|
||||
|
||||
// RFC 8414 / RFC 9728: discovery docs are world-readable — open CORS regardless of deployment config
|
||||
app.use(
|
||||
|
||||
@@ -1837,6 +1837,36 @@ function runMigrations(db: Database.Database): void {
|
||||
}
|
||||
} catch { /* notifications table may not exist on very old installs */ }
|
||||
},
|
||||
// Migration: widen idempotency_keys primary key to (key, user_id,
|
||||
// method, path). The middleware lookup was widened in the same audit
|
||||
// batch so a reused X-Idempotency-Key against a different endpoint
|
||||
// does not replay the cached body of an unrelated request. The old
|
||||
// PK was only (key, user_id), so the `INSERT OR IGNORE` on the
|
||||
// second endpoint silently skipped — the cache never stored request
|
||||
// B's response and replays re-executed the handler. Rebuild the
|
||||
// table with the widened PK, preserving existing rows (the old PK
|
||||
// guarantees no conflicts in the new, strictly looser unique key).
|
||||
() => {
|
||||
const hasTable = db.prepare("SELECT name FROM sqlite_master WHERE type = 'table' AND name = 'idempotency_keys'").get();
|
||||
if (!hasTable) return;
|
||||
db.exec(`
|
||||
CREATE TABLE idempotency_keys_new (
|
||||
key TEXT NOT NULL,
|
||||
user_id INTEGER NOT NULL REFERENCES users(id) ON DELETE CASCADE,
|
||||
method TEXT NOT NULL,
|
||||
path TEXT NOT NULL,
|
||||
status_code INTEGER NOT NULL,
|
||||
response_body TEXT NOT NULL,
|
||||
created_at INTEGER NOT NULL DEFAULT (strftime('%s','now')),
|
||||
PRIMARY KEY (key, user_id, method, path)
|
||||
);
|
||||
INSERT INTO idempotency_keys_new (key, user_id, method, path, status_code, response_body, created_at)
|
||||
SELECT key, user_id, method, path, status_code, response_body, created_at FROM idempotency_keys;
|
||||
DROP TABLE idempotency_keys;
|
||||
ALTER TABLE idempotency_keys_new RENAME TO idempotency_keys;
|
||||
CREATE INDEX IF NOT EXISTS idx_idempotency_keys_created ON idempotency_keys(created_at);
|
||||
`);
|
||||
},
|
||||
];
|
||||
|
||||
if (currentVersion < migrations.length) {
|
||||
|
||||
Reference in New Issue
Block a user