mirror of
https://github.com/tdurieux/anonymous_github.git
synced 2026-11-14 00:07:00 +01:00
fix: harden repository content and improve access recovery
Sandbox executable MIME aliases, enforce strict Mermaid rendering, and add security regression coverage. Upgrade vulnerable dependencies with archive compatibility checks and Node 22/24 CI. Make repository restoration atomic and provide actionable viewer errors with retry controls.
This commit is contained in:
1 parent
772e40debd
commit
a301c46a94
21 files changed
+1048
-1051
No files matched your search
@@ -21,8 +21,8 @@ function decodePathSegment(segment: string): string {
|
||||
}
|
||||
}
|
||||
|
||||
// Extensions the browser will execute script from when it renders the
|
||||
// response as a document (directly, or via the "Raw" action).
|
||||
// Keep explicit fallbacks for markup types not consistently mapped by MIME
|
||||
// databases. MIME classification below also covers aliases such as svgz/shtml.
|
||||
const SCRIPTABLE_EXTENSIONS = new Set([
|
||||
"html",
|
||||
"htm",
|
||||
@@ -33,10 +33,16 @@ const SCRIPTABLE_EXTENSIONS = new Set([
|
||||
"xsl",
|
||||
"xslt",
|
||||
"mhtml",
|
||||
"mht",
|
||||
]);
|
||||
|
||||
export function isScriptableDocument(anonymizedPath: string): boolean {
|
||||
const name = anonymizedPath.substring(anonymizedPath.lastIndexOf("/") + 1);
|
||||
const mime = lookup(name);
|
||||
if (mime && (
|
||||
mime === "text/html" || mime === "text/xml" || mime === "application/xml" ||
|
||||
mime.endsWith("+xml") || mime === "message/rfc822" || mime === "multipart/related"
|
||||
)) return true;
|
||||
const dot = name.lastIndexOf(".");
|
||||
if (dot < 0) return false;
|
||||
return SCRIPTABLE_EXTENSIONS.has(name.substring(dot + 1).toLowerCase());
|
||||
@@ -115,6 +121,7 @@ router.get(
|
||||
object: f,
|
||||
});
|
||||
}
|
||||
res.header("X-Content-Type-Options", "nosniff");
|
||||
if (req.query.download) {
|
||||
res.attachment(
|
||||
anonymizedPath.substring(anonymizedPath.lastIndexOf("/") + 1)
|
||||
|
||||
@@ -33,6 +33,24 @@ const router = express.Router();
|
||||
// user needs to be connected for all user API
|
||||
router.use(ensureAuthenticated);
|
||||
|
||||
async function enqueueSavedSnapshot(model: IAnonymizedRepositoryDocument) {
|
||||
try {
|
||||
await downloadQueue.add(model.repoId, { repoId: model.repoId }, {
|
||||
jobId: `repo-${model.repoId}`, attempts: 3,
|
||||
});
|
||||
} catch (error) {
|
||||
// Leave a failed enqueue retryable without overwriting a worker or a
|
||||
// lifecycle change that happened after this save.
|
||||
await AnonymizedRepositoryModel.updateOne({
|
||||
_id: model._id, status: RepositoryStatus.PREPARING,
|
||||
statusDate: model.statusDate, anonymizeDate: model.anonymizeDate,
|
||||
"githubAccess.revision": model.githubAccess?.revision || { $exists: false },
|
||||
}, { $set: { status: RepositoryStatus.ERROR, statusDate: new Date(),
|
||||
statusMessage: "preparation_interrupted" } }).exec();
|
||||
throw error;
|
||||
}
|
||||
}
|
||||
|
||||
async function previewToken(req: express.Request) {
|
||||
const user = await getUser(req);
|
||||
if (typeof req.query.anonymizedRepoId === "string") {
|
||||
@@ -185,15 +203,18 @@ router.post(
|
||||
repo.model.options.expirationDate
|
||||
);
|
||||
const reactivating = repo.status === RepositoryStatus.EXPIRED;
|
||||
const updates: Record<string, Date> = {
|
||||
const updates: Record<string, Date | string | null> = {
|
||||
"options.expirationDate": newExpiration,
|
||||
settingsSavedAt: new Date(),
|
||||
};
|
||||
repo.model.options.expirationDate = newExpiration;
|
||||
repo.model.settingsSavedAt = updates.settingsSavedAt;
|
||||
repo.model.settingsSavedAt = updates.settingsSavedAt as Date;
|
||||
if (reactivating) {
|
||||
repo.model.anonymizeDate = new Date();
|
||||
updates.anonymizeDate = repo.model.anonymizeDate;
|
||||
updates.status = RepositoryStatus.PREPARING;
|
||||
updates.statusDate = new Date();
|
||||
updates.statusMessage = null;
|
||||
}
|
||||
const saved = await AnonymizedRepositoryModel.updateOne(
|
||||
{ _id: repo.model._id, status: repo.status, statusDate: repo.model.statusDate },
|
||||
@@ -207,12 +228,10 @@ router.post(
|
||||
// directly instead of asking GitHub for the latest branch head first;
|
||||
// that lookup can fail after the new date has already been persisted
|
||||
// and leave the repository stuck in the expired state.
|
||||
await repo.updateStatus(RepositoryStatus.PREPARING);
|
||||
await downloadQueue.add(
|
||||
repo.repoId,
|
||||
{ repoId: repo.repoId },
|
||||
{ jobId: `repo-${repo.repoId}`, attempts: 3 }
|
||||
);
|
||||
repo.model.status = RepositoryStatus.PREPARING;
|
||||
repo.model.statusDate = updates.statusDate as Date;
|
||||
repo.model.statusMessage = undefined;
|
||||
await enqueueSavedSnapshot(repo.model);
|
||||
}
|
||||
res.json({ status: repo.status, expirationDate: newExpiration });
|
||||
} catch (error) {
|
||||
@@ -610,6 +629,9 @@ router.post(
|
||||
}
|
||||
}
|
||||
repo.model.conference = repoUpdate.conference;
|
||||
const preparation = sourceChanged || reactivating ? {
|
||||
status: RepositoryStatus.PREPARING, statusDate: new Date(), statusMessage: null,
|
||||
} : undefined;
|
||||
const saved = await AnonymizedRepositoryModel.updateOne(
|
||||
{ _id: repo.model._id, status: repo.status, statusDate: repo.model.statusDate,
|
||||
"githubAccess.revision": previousAccessRevision || { $exists: false } },
|
||||
@@ -621,6 +643,7 @@ router.post(
|
||||
githubAccess: repo.model.githubAccess,
|
||||
conference: repo.model.conference,
|
||||
anonymizeDate: repo.model.anonymizeDate,
|
||||
...preparation,
|
||||
},
|
||||
}
|
||||
).exec();
|
||||
@@ -630,13 +653,11 @@ router.post(
|
||||
return res.json({ status: repo.status });
|
||||
}
|
||||
|
||||
await repo.updateStatus(RepositoryStatus.PREPARING);
|
||||
repo.model.status = preparation!.status;
|
||||
repo.model.statusDate = preparation!.statusDate;
|
||||
repo.model.statusMessage = undefined;
|
||||
await enqueueSavedSnapshot(repo.model);
|
||||
res.json({ status: repo.status });
|
||||
await downloadQueue.add(
|
||||
repo.repoId,
|
||||
{ repoId: repo.repoId },
|
||||
{ jobId: `repo-${repo.repoId}` }
|
||||
);
|
||||
} catch (error) {
|
||||
return handleError(error, res, req);
|
||||
}
|
||||
|
||||
Reference in new issue
Block a user