Skip to content

Security: resolve pre-release review findings (LAB-38…LAB-44) - #2

Merged
RoboDonut merged 6 commits into
mainfrom
security-review-fixes
Oct 2, 2026
Merged

RoboDonut merged 6 commits into
mainfrom
security-review-fixes

Conversation

@RoboDonut

Copy link
Copy Markdown
Contributor

Resolves the code-level findings from the Labs charter security review (LAB-37) at c7774eb.

Finding Fix
LAB-38 (High) cross-origin deploy/teardown Host + Origin gate on every request; /api/exec is now POST + application/json, streamed via fetch; 6 regression tests
LAB-39 (High) stored XSS via callback output esc() helper; every server-sourced value escaped before innerHTML
LAB-40 (Med) API key in Lambda env Secrets Manager secret + one-ARN GetSecretValue policy; fetched at cold start; teardown deletes it
LAB-41 (Med) unpinned cold-start pip install wherobots-python-sdk==0.4.0 pinned and vendored into the zips at deploy time
LAB-42 (Med) relay hardening Stage throttle (burst 10 / rate 5), generic errors (details → CloudWatch), 256 KB payload cap, trust model documented in README "Security model" section. Per-run out-of-band secret is not implemented: any secret delivered via job args has exactly the token's visibility — compensating controls documented for the sign-off reviewer
LAB-43 (Med) repo scanning dependabot.yml (pip + github-actions). Secret scanning / push protection / Dependabot-alert toggles need an org admin (this token has only maintain)
LAB-44 (Low) hygiene SRI + crossorigin on highlight.js, internal SCP comment removed, ManagedBy=wherobots-labs, bogus teardown policy delete removed

Verified: 22 unit tests green (7 new), bash -n on both scripts, vendoring resolves to manylinux wheels and imports, live request-gate smoke (spoofed Host 403 / foreign Origin 403 / GET exec 404 / text-plain POST 415 / evil-origin POST 403 / legit 200), dashboard exercised in a real browser with zero console errors (SRI hashes verified by the browser). Not yet verified live on AWS (local AWS session expired): the reworked 02_deploy.sh secret + vendored-zip flow needs one deploy→run→teardown cycle once credentials are refreshed.

🤖 Generated with Claude Code

- LAB-38: dashboard rejects spoofed Host / cross-origin requests; /api/exec
  is POST + application/json (streamed via fetch, no more GET CSRF vector)
- LAB-39: every server-sourced value HTML-escaped before innerHTML
- LAB-40: Wherobots API key moved to Secrets Manager; Lambdas fetch at cold
  start under a one-secret GetSecretValue policy; teardown deletes the secret
- LAB-41: wherobots-python-sdk pinned (0.4.0) and vendored into Lambda zips;
  no network install at cold start
- LAB-42: API Gateway stage throttled (burst 10 / rate 5); relay returns
  generic errors with details to CloudWatch; 256KB payload cap; trust model
  documented in the relay docstring and a README security section
- LAB-43: dependabot.yml for pip + github-actions (repo-setting toggles need
  an org admin — tracked in the Linear issue)
- LAB-44: SRI on highlight.js, internal SCP comment removed, ManagedBy tag
  now wherobots-labs, bogus teardown policy delete fixed
- tests: 7 new (request-gate regression suite + pinned-requirements check)
@linear-code

linear-code Bot commented Oct 2, 2026 •

Copy link
Copy Markdown
LAB-44 [Low] Hygiene: SRI on CDN script, internal SCP comment, ManagedBy tag, teardown policy name

