Skip to content
Merged
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
10 changes: 10 additions & 0 deletions .github/dependabot.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
version: 2
updates:
# The workflows pin each action to a commit; this keeps those pins current.
- package-ecosystem: github-actions
directory: /
schedule:
interval: monthly
groups:
actions:
patterns: ["*"]
42 changes: 42 additions & 0 deletions .github/workflows/codeql.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,42 @@
name: codeql

# GitHub's semantic analysis, for Python and for these workflows. Findings
# land in the Security tab rather than failing the run.
on:
push:
branches: [main]
pull_request:
schedule:
- cron: "41 6 * * 1"

permissions:
contents: read

concurrency:
group: ${{ github.workflow }}-${{ github.ref }}
cancel-in-progress: true

jobs:
analyze:
name: ${{ matrix.language }}
runs-on: ubuntu-latest
timeout-minutes: 30
permissions:
contents: read
security-events: write
strategy:
fail-fast: false
matrix:
language: [python, actions]
steps:
- uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1
with:
persist-credentials: false
- uses: github/codeql-action/init@9b3254085494b55a0f7c4c2b3d2d52cc712ce370 # v4.39.1
with:
languages: ${{ matrix.language }}
build-mode: none
queries: security-extended
- uses: github/codeql-action/analyze@9b3254085494b55a0f7c4c2b3d2d52cc712ce370 # v4.39.1
with:
category: /language:${{ matrix.language }}
99 changes: 99 additions & 0 deletions .github/workflows/security.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,99 @@
name: security

# Four checks a server holding confidential imaging depends on: no credential
# lands in the history, the Python carries no known-dangerous pattern, the
# API's dependencies carry no published vulnerability, and the workflows
# themselves cannot be turned against the repository.
on:
push:
branches: [main]
pull_request:
# Weekly, so a rule or an advisory published upstream reaches code nobody
# has touched since.
schedule:
- cron: "29 6 * * 1"

permissions:
contents: read

concurrency:
group: ${{ github.workflow }}-${{ github.ref }}
cancel-in-progress: true

jobs:
secrets:
name: secrets (gitleaks)
runs-on: ubuntu-latest
timeout-minutes: 10
steps:
# The whole history: a token removed in a later commit is still public.
- uses: actions/checkout@fbc6f3992d24b796d5a048ff273f7fcc4a7b6c09 # v5
with:
fetch-depth: 0
persist-credentials: false
# The image, pinned by digest, rather than gitleaks-action, which needs
# a paid licence on an organisation's repository. Known placeholders are
# listed, one fingerprint each, in .gitleaksignore.
- name: Scan the history
run: >
docker run --rm -v "$PWD:/repo:ro"
ghcr.io/gitleaks/gitleaks@sha256:c00b6bd0aeb3071cbcb79009cb16a60dd9e0a7c60e2be9ab65d25e6bc8abbb7f
git /repo --no-banner --redact --exit-code 1

python:
name: python (bandit)
runs-on: ubuntu-latest
timeout-minutes: 10
steps:
- uses: actions/checkout@fbc6f3992d24b796d5a048ff273f7fcc4a7b6c09 # v5
with:
persist-credentials: false
- uses: actions/setup-python@6d4fd22b20465ccc0f7354476588e8df7c9ca0e0 # v7.1.0
with:
python-version: "3.13"
- name: Install bandit
run: python -m pip install "bandit==1.9.*"
# Medium severity and up fails the job. Tests and benchmarks are left
# out: their fixed /tmp paths and fake servers are what they are for. A
# finding that is not one is marked `# nosec <rule>` with the reason on
# the line above. B608 (SQL built from strings) is skipped: there is no
# SQL database, and every hit is an HTML page assembled from constants.
- name: Scan
run: bandit -r . -x './.git,*/tests/*,./benchmarks' -s B608 -ll -q

