C1: close forge package leak at the fly edge
forge.eblu.me's package registry (/api/packages/* and /api/v1/packages/*)
served anonymous reads to the world even for private-repo releases —
Forgejo's per-user visibility treats packages as world-readable when
the owner's Visibility is Public, and we keep eblume Public so the
profile page stays open. The sdist downloads include full source
trees of private repos; that's the leak.
The fix is to keep the user public but block /api/packages/* and
/api/v1/packages/* at the proxy edge. forge.ops.eblu.me (tailnet) is
untouched, so CI workflows + gilbert's uv + the nix-container-builder
still work — they just need to use the tailnet hostname.
Three consumers updated to forge.ops.eblu.me:
- containers/shower/default.nix (the FOD pip --extra-index-url)
- ansible/roles/cv/defaults/main.yml (cv_release_url for generic package)
- chezmoi-tracked fish dotfiles (devpi.fish + conf.d/pypi.fish) —
edited in chezmoi source, user will apply separately
The blumeops repo had no other forge-pypi consumers (audited: workers,
runner-job-image, ansible roles, container builds). Doc references in
changelog fragments + comments left as-is — they describe history.
The proper long-term fix is to move private packages to a Limited-
visibility Forgejo org instead of relying on a proxy-side block (see
queued Todoist for the migration plan). Edge block stays as
defense in depth.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
This commit is contained in:
parent
727ca2b460
commit
2d38418e6e
3 changed files with 19 additions and 2 deletions
|
|
@ -3,7 +3,7 @@
|
||||||
# Caddy serves cv_content_dir directly via the static-kind service block.
|
# Caddy serves cv_content_dir directly via the static-kind service block.
|
||||||
|
|
||||||
cv_version: "v1.0.3"
|
cv_version: "v1.0.3"
|
||||||
cv_release_url: "https://forge.eblu.me/api/packages/eblume/generic/cv/{{ cv_version }}/cv-{{ cv_version }}.tar.gz"
|
cv_release_url: "https://forge.ops.eblu.me/api/packages/eblume/generic/cv/{{ cv_version }}/cv-{{ cv_version }}.tar.gz"
|
||||||
|
|
||||||
cv_home: /Users/erichblume/blumeops/cv
|
cv_home: /Users/erichblume/blumeops/cv
|
||||||
cv_content_dir: "{{ cv_home }}/content"
|
cv_content_dir: "{{ cv_home }}/content"
|
||||||
|
|
|
||||||
|
|
@ -49,7 +49,7 @@ let
|
||||||
"$TMPDIR/venv/bin/pip" install \
|
"$TMPDIR/venv/bin/pip" install \
|
||||||
--no-cache-dir \
|
--no-cache-dir \
|
||||||
--index-url=https://pypi.ops.eblu.me/root/pypi/+simple/ \
|
--index-url=https://pypi.ops.eblu.me/root/pypi/+simple/ \
|
||||||
--extra-index-url=https://forge.eblu.me/api/packages/eblume/pypi/simple/ \
|
--extra-index-url=https://forge.ops.eblu.me/api/packages/eblume/pypi/simple/ \
|
||||||
"adelaide-baby-shower-app==${version}" \
|
"adelaide-baby-shower-app==${version}" \
|
||||||
gunicorn
|
gunicorn
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -184,6 +184,23 @@ http {
|
||||||
return 200 "User-agent: *\nDisallow: /mirrors/\nDisallow: /user/\nDisallow: /users/\nDisallow: /*/archive/\nDisallow: /*/releases/download/\n";
|
return 200 "User-agent: *\nDisallow: /mirrors/\nDisallow: /user/\nDisallow: /users/\nDisallow: /*/archive/\nDisallow: /*/releases/download/\n";
|
||||||
}
|
}
|
||||||
|
|
||||||
|
# Block the package registry at the public edge. Forgejo's per-user
|
||||||
|
# visibility model treats packages as world-readable when the owner
|
||||||
|
# has Visibility=Public — which means anyone on the internet can
|
||||||
|
# enumerate and download every wheel/sdist/generic artifact, even
|
||||||
|
# for private-repo releases (the sdist contains full source). We
|
||||||
|
# like keeping eblume's profile public, so we close the hole here
|
||||||
|
# at the proxy instead: WAN sees 403, tailnet (forge.ops.eblu.me)
|
||||||
|
# stays open for legitimate consumers (CI workflows, gilbert).
|
||||||
|
# See docs/tutorials/expose-service-publicly.md for the broader
|
||||||
|
# threat model on this proxy.
|
||||||
|
location /api/packages/ {
|
||||||
|
return 403 "Package downloads are tailnet-only — use forge.ops.eblu.me.\n";
|
||||||
|
}
|
||||||
|
location /api/v1/packages {
|
||||||
|
return 403 "Package enumeration is tailnet-only — use forge.ops.eblu.me.\n";
|
||||||
|
}
|
||||||
|
|
||||||
# Block swagger API docs — use forge.ops.eblu.me from tailnet
|
# Block swagger API docs — use forge.ops.eblu.me from tailnet
|
||||||
location /swagger {
|
location /swagger {
|
||||||
return 403 "API documentation is only available at forge.ops.eblu.me (tailnet).\n";
|
return 403 "API documentation is only available at forge.ops.eblu.me (tailnet).\n";
|
||||||
|
|
|
||||||
Loading…
Add table
Add a link
Reference in a new issue