Files
rea/src/domain/explicitSensitiveFailure.ts
T
Ysopwnable 2bf3663f65 feat(browser): inspect historical web network captures (#992)
* feat(browser): inspect retained HAR and mitmproxy capture evidence

Add independent offline format adapters, inline CLI/MCP evidence, bounded
owned decoder processes, and real producer verification.

Refs #882

Assisted-by: Yukari (gensokyo-skills:yukari-boundary-analysis)
Assisted-by: Kogasa (gensokyo-skills:kogasa-surprise-testing)

* fix(browser): preserve capture boundaries during cancellation and decoding

Honor cancellation during owned-command cleanup, retain opaque HAR
extension content, and clean verifier resources on every failure path.

Refs #882

Assisted-by: Yukari (gensokyo-skills:yukari-boundary-analysis)
Assisted-by: Kogasa (gensokyo-skills:kogasa-surprise-testing)

* fix(browser): exclude sensitive capture fields across representations

Normalize known native transport URLs for both bytes and strings, including backups. Exclude explicitly sensitive text fields with null so redaction markers cannot reintroduce declared literals. Cover both provider identities in catalog conformance.

Refs #882

Assisted-by: Yukari (gensokyo-skills:yukari-boundary-analysis)
Assisted-by: Kogasa (gensokyo-skills:kogasa-surprise-testing)

* fix(browser): exclude sensitive capture identities and bound diagnostics

* fix(browser): validate excluded HAR subtrees before projection

* fix(browser): distinguish capture input limits from decoder failures

* fix(browser): preserve decoder failure categories

* fix(browser): validate excluded native credential values

* test(browser): report capture verifier assertions directly

* fix(browser): classify native representation failures precisely

* fix(browser): exclude URL userinfo after header whitespace

* fix(browser): preserve capture evidence and failure boundaries

* fix(browser): exclude sensitive literals from capture limitations

* test(browser): check limitation values without JSON syntax

* fix(browser): preserve acquisition integrity and exclude selection diagnostics

* fix(browser): preserve capture acquisition and declaration boundaries

* fix(mcp): project sensitive capture argument validation

* fix: preserve capture bytes and prelaunch cancellation

* fix(browser): exclude sensitive HAR record ancestry

* fix(browser): preserve transport URL and parameter exclusions

* fix(browser): preserve numeric redaction and canonical capture provenance

* fix(browser): cover derived capture values and ownership preparation deadlines

* fix(browser): exclude explicitly sensitive artifact digests

* chore: regenerate generated metadata

* fix(browser): reject lossy HAR member normalization

* test(browser): assert the advertised unsupported-profile outcome

* fix(browser): preserve JSON visitor nesting failure locations

* fix(browser): exclude explicitly sensitive native source ranges

---------

Co-authored-by: Kaoru0822 <208281322+kaoru0822-kitauji@users.noreply.github.com>
Co-authored-by: github-actions[bot] <41898282+github-actions[bot]@users.noreply.github.com>
2026-10-08 07:53:41 +08:00

137 lines
4.5 KiB
TypeScript

import type { AnalysisError } from "./analysisErrorBase.js";
import {
AnalysisAccessDeniedError,
AnalysisArtifactChangedError,
AnalysisInputError,
AnalysisOutputError,
AnalysisCapabilityUnavailableError,
} from "./analysisErrorCore.js";
import { redactExplicitText } from "./explicitSensitiveValues.js";
import type { JsonValue } from "./jsonValue.js";
import { ProviderAdapterError } from "./providerAdapterError.js";
import { ProviderCleanupError } from "./providerCleanupError.js";
/** Keep a real JSON-pointer ancestor when a property identity is explicitly excluded. */
export const redactExplicitPointer = (
pointer: string,
values: readonly string[],
): string => {
let parent = "";
for (const segment of pointer.split("/").slice(1)) {
const decoded = segment.replaceAll("~1", "/").replaceAll("~0", "~");
const child = `${parent}/${segment}`;
if (
values.some(
(literal) => decoded.includes(literal) || child.includes(literal),
)
)
return parent;
parent = child;
}
return parent;
};
/** Exclude explicit literals from common acquisition/decoder failures, preserving typed diagnostics. */
export const redactExplicitFailure = (
error: AnalysisError,
values: readonly string[],
): AnalysisError => {
if (values.length === 0) return error;
const text = (value: string): string => redactExplicitText(value, values);
const path = (
parts: readonly (string | number)[],
ordinaryNames: boolean,
): (string | number)[] => {
const retained: (string | number)[] = [];
for (const part of parts) {
if (typeof part !== "string") {
retained.push(part);
} else if (!ordinaryNames && part.startsWith("/")) {
const pointer = redactExplicitPointer(part, values);
retained.push(pointer);
if (pointer !== part) break;
} else {
if (values.some((literal) => part.includes(literal))) break;
retained.push(part);
}
}
return retained;
};
const diagnostics = (
value: Readonly<Record<string, JsonValue>>,
): Record<string, JsonValue> =>
Object.fromEntries(
Object.entries(value)
.filter(([key]) => !values.some((literal) => key.includes(literal)))
.map(([key, item]) => [key, visit(item)]),
);
const visit = (value: JsonValue): JsonValue => {
if (typeof value === "string") return text(value);
if (Array.isArray(value)) return value.map(visit);
if (typeof value === "object" && value !== null) return diagnostics(value);
return value;
};
if (error instanceof AnalysisAccessDeniedError)
return new AnalysisAccessDeniedError(
error.operation,
text(error.path),
error.systemCode,
{ cause: error },
);
if (error instanceof AnalysisArtifactChangedError)
return new AnalysisArtifactChangedError(
error.operation,
text(error.path),
text(error.reason),
{ cause: error },
);
if (error instanceof AnalysisInputError)
return new AnalysisInputError(
error.operation,
{ cause: error },
error.issues.map((issue) => ({
...issue,
// Unknown-argument paths contain literal field names, including names
// beginning with '/'; they are not producer JSON-pointer coordinates.
path: path(issue.path, issue.reason === "unknown_argument"),
...(issue.message === undefined
? {}
: { message: text(issue.message) }),
...(issue.expected === undefined
? {}
: { expected: visit(issue.expected) }),
})),
);
if (error instanceof AnalysisOutputError)
return new AnalysisOutputError(error.operation, text(error.reason), {
cause: error,
});
if (error instanceof AnalysisCapabilityUnavailableError)
return new AnalysisCapabilityUnavailableError(
error.providerId,
error.operation,
text(error.reason),
{
cause: error,
...(error.userMessage === undefined
? {}
: { userMessage: text(error.userMessage) }),
},
);
if (error instanceof ProviderCleanupError)
return new ProviderCleanupError(
error.providerId,
error.cleanupResources.map(text),
diagnostics(error.diagnostics ?? {}),
{ operation: error.operation, cause: error },
);
if (error instanceof ProviderAdapterError)
return new ProviderAdapterError(error.providerId, error.operation, {
cause: error,
...(error.diagnostics === undefined
? {}
: { diagnostics: diagnostics(error.diagnostics) }),
});
return error;
};