Skip to content

gpt-6.1-sol, security and reliability fixes, working deploys - #3

Merged
hugbubby merged 15 commits into
stagingfrom
gpt-6.1-sol-hardening
Oct 3, 2026
Merged

hugbubby merged 15 commits into
stagingfrom
gpt-6.1-sol-hardening

Conversation

@hugbubby

@hugbubby hugbubby commented Oct 3, 2026

Copy link
Copy Markdown
Contributor

Switches investigations to gpt-6.1-sol, fixes the security and correctness bugs found in a full review, makes the extension work on the live sites again, and repairs the deploy pipeline (broken since March).

Commits are by package, so intermediate commits don't build on their own. The final tree passes typecheck, lint/knip/prettier, 680 unit, 99 integration, migration drift, build, extension e2e 21/21 and frontend e2e 13/13 (both headless). The API and frontend images run as non-root with both migrations. A live smoke test against gpt-6.1-sol passed: pnpm --filter @openerrata/api smoke:openai.

What changes

  • Model: gpt-6.1-sol is set in one request config, api/src/lib/investigators/openai-request-config.ts.
    • Investigation.model records the model that actually ran, written at completion. It used to be a hardcoded OPENAI_GPT_5.
    • The audit trail has one record per OpenAI request.
    • The worker refuses to start if OpenAI rejects the request shape.
  • API security:
    • Post URLs and authors come from server-verified data, which closes a javascript: link injection on the public site.
    • User OpenAI keys are verified first, can't take over server-paid runs, and are dropped rather than marking the post FAILED.
    • Image fetches and fetch_url are SSRF-safe.
    • Re-queuing no longer overwrites audit history.
  • Queue:
    • At most SELECTOR_DAILY_BUDGET selector admissions per UTC day, matching SPEC; it was per 5-minute run.
    • Lease ⇔ PROCESSING is enforced by the database, and losing the lease aborts the run.
    • The selector uses update lineage.
  • Extension 0.4.0:
    • Fixes posts stuck on "investigating", duplicate injection, and highlight markup leaking into submitted HTML.
    • Fixes extraction on logged-out X and the Substack free-unlock paywall.
    • Excludes boilerplate on every Wikipedia language and Substack page chrome, and <br> now separates words.
  • Interim carry-forward: a changed post keeps showing the earlier corrections whose exact text is still on the page, on every platform. SPEC §2.8/§2.9 are updated.
  • Website:
    • Responses are validated against the shared schemas.
    • Real 404/502 pages.
    • Only http(s) links, no remote images, and a CSP.
  • Deploy:
    • GitHub OIDC (AWS_DEPLOY_ROLE_ARN), Tailscale (tag:ci) and aws eks update-kubeconfig replace the static AWS keys and KUBE_CONFIG_DATA.
    • Private RDS (ingress 10.0.0.0/16, over the existing VPC peering).
    • No public bucket policy, non-root pods, and pods wait for migrations.
    • Deploys are serialized per stack without cancellation.
  • Local dev: Postgres 17, and Versity S3 Gateway in place of MinIO, which can no longer be pulled.

Migrations

  • 0024: Investigation.model becomes the recorded provider id, and the audit moves to InvestigationAttemptRequest rows. Existing audits become one LEGACY_COMBINED request each. It aborts if any COMPLETE investigation lacks exactly one SUCCEEDED attempt.
  • 0025: adds investigation admission origin/time, deferred lease-invariant triggers and input snapshot fields. It repairs non-https post URLs and drops unused columns.

Already done outside the repo

  • IAM: role openerrata-github-actions-deploy, its policy, and an EKS access entry mapping it to group openerrata-ci.
  • Cluster: group RBAC applied, and the old ServiceAccount token revoked.
  • Repo variables: AWS_DEPLOY_ROLE_ARN and PULUMI_MANAGED_DATABASE_{PUBLICLY_ACCESSIBLE=false, INGRESS_CIDRS=10.0.0.0/16, ENGINE_VERSION=17}. 17.9 is the current default minor, so this causes no restart.
  • Secrets: TS_OAUTH_CLIENT_ID and TS_AUDIENCE are set.

Rollout

  1. Merge into staging. This deploys staging and is the first test of the new deploy path.
  2. Upload extension 0.4.0 to the stores with publishing deferred.
  3. Once the store approves it, promote staging → main and publish 0.4.0 at the same time. The API's minimum supported extension version becomes 0.4.0, and the new extension needs the new API.
  4. After main deploys cleanly, delete the AWS_ACCESS_KEY_ID, AWS_SECRET_ACCESS_KEY, AWS_REGION and KUBE_CONFIG_DATA secrets and the openerrata-ci IAM user. The commands are in the README.

Behavior changes

  • Fewer automatic investigations: at most 100 per day.
  • The extraction fixes give many already-investigated posts a new content version. Carry-forward keeps their still-applicable corrections visible until the selector re-checks them.
  • Paid Substack posts are skipped even for paying subscribers.
  • Laptops lose direct database access, except via the tailnet.
  • Staging's deactivated image-upload key is reactivated by the first deploy.

🤖 Generated with Claude Code

hugbubby and others added 15 commits March 5, 2026 21:25
- http(s)-only URL schema for every link field
- View statuses carry investigationId; image URLs travel only as occurrences;
  per-platform external ids; Wikipedia URL identity as a PAGE_ID | TITLE union
- Typed content-script/background message maps; drop the protocol `v` field
- Public output schemas: provenance-discriminated origin, typed provider
- Drop the InvestigationModel enum (model is recorded as the provider id)
- Normalization: <br>/<hr> separate words; Wikipedia boilerplate excluded by
  structural markers and localized section titles

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Investigator
- gpt-6.1-sol in one request config (web_search with sources, verified
  reasoning summaries); OPENAI_MODEL_ID removed
- Investigation.model is the model that ran, recorded at completion
- One audit record per provider request (fact-check rounds, validations);
  typed SDK responses; incomplete responses fail without retries
- One request-shaped probe for worker startup and user-key validation;
  live smoke script (pnpm smoke:openai)

Security and correctness
- Post URL/author from server-verified data; client URLs validated
- User OpenAI keys verified before use, never take over server-paid runs,
  and are dropped (not FAILED) when OpenAI rejects them
- SSRF-safe fetching (validated, pinned addresses) for images and fetch_url
- FAILED is terminal; attempt numbers never reset; audit is insert-only
- recordViewAndGetStatus reports INVESTIGATING/FAILED with investigationId
- Interim claims carry forward from the latest finished investigation,
  limited to claims whose text is still in the version

Queue
- Selector admits at most SELECTOR_DAILY_BUDGET investigations per UTC day
- Lease ⇔ PROCESSING enforced by deferred triggers; one recovery path;
  lease loss aborts the run; update lineage shared with the selector

Migrations 0024 (model + per-request audit) and 0025 (admission origin,
lease invariant, input snapshot). Removes the unused attestation HMAC and
public tRPC router.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
- Resume polling for posts investigated by others (INVESTIGATING + id)
- Idempotent injection with a side-effect-free PING; distinct
  GET_VISIBILITY and background-driven LOCATION_CHANGED; unique session ids
- Error replies delivered; only real context invalidation resets silently
- One text index for adapters, claim mapping and mutation baseline;
  highlight marks never leave the page
- Claim markdown renders no images; typed message maps end to end
- Settings load as a union; an invalid API URL is an error, not a fallback
- Live-site fixes: logged-out X, Substack isAccessibleForFree paywall,
  fr.wikipedia cachelinks, Wikipedia video detection, LessWrong /w/ tags;
  Substack subscribe/share/embed chrome excluded
- Remove the unused attestation secret; http://*/* optional; Firefox 128+
- e2e suite runs headless (Chromium new headless, no xvfb)

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
- Parse every GraphQL response with the shared public schemas
- SvelteKit error()/+error.svelte (404/400/502) instead of errors as data;
  API_BASE_URL checked at startup
- Only http(s) links; no images in reasoning markdown; Content Security Policy
- Playwright runs headless on the full Chromium build

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
- Deploy job assumes AWS_DEPLOY_ROLE_ARN via GitHub OIDC, joins the tailnet
  (tag:ci) and builds its kubeconfig with `aws eks update-kubeconfig`;
  static AWS keys and KUBE_CONFIG_DATA are gone
- src/aws/ci-iam/setup.sh bootstraps the role, its least-privilege policy and
  an EKS access entry (group openerrata-ci); src/kubernetes/ci-rbac binds the
  group and revokes the old ServiceAccount token
- Managed RDS public access, ingress CIDRs and engine version are explicit
  config; bucket public-read policy removed, public access fully blocked;
  blob writer key status declared Active
- Deploys serialized per stack without cancellation; pods wait for
  migrations; config checksums restart pods; non-root pods and images
- Extension store publishing fails loudly on missing secrets; actions,
  Node and pnpm pinned

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
- docker-compose: Postgres 17 (matches CI) and Versity S3 Gateway in place
  of the no-longer-pullable MinIO images
- api/.env.example replaces the root example and boots the app as-is
- packageManager pins pnpm; lockfile for the new frontend/api dependencies
- SPEC, README, AGENTS and PRIVACY updated for all of the above

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Restores the shared package's function-coverage threshold (CI: 89% < 90%).

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
@hugbubby
hugbubby merged commit fa14c1a into staging Oct 3, 2026
10 checks passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant