Improper Access Control in GitHub repository answerdev/answer prior to 1.0.4.

func (uc *UserController) RetrievePassWord(ctx *gin.Context) {
	req := &schema.UserRetrievePassWordRequest{}
	if handler.BindAndCheck(ctx, req) {
		return
	}
	captchaPass := uc.actionService.ActionRecordVerifyCaptcha(ctx, schema.ActionRecordTypeFindPass, ctx.ClientIP(), req.CaptchaID, req.CaptchaCode)
	if !captchaPass {
		errFields := append([]*validator.FormErrorField{}, &validator.FormErrorField{
			ErrorField: "captcha_code",
			ErrorMsg:   translator.Tr(handler.GetLang(ctx), reason.CaptchaVerificationFailed),
		})
		handler.HandleResponse(ctx, errors.BadRequest(reason.CaptchaVerificationFailed), errFields)
		return
	}
	_, _ = uc.actionService.ActionRecordAdd(ctx, schema.ActionRecordTypeFindPass, ctx.ClientIP())
	code, err := uc.userService.RetrievePassWord(ctx, req)
	handler.HandleResponse(ctx, err, code)
}

func (uc *UserController) RetrievePassWord(ctx *gin.Context) {
	req := &schema.UserRetrievePassWordRequest{}
	if handler.BindAndCheck(ctx, req) {
		return
	}
	captchaPass := uc.actionService.ActionRecordVerifyCaptcha(ctx, schema.ActionRecordTypeFindPass, ctx.ClientIP(), req.CaptchaID, req.CaptchaCode)
	if !captchaPass {
		errFields := append([]*validator.FormErrorField{}, &validator.FormErrorField{
			ErrorField: "captcha_code",
			ErrorMsg:   translator.Tr(handler.GetLang(ctx), reason.CaptchaVerificationFailed),
		})
		handler.HandleResponse(ctx, errors.BadRequest(reason.CaptchaVerificationFailed), errFields)
		return
	}
	_, _ = uc.actionService.ActionRecordAdd(ctx, schema.ActionRecordTypeFindPass, ctx.ClientIP())
	_, err := uc.userService.RetrievePassWord(ctx, req)
	handler.HandleResponse(ctx, err, nil)
}

--- a/internal/controller/user_controller.go
+++ b/internal/controller/user_controller.go
@@ -13,6 +13,6 @@
 		return
 	}
 	_, _ = uc.actionService.ActionRecordAdd(ctx, schema.ActionRecordTypeFindPass, ctx.ClientIP())
-	code, err := uc.userService.RetrievePassWord(ctx, req)
-	handler.HandleResponse(ctx, err, code)
+	_, err := uc.userService.RetrievePassWord(ctx, req)
+	handler.HandleResponse(ctx, err, nil)
 }


matrix-appservice-bridge provides an API for setting up bridges. Starting in version 4.0.0 and prior to versions 8.1.2 and 9.0.1, a malicious Matrix server can use a foreign user's MXID in an OpenID exchange, allowing a bad actor to impersonate users when using the provisioning API. The library does not check that the servername part of the `sub` parameter (containing the user's *claimed* MXID) is the the same as the servername we are talking to. A malicious actor could spin up a server on any given domain, respond with a `sub` parameter according to the user they want to act as and use the resulting token to perform provisioning requests. Versions 8.1.2 and 9.0.1 contain a patch. As a workaround, disable the provisioning API.

Improper Authentication

When an actor claims to have a given identity, the product does not prove or insufficiently proves that the claim is correct.

    private async postExchangeOpenId(
        req: Request<unknown, unknown, ExchangeOpenAPIRequestBody>, res: Response<ExchangeOpenAPIResponseBody>) {
        const server = req.body?.matrixServer;
        const openIdToken = req.body?.openIdToken;
        if (typeof server !== "string") {
            throw new ApiError("Missing/invalid matrixServer in body", ErrCode.BadValue);
        }
        if (typeof openIdToken !== "string") {
            throw new ApiError("Missing/invalid openIdToken in body", ErrCode.BadValue);
        }
        let url: URL;
        let hostHeader: string;
        try {
            const overrideUrl = this.opts.openIdOverride?.[server];
            if (overrideUrl) {
                url = overrideUrl;
                hostHeader = server;
            }
            else {
                const urlRes = await this.wellknown.resolveMatrixServer(server);
                hostHeader = urlRes.hostHeader;
                url = urlRes.url;
                await this.checkIpBlacklist(url);
            }
        }
        catch (ex) {
            log.warn(`Failed to fetch the server URL for ${server}`, ex);
            throw new ApiError("Could not identify server url", ErrCode.BadOpenID);
        }

        // Now do the token exchange
        try {
            const requestUrl = new URL("/_matrix/federation/v1/openid/userinfo", url);
            const response = await axios.get<{sub: string}>(requestUrl.toString(), {
                params: {
                    access_token: openIdToken,
                },
                headers: {
                    'Host': hostHeader,
                }
            });
            if (!response.data.sub) {
                log.warn(`Server responded with invalid sub information for ${server}`, response.data);
                throw new ApiError("Server did not respond with the correct sub information", ErrCode.BadOpenID);
            }
            const userId = response.data.sub;
            const token = this.widgetTokenPrefix + uuid().replace(/-/g, "");
            const expiresTs = Date.now() + this.widgetTokenLifetimeMs;
            await this.store.createSession({
                userId,
                token,
                expiresTs,
            });
            res.send({ token, userId });
        }
        catch (ex) {
            log.warn(`Failed to exchange the token for ${server}`, ex);
            throw new ApiError("Failed to exchange token", ErrCode.BadOpenID);
        }
    }

    private async postExchangeOpenId(
        req: Request<unknown, unknown, ExchangeOpenAPIRequestBody>, res: Response<ExchangeOpenAPIResponseBody>) {
        const server = req.body?.matrixServer;
        const openIdToken = req.body?.openIdToken;
        if (typeof server !== "string") {
            throw new ApiError("Missing/invalid matrixServer in body", ErrCode.BadValue);
        }
        if (typeof openIdToken !== "string") {
            throw new ApiError("Missing/invalid openIdToken in body", ErrCode.BadValue);
        }
        let url: URL;
        let hostHeader: string;
        try {
            const overrideUrl = this.opts.openIdOverride?.[server];
            if (overrideUrl) {
                url = overrideUrl;
                hostHeader = server;
            }
            else {
                const urlRes = await this.wellknown.resolveMatrixServer(server);
                hostHeader = urlRes.hostHeader;
                url = urlRes.url;
                await this.checkIpBlacklist(url);
            }
        }
        catch (ex) {
            log.warn(`Failed to fetch the server URL for ${server}`, ex);
            throw new ApiError("Could not identify server url", ErrCode.BadOpenID);
        }

        // Now do the token exchange
        try {
            const requestUrl = new URL("/_matrix/federation/v1/openid/userinfo", url);
            const response = await axios.get<{sub: string}>(requestUrl.toString(), {
                params: {
                    access_token: openIdToken,
                },
                headers: {
                    'Host': hostHeader,
                }
            });
            if (!response.data.sub) {
                log.warn(`Server responded with invalid sub information for ${server}`, response.data);
                throw new ApiError("Server did not respond with the correct sub information", ErrCode.BadOpenID);
            }
            const userId = response.data.sub;

            const mxidMatch = userId.match(/([^:]+):(.+)/);
            if (!mxidMatch) {
                throw new ApiError("Server did not respond with a valid MXID", ErrCode.BadOpenID);
            }
            const [, _localpart, serverName] = mxidMatch;
            if (serverName !== server) {
                throw new ApiError("Server returned a MXID belonging to another homeserver", ErrCode.BadOpenID);
            }

            const token = this.widgetTokenPrefix + uuid().replace(/-/g, "");
            const expiresTs = Date.now() + this.widgetTokenLifetimeMs;
            await this.store.createSession({
                userId,
                token,
                expiresTs,
            });
            res.send({ token, userId });
        }
        catch (ex) {
            log.warn(`Failed to exchange the token for ${server}`, ex);
            throw new ApiError("Failed to exchange token", ErrCode.BadOpenID);
        }
    }

--- a/src/provisioning/api.ts
+++ b/src/provisioning/api.ts
@@ -44,6 +44,16 @@
                 throw new ApiError("Server did not respond with the correct sub information", ErrCode.BadOpenID);
             }
             const userId = response.data.sub;
