317 lines · 13.3 KB
import { env } from "cloudflare:workers";
import { all, batch, first, run, stmt } from "../../db";
import { toHex } from "../../crypto";
import { enforceRateLimit } from "../../limits";
import { recordUsage } from "../../metering/record";
import { parseTreeObject } from "../../git/snapshot";
import { isSafeTreePath } from "../../git/tree-path";
import { tarHeader, tarPadding, TAR_BLOCK } from "../../tar";
import type { PackObject, ParsedPack } from "../../git/pack";
import {
  ActionSourceError,
  fetchCommitObjects,
  GithubUnavailableError,
  isReachableFromRefs,
  resolveGithubRef,
} from "./github";

/**
 * Action archives are content-addressed by commit SHA and shared by every account: public code is identical
 * for everyone, so it is downloaded from GitHub once and kept in R2. The tarball's sha256 is recorded and
 * checked by runners, so neither GitHub nor a tampered R2 object can change what a run executes.
 */
const MAX_FILES = 20_000;
const MAX_TREE_VISITS = 20_000;
export const MAX_ARCHIVE_BYTES = 64 * 1024 * 1024;
const MAX_MANIFESTS = 100;
const REF_CACHE_SECONDS = 300;
/** Fallback when GitHub refuses: the last commit a tag resolved to, for up to a week. */
const LAST_REF_SECONDS = 7 * 24 * 60 * 60;
const NEGATIVE_PIN_CACHE_MS = 60 * 60 * 1000;
/** A pin that was reachable is re-checked weekly, so a SHA force-pushed off every ref stops being trusted. */
const POSITIVE_PIN_CACHE_MS = 7 * 24 * 60 * 60 * 1000;
const TOUCH_INTERVAL_MS = 24 * 60 * 60 * 1000;
const IDLE_MS = 30 * 24 * 60 * 60 * 1000;
const PRUNE_BATCH = 100;
const MANIFEST_NAMES = ["action.yml", "action.yaml"];
const FILE_MODES: Record<string, number> = { "100644": 0o644, "100755": 0o755 };
const TREE_LINE = /^tree ([0-9a-f]{40,64})$/m;
const COMMITTER_TIME = /^committer .* (\d+) [+-]\d{4}$/m;
const decoder = new TextDecoder();