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
105 changes: 105 additions & 0 deletions .github/workflows/netlify-ops.yml
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,8 @@ on:
- add-dl-custom-domain
- find-images-site
- add-images-custom-domain
- find-assets-site
- add-assets-custom-domain

jobs:
restore-devsy-sh:
Expand Down Expand Up @@ -139,6 +141,109 @@ jobs:
echo "Full site info:"
echo "$BODY" | jq '{id, name, custom_domain, subdomain, url, ssl_url}'

find-assets-site:
name: Find or Create assets.devsy.sh Netlify Site
if: inputs.operation == 'find-assets-site'
runs-on: ubuntu-latest
env:
NETLIFY_AUTH_TOKEN: ${{ secrets.NETLIFY_AUTH_TOKEN }}
steps:
- name: Find or create assets.devsy.sh site and output ID
run: |
echo "Listing Netlify sites..."
SITES_RESP=$(curl -s -w "\n%{http_code}" -H "Authorization: Bearer $NETLIFY_AUTH_TOKEN" \
"https://api.netlify.com/api/v1/sites?per_page=100")
SITES_HTTP=$(echo "$SITES_RESP" | tail -1)
SITES=$(echo "$SITES_RESP" | sed '$d')
if [ "$SITES_HTTP" != "200" ]; then
echo "ERROR: Failed to list sites (HTTP $SITES_HTTP)"
echo "$SITES"
exit 1
fi

ASSETS_SITE_ID=$(echo "$SITES" | \
jq -r '.[] | select(.custom_domain == "assets.devsy.sh" or .name == "assets-devsy-sh") | .id' | head -1)

if [ -n "$ASSETS_SITE_ID" ] && [ "$ASSETS_SITE_ID" != "null" ]; then
ASSETS_SITE_NAME=$(echo "$SITES" | jq -r ".[] | select(.id == \"$ASSETS_SITE_ID\") | .name")
echo "Found existing site: $ASSETS_SITE_NAME"
else
echo "Site not found. Listing all sites for reference:"
echo "$SITES" | jq '.[] | {id, name, custom_domain, url}' | head -100
echo ""
echo "Creating assets-devsy-sh..."
CREATE_RESP_RAW=$(curl -s -w "\n%{http_code}" -X POST \
-H "Authorization: Bearer $NETLIFY_AUTH_TOKEN" \
-H "Content-Type: application/json" \
-d '{"name": "assets-devsy-sh"}' \
"https://api.netlify.com/api/v1/sites")
CREATE_HTTP=$(echo "$CREATE_RESP_RAW" | tail -1)
CREATE_RESP=$(echo "$CREATE_RESP_RAW" | sed '$d')
if [ "$CREATE_HTTP" != "200" ] && [ "$CREATE_HTTP" != "201" ]; then
echo "ERROR: Site creation failed (HTTP $CREATE_HTTP)"
echo "$CREATE_RESP"
exit 1
fi
ASSETS_SITE_ID=$(echo "$CREATE_RESP" | jq -r '.id // empty')
if [ -z "$ASSETS_SITE_ID" ]; then
echo "ERROR: Site created but no id returned"
echo "$CREATE_RESP"
exit 1
fi
echo "Created: $(echo "$CREATE_RESP" | jq '{id, name, url}')"
fi

echo ""
echo "========================================"
echo "NETLIFY_ASSETS_SITE_ID = $ASSETS_SITE_ID"
echo "========================================"
echo "Run: gh secret set NETLIFY_ASSETS_SITE_ID --org devsy-org --visibility all --body \"$ASSETS_SITE_ID\""

