Skip to content
Open
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
65 changes: 24 additions & 41 deletions .github/workflows/grant-paper-executor-permissions.yml
Original file line number Diff line number Diff line change
Expand Up @@ -52,10 +52,24 @@ jobs:
IAM_APPROVED_REVISION: ${{ inputs.approved_revision }}
IAM_OWNER_RUN_CARD: ${{ inputs.owner_run_card }}
steps:
# Read mappings inside the action, never through logged inputs or env.
# Missing variable-read authority blocks before authentication or writes.
# Resolve mappings through the job Environment vars context (same channel as
# apply-paper-scheduler-jobs-get). Do not call the Variables REST API: the
# workflow GITHUB_TOKEN only receives contents/metadata and cannot read it
# (a historical mapping-read failure under that token shape stopped before
# authentication on an already-configured name). Mask every value before
# exporting it to later steps. Missing or blank mappings fail closed.
# Organization-only names are an operational prerequisite (unsupported by
# policy) but are NOT technically rejected: vars has no scope tag and
# GITHUB_TOKEN cannot query Variables REST.
- name: Mask protected mappings before exposing step environment
uses: actions/github-script@v8
env:
MAPPING_GCP_PROJECT_ID: ${{ vars.GCP_PROJECT_ID }}
MAPPING_PAPER_SCHEDULER_GET_PROJECT_ID: ${{ vars.PAPER_SCHEDULER_GET_PROJECT_ID }}
MAPPING_GCP_WORKLOAD_IDENTITY_SERVICE_ACCOUNT: ${{ vars.GCP_WORKLOAD_IDENTITY_SERVICE_ACCOUNT }}
MAPPING_PAPER_IAM_GRANT_RECIPIENT: ${{ vars.PAPER_IAM_GRANT_RECIPIENT }}
Comment on lines +66 to +70

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P1 Badge Mask mappings before binding them to the first step environment

When any mapping is populated, this first step exposes its value in the Actions log before the JavaScript can mask it: the runner prints the step environment during preparation, before executing the action. Values from vars are not automatically secret-masked, and the later core.setSecret() calls cannot redact output already written. This leaks project IDs, service-account emails and the WIF provider even when a subsequent blank mapping stops authentication. The offline harness only checks masking before export, so it misses this runner-generated disclosure. Use an ingestion mechanism that does not place unmasked mappings in the initial step details, preserving the public-resource boundary in AGENTS.md.

AGENTS.md reference: AGENTS.md:L81-L83

Useful? React with 👍 / 👎.

MAPPING_PAPER_IAM_BOOTSTRAP_SERVICE_ACCOUNT: ${{ vars.PAPER_IAM_BOOTSTRAP_SERVICE_ACCOUNT }}
MAPPING_PAPER_IAM_BOOTSTRAP_WIF_PROVIDER: ${{ vars.PAPER_IAM_BOOTSTRAP_WIF_PROVIDER }}
with:
script: |
const names = [
Expand All @@ -65,51 +79,20 @@ jobs:
];
const mappings = [];
for (const name of names) {
let scope = 'environment';
let category = 'transport_error';
const read = async (route, args) => {
let response;
try {
response = await github.request(route, args);
} catch (error) {
const status = error?.status;
if (status === 401 || status === 403 || status === 429) {
category = `http_${status}`;
} else if (status === 404) {
category = scope === 'repository' ? 'http_404_both' : 'other_http';
} else if (Number.isInteger(status) && status >= 500 && status <= 599) {
category = 'http_5xx';
} else if (Number.isInteger(status)) {
category = 'other_http';
} else {
category = 'transport_error';
}
// Only an Environment HTTP 404 permits the same-name fallback.
if (scope === 'environment' && status === 404) return null;
throw new Error('Mapping lookup failed');
}
const data = response?.data;
if (!data || typeof data.value !== 'string') {
const scope = 'vars';
let category = 'blank_value';
try {
const value = process.env['MAPPING_' + name];
if (value === undefined || value === null || typeof value !== 'string') {
category = 'malformed_response';
throw new Error('Invalid mapping response');
}
if (!data.value.trim()) {
if (!value.trim()) {
category = 'blank_value';
throw new Error('Empty mapping');
}
return data;
};
try {
let data = await read(
'GET /repos/{owner}/{repo}/environments/{environment_name}/variables/{name}',
{...context.repo, environment_name: 'longbridge-paper', name});
if (data === null) {
scope = 'repository';
data = await read('GET /repos/{owner}/{repo}/actions/variables/{name}',
{...context.repo, name});
}
core.setSecret(data.value);
mappings.push([name, data.value]);
core.setSecret(value);
mappings.push([name, value]);
} catch {
throw new Error(`Protected mapping read failed: name=${name} scope=${scope} category=${category}; stop before authentication`);
}
Expand Down
Loading
Loading