Compare commits

..

10 commits

Author SHA1 Message Date
zenja
dab22cd631
locales/de-DE.yml aktualisiert
Some checks are pending
/ test-build (push) Waiting to run
fixed name issues
2024-11-27 23:11:16 +01:00
Laura Hausmann
4e61f25d41
[backend] Bump msgpackr version
This fixes compatibility with NodeJS v23+
2024-11-25 22:47:38 +01:00
mia
bd1bb68da3
[backend] Bump re2
Fixes some build issues
2024-11-24 10:07:52 -08:00
Laura Hausmann
617f27d637
Release: v2023.12.11 2024-11-20 23:56:38 +01:00
Kopper
a5f4279d32
[backend] Check target IP before sending HTTP request
Backported upstream commit "fix(backend): check target IP before sending HTTP request"

Co-authored-by: rectcoordsystem <heohyun73@gmail.com>
Co-authored-by: anatawa12 <anatawa12@icloud.com>
2024-11-20 23:56:37 +01:00
Laura Hausmann
065590279e
[backend] Strengthen checks against local object resolution
This commit addresses disclosed primitives 26-29 & 31-33
2024-11-20 23:56:20 +01:00
Laura Hausmann
ca331d2406
[backend] Create a new resolver in parseAudience if none is passed to the function
This commit addresses disclosed primitive 23
2024-11-20 23:56:16 +01:00
Laura Hausmann
dc3c2d1ad4
[backend] Enforce blocks in NoteRepository.isVisibleForMe
This commit addresses disclosed primitive 20
2024-11-20 23:56:12 +01:00
Laura Hausmann
aa73a8905d
[backend] Require admin scope for AP get endpoint
This commit addresses disclosed primitive 18
2024-11-20 23:56:07 +01:00
Laura Hausmann
7542310e3e
[backend] Improve validation of AP activities & objects
This commit addresses disclosed primitives 4-5, 7-9, 12-17 & 21-22 (CVE-2024-51403, CVE-2024-51404, CVE-2024-51405)
2024-11-20 23:56:02 +01:00
37 changed files with 327 additions and 194 deletions

55
.pnp.cjs generated
View file

