Skip to content

Commit f8f6895

Browse files
committed
fix: address PR review feedback
- Fix import detection: use starts_with for from-line matching (no mid-line false positives) - Reject classic PATs (ghp_) early with clear error in both auth scripts - Fix base64url JWT decode for Azure OID extraction (proper padding + urlsafe) - Fix entrypoint.sh: disable set -e around agent run so output collection works on failure - Fix k8s-local-down: use lighter dependency check (no KVM required for teardown) - Fix k8s-infra-down: only require az CLI (not kubectl/envsubst) for teardown - Parameterise namespace in job manifests (uses NAMESPACE from envsubst) - Escape prompt for safe YAML embedding (backslashes, quotes, newlines) Note: edit_handler validation bypass (review comment #7) is a pre-existing issue requiring a larger refactor — tracked separately.
1 parent a2492b6 commit f8f6895

File tree

8 files changed

+78
-30
lines changed

8 files changed

+78
-30
lines changed

Justfile

Lines changed: 6 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -367,7 +367,7 @@ k8s-local-up: _k8s-check-local
367367
./deploy/k8s/local/setup.sh
368368

369369
# Tear down local KIND cluster and registry
370-
k8s-local-down: _k8s-check-local
370+
k8s-local-down: _k8s-check-common
371371
./deploy/k8s/local/teardown.sh
372372

373373
# Build and load image into local KIND cluster
@@ -406,8 +406,11 @@ k8s-local-run +ARGS:
406406
k8s-infra-up: _k8s-check-azure
407407
./deploy/k8s/azure/setup.sh
408408

409-
# Tear down all Azure resources
410-
k8s-infra-down: _k8s-check-azure
409+
# Tear down all Azure resources (only requires az CLI)
410+
k8s-infra-down:
411+
#!/usr/bin/env bash
412+
command -v az >/dev/null 2>&1 || { echo "Azure CLI (az) is required"; exit 1; }
413+
az account show >/dev/null 2>&1 || { echo "Please log in: az login"; exit 1; }
411414
./deploy/k8s/azure/teardown.sh
412415

413416
# Stop AKS cluster (save costs when not in use)

deploy/k8s/entrypoint.sh

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -5,9 +5,12 @@
55
# from the random temp dir to /output/ for kubectl cp retrieval.
66
set -e
77

8-
# Run HyperAgent with all provided arguments
8+
# Run HyperAgent with all provided arguments.
9+
# Disable -e so output collection still runs on agent failure.
10+
set +e
911
/app/dist/bin/hyperagent "$@"
1012
EXIT_CODE=$?
13+
set -e
1114

1215
# Copy output files from the fs-write temp dir to /output/
1316
# The fs-write plugin creates /tmp/hyperlight-fs-<random>/

deploy/k8s/manifests/hyperagent-job-keyvault.yaml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -10,7 +10,7 @@ apiVersion: batch/v1
1010
kind: Job
1111
metadata:
1212
name: ${JOB_NAME}
13-
namespace: hyperagent
13+
namespace: ${NAMESPACE}
1414
labels:
1515
app.kubernetes.io/name: hyperagent
1616
app.kubernetes.io/part-of: hyperagent

deploy/k8s/manifests/hyperagent-job.yaml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -8,7 +8,7 @@ apiVersion: batch/v1
88
kind: Job
99
metadata:
1010
name: ${JOB_NAME}
11-
namespace: hyperagent
11+
namespace: ${NAMESPACE}
1212
labels:
1313
app.kubernetes.io/name: hyperagent
1414
app.kubernetes.io/part-of: hyperagent

deploy/k8s/setup-auth-keyvault.sh

Lines changed: 29 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -76,12 +76,27 @@ if [ -z "$TOKEN" ]; then
7676
echo ""
7777
echo "Set GITHUB_TOKEN and re-run:"
7878
echo ""
79-
echo " 1. Create a PAT at https://github.com/settings/tokens"
80-
echo " → Click 'Generate new token (classic)'"
81-
echo " → No extra scopes needed"
79+
echo " 1. Create a fine-grained PAT at https://github.com/settings/personal-access-tokens/new"
80+
echo " → Do not use 'Generate new token (classic)'"
81+
echo " → No special permissions needed"
82+
echo " → Classic PATs (ghp_) do NOT work — must be fine-grained (github_pat_)"
8283
echo ""
8384
echo " 2. Run:"
84-
echo " GITHUB_TOKEN=ghp_your_token_here just k8s-setup-auth-keyvault"
85+
echo " GITHUB_TOKEN=github_pat_your_token_here just k8s-setup-auth-keyvault"
86+
echo ""
87+
exit 1
88+
fi
89+
90+
# ── Reject classic PATs early ────────────────────────────────────────
91+
92+
if [[ "$TOKEN" == ghp_* ]]; then
93+
log_error "Classic GitHub personal access tokens (ghp_) are not supported by the Copilot SDK."
94+
echo ""
95+
echo "Create a fine-grained personal access token instead:"
96+
echo " https://github.com/settings/personal-access-tokens/new"
97+
echo ""
98+
echo "Then re-run with:"
99+
echo " GITHUB_TOKEN=github_pat_your_token_here just k8s-setup-auth-keyvault"
85100
echo ""
86101
exit 1
87102
fi
@@ -93,10 +108,17 @@ log_step "Storing token in Key Vault..."
93108
# Grant current user permission to set secrets.
94109
# Extract user OID from the ARM access token JWT — avoids Graph API dependency
95110
# (az ad signed-in-user / az role assignment --assignee both need Graph scope).
111+
# JWT payloads use base64url encoding (- instead of +, _ instead of /, no padding)
112+
# so we use python to handle the decoding correctly.
96113
CURRENT_USER_OID=$(az account get-access-token --query "accessToken" -o tsv 2>/dev/null \
97-
| cut -d. -f2 \
98-
| base64 -d 2>/dev/null \
99-
| python3 -c "import sys,json; t=json.load(sys.stdin); print(t.get('oid',''))" 2>/dev/null \
114+
| python3 -c "
115+
import sys, json, base64
116+
token = sys.stdin.read().strip().split('.')[1]
117+
# Add padding for base64url
118+
padded = token + '=' * (4 - len(token) % 4)
119+
payload = json.loads(base64.urlsafe_b64decode(padded))
120+
print(payload.get('oid', ''))
121+
" 2>/dev/null \
100122
|| true)
101123

102124
KEYVAULT_ID=$(az keyvault show --name "${KEYVAULT_NAME}" --query id -o tsv)

deploy/k8s/setup-auth.sh

Lines changed: 16 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -36,20 +36,30 @@ if [ -z "$TOKEN" ]; then
3636
echo ""
3737
echo "Set GITHUB_TOKEN and re-run:"
3838
echo ""
39-
echo " 1. Create a PAT at https://github.com/settings/tokens"
40-
echo "Click 'Generate new token (classic)'"
41-
echo " → No extra scopes needed"
39+
echo " 1. Create a fine-grained PAT at https://github.com/settings/personal-access-tokens/new"
40+
echo "Do not use 'Generate new token (classic)'"
41+
echo " → No special permissions needed"
4242
echo ""
4343
echo " 2. Run:"
44-
echo " GITHUB_TOKEN=ghp_your_token_here just k8s-setup-auth"
44+
echo " GITHUB_TOKEN=github_pat_your_token_here just k8s-setup-auth"
4545
echo ""
4646
exit 1
4747
fi
4848

4949
# ── Validate token format ────────────────────────────────────────────
5050

51-
if [[ ! "$TOKEN" =~ ^(ghp_|gho_|github_pat_|ghu_) ]]; then
52-
log_warning "Token doesn't match known GitHub token formats (ghp_, gho_, github_pat_, ghu_)"
51+
if [[ "$TOKEN" =~ ^ghp_ ]]; then
52+
log_error "Classic GitHub personal access tokens (ghp_) are not supported by the Copilot SDK."
53+
log_error "Please create a fine-grained personal access token and re-run this script."
54+
echo ""
55+
echo "Create one at: https://github.com/settings/personal-access-tokens/new"
56+
echo "Then: GITHUB_TOKEN=github_pat_... just k8s-setup-auth"
57+
echo ""
58+
exit 1
59+
fi
60+
61+
if [[ ! "$TOKEN" =~ ^(gho_|github_pat_|ghu_) ]]; then
62+
log_warning "Token doesn't match known GitHub token formats (gho_, github_pat_, ghu_)"
5363
log_warning "Proceeding anyway — the Copilot SDK will validate it at runtime"
5464
fi
5565

scripts/hyperagent-k8s

Lines changed: 6 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -109,7 +109,7 @@ log_info " Timeout: ${TIMEOUT}s"
109109
[ -n "$INPUT_DIR" ] && log_info " Input: ${INPUT_DIR}"
110110

111111
# Export vars for envsubst
112-
export JOB_NAME IMAGE PROMPT EXTRA_ARGS
112+
export JOB_NAME IMAGE PROMPT EXTRA_ARGS NAMESPACE
113113

114114
# Select manifest based on auth method (Key Vault vs K8s Secret)
115115
if kubectl get secretproviderclass hyperagent-keyvault -n "${NAMESPACE}" &>/dev/null; then
@@ -129,8 +129,12 @@ if [ -n "$INPUT_DIR" ]; then
129129
PROMPT="${PROMPT} Input files are available at /input/ — enable fs-read with baseDir=/input to access them."
130130
fi
131131

132+
# Escape prompt for safe YAML embedding (double-quoted string).
133+
# Replace backslashes first, then double quotes, then newlines.
134+
PROMPT=$(printf '%s' "$PROMPT" | sed 's/\\/\\\\/g; s/"/\\"/g' | tr '\n' ' ')
135+
132136
# Render the job manifest (only substitute our specific variables)
133-
MANIFEST=$(envsubst '${JOB_NAME} ${IMAGE} ${PROMPT} ${EXTRA_ARGS}' < "${JOB_TEMPLATE}")
137+
MANIFEST=$(envsubst '${JOB_NAME} ${IMAGE} ${PROMPT} ${EXTRA_ARGS} ${NAMESPACE}' < "${JOB_TEMPLATE}")
134138

135139
# If --input-dir is set, inject an init container and input volume into the manifest.
136140
# The init container waits for a .ready marker file, which we create after kubectl cp.

src/code-validator/guest/runtime/src/js_parser.rs

Lines changed: 15 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -509,16 +509,22 @@ pub fn extract_all_imports(source: &str) -> Vec<String> {
509509
imports.push(String::from(stmt.specifier));
510510
}
511511
// Also check for `from "..."` pattern on continuation lines.
512-
// Only match when `from` is at the start of the line or after `}`
513-
// (e.g. ` } from "ha:pptx"` or ` from "ha:pptx"`).
514-
// This avoids false positives from prose inside string literals
515-
// like: `"an AI that builds documents from 'impressive demo'"`
516-
if (trimmed.starts_with("from ")
517-
|| trimmed.contains("} from ")
518-
|| trimmed.contains("}from "))
519-
&& let Some(from_pos) = trimmed.find("from ")
512+
// Only match when `from` is at the START of the trimmed line or
513+
// immediately after `}` at the start. Using `starts_with` prevents
514+
// false positives from `from` appearing mid-line inside strings.
515+
if trimmed.starts_with("from ")
516+
|| trimmed.starts_with("} from ")
517+
|| trimmed.starts_with("}from ")
520518
{
521-
let rest = &trimmed[from_pos + 5..];
519+
let rest = if trimmed.starts_with("from ") {
520+
&trimmed[5..]
521+
} else if trimmed.starts_with("} from ") {
522+
&trimmed[7..]
523+
} else {
524+
// "}from "
525+
&trimmed[6..]
526+
};
527+
522528
if let Ok((_, specifier)) = string_literal(rest.trim())
523529
&& !imports.iter().any(|s: &String| s == specifier)
524530
{

0 commit comments

Comments
 (0)