Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
21 changes: 20 additions & 1 deletion .github/workflows/deploy-production.yml
Original file line number Diff line number Diff line change
Expand Up @@ -248,6 +248,7 @@ jobs:
if: steps.read-model-policy.outputs.evidence_required == 'false'
env:
STAGED_TARGET_URL: ${{ steps.staged-deployment.outputs.target_url }}
VERCEL_AUTOMATION_BYPASS_SECRET: ${{ secrets.VERCEL_AUTOMATION_BYPASS_SECRET }}
run: |
node --input-type=module <<'NODE'
const origin = new URL(process.env.STAGED_TARGET_URL);
Expand All @@ -260,14 +261,32 @@ jobs:
) {
throw new Error("legacy smoke target is not an exact Vercel origin");
}
const automationBypassSecret =
process.env.VERCEL_AUTOMATION_BYPASS_SECRET;
const automationBypassSecretLength = Buffer.byteLength(
automationBypassSecret ?? "",
"utf8",
);
if (
typeof automationBypassSecret !== "string" ||
automationBypassSecretLength < 32 ||
automationBypassSecretLength > 512 ||
/[\r\n]/.test(automationBypassSecret)
) {
throw new Error("legacy smoke automation bypass is unavailable");
}
const legacySmokeRequestHeaders = Object.freeze({
Accept: "application/json",
"x-vercel-protection-bypass": automationBypassSecret,
});

const requestJson = async (path) => {
let lastError;
for (let attempt = 1; attempt <= 12; attempt += 1) {
try {
const response = await fetch(new URL(path, origin), {
redirect: "error",
headers: { Accept: "application/json" },
headers: legacySmokeRequestHeaders,
signal: AbortSignal.timeout(30_000),
});
const text = await response.text();
Expand Down
45 changes: 45 additions & 0 deletions scripts/perf/read-model-ops-source-contracts.mjs
Original file line number Diff line number Diff line change
Expand Up @@ -969,6 +969,51 @@ export function evaluateReadModelOperationsSourceContracts(
stagedWakeGateBlock.includes('--target-url "$STAGED_TARGET_URL"'),
"an active fast lane must pass the exact unaliased staged wake canary before attestation",
);
const stagedLegacySmoke = deployWorkflow.indexOf(
"Smoke legacy staged public APIs",
);
const stagedLegacySmokeEnd = deployWorkflow.indexOf(
"Record legacy-only read path",
);
const stagedLegacySmokeBlock =
stagedLegacySmoke >= 0 && stagedLegacySmokeEnd > stagedLegacySmoke
? deployWorkflow.slice(stagedLegacySmoke, stagedLegacySmokeEnd)
: "";
check(
"ops-protected-legacy-stage-smoke",
stagedLegacySmoke > stagedWakeGateEnd &&
stagedLegacySmokeBlock.includes(
"if: steps.read-model-policy.outputs.evidence_required == 'false'",
) &&
stagedLegacySmokeBlock.includes(
"VERCEL_AUTOMATION_BYPASS_SECRET: ${{ secrets.VERCEL_AUTOMATION_BYPASS_SECRET }}",
) &&
stagedLegacySmokeBlock.includes(
"process.env.VERCEL_AUTOMATION_BYPASS_SECRET",
) &&
stagedLegacySmokeBlock.includes(
'Buffer.byteLength(\n automationBypassSecret ?? "",\n "utf8",\n )',
) &&
stagedLegacySmokeBlock.includes("automationBypassSecretLength < 32") &&
stagedLegacySmokeBlock.includes("automationBypassSecretLength > 512") &&
stagedLegacySmokeBlock.includes(
"/[\\r\\n]/.test(automationBypassSecret)",
) &&
stagedLegacySmokeBlock.includes(
'"x-vercel-protection-bypass": automationBypassSecret',
) &&
stagedLegacySmokeBlock.includes("headers: legacySmokeRequestHeaders") &&
stagedLegacySmokeBlock.includes(
"STAGED_TARGET_URL: ${{ steps.staged-deployment.outputs.target_url }}",
) &&
(stagedLegacySmokeBlock.match(/\bfetch\(/gu) ?? []).length === 1 &&
!stagedLegacySmokeBlock.includes(
"NEXT_PUBLIC_VERCEL_AUTOMATION_BYPASS_SECRET",
) &&
!stagedLegacySmokeBlock.includes("${automationBypassSecret}") &&
!stagedLegacySmokeBlock.includes("console."),
"the legacy staged API smoke uses the protected deployment bypass only inside its exact step without exposing it",
);
check(
"ops-exact-release-dependency",
deployWorkflow.includes("needs: release-gate") &&
Expand Down
4 changes: 4 additions & 0 deletions tests/data-pipeline/read-model-deploy-policy.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -401,6 +401,10 @@ describe("read-model production deploy policy", () => {
expect(workflow).toContain("staged-release-attestation.json");
expect(workflow).toContain("attestation_sha256");
expect(workflow).toContain("Smoke legacy staged public APIs");
expect(workflow).toContain(
'"x-vercel-protection-bypass": automationBypassSecret',
);
expect(workflow).toContain("headers: legacySmokeRequestHeaders");
expect(workflow).toContain('"/api/ops/health"');
expect(workflow).toContain('"/api/indexers/v1/token-list"');
expect(workflow).toContain("/api/explore/token?address=");
Expand Down
85 changes: 85 additions & 0 deletions tests/data-pipeline/read-model-ops-contract.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -220,6 +220,91 @@ describe("read-model operations source contract", () => {
);
});

it("fails closed when the protected legacy staged smoke bypass is missing", () => {
const workflowPath = ".github/workflows/deploy-production.yml";
const legacyStep =
" - name: Smoke legacy staged public APIs\n" +
" if: steps.read-model-policy.outputs.evidence_required == 'false'\n" +
" env:\n" +
" STAGED_TARGET_URL: ${{ steps.staged-deployment.outputs.target_url }}\n" +
" VERCEL_AUTOMATION_BYPASS_SECRET: ${{ secrets.VERCEL_AUTOMATION_BYPASS_SECRET }}\n";
const unsafeWorkflow = readFileSync(resolve(ROOT, workflowPath), "utf8").replace(
legacyStep,
legacyStep.replace(
" VERCEL_AUTOMATION_BYPASS_SECRET: ${{ secrets.VERCEL_AUTOMATION_BYPASS_SECRET }}\n",
"",
),
);
expect(unsafeWorkflow).not.toBe(
readFileSync(resolve(ROOT, workflowPath), "utf8"),
);
const result = evaluateReadModelOperationsSourceContracts(ROOT, {
sourceOverrides: {
...integratedOverrides(),
[workflowPath]: unsafeWorkflow,
},
expectedSha256Overrides: fixtureDigests(),
});
expect(result.failures.map(({ id }: { id: string }) => id)).toContain(
"ops-protected-legacy-stage-smoke",
);
});

it("rejects a legacy staged smoke bypass relocated to another workflow step", () => {
const workflowPath = ".github/workflows/deploy-production.yml";
const secretLine =
" VERCEL_AUTOMATION_BYPASS_SECRET: ${{ secrets.VERCEL_AUTOMATION_BYPASS_SECRET }}\n";
const workflow = readFileSync(resolve(ROOT, workflowPath), "utf8");
const legacyStepStart = workflow.indexOf(
" - name: Smoke legacy staged public APIs",
);
const legacyStepEnd = workflow.indexOf(
" - name: Record legacy-only read path",
);
expect(legacyStepStart).toBeGreaterThanOrEqual(0);
expect(legacyStepEnd).toBeGreaterThan(legacyStepStart);
const legacyStep = workflow.slice(legacyStepStart, legacyStepEnd);
expect(legacyStep).toContain(secretLine);
const unsafeLegacyStep = legacyStep.replace(secretLine, "");
const unsafeWorkflow =
workflow.slice(0, legacyStepStart) +
unsafeLegacyStep +
workflow.slice(legacyStepEnd).replace(
" - name: Record legacy-only read path\n",
` - name: Record legacy-only read path\n env:\n${secretLine}`,
);
const result = evaluateReadModelOperationsSourceContracts(ROOT, {
sourceOverrides: {
...integratedOverrides(),
[workflowPath]: unsafeWorkflow,
},
expectedSha256Overrides: fixtureDigests(),
});
expect(result.failures.map(({ id }: { id: string }) => id)).toContain(
"ops-protected-legacy-stage-smoke",
);
});

it("fails closed when the legacy staged smoke drops the bypass header", () => {
const workflowPath = ".github/workflows/deploy-production.yml";
const workflow = readFileSync(resolve(ROOT, workflowPath), "utf8");
const unsafeWorkflow = workflow.replace(
' "x-vercel-protection-bypass": automationBypassSecret,\n',
"",
);
expect(unsafeWorkflow).not.toBe(workflow);
const result = evaluateReadModelOperationsSourceContracts(ROOT, {
sourceOverrides: {
...integratedOverrides(),
[workflowPath]: unsafeWorkflow,
},
expectedSha256Overrides: fixtureDigests(),
});
expect(result.failures.map(({ id }: { id: string }) => id)).toContain(
"ops-protected-legacy-stage-smoke",
);
});

it("rejects comment-only controls and jointly drifted manifests", () => {
const operations = JSON.parse(
readFileSync(resolve(ROOT, "config/read-model-operations.v1.json"), "utf8"),
Expand Down