add-assets-custom-domain:
name: Add assets.devsy.sh Custom Domain to assets-devsy-sh Site
if: inputs.operation == 'add-assets-custom-domain'
runs-on: ubuntu-latest
env:
NETLIFY_AUTH_TOKEN: ${{ secrets.NETLIFY_AUTH_TOKEN }}
ASSETS_SITE_ID: ${{ secrets.NETLIFY_ASSETS_SITE_ID }}
steps:
- name: Add custom domain and report DNS instructions
run: |
if [ -z "$ASSETS_SITE_ID" ]; then
echo "ERROR: NETLIFY_ASSETS_SITE_ID secret is not set. Run find-assets-site first."
exit 1
fi
echo "Adding assets.devsy.sh as custom domain to site $ASSETS_SITE_ID..."
RESPONSE=$(curl -s -w "\n%{http_code}" -X PUT \
-H "Authorization: Bearer $NETLIFY_AUTH_TOKEN" \
-H "Content-Type: application/json" \
-d '{"custom_domain": "assets.devsy.sh"}' \
"https://api.netlify.com/api/v1/sites/$ASSETS_SITE_ID")

HTTP_CODE=$(echo "$RESPONSE" | tail -1)
BODY=$(echo "$RESPONSE" | sed '$d')

if [ "$HTTP_CODE" != "200" ] && [ "$HTTP_CODE" != "201" ]; then
echo "ERROR: Custom domain update failed (HTTP $HTTP_CODE)"
echo "$BODY"
exit 1
fi

echo "Custom domain set (HTTP $HTTP_CODE)"
NETLIFY_SUBDOMAIN=$(echo "$BODY" | jq -r '.subdomain // .name')
echo ""
echo "========================================"
echo "CUSTOM DOMAIN CONFIGURED: assets.devsy.sh"
echo "========================================"
echo ""
echo "DNS action required — add this DNS record:"
echo " Type: CNAME"
echo " Name: assets"
echo " Value: ${NETLIFY_SUBDOMAIN}.netlify.app"
echo ""
echo "Full site info:"
echo "$BODY" | jq '{id, name, custom_domain, subdomain, url, ssl_url}'
Comment on lines +144 to +245

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

Add an explicit least-privilege permissions block to the new jobs.

Neither new job declares permissions, so they inherit the repository's default GITHUB_TOKEN scopes even though they only authenticate to Netlify via NETLIFY_AUTH_TOKEN. Scope the token down to avoid an over-privileged token being available during these steps.

🔒 Proposed hardening
   find-assets-site:
     name: Find or Create assets.devsy.sh Netlify Site
     if: inputs.operation == 'find-assets-site'
     runs-on: ubuntu-latest
+    permissions: {}
     env:
       NETLIFY_AUTH_TOKEN: ${{ secrets.NETLIFY_AUTH_TOKEN }}
   add-assets-custom-domain:
     name: Add assets.devsy.sh Custom Domain to assets-devsy-sh Site
     if: inputs.operation == 'add-assets-custom-domain'
     runs-on: ubuntu-latest
+    permissions: {}
     env:
       NETLIFY_AUTH_TOKEN: ${{ secrets.NETLIFY_AUTH_TOKEN }}
       ASSETS_SITE_ID: ${{ secrets.NETLIFY_ASSETS_SITE_ID }}

Note: the pre-existing dl/images jobs share this gap; a top-level permissions: {} would cover all of them.

