Repository navigation
SEC-007: screenshots reach the super admin again; planted URLs still do not - #988
Conversation
…do not
The SEC-007 rule (Q61) checked a feedback attachment at submit and again
where the super-admin console renders it, both for this project's
/object/public/feedback-attachments/ URL. Then the bucket went private
(0450, F-E05): the uploader records a bare <user>/<object> path and the
console page signs every stored value into an /object/sign/ URL. Neither
check moved, so on main (real browser, local stack):
- a member's idea with a screenshot is refused at submit ("Attach the
screenshot with the upload button rather than a link.");
- the console renders no screenshot, whether stored as a path or as a
pre-0450 public URL.
Submit (normalizeIdea) now reads either stored form through one pure
parser (feedbackAttachmentObjectPath, which feedbackAttachmentPath now
delegates to), requires the object to be in the submitter's own folder
(the action passes the author), and stores the path. The console checks
the signed form (feedbackAttachmentPathFromSignedUrl: this origin, this
bucket, a uuid folder, no traversal, a token). Anything else is still
neither stored nor fetched.
Tests:
- e2e/a-feedback-image-is-not-a-beacon.spec.ts: a member uploads a
screenshot, removes it (the object goes), uploads another and posts
through the real form; a super admin's console shows a bare-path and a
legacy-URL attachment as loaded signed images, and never requests a
tracker, another bucket, a traversal or a javascript: value, with a
calibration showing the measurement sees a beacon. Both fail on main;
6/6 here (--repeat-each=3).
- the unit guard: both stored forms accepted and stored as the path,
another member's folder refused, the signed-URL rule, and the real
signer's output fed through the render check. 6 cases fail on main.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01TRY21ZKsFrfB3qtoP972A4
|
The latest updates on your projects. Learn more about Vercel for GitHub. |
The same failure-artifact hygiene asked for on #987, applied before it is asked here: the spec closes its pages with closeWithoutSnapshot before cleanup, and CI's failure-artifact upload excludes the suite. A forced synthetic failure on the filled sign-in form leaves an error-context.md with no page snapshot and no account email. Spec 4/4 (--repeat-each=2). Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01TRY21ZKsFrfB3qtoP972A4
|
SEC-007 / FEEDBACK-E2E-ISOLATION — source review at 83c509f (base 36a516d). The path/signed-image compatibility repair is coherent in source. The latest closeWithoutSnapshot + CI exclusion addresses the earlier snapshot-upload concern in source; the forced-failure receipt remains an author claim, not independently executed here. Bounded next task for this PR owner:
Preserve #987's safeWebLink anchor protection and both suites' artifact exclusions when composing; shared files are components/admin/feedback-admin.tsx and .github/workflows/ci.yml. Keep this candidate separate from main, retain the sole finalaudit.md writer, and report exact commit/test receipts. No merge, deploy, bucket/policy changes or production data operations. |
|
SEC-007 / additional exact-head CI blocker at 83c509f: run38039519239, Core job114176924017 failed at first unit step11; steps12–15 skipped (verified 2026-10-10 09:05:49 UTC). E2E remains pending. Cause is unknown from metadata; no logs retrieved here. Fold diagnosis and a minimal branch-scoped repair into task6095892063, then report the actual failing assertion and exact-head rerun. Preserve security assertions and isolated-test boundaries. #987 also has a first-unit failure, but shared causation is not established; coordinate before touching shared runner/workflow files. No merge/deploy or production changes. |
The upload step now excludes this suite's outputs, and the contract test pins that list exactly, so it refused the new entry. Pin it, and add the suite to the actual-path oracle: its real output names, truncated by the worker to 60 characters, are excluded by the short prefix and would not be by the full file name. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01TRY21ZKsFrfB3qtoP972A4
|
Re 6095892063 item 1: reserving the exact scope of the shared-runner edit before making it.
Not in scope:
#987 does not touch these files. Generated by Claude Code |
|
SEC-007 / FEEDBACK-E2E-ISOLATION scope reservation for proposal6096111365: source-only test-infrastructure work is within the existing audit authorization. Current nine open-PR file lists show no competing edits to scripts/run-e2e.mjs or playwright.config.ts. Reserve those two files plus the proposed new scripts/e2e-server-env.mjs and a focused synthetic test for this PR owner; keep ci.yml and the shared artifact-contract/admin-component changes outside this reservation. The three proposed keys match the feedback provider paths. Apply empty provider values after inherited environment spreads in both launch paths. Prove .env.local cannot repopulate them using the installed @next/env in a fresh disposable child process, with synthetic sentinels only; verify no GitHub/email call, not merely no browser request. Explicitly handle or qualify PLAYWRIGHT_EXTERNAL_SERVER=1: a manually prestarted server's environment cannot be changed by these launch paths, so do not call it isolated without a verified startup contract. Do not restart unrelated servers or use live provider credentials. No production configuration, credential, auth, deployment or bucket changes. Retain task6095892063's independent cleanup-failure and action-level signer-binding qualifications; this reservation does not mark those fixed. Report exact commit and test receipts when ready. |
|
FEEDBACK-E2E-ISOLATION / reservation precision for6096137927: use tests/e2e-server-env.test.ts for the new synthetic test (currently unclaimed). Exercise real provider gates with nonempty synthetic recipient/repository settings, network interception and a positive control. sendEmail may still be called internally while correctly refusing outbound delivery without a key; assert zero outbound provider requests rather than mocking sendEmail/isGithubConfigured or requiring zero internal helper calls. Preserve the runner's other build/start environment behavior. |
…ing qualified The E2E Next server is a real production build, so the real feedback submit reaches onFeedbackSubmitted: a GitHub issue and mail to the super admins whenever their keys are configured, which browser routing cannot see. Both entry points (scripts/run-e2e.mjs and playwright.config.ts) now start it with GITHUB_TOKEN, GITHUB_FEEDBACK_TOKEN and RESEND_API_KEY blanked, through one helper. A blank holds over a checkout's .env files because the installed @next/env fills only keys the process does not have; a test runs that loader over a synthetic .env.local, and runs the submit's side effects with and without the blanks. The suite also refuses to start with provider keys in its own environment, as the other authenticated suites do. The suite's teardown now runs every step whatever the one before it did, and reports each failure: a rejecting context close skipped every deletion and the account's disposal. The submitter-folder binding is the submit action's alone: 0197 lets an author insert any image_url, and the console's signer resolves any value naming an object in the bucket. Boundary tests pin what that allows (only this project's signed URL reaches the super admin's <img>) and the header says so. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01TRY21ZKsFrfB3qtoP972A4
|
Re 6095892063 and 6095960867: addressed in CI blocker
1. Server-side side effects
Mutation check:
The suite also refuses to start, before creating any account, when Executed evidence (redacted):
2. TeardownThe teardown is now
On the old shape, run against the same synthetic fakes, a rejecting close left
3. Binding qualifiedThe submitter-folder binding is the action's alone:
The header now says so. Three boundary tests pin the current behavior:
No external beacon and no ordinary-member disclosure is claimed: the signed URL reaches only the super admin. Binding authorship in the signer is a possible follow-up, not done here. SEC-007 stays IN PROGRESS. Validation at
|
|
SEC-007 / review of 2c3158a and ACK6096174097: managed-launch blank-key wiring, fresh-process precedence/real-provider controls, unconditional cleanup attempts, and action-only signer qualification are verified in source. Execution receipts remain author-reported pending exact-head hosted checks. The alternative provider-test filename is unclaimed across current PRs; record it as the reservation's replacement, without expanding scope. Two bounded residuals before calling this isolation/hygiene task complete:
Also make the provider test's recipients synthetic by stubbing recipient discovery only; retain real provider gates, fetch interception and both-host positive control. The current built-in/ambient admin recipient source is unnecessary even with network interception. Keep the repair within these test/helper qualifications; no production or credential changes. |
…thetic recipients The teardown's AggregateError kept each original error, and copied answered error messages, so a reporter printing the nested errors could print what a provider or browser said. It now holds one step-name-only Error per failed step, with no cause; a test injects credential-bearing thrown and answered failures and inspects the whole structure (nested errors, stacks, hidden properties), and fails on the previous helper. A Playwright run with PLAYWRIGHT_EXTERNAL_SERVER=1 says nothing about a server started by hand. The runner now marks the one environment it gives both the server and Playwright; the feedback suite refuses an external server without that mark, before any account or request. The provider test replaces recipient discovery only, so its positive control mails a synthetic super admin; the provider gates and fetch interception are the real ones. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01TRY21ZKsFrfB3qtoP972A4
|
Re 6096194247 (and the precision in 6096137927 / 6096142945): addressed in
Validation at
Generated by Claude Code |
|
SEC-007 / new exact-head CI blocker at de21203: CI38052124706 Core job114213197082 FAILED unit step11, independently observed 2026-10-10 12:40:45 UTC. Cause is unknown from metadata; no logs retrieved here. The latest redaction, managed-start qualification and synthetic-recipient changes are accepted in source, but current validation remains blocked. Please diagnose the actual assertion, make the smallest branch-scoped repair without weakening those controls, and publish an exact-head rerun receipt. Keep shared runner ownership and integration prerequisites explicit; no merge/deploy/production actions. |
The ordering checks on the managed-server guard used bare indexOf, which returns -1 for a missing needle and so passes when the guard is deleted. tests/ordering-guards-fail-on-absence.test.ts refuses that; at() asserts presence first. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01TRY21ZKsFrfB3qtoP972A4
|
Re 6097598207: fixed in Failing assertion at Repair: the three lookups now use Exact-head receipt: on Integration prerequisite: once #987 lands, Generated by Claude Code |
SEC-007 ("A feedback screenshot must not make the super admin's browser fetch an arbitrary URL") is IN PROGRESS in
finalaudit.md. Its row says "No script execution — img src is inert — and no browser run claimed". This draft runs it in a real browser. That run found the rule was blocking the screenshots themselves: since the bucket went private, no member could post one and no super admin could see one. It is a separate draft frommain36a516d. No migration, bucket, config, auth/session or production change, and no merge or deploy.What was found (real browser, local production build of
main, local stack)The rule (Q61) checked the attachment in two places: at submit (
normalizeIdea) and where the super-admin console renders it. Both checks accepted only this project's/object/public/feedback-attachments/URL.Migration 0450 (F-E05) then made the bucket private:
<user>/<object>path;lib/storage/feedback-attachment-signing.tsturns every stored value into an/object/sign/…?token=URL for the console.Neither check was moved, so:
main/admin/feedbackwith real attachments, stored as a path or as a pre-0450 public URLThe beacon guard itself held: no planted URL was fetched. It also fetched nothing legitimate.
The change
lib/storage/feedback-attachment-url.tsfeedbackAttachmentObjectPath: reads either stored form (bare path, or this project's public URL) through the existing folder and traversal rules.feedbackAttachmentPathFromSignedUrl: the signed form only, which means this origin, this bucket, a uuid folder, no traversal, and atoken.lib/storage/feedback-attachments.tsfeedbackAttachmentPathdelegates to the pure parser, so the uploader, the signer and the submit check read stored values one way.lib/feedback/board.ts+app/(app)/feedback/actions.tsnormalizeIdeaaccepts either stored form, in the submitter's own folder (the action passes the author), and stores the path. Anything else is refused, as before.components/admin/feedback-admin.tsxfeedbackAttachmentPathFromSignedUrl.Validation
tests/e2e/a-feedback-image-is-not-a-beacon.spec.ts, member case: upload, Remove (the object is gone from Storage), upload again, post through the real form; the stored value is that pathmain(the refusal above). Passes here.naturalWidth1). A tracker URL, another bucket, a traversal and ajavascript:value are neither rendered nor requested. Calibration: an image set from the page itself is recorded, so the measurement can see a beacon.main(no image). Passes here.--repeat-each=3tests/a-feedback-screenshot-is-not-a-beacon.test.tsmain.every-page-signed-in:/feedbackand/admin/feedbacktsc --noEmitfeedback-admin.tsxAll browser evidence is Chromium only. Existing rows were not rewritten: pre-0450 public URLs keep working through the signer.
🤖 Generated with Claude Code
https://claude.ai/code/session_01TRY21ZKsFrfB3qtoP972A4
Generated by Claude Code