Merge branch 'dev' into ai-analytics

This commit is contained in:
Mantra 2026-05-23 11:50:14 -07:00 committed by GitHub
commit 45fd5ad7da
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
44 changed files with 84 additions and 44 deletions

View File

@ -541,3 +541,6 @@ A: Put restricted-user docs at `docs-mintlify/guides/apps/authentication/restric
## Q: How should e2e tests switch to a newly created project?
A: `Project.createAndSwitch` should leave `backendContext.projectKeys` set to real project API keys, not only `{ projectId, adminAccessToken }`. Internal admin access tokens are regular short-lived access tokens; keeping one in the default project context makes later server/admin requests fail with `ADMIN_ACCESS_TOKEN_EXPIRED` or validate the token against the wrong project.
## Q: How should backend SMTP SSRF checks be rolled out?
A: Keep the real outbound SMTP policy in `apps/backend/src/private/implementation/smtp-egress-policy.ts`, export it through `apps/backend/src/private/index.ts`, and provide a simple `implementation-fallback` function for self-hosters. It should allow only SMTP ports 25, 465, 587, 2465, 2587, and 2525, reject internal IP literals or DNS resolutions, and initially run report-only from `emails-low-level.tsx` via `captureError("smtp-egress-policy-report-only", ...)` before enforcing hard failures.

View File

@ -116,6 +116,8 @@ To see all development ports, refer to the index.html of `apps/dev-launchpad/pub
- NEVER INSTALL A NEW PACKAGE (or anything else) WITHOUT EXPLICIT APPROVAL FROM THE USER.
- A "development environment" is either an RDE (remote development environment; = local dashboard + prod backend) or a local emulator (local dashboard + local backend). When communicating to the user, we always say "development environment" instead of RDE or local emulator (the distinction to the user is minor, even though the implementation is quite different).
- NEVER EVER return a server error with an internal server error that may contain information that the user shouldn't see. For example, never return the error message on a public API from an upstream provider without properly filtering it first. Most of the time, for internal server errors, you should just use StackAssertionError (which won't pass the message to the user), not StatusError (you almost never want to instantiate a StatusError with status 5xx).
- When adding code to the `private` part of the backend, put the actual implementation into `implementation` (if the submodule is checked out), and implement a simple fallback in `implementation-fallback` for self-hosters. `implementation.generated.ts` will automatically be generated, which you can then import from `index.ts`. (See the existing code as an example.) If the submodule isn't checked out, but you need to add code to the `private` part of the backend, let the user know.
- Security-sensitive code on the backend that shouldn't be public should be in the `private` part of the backend.
### Code-related
- Use ES6 maps instead of records wherever you can.

View File

