mirror of
https://github.com/mealie-recipes/mealie.git
synced 2025-08-05 05:25:26 +02:00
security: multiple reported CVE fixes (#1515)
* update out of date license * update typing / refactor * fix arbitrarty path injection * use markdown sanatizer to prevent XSS CWE-79 * fix CWE-918 SSRF by validating url and mime type * add security docs * update recipe-scrapers * resolve DOS from arbitrary url * update changelog * bump version * add ref to #1506 * add #1511 to changelog * use requests decoder * actually fix encoding issue
This commit is contained in:
parent
483f789b8e
commit
13850cda1f
23 changed files with 401 additions and 118 deletions
|
@ -11,7 +11,7 @@
|
|||
<v-list-item dense @click="toggleChecked(index)">
|
||||
<v-checkbox hide-details :value="checked[index]" class="pt-0 my-auto py-auto" color="secondary" />
|
||||
<v-list-item-content :key="ingredient.quantity">
|
||||
<VueMarkdown class="ma-0 pa-0 text-subtitle-1 dense-markdown" :source="ingredientDisplay[index]" />
|
||||
<SafeMarkdown class="ma-0 pa-0 text-subtitle-1 dense-markdown" :source="ingredientDisplay[index]" />
|
||||
</v-list-item-content>
|
||||
</v-list-item>
|
||||
</div>
|
||||
|
@ -22,14 +22,11 @@
|
|||
<script lang="ts">
|
||||
import { computed, defineComponent, reactive, toRefs } from "@nuxtjs/composition-api";
|
||||
// @ts-ignore vue-markdown has no types
|
||||
import VueMarkdown from "@adapttive/vue-markdown";
|
||||
import { parseIngredientText } from "~/composables/recipes";
|
||||
import { RecipeIngredient } from "~/types/api-types/recipe";
|
||||
|
||||
export default defineComponent({
|
||||
components: {
|
||||
VueMarkdown,
|
||||
},
|
||||
components: {},
|
||||
props: {
|
||||
value: {
|
||||
type: Array as () => RecipeIngredient[],
|
||||
|
|
|
@ -197,7 +197,7 @@
|
|||
<v-expand-transition>
|
||||
<div v-show="!isChecked(index) && !edit" class="m-0 p-0">
|
||||
<v-card-text class="markdown">
|
||||
<VueMarkdown class="markdown" :source="step.text"> </VueMarkdown>
|
||||
<SafeMarkdown class="markdown" :source="step.text" />
|
||||
<div v-if="cookMode && step.ingredientReferences && step.ingredientReferences.length > 0">
|
||||
<v-divider class="mb-2"></v-divider>
|
||||
<div
|
||||
|
@ -219,8 +219,6 @@
|
|||
|
||||
<script lang="ts">
|
||||
import draggable from "vuedraggable";
|
||||
// @ts-ignore vue-markdown has no types
|
||||
import VueMarkdown from "@adapttive/vue-markdown";
|
||||
import {
|
||||
ref,
|
||||
toRefs,
|
||||
|
@ -245,7 +243,6 @@ interface MergerHistory {
|
|||
|
||||
export default defineComponent({
|
||||
components: {
|
||||
VueMarkdown,
|
||||
draggable,
|
||||
},
|
||||
props: {
|
||||
|
|
|
@ -18,7 +18,7 @@
|
|||
{{ note.title }}
|
||||
</v-card-title>
|
||||
<v-card-text>
|
||||
<VueMarkdown :source="note.text"> </VueMarkdown>
|
||||
<SafeMarkdown :source="note.text" />
|
||||
</v-card-text>
|
||||
</div>
|
||||
</div>
|
||||
|
@ -30,15 +30,10 @@
|
|||
</template>
|
||||
|
||||
<script lang="ts">
|
||||
// @ts-ignore vue-markdown has no types
|
||||
import VueMarkdown from "@adapttive/vue-markdown";
|
||||
import { defineComponent } from "@nuxtjs/composition-api";
|
||||
import { RecipeNote } from "~/types/api-types/recipe";
|
||||
|
||||
export default defineComponent({
|
||||
components: {
|
||||
VueMarkdown,
|
||||
},
|
||||
props: {
|
||||
value: {
|
||||
type: Array as () => RecipeNote[],
|
||||
|
|
|
@ -11,7 +11,7 @@
|
|||
</section>
|
||||
|
||||
<v-card-text class="px-0">
|
||||
<VueMarkdown :source="recipe.description" />
|
||||
<SafeMarkdown :source="recipe.description" />
|
||||
</v-card-text>
|
||||
|
||||
<!-- Ingredients -->
|
||||
|
@ -47,7 +47,7 @@
|
|||
{{ step.title }}
|
||||
</h4>
|
||||
<h5>{{ $t("recipe.step-index", { step: stepIndex + instructionSection.stepOffset + 1 }) }}</h5>
|
||||
<VueMarkdown :source="step.text" class="recipe-step-body" />
|
||||
<SafeMarkdown :source="step.text" class="recipe-step-body" />
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
@ -60,7 +60,7 @@
|
|||
<div v-for="(note, index) in recipe.notes" :key="index + 'note'">
|
||||
<div class="print-section">
|
||||
<h4>{{ note.title }}</h4>
|
||||
<VueMarkdown :source="note.text" class="note-body" />
|
||||
<SafeMarkdown :source="note.text" class="note-body" />
|
||||
</div>
|
||||
</div>
|
||||
</section>
|
||||
|
@ -69,8 +69,6 @@
|
|||
|
||||
<script lang="ts">
|
||||
import { defineComponent, computed } from "@nuxtjs/composition-api";
|
||||
// @ts-ignore vue-markdown has no types
|
||||
import VueMarkdown from "@adapttive/vue-markdown";
|
||||
import RecipeTimeCard from "~/components/Domain/Recipe/RecipeTimeCard.vue";
|
||||
import { Recipe, RecipeIngredient, RecipeStep } from "~/types/api-types/recipe";
|
||||
import { parseIngredientText } from "~/composables/recipes";
|
||||
|
@ -89,7 +87,6 @@ type InstructionSection = {
|
|||
export default defineComponent({
|
||||
components: {
|
||||
RecipeTimeCard,
|
||||
VueMarkdown,
|
||||
},
|
||||
props: {
|
||||
recipe: {
|
||||
|
|
|
@ -22,21 +22,15 @@
|
|||
dense
|
||||
rows="4"
|
||||
/>
|
||||
<VueMarkdown v-else :source="value" />
|
||||
<SafeMarkdown v-else :source="value" />
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script lang="ts">
|
||||
// @ts-ignore vue-markdown has no types
|
||||
import VueMarkdown from "@adapttive/vue-markdown";
|
||||
|
||||
import { defineComponent, computed, ref } from "@nuxtjs/composition-api";
|
||||
|
||||
export default defineComponent({
|
||||
name: "MarkdownEditor",
|
||||
components: {
|
||||
VueMarkdown,
|
||||
},
|
||||
props: {
|
||||
value: {
|
||||
type: String,
|
||||
|
|
42
frontend/components/global/SafeMarkdown.vue
Normal file
42
frontend/components/global/SafeMarkdown.vue
Normal file
|
@ -0,0 +1,42 @@
|
|||
<template>
|
||||
<VueMarkdown :source="sanitizeMarkdown(source)"></VueMarkdown>
|
||||
</template>
|
||||
|
||||
<script lang="ts">
|
||||
// @ts-ignore vue-markdown has no types
|
||||
import VueMarkdown from "@adapttive/vue-markdown";
|
||||
import { defineComponent } from "@nuxtjs/composition-api";
|
||||
import DOMPurify from "isomorphic-dompurify";
|
||||
|
||||
export default defineComponent({
|
||||
components: {
|
||||
VueMarkdown,
|
||||
},
|
||||
props: {
|
||||
source: {
|
||||
type: String,
|
||||
default: "",
|
||||
},
|
||||
},
|
||||
setup() {
|
||||
function sanitizeMarkdown(rawHtml: string | null | undefined): string {
|
||||
if (!rawHtml) {
|
||||
return "";
|
||||
}
|
||||
|
||||
const sanitized = DOMPurify.sanitize(rawHtml, {
|
||||
USE_PROFILES: { html: true },
|
||||
// TODO: some more thought could be put into what is allowed and what isn't
|
||||
ALLOWED_TAGS: ["img", "div", "p"],
|
||||
ADD_ATTR: ["src", "alt", "height", "width", "class"],
|
||||
});
|
||||
|
||||
return sanitized;
|
||||
}
|
||||
|
||||
return {
|
||||
sanitizeMarkdown,
|
||||
};
|
||||
},
|
||||
});
|
||||
</script>
|
|
@ -17,7 +17,7 @@
|
|||
<RecipeRating :key="recipe.slug" :value="recipe.rating" :name="recipe.name" :slug="recipe.slug" />
|
||||
</v-card-title>
|
||||
<v-divider class="my-2"></v-divider>
|
||||
<VueMarkdown :source="recipe.description"> </VueMarkdown>
|
||||
<SafeMarkdown :source="recipe.description" />
|
||||
<v-divider></v-divider>
|
||||
<div class="d-flex justify-center mt-5">
|
||||
<RecipeTimeCard
|
||||
|
@ -81,7 +81,7 @@
|
|||
<v-card-title class="px-0 py-2 ma-0 headline">
|
||||
{{ recipe.name }}
|
||||
</v-card-title>
|
||||
<VueMarkdown :source="recipe.description"> </VueMarkdown>
|
||||
<SafeMarkdown :source="recipe.description" />
|
||||
|
||||
<div class="pb-2 d-flex justify-center flex-wrap">
|
||||
<RecipeTimeCard
|
||||
|
@ -465,8 +465,6 @@ import {
|
|||
useRouter,
|
||||
onMounted,
|
||||
} from "@nuxtjs/composition-api";
|
||||
// @ts-ignore vue-markdown has no types
|
||||
import VueMarkdown from "@adapttive/vue-markdown";
|
||||
import draggable from "vuedraggable";
|
||||
import { invoke, until, useWakeLock } from "@vueuse/core";
|
||||
import { onUnmounted } from "vue-demi";
|
||||
|
@ -494,7 +492,6 @@ import { Recipe } from "~/types/api-types/recipe";
|
|||
import { uuid4, deepCopy } from "~/composables/use-utils";
|
||||
import { useRouteQuery } from "~/composables/use-router";
|
||||
import { useToolStore } from "~/composables/store";
|
||||
|
||||
export default defineComponent({
|
||||
components: {
|
||||
draggable,
|
||||
|
@ -520,7 +517,6 @@ export default defineComponent({
|
|||
RecipeTimeCard,
|
||||
RecipeTools,
|
||||
RecipeScaleEditButton,
|
||||
VueMarkdown,
|
||||
},
|
||||
async beforeRouteLeave(_to, _from, next) {
|
||||
const isSame = JSON.stringify(this.recipe) === JSON.stringify(this.originalRecipe);
|
||||
|
|
|
@ -17,7 +17,7 @@
|
|||
<RecipeRating :key="recipe.slug" :value="recipe.rating" :name="recipe.name" :slug="recipe.slug" />
|
||||
</v-card-title>
|
||||
<v-divider class="my-2"></v-divider>
|
||||
<VueMarkdown :source="recipe.description"> </VueMarkdown>
|
||||
<SafeMarkdown :source="recipe.description"> </SafeMarkdown>
|
||||
<v-divider></v-divider>
|
||||
<div class="d-flex justify-center mt-5">
|
||||
<RecipeTimeCard
|
||||
|
@ -61,7 +61,7 @@
|
|||
<v-card-title class="pa-0 ma-0 headline">
|
||||
{{ recipe.name }}
|
||||
</v-card-title>
|
||||
<VueMarkdown :source="recipe.description"> </VueMarkdown>
|
||||
<SafeMarkdown :source="recipe.description"> </SafeMarkdown>
|
||||
</template>
|
||||
|
||||
<template v-else-if="form">
|
||||
|
@ -273,8 +273,6 @@ import {
|
|||
useMeta,
|
||||
useRoute,
|
||||
} from "@nuxtjs/composition-api";
|
||||
// @ts-ignore vue-markdown has no types
|
||||
import VueMarkdown from "@adapttive/vue-markdown";
|
||||
// import { useRecipeMeta } from "~/composables/recipes";
|
||||
import { useStaticRoutes, useUserApi } from "~/composables/api";
|
||||
import RecipeChips from "~/components/Domain/Recipe/RecipeChips.vue";
|
||||
|
@ -296,7 +294,6 @@ export default defineComponent({
|
|||
RecipePrintView,
|
||||
RecipeRating,
|
||||
RecipeTimeCard,
|
||||
VueMarkdown,
|
||||
},
|
||||
layout: "basic",
|
||||
setup() {
|
||||
|
|
2
frontend/types/components.d.ts
vendored
2
frontend/types/components.d.ts
vendored
|
@ -21,6 +21,7 @@ import ToggleState from "@/components/global/ToggleState.vue";
|
|||
import ContextMenu from "@/components/global/ContextMenu.vue";
|
||||
import AppButtonCopy from "@/components/global/AppButtonCopy.vue";
|
||||
import CrudTable from "@/components/global/CrudTable.vue";
|
||||
import SafeMarkdown from "@/components/global/SafeMarkdown.vue";
|
||||
import InputColor from "@/components/global/InputColor.vue";
|
||||
import BaseDivider from "@/components/global/BaseDivider.vue";
|
||||
import AutoForm from "@/components/global/AutoForm.vue";
|
||||
|
@ -59,6 +60,7 @@ declare module "vue" {
|
|||
ContextMenu: typeof ContextMenu;
|
||||
AppButtonCopy: typeof AppButtonCopy;
|
||||
CrudTable: typeof CrudTable;
|
||||
SafeMarkdown: typeof SafeMarkdown;
|
||||
InputColor: typeof InputColor;
|
||||
BaseDivider: typeof BaseDivider;
|
||||
AutoForm: typeof AutoForm;
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue