Skip to content

Auto-PR: feat(dashboard): embed can no longer answer every website; customer owns the allowlist#123

Merged
ncimino merged 3 commits into
mainfrom
feature/nik-embed-lockdown
Jul 23, 2026
Merged

Auto-PR: feat(dashboard): embed can no longer answer every website; customer owns the allowlist#123
ncimino merged 3 commits into
mainfrom
feature/nik-embed-lockdown

Conversation

@weown-bot

@weown-bot weown-bot commented Jul 23, 2026

Copy link
Copy Markdown
Contributor

🤖 Automated Pull Request — authored by weown-bot (ecosystem service account)

Opened by: @ncimino
Last pushed by: @ncimino
Branch: feature/nik-embed-lockdownmain

Contributors on this branch:


📋 Human Review Checklist — NIST CSF 2.0 Functions

Review per the 6 NIST CSF Functions. Frameworks referenced: NIST CSF 2.0, CIS Controls v8 IG1, CSA CCM v4, ISO/IEC 27001:2022, SOC 2, ISO/IEC 42001:2023. See docs/COMPLIANCE_ROADMAP.md.

🏛️ Govern (GV)

  • CODEOWNERS correct for affected paths (.github/CODEOWNERS)
  • ADR required/updated if an architectural decision is introduced
  • Policy impact considered and documented
  • All Copilot AI review comments addressed or explicitly deferred with rationale

🔍 Identify (ID)

  • New assets inventoried (Helm values, container images, dependencies)
  • SBOM regenerated if dependencies changed
  • Risk register / threat model touched if threat surface changed (.github/SECURITY_ASSESSMENT.md)

🛡️ Protect (PR)

  • Least privilege: RBAC, ServiceAccounts, scoped PATs (NIST PR.AC, CIS 5/6, ISO A.5.15-A.5.18)
  • Secrets managed via Infisical (never --from-literal, never /tmp, always $(mktemp) — ISO A.8.24)
  • NetworkPolicy present for new deployments (NIST PR.AC-5, CIS 12, CSA IVS)
  • TLS 1.3 with strong cipher suites where applicable (NIST PR.DS-1, CIS 3)
  • Container security: non-root UID 1000+, Pod Security restricted (NIST PR.IP, CIS 4)

🕵️ Detect (DE)

  • Logs / metrics added for new components (NIST DE.CM, CIS 8/13)
  • Alert rules updated if thresholds change
  • Health checks (livenessProbe + readinessProbe) configured

🚨 Respond (RS)

  • Runbook updated if operational behavior changes (.github/INCIDENT_RESPONSE.md)
  • Incident response impact considered (escalation paths, on-call)

♻️ Recover (RC)

  • Backup strategy covers new persistent data (NIST RC.RP, CIS 11, ISO A.8.13)
  • Rollback procedure tested or documented
  • DR impact assessed for new critical components

📚 Documentation & Versioning

  • Relevant CHANGELOG.md updated (per-directory or repo-level /CHANGELOG.md)
  • #WeOwnVer version bumped per docs/VERSIONING_WEOWNVER.md
  • READMEs / ADRs / inline comments updated

📝 Recent Commits (full bodies for Copilot context)

e9147d9 Merge branch 'main' into feature/nik-embed-lockdown

Author: Nik
Date: Thu Jul 23 11:51:19 2026 -0600


7912ad3 docs(compose): EMBED_REQUIRE_ALLOWLIST is inverted before v1.15.0

Author: Nik
Date: Wed Jul 22 22:14:56 2026 -0600

The flag landed 2026-06-06 with its condition backwards and was fixed
2026-06-17, so on v1.14.0/v1.14.1 leaving it UNSET makes a no-allowlist embed
refuse every origin, and SETTING it opens the embed to all of them — the exact
opposite of the intent. v1.15.0 (2026-06-25) is the first release with the
correct logic. Records which versions the flag can be trusted on.


1b998e1 feat(dashboard): embed can no longer answer every website; customer owns the allowlist

Author: Nik
Date: Wed Jul 22 20:41:41 2026 -0600

The public chat widget is the customer's brand and their capped LLM budget, but
AnythingLLM treats an embed with no allowlist as "allow every origin" —
parseAllowedHosts returns null and canRespond skips the origin check entirely.

Three changes:

EMBED_REQUIRE_ALLOWLIST="true" in compose.prod flips no-allowlist to deny-all,
so the failure mode becomes a visibly dead widget instead of a stranger quietly
spending the customer's budget. Upstream #5759 plus its 2026-06-17 inverted-logic
fix; older images ignore the variable harmlessly, which is why the explicit
allowlist set by provisioning remains the real protection.

The dashboard gains an "Authorized websites" panel, so a customer adding a second
site never needs an operator. Entries are normalised to scheme://host[:port] —
lower-cased, path and trailing slash stripped, wildcards refused — because ALLM
compares the browser Origin byte-for-byte, and the payload goes out as the
comma-separated string ALLM actually parses. The instance's own origin is always
retained, so the list can never be emptied back into the allow-any state. The
list persists in a new dashboard_state volume; provisioning seeds it via
EMBED_ALLOWLIST_DOMAINS.

The embed snippet now carries data-greeting with the AI disclaimer and
data-no-sponsor, so the PRD's non-negotiable disclosure is stated the moment the
widget opens rather than depending on the model remembering to say it.

Also fixes the /app/healthz false warning: the public health route was matched
before the base path was stripped, so it only ever answered the container's
internal /healthz — while Caddy routes only /app* to the dashboard, leaving the
public probe to fall through to the auth gate and return 401.

Verification: 16-check live test against a running dashboard + stub AnythingLLM
covering the public health probe, auth and CSRF gates, wildcard rejection,
case/path normalisation, self-origin retention, the empty-submission floor,
state-file persistence, the outgoing payload shape and the snippet contents;
node --check on the server and the dashboard's inline script; copier render +
YAML parse of the compose template; authed UI rendered and eyeballed.



🔍 Copilot AI Review: Copilot is configured to auto-request review for bot-authored PRs. If an auto-created PR opens without an initial Copilot review, push a follow-up commit to the same open PR (review_on_push: true) to trigger review automatically.

👥 Required Reviewers: human approval enforced by branch protection + CODEOWNERS. @ncimino requested automatically.

📚 Review Guidelines: .github/copilot-instructions.md (phase-aware compliance directives)

🛠️ Workflow Operations: .github/workflows/README.md

Auto-generated by .github/workflows/auto-pr-to-main.yml

…wns the allowlist

The public chat widget is the customer's brand and their capped LLM budget, but
AnythingLLM treats an embed with no allowlist as "allow every origin" —
parseAllowedHosts returns null and canRespond skips the origin check entirely.

Three changes:

EMBED_REQUIRE_ALLOWLIST="true" in compose.prod flips no-allowlist to deny-all,
so the failure mode becomes a visibly dead widget instead of a stranger quietly
spending the customer's budget. Upstream #5759 plus its 2026-06-17 inverted-logic
fix; older images ignore the variable harmlessly, which is why the explicit
allowlist set by provisioning remains the real protection.

The dashboard gains an "Authorized websites" panel, so a customer adding a second
site never needs an operator. Entries are normalised to scheme://host[:port] —
lower-cased, path and trailing slash stripped, wildcards refused — because ALLM
compares the browser Origin byte-for-byte, and the payload goes out as the
comma-separated string ALLM actually parses. The instance's own origin is always
retained, so the list can never be emptied back into the allow-any state. The
list persists in a new dashboard_state volume; provisioning seeds it via
EMBED_ALLOWLIST_DOMAINS.

The embed snippet now carries data-greeting with the AI disclaimer and
data-no-sponsor, so the PRD's non-negotiable disclosure is stated the moment the
widget opens rather than depending on the model remembering to say it.

Also fixes the /app/healthz false warning: the public health route was matched
before the base path was stripped, so it only ever answered the container's
internal /healthz — while Caddy routes only /app* to the dashboard, leaving the
public probe to fall through to the auth gate and return 401.

Verification: 16-check live test against a running dashboard + stub AnythingLLM
covering the public health probe, auth and CSRF gates, wildcard rejection,
case/path normalisation, self-origin retention, the empty-submission floor,
state-file persistence, the outgoing payload shape and the snippet contents;
node --check on the server and the dashboard's inline script; copier render +
YAML parse of the compose template; authed UI rendered and eyeballed.
@weown-bot
weown-bot requested a review from ncimino as a code owner July 23, 2026 02:42
@weown-bot
weown-bot requested review from Copilot and removed request for Copilot July 23, 2026 02:42
The flag landed 2026-06-06 with its condition backwards and was fixed
2026-06-17, so on v1.14.0/v1.14.1 leaving it UNSET makes a no-allowlist embed
refuse every origin, and SETTING it opens the embed to all of them — the exact
opposite of the intent. v1.15.0 (2026-06-25) is the first release with the
correct logic. Records which versions the flag can be trusted on.
Copilot AI review requested due to automatic review settings July 23, 2026 04:15

Copilot AI left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Copilot was unable to review this pull request because the user who requested the review has reached their quota limit.

Copilot AI review requested due to automatic review settings July 23, 2026 17:51

Copilot AI left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Copilot was unable to review this pull request because the user who requested the review has reached their quota limit.

@ncimino
ncimino merged commit 9bcc23d into main Jul 23, 2026
18 checks passed
@ncimino
ncimino deleted the feature/nik-embed-lockdown branch July 23, 2026 17:53
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.

3 participants