mirror of
https://code.forgejo.org/actions/checkout.git
synced 2026-07-30 16:14:00 +00:00
add lock awareness
This commit is contained in:
parent
7484b46911
commit
b3ad80abe3
5 changed files with 152 additions and 13 deletions
|
|
@ -3,7 +3,8 @@ import * as core from '@actions/core'
|
|||
|
||||
// Client for backend-core's /api/v1/git-mirrors endpoints, authed by the runner
|
||||
// verification token. Contract: 200 = presigned URL; 404 = miss (upload after the
|
||||
// stock fetch); 403 = unservable org (skip cache + upload); else = fall back.
|
||||
// stock fetch); 403 = unservable org (skip cache + upload); 409 = another job is
|
||||
// uploading this snapshot (skip upload); else = fall back.
|
||||
|
||||
const API_TIMEOUT_MS = 10_000
|
||||
|
||||
|
|
@ -22,6 +23,7 @@ export type SnapshotLookup =
|
|||
export type UploadURLResult =
|
||||
| {kind: 'ok'; url: string}
|
||||
| {kind: 'disabled'}
|
||||
| {kind: 'locked'}
|
||||
| {kind: 'error'}
|
||||
|
||||
function baseUrl(): string {
|
||||
|
|
@ -89,6 +91,10 @@ export async function requestUploadURL(
|
|||
core.debug(`[wb-cache] upload-url answered 403 (disabled)`)
|
||||
return {kind: 'disabled'}
|
||||
}
|
||||
if (res.status === 409) {
|
||||
core.debug(`[wb-cache] upload-url answered 409 (locked)`)
|
||||
return {kind: 'locked'}
|
||||
}
|
||||
core.debug(`[wb-cache] upload-url answered ${res.status}`)
|
||||
return {kind: 'error'}
|
||||
} catch (error) {
|
||||
|
|
|
|||
|
|
@ -171,6 +171,31 @@ export async function contribute(settings: IGitSourceSettings): Promise<void> {
|
|||
}
|
||||
}
|
||||
|
||||
// Refuse any tar member outside objects/ or shallow, absolute, or with a `..`
|
||||
// component — the tar is remote and extracted into .git, so a crafted member could
|
||||
// escape (e.g. hooks/, ../) and run code during checkout.
|
||||
export async function assertSafeTarMembers(tar: string): Promise<void> {
|
||||
let listing = ''
|
||||
await exec.exec('tar', ['-tf', tar], {
|
||||
silent: true,
|
||||
listeners: {stdout: (d: Buffer) => (listing += d.toString())}
|
||||
})
|
||||
for (const raw of listing.split('\n')) {
|
||||
const member = raw.trim()
|
||||
if (!member) {
|
||||
continue
|
||||
}
|
||||
const top = member.replace(/^\.\//, '').split('/')[0]
|
||||
if (
|
||||
member.startsWith('/') ||
|
||||
member.split('/').includes('..') ||
|
||||
(top !== 'objects' && top !== 'shallow')
|
||||
) {
|
||||
throw new Error(`unexpected snapshot tar member: ${member}`)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
async function restoreSnapshot(
|
||||
settings: IGitSourceSettings,
|
||||
url: string,
|
||||
|
|
@ -189,6 +214,7 @@ async function restoreSnapshot(
|
|||
Readable.fromWeb(res.body as import('stream/web').ReadableStream),
|
||||
fs.createWriteStream(tmpTar)
|
||||
)
|
||||
await assertSafeTarMembers(tmpTar)
|
||||
await exec.exec('tar', ['-xf', tmpTar, '-C', gitDir])
|
||||
|
||||
const check = await exec.exec(
|
||||
|
|
@ -253,9 +279,11 @@ async function uploadSnapshot(settings: IGitSourceSettings): Promise<void> {
|
|||
const upload = await api.requestUploadURL(repoKey, sha)
|
||||
if (upload.kind !== 'ok') {
|
||||
core.info(
|
||||
upload.kind === 'disabled'
|
||||
? 'Snapshot cache is disabled by the backend; not uploading'
|
||||
: 'Snapshot cache backend unavailable; not uploading'
|
||||
upload.kind === 'locked'
|
||||
? 'Another job is already uploading this snapshot; skipping'
|
||||
: upload.kind === 'disabled'
|
||||
? 'Snapshot cache is disabled by the backend; not uploading'
|
||||
: 'Snapshot cache backend unavailable; not uploading'
|
||||
)
|
||||
return
|
||||
}
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue