Skip to content

Commit 1fb3f34

Browse files
committed
refactoring policy redaction check and warning
1 parent 0a8ab60 commit 1fb3f34

5 files changed

Lines changed: 39 additions & 14 deletions

File tree

.squad/agents/apimexpert/history.md

Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -116,3 +116,17 @@ When comparing a *link-imported* API (e.g. Petstore via `swagger-link`/`openapi-
116116

117117
Round-trip comparison harness (`tests/integration/all-resource-types`) normalizes both via `RepresentationSchemaRefIgnoredProperties` and `ParameterIgnoredProperties`. Symptom if not stripped: every operation shows a `properties.request/responses/templateParameters` DIFF present-on-one-side-only.
118118

119+
### 2026-07-01: Overriding a policy to clear a redacted secret — possible but rarely the right fix
120+
121+
**Question that comes up:** when the extract-time secret redactor leaves `*** REDACTED ***` inline in a policy, can you clear the publish pre-flight guard by *overriding the policy* instead of fixing the source?
122+
123+
**Answer: yes, technically — but it's almost never the intended path.**
124+
125+
- All five policy types are wired into the override system (`src/services/override-merger.ts`): `ServicePolicy → policies`, `PolicyFragment → policyFragments` (direct); `ApiPolicy → apis.<api>.policies`, `ProductPolicy → products.<product>.policies` (child); `ApiOperationPolicy → apis.<api>.operations.<op>.policies` (grandchild).
126+
- The publish payload for a policy is `{ properties: { value, format } }`, and `applyOverrides` deep-merges `properties`, so an override supplying `properties.value` replaces the policy XML wholesale.
127+
- The pre-flight guard (`src/services/secret-redaction-guard.ts`) applies overrides **before** scanning for the marker — by design — so a policy override that yields clean content passes the check.
128+
129+
**Why it's the wrong tool for redacted secrets:**
130+
- The marker is inserted **inline** inside the XML (e.g. inside a `set-header` `<value>`), but overrides are **whole-value** replacements of `properties.value` — there is no inline/sub-string patch. You'd have to paste the entire policy XML (with the real secret) into a committed override file, re-introducing the plaintext secret that redaction removed.
131+
- Intended remediation: change the **source** policy to reference a named value (`{{my-secret}}`) so redaction never triggers, then supply the secret via a named-value override or Key Vault reference. The docs' "Gotcha: Redacted secrets" section (`docs/guides/environment-overrides.md`) only covers named values — there is no documented "override a redacted policy" workflow, reflecting this.
132+

src/services/api-extractor.ts

Lines changed: 4 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -14,7 +14,7 @@ import { ResourceType, RESOURCE_TYPE_METADATA } from '../models/resource-types.j
1414
import { FilterConfig } from '../models/config.js';
1515
import { shouldIncludeResource } from './filter-service.js';
1616
import { extractResourceType, ExtractedResource } from './resource-extractor.js';
17-
import { redactPolicySecrets, warnPolicySecretRedactions } from './secret-redactor.js';
17+
import { redactAndWarnPolicySecrets } from './secret-redactor.js';
1818
import { logger } from '../lib/logger.js';
1919
import { buildResourceLabel } from '../lib/resource-uri.js';
2020
import { getNamePart } from '../lib/resource-path.js';
@@ -364,8 +364,7 @@ async function extractApiPolicy(
364364
const policyContent = properties?.value as string | undefined;
365365

366366
if (policyContent) {
367-
const { redactedContent, findings } = redactPolicySecrets(policyContent);
368-
warnPolicySecretRedactions(policyDescriptor, findings);
367+
const redactedContent = redactAndWarnPolicySecrets(policyDescriptor, policyContent);
369368
await store.writeContent(
370369
outputDir,
371370
policyDescriptor,
@@ -423,8 +422,7 @@ async function extractApiOperations(
423422
const policyContent = properties?.value as string | undefined;
424423

425424
if (policyContent) {
426-
const { redactedContent, findings } = redactPolicySecrets(policyContent);
427-
warnPolicySecretRedactions(opPolicyDescriptor, findings);
425+
const redactedContent = redactAndWarnPolicySecrets(opPolicyDescriptor, policyContent);
428426
await store.writeContent(outputDir, opPolicyDescriptor, redactedContent, 'policy');
429427
operationPolicies.push({
430428
descriptor: opPolicyDescriptor,
@@ -536,8 +534,7 @@ async function extractGraphQLResolvers(
536534
const policyContent = props?.value as string | undefined;
537535

538536
if (policyContent) {
539-
const { redactedContent, findings } = redactPolicySecrets(policyContent);
540-
warnPolicySecretRedactions(resolverPolicyDescriptor, findings);
537+
const redactedContent = redactAndWarnPolicySecrets(resolverPolicyDescriptor, policyContent);
541538
await store.writeContent(outputDir, resolverPolicyDescriptor, redactedContent, 'policy');
542539
resolverPolicies.push({
543540
descriptor: resolverPolicyDescriptor,

src/services/extract-service.ts

Lines changed: 2 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -29,7 +29,7 @@ import { extractWorkspaces, WorkspaceExtractionResult } from './workspace-extrac
2929
import {
3030
findTransitiveDependencies,
3131
} from './transitive-resolver.js';
32-
import { redactPolicySecrets, warnPolicySecretRedactions } from './secret-redactor.js';
32+
import { redactAndWarnPolicySecrets } from './secret-redactor.js';
3333
import { logger } from '../lib/logger.js';
3434
import { buildResourceLabel } from '../lib/resource-uri.js';
3535
import { EXIT_SUCCESS, EXIT_PARTIAL, EXIT_FATAL } from '../lib/exit-codes.js';
@@ -402,8 +402,7 @@ async function extractServicePolicy(
402402
const policyContent = properties?.value as string | undefined;
403403

404404
if (policyContent) {
405-
const { redactedContent, findings } = redactPolicySecrets(policyContent);
406-
warnPolicySecretRedactions(descriptor, findings);
405+
const redactedContent = redactAndWarnPolicySecrets(descriptor, policyContent);
407406
await store.writeContent(outputDir, descriptor, redactedContent, 'policy');
408407
result.totalExtracted++;
409408
result.extractedDescriptors.push(descriptor);

src/services/product-extractor.ts

Lines changed: 2 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -11,7 +11,7 @@ import { ApimServiceContext, AssociationEntry, ResourceDescriptor } from '../mod
1111
import { ResourceType, RESOURCE_TYPE_METADATA } from '../models/resource-types.js';
1212
import { FilterConfig } from '../models/config.js';
1313
import { extractResourceName } from './resource-extractor.js';
14-
import { redactPolicySecrets, warnPolicySecretRedactions } from './secret-redactor.js';
14+
import { redactAndWarnPolicySecrets } from './secret-redactor.js';
1515
import { logger } from '../lib/logger.js';
1616
import { getNamePart } from '../lib/resource-path.js';
1717
import { isWorkspaceScope, extractNameFromLink, extractLinkTarget } from '../lib/workspace-link.js';
@@ -216,8 +216,7 @@ async function extractProductPolicy(
216216
const policyContent = properties?.value as string | undefined;
217217

218218
if (policyContent) {
219-
const { redactedContent, findings } = redactPolicySecrets(policyContent);
220-
warnPolicySecretRedactions(policyDescriptor, findings);
219+
const redactedContent = redactAndWarnPolicySecrets(policyDescriptor, policyContent);
221220
await store.writeContent(outputDir, policyDescriptor, redactedContent, 'policy');
222221
logger.debug(`Extracted policy for product "${getNamePart(productDescriptor.nameParts, 0)}"`);
223222
return redactedContent;

src/services/secret-redactor.ts

Lines changed: 17 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -250,10 +250,26 @@ export function redactPolicySecrets(
250250
};
251251
}
252252

253+
/**
254+
* Redact inline literal secrets in policy XML content and emit a warning log
255+
* for every finding. This is the entry point services should use so that
256+
* redaction and warning always happen together; the underlying
257+
* {@link redactPolicySecrets} (pure, exported) and `warnPolicySecretRedactions`
258+
* (private) helpers stay separate for testability.
259+
*/
260+
export function redactAndWarnPolicySecrets(
261+
descriptor: ResourceDescriptor,
262+
policyContent: string
263+
): string {
264+
const { redactedContent, findings } = redactPolicySecrets(policyContent);
265+
warnPolicySecretRedactions(descriptor, findings);
266+
return redactedContent;
267+
}
268+
253269
/**
254270
* Emit warning logs for policy secret redaction findings.
255271
*/
256-
export function warnPolicySecretRedactions(
272+
function warnPolicySecretRedactions(
257273
descriptor: ResourceDescriptor,
258274
findings: PolicySecretFinding[]
259275
): void {

0 commit comments

Comments
 (0)