Small fixes, bundled together:

  1. No SRI on highlight.js (dashboard/index.html:10-11). The script and stylesheet load from cdnjs without an integrity= attribute (a hash check on the downloaded file). A tampered copy would run in the page that has deploy and teardown power. Add integrity plus crossorigin="anonymous", or vendor the file.
  2. Internal infra detail (scripts/02_deploy.sh:182-183): "many orgs (this one included) block anonymous lambda:InvokeFunctionUrl via SCP". This discloses a Wherobots-internal control, which the charter's "no internal references" rule covers. Drop "(this one included)". The header comment at 02_deploy.sh:7-8 also still mentions a "public Function URL", which is stale since the relay now uses API Gateway.
  3. ManagedBy=claude-code tag on every AWS resource created in the user's account (02_deploy.sh:54-56, 192). The charter allows this, but it puts AI-tool branding into customers' AWS accounts. Change it to ManagedBy=wherobots-labs.
  4. Teardown policy mismatch (scripts/99_teardown.sh:65-66). It deletes ${RESOURCE_PREFIX}-sendtask-policy from lambda-role, which never had it. That policy is on relay-role, which is handled at :70. It's harmless because the "already gone" branch swallows it, but it should be removed.

LAB-38 [High] Dashboard accepts cross-origin requests: any website can trigger deploy/teardown or start job runs

Where: aws-step-functions-python-sdk/dashboard/server.py:224-260

The local dashboard server never checks the requesting site (Origin) or the Host header. Binding to 127.0.0.1 doesn't stop the user's own browser from sending it requests.

  • GET /api/exec?script=deploy|teardown|upload runs the lifecycle script. A hidden <img src="http://127.0.0.1:8321/api/exec?script=teardown"> on any page the user visits while the dashboard is running tears down their stack, and script=deploy rebuilds IAM, Lambda and API Gateway with the user's AWS credentials.
  • POST /api/start parses the body whatever the Content-Type, so a cross-site text/plain POST (which browsers send without asking first) starts Wherobots job runs, including hard_fail. That costs the user money.
  • Because Host isn't validated, a DNS-rebinding page (one that makes its own domain resolve to 127.0.0.1) can also read the responses: preflight, execution digests and outputs.

Reproduced locally (no .env, so nothing reached AWS):

  • a GET to /api/exec?script=upload with Origin: https://evil.example executed the script; it stopped only because .env was missing;
  • a text/plain POST to /api/start from that origin reached the start logic;
  • a request with Host: attacker.example:8321 got a preflight response back.

Fix:

  • Make /api/exec a POST. It can stream its output with fetch plus ReadableStream instead of EventSource.
  • Reject any request whose Origin isn't http://127.0.0.1:8321 / http://localhost:8321.
  • Allow only 127.0.0.1 / localhost as Host.
  • Require Content-Type: application/json on POSTs. Optionally add a random token printed at launch.

Review in Linear

Comment thread aws-step-functions-python-sdk/scripts/02_deploy.sh Outdated

@salty-hambot salty-hambot Bot left a comment •

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Reviewed by Salty Hambot 🤖🧂 at a93d9b2

No issues found.
💰 $0.2386 · 306.5k in / 4.5k out tokens · ⏱️ 1m2.9s · us.openai.gpt-6-sol high, prompt coverage-v1
💬 To request a re-review, comment @salty-hambot review

…letion, restore soft deletes, fail loudly after 2 minutes
@RoboDonut

Copy link
Copy Markdown
Contributor Author

@salty-hambot review

Comment thread aws-step-functions-python-sdk/lambdas/callback_relay/handler.py Outdated
@RoboDonut

Copy link
Copy Markdown
Contributor Author

@salty-hambot review

Comment thread aws-step-functions-python-sdk/scripts/02_deploy.sh
Comment thread aws-step-functions-python-sdk/dashboard/server.py
…ect JSON bodies with a 400 instead of hanging
@RoboDonut

Copy link
Copy Markdown
Contributor Author

@salty-hambot review

@RoboDonut
RoboDonut merged commit c3b0a89 into main Oct 2, 2026
2 checks passed
@RoboDonut
RoboDonut deleted the security-review-fixes branch October 2, 2026 18:43
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Development

Successfully merging this pull request may close these issues.

2 participants