dependencies:
name: dependencies (pip-audit)
runs-on: ubuntu-latest
timeout-minutes: 10
steps:
- uses: actions/checkout@fbc6f3992d24b796d5a048ff273f7fcc4a7b6c09 # v5
with:
persist-credentials: false
- uses: actions/setup-python@6d4fd22b20465ccc0f7354476588e8df7c9ca0e0 # v7.1.0
with:
python-version: "3.13"
- name: Install pip-audit
run: python -m pip install "pip-audit==2.*"
# requirements-api.txt is all the server image installs, unpinned, so it
# resolves here to what the next image build would get. A tool's own
# dependencies live in the tools repository and are audited there.
- name: Audit the API's dependencies
run: pip-audit -r server/requirements-api.txt --progress-spinner off

workflows:
name: workflows (zizmor)
runs-on: ubuntu-latest
timeout-minutes: 10
steps:
- uses: actions/checkout@fbc6f3992d24b796d5a048ff273f7fcc4a7b6c09 # v5
with:
persist-credentials: false
- uses: actions/setup-python@6d4fd22b20465ccc0f7354476588e8df7c9ca0e0 # v7.1.0
with:
python-version: "3.13"
- name: Install zizmor
run: python -m pip install "zizmor==1.*"
# Offline: the online audits call the GitHub API, and the runner's
# token is read-only here anyway.
- name: Audit
run: zizmor --offline --min-severity medium .github/workflows
8 changes: 6 additions & 2 deletions .github/workflows/server-image.yml
Original file line number Diff line number Diff line change
Expand Up @@ -37,9 +37,10 @@ jobs:
runs-on: ubuntu-latest
timeout-minutes: 20
steps:
- uses: actions/checkout@v5
- uses: actions/checkout@fbc6f3992d24b796d5a048ff273f7fcc4a7b6c09 # v5
with:
fetch-depth: 0
persist-credentials: false

- name: Decide what to publish
id: plan
Expand Down Expand Up @@ -71,7 +72,10 @@ jobs:

- name: Log in to ghcr.io
if: steps.plan.outputs.publish == 'true'
run: echo "${{ secrets.GITHUB_TOKEN }}" | docker login ghcr.io -u "${{ github.actor }}" --password-stdin
env:
REGISTRY_TOKEN: ${{ secrets.GITHUB_TOKEN }}
REGISTRY_USER: ${{ github.actor }}
run: echo "$REGISTRY_TOKEN" | docker login ghcr.io -u "$REGISTRY_USER" --password-stdin

- name: Build
if: steps.plan.outputs.publish == 'true'
Expand Down
11 changes: 7 additions & 4 deletions .github/workflows/tests.yml
Original file line number Diff line number Diff line change
Expand Up @@ -23,7 +23,9 @@ jobs:
runs-on: ubuntu-latest
timeout-minutes: 20
steps:
- uses: actions/checkout@v5
- uses: actions/checkout@fbc6f3992d24b796d5a048ff273f7fcc4a7b6c09 # v5
with:
persist-credentials: false

# The server image is BUILT here, from this commit's
# docker/server.Dockerfile, under the name docker-compose.yml runs. It
Expand Down Expand Up @@ -89,8 +91,9 @@ jobs:
github.event_name == 'pull_request' ||
github.ref == 'refs/heads/main'
steps:
- uses: actions/checkout@v5
- uses: actions/checkout@fbc6f3992d24b796d5a048ff273f7fcc4a7b6c09 # v5
with:
persist-credentials: false
# Full history, not a shallow clone: the step below diffs against the
# PR's merge base, which on a long-lived branch is far more than a
# couple of commits back. `fetch-depth: 2` makes that `git diff` fail
Expand Down Expand Up @@ -125,7 +128,7 @@ jobs:
echo "run=true" >> "$GITHUB_OUTPUT"
fi

- uses: docker/setup-buildx-action@v4
- uses: docker/setup-buildx-action@f87e5991a6d7451dcb8d9637bfbc97413f497069 # v4
if: steps.changed.outputs.run == 'true'

# Built against docker/fixtures/, not the real tools: three tools with
Expand All @@ -134,7 +137,7 @@ jobs:
# still resolves without pulling a CUDA stack or the other repository.
- name: Build with the fixture tools
if: steps.changed.outputs.run == 'true'
uses: docker/build-push-action@v7
uses: docker/build-push-action@c3c9e263c25d99ce0380d002d59b67737d91b0dc # v7
with:
context: .
file: docker/Dockerfile
Expand Down
12 changes: 12 additions & 0 deletions .gitleaksignore
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
# Placeholders in server/README.md examples (`Bearer your-...-token`) and the
# word "tokens" in prose: no credential. One fingerprint per finding, as gitleaks
# prints it; a real secret stays reported.
0d2ca921460692722561d2cfab21a0a2bfdfeafe:server/README.md:curl-auth-header:213
0d2ca921460692722561d2cfab21a0a2bfdfeafe:server/README.md:curl-auth-header:219
0d2ca921460692722561d2cfab21a0a2bfdfeafe:server/README.md:curl-auth-header:226
327f665f56a4b141dafa75c1d2ac0178b2e18c04:server/README.md:generic-api-key:420
34f6e4ca2589f3c3f7e6059fd79681d77fd7c298:server/README.md:curl-auth-header:98
ddb07475e3dba207f0b48f672869fb5cfd0d8949:server/README.md:curl-auth-header:98
d35a99467206d75f992884b4f737048bb855182d:server/README.md:curl-auth-header:71
d35a99467206d75f992884b4f737048bb855182d:server/README.md:curl-auth-header:76
fb990dcab7ffc18b6ddc75e6a126a101a0d0312d:server/README.md:curl-auth-header:40
3 changes: 2 additions & 1 deletion scripts/image_release.py
Original file line number Diff line number Diff line change
Expand Up @@ -59,7 +59,8 @@ def _bearer(challenge: str, repository: str) -> str:
if fields.get("service"):
query["service"] = fields["service"]
try:
with urllib.request.urlopen(urllib.request.Request(
# HTTPS registry realm read from the registry's own challenge.
with urllib.request.urlopen(urllib.request.Request( # nosec B310
realm + "?" + urllib.parse.urlencode(query), headers=_HEADERS), timeout=20) as response:
payload = json.loads(response.read().decode("utf-8") or "{}")
except (OSError, ValueError):
Expand Down
3 changes: 2 additions & 1 deletion scripts/server_ctl.py
Original file line number Diff line number Diff line change
Expand Up @@ -417,7 +417,8 @@ def tls_context(url: str) -> ssl.SSLContext:
def server_urlopen(target, timeout: float):
"""`urllib.request.urlopen` for this server: HTTPS, verified (tls_context)."""
url = target.full_url if isinstance(target, urllib.request.Request) else target
return urllib.request.urlopen(target, timeout=timeout, context=tls_context(url))
# The URL is this server's own https address.
return urllib.request.urlopen(target, timeout=timeout, context=tls_context(url)) # nosec B310


def ensure_env(service: str, bind_addr=None, token=None, port=None) -> str:
Expand Down
6 changes: 4 additions & 2 deletions scripts/update_agent.py
Original file line number Diff line number Diff line change
Expand Up @@ -140,7 +140,8 @@ def _load_release_diff():
panel and this agent can never disagree about a commit."""
module = types.ModuleType("release_diff_for_agent")
module.__file__ = "<HEAD:server/wire/release_diff.py>"
exec(compile(_release_diff_source(), module.__file__, "exec"), module.__dict__)
# Compiles the repository's own release_diff.py at HEAD, not input.
exec(compile(_release_diff_source(), module.__file__, "exec"), module.__dict__) # nosec B102
return module


Expand Down Expand Up @@ -762,7 +763,8 @@ def apply(self, request: dict) -> None:
self.wait_idle("Before restarting", unreachable_grace=UNREACHABLE_GRACE_SECONDS)
self.phase("Restarting the server." if restart == RESTART else "Recreating the server's container.")
if self.args.restart_cmd:
done = subprocess.run(self.args.restart_cmd, shell=True, capture_output=True, text=True, timeout=600)
# --restart-cmd is the operator's own command line, passed on the agent's argv.
done = subprocess.run(self.args.restart_cmd, shell=True, capture_output=True, text=True, timeout=600) # nosec B602
else:
service = self.args.service or server_ctl.pick_service()
verb = ["restart", service] if restart == RESTART else ["up", "-d", "--force-recreate", service]
Expand Down
6 changes: 4 additions & 2 deletions server/benchmark_client.py
Original file line number Diff line number Diff line change
Expand Up @@ -133,7 +133,8 @@ def _request(url: str, token: str, data=None, method=None, timeout=POLL_TIMEOUT,
request.add_header("Content-Type", "application/x-www-form-urlencoded")
for key, value in (headers or {}).items():
request.add_header(key, value)
with urllib.request.urlopen(request, timeout=timeout, context=_tls_context()) as response:
# HTTPS URL of the server under benchmark.
with urllib.request.urlopen(request, timeout=timeout, context=_tls_context()) as response: # nosec B310
return response.status, response.read(), dict(response.headers)


Expand All @@ -144,7 +145,8 @@ def _send(url: str, token: str, body: bytes, method: str, content_type: str,
request.add_header("Content-Type", content_type)
for key, value in (headers or {}).items():
request.add_header(key, value)
with urllib.request.urlopen(request, timeout=REQUEST_TIMEOUT, context=_tls_context()) as response:
# HTTPS URL of the server under benchmark.
with urllib.request.urlopen(request, timeout=REQUEST_TIMEOUT, context=_tls_context()) as response: # nosec B310
return response.status, response.read()


Expand Down
3 changes: 2 additions & 1 deletion server/config.py
Original file line number Diff line number Diff line change
Expand Up @@ -26,7 +26,8 @@ class Settings(BaseSettings):
# --- core ---------------------------------------------------------
API_TOKEN: str # required on /run; no default
DEVICE: str = "cpu" # "cuda" on the GPU server
TEMP_DIR: str = "/tmp/inference_server" # emptied once a request is served
# The default only; production sets TEMP_DIR to its own volume.
TEMP_DIR: str = "/tmp/inference_server" # emptied once a request is served # nosec B108
DATA_DIR: str = "/data" # DATA_DIR/<tool>/{models,testfiles}, read-only
DATA_BACKEND: str = "local" # see data_store.py to plug in another

Expand Down
3 changes: 2 additions & 1 deletion server/main.py
Original file line number Diff line number Diff line change
Expand Up @@ -3949,4 +3949,5 @@ def dashboard() -> HTMLResponse:


if __name__ == "__main__":
uvicorn.run(app, host="0.0.0.0", port=8000)
# Direct-run fallback; serve.py is the entry point and chooses the address.
uvicorn.run(app, host="0.0.0.0", port=8000) # nosec B104
3 changes: 2 additions & 1 deletion server/serve.py
Original file line number Diff line number Diff line change
Expand Up @@ -162,7 +162,8 @@ def check(port: int) -> int:

try:
cert, _key = tls.certificate_paths()
with urllib.request.urlopen(f"https://127.0.0.1:{port}/health", timeout=5,
# Fixed loopback https URL.
with urllib.request.urlopen(f"https://127.0.0.1:{port}/health", timeout=5, # nosec B310
context=tls.loopback_context(cert)) as response:
return 0 if response.status == 200 else 1
except Exception: # noqa: BLE001 - a probe answers 1, it never raises
Expand Down
3 changes: 2 additions & 1 deletion server/wire/update_check.py
Original file line number Diff line number Diff line change
Expand Up @@ -56,7 +56,8 @@ def _compare(url: str, base: str, head: str) -> dict:
request = urllib.request.Request(
f"https://api.github.com/repos/{owner}/{name}/compare/{base}...{head}",
headers={"Accept": "application/vnd.github+json", "User-Agent": "visor-serve-update-check"})
with urllib.request.urlopen(request, timeout=15) as response:
# Fixed https://api.github.com URL.
with urllib.request.urlopen(request, timeout=15) as response: # nosec B310
return json.loads(response.read().decode("utf-8"))


Expand Down
Loading