mirror of
https://github.com/hanzoai/flow.git
synced 2026-10-17 01:29:18 +00:00
Let the security scan install, then say why each finding is not one
The lane never scanned anything. `pip --break-system-packages` cannot
install semgrep on the runner image: semgrep wants PyJWT, the image has
PyJWT 2.7.0 from its distro, a distro package carries no RECORD, so pip
cannot uninstall what it needs to replace and stops. The flag grants
permission to touch that environment; it does not make the uninstall
possible. uv gives semgrep its own environment and shims the binary onto
PATH — the same fix the insights lane took, so both read alike now.
With the install working the scan reports four, and all four are the
code being careful rather than careless. Each carries the reason at the
line, because a bare suppression is indistinguishable from someone
silencing a real one:
alembic/env.py the advisory-lock key is an int on both branches —
a sha256 digest reduced mod 2**63-1, or a literal
auth/iam.py decoded unverified only to read `iss` and pick a
validator; a token claiming our issuer then goes to
validate(), which checks the signature and raises
auth/service.py reads a claim without authenticating it, safe while
nothing authorises on the result — today only this
module's tests call it
arxiv.py imports the Element TYPE for annotations; parsing is
defusedxml.fromstring, two lines below
Placement matters and cost a round trip: the directive has to sit on the
line the rule anchors to, which for the jwt call is the `options=` line
carrying verify_signature, not the line opening the call.
Co-authored-by: Hanzo Dev <[email protected]>
This commit is contained in:
@@ -50,7 +50,26 @@ jobs:
|
||||
# Pinned. A scanner that changes rules underneath the build turns an
|
||||
# unrelated commit red and teaches people to ignore it.
|
||||
- name: install semgrep
|
||||
run: python3 -m pip install --break-system-packages --quiet semgrep==1.167.0
|
||||
# `uv tool install`, not `pip --break-system-packages`. Semgrep depends on
|
||||
# PyJWT, the runner image has PyJWT 2.7.0 from its DISTRO, and a distro
|
||||
# package carries no RECORD — so pip cannot uninstall what it needs to
|
||||
# replace and stops:
|
||||
#
|
||||
# ERROR: Cannot uninstall PyJWT 2.7.0, RECORD file not found.
|
||||
#
|
||||
# --break-system-packages grants permission to touch that environment; it
|
||||
# does not make the uninstall possible. uv puts semgrep in its own
|
||||
# environment and shims the binary onto PATH, so nothing is replaced and
|
||||
# the pin above is still exact. It is also what every other python lane
|
||||
# in this fleet now uses.
|
||||
run: |
|
||||
command -v uv >/dev/null 2>&1 || {
|
||||
curl -fsSL https://astral.sh/uv/install.sh | sh
|
||||
echo "$HOME/.local/bin" >> "$GITHUB_PATH"
|
||||
export PATH="$HOME/.local/bin:$PATH"
|
||||
}
|
||||
uv tool install semgrep==1.167.0
|
||||
echo "$HOME/.local/bin" >> "$GITHUB_PATH"
|
||||
|
||||
# The whole tree, not a named list — an allowlist stops covering whatever
|
||||
# is added after it is written. semgrep walks only git-tracked files, so
|
||||
|
||||
@@ -114,6 +114,9 @@ def _do_run_migrations(connection):
|
||||
logger.info(f"Using default migration lock_key: {lock_key}")
|
||||
|
||||
connection.execute(text("SET LOCAL lock_timeout = '180s';"))
|
||||
# lock_key is an int either way above — a sha256 digest reduced mod 2**63-1,
|
||||
# or the literal default. There is no string here to inject into.
|
||||
# nosemgrep: python.sqlalchemy.security.audit.avoid-sqlalchemy-text.avoid-sqlalchemy-text
|
||||
connection.execute(text(f"SELECT pg_advisory_xact_lock({lock_key});"))
|
||||
context.run_migrations()
|
||||
|
||||
|
||||
@@ -142,7 +142,11 @@ def try_validate(token: str, *, validator: HanzoIAMValidator | None = None) -> I
|
||||
# Peek the issuer unverified: only tokens claiming OUR issuer are IAM tokens; anything
|
||||
# else is a local token and must fall back (not error).
|
||||
try:
|
||||
# Unverified ON PURPOSE and only to read `iss`, which decides WHICH validator
|
||||
# the token belongs to. Nothing is trusted from this: a token claiming our
|
||||
# issuer goes to v.validate() below, which verifies the signature and raises.
|
||||
unverified = jwt.decode(
|
||||
# nosemgrep: python.jwt.security.unverified-jwt-decode.unverified-jwt-decode
|
||||
token, options={"verify_signature": False, "verify_exp": False, "verify_aud": False}
|
||||
)
|
||||
except jwt.InvalidTokenError:
|
||||
|
||||
@@ -617,6 +617,10 @@ class AuthService(BaseAuthService):
|
||||
not verified as this is a utility function, not an authentication function.
|
||||
"""
|
||||
try:
|
||||
# Reads a claim without authenticating it, which is safe only while nothing
|
||||
# authorises on the result — today the only callers are this module's tests.
|
||||
# If a request path ever needs the subject, verify the token instead.
|
||||
# nosemgrep: python.jwt.security.unverified-jwt-decode.unverified-jwt-decode
|
||||
claims = jwt.decode(token, options={"verify_signature": False})
|
||||
user_id = claims["sub"]
|
||||
return UUID(user_id)
|
||||
|
||||
@@ -1,4 +1,6 @@
|
||||
import urllib
|
||||
# The TYPE only, for annotations. Parsing is defusedxml.fromstring, imported below.
|
||||
# nosemgrep: python.lang.security.use-defused-xml.use-defused-xml
|
||||
from xml.etree.ElementTree import Element
|
||||
|
||||
from defusedxml.ElementTree import fromstring
|
||||
|
||||
Reference in New Issue
Block a user