From 92f9812c23234bc880f3f489d5942331f86c4fe9 Mon Sep 17 00:00:00 2001 From: Elizabeth Maher Date: Thu, 2 Jul 2026 16:54:22 +0000 Subject: [PATCH 1/3] fix: prevent publish abort on auto-generated logger-credential named values The publish pre-flight redaction guard scanned every named value, including APIM auto-generated logger credentials (24-char hex IDs, e.g. Logger-Credentials--) whose secret values are redacted on extract. Because splitNamedValues() already skips these during publish (APIM recreates them when the referencing logger is published), the guard flagged markers in artifacts that are never sent to APIM and aborted the publish with a false positive. Make the guard mirror the publisher: skip auto-generated named values that have no explicit override. Extract hasNamedValueOverride() into override-merger.ts as the single source of truth shared by both call sites. --- src/services/override-merger.ts | 12 +++++ src/services/publish-service.ts | 13 +---- src/services/secret-redaction-guard.ts | 14 +++++- .../services/secret-redaction-guard.test.ts | 49 +++++++++++++++++++ 4 files changed, 75 insertions(+), 13 deletions(-) diff --git a/src/services/override-merger.ts b/src/services/override-merger.ts index 53a91cdc..5bc4e25e 100644 --- a/src/services/override-merger.ts +++ b/src/services/override-merger.ts @@ -58,6 +58,18 @@ const GRANDCHILD_OVERRIDE_MAP: Partial key.toLowerCase() === lowerName + ); +} + /** * Apply environment overrides from OverrideConfig to a resource JSON payload. * Deep-merges matching override properties using case-insensitive key matching. diff --git a/src/services/publish-service.ts b/src/services/publish-service.ts index 121d5639..ed9604c9 100644 --- a/src/services/publish-service.ts +++ b/src/services/publish-service.ts @@ -27,6 +27,7 @@ import { generateDryRunReport, DryRunReport } from './dry-run-reporter.js'; import { computeDeleteActions } from './delete-unmatched-service.js'; import { computeGitDiff } from './git-diff-service.js'; import { scanForRedactionMarkers } from './secret-redaction-guard.js'; +import { hasNamedValueOverride } from './override-merger.js'; import { REDACTION_MARKER } from './secret-redactor.js'; /** @@ -446,18 +447,6 @@ function splitNamedValues( return { namedValues, otherTier1 }; } -/** - * Check whether a named value has an explicit override entry. - * Uses case-insensitive matching to align with override-merger behavior. - */ -function hasNamedValueOverride(name: string, overrides?: OverrideConfig): boolean { - if (!overrides?.namedValues) return false; - const lowerName = name.toLowerCase(); - return Object.keys(overrides.namedValues).some( - (key) => key.toLowerCase() === lowerName - ); -} - /** * Separates Backend descriptors that are pool backends (properties.type === "Pool") * from all other descriptors in the supplied list. diff --git a/src/services/secret-redaction-guard.ts b/src/services/secret-redaction-guard.ts index cad1351a..ba1a8442 100644 --- a/src/services/secret-redaction-guard.ts +++ b/src/services/secret-redaction-guard.ts @@ -22,10 +22,12 @@ import type { IArtifactStore } from '../clients/iartifact-store.js'; import type { PublishConfig } from '../models/config.js'; import type { ResourceDescriptor } from '../models/types.js'; import { ResourceType } from '../models/resource-types.js'; -import { applyOverrides } from './override-merger.js'; +import { applyOverrides, hasNamedValueOverride } from './override-merger.js'; import { POLICY_TYPES } from './resource-publisher.js'; import { REDACTION_MARKER } from './secret-redactor.js'; import { buildResourceLabel } from '../lib/resource-uri.js'; +import { getNamePart } from '../lib/resource-path.js'; +import { isAutoGeneratedId } from '../lib/auto-generated.js'; /** * A single artifact that still contains a redaction marker after overrides. @@ -103,6 +105,16 @@ async function scanNamedValue( config: PublishConfig, descriptor: ResourceDescriptor ): Promise { + // Auto-generated named values (e.g. Logger-Credentials--) are skipped + // during publish because APIM recreates them when the referencing resource + // (Logger, etc.) is published. splitNamedValues() drops them unless the user + // supplies an explicit override, so scanning them here would flag markers in + // artifacts that are never sent to APIM. Mirror the publish filter exactly. + const name = getNamePart(descriptor.nameParts, 0); + if (isAutoGeneratedId(name) && !hasNamedValueOverride(name, config.overrides)) { + return undefined; + } + const json = await store.readResource(config.sourceDir, descriptor); if (!json) { return undefined; diff --git a/tests/unit/services/secret-redaction-guard.test.ts b/tests/unit/services/secret-redaction-guard.test.ts index cc58e81a..381bb9aa 100644 --- a/tests/unit/services/secret-redaction-guard.test.ts +++ b/tests/unit/services/secret-redaction-guard.test.ts @@ -123,6 +123,55 @@ describe('secret-redaction-guard', () => { expect(findings).toEqual([]); }); + it('ignores an auto-generated named value (logger credential) with no override', async () => { + const store = createMockStore(); + store.readResource.mockResolvedValue({ + properties: { secret: true, value: REDACTION_MARKER }, + }); + + const autoGeneratedDescriptor: ResourceDescriptor = { + type: ResourceType.NamedValue, + nameParts: ['6a46928afa1f751d044d805e'], + }; + + const findings = await scanForRedactionMarkers(store, testConfig, [autoGeneratedDescriptor]); + + // APIM recreates these when the referencing logger publishes, so publish + // skips them and the guard must not flag them. It should skip before any read. + expect(findings).toEqual([]); + expect(store.readResource).not.toHaveBeenCalled(); + }); + + it('scans an auto-generated named value when an explicit override exists', async () => { + const store = createMockStore(); + store.readResource.mockResolvedValue({ + properties: { secret: true, value: REDACTION_MARKER }, + }); + + const autoGeneratedDescriptor: ResourceDescriptor = { + type: ResourceType.NamedValue, + nameParts: ['6a46928afa1f751d044d805e'], + }; + + // Override targets the auto-generated name but does not replace the value, + // so the marker remains and the resource is published — it must be scanned. + const configWithOverride: PublishConfig = { + ...testConfig, + overrides: { + namedValues: { + '6a46928afa1f751d044d805e': { properties: { tags: ['keep'] } }, + }, + }, + }; + + const findings = await scanForRedactionMarkers(store, configWithOverride, [ + autoGeneratedDescriptor, + ]); + + expect(findings).toHaveLength(1); + expect(findings[0].location).toBe('properties.value'); + }); + it('does not flag a marker that an override replaces with clean content', async () => { const store = createMockStore(); store.readContent.mockResolvedValue({ From aad40bfdade3ceb65d1d94dc5e932a08dacdf6e8 Mon Sep 17 00:00:00 2001 From: Elizabeth Maher Date: Thu, 2 Jul 2026 17:41:58 +0000 Subject: [PATCH 2/3] docs: add CHANGELOG entry for redaction-guard fix (#220) --- CHANGELOG.md | 1 + 1 file changed, 1 insertion(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index 35c934ed..6080b195 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -20,6 +20,7 @@ This project uses [Semantic Versioning](https://semver.org/) with alpha pre-rele ### Bug Fixes +- **Publish no longer aborts on auto-generated logger-credential named values** — the pre-flight secret-redaction guard scanned APIM auto-generated `Logger-Credentials--` named values (created to back Application Insights / Event Hub loggers) and aborted `apiops publish` on their `*** REDACTED ***` markers, even though the publisher already skips these resources (APIM recreates them when the referencing logger is published). The guard now mirrors the publisher and skips auto-generated named values that have no explicit override ([#220](https://github.com/Azure/apiops-cli/pull/220)) - **Removed stale `mcpServerInformation.json` references** — follow-up to #173, which moved MCP server configuration into `apiInformation.json`. Documentation (`docs/reference/artifact-format.md`, `docs/reference/resource-types.md`) and the `all-resource-types` integration test manifest now match the post-#173 artifact layout ([#205](https://github.com/Azure/apiops-cli/pull/205)) ## [0.3.0-alpha.0] — 2026-06-25 From c29bcb4f37ba3de63f8002e11cc12d3a66139d99 Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Thu, 2 Jul 2026 18:37:46 +0000 Subject: [PATCH 3/3] chore: revert CHANGELOG update --- CHANGELOG.md | 1 - 1 file changed, 1 deletion(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 6080b195..35c934ed 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -20,7 +20,6 @@ This project uses [Semantic Versioning](https://semver.org/) with alpha pre-rele ### Bug Fixes -- **Publish no longer aborts on auto-generated logger-credential named values** — the pre-flight secret-redaction guard scanned APIM auto-generated `Logger-Credentials--` named values (created to back Application Insights / Event Hub loggers) and aborted `apiops publish` on their `*** REDACTED ***` markers, even though the publisher already skips these resources (APIM recreates them when the referencing logger is published). The guard now mirrors the publisher and skips auto-generated named values that have no explicit override ([#220](https://github.com/Azure/apiops-cli/pull/220)) - **Removed stale `mcpServerInformation.json` references** — follow-up to #173, which moved MCP server configuration into `apiInformation.json`. Documentation (`docs/reference/artifact-format.md`, `docs/reference/resource-types.md`) and the `all-resource-types` integration test manifest now match the post-#173 artifact layout ([#205](https://github.com/Azure/apiops-cli/pull/205)) ## [0.3.0-alpha.0] — 2026-06-25