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
7 changes: 5 additions & 2 deletions packages/core/src/auth.ts
Original file line number Diff line number Diff line change
Expand Up @@ -7,15 +7,18 @@ import {
exchangeGitHubActionsTokenlessToken,
} from "./github-actions-tokenless";
import type { Config } from "./config";
import { debug } from "./debug";
import { debug, maskToken } from "./debug";

/**
* Resolve the Argos authentication token.
* Priority: ARGOS_TOKEN > GitHub Actions OIDC > GitHub Actions tokenless exchange.
*/
export async function resolveArgosToken(config: Config): Promise<string> {
if (config.token) {
debug("Authenticated with ARGOS_TOKEN.");
// Masked, and logged here only: this is the one place that knows which
// token the command ends up using, whether it came from the parameters or
// from the environment.
debug(`Authenticated with ARGOS_TOKEN (${maskToken(config.token)}).`);
return config.token;
}

Expand Down
3 changes: 2 additions & 1 deletion packages/core/src/ci-environment/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -73,7 +73,7 @@ export function listAncestorCommits(input: {
export async function getCiEnvironment(): Promise<CiEnvironment | null> {
const context = createContext();

debug("Detecting CI environment", context);
debug("Detecting CI environment");
const service = getCiService(context);

// Service matched
Expand All @@ -89,5 +89,6 @@ export async function getCiEnvironment(): Promise<CiEnvironment | null> {
return ciEnvironment;
}

debug("No CI service matched");
return null;
}
24 changes: 24 additions & 0 deletions packages/core/src/debug.ts
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,30 @@ const KEY = "@argos-ci/core";

export const debug = createDebug(KEY);

/**
* Leading characters of a token kept in the debug output: enough to tell two
* tokens apart, far too few to use.
*/
const TOKEN_PREVIEW_LENGTH = 6;

/**
* Show which token is in play without publishing it.
*
* Debug output is the documented way to report an upload problem: it gets
* pasted into public issues and written to CI logs, which are world-readable on
* public repositories, so a token printed in full is a published token
* (GHSA-28pg-v3hp-9g7f). The first few characters answer "is that the token I
* think it is?" and are useless to anyone else.
*/
export function maskToken(
token: string | null | undefined,
): string | null | undefined {
if (!token) {
return token;
}
return `${token.slice(0, TOKEN_PREVIEW_LENGTH)}…`;
}

export const isDebugEnabled = createDebug.enabled(KEY);

export const debugTime = (arg: string) => {
Expand Down
29 changes: 28 additions & 1 deletion packages/core/src/upload.test.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
import { describe, it, expect } from "vitest";
import { describe, it, expect, vi } from "vitest";
import createDebug from "debug";
import { join } from "node:path";
import { upload } from "./upload";
import { server, setupMockServer } from "../mocks/server";
Expand Down Expand Up @@ -292,4 +293,30 @@ describe("#upload", () => {
expect(receivedMergeQueuePrNumbers).toEqual([12, 34]);
})();
});

it("never prints the token in debug output (GHSA-28pg-v3hp-9g7f)", async () => {
const token = "92d832e0d22ab113c8979d73a87a11130eaa24a9";
const write = vi.spyOn(process.stderr, "write").mockReturnValue(true);
createDebug.enable("@argos-ci/core");

let output: string;
try {
await upload({
branch: "main",
apiBaseUrl: "https://api.argos-ci.dev",
root: join(__dirname, "../../../__fixtures__/screenshots"),
commit: "f16f980bd17cccfa93a1ae7766727e67950773d0",
token,
});
output = write.mock.calls.map(([chunk]) => String(chunk)).join("");
} finally {
createDebug.disable();
write.mockRestore();
}

expect(output).not.toContain(token);
// Masked once, where the token is resolved, so the user can still tell
// which one was used.
expect(output).toContain("Authenticated with ARGOS_TOKEN (92d832…)");
});
});
6 changes: 4 additions & 2 deletions packages/core/src/upload.ts
Original file line number Diff line number Diff line change
Expand Up @@ -188,7 +188,8 @@ export async function upload(params: UploadParameters): Promise<{
build: ArgosAPISchema.components["schemas"]["Build"];
screenshots: Screenshot[];
}> {
debug("Starting upload with params", params);
const { token: _paramsToken, ...debugParams } = params;
debug("Starting upload with params", debugParams);

// Read config
const [config, argosSdk] = await Promise.all([
Expand All @@ -213,7 +214,8 @@ export async function upload(params: UploadParameters): Promise<{
(config.previewBaseUrl ? { baseUrl: config.previewBaseUrl } : undefined);

const globs = params.files ?? ["**/*.{png,jpg,jpeg}"];
debug("Using config and files", config, globs);
const { token: _configToken, ...debugConfig } = config;
debug("Using config and files", debugConfig, globs);

// Collect snapshots
const files = await discoverSnapshots(globs, {
Expand Down
Loading