📝 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
find-assets-site:
name: Find or Create assets.devsy.sh Netlify Site
if: inputs.operation == 'find-assets-site'
runs-on: ubuntu-latest
env:
NETLIFY_AUTH_TOKEN: ${{ secrets.NETLIFY_AUTH_TOKEN }}
steps:
- name: Find or create assets.devsy.sh site and output ID
run: |
echo "Listing Netlify sites..."
SITES_RESP=$(curl -s -w "\n%{http_code}" -H "Authorization: Bearer $NETLIFY_AUTH_TOKEN" \
"https://api.netlify.com/api/v1/sites?per_page=100")
SITES_HTTP=$(echo "$SITES_RESP" | tail -1)
SITES=$(echo "$SITES_RESP" | sed '$d')
if [ "$SITES_HTTP" != "200" ]; then
echo "ERROR: Failed to list sites (HTTP $SITES_HTTP)"
echo "$SITES"
exit 1
fi
ASSETS_SITE_ID=$(echo "$SITES" | \
jq -r '.[] | select(.custom_domain == "assets.devsy.sh" or .name == "assets-devsy-sh") | .id' | head -1)
if [ -n "$ASSETS_SITE_ID" ] && [ "$ASSETS_SITE_ID" != "null" ]; then
ASSETS_SITE_NAME=$(echo "$SITES" | jq -r ".[] | select(.id == \"$ASSETS_SITE_ID\") | .name")
echo "Found existing site: $ASSETS_SITE_NAME"
else
echo "Site not found. Listing all sites for reference:"
echo "$SITES" | jq '.[] | {id, name, custom_domain, url}' | head -100
echo ""
echo "Creating assets-devsy-sh..."
CREATE_RESP_RAW=$(curl -s -w "\n%{http_code}" -X POST \
-H "Authorization: Bearer $NETLIFY_AUTH_TOKEN" \
-H "Content-Type: application/json" \
-d '{"name": "assets-devsy-sh"}' \
"https://api.netlify.com/api/v1/sites")
CREATE_HTTP=$(echo "$CREATE_RESP_RAW" | tail -1)
CREATE_RESP=$(echo "$CREATE_RESP_RAW" | sed '$d')
if [ "$CREATE_HTTP" != "200" ] && [ "$CREATE_HTTP" != "201" ]; then
echo "ERROR: Site creation failed (HTTP $CREATE_HTTP)"
echo "$CREATE_RESP"
exit 1
fi
ASSETS_SITE_ID=$(echo "$CREATE_RESP" | jq -r '.id // empty')
if [ -z "$ASSETS_SITE_ID" ]; then
echo "ERROR: Site created but no id returned"
echo "$CREATE_RESP"
exit 1
fi
echo "Created: $(echo "$CREATE_RESP" | jq '{id, name, url}')"
fi
echo ""
echo "========================================"
echo "NETLIFY_ASSETS_SITE_ID = $ASSETS_SITE_ID"
echo "========================================"
echo "Run: gh secret set NETLIFY_ASSETS_SITE_ID --org devsy-org --visibility all --body \"$ASSETS_SITE_ID\""
add-assets-custom-domain:
name: Add assets.devsy.sh Custom Domain to assets-devsy-sh Site
if: inputs.operation == 'add-assets-custom-domain'
runs-on: ubuntu-latest
env:
NETLIFY_AUTH_TOKEN: ${{ secrets.NETLIFY_AUTH_TOKEN }}
ASSETS_SITE_ID: ${{ secrets.NETLIFY_ASSETS_SITE_ID }}
steps:
- name: Add custom domain and report DNS instructions
run: |
if [ -z "$ASSETS_SITE_ID" ]; then
echo "ERROR: NETLIFY_ASSETS_SITE_ID secret is not set. Run find-assets-site first."
exit 1
fi
echo "Adding assets.devsy.sh as custom domain to site $ASSETS_SITE_ID..."
RESPONSE=$(curl -s -w "\n%{http_code}" -X PUT \
-H "Authorization: Bearer $NETLIFY_AUTH_TOKEN" \
-H "Content-Type: application/json" \
-d '{"custom_domain": "assets.devsy.sh"}' \
"https://api.netlify.com/api/v1/sites/$ASSETS_SITE_ID")
HTTP_CODE=$(echo "$RESPONSE" | tail -1)
BODY=$(echo "$RESPONSE" | sed '$d')
if [ "$HTTP_CODE" != "200" ] && [ "$HTTP_CODE" != "201" ]; then
echo "ERROR: Custom domain update failed (HTTP $HTTP_CODE)"
echo "$BODY"
exit 1
fi
echo "Custom domain set (HTTP $HTTP_CODE)"
NETLIFY_SUBDOMAIN=$(echo "$BODY" | jq -r '.subdomain // .name')
echo ""
echo "========================================"
echo "CUSTOM DOMAIN CONFIGURED: assets.devsy.sh"
echo "========================================"
echo ""
echo "DNS action required — add this DNS record:"
echo " Type: CNAME"
echo " Name: assets"
echo " Value: ${NETLIFY_SUBDOMAIN}.netlify.app"
echo ""
echo "Full site info:"
echo "$BODY" | jq '{id, name, custom_domain, subdomain, url, ssl_url}'
find-assets-site:
name: Find or Create assets.devsy.sh Netlify Site
if: inputs.operation == 'find-assets-site'
runs-on: ubuntu-latest
permissions: {}
env:
NETLIFY_AUTH_TOKEN: ${{ secrets.NETLIFY_AUTH_TOKEN }}
steps:
- name: Find or create assets.devsy.sh site and output ID
run: |
echo "Listing Netlify sites..."
SITES_RESP=$(curl -s -w "\n%{http_code}" -H "Authorization: Bearer $NETLIFY_AUTH_TOKEN" \
"https://api.netlify.com/api/v1/sites?per_page=100")
SITES_HTTP=$(echo "$SITES_RESP" | tail -1)
SITES=$(echo "$SITES_RESP" | sed '$d')
if [ "$SITES_HTTP" != "200" ]; then
echo "ERROR: Failed to list sites (HTTP $SITES_HTTP)"
echo "$SITES"
exit 1
fi
ASSETS_SITE_ID=$(echo "$SITES" | \
jq -r '.[] | select(.custom_domain == "assets.devsy.sh" or .name == "assets-devsy-sh") | .id' | head -1)
if [ -n "$ASSETS_SITE_ID" ] && [ "$ASSETS_SITE_ID" != "null" ]; then
ASSETS_SITE_NAME=$(echo "$SITES" | jq -r ".[] | select(.id == \"$ASSETS_SITE_ID\") | .name")
echo "Found existing site: $ASSETS_SITE_NAME"
else
echo "Site not found. Listing all sites for reference:"
echo "$SITES" | jq '.[] | {id, name, custom_domain, url}' | head -100
echo ""
echo "Creating assets-devsy-sh..."
CREATE_RESP_RAW=$(curl -s -w "\n%{http_code}" -X POST \
-H "Authorization: Bearer $NETLIFY_AUTH_TOKEN" \
-H "Content-Type: application/json" \
-d '{"name": "assets-devsy-sh"}' \
"https://api.netlify.com/api/v1/sites")
CREATE_HTTP=$(echo "$CREATE_RESP_RAW" | tail -1)
CREATE_RESP=$(echo "$CREATE_RESP_RAW" | sed '$d')
if [ "$CREATE_HTTP" != "200" ] && [ "$CREATE_HTTP" != "201" ]; then
echo "ERROR: Site creation failed (HTTP $CREATE_HTTP)"
echo "$CREATE_RESP"
exit 1
fi
ASSETS_SITE_ID=$(echo "$CREATE_RESP" | jq -r '.id // empty')
if [ -z "$ASSETS_SITE_ID" ]; then
echo "ERROR: Site created but no id returned"
echo "$CREATE_RESP"
exit 1
fi
echo "Created: $(echo "$CREATE_RESP" | jq '{id, name, url}')"
fi
echo ""
echo "========================================"
echo "NETLIFY_ASSETS_SITE_ID = $ASSETS_SITE_ID"
echo "========================================"
echo "Run: gh secret set NETLIFY_ASSETS_SITE_ID --org devsy-org --visibility all --body \"$ASSETS_SITE_ID\""
add-assets-custom-domain:
name: Add assets.devsy.sh Custom Domain to assets-devsy-sh Site
if: inputs.operation == 'add-assets-custom-domain'
runs-on: ubuntu-latest
permissions: {}
env:
NETLIFY_AUTH_TOKEN: ${{ secrets.NETLIFY_AUTH_TOKEN }}
ASSETS_SITE_ID: ${{ secrets.NETLIFY_ASSETS_SITE_ID }}
steps:
- name: Add custom domain and report DNS instructions
run: |
if [ -z "$ASSETS_SITE_ID" ]; then
echo "ERROR: NETLIFY_ASSETS_SITE_ID secret is not set. Run find-assets-site first."
exit 1
fi
echo "Adding assets.devsy.sh as custom domain to site $ASSETS_SITE_ID..."
RESPONSE=$(curl -s -w "\n%{http_code}" -X PUT \
-H "Authorization: Bearer $NETLIFY_AUTH_TOKEN" \
-H "Content-Type: application/json" \
-d '{"custom_domain": "assets.devsy.sh"}' \
"https://api.netlify.com/api/v1/sites/$ASSETS_SITE_ID")
HTTP_CODE=$(echo "$RESPONSE" | tail -1)
BODY=$(echo "$RESPONSE" | sed '$d')
if [ "$HTTP_CODE" != "200" ] && [ "$HTTP_CODE" != "201" ]; then
echo "ERROR: Custom domain update failed (HTTP $HTTP_CODE)"
echo "$BODY"
exit 1
fi
echo "Custom domain set (HTTP $HTTP_CODE)"
NETLIFY_SUBDOMAIN=$(echo "$BODY" | jq -r '.subdomain // .name')
echo ""
echo "========================================"
echo "CUSTOM DOMAIN CONFIGURED: assets.devsy.sh"
echo "========================================"
echo ""
echo "DNS action required — add this DNS record:"
echo " Type: CNAME"
echo " Name: assets"
echo " Value: ${NETLIFY_SUBDOMAIN}.netlify.app"
echo ""
echo "Full site info:"
echo "$BODY" | jq '{id, name, custom_domain, subdomain, url, ssl_url}'
🧰 Tools
🪛 zizmor (1.26.1)