@ -7295,7 +7295,7 @@ const RAW_RUNTIME_STATE =
["mfm-js", "npm:0.23.3"],\ ["mfm-js", "npm:0.23.3"],\
["mime-types", "npm:2.1.35"],\ ["mime-types", "npm:2.1.35"],\
["mocha", "npm:10.2.0"],\ ["mocha", "npm:10.2.0"],\
["msgpackr", "npm:1.9.5"],\ ["msgpackr", "npm:1.11.2"],\
["multer", "npm:1.4.4-lts.1"],\ ["multer", "npm:1.4.4-lts.1"],\
["nested-property", "npm:4.0.0"],\ ["nested-property", "npm:4.0.0"],\
["node-fetch", "npm:3.3.2"],\ ["node-fetch", "npm:3.3.2"],\
@ -7317,7 +7317,7 @@ const RAW_RUNTIME_STATE =
["qs", "npm:6.11.2"],\ ["qs", "npm:6.11.2"],\
["random-seed", "npm:0.3.0"],\ ["random-seed", "npm:0.3.0"],\
["ratelimiter", "npm:3.4.1"],\ ["ratelimiter", "npm:3.4.1"],\
["re2", "npm:1.20.11"],\ ["re2", "npm:1.21.4"],\
["redis-lock", "npm:0.1.4"],\ ["redis-lock", "npm:0.1.4"],\
["redis-semaphore", "virtual:aa59773ac87791c4813d53447077fcf8a847d6de5a301d34dc31286584b1dbb26d30d3adb5b4c41c1e8aea04371e926fda05c09c6253647c432e11d872a304ba#npm:5.3.1"],\ ["redis-semaphore", "virtual:aa59773ac87791c4813d53447077fcf8a847d6de5a301d34dc31286584b1dbb26d30d3adb5b4c41c1e8aea04371e926fda05c09c6253647c432e11d872a304ba#npm:5.3.1"],\
["reflect-metadata", "npm:0.1.13"],\ ["reflect-metadata", "npm:0.1.13"],\
@ -18152,10 +18152,10 @@ const RAW_RUNTIME_STATE =
],\ ],\
"linkType": "HARD"\ "linkType": "HARD"\
}],\ }],\
["npm:1.9.5", {\ ["npm:1.11.2", {\
"packageLocation": "./.yarn/cache/msgpackr-npm-1.9.5-69f0e8f5b8-d95fbee39b.zip/node_modules/msgpackr/",\ "packageLocation": "./.yarn/cache/msgpackr-npm-1.11.2-a21c5db6f8-7602f1e91e.zip/node_modules/msgpackr/",\
"packageDependencies": [\ "packageDependencies": [\
["msgpackr", "npm:1.9.5"],\ ["msgpackr", "npm:1.11.2"],\
["msgpackr-extract", "npm:3.0.2"]\ ["msgpackr-extract", "npm:3.0.2"]\
],\ ],\
"linkType": "HARD"\ "linkType": "HARD"\
@ -18225,10 +18225,10 @@ const RAW_RUNTIME_STATE =
}]\ }]\
]],\ ]],\
["nan", [\ ["nan", [\
["npm:2.19.0", {\ ["npm:2.22.0", {\
"packageLocation": "./.yarn/unplugged/nan-npm-2.19.0-2f5da4a528/node_modules/nan/",\ "packageLocation": "./.yarn/unplugged/nan-npm-2.22.0-3750ad85d9/node_modules/nan/",\
"packageDependencies": [\ "packageDependencies": [\
["nan", "npm:2.19.0"],\ ["nan", "npm:2.22.0"],\
["node-gyp", "npm:9.4.0"]\ ["node-gyp", "npm:9.4.0"]\
],\ ],\
"linkType": "HARD"\ "linkType": "HARD"\
@ -18406,19 +18406,19 @@ const RAW_RUNTIME_STATE =
}]\ }]\
]],\ ]],\
["node-gyp", [\ ["node-gyp", [\
["npm:10.0.1", {\ ["npm:10.2.0", {\
"packageLocation": "./.yarn/unplugged/node-gyp-npm-10.0.1-48708ce70b/node_modules/node-gyp/",\ "packageLocation": "./.yarn/unplugged/node-gyp-npm-10.2.0-cad1109948/node_modules/node-gyp/",\
"packageDependencies": [\ "packageDependencies": [\
["node-gyp", "npm:10.0.1"],\ ["node-gyp", "npm:10.2.0"],\
["env-paths", "npm:2.2.1"],\ ["env-paths", "npm:2.2.1"],\
["exponential-backoff", "npm:3.1.1"],\ ["exponential-backoff", "npm:3.1.1"],\
["glob", "npm:10.3.10"],\ ["glob", "npm:10.3.10"],\
["graceful-fs", "npm:4.2.11"],\ ["graceful-fs", "npm:4.2.11"],\
["make-fetch-happen", "npm:13.0.0"],\ ["make-fetch-happen", "npm:13.0.0"],\
["nopt", "npm:7.2.0"],\ ["nopt", "npm:7.2.0"],\
["proc-log", "npm:3.0.0"],\ ["proc-log", "npm:4.2.0"],\
["semver", "npm:7.5.4"],\ ["semver", "npm:7.5.4"],\
["tar", "npm:6.1.15"],\ ["tar", "npm:6.2.1"],\
["which", "npm:4.0.0"]\ ["which", "npm:4.0.0"]\
],\ ],\
"linkType": "HARD"\ "linkType": "HARD"\
@ -20445,10 +20445,10 @@ const RAW_RUNTIME_STATE =
}]\ }]\
]],\ ]],\
["proc-log", [\ ["proc-log", [\
["npm:3.0.0", {\ ["npm:4.2.0", {\
"packageLocation": "./.yarn/cache/proc-log-npm-3.0.0-a8c21c2f0f-02b64e1b39.zip/node_modules/proc-log/",\ "packageLocation": "./.yarn/cache/proc-log-npm-4.2.0-4d65296a9d-4e1394491b.zip/node_modules/proc-log/",\
"packageDependencies": [\ "packageDependencies": [\
["proc-log", "npm:3.0.0"]\ ["proc-log", "npm:4.2.0"]\
],\ ],\
"linkType": "HARD"\ "linkType": "HARD"\
}]\ }]\
@ -20975,13 +20975,13 @@ const RAW_RUNTIME_STATE =
}]\ }]\
]],\ ]],\
["re2", [\ ["re2", [\
["npm:1.20.11", {\ ["npm:1.21.4", {\
"packageLocation": "./.yarn/unplugged/re2-npm-1.20.11-ab65de125e/node_modules/re2/",\ "packageLocation": "./.yarn/unplugged/re2-npm-1.21.4-315af4327e/node_modules/re2/",\
"packageDependencies": [\ "packageDependencies": [\
["re2", "npm:1.20.11"],\ ["re2", "npm:1.21.4"],\
["install-artifact-from-github", "npm:1.3.5"],\ ["install-artifact-from-github", "npm:1.3.5"],\
["nan", "npm:2.19.0"],\ ["nan", "npm:2.22.0"],\
["node-gyp", "npm:10.0.1"]\ ["node-gyp", "npm:10.2.0"]\
],\ ],\
"linkType": "HARD"\ "linkType": "HARD"\
}]\ }]\
@ -23165,6 +23165,19 @@ const RAW_RUNTIME_STATE =
["yallist", "npm:4.0.0"]\ ["yallist", "npm:4.0.0"]\
],\ ],\
"linkType": "HARD"\ "linkType": "HARD"\
}],\
["npm:6.2.1", {\
"packageLocation": "./.yarn/cache/tar-npm-6.2.1-237800bb20-bfbfbb2861.zip/node_modules/tar/",\
"packageDependencies": [\
["tar", "npm:6.2.1"],\
["chownr", "npm:2.0.0"],\
["fs-minipass", "npm:2.1.0"],\
["minipass", "npm:5.0.0"],\
["minizlib", "npm:2.1.2"],\
["mkdirp", "npm:1.0.4"],\
["yallist", "npm:4.0.0"]\
],\
"linkType": "HARD"\
}]\ }]\
]],\ ]],\
["tar-stream", [\ ["tar-stream", [\

BIN
.yarn/cache/msgpackr-npm-1.11.2-a21c5db6f8-7602f1e91e.zip (Stored with Git LFS) vendored Normal file

Binary file not shown.

Binary file not shown.

BIN
.yarn/cache/nan-npm-2.19.0-2f5da4a528-b97f680753.zip (Stored with Git LFS) vendored

Binary file not shown.

BIN
.yarn/cache/nan-npm-2.22.0-3750ad85d9-ab165ba910.zip (Stored with Git LFS) vendored Normal file

Binary file not shown.

Binary file not shown.

BIN
.yarn/cache/node-gyp-npm-10.2.0-cad1109948-41773093b1.zip (Stored with Git LFS) vendored Normal file

Binary file not shown.

Binary file not shown.

BIN
.yarn/cache/proc-log-npm-4.2.0-4d65296a9d-4e1394491b.zip (Stored with Git LFS) vendored Normal file

Binary file not shown.

BIN
.yarn/cache/re2-npm-1.20.11-ab65de125e-a8665c861c.zip (Stored with Git LFS) vendored

Binary file not shown.

BIN
.yarn/cache/re2-npm-1.21.4-315af4327e-926871cc84.zip (Stored with Git LFS) vendored Normal file

Binary file not shown.

BIN
.yarn/cache/tar-npm-6.2.1-237800bb20-bfbfbb2861.zip (Stored with Git LFS) vendored Normal file

Binary file not shown.

View file

@ -1,3 +1,23 @@
## v2023.12.11
This release contains several critical security patches, as well as minor fixes and improvements. Upgrading is strongly recommended for all server operators.
### Highlights
- Several DoS, impersonation, data leakage & click jacking vulnerabilities have been patched
### Backend
- Various issues related to AP object validation have been resolved
- The ap/get API endpoint is now only available to administrators
- Blocks are now enforced in NoteRepository.isVisibleForMe
- Audience parsing no longer bypasses the AP recursion limit
- Edits of local-only notes are no longer federated out
- AP object URIs now get canonicalized before comparing them for consistency
- SSRF prevention now applies to all code paths
### Attribution
This release was made possible by project contributors: Kopper & Laura Hausmann
Furthermore, I want to give special thanks to Hazel Koehler for the vulnerability disclosure.
## v2023.12.10 ## v2023.12.10
This release contains a critical security patch, as well as minor fixes and improvements. Upgrading is strongly recommended for all server operators. This release contains a critical security patch, as well as minor fixes and improvements. Upgrading is strongly recommended for all server operators.

View file

@ -1086,7 +1086,7 @@ _registry:
domain: "Domain" domain: "Domain"
createKey: "Schlüssel erstellen" createKey: "Schlüssel erstellen"
_aboutIceshrimp: _aboutIceshrimp:
about: "Iceshrimp ist ein Fork von Iceshrimp, der seit 2022 von ThatOneCalculator about: "Iceshrimp ist ein Fork von Firefish, der seit 2022 von zotan
entwickelt wird." entwickelt wird."
contributors: "Hauptmitwirkende" contributors: "Hauptmitwirkende"
allContributors: "Alle Mitwirkenden" allContributors: "Alle Mitwirkenden"

View file

@ -1,6 +1,6 @@
{ {
"name": "iceshrimp", "name": "iceshrimp",
"version": "2023.12.10", "version": "2023.12.11",
"repository": { "repository": {
"type": "git", "type": "git",
"url": "https://iceshrimp.dev/iceshrimp/iceshrimp.git" "url": "https://iceshrimp.dev/iceshrimp/iceshrimp.git"

View file

@ -90,7 +90,7 @@
"koa-views": "7.0.2", "koa-views": "7.0.2",
"mfm-js": "0.23.3", "mfm-js": "0.23.3",
"mime-types": "2.1.35", "mime-types": "2.1.35",
"msgpackr": "1.9.5", "msgpackr": "1.11.2",
"multer": "1.4.4-lts.1", "multer": "1.4.4-lts.1",
"nested-property": "4.0.0", "nested-property": "4.0.0",
"node-fetch": "3.3.2", "node-fetch": "3.3.2",
@ -111,7 +111,7 @@
"qs": "6.11.2", "qs": "6.11.2",
"random-seed": "0.3.0", "random-seed": "0.3.0",
"ratelimiter": "3.4.1", "ratelimiter": "3.4.1",
"re2": "^1.20.11", "re2": "^1.21.4",
"redis-lock": "0.1.4", "redis-lock": "0.1.4",
"redis-semaphore": "5.3.1", "redis-semaphore": "5.3.1",
"reflect-metadata": "0.1.13", "reflect-metadata": "0.1.13",

View file

@ -0,0 +1,60 @@
import * as http from "node:http";
import * as https from "node:https";
import net from "node:net";
import { HttpProxyAgent, HttpsProxyAgent } from "hpagent";
import config from "@/config/index.js";
import IPCIDR from "ip-cidr";
import PrivateIp from "private-ip";
declare module 'node:http' {
interface Agent {
createConnection(options: net.NetConnectOpts, callback?: (err: unknown, stream: net.Socket) => void): net.Socket;
}
}
function isPrivateIp(ip: string): boolean {
for (const net of config.allowedPrivateNetworks || []) {
const cidr = new IPCIDR(net);
if (cidr.contains(ip)) {
return false;
}
}
return PrivateIp(ip);
}
function checkConnection(socket: net.Socket) {
const address = socket.remoteAddress;
if (process.env.NODE_ENV === 'production') {
if (address && IPCIDR.isValidAddress(address) && isPrivateIp(address)) {
socket.destroy(new Error(`Blocked address: ${address}`));
}
}
}
export class CheckedHttpAgent extends http.Agent {
createConnection(options: net.NetConnectOpts, callback?: (err: unknown, stream: net.Socket) => void): net.Socket {
const socket = super.createConnection(options, callback).on('connect', () => { checkConnection(socket) });
return socket;
}
}
export class CheckedHttpsAgent extends https.Agent {
createConnection(options: net.NetConnectOpts, callback?: (err: unknown, stream: net.Socket) => void): net.Socket {
const socket = super.createConnection(options, callback).on('connect', () => { checkConnection(socket) });
return socket;
}
}
export class CheckedHttpProxyAgent extends HttpProxyAgent {
createConnection(options: net.NetConnectOpts, callback?: (err: unknown, stream: net.Socket) => void): net.Socket {
const socket = super.createConnection(options, callback).on('connect', () => { checkConnection(socket) });
return socket;
}
}
export class CheckedHttpsProxyAgent extends HttpsProxyAgent {
createConnection(options: net.NetConnectOpts, callback?: (err: unknown, stream: net.Socket) => void): net.Socket {
const socket = super.createConnection(options, callback).on('connect', () => { checkConnection(socket) });
return socket;
}
}

View file

@ -6,8 +6,6 @@ import { httpAgent, httpsAgent, StatusError } from "./fetch.js";
import config from "@/config/index.js"; import config from "@/config/index.js";
import chalk from "chalk"; import chalk from "chalk";
import Logger from "@/services/logger.js"; import Logger from "@/services/logger.js";
import IPCIDR from "ip-cidr";
import PrivateIp from "private-ip";
const pipeline = util.promisify(stream.pipeline); const pipeline = util.promisify(stream.pipeline);
@ -45,18 +43,6 @@ export async function downloadUrl(url: string, path: string): Promise<void> {
}, },
}) })
.on("response", (res: Got.Response) => { .on("response", (res: Got.Response) => {
if (
(process.env.NODE_ENV === "production" ||
process.env.NODE_ENV === "test") &&
!config.proxy &&
res.ip
) {
if (isPrivateIp(res.ip)) {
logger.warn(`Blocked address: ${res.ip}`);
req.destroy();
}
}
const contentLength = res.headers["content-length"]; const contentLength = res.headers["content-length"];
if (contentLength != null) { if (contentLength != null) {
const size = Number(contentLength); const size = Number(contentLength);
@ -92,13 +78,3 @@ export async function downloadUrl(url: string, path: string): Promise<void> {
logger.succ(`Download finished: ${chalk.cyan(url)}`); logger.succ(`Download finished: ${chalk.cyan(url)}`);
} }
function isPrivateIp(ip: string): boolean {
for (const net of config.allowedPrivateNetworks || []) {
const cidr = new IPCIDR(net);
if (cidr.contains(ip)) {
return false;
}
}
return PrivateIp(ip);
}

View file

@ -3,8 +3,9 @@ import * as https from "node:https";
import type { URL } from "node:url"; import type { URL } from "node:url";
import CacheableLookup from "cacheable-lookup"; import CacheableLookup from "cacheable-lookup";
import fetch from "node-fetch"; import fetch from "node-fetch";
import { HttpProxyAgent, HttpsProxyAgent } from "hpagent";
import config from "@/config/index.js"; import config from "@/config/index.js";
import net from "node:net";
import {CheckedHttpAgent, CheckedHttpProxyAgent, CheckedHttpsAgent, CheckedHttpsProxyAgent} from "@/misc/checked-fetch.js";
export async function getJson( export async function getJson(
url: string, url: string,
@ -132,7 +133,7 @@ const cache = new CacheableLookup({
/** /**
* Get http non-proxy agent * Get http non-proxy agent
*/ */
const _http = new http.Agent({ const _http = new CheckedHttpAgent({
keepAlive: true, keepAlive: true,
keepAliveMsecs: 30 * 1000, keepAliveMsecs: 30 * 1000,
lookup: cache.lookup, lookup: cache.lookup,
@ -141,7 +142,7 @@ const _http = new http.Agent({
/** /**
* Get https non-proxy agent * Get https non-proxy agent
*/ */
const _https = new https.Agent({ const _https = new CheckedHttpsAgent({
keepAlive: true, keepAlive: true,
keepAliveMsecs: 30 * 1000, keepAliveMsecs: 30 * 1000,
lookup: cache.lookup, lookup: cache.lookup,
@ -153,7 +154,7 @@ const maxSockets = Math.max(256, config.deliverJobConcurrency || 128);
* Get http proxy or non-proxy agent * Get http proxy or non-proxy agent
*/ */
export const httpAgent = config.proxy export const httpAgent = config.proxy
? new HttpProxyAgent({ ? new CheckedHttpProxyAgent({
keepAlive: true, keepAlive: true,
keepAliveMsecs: 30 * 1000, keepAliveMsecs: 30 * 1000,
maxSockets, maxSockets,
@ -167,7 +168,7 @@ export const httpAgent = config.proxy
* Get https proxy or non-proxy agent * Get https proxy or non-proxy agent
*/ */
export const httpsAgent = config.proxy export const httpsAgent = config.proxy
? new HttpsProxyAgent({ ? new CheckedHttpsProxyAgent({
keepAlive: true, keepAlive: true,
keepAliveMsecs: 30 * 1000, keepAliveMsecs: 30 * 1000,
maxSockets, maxSockets,

View file

@ -10,7 +10,7 @@ import {
Followings, Followings,
Polls, Polls,
Channels, Channels,
Notes, UserProfiles, Notes, UserProfiles, Blockings,
} from "../index.js"; } from "../index.js";
import type { Packed } from "@/misc/schema.js"; import type { Packed } from "@/misc/schema.js";
import { nyaize } from "@/misc/nyaize.js"; import { nyaize } from "@/misc/nyaize.js";
@ -113,6 +113,20 @@ async function populateIsRenoted(
export const NoteRepository = db.getRepository(Note).extend({ export const NoteRepository = db.getRepository(Note).extend({
async isVisibleForMe(note: Note, meId: User["id"] | null): Promise<boolean> { async isVisibleForMe(note: Note, meId: User["id"] | null): Promise<boolean> {
if (meId != null && meId !== note.userId) {
const blocked = await Blockings.count({
where: {
blockeeId: meId,
blockerId: note.userId
},
take: 1
});
if (blocked !== 0) {
return false;
}
}
// This code must always be synchronized with the checks in generateVisibilityQuery. // This code must always be synchronized with the checks in generateVisibilityQuery.
// visibility が specified かつ自分が指定されていなかったら非表示 // visibility が specified かつ自分が指定されていなかったら非表示
if (note.visibility === "specified") { if (note.visibility === "specified") {

View file

@ -178,12 +178,14 @@ async function process(job: Job<InboxJobData>): Promise<string> {
} }
// activity.idがあればホストが署名者のホストであることを確認する // activity.idがあればホストが署名者のホストであることを確認する
if (typeof activity.id === "string") { if (typeof activity.id !== "string") {
const signerHost = extractDbHost(authUser.user.uri!); return 'skip: activity.id is not a string';
const activityIdHost = extractDbHost(activity.id); }
if (signerHost !== activityIdHost) {
return `skip: signerHost(${signerHost}) !== activity.id host(${activityIdHost}`; const signerHost = extractDbHost(authUser.user.uri!);
} const activityIdHost = extractDbHost(activity.id);
if (signerHost !== activityIdHost) {
return `skip: signerHost(${signerHost}) !== activity.id host(${activityIdHost}`;
} }
// Update stats // Update stats

View file

@ -1,6 +1,6 @@
import type { ApObject } from "./type.js"; import type { ApObject } from "./type.js";
import { getApIds } from "./type.js"; import { getApIds } from "./type.js";
import type Resolver from "./resolver.js"; import Resolver from "./resolver.js";
import { resolvePerson } from "./models/person.js"; import { resolvePerson } from "./models/person.js";
import { unique, concat } from "@/prelude/array.js"; import { unique, concat } from "@/prelude/array.js";
import promiseLimit from "promise-limit"; import promiseLimit from "promise-limit";
@ -31,6 +31,7 @@ export async function parseAudience(
const others = unique(concat([toGroups.other, ccGroups.other])); const others = unique(concat([toGroups.other, ccGroups.other]));
resolver ??= new Resolver();
const limit = promiseLimit<CacheableUser | null>(2); const limit = promiseLimit<CacheableUser | null>(2);
const mentionedUsers = ( const mentionedUsers = (
await Promise.all( await Promise.all(

View file

@ -37,7 +37,7 @@ export async function checkFetch(req: IncomingMessage): Promise<number> {
let signature; let signature;
try { try {
signature = httpSignature.parseRequest(req, { headers: ["(request-target)", "host", "date"] }); signature = httpSignature.parseRequest(req, { headers: ["(request-target)", "host", "date"], authorizationHeaderName: 'signature' });
} catch (e) { } catch (e) {
return 401; return 401;
} }

View file

@ -19,6 +19,7 @@ import type { IObject } from "./type.js";
import { getApId } from "./type.js"; import { getApId } from "./type.js";
import { resolvePerson, updatePerson } from "./models/person.js"; import { resolvePerson, updatePerson } from "./models/person.js";
import {redisClient, subscriber} from "@/db/redis.js"; import {redisClient, subscriber} from "@/db/redis.js";
import { extractDbHost, toPuny } from "@/misc/convert-host.js";
const publicKeyCache = new Cache<UserPublickey | null>("publicKey", 60 * 30); const publicKeyCache = new Cache<UserPublickey | null>("publicKey", 60 * 30);
const publicKeyByUserIdCache = new Cache<UserPublickey | null>( const publicKeyByUserIdCache = new Cache<UserPublickey | null>(
@ -46,15 +47,15 @@ export type UriParseResult =
export function parseUri(value: string | IObject): UriParseResult { export function parseUri(value: string | IObject): UriParseResult {
const uri = getApId(value); const uri = getApId(value);
const parsed = new URL(uri);
// the host part of a URL is case insensitive, so use the 'i' flag. if (toPuny(parsed.host) === toPuny(config.host)) {
const localRegex = new RegExp( const localRegex = new RegExp(`^.*?/(\\w+)/(\\w+)(?:/(.+))?`);
`^${escapeRegexp(config.url)}/(\\w+)/(\\w+)(?:/(.+))?`, const matchLocal = uri.match(localRegex);
"i", if (matchLocal == null) {
); throw new Error(`Failed to parse local URI: ${uri}`);
const matchLocal = uri.match(localRegex); }
if (matchLocal) {
return { return {
local: true, local: true,
type: matchLocal[1], type: matchLocal[1],

View file

@ -29,6 +29,9 @@ export default async function (
return "skip: host in actor.uri !== note.id"; return "skip: host in actor.uri !== note.id";
} }
} }
else {
return "skip: note.id is not a string";
}
} }
const unlock = await getApLock(uri); const unlock = await getApLock(uri);

View file

@ -46,19 +46,8 @@ export async function performActivity(
activity: IObject, activity: IObject,
) { ) {
if (isCollectionOrOrderedCollection(activity)) { if (isCollectionOrOrderedCollection(activity)) {
const resolver = new Resolver(); apLogger.debug('Refusing to ingest collection as activity');
for (const item of toArray( return;
isCollection(activity) ? activity.items : activity.orderedItems,
)) {
const act = await resolver.resolve(item);
try {
await performOneActivity(actor, act);
} catch (err) {
if (err instanceof Error || typeof err === "string") {
apLogger.error(err);
}
}
}
} else { } else {
await performOneActivity(actor, activity); await performOneActivity(actor, activity);
} }

View file

@ -1,5 +1,5 @@
import type { CacheableRemoteUser } from "@/models/entities/user.js"; import type { CacheableRemoteUser } from "@/models/entities/user.js";
import type { IUpdate } from "../../type.js"; import { getApId, IUpdate } from "../../type.js";
import { getApType, isActor } from "../../type.js"; import { getApType, isActor } from "../../type.js";
import { apLogger } from "../../logger.js"; import { apLogger } from "../../logger.js";
import { updateNote } from "../../models/note.js"; import { updateNote } from "../../models/note.js";
@ -13,7 +13,7 @@ export default async (
actor: CacheableRemoteUser, actor: CacheableRemoteUser,
activity: IUpdate, activity: IUpdate,
): Promise<string> => { ): Promise<string> => {
if ("actor" in activity && actor.uri !== activity.actor) { if (actor.uri == null || actor.uri !== getApId(activity.actor)) {
return "skip: invalid actor"; return "skip: invalid actor";
} }
@ -27,6 +27,10 @@ export default async (
}); });
if (isActor(object)) { if (isActor(object)) {
if (actor.uri !== object.id) {
return "skip: actor id mismatch";
}
await updatePerson(actor.uri!, resolver, object); await updatePerson(actor.uri!, resolver, object);
return "ok: Person updated"; return "ok: Person updated";
} }
@ -39,7 +43,7 @@ export default async (
case "Document": case "Document":
case "Page": case "Page":
let failed = false; let failed = false;
await updateNote(object, resolver).catch((e: Error) => { await updateNote(object, actor, resolver).catch((e: Error) => {
failed = true; failed = true;
}); });
return failed ? "skip: Note update failed" : "ok: Note updated"; return failed ? "skip: Note update failed" : "ok: Note updated";

View file

@ -131,13 +131,20 @@ export async function createNote(
const note: IPost = object; const note: IPost = object;
if (note.id && !note.id.startsWith("https://")) { if (note.id == null) {
throw new Error('Note must have an id');
}
const idUrl = new URL(note.id);
if (idUrl.protocol != 'https:') {
throw new Error(`unexpected schema of note.id: ${note.id}`); throw new Error(`unexpected schema of note.id: ${note.id}`);
} }
const url = getOneApHrefNullable(note.url); let url = getOneApHrefNullable(note.url);
const urlUrl = url != null ? new URL(url) : null;
if (url && !url.startsWith("https://")) { if (urlUrl != null && urlUrl.protocol != 'https:') {
throw new Error(`unexpected schema of note url: ${url}`); throw new Error(`unexpected schema of note url: ${url}`);
} }
@ -169,6 +176,22 @@ export async function createNote(
limiter limiter
)) as CacheableRemoteUser; )) as CacheableRemoteUser;
if (actor.uri == null) {
logger.warn('Note actor uri is null, discarding');
return null;
}
const actorUri = new URL(actor.uri);
if (idUrl.host != actorUri.host) {
logger.warn("Note id host doesn't match actor host, discarding");
return null;
}
if (urlUrl != null && urlUrl.host != actorUri.host) {
logger.debug("Note url host doesn't match actor host, clearing variable");
url = undefined;
}
// Skip if author is suspended. // Skip if author is suspended.
if (actor.isSuspended) { if (actor.isSuspended) {
logger.debug( logger.debug(
@ -432,7 +455,7 @@ export async function resolveNote(
} }
//#endregion //#endregion
if (uri.startsWith(config.url)) { if (extractDbHost(uri) === toPuny(config.host)) {
throw new StatusError( throw new StatusError(
"cannot resolve local note", "cannot resolve local note",
400, 400,
@ -544,12 +567,12 @@ function notEmpty(partial: Partial<any>) {
return Object.keys(partial).length > 0; return Object.keys(partial).length > 0;
} }
export async function updateNote(value: string | IObject, resolver?: Resolver) { export async function updateNote(value: string | IObject, actor: CacheableRemoteUser, resolver?: Resolver) {
const uri = typeof value === "string" ? value : value.id; const uri = typeof value === "string" ? value : value.id;
if (!uri) throw new Error("Missing note uri"); if (!uri) throw new Error("Missing note uri");
// Skip if URI points to this server // Skip if URI points to this server
if (uri.startsWith(`${config.url}/`)) throw new Error("uri points local"); if (extractDbHost(uri) === toPuny(config.host)) throw new Error("uri points local");
// A new resolver is created if not specified // A new resolver is created if not specified
if (resolver == null) resolver = new Resolver(); if (resolver == null) resolver = new Resolver();
@ -557,16 +580,18 @@ export async function updateNote(value: string | IObject, resolver?: Resolver) {
// Resolve the updated Note object // Resolve the updated Note object
const post = (await resolver.resolve(value)) as IPost; const post = (await resolver.resolve(value)) as IPost;
const actor = (await resolvePerson( if (getOneApId(post.attributedTo) !== actor.uri || actor.uri == null) {
getOneApId(post.attributedTo), throw new Error('Refusing to ingest update for note with mismatching actor');
resolver, }
)) as CacheableRemoteUser;
// Already registered with this server? // Already registered with this server?
const note = await Notes.findOneBy({ uri }); const note = await Notes.findOneBy({ uri });
if (note == null) { if (note == null) {
return await createNote(post, resolver); return await createNote(post, resolver);
} }
if (note.userId !== actor.id) {
throw new Error('Refusing to ingest update for note of different user');
}
// Whether to tell clients the note has been updated and requires refresh. // Whether to tell clients the note has been updated and requires refresh.
let updating = false; let updating = false;
@ -699,6 +724,10 @@ export async function updateNote(value: string | IObject, resolver?: Resolver) {
if (poll) { if (poll) {
const dbPoll = await Polls.findOneBy({ noteId: note.id }); const dbPoll = await Polls.findOneBy({ noteId: note.id });
if (poll?.votes != null && poll.votes.find(p => !Number.isInteger(p) || p < 0) !== undefined) {
throw new Error('Refusing to ingest poll with non-integer or negative vote count');
}
if (dbPoll == null) { if (dbPoll == null) {
await Polls.insert({ await Polls.insert({
noteId: note.id, noteId: note.id,

View file

@ -21,7 +21,7 @@ import { genId } from "@/misc/gen-id.js";
import { instanceChart, usersChart } from "@/services/chart/index.js"; import { instanceChart, usersChart } from "@/services/chart/index.js";
import { UserPublickey } from "@/models/entities/user-publickey.js"; import { UserPublickey } from "@/models/entities/user-publickey.js";
import { isDuplicateKeyValueError } from "@/misc/is-duplicate-key-value-error.js"; import { isDuplicateKeyValueError } from "@/misc/is-duplicate-key-value-error.js";
import { toPuny } from "@/misc/convert-host.js"; import { extractDbHost, toPuny } from "@/misc/convert-host.js";
import { UserProfile } from "@/models/entities/user-profile.js"; import { UserProfile } from "@/models/entities/user-profile.js";
import { toArray } from "@/prelude/array.js"; import { toArray } from "@/prelude/array.js";
import { fetchInstanceMetadata } from "@/services/fetch-instance-metadata.js"; import { fetchInstanceMetadata } from "@/services/fetch-instance-metadata.js";
@ -69,7 +69,7 @@ const summaryLength = 2048;
* @param uri Fetch target URI * @param uri Fetch target URI
*/ */
function validateActor(x: IObject, uri: string): IActor { function validateActor(x: IObject, uri: string): IActor {
const expectHost = toPuny(new URL(uri).hostname); const expectHost = extractDbHost(uri);
if (x == null) { if (x == null) {
throw new Error("invalid Actor: object is null"); throw new Error("invalid Actor: object is null");
@ -83,10 +83,36 @@ function validateActor(x: IObject, uri: string): IActor {
throw new Error("invalid Actor: wrong id"); throw new Error("invalid Actor: wrong id");
} }
if (!(typeof x.inbox === "string" && x.inbox.length > 0)) { if (!(typeof x.inbox === "string" && x.inbox.length > 0 && extractDbHost(x.inbox) === expectHost)) {
throw new Error("invalid Actor: wrong inbox"); throw new Error("invalid Actor: wrong inbox");
} }
if (!(typeof x.outbox === "string" && x.outbox.length > 0 && extractDbHost(getApId(x.outbox)) === expectHost)) {
throw new Error("invalid Actor: wrong outbox");
}
const sharedInboxObject = x.sharedInbox ?? (x.endpoints ? x.endpoints.sharedInbox : undefined);
if (sharedInboxObject != null) {
const sharedInbox = getApId(sharedInboxObject);
if (!(typeof sharedInbox === "string" && sharedInbox.length > 0 && extractDbHost(sharedInbox) === expectHost)) {
throw new Error("invalid Actor: wrong shared inbox");
}
}
if (x.followers != null) {
x.followers = getApId(x.followers);
if (!(typeof x.followers === "string" && x.followers.length > 0 && extractDbHost(x.followers) === expectHost)) {
throw new Error("invalid Actor: wrong followers");
}
}
if (x.following != null) {
x.following = getApId(x.following);
if (!(typeof x.following === "string" && x.following.length > 0 && extractDbHost(x.following) === expectHost)) {
throw new Error("invalid Actor: wrong following");
}
}
if ( if (
!( !(
typeof x.preferredUsername === "string" && typeof x.preferredUsername === "string" &&
@ -114,7 +140,7 @@ function validateActor(x: IObject, uri: string): IActor {
x.summary = truncate(x.summary, summaryLength); x.summary = truncate(x.summary, summaryLength);
} }
const idHost = toPuny(new URL(x.id!).hostname); const idHost = toPuny(new URL(x.id!).host);
if (idHost !== expectHost) { if (idHost !== expectHost) {
throw new Error("invalid Actor: id has different host"); throw new Error("invalid Actor: id has different host");
} }
@ -124,7 +150,7 @@ function validateActor(x: IObject, uri: string): IActor {
throw new Error("invalid Actor: publicKey.id is not a string"); throw new Error("invalid Actor: publicKey.id is not a string");
} }
const publicKeyIdHost = toPuny(new URL(x.publicKey.id).hostname); const publicKeyIdHost = toPuny(new URL(x.publicKey.id).host);
if (publicKeyIdHost !== expectHost) { if (publicKeyIdHost !== expectHost) {
throw new Error("invalid Actor: publicKey.id has different host"); throw new Error("invalid Actor: publicKey.id has different host");
} }
@ -148,7 +174,7 @@ export async function fetchPerson(
if (cached) return cached; if (cached) return cached;
// Fetch from the database if the URI points to this server // Fetch from the database if the URI points to this server
if (uri.startsWith(`${config.url}/`)) { if (extractDbHost(uri) === toPuny(config.host)) {
const id = uri.split("/").pop(); const id = uri.split("/").pop();
const u = await Users.findOneBy({ id }); const u = await Users.findOneBy({ id });
if (u) await uriPersonCache.set(uri, u); if (u) await uriPersonCache.set(uri, u);
@ -178,7 +204,7 @@ export async function createPerson(
): Promise<User> { ): Promise<User> {
if (typeof uri !== "string") throw new Error("uri is not string"); if (typeof uri !== "string") throw new Error("uri is not string");
if (uri.startsWith(config.url)) { if (extractDbHost(uri) === toPuny(config.host)) {
throw new StatusError( throw new StatusError(
"cannot resolve local user", "cannot resolve local user",
400, 400,
@ -195,10 +221,10 @@ export async function createPerson(
person = validateActor(object, uri); person = validateActor(object, uri);
} }
catch (e: any) { catch (e: any) {
if (typeof object.publicKey?.owner !== 'string') // Work around GoToSocial issue #1186 (ref: https://github.com/superseriousbusiness/gotosocial/issues/1186)
if (typeof object.publicKey?.owner !== 'string' || object.inbox != null)
throw e; throw e;
// Work around GoToSocial issue #1186 (ref: https://github.com/superseriousbusiness/gotosocial/issues/1186)
logger.info(`Received stub actor, re-resolving with key owner uri: ${object.publicKey.owner}`); logger.info(`Received stub actor, re-resolving with key owner uri: ${object.publicKey.owner}`);
object = (await resolver.resolve(object.publicKey.owner)) as any; object = (await resolver.resolve(object.publicKey.owner)) as any;
person = validateActor(object, uri); person = validateActor(object, uri);
@ -261,12 +287,19 @@ export async function createPerson(
const bday = person["vcard:bday"]?.match(/^\d{4}-\d{2}-\d{2}/); const bday = person["vcard:bday"]?.match(/^\d{4}-\d{2}-\d{2}/);
const url = getOneApHrefNullable(person.url); let url = getOneApHrefNullable(person.url);
const urlUrl = url != null ? new URL(url) : null;
const uriUrl = new URL(uri);
if (url && !url.startsWith("https://")) { if (urlUrl != null && urlUrl.protocol != 'https:') {
throw new Error(`unexpected schema of person url: ${url}`); throw new Error(`unexpected schema of person url: ${url}`);
} }
if (urlUrl != null && urlUrl.host != uriUrl.host) {
logger.debug("Person url host doesn't match person uri host, clearing variable");
url = undefined;
}
let followersCount: number | undefined; let followersCount: number | undefined;
if (typeof person.followers === "string") { if (typeof person.followers === "string") {
@ -474,7 +507,7 @@ export async function updatePerson(
if (typeof uri !== "string") throw new Error("uri is not string"); if (typeof uri !== "string") throw new Error("uri is not string");
// Skip if the URI points to this server // Skip if the URI points to this server
if (uri.startsWith(`${config.url}/`)) { if (extractDbHost(uri) === toPuny(config.host)) {
return; return;
} }

View file

@ -5,6 +5,7 @@ import { getApId, isQuestion } from "../type.js";
import { apLogger } from "../logger.js"; import { apLogger } from "../logger.js";
import { Notes, Polls } from "@/models/index.js"; import { Notes, Polls } from "@/models/index.js";
import type { IPoll } from "@/models/entities/poll.js"; import type { IPoll } from "@/models/entities/poll.js";
import { extractDbHost, toPuny } from "@/misc/convert-host.js";
export async function extractPollFromQuestion( export async function extractPollFromQuestion(
source: string | IObject, source: string | IObject,
@ -55,7 +56,7 @@ export async function updateQuestion(
const uri = typeof value === "string" ? value : getApId(value); const uri = typeof value === "string" ? value : getApId(value);
// Skip if URI points to this server // Skip if URI points to this server
if (uri.startsWith(`${config.url}/`)) throw new Error("uri points local"); if (extractDbHost(uri) === toPuny(config.host)) throw new Error("uri points local");
//#region Already registered with this server? //#region Already registered with this server?
const note = await Notes.findOneBy({ uri }); const note = await Notes.findOneBy({ uri });

View file

@ -51,7 +51,7 @@ function inbox(ctx: Router.RouterContext) {
let signature; let signature;
try { try {
signature = httpSignature.parseRequest(ctx.req, { headers: ['(request-target)', 'digest', 'host', 'date'] }); signature = httpSignature.parseRequest(ctx.req, { headers: ['(request-target)', 'digest', 'host', 'date'], authorizationHeaderName: 'signature' });
} catch (e) { } catch (e) {
ctx.status = 401; ctx.status = 401;
return; return;

View file

@ -6,6 +6,7 @@ export const meta = {
tags: ["federation"], tags: ["federation"],
requireCredential: true, requireCredential: true,
requireAdmin: true,
limit: { limit: {
duration: HOUR, duration: HOUR,

View file

@ -51,40 +51,6 @@ export async function proxyMedia(ctx: Koa.Context) {
if (ctx.status == 429) return; if (ctx.status == 429) return;
const { hostname } = new URL(url);
let resolvedIps;
try {
resolvedIps = await promises.resolve(hostname);
} catch (error) {
ctx.status = 400;
ctx.body = { message: "Invalid URL" };
return;
}
const isSSRF = resolvedIps.some((ip) => {
if (net.isIPv4(ip)) {
const parts = ip.split(".").map(Number);
return (
parts[0] === 10 ||
(parts[0] === 172 && parts[1] >= 16 && parts[1] < 32) ||
(parts[0] === 192 && parts[1] === 168) ||
parts[0] === 127 ||
parts[0] === 0
);
} else if (net.isIPv6(ip)) {
return (
ip.startsWith("::") || ip.startsWith("fc00:") || ip.startsWith("fe80:")
);
}
return false;
});
if (isSSRF) {
ctx.status = 400;
ctx.body = { message: "Access to this URL is not allowed" };
return;
}
// Create temp file // Create temp file
const [path, cleanup] = await createTemp(); const [path, cleanup] = await createTemp();

View file

@ -169,7 +169,7 @@
{{ i18n.ts.updateRemoteUser }}</FormButton {{ i18n.ts.updateRemoteUser }}</FormButton
> >
<FormFolder class="_formBlock"> <FormFolder class="_formBlock" v-if="iAmAdmin">
<template #label>Raw</template> <template #label>Raw</template>
<MkObjectView v-if="ap" tall :value="ap"> <MkObjectView v-if="ap" tall :value="ap">
@ -577,13 +577,15 @@ watch(
}, },
); );
watch($$(user), () => { if (iAmAdmin) {
os.api("ap/get", { watch($$(user), () => {
uri: user.uri ?? `${url}/users/${user.id}`, os.api("ap/get", {
}).then((res) => { uri: user.uri ?? `${url}/users/${user.id}`,
ap = res; }).then((res) => {
ap = res;
});
}); });
}); }
const headerActions = $computed(() => []); const headerActions = $computed(() => []);

View file

@ -15,7 +15,7 @@
/> />
<MkRemoteCaution <MkRemoteCaution
v-if="user.host != null" v-if="user.host != null"
:href="user.url" :href="user.url ?? user.uri"
class="warn" class="warn"
/> />

View file

@ -293,7 +293,7 @@ export function getUserMenu(user, router: Router = mainRouter) {
type: "a", type: "a",
icon: "ph-arrow-square-out ph-bold ph-lg", icon: "ph-arrow-square-out ph-bold ph-lg",
text: i18n.ts.showOnRemote, text: i18n.ts.showOnRemote,
href: user.url, href: user.url ?? user.uri,
target: "_blank", target: "_blank",
} }
: undefined, : undefined,

View file

@ -5593,7 +5593,7 @@ __metadata:
mfm-js: "npm:0.23.3" mfm-js: "npm:0.23.3"
mime-types: "npm:2.1.35" mime-types: "npm:2.1.35"
mocha: "npm:10.2.0" mocha: "npm:10.2.0"
msgpackr: "npm:1.9.5" msgpackr: "npm:1.11.2"
multer: "npm:1.4.4-lts.1" multer: "npm:1.4.4-lts.1"
nested-property: "npm:4.0.0" nested-property: "npm:4.0.0"
node-fetch: "npm:3.3.2" node-fetch: "npm:3.3.2"
@ -5615,7 +5615,7 @@ __metadata:
qs: "npm:6.11.2" qs: "npm:6.11.2"
random-seed: "npm:0.3.0" random-seed: "npm:0.3.0"
ratelimiter: "npm:3.4.1" ratelimiter: "npm:3.4.1"
re2: "npm:^1.20.11" re2: "npm:^1.21.4"
redis-lock: "npm:0.1.4" redis-lock: "npm:0.1.4"
redis-semaphore: "npm:5.3.1" redis-semaphore: "npm:5.3.1"
reflect-metadata: "npm:0.1.13" reflect-metadata: "npm:0.1.13"
@ -15035,15 +15035,15 @@ __metadata:
languageName: node languageName: node
linkType: hard linkType: hard
"msgpackr@npm:1.9.5": "msgpackr@npm:1.11.2":
version: 1.9.5 version: 1.11.2
resolution: "msgpackr@npm:1.9.5" resolution: "msgpackr@npm:1.11.2"
dependencies: dependencies:
msgpackr-extract: "npm:^3.0.2" msgpackr-extract: "npm:^3.0.2"
dependenciesMeta: dependenciesMeta:
msgpackr-extract: msgpackr-extract:
optional: true optional: true
checksum: 10/d95fbee39b6046bdee06c59af43efda7068c6d1b0406d82b345b2ffd31f917b58829f925ceb56bbd686374ae52c952d3106747c41097021a8218c023715c948a checksum: 10/7602f1e91e5ba13f4289ec9cab0d3f3db87d4ed323bebcb40a0c43ba2f6153192bffb63a5bb4755faacb6e0985f307c35084f40eaba1c325b7035da91381f01a
languageName: node languageName: node
linkType: hard linkType: hard
@ -15099,12 +15099,12 @@ __metadata:
languageName: node languageName: node
linkType: hard linkType: hard
"nan@npm:^2.19.0": "nan@npm:^2.20.0":
version: 2.19.0 version: 2.22.0
resolution: "nan@npm:2.19.0" resolution: "nan@npm:2.22.0"
dependencies: dependencies:
node-gyp: "npm:latest" node-gyp: "npm:latest"
checksum: 10/b97f680753113bcd803cb174e40baa01e04aa4cb95ee62b48841336d9c48b278a2eeff71a4a0d7315b8f639fb1e38049925d3be1c6e266c158dc8f7d95d67eaa checksum: 10/ab165ba910e549fcc21fd561a33f534d86e81ae36c97b1019dcfe506b09692ff867c97794a54b49c9a83b8b485f529f0f58d24966c3a11863c97dc70814f4d50
languageName: node languageName: node
linkType: hard linkType: hard
@ -15277,9 +15277,9 @@ __metadata:
languageName: node languageName: node
linkType: hard linkType: hard
"node-gyp@npm:^10.0.1": "node-gyp@npm:^10.2.0":
version: 10.0.1 version: 10.2.0
resolution: "node-gyp@npm:10.0.1" resolution: "node-gyp@npm:10.2.0"
dependencies: dependencies:
env-paths: "npm:^2.2.0" env-paths: "npm:^2.2.0"
exponential-backoff: "npm:^3.1.1" exponential-backoff: "npm:^3.1.1"
@ -15287,13 +15287,13 @@ __metadata:
graceful-fs: "npm:^4.2.6" graceful-fs: "npm:^4.2.6"
make-fetch-happen: "npm:^13.0.0" make-fetch-happen: "npm:^13.0.0"
nopt: "npm:^7.0.0" nopt: "npm:^7.0.0"
proc-log: "npm:^3.0.0" proc-log: "npm:^4.1.0"
semver: "npm:^7.3.5" semver: "npm:^7.3.5"
tar: "npm:^6.1.2" tar: "npm:^6.2.1"
which: "npm:^4.0.0" which: "npm:^4.0.0"
bin: bin:
node-gyp: bin/node-gyp.js node-gyp: bin/node-gyp.js
checksum: 10/578cf0c821f258ce4b6ebce4461eca4c991a4df2dee163c0624f2fe09c7d6d37240be4942285a0048d307230248ee0b18382d6623b9a0136ce9533486deddfa8 checksum: 10/41773093b1275751dec942b985982fd4e7a69b88cae719b868babcef3880ee6168aaec8dcaa8cd0b9fa7c84873e36cc549c6cac6a124ee65ba4ce1f1cc108cfe
languageName: node languageName: node
linkType: hard linkType: hard
@ -17073,10 +17073,10 @@ __metadata:
languageName: node languageName: node
linkType: hard linkType: hard
"proc-log@npm:^3.0.0": "proc-log@npm:^4.1.0":
version: 3.0.0 version: 4.2.0
resolution: "proc-log@npm:3.0.0" resolution: "proc-log@npm:4.2.0"
checksum: 10/02b64e1b3919e63df06f836b98d3af002b5cd92655cab18b5746e37374bfb73e03b84fe305454614b34c25b485cc687a9eebdccf0242cda8fda2475dd2c97e02 checksum: 10/4e1394491b717f6c1ade15c570ecd4c2b681698474d3ae2d303c1e4b6ab9455bd5a81566211e82890d5a5ae9859718cc6954d5150bb18b09b72ecb297beae90a
languageName: node languageName: node
linkType: hard linkType: hard
@ -17541,14 +17541,14 @@ __metadata:
languageName: node languageName: node
linkType: hard linkType: hard
"re2@npm:^1.20.11": "re2@npm:^1.21.4":
version: 1.20.11 version: 1.21.4
resolution: "re2@npm:1.20.11" resolution: "re2@npm:1.21.4"
dependencies: dependencies:
install-artifact-from-github: "npm:^1.3.5" install-artifact-from-github: "npm:^1.3.5"
nan: "npm:^2.19.0" nan: "npm:^2.20.0"
node-gyp: "npm:^10.0.1" node-gyp: "npm:^10.2.0"
checksum: 10/a8665c861c632c67db448832a5a6a0092a1a29b8b6b731d6ce10f0017ba2871620780a745a8b2cbdd77e57ecf9e7bc8983c7ec5e10e6da6c06079a98146db443 checksum: 10/926871cc84dab656afc035118c79d121211f21f4154084d7f6c05a05f746b5355f04e80a773db9ca817718dde03c561421b0a962300698c9f2eeafa4f70fd364
languageName: node languageName: node
linkType: hard linkType: hard
@ -19655,6 +19655,20 @@ __metadata:
languageName: node languageName: node
linkType: hard linkType: hard
"tar@npm:^6.2.1":
version: 6.2.1
resolution: "tar@npm:6.2.1"
dependencies:
chownr: "npm:^2.0.0"
fs-minipass: "npm:^2.0.0"
minipass: "npm:^5.0.0"
minizlib: "npm:^2.1.1"
mkdirp: "npm:^1.0.3"
yallist: "npm:^4.0.0"
checksum: 10/bfbfbb2861888077fc1130b84029cdc2721efb93d1d1fb80f22a7ac3a98ec6f8972f29e564103bbebf5e97be67ebc356d37fa48dbc4960600a1eb7230fbd1ea0
languageName: node
linkType: hard
"tdigest@npm:^0.1.1": "tdigest@npm:^0.1.1":
version: 0.1.2 version: 0.1.2
resolution: "tdigest@npm:0.1.2" resolution: "tdigest@npm:0.1.2"