+
+            const mxidMatch = userId.match(/([^:]+):(.+)/);
+            if (!mxidMatch) {
+                throw new ApiError("Server did not respond with a valid MXID", ErrCode.BadOpenID);
+            }
+            const [, _localpart, serverName] = mxidMatch;
+            if (serverName !== server) {
+                throw new ApiError("Server returned a MXID belonging to another homeserver", ErrCode.BadOpenID);
+            }
+
             const token = this.widgetTokenPrefix + uuid().replace(/-/g, "");
             const expiresTs = Date.now() + this.widgetTokenLifetimeMs;
             await this.store.createSession({


A flaw was found in Podman, where containers were started incorrectly with non-empty default permissions. A vulnerability was found in Moby (Docker Engine), where containers were started incorrectly with non-empty inheritable Linux process capabilities. This flaw allows an attacker with access to programs with inheritable file capabilities to elevate those capabilities to the permitted set when execve(2) runs.

Incorrect Default Permissions

During installation, installed file permissions are set to allow anyone to modify those files.

func prepareProcessExec(c *Container, options *ExecOptions, env []string, sessionID string) (*os.File, error) {
	f, err := ioutil.TempFile(c.execBundlePath(sessionID), "exec-process-")
	if err != nil {
		return nil, err
	}
	pspec := new(spec.Process)
	if err := JSONDeepCopy(c.config.Spec.Process, pspec); err != nil {
		return nil, err
	}
	pspec.SelinuxLabel = c.config.ProcessLabel
	pspec.Args = options.Cmd

	// We need to default this to false else it will inherit terminal as true
	// from the container.
	pspec.Terminal = false
	if options.Terminal {
		pspec.Terminal = true
	}
	if len(env) > 0 {
		pspec.Env = append(pspec.Env, env...)
	}

	// Add secret envs if they exist
	manager, err := c.runtime.SecretsManager()
	if err != nil {
		return nil, err
	}
	for name, secr := range c.config.EnvSecrets {
		_, data, err := manager.LookupSecretData(secr.Name)
		if err != nil {
			return nil, err
		}
		pspec.Env = append(pspec.Env, fmt.Sprintf("%s=%s", name, string(data)))
	}

	if options.Cwd != "" {
		pspec.Cwd = options.Cwd
	}

	var addGroups []string
	var sgids []uint32

	// if the user is empty, we should inherit the user that the container is currently running with
	user := options.User
	if user == "" {
		logrus.Debugf("Set user to %s", c.config.User)
		user = c.config.User
		addGroups = c.config.Groups
	}

	overrides := c.getUserOverrides()
	execUser, err := lookup.GetUserGroupInfo(c.state.Mountpoint, user, overrides)
	if err != nil {
		return nil, err
	}

	if len(addGroups) > 0 {
		sgids, err = lookup.GetContainerGroups(addGroups, c.state.Mountpoint, overrides)
		if err != nil {
			return nil, errors.Wrapf(err, "error looking up supplemental groups for container %s exec session %s", c.ID(), sessionID)
		}
	}

	// If user was set, look it up in the container to get a UID to use on
	// the host
	if user != "" || len(sgids) > 0 {
		if user != "" {
			for _, sgid := range execUser.Sgids {
				sgids = append(sgids, uint32(sgid))
			}
		}
		processUser := spec.User{
			UID:            uint32(execUser.Uid),
			GID:            uint32(execUser.Gid),
			AdditionalGids: sgids,
		}

		pspec.User = processUser
	}

	ctrSpec, err := c.specFromState()
	if err != nil {
		return nil, err
	}

	allCaps, err := capabilities.BoundingSet()
	if err != nil {
		return nil, err
	}
	if options.Privileged {
		pspec.Capabilities.Bounding = allCaps
	} else {
		pspec.Capabilities.Bounding = ctrSpec.Process.Capabilities.Bounding
	}
	if execUser.Uid == 0 {
		pspec.Capabilities.Effective = pspec.Capabilities.Bounding
		pspec.Capabilities.Inheritable = pspec.Capabilities.Bounding
		pspec.Capabilities.Permitted = pspec.Capabilities.Bounding
		pspec.Capabilities.Ambient = pspec.Capabilities.Bounding
	} else {
		if user == c.config.User {
			pspec.Capabilities.Effective = ctrSpec.Process.Capabilities.Effective
			pspec.Capabilities.Inheritable = ctrSpec.Process.Capabilities.Effective
			pspec.Capabilities.Permitted = ctrSpec.Process.Capabilities.Effective
			pspec.Capabilities.Ambient = ctrSpec.Process.Capabilities.Effective
		}
	}

	hasHomeSet := false
	for _, s := range pspec.Env {
		if strings.HasPrefix(s, "HOME=") {
			hasHomeSet = true
			break
		}
	}
	if !hasHomeSet {
		pspec.Env = append(pspec.Env, fmt.Sprintf("HOME=%s", execUser.Home))
	}

	processJSON, err := json.Marshal(pspec)
	if err != nil {
		return nil, err
	}

	if err := ioutil.WriteFile(f.Name(), processJSON, 0644); err != nil {
		return nil, err
	}
	return f, nil
}

func securityConfigureGenerator(s *specgen.SpecGenerator, g *generate.Generator, newImage *libimage.Image, rtc *config.Config) error {
	var (
		caplist []string
		err     error
	)
	// HANDLE CAPABILITIES
	// NOTE: Must happen before SECCOMP
	if s.Privileged {
		g.SetupPrivileged(true)
		caplist, err = capabilities.BoundingSet()
		if err != nil {
			return err
		}
	} else {
		mergedCaps, err := capabilities.MergeCapabilities(rtc.Containers.DefaultCapabilities, s.CapAdd, s.CapDrop)
		if err != nil {
			return err
		}
		boundingSet, err := capabilities.BoundingSet()
		if err != nil {
			return err
		}
		boundingCaps := make(map[string]interface{})
		for _, b := range boundingSet {
			boundingCaps[b] = b
		}
		for _, c := range mergedCaps {
			if _, ok := boundingCaps[c]; ok {
				caplist = append(caplist, c)
			}
		}

		privCapsRequired := []string{}

		// If the container image specifies an label with a
		// capabilities.ContainerImageLabel then split the comma separated list
		// of capabilities and record them.  This list indicates the only
		// capabilities, required to run the container.
		var capsRequiredRequested []string
		for key, val := range s.Labels {
			if util.StringInSlice(key, capabilities.ContainerImageLabels) {
				capsRequiredRequested = strings.Split(val, ",")
			}
		}
		if !s.Privileged && len(capsRequiredRequested) > 0 {
			// Pass capRequiredRequested in CapAdd field to normalize capabilities names
			capsRequired, err := capabilities.MergeCapabilities(nil, capsRequiredRequested, nil)
			if err != nil {
				return errors.Wrapf(err, "capabilities requested by user or image are not valid: %q", strings.Join(capsRequired, ","))
			}
			// Verify all capRequired are in the capList
			for _, cap := range capsRequired {
				if !util.StringInSlice(cap, caplist) {
					privCapsRequired = append(privCapsRequired, cap)
				}
			}
			if len(privCapsRequired) == 0 {
				caplist = capsRequired
			} else {
				logrus.Errorf("Capabilities requested by user or image are not allowed by default: %q", strings.Join(privCapsRequired, ","))
			}
		}
	}

	configSpec := g.Config
	configSpec.Process.Capabilities.Ambient = []string{}
	configSpec.Process.Capabilities.Bounding = caplist

	user := strings.Split(s.User, ":")[0]

	if (user == "" && s.UserNS.NSMode != specgen.KeepID) || user == "root" || user == "0" {
		configSpec.Process.Capabilities.Effective = caplist
		configSpec.Process.Capabilities.Permitted = caplist
		configSpec.Process.Capabilities.Inheritable = caplist
	} else {
		mergedCaps, err := capabilities.MergeCapabilities(nil, s.CapAdd, nil)
		if err != nil {
			return errors.Wrapf(err, "capabilities requested by user are not valid: %q", strings.Join(s.CapAdd, ","))
		}
		boundingSet, err := capabilities.BoundingSet()
		if err != nil {
			return err
		}
		boundingCaps := make(map[string]interface{})
		for _, b := range boundingSet {
			boundingCaps[b] = b
		}
		var userCaps []string
		for _, c := range mergedCaps {
			if _, ok := boundingCaps[c]; ok {
				userCaps = append(userCaps, c)
			}
		}
		configSpec.Process.Capabilities.Effective = userCaps
		configSpec.Process.Capabilities.Permitted = userCaps
		configSpec.Process.Capabilities.Inheritable = userCaps

		// Ambient capabilities were added to Linux 4.3.  Set ambient
		// capabilities only when the kernel supports them.
		if supportAmbientCapabilities() {
			configSpec.Process.Capabilities.Ambient = userCaps
		}
	}

	g.SetProcessNoNewPrivileges(s.NoNewPrivileges)

	if err := setupApparmor(s, rtc, g); err != nil {
		return err
	}

	// HANDLE SECCOMP
	if s.SeccompProfilePath != "unconfined" {
		seccompConfig, err := getSeccompConfig(s, configSpec, newImage)
		if err != nil {
			return err
		}
		configSpec.Linux.Seccomp = seccompConfig
	}

	// Clear default Seccomp profile from Generator for unconfined containers
	// and privileged containers which do not specify a seccomp profile.
	if s.SeccompProfilePath == "unconfined" || (s.Privileged && (s.SeccompProfilePath == "" || s.SeccompProfilePath == config.SeccompOverridePath || s.SeccompProfilePath == config.SeccompDefaultPath)) {
		configSpec.Linux.Seccomp = nil
	}

	g.SetRootReadonly(s.ReadOnlyFilesystem)

	noUseIPC := s.IpcNS.NSMode == specgen.FromContainer || s.IpcNS.NSMode == specgen.FromPod || s.IpcNS.NSMode == specgen.Host
	noUseNet := s.NetNS.NSMode == specgen.FromContainer || s.NetNS.NSMode == specgen.FromPod || s.NetNS.NSMode == specgen.Host
	noUseUTS := s.UtsNS.NSMode == specgen.FromContainer || s.UtsNS.NSMode == specgen.FromPod || s.UtsNS.NSMode == specgen.Host

	// Add default sysctls
	defaultSysctls, err := util.ValidateSysctls(rtc.Sysctls())
	if err != nil {
		return err
	}
	for sysctlKey, sysctlVal := range defaultSysctls {
		// Ignore mqueue sysctls if --ipc=host
		if noUseIPC && strings.HasPrefix(sysctlKey, "fs.mqueue.") {
			logrus.Infof("Sysctl %s=%s ignored in containers.conf, since IPC Namespace set to host", sysctlKey, sysctlVal)

			continue
		}

		// Ignore net sysctls if --net=host
		if noUseNet && strings.HasPrefix(sysctlKey, "net.") {
			logrus.Infof("Sysctl %s=%s ignored in containers.conf, since Network Namespace set to host", sysctlKey, sysctlVal)
			continue
		}

		// Ignore uts sysctls if --uts=host
		if noUseUTS && (strings.HasPrefix(sysctlKey, "kernel.domainname") || strings.HasPrefix(sysctlKey, "kernel.hostname")) {
			logrus.Infof("Sysctl %s=%s ignored in containers.conf, since UTS Namespace set to host", sysctlKey, sysctlVal)
			continue
		}

		g.AddLinuxSysctl(sysctlKey, sysctlVal)
	}

	for sysctlKey, sysctlVal := range s.Sysctl {
		if s.IpcNS.IsHost() && strings.HasPrefix(sysctlKey, "fs.mqueue.") {
			return errors.Wrapf(define.ErrInvalidArg, "sysctl %s=%s can't be set since IPC Namespace set to host", sysctlKey, sysctlVal)
		}

		// Ignore net sysctls if --net=host
		if s.NetNS.IsHost() && strings.HasPrefix(sysctlKey, "net.") {
			return errors.Wrapf(define.ErrInvalidArg, "sysctl %s=%s can't be set since Network Namespace set to host", sysctlKey, sysctlVal)
		}

		// Ignore uts sysctls if --uts=host
		if s.UtsNS.IsHost() && (strings.HasPrefix(sysctlKey, "kernel.domainname") || strings.HasPrefix(sysctlKey, "kernel.hostname")) {
			return errors.Wrapf(define.ErrInvalidArg, "sysctl %s=%s can't be set since UTS Namespace set to host", sysctlKey, sysctlVal)
		}

		g.AddLinuxSysctl(sysctlKey, sysctlVal)
	}

	return nil
}

func prepareProcessExec(c *Container, options *ExecOptions, env []string, sessionID string) (*os.File, error) {
	f, err := ioutil.TempFile(c.execBundlePath(sessionID), "exec-process-")
	if err != nil {
		return nil, err
	}
	pspec := new(spec.Process)
	if err := JSONDeepCopy(c.config.Spec.Process, pspec); err != nil {
		return nil, err
	}
	pspec.SelinuxLabel = c.config.ProcessLabel
	pspec.Args = options.Cmd

	// We need to default this to false else it will inherit terminal as true
	// from the container.
	pspec.Terminal = false
	if options.Terminal {
		pspec.Terminal = true
	}
	if len(env) > 0 {
		pspec.Env = append(pspec.Env, env...)
	}

	// Add secret envs if they exist
	manager, err := c.runtime.SecretsManager()
	if err != nil {
		return nil, err
	}
	for name, secr := range c.config.EnvSecrets {
		_, data, err := manager.LookupSecretData(secr.Name)
		if err != nil {
			return nil, err
		}
		pspec.Env = append(pspec.Env, fmt.Sprintf("%s=%s", name, string(data)))
	}

	if options.Cwd != "" {
		pspec.Cwd = options.Cwd
	}

	var addGroups []string
	var sgids []uint32

	// if the user is empty, we should inherit the user that the container is currently running with
	user := options.User
	if user == "" {
		logrus.Debugf("Set user to %s", c.config.User)
		user = c.config.User
		addGroups = c.config.Groups
	}

	overrides := c.getUserOverrides()
	execUser, err := lookup.GetUserGroupInfo(c.state.Mountpoint, user, overrides)
	if err != nil {
		return nil, err
	}

	if len(addGroups) > 0 {
		sgids, err = lookup.GetContainerGroups(addGroups, c.state.Mountpoint, overrides)
		if err != nil {
			return nil, errors.Wrapf(err, "error looking up supplemental groups for container %s exec session %s", c.ID(), sessionID)
		}
	}

	// If user was set, look it up in the container to get a UID to use on
	// the host
	if user != "" || len(sgids) > 0 {
		if user != "" {
			for _, sgid := range execUser.Sgids {
				sgids = append(sgids, uint32(sgid))
			}
		}
		processUser := spec.User{
			UID:            uint32(execUser.Uid),
			GID:            uint32(execUser.Gid),
			AdditionalGids: sgids,
		}

		pspec.User = processUser
	}

	ctrSpec, err := c.specFromState()
	if err != nil {
		return nil, err
	}

	allCaps, err := capabilities.BoundingSet()
	if err != nil {
		return nil, err
	}
	if options.Privileged {
		pspec.Capabilities.Bounding = allCaps
	} else {
		pspec.Capabilities.Bounding = ctrSpec.Process.Capabilities.Bounding
	}

	// Always unset the inheritable capabilities similarly to what the Linux kernel does
	// They are used only when using capabilities with uid != 0.
	pspec.Capabilities.Inheritable = []string{}

	if execUser.Uid == 0 {
		pspec.Capabilities.Effective = pspec.Capabilities.Bounding
		pspec.Capabilities.Permitted = pspec.Capabilities.Bounding
	} else {
		if user == c.config.User {
			pspec.Capabilities.Effective = ctrSpec.Process.Capabilities.Effective
			pspec.Capabilities.Inheritable = ctrSpec.Process.Capabilities.Effective
			pspec.Capabilities.Permitted = ctrSpec.Process.Capabilities.Effective
			pspec.Capabilities.Ambient = ctrSpec.Process.Capabilities.Effective
		}
	}

	hasHomeSet := false
	for _, s := range pspec.Env {
		if strings.HasPrefix(s, "HOME=") {
			hasHomeSet = true
			break
		}
	}
	if !hasHomeSet {
		pspec.Env = append(pspec.Env, fmt.Sprintf("HOME=%s", execUser.Home))
	}

	processJSON, err := json.Marshal(pspec)
	if err != nil {
		return nil, err
	}

	if err := ioutil.WriteFile(f.Name(), processJSON, 0644); err != nil {
		return nil, err
	}
	return f, nil
}

func securityConfigureGenerator(s *specgen.SpecGenerator, g *generate.Generator, newImage *libimage.Image, rtc *config.Config) error {
	var (
		caplist []string
		err     error
	)
	// HANDLE CAPABILITIES
	// NOTE: Must happen before SECCOMP
	if s.Privileged {
		g.SetupPrivileged(true)
		caplist, err = capabilities.BoundingSet()
		if err != nil {
			return err
		}
	} else {
		mergedCaps, err := capabilities.MergeCapabilities(rtc.Containers.DefaultCapabilities, s.CapAdd, s.CapDrop)
		if err != nil {
			return err
		}
		boundingSet, err := capabilities.BoundingSet()
		if err != nil {
			return err
		}
		boundingCaps := make(map[string]interface{})
		for _, b := range boundingSet {
			boundingCaps[b] = b
		}
		for _, c := range mergedCaps {
			if _, ok := boundingCaps[c]; ok {
				caplist = append(caplist, c)
			}
		}

		privCapsRequired := []string{}

		// If the container image specifies an label with a
		// capabilities.ContainerImageLabel then split the comma separated list
		// of capabilities and record them.  This list indicates the only
		// capabilities, required to run the container.
		var capsRequiredRequested []string
		for key, val := range s.Labels {
			if util.StringInSlice(key, capabilities.ContainerImageLabels) {
				capsRequiredRequested = strings.Split(val, ",")
			}
		}
		if !s.Privileged && len(capsRequiredRequested) > 0 {
			// Pass capRequiredRequested in CapAdd field to normalize capabilities names
			capsRequired, err := capabilities.MergeCapabilities(nil, capsRequiredRequested, nil)
			if err != nil {
				return errors.Wrapf(err, "capabilities requested by user or image are not valid: %q", strings.Join(capsRequired, ","))
			}
			// Verify all capRequired are in the capList
			for _, cap := range capsRequired {
				if !util.StringInSlice(cap, caplist) {
					privCapsRequired = append(privCapsRequired, cap)
				}
			}
			if len(privCapsRequired) == 0 {
				caplist = capsRequired
			} else {
				logrus.Errorf("Capabilities requested by user or image are not allowed by default: %q", strings.Join(privCapsRequired, ","))
			}
		}
	}

	configSpec := g.Config
	configSpec.Process.Capabilities.Ambient = []string{}

	// Always unset the inheritable capabilities similarly to what the Linux kernel does
	// They are used only when using capabilities with uid != 0.
	configSpec.Process.Capabilities.Inheritable = []string{}
	configSpec.Process.Capabilities.Bounding = caplist

	user := strings.Split(s.User, ":")[0]

	if (user == "" && s.UserNS.NSMode != specgen.KeepID) || user == "root" || user == "0" {
		configSpec.Process.Capabilities.Effective = caplist
		configSpec.Process.Capabilities.Permitted = caplist
	} else {
		mergedCaps, err := capabilities.MergeCapabilities(nil, s.CapAdd, nil)
		if err != nil {
			return errors.Wrapf(err, "capabilities requested by user are not valid: %q", strings.Join(s.CapAdd, ","))
		}
		boundingSet, err := capabilities.BoundingSet()
		if err != nil {
			return err
		}
		boundingCaps := make(map[string]interface{})
		for _, b := range boundingSet {
			boundingCaps[b] = b
		}
		var userCaps []string
		for _, c := range mergedCaps {
			if _, ok := boundingCaps[c]; ok {
				userCaps = append(userCaps, c)
			}
		}
		configSpec.Process.Capabilities.Effective = userCaps
		configSpec.Process.Capabilities.Permitted = userCaps

		// Ambient capabilities were added to Linux 4.3.  Set ambient
		// capabilities only when the kernel supports them.
		if supportAmbientCapabilities() {
			configSpec.Process.Capabilities.Ambient = userCaps
			configSpec.Process.Capabilities.Inheritable = userCaps
		}
	}

	g.SetProcessNoNewPrivileges(s.NoNewPrivileges)

	if err := setupApparmor(s, rtc, g); err != nil {
		return err
	}

	// HANDLE SECCOMP
	if s.SeccompProfilePath != "unconfined" {
		seccompConfig, err := getSeccompConfig(s, configSpec, newImage)
		if err != nil {
			return err
		}
		configSpec.Linux.Seccomp = seccompConfig
	}

	// Clear default Seccomp profile from Generator for unconfined containers
	// and privileged containers which do not specify a seccomp profile.
	if s.SeccompProfilePath == "unconfined" || (s.Privileged && (s.SeccompProfilePath == "" || s.SeccompProfilePath == config.SeccompOverridePath || s.SeccompProfilePath == config.SeccompDefaultPath)) {
		configSpec.Linux.Seccomp = nil
	}

	g.SetRootReadonly(s.ReadOnlyFilesystem)

	noUseIPC := s.IpcNS.NSMode == specgen.FromContainer || s.IpcNS.NSMode == specgen.FromPod || s.IpcNS.NSMode == specgen.Host
	noUseNet := s.NetNS.NSMode == specgen.FromContainer || s.NetNS.NSMode == specgen.FromPod || s.NetNS.NSMode == specgen.Host
	noUseUTS := s.UtsNS.NSMode == specgen.FromContainer || s.UtsNS.NSMode == specgen.FromPod || s.UtsNS.NSMode == specgen.Host

	// Add default sysctls
	defaultSysctls, err := util.ValidateSysctls(rtc.Sysctls())
	if err != nil {
		return err
	}
	for sysctlKey, sysctlVal := range defaultSysctls {
		// Ignore mqueue sysctls if --ipc=host
		if noUseIPC && strings.HasPrefix(sysctlKey, "fs.mqueue.") {
			logrus.Infof("Sysctl %s=%s ignored in containers.conf, since IPC Namespace set to host", sysctlKey, sysctlVal)

			continue
		}

		// Ignore net sysctls if --net=host
		if noUseNet && strings.HasPrefix(sysctlKey, "net.") {
			logrus.Infof("Sysctl %s=%s ignored in containers.conf, since Network Namespace set to host", sysctlKey, sysctlVal)
			continue
		}

		// Ignore uts sysctls if --uts=host
		if noUseUTS && (strings.HasPrefix(sysctlKey, "kernel.domainname") || strings.HasPrefix(sysctlKey, "kernel.hostname")) {
			logrus.Infof("Sysctl %s=%s ignored in containers.conf, since UTS Namespace set to host", sysctlKey, sysctlVal)
			continue
		}

		g.AddLinuxSysctl(sysctlKey, sysctlVal)
	}

	for sysctlKey, sysctlVal := range s.Sysctl {
		if s.IpcNS.IsHost() && strings.HasPrefix(sysctlKey, "fs.mqueue.") {
			return errors.Wrapf(define.ErrInvalidArg, "sysctl %s=%s can't be set since IPC Namespace set to host", sysctlKey, sysctlVal)
		}

		// Ignore net sysctls if --net=host
		if s.NetNS.IsHost() && strings.HasPrefix(sysctlKey, "net.") {
			return errors.Wrapf(define.ErrInvalidArg, "sysctl %s=%s can't be set since Network Namespace set to host", sysctlKey, sysctlVal)
		}

		// Ignore uts sysctls if --uts=host
		if s.UtsNS.IsHost() && (strings.HasPrefix(sysctlKey, "kernel.domainname") || strings.HasPrefix(sysctlKey, "kernel.hostname")) {
			return errors.Wrapf(define.ErrInvalidArg, "sysctl %s=%s can't be set since UTS Namespace set to host", sysctlKey, sysctlVal)
		}

		g.AddLinuxSysctl(sysctlKey, sysctlVal)
	}

	return nil
}

--- a/libpod/oci_conmon_exec_linux.go
+++ b/libpod/oci_conmon_exec_linux.go
@@ -92,11 +92,14 @@
 	} else {
 		pspec.Capabilities.Bounding = ctrSpec.Process.Capabilities.Bounding
 	}
+
+	// Always unset the inheritable capabilities similarly to what the Linux kernel does
+	// They are used only when using capabilities with uid != 0.
+	pspec.Capabilities.Inheritable = []string{}
+
 	if execUser.Uid == 0 {
 		pspec.Capabilities.Effective = pspec.Capabilities.Bounding
-		pspec.Capabilities.Inheritable = pspec.Capabilities.Bounding
 		pspec.Capabilities.Permitted = pspec.Capabilities.Bounding
-		pspec.Capabilities.Ambient = pspec.Capabilities.Bounding
 	} else {
 		if user == c.config.User {
 			pspec.Capabilities.Effective = ctrSpec.Process.Capabilities.Effective

--- a/pkg/specgen/generate/security.go
+++ b/pkg/specgen/generate/security.go
@@ -64,6 +64,10 @@

 	configSpec := g.Config
 	configSpec.Process.Capabilities.Ambient = []string{}
+
+	// Always unset the inheritable capabilities similarly to what the Linux kernel does
+	// They are used only when using capabilities with uid != 0.
+	configSpec.Process.Capabilities.Inheritable = []string{}
 	configSpec.Process.Capabilities.Bounding = caplist

 	user := strings.Split(s.User, ":")[0]
@@ -71,7 +75,6 @@
 	if (user == "" && s.UserNS.NSMode != specgen.KeepID) || user == "root" || user == "0" {
 		configSpec.Process.Capabilities.Effective = caplist
 		configSpec.Process.Capabilities.Permitted = caplist
-		configSpec.Process.Capabilities.Inheritable = caplist
 	} else {
 		mergedCaps, err := capabilities.MergeCapabilities(nil, s.CapAdd, nil)
 		if err != nil {
@@ -93,12 +96,12 @@
 		}
 		configSpec.Process.Capabilities.Effective = userCaps
 		configSpec.Process.Capabilities.Permitted = userCaps
-		configSpec.Process.Capabilities.Inheritable = userCaps

 		// Ambient capabilities were added to Linux 4.3.  Set ambient
 		// capabilities only when the kernel supports them.
 		if supportAmbientCapabilities() {
 			configSpec.Process.Capabilities.Ambient = userCaps
+			configSpec.Process.Capabilities.Inheritable = userCaps
 		}
 	}


Smokescreen is an HTTP proxy. The primary use case for Smokescreen is to prevent server-side request forgery (SSRF) attacks in which external attackers leverage the behavior of applications to connect to or scan internal infrastructure. Smokescreen also offers an option to deny access to additional (e.g., external) URLs by way of a deny list. There was an issue in Smokescreen that made it possible to bypass the deny list feature by surrounding the hostname with square brackets (e.g. `[example.com]`). This only impacted the HTTP proxy functionality of Smokescreen. HTTPS requests were not impacted. Smokescreen version 0.0.4 contains a patch for this issue.

Server-Side Request Forgery (SSRF)

The web server receives a URL or similar request from an upstream component and retrieves the contents of this URL, but it does not sufficiently ensure that the request is being sent to the expected destination.

func BuildProxy(config *Config) *goproxy.ProxyHttpServer {
	proxy := goproxy.NewProxyHttpServer()
	proxy.Verbose = false
	configureTransport(proxy.Tr, config)

	// dialContext will be invoked for both CONNECT and traditional proxy requests
	proxy.Tr.DialContext = dialContext

	// Use a custom goproxy.RoundTripperFunc to ensure that the correct context is attached to the request.
	// This is only used for non-CONNECT HTTP proxy requests. For connect requests, goproxy automatically
	// attaches goproxy.ProxyCtx prior to calling dialContext.
	rtFn := goproxy.RoundTripperFunc(func(req *http.Request, pctx *goproxy.ProxyCtx) (*http.Response, error) {
		ctx := context.WithValue(req.Context(), goproxy.ProxyContextKey, pctx)
		return proxy.Tr.RoundTrip(req.WithContext(ctx))
	})

	// Associate a timeout with the CONNECT proxy client connection
	if config.IdleTimeout != 0 {
		proxy.ConnectClientConnHandler = func(conn net.Conn) net.Conn {
			return NewTimeoutConn(conn, config.IdleTimeout)
		}
	}

	// Handle traditional HTTP proxy
	proxy.OnRequest().DoFunc(func(req *http.Request, pctx *goproxy.ProxyCtx) (*http.Request, *http.Response) {

		// We are intentionally *not* setting pctx.HTTPErrorHandler because with traditional HTTP
		// proxy requests we are able to specify the request during the call to OnResponse().
		sctx := newContext(config, httpProxy, req)

		// Attach smokescreenContext to goproxy.ProxyCtx
		pctx.UserData = sctx

		// Delete Smokescreen specific headers before goproxy forwards the request
		defer func() {
			req.Header.Del(roleHeader)
			req.Header.Del(traceHeader)
		}()

		// Set this on every request as every request mints a new goproxy.ProxyCtx
		pctx.RoundTripper = rtFn

		// Build an address parsable by net.ResolveTCPAddr
		remoteHost := req.Host
		if strings.LastIndex(remoteHost, ":") <= strings.LastIndex(remoteHost, "]") {
			switch req.URL.Scheme {
			case "http":
				remoteHost = net.JoinHostPort(remoteHost, "80")
			case "https":
				remoteHost = net.JoinHostPort(remoteHost, "443")
			default:
				remoteHost = net.JoinHostPort(remoteHost, "0")
			}
		}

		sctx.logger.WithField("url", req.RequestURI).Debug("received HTTP proxy request")

		sctx.decision, sctx.lookupTime, pctx.Error = checkIfRequestShouldBeProxied(config, req, remoteHost)

		// Returning any kind of response in this handler is goproxy's way of short circuiting
		// the request. The original request will never be sent, and goproxy will invoke our
		// response filter attached via the OnResponse() handler.
		if pctx.Error != nil {
			return req, rejectResponse(pctx, pctx.Error)
		}
		if !sctx.decision.allow {
			return req, rejectResponse(pctx, denyError{errors.New(sctx.decision.reason)})
		}

		// Proceed with proxying the request
		return req, nil
	})

	// Handle CONNECT proxy to TLS & other TCP protocols destination
	proxy.OnRequest().HandleConnectFunc(func(host string, pctx *goproxy.ProxyCtx) (*goproxy.ConnectAction, string) {
		pctx.UserData = newContext(config, connectProxy, pctx.Req)
		pctx.HTTPErrorHandler = HTTPErrorHandler

		// Defer logging the proxy event here because logProxy relies
		// on state set in handleConnect
		defer logProxy(config, pctx)
		defer pctx.Req.Header.Del(traceHeader)

		err := handleConnect(config, pctx)
		if err != nil {
			pctx.Resp = rejectResponse(pctx, err)
			return goproxy.RejectConnect, ""
		}
		return goproxy.OkConnect, host
	})

	// Strangely, goproxy can invoke this same function twice for a single HTTP request.
	//
	// If a proxy request is rejected due to an ACL denial, the response passed to this
	// function was created by Smokescreen's call to rejectResponse() in the OnRequest()
	// handler. This only happens once. This is also the behavior for an allowed request
	// which is completed successfully.
	//
	// If a proxy request is allowed, but the RoundTripper returns an error fulfulling
	// the HTTP request, goproxy will invoke this OnResponse() filter twice. First this
	// function will be called with a nil response, and as a result this function will
	// return a response to send back to the proxy client using rejectResponse(). This
	// function will be called again with the previously returned response, which will
	// simply trigger the logHTTP function and return.
	proxy.OnResponse().DoFunc(func(resp *http.Response, pctx *goproxy.ProxyCtx) *http.Response {
		sctx := pctx.UserData.(*smokescreenContext)

		if resp != nil && resp.Header.Get(errorHeader) != "" {
			if pctx.Error == nil && sctx.decision.allow {
				resp.Header.Del(errorHeader)
			}
		}

		if resp == nil && pctx.Error != nil {
			return rejectResponse(pctx, pctx.Error)
		}

		// In case of an error, this function is called a second time to filter the
		// response we generate so this logger will be called once.
		logProxy(config, pctx)
		return resp
	})
	return proxy
}

func handleConnect(config *Config, pctx *goproxy.ProxyCtx) error {
	sctx := pctx.UserData.(*smokescreenContext)

	// Check if requesting role is allowed to talk to remote
	sctx.decision, sctx.lookupTime, pctx.Error = checkIfRequestShouldBeProxied(config, pctx.Req, pctx.Req.Host)
	if pctx.Error != nil {
		return pctx.Error
	}
	if !sctx.decision.allow {
		return denyError{errors.New(sctx.decision.reason)}
	}

	return nil
}

func BuildProxy(config *Config) *goproxy.ProxyHttpServer {
	proxy := goproxy.NewProxyHttpServer()
	proxy.Verbose = false
	configureTransport(proxy.Tr, config)

	// dialContext will be invoked for both CONNECT and traditional proxy requests
	proxy.Tr.DialContext = dialContext

	// Use a custom goproxy.RoundTripperFunc to ensure that the correct context is attached to the request.
	// This is only used for non-CONNECT HTTP proxy requests. For connect requests, goproxy automatically
	// attaches goproxy.ProxyCtx prior to calling dialContext.
	rtFn := goproxy.RoundTripperFunc(func(req *http.Request, pctx *goproxy.ProxyCtx) (*http.Response, error) {
		ctx := context.WithValue(req.Context(), goproxy.ProxyContextKey, pctx)
		return proxy.Tr.RoundTrip(req.WithContext(ctx))
	})

	// Associate a timeout with the CONNECT proxy client connection
	if config.IdleTimeout != 0 {
		proxy.ConnectClientConnHandler = func(conn net.Conn) net.Conn {
			return NewTimeoutConn(conn, config.IdleTimeout)
		}
	}

	// Handle traditional HTTP proxy
	proxy.OnRequest().DoFunc(func(req *http.Request, pctx *goproxy.ProxyCtx) (*http.Request, *http.Response) {

		// We are intentionally *not* setting pctx.HTTPErrorHandler because with traditional HTTP
		// proxy requests we are able to specify the request during the call to OnResponse().
		sctx := newContext(config, httpProxy, req)

		// Attach smokescreenContext to goproxy.ProxyCtx
		pctx.UserData = sctx

		// Delete Smokescreen specific headers before goproxy forwards the request
		defer func() {
			req.Header.Del(roleHeader)
			req.Header.Del(traceHeader)
		}()

		// Set this on every request as every request mints a new goproxy.ProxyCtx
		pctx.RoundTripper = rtFn

		// Build an address parsable by net.ResolveTCPAddr
		remoteHost := req.URL.Hostname()
		if strings.LastIndex(remoteHost, ":") <= strings.LastIndex(remoteHost, "]") {
			switch req.URL.Scheme {
			case "http":
				remoteHost = net.JoinHostPort(remoteHost, "80")
			case "https":
				remoteHost = net.JoinHostPort(remoteHost, "443")
			default:
				remoteHost = net.JoinHostPort(remoteHost, "0")
			}
		}

		sctx.logger.WithField("url", req.RequestURI).Debug("received HTTP proxy request")

		sctx.decision, sctx.lookupTime, pctx.Error = checkIfRequestShouldBeProxied(config, req, remoteHost)

		// Returning any kind of response in this handler is goproxy's way of short circuiting
		// the request. The original request will never be sent, and goproxy will invoke our
		// response filter attached via the OnResponse() handler.
		if pctx.Error != nil {
			return req, rejectResponse(pctx, pctx.Error)
		}
		if !sctx.decision.allow {
			return req, rejectResponse(pctx, denyError{errors.New(sctx.decision.reason)})
		}

		// Proceed with proxying the request
		return req, nil
	})

	// Handle CONNECT proxy to TLS & other TCP protocols destination
	proxy.OnRequest().HandleConnectFunc(func(host string, pctx *goproxy.ProxyCtx) (*goproxy.ConnectAction, string) {
		pctx.UserData = newContext(config, connectProxy, pctx.Req)
		pctx.HTTPErrorHandler = HTTPErrorHandler

		// Defer logging the proxy event here because logProxy relies
		// on state set in handleConnect
		defer logProxy(config, pctx)
		defer pctx.Req.Header.Del(traceHeader)

		err := handleConnect(config, pctx)
		if err != nil {
			pctx.Resp = rejectResponse(pctx, err)
			return goproxy.RejectConnect, ""
		}
		return goproxy.OkConnect, host
	})

	// Strangely, goproxy can invoke this same function twice for a single HTTP request.
	//
	// If a proxy request is rejected due to an ACL denial, the response passed to this
	// function was created by Smokescreen's call to rejectResponse() in the OnRequest()
	// handler. This only happens once. This is also the behavior for an allowed request
	// which is completed successfully.
	//
	// If a proxy request is allowed, but the RoundTripper returns an error fulfulling
	// the HTTP request, goproxy will invoke this OnResponse() filter twice. First this
	// function will be called with a nil response, and as a result this function will
	// return a response to send back to the proxy client using rejectResponse(). This
	// function will be called again with the previously returned response, which will
	// simply trigger the logHTTP function and return.
	proxy.OnResponse().DoFunc(func(resp *http.Response, pctx *goproxy.ProxyCtx) *http.Response {
		sctx := pctx.UserData.(*smokescreenContext)

		if resp != nil && resp.Header.Get(errorHeader) != "" {
			if pctx.Error == nil && sctx.decision.allow {
				resp.Header.Del(errorHeader)
			}
		}

		if resp == nil && pctx.Error != nil {
			return rejectResponse(pctx, pctx.Error)
		}

		// In case of an error, this function is called a second time to filter the
		// response we generate so this logger will be called once.
		logProxy(config, pctx)
		return resp
	})
	return proxy
}

func handleConnect(config *Config, pctx *goproxy.ProxyCtx) error {
	sctx := pctx.UserData.(*smokescreenContext)

	// Check if requesting role is allowed to talk to remote
	sctx.decision, sctx.lookupTime, pctx.Error = checkIfRequestShouldBeProxied(config, pctx.Req, pctx.Req.URL.Hostname())
	if pctx.Error != nil {
		return pctx.Error
	}
	if !sctx.decision.allow {
		return denyError{errors.New(sctx.decision.reason)}
	}

	return nil
}

--- a/pkg/smokescreen/smokescreen.go
+++ b/pkg/smokescreen/smokescreen.go
@@ -41,7 +41,7 @@
 		pctx.RoundTripper = rtFn

 		// Build an address parsable by net.ResolveTCPAddr
-		remoteHost := req.Host
+		remoteHost := req.URL.Hostname()
 		if strings.LastIndex(remoteHost, ":") <= strings.LastIndex(remoteHost, "]") {
 			switch req.URL.Scheme {
 			case "http":

--- a/pkg/smokescreen/smokescreen.go
+++ b/pkg/smokescreen/smokescreen.go
@@ -2,7 +2,7 @@
 	sctx := pctx.UserData.(*smokescreenContext)

 	// Check if requesting role is allowed to talk to remote
-	sctx.decision, sctx.lookupTime, pctx.Error = checkIfRequestShouldBeProxied(config, pctx.Req, pctx.Req.Host)
+	sctx.decision, sctx.lookupTime, pctx.Error = checkIfRequestShouldBeProxied(config, pctx.Req, pctx.Req.URL.Hostname())
 	if pctx.Error != nil {
 		return pctx.Error
 	}

#!/bin/bash
# From ghcr.io/anonymous2578-data/cve-2022-29188:latest
# bash /workspace/fix-run.sh
set -e

cd /workspace/smokescreen
git apply --whitespace=nowarn /workspace/test.patch /workspace/fix.patch
go test  -timeout 30s -run ^TestHostSquareBrackets$ github.com/stripe/smokescreen/pkg/smokescreen

#!/bin/bash
# From ghcr.io/anonymous2578-data/cve-2022-29188:latest
# bash /workspace/unit_test.sh
set -e

cd /workspace/smokescreen
git apply --whitespace=nowarn /workspace/fix.patch
go test -timeout 120s -run '^(TestShuttingDownValue|TestClassifyAddr|TestErrorHeader|TestClearsErrorHeader|TestHealthcheck|TestConsistentHostHeader|TestUnsafeAllowPrivateRanges|TestClearsTraceIDHeader|TestRejectResponseHandler)$' github.com/stripe/smokescreen/pkg/smokescreen


A vulnerability in Example Dags of Apache Airflow allows an attacker with UI access who can trigger DAGs, to execute arbitrary commands via manually provided run_id parameter. This issue affects Apache Airflow Apache Airflow versions prior to 2.4.0.

Improper Control of Generation of Code ('Code Injection')

The product constructs all or part of a code segment using externally-influenced input from an upstream component, but it does not neutralize or incorrectly neutralizes special elements that could modify the syntax or behavior of the intended code segment.

Improper Neutralization of Special Elements used in a Command ('Command Injection')

The product constructs all or part of a command using externally-influenced input from an upstream component, but it does not neutralize or incorrectly neutralizes special elements that could modify the intended command when it is sent to a downstream component.

Improper Neutralization of Special Elements used in an OS Command ('OS Command Injection')

The product constructs all or part of an OS command using externally-influenced input from an upstream component, but it does not neutralize or incorrectly neutralizes special elements that could modify the intended OS command when it is sent to a downstream component.

with DAG(
    dag_id='example_bash_operator',
    schedule='0 0 * * *',
    start_date=pendulum.datetime(2021, 1, 1, tz="UTC"),
    catchup=False,
    dagrun_timeout=datetime.timedelta(minutes=60),
    tags=['example', 'example2'],
    params={"example_key": "example_value"},
) as dag:
    run_this_last = EmptyOperator(
        task_id='run_this_last',
    )

    # [START howto_operator_bash]
    run_this = BashOperator(
        task_id='run_after_loop',
        bash_command='echo 1',
    )
    # [END howto_operator_bash]

    run_this >> run_this_last

    for i in range(3):
        task = BashOperator(
            task_id='runme_' + str(i),
            bash_command='echo "{{ task_instance_key_str }}" && sleep 1',
        )
        task >> run_this

    # [START howto_operator_bash_template]
    also_run_this = BashOperator(
        task_id='also_run_this',
        bash_command='echo "run_id={{ run_id }} | dag_run={{ dag_run }}"',
    )
    # [END howto_operator_bash_template]
    also_run_this >> run_this_last

with DAG(
    dag_id='example_bash_operator',
    schedule='0 0 * * *',
    start_date=pendulum.datetime(2021, 1, 1, tz="UTC"),
    catchup=False,
    dagrun_timeout=datetime.timedelta(minutes=60),
    tags=['example', 'example2'],
    params={"example_key": "example_value"},
) as dag:
    run_this_last = EmptyOperator(
        task_id='run_this_last',
    )

    # [START howto_operator_bash]
    run_this = BashOperator(
        task_id='run_after_loop',
        bash_command='echo 1',
    )
    # [END howto_operator_bash]

    run_this >> run_this_last

    for i in range(3):
        task = BashOperator(
            task_id='runme_' + str(i),
            bash_command='echo "{{ task_instance_key_str }}" && sleep 1',
        )
        task >> run_this

    # [START howto_operator_bash_template]
    also_run_this = BashOperator(
        task_id='also_run_this',
        bash_command='echo "ti_key={{ task_instance_key_str }}"',
    )
    # [END howto_operator_bash_template]
    also_run_this >> run_this_last

--- a/airflow/example_dags/example_bash_operator.py
+++ b/airflow/example_dags/example_bash_operator.py
@@ -30,7 +30,7 @@
     # [START howto_operator_bash_template]
     also_run_this = BashOperator(
         task_id='also_run_this',
-        bash_command='echo "run_id={{ run_id }} | dag_run={{ dag_run }}"',
+        bash_command='echo "ti_key={{ task_instance_key_str }}"',
     )
     # [END howto_operator_bash_template]
     also_run_this >> run_this_last


Improper Access Control in GitHub repository usememos/memos prior to 0.13.2.

Improper Access Control

The product does not restrict or incorrectly restricts access to a resource from an unauthorized actor.

func JWTMiddleware(server *Server, next echo.HandlerFunc, secret string) echo.HandlerFunc {
	return func(c echo.Context) error {
		path := c.Request().URL.Path
		method := c.Request().Method

		if server.defaultAuthSkipper(c) {
			return next(c)
		}

		// Skip validation for server status endpoints.
		if common.HasPrefixes(path, "/api/ping", "/api/v1/idp", "/api/user/:id") && method == http.MethodGet {
			return next(c)
		}

		token := findAccessToken(c)
		if token == "" {
			// Allow the user to access the public endpoints.
			if common.HasPrefixes(path, "/o") {
				return next(c)
			}
			// When the request is not authenticated, we allow the user to access the memo endpoints for those public memos.
			if common.HasPrefixes(path, "/api/status", "/api/memo") && method == http.MethodGet {
				return next(c)
			}
			return echo.NewHTTPError(http.StatusUnauthorized, "Missing access token")
		}

		claims := &Claims{}
		accessToken, err := jwt.ParseWithClaims(token, claims, func(t *jwt.Token) (any, error) {
			if t.Method.Alg() != jwt.SigningMethodHS256.Name {
				return nil, errors.Errorf("unexpected access token signing method=%v, expect %v", t.Header["alg"], jwt.SigningMethodHS256)
			}
			if kid, ok := t.Header["kid"].(string); ok {
				if kid == "v1" {
					return []byte(secret), nil
				}
			}
			return nil, errors.Errorf("unexpected access token kid=%v", t.Header["kid"])
		})
		if !audienceContains(claims.Audience, auth.AccessTokenAudienceName) {
			return echo.NewHTTPError(http.StatusUnauthorized, fmt.Sprintf("Invalid access token, audience mismatch, got %q, expected %q.", claims.Audience, auth.AccessTokenAudienceName))
		}
		generateToken := time.Until(claims.ExpiresAt.Time) < auth.RefreshThresholdDuration
		if err != nil {
			var ve *jwt.ValidationError
			if errors.As(err, &ve) {
				// If expiration error is the only error, we will clear the err
				// and generate new access token and refresh token
				if ve.Errors == jwt.ValidationErrorExpired {
					generateToken = true
				}
			} else {
				return echo.NewHTTPError(http.StatusUnauthorized, errors.Wrap(err, "Invalid or expired access token"))
			}
		}

		// We either have a valid access token or we will attempt to generate new access token and refresh token
		ctx := c.Request().Context()
		userID, err := strconv.Atoi(claims.Subject)
		if err != nil {
			return echo.NewHTTPError(http.StatusUnauthorized, "Malformed ID in the token.")
		}

		// Even if there is no error, we still need to make sure the user still exists.
		user, err := server.Store.GetUser(ctx, &store.FindUserMessage{
			ID: &userID,
		})
		if err != nil {
			return echo.NewHTTPError(http.StatusInternalServerError, fmt.Sprintf("Server error to find user ID: %d", userID)).SetInternal(err)
		}
		if user == nil {
			return echo.NewHTTPError(http.StatusUnauthorized, fmt.Sprintf("Failed to find user ID: %d", userID))
		}

		if generateToken {
			generateTokenFunc := func() error {
				rc, err := c.Cookie(auth.RefreshTokenCookieName)
				if err != nil {
					return echo.NewHTTPError(http.StatusUnauthorized, "Failed to generate access token. Missing refresh token.")
				}

				// Parses token and checks if it's valid.
				refreshTokenClaims := &Claims{}
				refreshToken, err := jwt.ParseWithClaims(rc.Value, refreshTokenClaims, func(t *jwt.Token) (any, error) {
					if t.Method.Alg() != jwt.SigningMethodHS256.Name {
						return nil, errors.Errorf("unexpected refresh token signing method=%v, expected %v", t.Header["alg"], jwt.SigningMethodHS256)
					}

					if kid, ok := t.Header["kid"].(string); ok {
						if kid == "v1" {
							return []byte(secret), nil
						}
					}
					return nil, errors.Errorf("unexpected refresh token kid=%v", t.Header["kid"])
				})
				if err != nil {
					if err == jwt.ErrSignatureInvalid {
						return echo.NewHTTPError(http.StatusUnauthorized, "Failed to generate access token. Invalid refresh token signature.")
					}
					return echo.NewHTTPError(http.StatusInternalServerError, fmt.Sprintf("Server error to refresh expired token. User Id %d", userID)).SetInternal(err)
				}

				if !audienceContains(refreshTokenClaims.Audience, auth.RefreshTokenAudienceName) {
					return echo.NewHTTPError(http.StatusUnauthorized,
						fmt.Sprintf("Invalid refresh token, audience mismatch, got %q, expected %q. you may send request to the wrong environment",
							refreshTokenClaims.Audience,
							auth.RefreshTokenAudienceName,
						))
				}

				// If we have a valid refresh token, we will generate new access token and refresh token
				if refreshToken != nil && refreshToken.Valid {
					if err := auth.GenerateTokensAndSetCookies(c, user, secret); err != nil {
						return echo.NewHTTPError(http.StatusInternalServerError, fmt.Sprintf("Server error to refresh expired token. User Id %d", userID)).SetInternal(err)
					}
				}

				return nil
			}

			// It may happen that we still have a valid access token, but we encounter issue when trying to generate new token
			// In such case, we won't return the error.
			if err := generateTokenFunc(); err != nil && !accessToken.Valid {
				return err
			}
		}

		// Stores userID into context.
		c.Set(getUserIDContextKey(), userID)
		return next(c)
	}
}

func JWTMiddleware(server *Server, next echo.HandlerFunc, secret string) echo.HandlerFunc {
	return func(c echo.Context) error {
		path := c.Request().URL.Path
		method := c.Request().Method

		if server.defaultAuthSkipper(c) {
			return next(c)
		}

		// Skip validation for server status endpoints.
		if common.HasPrefixes(path, "/api/ping", "/api/v1/idp", "/api/user/:id") && method == http.MethodGet {
			return next(c)
		}

		token := findAccessToken(c)
		if token == "" {
			// Allow the user to access the public endpoints.
			if common.HasPrefixes(path, "/o") {
				return next(c)
			}
			// When the request is not authenticated, we allow the user to access the memo endpoints for those public memos.
			if common.HasPrefixes(path, "/api/status", "/api/memo") && method == http.MethodGet {
				return next(c)
			}
			return echo.NewHTTPError(http.StatusUnauthorized, "Missing access token")
		}

		claims := &Claims{}
		accessToken, err := jwt.ParseWithClaims(token, claims, func(t *jwt.Token) (any, error) {
			if t.Method.Alg() != jwt.SigningMethodHS256.Name {
				return nil, errors.Errorf("unexpected access token signing method=%v, expect %v", t.Header["alg"], jwt.SigningMethodHS256)
			}
			if kid, ok := t.Header["kid"].(string); ok {
				if kid == "v1" {
					return []byte(secret), nil
				}
			}
			return nil, errors.Errorf("unexpected access token kid=%v", t.Header["kid"])
		})

		if !accessToken.Valid {
			return echo.NewHTTPError(http.StatusUnauthorized, "Invalid access token.")
		}

		if !audienceContains(claims.Audience, auth.AccessTokenAudienceName) {
			return echo.NewHTTPError(http.StatusUnauthorized, fmt.Sprintf("Invalid access token, audience mismatch, got %q, expected %q.", claims.Audience, auth.AccessTokenAudienceName))
		}
		generateToken := time.Until(claims.ExpiresAt.Time) < auth.RefreshThresholdDuration
		if err != nil {
			var ve *jwt.ValidationError
			if errors.As(err, &ve) {
				// If expiration error is the only error, we will clear the err
				// and generate new access token and refresh token
				if ve.Errors == jwt.ValidationErrorExpired {
					generateToken = true
				}
			} else {
				return echo.NewHTTPError(http.StatusUnauthorized, errors.Wrap(err, "Invalid or expired access token"))
			}
		}

		// We either have a valid access token or we will attempt to generate new access token and refresh token
		ctx := c.Request().Context()
		userID, err := strconv.Atoi(claims.Subject)
		if err != nil {
			return echo.NewHTTPError(http.StatusUnauthorized, "Malformed ID in the token.")
		}

		// Even if there is no error, we still need to make sure the user still exists.
		user, err := server.Store.GetUser(ctx, &store.FindUserMessage{
			ID: &userID,
		})
		if err != nil {
			return echo.NewHTTPError(http.StatusInternalServerError, fmt.Sprintf("Server error to find user ID: %d", userID)).SetInternal(err)
		}
		if user == nil {
			return echo.NewHTTPError(http.StatusUnauthorized, fmt.Sprintf("Failed to find user ID: %d", userID))
		}

		if generateToken {
			generateTokenFunc := func() error {
				rc, err := c.Cookie(auth.RefreshTokenCookieName)
				if err != nil {
					return echo.NewHTTPError(http.StatusUnauthorized, "Failed to generate access token. Missing refresh token.")
				}

				// Parses token and checks if it's valid.
				refreshTokenClaims := &Claims{}
				refreshToken, err := jwt.ParseWithClaims(rc.Value, refreshTokenClaims, func(t *jwt.Token) (any, error) {
					if t.Method.Alg() != jwt.SigningMethodHS256.Name {
						return nil, errors.Errorf("unexpected refresh token signing method=%v, expected %v", t.Header["alg"], jwt.SigningMethodHS256)
					}

					if kid, ok := t.Header["kid"].(string); ok {
						if kid == "v1" {
							return []byte(secret), nil
						}
					}
					return nil, errors.Errorf("unexpected refresh token kid=%v", t.Header["kid"])
				})
				if err != nil {
					if err == jwt.ErrSignatureInvalid {
						return echo.NewHTTPError(http.StatusUnauthorized, "Failed to generate access token. Invalid refresh token signature.")
					}
					return echo.NewHTTPError(http.StatusInternalServerError, fmt.Sprintf("Server error to refresh expired token. User Id %d", userID)).SetInternal(err)
				}

				if !audienceContains(refreshTokenClaims.Audience, auth.RefreshTokenAudienceName) {
					return echo.NewHTTPError(http.StatusUnauthorized,
						fmt.Sprintf("Invalid refresh token, audience mismatch, got %q, expected %q. you may send request to the wrong environment",
							refreshTokenClaims.Audience,
							auth.RefreshTokenAudienceName,
						))
				}

				// If we have a valid refresh token, we will generate new access token and refresh token
				if refreshToken != nil && refreshToken.Valid {
					if err := auth.GenerateTokensAndSetCookies(c, user, secret); err != nil {
						return echo.NewHTTPError(http.StatusInternalServerError, fmt.Sprintf("Server error to refresh expired token. User Id %d", userID)).SetInternal(err)
					}
				}

				return nil
			}

			// It may happen that we still have a valid access token, but we encounter issue when trying to generate new token
			// In such case, we won't return the error.
			if err := generateTokenFunc(); err != nil && !accessToken.Valid {
				return err
			}
		}

		// Stores userID into context.
		c.Set(getUserIDContextKey(), userID)
		return next(c)
	}
}

--- a/server/jwt.go
+++ b/server/jwt.go
@@ -37,6 +37,11 @@
 			}
 			return nil, errors.Errorf("unexpected access token kid=%v", t.Header["kid"])
 		})
+
+		if !accessToken.Valid {
+			return echo.NewHTTPError(http.StatusUnauthorized, "Invalid access token.")
+		}
+
 		if !audienceContains(claims.Audience, auth.AccessTokenAudienceName) {
 			return echo.NewHTTPError(http.StatusUnauthorized, fmt.Sprintf("Invalid access token, audience mismatch, got %q, expected %q.", claims.Audience, auth.AccessTokenAudienceName))
 		}


Wings is Pterodactyl's server control plane. This vulnerability can be used to delete files and directories recursively on the host system.  This vulnerability can be combined with `GHSA-p8r3-83r8-jwj5` to overwrite files on the host system. In order to use this exploit, an attacker must have an existing "server" allocated and controlled by Wings. This vulnerability has been resolved in version `v1.11.4` of Wings, and has been back-ported to the 1.7 release series in `v1.7.4`. Anyone running `v1.11.x` should upgrade to `v1.11.4` and anyone running `v1.7.x` should upgrade to `v1.7.4`. There are no known workarounds for this issue.

Improper Link Resolution Before File Access ('Link Following')

The product attempts to access a file based on the filename, but it does not properly prevent that filename from identifying a link or shortcut that resolves to an unintended resource.

func (fs *Filesystem) Delete(p string) error {
	wg := sync.WaitGroup{}
	// This is one of the few (only?) places in the codebase where we're explicitly not using
	// the SafePath functionality when working with user provided input. If we did, you would
	// not be able to delete a file that is a symlink pointing to a location outside of the data
	// directory.
	//
	// We also want to avoid resolving a symlink that points _within_ the data directory and thus
	// deleting the actual source file for the symlink rather than the symlink itself. For these
	// purposes just resolve the actual file path using filepath.Join() and confirm that the path
	// exists within the data directory.
	resolved := fs.unsafeFilePath(p)
	if !fs.unsafeIsInDataDirectory(resolved) {
		return NewBadPathResolution(p, resolved)
	}

	// Block any whoopsies.
	if resolved == fs.Path() {
		return errors.New("cannot delete root server directory")
	}

	if st, err := os.Lstat(resolved); err != nil {
		if !os.IsNotExist(err) {
			fs.error(err).Warn("error while attempting to stat file before deletion")
		}
	} else {
		if !st.IsDir() {
			fs.addDisk(-st.Size())
		} else {
			wg.Add(1)
			go func(wg *sync.WaitGroup, st os.FileInfo, resolved string) {
				defer wg.Done()
				if s, err := fs.DirectorySize(resolved); err == nil {
					fs.addDisk(-s)
				}
			}(&wg, st, resolved)
		}
	}

	wg.Wait()

	return os.RemoveAll(resolved)
}

func (fs *Filesystem) Delete(p string) error {
	// This is one of the few (only?) places in the codebase where we're explicitly not using
	// the SafePath functionality when working with user provided input. If we did, you would
	// not be able to delete a file that is a symlink pointing to a location outside the data
	// directory.
	//
	// We also want to avoid resolving a symlink that points _within_ the data directory and thus
	// deleting the actual source file for the symlink rather than the symlink itself. For these
	// purposes just resolve the actual file path using filepath.Join() and confirm that the path
	// exists within the data directory.
	resolved := fs.unsafeFilePath(p)
	if !fs.unsafeIsInDataDirectory(resolved) {
		return NewBadPathResolution(p, resolved)
	}

	// Block any whoopsies.
	if resolved == fs.Path() {
		return errors.New("cannot delete root server directory")
	}

	st, err := os.Lstat(resolved)
	if err != nil {
		if !os.IsNotExist(err) {
			fs.error(err).Warn("error while attempting to stat file before deletion")
			return err
		}

		// The following logic is used to handle a case where a user attempts to
		// delete a file that does not exist through a directory symlink.
		// We don't want to reveal that the file does not exist, so we validate
		// the path of the symlink and return a bad path error if it is invalid.

		// The requested file or directory doesn't exist, so at this point we
		// need to iterate up the path chain until we hit a directory that
		// _does_ exist and can be validated.
		parts := strings.Split(filepath.Dir(resolved), "/")

		// Range over all the path parts and form directory paths from the end
		// moving up until we have a valid resolution, or we run out of paths to
		// try.
		for k := range parts {
			try := strings.Join(parts[:(len(parts)-k)], "/")
			if !fs.unsafeIsInDataDirectory(try) {
				break
			}

			t, err := filepath.EvalSymlinks(try)
			if err == nil {
				if !fs.unsafeIsInDataDirectory(t) {
					return NewBadPathResolution(p, t)
				}
				break
			}
		}

		// Always return early if the file does not exist.
		return nil
	}

	// If the file is not a symlink, we need to check that it is not within a
	// symlinked directory that points outside the data directory.
	if st.Mode()&os.ModeSymlink == 0 {
		ep, err := filepath.EvalSymlinks(resolved)
		if err != nil {
			if !os.IsNotExist(err) {
				return err
			}
		} else if !fs.unsafeIsInDataDirectory(ep) {
			return NewBadPathResolution(p, ep)
		}
	}

	if st.IsDir() {
		if s, err := fs.DirectorySize(resolved); err == nil {
			fs.addDisk(-s)
		}
	} else {
		fs.addDisk(-st.Size())
	}

	return os.RemoveAll(resolved)
}

--- a/server/filesystem/filesystem.go
+++ b/server/filesystem/filesystem.go
@@ -1,8 +1,7 @@
 func (fs *Filesystem) Delete(p string) error {
-	wg := sync.WaitGroup{}
 	// This is one of the few (only?) places in the codebase where we're explicitly not using
 	// the SafePath functionality when working with user provided input. If we did, you would
-	// not be able to delete a file that is a symlink pointing to a location outside of the data
+	// not be able to delete a file that is a symlink pointing to a location outside the data
 	// directory.
 	//
 	// We also want to avoid resolving a symlink that points _within_ the data directory and thus
@@ -19,25 +18,65 @@
 		return errors.New("cannot delete root server directory")
 	}

-	if st, err := os.Lstat(resolved); err != nil {
+	st, err := os.Lstat(resolved)
+	if err != nil {
 		if !os.IsNotExist(err) {
 			fs.error(err).Warn("error while attempting to stat file before deletion")
+			return err
 		}
-	} else {
-		if !st.IsDir() {
-			fs.addDisk(-st.Size())
-		} else {
-			wg.Add(1)
-			go func(wg *sync.WaitGroup, st os.FileInfo, resolved string) {
-				defer wg.Done()
-				if s, err := fs.DirectorySize(resolved); err == nil {
-					fs.addDisk(-s)
+
+		// The following logic is used to handle a case where a user attempts to
+		// delete a file that does not exist through a directory symlink.
+		// We don't want to reveal that the file does not exist, so we validate
+		// the path of the symlink and return a bad path error if it is invalid.
+
+		// The requested file or directory doesn't exist, so at this point we
+		// need to iterate up the path chain until we hit a directory that
+		// _does_ exist and can be validated.
+		parts := strings.Split(filepath.Dir(resolved), "/")
+
+		// Range over all the path parts and form directory paths from the end
+		// moving up until we have a valid resolution, or we run out of paths to
+		// try.
+		for k := range parts {
+			try := strings.Join(parts[:(len(parts)-k)], "/")
+			if !fs.unsafeIsInDataDirectory(try) {
+				break
+			}
+
+			t, err := filepath.EvalSymlinks(try)
+			if err == nil {
+				if !fs.unsafeIsInDataDirectory(t) {
+					return NewBadPathResolution(p, t)
 				}
-			}(&wg, st, resolved)
+				break
+			}
+		}
+
+		// Always return early if the file does not exist.
+		return nil
+	}
+
+	// If the file is not a symlink, we need to check that it is not within a
+	// symlinked directory that points outside the data directory.
+	if st.Mode()&os.ModeSymlink == 0 {
+		ep, err := filepath.EvalSymlinks(resolved)
+		if err != nil {
+			if !os.IsNotExist(err) {
+				return err
+			}
+		} else if !fs.unsafeIsInDataDirectory(ep) {
+			return NewBadPathResolution(p, ep)
 		}
 	}

-	wg.Wait()
+	if st.IsDir() {
+		if s, err := fs.DirectorySize(resolved); err == nil {
+			fs.addDisk(-s)
+		}
+	} else {
+		fs.addDisk(-st.Size())
+	}

 	return os.RemoveAll(resolved)
 }

#!/bin/bash
# From ghcr.io/anonymous2578-data/cve-2023-25168:latest
# bash /workspace/fix-run.sh
set -e

cd /workspace/wings
git apply --whitespace=nowarn /workspace/test.patch /workspace/fix.patch
go test -timeout 30s -run ^TestFilesystem_Delete$ github.com/pterodactyl/wings/server/filesystem

#!/bin/bash
# From ghcr.io/anonymous2578-data/cve-2023-25168:latest
# bash /workspace/unit_test.sh
set -e

cd /workspace/wings
git apply --whitespace=nowarn /workspace/fix.patch
go test -timeout 30s -run '^(TestFilesystem_Writefile|TestFilesystem_CreateDirectory|TestFilesystem_Rename|TestFilesystem_Copy|TestFilesystem_Openfile|TestFilesystem_Delete)$' github.com/pterodactyl/wings/server/filesystem


Langroid is a framework for building large-language-model-powered applications. Prior to version 0.53.4, a LLM application leveraging `XMLToolMessage` class may be exposed to untrusted XML input that could result in DoS and/or exposing local files with sensitive information. Version 0.53.4 fixes the issue.

Improper Restriction of XML External Entity Reference

The product processes an XML document that can contain XML entities with URIs that resolve to documents outside of the intended sphere of control, causing the product to embed incorrect documents into its output.

    def pandas_eval(self, msg: PandasEvalTool) -> str:
        """
        Handle a PandasEvalTool message by evaluating the `expression` field
            and returning the result.
        Args:
            msg (PandasEvalTool): The tool-message to handle.

        Returns:
            str: The result of running the code along with any print output.
        """
        self.sent_expression = True
        exprn = msg.expression
        local_vars = {"df": self.df}
        # Create a string-based I/O stream
        code_out = io.StringIO()

        # Temporarily redirect standard output to our string-based I/O stream
        sys.stdout = code_out

        # Evaluate the last line and get the result
        try:
            eval_result = pd.eval(exprn, local_dict=local_vars)
        except Exception as e:
            eval_result = f"ERROR: {type(e)}: {e}"

        if eval_result is None:
            eval_result = ""

        # Always restore the original standard output
        sys.stdout = sys.__stdout__

        # If df has been modified in-place, save the changes back to self.df
        self.df = local_vars["df"]

        # Get the resulting string from the I/O stream
        print_result = code_out.getvalue() or ""
        sep = "\n" if print_result else ""
        # Combine the print and eval results
        result = f"{print_result}{sep}{eval_result}"
        if result == "":
            result = "No result"
        # Return the result
        return result

    def extract_field_values(cls, formatted_string: str) -> Optional[Dict[str, Any]]:
        """
        Extracts field values from an XML-formatted string.

        Args:
            formatted_string (str): The XML-formatted string to parse.

        Returns:
            Optional[Dict[str, Any]]: A dictionary containing the extracted field
                values, where keys are the XML element names and values are their
                corresponding contents.
            Returns None if parsing fails or the root element is not a dictionary.

        Raises:
            etree.XMLSyntaxError: If the input string is not valid XML.
        """
        parser = etree.XMLParser(strip_cdata=False)
        root = etree.fromstring(formatted_string.encode("utf-8"), parser=parser)

        def parse_element(element: etree._Element) -> Any:
            # Skip elements starting with underscore
            if element.tag.startswith("_"):
                return {}

            field_info = cls.__fields__.get(element.tag)
            is_verbatim = field_info and field_info.field_info.extra.get(
                "verbatim", False
            )

            if is_verbatim:
                # For code elements, preserve the content as is, including whitespace
                content = element.text if element.text else ""
                # Strip leading and trailing triple backticks if present,
                # accounting for whitespace
                return (
                    content.strip().removeprefix("```").removesuffix("```").strip()
                    if content.strip().startswith("```")
                    and content.strip().endswith("```")
                    else content
                )
            elif len(element) == 0:
                # For non-code leaf elements, strip whitespace
                return element.text.strip() if element.text else ""
            else:
                # For branch elements, handle potential lists or nested structures
                children = [parse_element(child) for child in element]
                if all(child.tag == element[0].tag for child in element):
                    # If all children have the same tag, treat as a list
                    return children
                else:
                    # Otherwise, treat as a dictionary
                    result = {child.tag: parse_element(child) for child in element}
                    # Check if this corresponds to a nested Pydantic model
                    if field_info and issubclass(field_info.type_, BaseModel):
                        return field_info.type_(**result)
                    return result

        result = parse_element(root)
        if not isinstance(result, dict):
            return None
        # Filter out empty dictionaries from skipped underscore fields
        return {k: v for k, v in result.items() if v != {}}

    def pandas_eval(self, msg: PandasEvalTool) -> str:
        """
        Handle a PandasEvalTool message by evaluating the `expression` field
            and returning the result.
        Args:
            msg (PandasEvalTool): The tool-message to handle.

        Returns:
            str: The result of running the code along with any print output.
        """
        self.sent_expression = True
        exprn = msg.expression
        local_vars = {"df": self.df}
        # Create a string-based I/O stream
        code_out = io.StringIO()

        # Temporarily redirect standard output to our string-based I/O stream
        sys.stdout = code_out

        # Evaluate the last line and get the result;
        # SECURITY: eval only with empty globals and {"df": df} in locals to
        # prevent arbitrary Python code execution.
        try:
            eval_result = eval(exprn, {}, local_vars)
        except Exception as e:
            eval_result = f"ERROR: {type(e)}: {e}"

        if eval_result is None:
            eval_result = ""

        # Always restore the original standard output
        sys.stdout = sys.__stdout__

        # If df has been modified in-place, save the changes back to self.df
        self.df = local_vars["df"]

        # Get the resulting string from the I/O stream
        print_result = code_out.getvalue() or ""
        sep = "\n" if print_result else ""
        # Combine the print and eval results
        result = f"{print_result}{sep}{eval_result}"
        if result == "":
            result = "No result"
        # Return the result
        return result

    def extract_field_values(cls, formatted_string: str) -> Optional[Dict[str, Any]]:
        """
        Extracts field values from an XML-formatted string.

        Args:
            formatted_string (str): The XML-formatted string to parse.

        Returns:
            Optional[Dict[str, Any]]: A dictionary containing the extracted field
                values, where keys are the XML element names and values are their
                corresponding contents.
            Returns None if parsing fails or the root element is not a dictionary.

        Raises:
            etree.XMLSyntaxError: If the input string is not valid XML.
        """
        # SECURITY: Initialize XMLParser with flags to prevent
        # XML External Entity (XXE), billion laughs, and external DTD attacks by
        # disabling entity resolution, DTD loading, and network access;
        # `strip_cdata=False` is needed to preserve
        # content within CDATA sections (e.g., for code).
        parser = etree.XMLParser(
            strip_cdata=False,
            resolve_entities=False,
            load_dtd=False,
            no_network=True,
        )
        root = etree.fromstring(formatted_string.encode("utf-8"), parser=parser)

        def parse_element(element: etree._Element) -> Any:
            # Skip elements starting with underscore
            if element.tag.startswith("_"):
                return {}

            field_info = cls.__fields__.get(element.tag)
            is_verbatim = field_info and field_info.field_info.extra.get(
                "verbatim", False
            )

            if is_verbatim:
                # For code elements, preserve the content as is, including whitespace
                content = element.text if element.text else ""
                # Strip leading and trailing triple backticks if present,
                # accounting for whitespace
                return (
                    content.strip().removeprefix("```").removesuffix("```").strip()
                    if content.strip().startswith("```")
                    and content.strip().endswith("```")
                    else content
                )
            elif len(element) == 0:
                # For non-code leaf elements, strip whitespace
                return element.text.strip() if element.text else ""
            else:
                # For branch elements, handle potential lists or nested structures
                children = [parse_element(child) for child in element]
                if all(child.tag == element[0].tag for child in element):
                    # If all children have the same tag, treat as a list
                    return children
                else:
                    # Otherwise, treat as a dictionary
                    result = {child.tag: parse_element(child) for child in element}
                    # Check if this corresponds to a nested Pydantic model
                    if field_info and issubclass(field_info.type_, BaseModel):
                        return field_info.type_(**result)
                    return result

        result = parse_element(root)
        if not isinstance(result, dict):
            return None
        # Filter out empty dictionaries from skipped underscore fields
        return {k: v for k, v in result.items() if v != {}}

--- a/langroid/agent/special/table_chat_agent.py
+++ b/langroid/agent/special/table_chat_agent.py
@@ -17,9 +17,11 @@
         # Temporarily redirect standard output to our string-based I/O stream
         sys.stdout = code_out

-        # Evaluate the last line and get the result
+        # Evaluate the last line and get the result;
+        # SECURITY: eval only with empty globals and {"df": df} in locals to
+        # prevent arbitrary Python code execution.
         try:
-            eval_result = pd.eval(exprn, local_dict=local_vars)
+            eval_result = eval(exprn, {}, local_vars)
         except Exception as e:
             eval_result = f"ERROR: {type(e)}: {e}"

--- a/langroid/agent/xml_tool_message.py
+++ b/langroid/agent/xml_tool_message.py
@@ -14,7 +14,17 @@
         Raises:
             etree.XMLSyntaxError: If the input string is not valid XML.
         """
-        parser = etree.XMLParser(strip_cdata=False)
+        # SECURITY: Initialize XMLParser with flags to prevent
+        # XML External Entity (XXE), billion laughs, and external DTD attacks by
+        # disabling entity resolution, DTD loading, and network access;
+        # `strip_cdata=False` is needed to preserve
+        # content within CDATA sections (e.g., for code).
+        parser = etree.XMLParser(
+            strip_cdata=False,
+            resolve_entities=False,
+            load_dtd=False,
+            no_network=True,
+        )
         root = etree.fromstring(formatted_string.encode("utf-8"), parser=parser)

         def parse_element(element: etree._Element) -> Any:


OSU Open Source Lab VNCAuthProxy through 1.1.1 is affected by an vncap/vnc/protocol.py VNCServerAuthenticator authentication-bypass vulnerability that could allow a malicious actor to gain unauthorized access to a VNC session or to disconnect a legitimate user from a VNC session. A remote attacker with network access to the proxy server could leverage this vulnerability to connect to VNC servers protected by the proxy server without providing any authentication credentials. Exploitation of this issue requires that the proxy server is currently accepting connections for the target VNC server.

    def check_version(self, version):
        """
        Determine the client's version and decide whether to continue the
        handshake.
        """

        if version == self.VERSION:
            log.msg("Client version %s is valid" % version.strip())
            # Hardcoded: 2 security types: None and VNC Auth.
            self.transport.write("\x02\x01\x02")
            return self.select_security_type, 1
        else:
            log.err("Can't handle VNC version %r" % version)
            self.transport.loseConnection()

    def select_security_type(self, security_type):
        """
        Choose the security type that the client wants.
        """

        security_type = ord(security_type)

        if security_type == 2:
            # VNC authentication. Issue our challenge.
            self.challenge = urandom(16)
            self.transport.write(self.challenge)

            return self.vnc_authentication_result, 16
        elif security_type == 1:
            # No authentication. Just move to the SecurityResult.
            self.authenticated()
        else:
            log.err("Couldn't agree on an authentication scheme!")
            self.transport.loseConnection()

    def check_version(self, version):
        """
        Determine the client's version and decide whether to continue the
        handshake.
        """

        if version == self.VERSION:
            log.msg("Client version %s is valid" % version.strip())
            # Hardcoded: 1 security type: VNC Auth.
            self.transport.write("\x01\x02")
            return self.select_security_type, 1
        else:
            log.err("Can't handle VNC version %r" % version)
            self.transport.loseConnection()

    def select_security_type(self, security_type):
        """
        Choose the security type that the client wants.
        """

        security_type = ord(security_type)

        if security_type == 2:
            # VNC authentication. Issue our challenge.
            self.challenge = urandom(16)
            self.transport.write(self.challenge)

            return self.vnc_authentication_result, 16
        else:
            log.err("Couldn't agree on an authentication scheme!")
            self.transport.loseConnection()

--- a/vncap/vnc/protocol.py
+++ b/vncap/vnc/protocol.py
@@ -6,8 +6,8 @@

         if version == self.VERSION:
             log.msg("Client version %s is valid" % version.strip())
-            # Hardcoded: 2 security types: None and VNC Auth.
-            self.transport.write("\x02\x01\x02")
+            # Hardcoded: 1 security type: VNC Auth.
+            self.transport.write("\x01\x02")
             return self.select_security_type, 1
         else:
             log.err("Can't handle VNC version %r" % version)

--- a/vncap/vnc/protocol.py
+++ b/vncap/vnc/protocol.py
@@ -11,9 +11,6 @@
             self.transport.write(self.challenge)

             return self.vnc_authentication_result, 16
-        elif security_type == 1:
-            # No authentication. Just move to the SecurityResult.
-            self.authenticated()
         else:
             log.err("Couldn't agree on an authentication scheme!")
             self.transport.loseConnection()


A Server Side Request Forgery (SSRF) in the Data Import module in Heartex - Label Studio Community Edition versions 1.5.0 and earlier allows an authenticated user to access arbitrary files on the system. Furthermore, self-registration is enabled by default in these versions of Label Studio enabling a remote attacker to create a new account and then exploit the SSRF.

def load_tasks(request, project):
    """ Load tasks from different types of request.data / request.files
    """
    file_upload_ids, found_formats, data_keys = [], [], set()
    could_be_tasks_lists = False

    # take tasks from request FILES
    if len(request.FILES):
        check_file_sizes_and_number(request.FILES)
        for filename, file in request.FILES.items():
            file_upload = create_file_upload(request, project, file)
            if file_upload.format_could_be_tasks_list:
                could_be_tasks_lists = True
            file_upload_ids.append(file_upload.id)
        tasks, found_formats, data_keys = FileUpload.load_tasks_from_uploaded_files(project, file_upload_ids)

    # take tasks from url address
    elif 'application/x-www-form-urlencoded' in request.content_type:
        # empty url
        url = request.data.get('url')
        if not url:
            raise ValidationError('"url" is not found in request data')

        # try to load json with task or tasks from url as string
        json_data = str_to_json(url)
        if json_data:
            file_upload = create_file_upload(request, project, SimpleUploadedFile('inplace.json', url.encode()))
            file_upload_ids.append(file_upload.id)
            tasks, found_formats, data_keys = FileUpload.load_tasks_from_uploaded_files(project, file_upload_ids)

        # download file using url and read tasks from it
        else:
            if settings.SSRF_PROTECTION_ENABLED and url_is_local(url):
                raise ImportFromLocalIPError
            data_keys, found_formats, tasks, file_upload_ids = tasks_from_url(
                file_upload_ids, project, request, url
            )

    # take one task from request DATA
    elif 'application/json' in request.content_type and isinstance(request.data, dict):
        tasks = [request.data]

    # take many tasks from request DATA
    elif 'application/json' in request.content_type and isinstance(request.data, list):
        tasks = request.data

    # incorrect data source
    else:
        raise ValidationError('load_tasks: No data found in DATA or in FILES')

    # check is data root is list
    if not isinstance(tasks, list):
        raise ValidationError('load_tasks: Data root must be list')

    # empty tasks error
    if not tasks:
        raise ValidationError('load_tasks: No tasks added')

    check_max_task_number(tasks)
    return tasks, file_upload_ids, could_be_tasks_lists, found_formats, list(data_keys)

def load_tasks(request, project):
    """ Load tasks from different types of request.data / request.files
    """
    file_upload_ids, found_formats, data_keys = [], [], set()
    could_be_tasks_lists = False

    # take tasks from request FILES
    if len(request.FILES):
        check_file_sizes_and_number(request.FILES)
        for filename, file in request.FILES.items():
            file_upload = create_file_upload(request, project, file)
            if file_upload.format_could_be_tasks_list:
                could_be_tasks_lists = True
            file_upload_ids.append(file_upload.id)
        tasks, found_formats, data_keys = FileUpload.load_tasks_from_uploaded_files(project, file_upload_ids)

    # take tasks from url address
    elif 'application/x-www-form-urlencoded' in request.content_type:
        # empty url
        url = request.data.get('url')
        if not url:
            raise ValidationError('"url" is not found in request data')

        # try to load json with task or tasks from url as string
        json_data = str_to_json(url)
        if json_data:
            file_upload = create_file_upload(request, project, SimpleUploadedFile('inplace.json', url.encode()))
            file_upload_ids.append(file_upload.id)
            tasks, found_formats, data_keys = FileUpload.load_tasks_from_uploaded_files(project, file_upload_ids)

        # download file using url and read tasks from it
        else:
            if settings.SSRF_PROTECTION_ENABLED and url_is_local(url):
                raise ImportFromLocalIPError

            if url.strip().startswith('file://'):
                raise ValidationError('"url" is not valid')

            data_keys, found_formats, tasks, file_upload_ids = tasks_from_url(
                file_upload_ids, project, request, url
            )

    # take one task from request DATA
    elif 'application/json' in request.content_type and isinstance(request.data, dict):
        tasks = [request.data]

    # take many tasks from request DATA
    elif 'application/json' in request.content_type and isinstance(request.data, list):
        tasks = request.data

    # incorrect data source
    else:
        raise ValidationError('load_tasks: No data found in DATA or in FILES')

    # check is data root is list
    if not isinstance(tasks, list):
        raise ValidationError('load_tasks: Data root must be list')

    # empty tasks error
    if not tasks:
        raise ValidationError('load_tasks: No tasks added')

    check_max_task_number(tasks)
    return tasks, file_upload_ids, could_be_tasks_lists, found_formats, list(data_keys)

--- a/label_studio/data_import/uploader.py
+++ b/label_studio/data_import/uploader.py
@@ -32,6 +32,10 @@
         else:
             if settings.SSRF_PROTECTION_ENABLED and url_is_local(url):
                 raise ImportFromLocalIPError
+
+            if url.strip().startswith('file://'):
+                raise ValidationError('"url" is not valid')
+
             data_keys, found_formats, tasks, file_upload_ids = tasks_from_url(
                 file_upload_ids, project, request, url
             )


This affects the package total.js before 3.4.7. The issue occurs in the image.pipe and image.stream functions. The type parameter is used to build the command that is then executed using child_process.spawn. The issue occurs because child_process.spawn is called with the option shell set to true and because the type parameter is not properly sanitized.

ImageProto.stream = function(type, writer) {

	var self = this;

	!self.builder.length && self.minify();

	if (!type)
		type = self.outputType;

	F.stats.performance.open++;
	var cmd = spawn(CMD_CONVERT[self.cmdarg], self.arg(self.filename ? wrap(self.filename) : '-', (type ? type + ':' : '') + '-'), SPAWN_OPT);
	if (self.currentStream) {
		if (self.currentStream instanceof Buffer)
			cmd.stdin.end(self.currentStream);
		else
			self.currentStream.pipe(cmd.stdin);
	}

	writer && writer(cmd.stdin);
	var middleware = middlewares[type];
	return middleware ? cmd.stdout.pipe(middleware()) : cmd.stdout;
};

ImageProto.stream = function(type, writer) {

	var self = this;

	!self.builder.length && self.minify();

	if (!type || !SUPPORTEDIMAGES[type])
		type = self.outputType;

	F.stats.performance.open++;
	var cmd = spawn(CMD_CONVERT[self.cmdarg], self.arg(self.filename ? wrap(self.filename) : '-', (type ? type + ':' : '') + '-'), SPAWN_OPT);
	if (self.currentStream) {
		if (self.currentStream instanceof Buffer)
			cmd.stdin.end(self.currentStream);
		else
			self.currentStream.pipe(cmd.stdin);
	}

	writer && writer(cmd.stdin);
	var middleware = middlewares[type];
	return middleware ? cmd.stdout.pipe(middleware()) : cmd.stdout;
};

--- a/image.js
+++ b/image.js
@@ -4,7 +4,7 @@

 	!self.builder.length && self.minify();

-	if (!type)
+	if (!type || !SUPPORTEDIMAGES[type])
 		type = self.outputType;

 	F.stats.performance.open++;

--- /dev/null
+++ b/image.js
@@ -0,0 +1 @@
+const SUPPORTEDIMAGES = { jpg: 1, png: 1, gif: 1, apng: 1, jpeg: 1, heif: 1, heic: 1, webp: 1, ico: 1 };

#!/bin/bash
# From ghcr.io/anonymous2578-data/cve-2020-28494:latest
# bash /workspace/fix-run.sh
set -e

cd /workspace/framework
git apply --whitespace=nowarn /workspace/test.patch /workspace/fix.patch
jest ./poc


access-policy through 3.1.0 is vulnerable to Arbitrary Code Execution. User input provided to the `template` function is executed by the `eval` function resulting in code execution.

function template(literal, data) {
  var tmpl = literal.replace(/(\$\{)/gm, '$1data.');

  return eval('`' + tmpl + '`');
}

function template(literal, data) {
  var tmpl = literal.replace(/(\$\{)/gm, '$1data.');
  let blacklist = ['&', ';', '|', '-', '$', '`', '||'];

  if (blacklist.some(v => tmpl.includes(v))) {
    for(var i=0; i<tmpl.length; i++){
      if(blacklist.includes(tmpl[i])){
        tmpl = tmpl.replace(tmpl[i], "");
      }
    }
  }
  return eval('`' + tmpl + '`');
}

function encodeStatements(statements, data) {
  if (!statements) {
    throw new SyntaxError('Must include statements to encode');
  }

  if (!data) {
    return statements;
  }

  var output = template(JSON.stringify(statements), data);
  output = JSON.parse(output);

  Object.defineProperty(output, 'encoded', {
    __proto__: null,
    value: true
  });

  return output;
}

--- a/lib/encode.js
+++ b/lib/encode.js
@@ -1,5 +1,33 @@
 function template(literal, data) {
   var tmpl = literal.replace(/(\$\{)/gm, '$1data.');
+  let blacklist = ['&', ';', '|', '-', '$', '`', '||'];

+  if (blacklist.some(v => tmpl.includes(v))) {
+    for(var i=0; i<tmpl.length; i++){
+      if(blacklist.includes(tmpl[i])){
+        tmpl = tmpl.replace(tmpl[i], "");
+      }
+    }
+  }
   return eval('`' + tmpl + '`');
 }
+
+function encodeStatements(statements, data) {
+  if (!statements) {
+    throw new SyntaxError('Must include statements to encode');
+  }
+
+  if (!data) {
+    return statements;
+  }
+
+  var output = template(JSON.stringify(statements), data);
+  output = JSON.parse(output);
+
+  Object.defineProperty(output, 'encoded', {
+    __proto__: null,
+    value: true
+  });
+
+  return output;
+}

#!/bin/bash
# From ghcr.io/anonymous2578-data/cve-2020-7674:latest
# bash /workspace/fix-run.sh
set -e

cd /workspace/access-policy
git apply --whitespace=nowarn /workspace/test.patch /workspace/fix.patch
jest ./poc

#!/bin/bash
# From ghcr.io/anonymous2578-data/cve-2020-7674:latest
# bash /workspace/unit_test.sh
set -e

cd /workspace/access-policy
git apply --whitespace=nowarn /workspace/fix.patch
npx mocha --grep "should not allow path '/user/12345/bill'|should throw a SyntaxError|should return the exact policy when no data is provided|should return an object|should throw a syntax error with less than 2 parameters|should thorw a syntax error when missing \`action\`|should not allow path '/user/123456'|should thorw a syntax error when missing \`resource\`|should always be false when length 0|should throw a type error"