@ -1,6 +1,6 @@
{
"name": "@stackframe/backend",
"version": "2.8.102",
"version": "2.8.103",
"repository": "https://github.com/hexclave/stack-auth",
"private": true,
"type": "module",

View File

@ -10,6 +10,7 @@ import { runAsynchronously, wait } from '@stackframe/stack-shared/dist/utils/pro
import { Result } from '@stackframe/stack-shared/dist/utils/results';
import { traceSpan } from '@stackframe/stack-shared/dist/utils/telemetry';
import nodemailer from 'nodemailer';
import { checkSmtpEgressPolicy } from '@/private';
export function isSecureEmailPort(port: number | string) {
// "secure" in most SMTP clients means implicit TLS from byte 1 (SMTPS)
@ -76,10 +77,27 @@ async function _lowLevelSendEmailWithoutRetries(options: LowLevelSendEmailOption
return await traceSpan('sending email to ' + JSON.stringify(toArray), async () => {
try {
const smtpEgressPolicyResult = await checkSmtpEgressPolicy({
host: options.emailConfig.host,
port: options.emailConfig.port,
});
if (smtpEgressPolicyResult.status === "error") {
console.warn("SMTP config rejected by the egress policy.", {
violation: smtpEgressPolicyResult.violation,
config: strippedEmailConfig,
});
captureError("smtp-egress-policy-report-only", new StackAssertionError("SMTP config would be rejected by the egress policy", {
violation: smtpEgressPolicyResult.violation,
config: strippedEmailConfig,
}));
}
const transporter = nodemailer.createTransport({
host: options.emailConfig.host,
port: options.emailConfig.port,
secure: options.emailConfig.secure,
disableFileAccess: true,
disableUrlAccess: true,
connectionTimeout: 15000,
greetingTimeout: 10000,
socketTimeout: 20000,

@ -1 +1 @@
Subproject commit b05bcca3444c00fa6623f7eec332376031aefd2c
Subproject commit a815ddcd1354d4bf27042626d1035709c80abdc6

View File

@ -1,6 +1,7 @@
import { AiProxyBodyProcessor } from "@/lib/ai/proxy-preprocessing";
import { SignUpRiskEngine } from "@/lib/risk-scores";
import { createNeutralSignUpHeuristicFacts } from "@/lib/sign-up-heuristics";
import type { SmtpEgressPolicyResult } from "../types";
export const signUpRiskEngine: SignUpRiskEngine = {
async calculateRiskAssessment() {
@ -12,3 +13,13 @@ export const signUpRiskEngine: SignUpRiskEngine = {
};
export const preprocessProxyBody: AiProxyBodyProcessor = ({ parsedBody }) => parsedBody;
export async function checkSmtpEgressPolicy(options: {
host: string,
port: number,
}): Promise<SmtpEgressPolicyResult> {
return {
status: "ok",
addresses: [options.host],
};
}

View File

@ -1 +1 @@
export { signUpRiskEngine, preprocessProxyBody } from "./implementation.generated";
export { signUpRiskEngine, preprocessProxyBody, checkSmtpEgressPolicy } from "./implementation.generated";

View File

@ -0,0 +1,11 @@
export type SmtpEgressPolicyViolation = {
reason: "disallowed-port" | "internal-ip-literal" | "internal-resolved-address" | "no-dns-addresses" | "dns-lookup-failed",
host: string,
port: number,
addresses?: string[],
cause?: unknown,
};
export type SmtpEgressPolicyResult =
| { status: "ok", addresses: string[] }
| { status: "error", violation: SmtpEgressPolicyViolation };

View File

@ -325,12 +325,7 @@ const parseAuth = withTraceSpan('smart request parseAuth', async (req: NextReque
throw new KnownErrors.BranchDoesNotExist(branchId);
}
// As explained above, as a performance optimization we already fetch the user from the global database optimistically
// If it turned out that the source-of-truth is not the global database, we'll fetch the user from the source-of-truth
// database instead.
const user = tenancy.config.sourceOfTruth.type === "hosted"
? await queriesResults.userIfOnGlobalPrismaClient
: (userId ? await getUser({ userId, projectId, branchId }) : undefined);
const user = await queriesResults.userIfOnGlobalPrismaClient;
return {
project,

View File

@ -1,6 +1,6 @@
{
"name": "@stackframe/dashboard",
"version": "2.8.102",
"version": "2.8.103",
"repository": "https://github.com/hexclave/stack-auth",
"private": true,
"scripts": {

View File

@ -1,6 +1,6 @@
{
"name": "@stackframe/dev-launchpad",
"version": "2.8.102",
"version": "2.8.103",
"repository": "https://github.com/hexclave/stack-auth",
"private": true,
"scripts": {

View File

@ -1,6 +1,6 @@
{
"name": "@stackframe/e2e-tests",
"version": "2.8.102",
"version": "2.8.103",
"repository": "https://github.com/hexclave/stack-auth",
"private": true,
"type": "module",

View File

@ -1,7 +1,7 @@
{
"name": "@stackframe/hosted-components",
"private": true,
"version": "2.8.102",
"version": "2.8.103",
"type": "module",
"scripts": {
"dev": "vite dev --port ${NEXT_PUBLIC_STACK_PORT_PREFIX:-81}09",

View File

@ -1,7 +1,7 @@
{
"name": "@stackframe/internal-tool",
"private": true,
"version": "2.8.102",
"version": "2.8.103",
"type": "module",
"scripts": {
"dev": "node scripts/pre-dev.mjs && next dev --turbopack --port ${NEXT_PUBLIC_STACK_PORT_PREFIX:-81}41",

View File

@ -1,6 +1,6 @@
{
"name": "@stackframe/mcp",
"version": "2.8.102",
"version": "2.8.103",
"repository": "https://github.com/hexclave/stack-auth",
"private": true,
"type": "module",

View File

@ -1,6 +1,6 @@
{
"name": "@stackframe/mock-oauth-server",
"version": "2.8.102",
"version": "2.8.103",
"repository": "https://github.com/hexclave/stack-auth",
"private": true,
"main": "index.js",

View File

@ -1,6 +1,6 @@
{
"name": "@stackframe/skills",
"version": "2.8.102",
"version": "2.8.103",
"repository": "https://github.com/hexclave/stack-auth",
"private": true,
"type": "module",

View File

@ -1,6 +1,6 @@
{
"name": "@stackframe/docs-mintlify",
"version": "2.8.102",
"version": "2.8.103",
"private": true,
"scripts": {
"dev": "mint dev --port ${NEXT_PUBLIC_STACK_PORT_PREFIX:-81}04 --no-open",

View File

@ -1,6 +1,6 @@
{
"name": "@stackframe/stack-docs",
"version": "2.8.102",
"version": "2.8.103",
"repository": "https://github.com/hexclave/stack-auth",
"description": "",
"main": "index.js",

View File

@ -1,6 +1,6 @@
{
"name": "@stackframe/example-cjs-test",
"version": "2.8.102",
"version": "2.8.103",
"repository": "https://github.com/hexclave/stack-auth",
"private": true,
"scripts": {

View File

@ -1,6 +1,6 @@
{
"name": "@stackframe/convex-example",
"version": "2.8.102",
"version": "2.8.103",
"repository": "https://github.com/hexclave/stack-auth",
"private": true,
"scripts": {

View File

@ -1,6 +1,6 @@
{
"name": "@stackframe/example-demo-app",
"version": "2.8.102",
"version": "2.8.103",
"repository": "https://github.com/hexclave/stack-auth",
"description": "",
"private": true,

View File

@ -1,6 +1,6 @@
{
"name": "@stackframe/docs-examples",
"version": "2.8.102",
"version": "2.8.103",
"repository": "https://github.com/hexclave/stack-auth",
"description": "",
"private": true,

View File

@ -1,6 +1,6 @@
{
"name": "@stackframe/e-commerce-demo",
"version": "2.8.102",
"version": "2.8.103",
"repository": "https://github.com/hexclave/stack-auth",
"private": true,
"scripts": {

View File

@ -1,6 +1,6 @@
{
"name": "@stackframe/js-example",
"version": "2.8.102",
"version": "2.8.103",
"repository": "https://github.com/hexclave/stack-auth",
"private": true,
"description": "",

View File

@ -1,7 +1,7 @@
{
"name": "@stackframe/lovable-react-18-example",
"private": true,
"version": "2.8.102",
"version": "2.8.103",
"repository": "https://github.com/hexclave/stack-auth",
"type": "module",
"scripts": {

View File

@ -1,6 +1,6 @@
{
"name": "@stackframe/example-middleware-demo",
"version": "2.8.102",
"version": "2.8.103",
"repository": "https://github.com/hexclave/stack-auth",
"private": true,
"scripts": {

View File

@ -1,7 +1,7 @@
{
"name": "react-example",
"private": true,
"version": "2.8.102",
"version": "2.8.103",
"repository": "https://github.com/hexclave/stack-auth",
"type": "module",
"scripts": {

View File

@ -1,6 +1,6 @@
{
"name": "@stackframe/example-supabase",
"version": "2.8.102",
"version": "2.8.103",
"repository": "https://github.com/hexclave/stack-auth",
"private": true,
"scripts": {

View File

@ -1,6 +1,6 @@
{
"name": "@stackframe/example-tanstack-start-demo",
"version": "2.8.102",
"version": "2.8.103",
"repository": "https://github.com/hexclave/stack-auth",
"description": "TanStack Start demo app for Stack Auth",
"private": true,

View File

@ -1,6 +1,6 @@
{
"name": "@stackframe/dashboard-ui-components",
"version": "2.8.102",
"version": "2.8.103",
"repository": "https://github.com/hexclave/stack-auth",
"main": "./dist/index.js",
"types": "./dist/index.d.ts",

View File

@ -1,6 +1,6 @@
{
"name": "@stackframe/init-stack",
"version": "2.8.102",
"version": "2.8.103",
"repository": "https://github.com/hexclave/stack-auth",
"description": "The setup wizard for Stack. https://stack-auth.com",
"main": "dist/index.mjs",

View File

@ -1,7 +1,7 @@
{
"//": "THIS FILE IS AUTO-GENERATED FROM TEMPLATE. DO NOT EDIT IT DIRECTLY, INSTEAD EDIT THE CORRESPONDING FILE IN packages/template (FOR package.json FILES, PLEASE EDIT package-template.json)",
"name": "@stackframe/js",
"version": "2.8.102",
"version": "2.8.103",
"repository": "https://github.com/hexclave/stack-auth",
"sideEffects": false,
"main": "./dist/index.js",

View File

@ -1,7 +1,7 @@
{
"//": "THIS FILE IS AUTO-GENERATED FROM TEMPLATE. DO NOT EDIT IT DIRECTLY, INSTEAD EDIT THE CORRESPONDING FILE IN packages/template (FOR package.json FILES, PLEASE EDIT package-template.json)",
"name": "@stackframe/react",
"version": "2.8.102",
"version": "2.8.103",
"repository": "https://github.com/hexclave/stack-auth",
"sideEffects": false,
"main": "./dist/index.js",

View File

@ -1,6 +1,6 @@
{
"name": "@stackframe/stack-cli",
"version": "2.8.102",
"version": "2.8.103",
"repository": "https://github.com/hexclave/stack-auth",
"description": "The CLI for Stack Auth. https://stack-auth.com",
"main": "dist/index.js",

View File

@ -1,6 +1,6 @@
{
"name": "@stackframe/stack-sc",
"version": "2.8.102",
"version": "2.8.103",
"repository": "https://github.com/hexclave/stack-auth",
"exports": {
"./force-react-server": {

View File

@ -1,6 +1,6 @@
{
"name": "@stackframe/stack-shared",
"version": "2.8.102",
"version": "2.8.103",
"repository": "https://github.com/hexclave/stack-auth",
"scripts": {
"build": "rimraf dist && tsdown",

View File

@ -1,6 +1,6 @@
{
"name": "@stackframe/stack-ui",
"version": "2.8.102",
"version": "2.8.103",
"repository": "https://github.com/hexclave/stack-auth",
"main": "./dist/index.js",
"types": "./dist/index.d.ts",

View File

@ -1,7 +1,7 @@
{
"//": "THIS FILE IS AUTO-GENERATED FROM TEMPLATE. DO NOT EDIT IT DIRECTLY, INSTEAD EDIT THE CORRESPONDING FILE IN packages/template (FOR package.json FILES, PLEASE EDIT package-template.json)",
"name": "@stackframe/stack",
"version": "2.8.102",
"version": "2.8.103",
"repository": "https://github.com/hexclave/stack-auth",
"sideEffects": false,
"main": "./dist/index.js",

View File

@ -1,7 +1,7 @@
{
"//": "THIS FILE IS AUTO-GENERATED FROM TEMPLATE. DO NOT EDIT IT DIRECTLY, INSTEAD EDIT THE CORRESPONDING FILE IN packages/template (FOR package.json FILES, PLEASE EDIT package-template.json)",
"name": "@stackframe/tanstack-start",
"version": "2.8.102",
"version": "2.8.103",
"repository": "https://github.com/hexclave/stack-auth",
"sideEffects": false,
"main": "./dist/index.js",

View File

@ -13,7 +13,7 @@
"//": "NEXT_LINE_PLATFORM template",
"private": true,
"version": "2.8.102",
"version": "2.8.103",
"repository": "https://github.com/hexclave/stack-auth",
"sideEffects": false,
"main": "./dist/index.js",

View File

@ -2,7 +2,7 @@
"//": "THIS FILE IS AUTO-GENERATED FROM TEMPLATE. DO NOT EDIT IT DIRECTLY, INSTEAD EDIT THE CORRESPONDING FILE IN packages/template (FOR package.json FILES, PLEASE EDIT package-template.json)",
"name": "@stackframe/template",
"private": true,
"version": "2.8.102",
"version": "2.8.103",
"repository": "https://github.com/hexclave/stack-auth",
"sideEffects": false,
"main": "./dist/index.js",

View File

@ -1,6 +1,6 @@
{
"name": "@stackframe/swift-sdk",
"version": "2.8.102",
"version": "2.8.103",
"private": true,
"description": "Stack Auth Swift SDK",
"scripts": {

View File

@ -1,6 +1,6 @@
{
"name": "@stackframe/sdk-spec",
"version": "2.8.102",
"version": "2.8.103",
"private": true,
"description": "Stack Auth SDK specification files",
"scripts": {}