Received: from malur.postgresql.org ([217.196.149.56]) by arkaria.postgresql.org with esmtps (TLS1.3) tls TLS_ECDHE_RSA_WITH_AES_256_GCM_SHA384 (Exim 4.94.2) (envelope-from ) id 1ryx0k-001DjY-UG for psycopg@arkaria.postgresql.org; Mon, 22 Apr 2024 16:59:23 +0000 Received: from localhost ([127.0.0.1] helo=malur.postgresql.org) by malur.postgresql.org with esmtp (Exim 4.94.2) (envelope-from ) id 1ryx0j-009JZ2-Jw for psycopg@arkaria.postgresql.org; Mon, 22 Apr 2024 16:59:21 +0000 Received: from makus.postgresql.org ([2001:4800:3e1:1::229]) by malur.postgresql.org with esmtps (TLS1.3) tls TLS_ECDHE_RSA_WITH_AES_256_GCM_SHA384 (Exim 4.94.2) (envelope-from ) id 1ryx0j-009JY7-96 for psycopg@lists.postgresql.org; Mon, 22 Apr 2024 16:59:21 +0000 Received: from mail-ot1-x32f.google.com ([2607:f8b0:4864:20::32f]) by makus.postgresql.org with esmtps (TLS1.3) tls TLS_ECDHE_RSA_WITH_AES_128_GCM_SHA256 (Exim 4.94.2) (envelope-from ) id 1ryx0f-0046qj-S5 for psycopg@lists.postgresql.org; Mon, 22 Apr 2024 16:59:20 +0000 Received: by mail-ot1-x32f.google.com with SMTP id 46e09a7af769-6eb7500abe3so2564491a34.2 for ; Mon, 22 Apr 2024 09:59:17 -0700 (PDT) DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/relaxed; d=gmail.com; s=20230601; t=1713805156; x=1714409956; darn=lists.postgresql.org; h=cc:to:subject:message-id:date:from:in-reply-to:references :mime-version:from:to:cc:subject:date:message-id:reply-to; bh=q1CQFmSNvc3jEIE7JX90NPq8DayAW0Cdkd390NHo0o8=; b=EZuSZQQMThIZ1zxxHmv8t6VTH8BO5KTecd6lF6eU692BkTQGdMZxtWb8mZ7XGc/u8T Pwi3bKTA6INm03LoJn86LOgu/r/KFZ59QRHzHabLR0qeZpYCAKlMEzH/eHPfh7ff8rlA IVBdv2VaHq39A8fMOIpqvKkBJMPAnCmeLnUx13m7wnabGUVoLk8cSemfNutO6seThBbk zOaRjD8bl51z40gwX7MmDle4VoqLkHM1Tp10rEf4Tzzi65ymGdSUgN1lPRNlb3XOal0Q juFR3b6GksBI4NVKWa+X/6ZSYtjTmMWNEQoXSjqp72sM3CrxTXXRHfcpj8z1JtJHp7lR nV+w== X-Google-DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/relaxed; d=1e100.net; s=20230601; t=1713805156; x=1714409956; h=cc:to:subject:message-id:date:from:in-reply-to:references :mime-version:x-gm-message-state:from:to:cc:subject:date:message-id :reply-to; bh=q1CQFmSNvc3jEIE7JX90NPq8DayAW0Cdkd390NHo0o8=; b=kvZZN78kUO699NNutHJGasgBRYmiSkgmrY41CFx8i6WbvLBFE0O8S9338plBQyFrCV oKbZLdZ961WbCR1eoa2tOOl/adCLW5BlPlK8v9oky55+C4UqFJP7Sar8vmJnldZ8CO/8 2z8+PcWU8HCshebIClCkl+Ph0FLKGWr4KnjOVSZNt47sq8Pndr5Lh2Bhwl1AfNoM84ME i/4vTgkebBINDUIQyd0RlGNZ/OLDJ4QDLwfxFqF6FIdofyGuCPDCGG2ZR7WeljL6Jok3 dvmRbBuNP8P0pPuzYMVV3yHx8t0cFN3iFNq3JIgIfDlrrAx0O65ak3ThccQKKPnW12jp GnaA== X-Gm-Message-State: AOJu0YxgrpvBEdw0vaHHrOcuf145X6FRyI4V1zNl7RLw4BxFbN1G3siH oyU8cTm5F7qi7ZuaTsp09qEudrEgXnGGbACmZ70c0OhIhxRziCiN7Yaev79bDekFDI7Ky+mU/vZ cj5QEeusT4GaVw3jsL74tGdynbbGKSb/3 X-Google-Smtp-Source: AGHT+IHmz4EMhCWydNDe98xau0OroZHGgdmKMeo1TlkzdoStoufPFeEA8pkDMpztZcGEXR1uuqV0oA7ED9KyGKFLHGY= X-Received: by 2002:a05:6870:2d4:b0:22e:9901:ede4 with SMTP id r20-20020a05687002d400b0022e9901ede4mr13294151oaf.38.1713805156496; Mon, 22 Apr 2024 09:59:16 -0700 (PDT) MIME-Version: 1.0 References: <6b4e2581-8b08-4f0c-b159-cd078fd988a9@straussaudio.ch> In-Reply-To: <6b4e2581-8b08-4f0c-b159-cd078fd988a9@straussaudio.ch> From: Daniele Varrazzo Date: Mon, 22 Apr 2024 18:59:04 +0200 Message-ID: Subject: Re: Safe SELECT ... LIKE abc% in psycopg To: Philippe Strauss Cc: psycopg@lists.postgresql.org Content-Type: multipart/alternative; boundary="000000000000913f1d0616b25b82" List-Id: List-Help: List-Subscribe: List-Post: List-Owner: List-Archive: Archived-At: Precedence: bulk --000000000000913f1d0616b25b82 Content-Type: text/plain; charset="UTF-8" Hello Philippe, Your code seems safe to me. Because the `%` is in the value, not in the query, I don't think you need to escape it. If any, I would suggest you to avoid using LIKE and to use the Postgres regular expression operators ( https://www.postgresql.org/docs/current/functions-matching.html#FUNCTIONS-POSIX-REGEXP), which integrate better with Python regular expression. Using them, your code (which I assume be looking for a prefix) might end up looking like: import re ... WHERE myco.genus.name ~ %s""", ('^' + re.escape(genus.upper()),)) or using the `~*` operator if you want a non-case-sensitive match. Note that if your table is large you can index the search using trigram indexes: see . But this is not related to psycopg. Cheers -- Daniele On Mon, 22 Apr 2024 at 18:34, Philippe Strauss wrote: > Hello, I'm Philippe from switzerland, > > I'm writing using python a small JSON API for a mycology photos archive > webapp. Aside the main API endpoint are two > helpers for an autocomplete form. > Here is the first one: > > --8<-- > @app.route('/genus/') > def genus(genus): > with dbconn.cursor() as cur: > cur.execute("""SELECT myco.genus.name > FROM myco.genus > WHERE myco.genus.name LIKE %s""", (genus.upper()+'%',)) > lsgenus = cur.fetchall() > ls = [] > for genus in lsgenus: > ls.append(genus[0]) > return jsonify(ls) > --8<-- > > My questions: > - What is the best way to use in psycopg3 to express a SELECT ... WHERE > ... LIKE blah% ? > - Is my code above safe or vulnerable to a injection attack? > - What peoples having passed on the same pattern have to recommend? > > Thanks! > > -- > Philippe Strauss > https://straussengineering.ch/ > > > > --000000000000913f1d0616b25b82 Content-Type: text/html; charset="UTF-8" Content-Transfer-Encoding: quoted-printable
Hello Philippe,

