Skip to content
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 2 additions & 2 deletions antseed/control.js
Original file line number Diff line number Diff line change
Expand Up @@ -13,7 +13,7 @@
const http = require('http');
const { execFile } = require('child_process');
const { Pool } = require('pg');
const { UPSERT_BUYER_STATUS, buyerStatusRow } = require('./store.js');
const { UPSERT_BUYER_STATUS, buyerStatusRow, pgConfig } = require('./store.js');

const PORT = parseInt(process.env.ANTSEED_CONTROL_PORT || '8379', 10);
const TOKEN = process.env.ANTSEED_CONTROL_TOKEN || '';
Expand All @@ -23,7 +23,7 @@ const STATUS_TIMEOUT_MS = 30000;

// One pool for the long-lived control server (write-status.js, the poll-loop
// twin, is one-shot and uses a plain Client instead).
const pool = new Pool({ connectionString: process.env.DATABASE_URL });
const pool = new Pool(pgConfig());

if (!TOKEN) {
console.error('[control] ANTSEED_CONTROL_TOKEN unset — control server disabled');
Expand Down
28 changes: 27 additions & 1 deletion antseed/store.js
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,32 @@ const UPSERT_BUYER_STATUS = `INSERT INTO buyer_status
connection_state=EXCLUDED.connection_state,
fetched_at=EXCLUDED.fetched_at`;

// node-postgres's `connectionString` only understands a postgres:// URL, but the
// prod secret hands us a libpq KEYWORD/VALUE conninfo (host=… port=… dbname=…
// user=… password='…' sslmode=require) — the form psycopg uses on the router/
// ingress. Detect that and parse it into a pg config object; pass a URL through
// untouched (dev/compose). Without this the sidecar resolves host "base" and
// dies with ENOTFOUND, leaving peer_offers/buyer_status empty.
function pgConfig() {
const dsn = process.env.DATABASE_URL || "";
if (!dsn || dsn.includes("://")) return { connectionString: dsn };

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

🩺 Stability & Availability | 🟠 Major | ⚡ Quick win

Anchor URL detection to the DSN scheme.

dsn.includes("://") misclassifies valid libpq conninfo if a quoted value contains ://, such as a generated password. That would send the whole key/value string back through connectionString and reintroduce the connection failure this helper is meant to avoid.

Proposed fix
-  if (!dsn || dsn.includes("://")) return { connectionString: dsn };
+  if (!dsn || /^[a-z][a-z0-9+.-]*:\/\//i.test(dsn.trimStart())) {
+    return { connectionString: dsn };
+  }
📝 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
if (!dsn || dsn.includes("://")) return { connectionString: dsn };
if (!dsn || /^[a-z][a-z0-9+.-]*:\/\//i.test(dsn.trimStart())) {
return { connectionString: dsn };
}
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@antseed/store.js` at line 27, The DSN check currently uses a generic
substring test, which can misclassify libpq conninfo strings when a quoted value
contains “://”. Update the DSN normalization logic in the store helper to detect
URLs by anchoring on the actual scheme at the start of the string, and only
return { connectionString: dsn } for real URI-style DSNs while leaving key/value
conninfo untouched.

const cfg = {};
const re = /(\w+)\s*=\s*'((?:[^'\\]|\\.)*)'|(\w+)\s*=\s*(\S+)/g;
let m;
while ((m = re.exec(dsn)) !== null) {
const key = m[1] || m[3];
const val = m[1] ? m[2].replace(/\\(.)/g, "$1") : m[4];
if (key === "host") cfg.host = val;
else if (key === "hostaddr" && !cfg.host) cfg.host = val;
else if (key === "port") cfg.port = Number(val);
else if (key === "dbname") cfg.database = val;
else if (key === "user") cfg.user = val;
else if (key === "password") cfg.password = val;
else if (key === "sslmode") cfg.ssl = val === "disable" ? false : { rejectUnauthorized: false };

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

🔒 Security & Privacy | 🟠 Major | ⚡ Quick win

🧩 Analysis chain

🏁 Script executed:

#!/bin/bash
# Check whether repository-managed DATABASE_URL examples or manifests use stricter sslmode values.
rg -n "sslmode=(verify-ca|verify-full|require|disable|prefer|allow)" .

Repository: genlayerlabs/unhardcoded

Length of output: 263


🏁 Script executed:

#!/bin/bash
set -euo pipefail

printf '\n== antseed/store.js (relevant section) ==\n'
cat -n antseed/store.js | sed -n '1,140p'

printf '\n== Search for sslmode handling and postgres URL usage ==\n'
rg -n "sslmode|DATABASE_URL|postgres://|postgresql://" antseed -g '!**/node_modules/**' || true

printf '\n== Search for any code paths that distinguish verify-ca / verify-full ==\n'
rg -n "verify-ca|verify-full|rejectUnauthorized|ssl\s*=" antseed -g '!**/node_modules/**' || true

Repository: genlayerlabs/unhardcoded

Length of output: 3717


Fail closed for stricter sslmode values. pgConfig() maps every non-disable value to { rejectUnauthorized: false }, so verify-ca and verify-full would silently lose certificate verification. Either preserve those modes or reject unsupported values instead of downgrading them.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@antseed/store.js` at line 40, The pgConfig() sslmode handling in store.js is
too permissive because every value other than "disable" is downgraded to {
rejectUnauthorized: false }, which breaks stricter modes. Update the sslmode
branch in pgConfig() to either map each supported mode explicitly (including
verify-ca and verify-full with proper certificate verification behavior) or
throw/reject unsupported values instead of silently disabling verification.

}
return cfg;
}