[warning] 144-200: overly broad permissions (excessive-permissions): default permissions used due to no permissions: block

(excessive-permissions)


[warning] 202-245: overly broad permissions (excessive-permissions): default permissions used due to no permissions: block

(excessive-permissions)

🤖 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 @.github/workflows/netlify-ops.yml around lines 144 - 245, The two new
Netlify workflow jobs inherit broad GITHUB_TOKEN scopes because they do not
declare permissions, even though they only use NETLIFY_AUTH_TOKEN. Add an
explicit least-privilege permissions block to the find-assets-site and
add-assets-custom-domain jobs in netlify-ops.yml, using the workflow/job-level
permissions syntax to restrict access to only what these steps need. Use the job
names and their run steps as the location anchors when updating the YAML, and
consider whether a top-level permissions: {} is appropriate for the whole
workflow if the existing dl/images jobs should also be hardened.

Source: Linters/SAST tools


find-images-site:
name: Find or Create images.devsy.sh Netlify Site
if: inputs.operation == 'find-images-site'
Expand Down
2 changes: 1 addition & 1 deletion cmd/pro/login.go
Original file line number Diff line number Diff line change
Expand Up @@ -362,7 +362,7 @@ func login(

var fallbackProvider = `name: devsy-pro
version: v0.0.0
icon: https://devsy.sh/assets/devsy.svg
icon: https://assets.devsy.sh/devsy.svg
description: Devsy Pro
options:
DEVSY_CONFIG:
Expand Down
4 changes: 2 additions & 2 deletions desktop/e2e/fixtures/mock-devsy.cjs
Original file line number Diff line number Diff line change
Expand Up @@ -18,7 +18,7 @@ function defaultState() {
config: {
name: "docker",
version: "v0.5.0",
icon: "https://devsy.sh/icons/docker.svg",
icon: "https://assets.devsy.sh/docker.svg",
description: "Devsy on Docker",
source: { github: "devsy-org/devsy-provider-docker" },
options: {
Expand All @@ -38,7 +38,7 @@ function defaultState() {
config: {
name: "kubernetes",
version: "v0.3.0",
icon: "https://devsy.sh/icons/k8s.svg",
icon: "https://assets.devsy.sh/kubernetes.svg",
description: "Devsy on Kubernetes",
source: { github: "devsy-org/devsy-provider-kubernetes" },
options: {},
Expand Down
2 changes: 1 addition & 1 deletion hack/pro/provider.yaml
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
name: devsy-pro
version: ##VERSION##
icon: https://devsy.sh/assets/devsy.svg
icon: https://assets.devsy.sh/devsy.svg
description: Devsy Pro
options:
DEVSY_CONFIG:
Expand Down
6 changes: 3 additions & 3 deletions pkg/config/repo.go
Original file line number Diff line number Diff line change
Expand Up @@ -25,11 +25,11 @@ const (
// SSHHostSuffix is appended to workspace IDs for SSH config host entries.
SSHHostSuffix = "." + BinaryName

// WebsiteBaseURL is the project website used for asset URLs.
// WebsiteBaseURL is the project website.
WebsiteBaseURL = "https://" + RepoName + ".sh"

// WebsiteAssetsURL is the base URL for icon/image assets.
WebsiteAssetsURL = WebsiteBaseURL + "/assets"
// WebsiteAssetsURL is the root URL for icon/image assets, served at WebsiteAssetsURL + "/<name>.svg".
WebsiteAssetsURL = "https://assets." + RepoName + ".sh"

// AgentDownloadBaseURL is the prefix under which versioned agent binaries are published.
AgentDownloadBaseURL = GitHubReleasesURL + "/download/"
Expand Down
2 changes: 1 addition & 1 deletion pkg/ide/ideparse/parse.go
Original file line number Diff line number Diff line change
Expand Up @@ -228,7 +228,7 @@ var AllowedIDEs = []AllowedIDE{
Name: config.IDEBob,
DisplayName: "IBM Bob",
Options: vscode.Options,
Icon: "https://devsy.sh/assets/bob.svg",
Icon: config.WebsiteAssetsURL + "/bob.svg",
Group: config.IDEGroupPrimary,
},
}
Expand Down
2 changes: 1 addition & 1 deletion providers/docker/provider.yaml
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
name: docker
version: v1.0.0
icon: https://devsy.sh/assets/docker.svg
icon: https://assets.devsy.sh/docker.svg
home: https://github.com/devsy-org/devsy
description: |-
Devsy on Docker
Expand Down
2 changes: 1 addition & 1 deletion providers/kubernetes/provider.yaml
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
name: kubernetes
version: v1.0.0
icon: https://devsy.sh/assets/kubernetes.svg
icon: https://assets.devsy.sh/kubernetes.svg
home: https://github.com/devsy-org/devsy
description: |-
Devsy on Kubernetes
Expand Down
2 changes: 1 addition & 1 deletion providers/podman/provider.yaml
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
name: podman
version: v1.0.0
icon: https://devsy.sh/assets/podman.svg
icon: https://assets.devsy.sh/podman.svg
home: https://github.com/devsy-org/devsy
description: |-
Devsy on Podman
Expand Down
2 changes: 1 addition & 1 deletion providers/pro/provider.yaml
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
name: devsy-pro
version: v1.0.0
icon: https://devsy.sh/assets/devsy.svg
icon: https://assets.devsy.sh/devsy.svg
home: https://github.com/devsy-org/devsy
description: Devsy Pro
options:
Expand Down
1 change: 1 addition & 0 deletions sites/assets-devsy-sh/.gitignore
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
node_modules/
8 changes: 8 additions & 0 deletions sites/assets-devsy-sh/netlify.toml
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
[build]
publish = "public"

[[headers]]
for = "/*"
[headers.values]
Access-Control-Allow-Origin = "*"
Cache-Control = "public, max-age=300"
101 changes: 101 additions & 0 deletions sites/assets-devsy-sh/public/antigravity.svg
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Loading