Your code se= ems safe to me. Because the `%` is in the value, not in the query, I don= 9;t think you need to escape it.

If any, I would s= uggest you to avoid using LIKE and to use the Postgres regular expression o= perators (https://www.postgresql.org/docs/current/f= unctions-matching.html#FUNCTIONS-POSIX-REGEXP), which integrate better = with Python regular expression. Using them, your code (which I assume be lo= oking for a prefix) might end up looking like:

=C2=A0=C2= =A0=C2=A0 import re
=C2=A0=C2=A0=C2=A0 ...
=C2=A0= =C2=A0=C2=A0 WHERE myco.genus.name ~ %s""", ('^' + = re.escape(genus.upper()),))

or using the `~*` oper= ator if you want a non-case-sensitive match.

N= ote that if your table is large you can index the search using trigram inde= xes: see <https://www.postgresql.org/docs/current/pgtrgm.html>. But this is = not related to psycopg.

Cheers

-- Daniele

On Mon, 22 Apr 2024 at 18:34, Philippe Strauss &l= t;philippe@straussaudio.ch&= gt; wrote:
Hello= , I'm Philippe from switzerland,

I'm writing using python a small JSON API for a mycology photos archive=
webapp. Aside the main API endpoint are two
helpers for an autocomplete form.
Here is the first one:

--8<--
@app.route('/genus/<genus>')
def genus(genus):
=C2=A0=C2=A0=C2=A0=C2=A0 with dbconn.cursor() as cur:
=C2=A0=C2=A0=C2=A0=C2=A0=C2=A0=C2=A0=C2=A0=C2=A0 cur.execute(""&q= uot;SELECT myco.genus.name
=C2=A0=C2=A0=C2=A0=C2=A0=C2=A0=C2=A0=C2=A0=C2=A0=C2=A0=C2=A0=C2=A0=C2=A0 FR= OM myco.genus
=C2=A0=C2=A0=C2=A0=C2=A0=C2=A0=C2=A0=C2=A0=C2=A0=C2=A0=C2=A0=C2=A0=C2=A0 WH= ERE myco.genus.name LIKE %s""", (genus.upper()+'%',= ))
=C2=A0=C2=A0=C2=A0=C2=A0=C2=A0=C2=A0=C2=A0=C2=A0 lsgenus =3D cur.fetchall()=
=C2=A0=C2=A0=C2=A0=C2=A0=C2=A0=C2=A0=C2=A0=C2=A0 ls =3D []
=C2=A0=C2=A0=C2=A0=C2=A0=C2=A0=C2=A0=C2=A0=C2=A0 for genus in lsgenus:
=C2=A0=C2=A0=C2=A0=C2=A0=C2=A0=C2=A0=C2=A0=C2=A0=C2=A0=C2=A0=C2=A0=C2=A0 ls= .append(genus[0])
=C2=A0=C2=A0=C2=A0=C2=A0 return jsonify(ls)
--8<--

My questions:
- What is the best way to use in psycopg3 to express a SELECT ... WHERE ... LIKE blah% ?
- Is my code above safe or vulnerable to a injection attack?
- What peoples having passed on the same pattern have to recommend?

Thanks!

--
Philippe Strauss
https://straussengineering.ch/



--000000000000913f1d0616b25b82--