const str = (v) => (v === null || v === undefined) ? null : String(v);

function buyerStatusRow(d, pid) {
Expand All @@ -24,4 +50,4 @@ function buyerStatusRow(d, pid) {
str(d.connectionState), Date.now()];
}

module.exports = { UPSERT_BUYER_STATUS, buyerStatusRow };
module.exports = { UPSERT_BUYER_STATUS, buyerStatusRow, pgConfig };
3 changes: 2 additions & 1 deletion antseed/write-market.js
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,7 @@
// Validation: a non-dump (the CLI prints a human "No peers found" line even with
// --json) exits non-zero so entrypoint.sh keeps the last good window (no write).
const { Client } = require("pg");
const { pgConfig } = require("./store.js");

const WINDOW_MS = Number(process.env.ANTSEED_PEER_WINDOW_MS || 15 * 60 * 1000);
const now = Date.now();
Expand Down Expand Up @@ -70,7 +71,7 @@ const UPSERT = `INSERT INTO peer_offers
fetched_at=EXCLUDED.fetched_at`; // first_seen preserved across conflicts

(async () => {
const client = new Client({ connectionString: process.env.DATABASE_URL });
const client = new Client(pgConfig());
try {
await client.connect();
for (const r of rows) await client.query(UPSERT, r);
Expand Down
4 changes: 2 additions & 2 deletions antseed/write-status.js
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,7 @@
// Validation: a non-object / non-JSON exits non-zero so entrypoint.sh keeps the
// last good row (no write).
const { Client } = require("pg");
const { UPSERT_BUYER_STATUS, buyerStatusRow } = require("./store.js");
const { UPSERT_BUYER_STATUS, buyerStatusRow, pgConfig } = require("./store.js");

const PID = process.env.ANTSEED_BUYER_PID || "antseed";

Expand All @@ -16,7 +16,7 @@ catch (e) { process.exit(2); } // not JSON
if (d === null || typeof d !== "object") process.exit(3); // JSON but not a status

(async () => {
const client = new Client({ connectionString: process.env.DATABASE_URL });
const client = new Client(pgConfig());
try {
await client.connect();
await client.query(UPSERT_BUYER_STATUS, buyerStatusRow(d, PID));
Expand Down