@piagent/platform 1.5.4 → 1.6.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.md +136 -0
- package/README.md +9 -8
- package/README.vi.md +9 -5
- package/adapters/web-frontend/profile.json +2 -4
- package/architecture/layers.json +18 -3
- package/benchmarks/deep-logic-v1/SPECIALIST_PROTOCOL.vi.md +122 -0
- package/benchmarks/deep-logic-v1/grade.mjs +272 -10
- package/benchmarks/deep-logic-v1/project/apps/admin/src/billing-summary.js +3 -0
- package/benchmarks/deep-logic-v1/project/benchmark-contract/public-smoke.test.js +166 -0
- package/benchmarks/deep-logic-v1/project/package.json +1 -1
- package/benchmarks/deep-logic-v1/project/packages/billing/src/close-period.js +3 -0
- package/benchmarks/deep-logic-v1/project/packages/billing/src/plan-timeline.js +3 -0
- package/benchmarks/deep-logic-v1/project/test/smoke.test.js +7 -1
- package/benchmarks/deep-logic-v1/prompts/budgeted-context-graph.md +2 -0
- package/benchmarks/deep-logic-v1/prompts/fair-dependency-scheduler.md +2 -0
- package/benchmarks/deep-logic-v1/prompts/layered-policy-resolution.md +2 -0
- package/benchmarks/deep-logic-v1/prompts/resumable-stream-assembly.md +3 -1
- package/benchmarks/deep-logic-v1/prompts/revision-event-reconciliation.md +2 -0
- package/benchmarks/deep-logic-v1/prompts/temporal-usage-billing-close.md +123 -0
- package/benchmarks/deep-logic-v1/prompts/transactional-config-merge.md +2 -0
- package/benchmarks/deep-logic-v1/suite.json +51 -3
- package/benchmarks/deep-logic-v1/variant.mjs +22 -1
- package/benchmarks/production-v1/spend-control.v1.json +68 -0
- package/benchmarks/production-v1/suite.json +42 -3
- package/catalog/capabilities.json +6 -6
- package/docs/command-reference-vietnamese.md +6 -6
- package/docs/context-engine.md +7 -1
- package/docs/distribution-standard.md +7 -7
- package/docs/en/architecture.md +1 -0
- package/docs/languages.json +4 -4
- package/docs/model-options.md +8 -5
- package/docs/operator-manual-vietnamese.md +13 -11
- package/docs/project-adapters.md +2 -2
- package/docs/publishing-for-teams.md +9 -0
- package/docs/quality-benchmark.md +271 -62
- package/docs/release-install-policy.md +10 -9
- package/docs/runtime-policy-design.md +4 -4
- package/docs/runtime-quality-baseline.md +13 -6
- package/docs/security-threat-model.md +2 -2
- package/docs/task-lifecycle-vietnamese.md +2 -2
- package/docs/team-onboarding.md +2 -2
- package/docs/usage-observability.md +7 -2
- package/docs/vi/architecture.md +1 -0
- package/docs-site/architecture.html +1 -1
- package/docs-site/benchmark.html +1 -1
- package/docs-site/commands.html +1 -1
- package/docs-site/context-engine.html +1 -1
- package/docs-site/en/architecture.html +1 -1
- package/docs-site/en/benchmark.html +1 -1
- package/docs-site/en/commands.html +1 -1
- package/docs-site/en/context-engine.html +1 -1
- package/docs-site/en/index.html +1 -1
- package/docs-site/en/mcp-approval.html +1 -1
- package/docs-site/en/mcp-auth.html +1 -1
- package/docs-site/en/mcp-servers.html +1 -1
- package/docs-site/en/mcp.html +1 -1
- package/docs-site/en/permissions.html +1 -1
- package/docs-site/en/profiles.html +1 -1
- package/docs-site/en/quickstart.html +1 -1
- package/docs-site/en/runtime.html +1 -1
- package/docs-site/en/scope.html +1 -1
- package/docs-site/en/security.html +1 -1
- package/docs-site/en/team.html +1 -1
- package/docs-site/en/whats-new.html +1 -1
- package/docs-site/en/workflows.html +1 -1
- package/docs-site/index.html +1 -1
- package/docs-site/mcp-approval.html +1 -1
- package/docs-site/mcp-auth.html +1 -1
- package/docs-site/mcp-servers.html +1 -1
- package/docs-site/mcp.html +1 -1
- package/docs-site/permissions.html +1 -1
- package/docs-site/profiles.html +1 -1
- package/docs-site/quickstart.html +1 -1
- package/docs-site/runtime.html +1 -1
- package/docs-site/scope.html +1 -1
- package/docs-site/security.html +1 -1
- package/docs-site/team.html +1 -1
- package/docs-site/whats-new.html +1 -1
- package/docs-site/workflows.html +1 -1
- package/evals/fixtures/benchmark-assurance-evidence.invalid.json +1 -1
- package/evals/fixtures/benchmark-assurance-evidence.valid.json +1 -1
- package/evals/fixtures/piagent-webui/session-live-state-v1.invalid.json +18 -0
- package/evals/fixtures/piagent-webui/session-live-state-v1.valid.json +27 -0
- package/evals/fs4-readiness-matrix.v1.json +2 -2
- package/evals/fs5-causal-arm.v1.json +2 -2
- package/evals/fs5-pilot-protocol.v1.json +2 -2
- package/evals/fs5-pilot-protocol.v4.json +6 -5
- package/evals/fs5-pilot-protocol.v5.json +7 -6
- package/evals/private-holdout-v1/public-exposure.v1.json +1 -1
- package/package.json +3 -1
- package/packages/piagent-core/benchmark/benchmark-assurance.js +4 -1
- package/packages/piagent-core/benchmark/benchmark-bootstrap.js +91 -9
- package/packages/piagent-core/benchmark/benchmark-claim-restrictions.js +38 -7
- package/packages/piagent-core/benchmark/benchmark-cli.js +7 -7
- package/packages/piagent-core/benchmark/benchmark-codex.js +3 -1
- package/packages/piagent-core/benchmark/benchmark-comparison.js +271 -2
- package/packages/piagent-core/benchmark/benchmark-core.js +732 -661
- package/packages/piagent-core/benchmark/benchmark-execution-guard.js +7 -5
- package/packages/piagent-core/benchmark/benchmark-forensics.js +439 -4
- package/packages/piagent-core/benchmark/benchmark-host-readiness.js +639 -0
- package/packages/piagent-core/benchmark/benchmark-matrix.js +22 -4
- package/packages/piagent-core/benchmark/benchmark-normalized-cost.js +317 -0
- package/packages/piagent-core/benchmark/benchmark-preflight.js +3 -1
- package/packages/piagent-core/benchmark/benchmark-process.js +6 -5
- package/packages/piagent-core/benchmark/benchmark-provider-wire.js +126 -0
- package/packages/piagent-core/benchmark/benchmark-record-validation.js +183 -0
- package/packages/piagent-core/benchmark/benchmark-report.js +116 -12
- package/packages/piagent-core/benchmark/benchmark-resume-recovery.js +14 -0
- package/packages/piagent-core/benchmark/benchmark-runner-policy.js +26 -0
- package/packages/piagent-core/benchmark/benchmark-stage-diagnostic.js +782 -0
- package/packages/piagent-core/benchmark/benchmark-statistics.js +64 -0
- package/packages/piagent-core/benchmark/benchmark-stop-policy.js +8 -5
- package/packages/piagent-core/benchmark/benchmark-suite-identity.js +31 -0
- package/packages/piagent-core/benchmark/benchmark-suite-runtime.js +13 -9
- package/packages/piagent-core/benchmark/benchmark-suite.js +128 -2
- package/packages/piagent-core/benchmark/benchmark-summary-support.js +376 -0
- package/packages/piagent-core/benchmark/benchmark-timing-diagnostics.js +971 -0
- package/packages/piagent-core/benchmark/benchmark-usage.js +311 -0
- package/packages/piagent-core/benchmark/benchmark-workflow.js +166 -0
- package/packages/piagent-core/docs/context-efficiency-improvement-plan.md +1080 -0
- package/packages/piagent-core/extensions/acceptance-contract-semantics.js +83 -69
- package/packages/piagent-core/extensions/acceptance-error-classes.js +160 -0
- package/packages/piagent-core/extensions/acceptance-executable-evidence.js +12 -9
- package/packages/piagent-core/extensions/acceptance-receipt.js +34 -11
- package/packages/piagent-core/extensions/acceptance-state-machine-evidence.js +518 -0
- package/packages/piagent-core/extensions/acceptance-test-binding-evidence.js +60 -0
- package/packages/piagent-core/extensions/context-efficiency-metrics.js +302 -0
- package/packages/piagent-core/extensions/context-efficiency-report.js +167 -0
- package/packages/piagent-core/extensions/context-engine.js +148 -145
- package/packages/piagent-core/extensions/context-evidence.js +48 -0
- package/packages/piagent-core/extensions/context-import-links.js +296 -0
- package/packages/piagent-core/extensions/context-telemetry-integrity.js +65 -0
- package/packages/piagent-core/extensions/criterion-context-pack.js +422 -7
- package/packages/piagent-core/extensions/criterion-graph.js +78 -8
- package/packages/piagent-core/extensions/guard-types.ts +3 -0
- package/packages/piagent-core/extensions/javascript-regex-evidence.js +32 -0
- package/packages/piagent-core/extensions/piagent-guard.ts +250 -43
- package/packages/piagent-core/extensions/policy-core.js +1 -1
- package/packages/piagent-core/extensions/task-contract-view.js +17 -3
- package/packages/piagent-core/extensions/task-lifecycle.js +7 -2
- package/packages/piagent-core/extensions/task-state.js +40 -134
- package/packages/piagent-core/extensions/verification-intelligence.js +3 -1
- package/packages/piagent-core/extensions/workspace-evidence-roots.js +157 -0
- package/packages/piagent-core/package.json +1 -1
- package/packages/piagent-core/policies/base-policy.json +3 -0
- package/packages/piagent-core/runtime/context/context-delivery.ts +103 -0
- package/packages/piagent-core/runtime/context/context-delta-shadow.ts +53 -0
- package/packages/piagent-core/runtime/context/context-efficiency-format.ts +47 -0
- package/packages/piagent-core/runtime/context/context-evidence-qualification.ts +32 -0
- package/packages/piagent-core/runtime/context/prefix-telemetry.ts +47 -0
- package/packages/piagent-core/runtime/context/repository-memory-hints.ts +27 -0
- package/packages/piagent-core/runtime/hooks/agent-prompt-telemetry.ts +56 -0
- package/packages/piagent-core/runtime/hooks/agent-start-hook.ts +162 -102
- package/packages/piagent-core/runtime/hooks/completion-hook.ts +5 -6
- package/packages/piagent-core/runtime/hooks/input-hook.ts +11 -1
- package/packages/piagent-core/runtime/hooks/session-hooks.ts +22 -2
- package/packages/piagent-core/runtime/hooks/session-start-hook.ts +7 -2
- package/packages/piagent-core/runtime/hooks/tool-result-content-guards.ts +95 -0
- package/packages/piagent-core/runtime/hooks/tool-result-hook.ts +84 -102
- package/packages/piagent-core/runtime/inspection/activity-event-adapter.ts +9 -3
- package/packages/piagent-core/runtime/inspection/activity-recovery.ts +104 -0
- package/packages/piagent-core/runtime/inspection/approval-broker.ts +29 -5
- package/packages/piagent-core/runtime/inspection/task-compaction-history.ts +1 -1
- package/packages/piagent-core/runtime/inspection/task-handoff-history.ts +1 -1
- package/packages/piagent-core/runtime/inspection/tool-failure-classification.ts +53 -0
- package/packages/piagent-core/runtime/inspection/webui-snapshot.ts +34 -6
- package/packages/piagent-core/runtime/model/authenticated-catalog.ts +17 -9
- package/packages/piagent-core/runtime/model/openai-codex-reasoning.ts +83 -0
- package/packages/piagent-core/runtime/model/provider-wire-fingerprint.ts +238 -0
- package/packages/piagent-core/runtime/orchestration/role-policy.ts +1 -1
- package/packages/piagent-core/runtime/product/activity-inspector.ts +7 -2
- package/packages/piagent-core/runtime/product/efficiency-metrics.ts +4 -2
- package/packages/piagent-core/runtime/quality/exact-output-contract.ts +2 -1
- package/packages/piagent-core/runtime/quality/performance-assurance-signals.ts +23 -0
- package/packages/piagent-core/runtime/quality/performance-assurance.ts +6 -2
- package/packages/piagent-core/runtime/recovery/edit-recovery-context.ts +113 -0
- package/packages/piagent-core/runtime/recovery/edit-recovery-delivery.ts +42 -0
- package/packages/piagent-core/runtime/recovery/semantic-repair-handshake.ts +55 -2
- package/packages/piagent-core/runtime/recovery/semantic-repair-runtime.ts +55 -2
- package/packages/piagent-core/runtime/registration/context-commands.ts +66 -5
- package/packages/piagent-core/runtime/registration/policy-tools.ts +46 -11
- package/packages/piagent-core/runtime/registration/task-completion-tools.ts +49 -16
- package/packages/piagent-core/runtime/registration/task-start-guidance.ts +163 -0
- package/packages/piagent-core/runtime/registration/task-start-retry-helpers.ts +37 -0
- package/packages/piagent-core/runtime/registration/task-start-tool.ts +67 -62
- package/packages/piagent-core/runtime/session/runtime-state.ts +154 -2
- package/packages/piagent-core/runtime/session/system-prompt.ts +4 -4
- package/packages/piagent-core/runtime/tools/apply-patch-tool.ts +500 -0
- package/packages/piagent-core/runtime/trajectory/trajectory-runtime.ts +2 -1
- package/packages/piagent-core/runtime/workflows/task-intake.ts +43 -2
- package/packages/piagent-core/security/sensitive-data.js +13 -218
- package/packages/piagent-core/security/sensitive-project-file.js +42 -0
- package/packages/piagent-core/security/sensitive-source-expression.js +330 -0
- package/packages/piagent-core/security/sensitive-source-formats.js +394 -0
- package/packages/piagent-core/security/sensitive-text.js +234 -0
- package/packages/piagent-core/subagents/piagent-worker.md +1 -1
- package/packages/piagent-webui/dist/client/assets/{MarkdownMessage-rD9WJKxs.js → MarkdownMessage-CxeZqTte.js} +1 -1
- package/packages/piagent-webui/dist/client/assets/index-DynIMe8I.js +176 -0
- package/packages/piagent-webui/dist/client/index.html +1 -1
- package/packages/piagent-webui/gateway/gateway-events.ts +109 -5
- package/packages/piagent-webui/gateway/gateway-service.ts +3 -0
- package/packages/piagent-webui/gateway/gateway-session-stream.ts +93 -7
- package/packages/piagent-webui/gateway/session-command-controller.ts +18 -3
- package/packages/piagent-webui/gateway/session-effective-options.ts +47 -0
- package/packages/piagent-webui/gateway/session-live-state.ts +21 -0
- package/packages/piagent-webui/gateway/session-operation-start.ts +5 -1
- package/packages/piagent-webui/gateway/session-operation-watchdog.ts +158 -0
- package/packages/piagent-webui/gateway/session-runtime-factory.ts +45 -0
- package/packages/piagent-webui/gateway/session-runtime-supervisor.ts +139 -120
- package/packages/piagent-webui/server/loopback-server.ts +8 -0
- package/packages/piagent-webui/server/transcript-projection.ts +22 -4
- package/packages/piagent-webui/shared/text-visibility.ts +10 -0
- package/schemas/piagent-webui/catalog-v1.json +9 -0
- package/schemas/piagent-webui/gateway-protocol-v1.schema.json +53 -3
- package/schemas/piagent-webui/session-live-state-v1.schema.json +76 -0
- package/schemas/task-contract.schema.json +12 -1
- package/scripts/benchmark-runner-core.mjs +355 -359
- package/scripts/benchmark-runner-finalization.mjs +309 -0
- package/scripts/benchmark-runner-host-readiness.mjs +94 -0
- package/scripts/benchmark-runner-support.mjs +200 -0
- package/scripts/benchmark-session.mjs +95 -41
- package/scripts/configure-model-scope.sh +4 -3
- package/scripts/helper-evaluation.mjs +1 -1
- package/scripts/install-global.sh +2 -2
- package/scripts/pi-usage-history.mjs +37 -10
- package/scripts/piagent-dashboard.mjs +13 -1
- package/scripts/setup.sh +2 -2
- package/scripts/verify-local.sh +5 -1
- package/templates/global/settings.json +2 -1
- package/packages/piagent-webui/dist/client/assets/index-BZRFw1u6.js +0 -174
|
@@ -1,221 +1,16 @@
|
|
|
1
|
-
|
|
2
|
-
|
|
3
|
-
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
// is the whole of an `.npmrc`, and a `cat` of that file or a failing publish log
|
|
15
|
-
// carried the credential through unredacted. Every other provider here is
|
|
16
|
-
// recognised by the shape of its token; npm was the gap.
|
|
17
|
-
/\bnpm_[A-Za-z0-9]{30,}\b/g,
|
|
18
|
-
/\bAIza[0-9A-Za-z_-]{20,}\b/g,
|
|
19
|
-
/\beyJ[A-Za-z0-9_-]{8,}\.[A-Za-z0-9_-]{8,}\.[A-Za-z0-9_-]{8,}\b/g
|
|
20
|
-
];
|
|
21
|
-
|
|
22
|
-
const CONNECTION_URL_PATTERN = /\b((?:https?|ftp|postgres(?:ql)?|mysql|mariadb|mongodb(?:\+srv)?|redis):\/\/[^:\s/"']+:)([^@\s/"']+)(@[^\s"'<>]+)/gi;
|
|
23
|
-
const BEARER_PATTERN = /\b(Bearer\s+)([A-Za-z0-9._~+/=-]{20,})\b/gi;
|
|
24
|
-
const BASIC_AUTH_PATTERN = /\b(Basic\s+)([A-Za-z0-9+/]{12,}={0,2})(?=$|[\s,;}])/gi;
|
|
25
|
-
// The value is one whitespace-free token, not the rest of the line. Taking the
|
|
26
|
-
// rest of the line made the result depend on where the header sat among the
|
|
27
|
-
// arguments: `curl URL -H "Authorization: Token abc123"` yielded `abc123"`, short
|
|
28
|
-
// enough to keep, while moving the header before the URL swept the URL into the
|
|
29
|
-
// value and pushed it past the length bar. Same header, same credential, opposite
|
|
30
|
-
// outcome. Credentials carry no spaces, so stopping at the first one also keeps
|
|
31
|
-
// prose like "Authorization: not required for local runs" intact.
|
|
32
|
-
const AUTHORIZATION_HEADER_PATTERN = /\b(Authorization\s*:\s*)([A-Za-z][A-Za-z0-9_-]{0,31})(\s+)([^\s\r\n"'`]+)/gi;
|
|
33
|
-
|
|
34
|
-
// A credential passed as a command-line option, which is how most CLIs take one.
|
|
35
|
-
// The assignment patterns below require a key starting with a letter, so every
|
|
36
|
-
// `--token=...` and `--token ...` walked straight past them.
|
|
37
|
-
const CLI_OPTION_SECRET_PATTERN = /(^|[\s({[])(--[A-Za-z][A-Za-z0-9_.-]{0,80})(=|\s+)("[^"\r\n]*"|'[^'\r\n]*'|[^\s"'`;,)}]+)/g;
|
|
38
|
-
|
|
39
|
-
// Under one of these schemes the header carries an opaque credential, so its
|
|
40
|
-
// length says nothing. Anything else after `Authorization:` is more likely prose
|
|
41
|
-
// — "Authorization: not required" — and keeps the ambiguous-syntax length bar.
|
|
42
|
-
const AUTHORIZATION_SCHEMES = new Set([
|
|
43
|
-
"bearer", "basic", "token", "digest", "negotiate", "ntlm",
|
|
44
|
-
"oauth", "hawk", "signature", "apikey", "jwt", "aws4-hmac-sha256"
|
|
45
|
-
]);
|
|
46
|
-
const QUERY_SECRET_PATTERN = /([?&])([A-Za-z][A-Za-z0-9_.-]{0,80})(=)([^&#\s]+)/gi;
|
|
47
|
-
const DOUBLE_QUOTED_SECRET_ASSIGNMENT_PATTERN = /(^|[\s{[,(;])((?:"|')?[A-Za-z][A-Za-z0-9_.-]{0,80}(?:"|')?\s*)(:|=(?!=))\s*"((?:\\.|[^"\\\r\n])*)"/gim;
|
|
48
|
-
const SINGLE_QUOTED_SECRET_ASSIGNMENT_PATTERN = /(^|[\s{[,(;])((?:"|')?[A-Za-z][A-Za-z0-9_.-]{0,80}(?:"|')?\s*)(:|=(?!=))\s*'((?:\\.|[^'\\\r\n])*)'/gim;
|
|
49
|
-
const SECRET_ASSIGNMENT_PATTERN = /(^|[\s{[,(;])((?:"|')?[A-Za-z][A-Za-z0-9_.-]{0,80}(?:"|')?\s*)(:|=(?!=))\s*([^"'\s,;}]+)/gim;
|
|
50
|
-
const WHITESPACE_SECRET_ASSIGNMENT_PATTERN = /(^|[\s{[,(;])((?:"|')?(?:aws_)?secret_access_key(?:"|')?\s+)(["']?)([A-Za-z0-9/+=]{20,})/gim;
|
|
51
|
-
|
|
52
|
-
const SENSITIVE_KEY_TERMS = [
|
|
53
|
-
"api_key",
|
|
54
|
-
"token",
|
|
55
|
-
"password",
|
|
56
|
-
"passwd",
|
|
57
|
-
"pwd",
|
|
58
|
-
"secret",
|
|
59
|
-
"credential",
|
|
60
|
-
"client_secret",
|
|
61
|
-
"access_token",
|
|
62
|
-
"refresh_token",
|
|
63
|
-
"secret_access_key",
|
|
64
|
-
"passphrase",
|
|
65
|
-
"private_key",
|
|
66
|
-
"signing_key",
|
|
67
|
-
"authorization"
|
|
68
|
-
];
|
|
69
|
-
const SENSITIVE_PLURAL_KEYS = new Set([
|
|
70
|
-
"tokens",
|
|
71
|
-
"api_keys",
|
|
72
|
-
"passwords",
|
|
73
|
-
"secrets",
|
|
74
|
-
"credentials",
|
|
75
|
-
"client_secrets",
|
|
76
|
-
"access_tokens",
|
|
77
|
-
"refresh_tokens",
|
|
78
|
-
"session_tokens",
|
|
79
|
-
"passphrases",
|
|
80
|
-
"private_keys",
|
|
81
|
-
"signing_keys",
|
|
82
|
-
"authorizations"
|
|
83
|
-
]);
|
|
84
|
-
|
|
85
|
-
function normalizeSecretValue(value) {
|
|
86
|
-
return String(value ?? "")
|
|
87
|
-
.trim()
|
|
88
|
-
.replace(/^[`"']|[`"']$/g, "")
|
|
89
|
-
.replace(/[.;)]+$/g, "");
|
|
90
|
-
}
|
|
91
|
-
|
|
92
|
-
function patternMatches(pattern, value) {
|
|
93
|
-
pattern.lastIndex = 0;
|
|
94
|
-
return pattern.test(value);
|
|
95
|
-
}
|
|
96
|
-
|
|
97
|
-
function looksLikeKnownSecret(value) {
|
|
98
|
-
return TOKEN_PATTERNS.some((pattern) => patternMatches(pattern, value))
|
|
99
|
-
|| patternMatches(CONNECTION_URL_PATTERN, value)
|
|
100
|
-
|| patternMatches(BEARER_PATTERN, value);
|
|
101
|
-
}
|
|
102
|
-
|
|
103
|
-
function normalizeSecretKey(key) {
|
|
104
|
-
return String(key ?? "")
|
|
105
|
-
.trim()
|
|
106
|
-
.replace(/^["']|["']$/g, "")
|
|
107
|
-
.replace(/([a-z0-9])([A-Z])/g, "$1_$2")
|
|
108
|
-
.replace(/[^A-Za-z0-9]+/g, "_")
|
|
109
|
-
.replace(/^_+|_+$/g, "")
|
|
110
|
-
.toLowerCase();
|
|
111
|
-
}
|
|
112
|
-
|
|
113
|
-
function keyLooksSensitive(key) {
|
|
114
|
-
const normalized = normalizeSecretKey(key);
|
|
115
|
-
return SENSITIVE_PLURAL_KEYS.has(normalized) || SENSITIVE_KEY_TERMS.some((term) => normalized === term
|
|
116
|
-
|| normalized.endsWith(`_${term}`));
|
|
117
|
-
}
|
|
118
|
-
|
|
119
|
-
function valueLooksPlaceholder(value) {
|
|
120
|
-
const clean = normalizeSecretValue(value);
|
|
121
|
-
if (!clean) return true;
|
|
122
|
-
if (/^(?:null|undefined|none|false|true|0|""|'')$/i.test(clean)) return true;
|
|
123
|
-
if (/^<[^>\n]{1,80}>$/.test(clean)) return true;
|
|
124
|
-
if (/^\[REDACTED_SECRET\]$/i.test(clean)) return true;
|
|
125
|
-
if (/^(?:not-set|unset|placeholder|example|changeme|change-me|redacted|xxx|\*{3,})$/i.test(clean)) return true;
|
|
126
|
-
if (/^your[-_][a-z0-9][a-z0-9_-]*$/i.test(clean)) return true;
|
|
127
|
-
return false;
|
|
128
|
-
}
|
|
129
|
-
|
|
130
|
-
// Length is only evidence where the syntax is ambiguous, and `=` is not.
|
|
131
|
-
//
|
|
132
|
-
// A short value used to be kept in the clear whatever the syntax, so `TOKEN=hunter2`
|
|
133
|
-
// and `PASSWORD=short7` went into the ledger verbatim. Every name in
|
|
134
|
-
// SENSITIVE_KEY_TERMS states outright what it holds — there is no `key` or `id`
|
|
135
|
-
// among them — so under an `=`, or inside quotes, a non-placeholder value is the
|
|
136
|
-
// secret the key says it is, at any length.
|
|
137
|
-
//
|
|
138
|
-
// A bare `:` is the ambiguous case, because English uses it too: `password: short`
|
|
139
|
-
// and `The secret: always run tests` are prose, and `Authorization: Token abc123`
|
|
140
|
-
// puts the scheme name where a value would go. Those keep a length bar.
|
|
141
|
-
const SHORT_VALUE_MIN_LENGTH = 8;
|
|
142
|
-
const AMBIGUOUS_VALUE_MIN_LENGTH = 12;
|
|
143
|
-
|
|
144
|
-
function valueLooksSensitive(key, value, syntax = "unquoted-colon") {
|
|
145
|
-
const clean = normalizeSecretValue(value);
|
|
146
|
-
if (!clean || valueLooksPlaceholder(clean)) return false;
|
|
147
|
-
if (looksLikeKnownSecret(clean)) return true;
|
|
148
|
-
if (!keyLooksSensitive(key)) return false;
|
|
149
|
-
if (syntax !== "unquoted-colon") return true;
|
|
150
|
-
return clean.length >= (keyNamesASecretOutright(key) ? SHORT_VALUE_MIN_LENGTH : AMBIGUOUS_VALUE_MIN_LENGTH);
|
|
151
|
-
}
|
|
152
|
-
|
|
153
|
-
// Under a bare `:`, these names carry enough intent on their own to redact a short
|
|
154
|
-
// value; the broader terms wait for length before touching prose.
|
|
155
|
-
function keyNamesASecretOutright(key) {
|
|
156
|
-
const normalized = normalizeSecretKey(key);
|
|
157
|
-
return ["password", "passwd", "pwd", "secret", "credential", "passphrase", "private_key", "signing_key"]
|
|
158
|
-
.some((term) => normalized === term || normalized.endsWith(`_${term}`))
|
|
159
|
-
|| ["passwords", "secrets", "credentials", "passphrases", "private_keys", "signing_keys"].includes(normalized);
|
|
160
|
-
}
|
|
161
|
-
|
|
162
|
-
function assignmentSyntax(separator) {
|
|
163
|
-
return separator === "=" ? "assignment" : "unquoted-colon";
|
|
164
|
-
}
|
|
165
|
-
|
|
166
|
-
export function redactSensitiveText(input) {
|
|
167
|
-
if (typeof input !== "string") return { text: "", redacted: false };
|
|
168
|
-
let text = input;
|
|
169
|
-
|
|
170
|
-
text = text.replace(CONNECTION_URL_PATTERN, (_match, prefix, _password, suffix) => `${prefix}${REDACTION}${suffix}`);
|
|
171
|
-
text = text.replace(BEARER_PATTERN, (_match, prefix) => `${prefix}${REDACTION}`);
|
|
172
|
-
text = text.replace(BASIC_AUTH_PATTERN, (_match, prefix) => `${prefix}${REDACTION}`);
|
|
173
|
-
text = text.replace(AUTHORIZATION_HEADER_PATTERN, (match, prefix, scheme, spacing, value) => {
|
|
174
|
-
const syntax = AUTHORIZATION_SCHEMES.has(scheme.toLowerCase()) ? "assignment" : "unquoted-colon";
|
|
175
|
-
if (!valueLooksSensitive("authorization", value, syntax)) return match;
|
|
176
|
-
return `${prefix}${scheme}${spacing}${REDACTION}`;
|
|
177
|
-
});
|
|
178
|
-
|
|
179
|
-
// Over-redaction is the safe direction here: a flag whose name says "secret"
|
|
180
|
-
// and whose argument is a plain name — `docker service create --secret build` —
|
|
181
|
-
// loses that name from the record and cannot serve as verify evidence. Reading
|
|
182
|
-
// the name as the credential it is called is the cheaper mistake.
|
|
183
|
-
text = text.replace(CLI_OPTION_SECRET_PATTERN, (match, prefix, flag, separator, value) => {
|
|
184
|
-
// A following option is the next flag, not this flag's argument.
|
|
185
|
-
if (separator !== "=" && value.startsWith("-")) return match;
|
|
186
|
-
if (!keyLooksSensitive(flag) || !valueLooksSensitive(flag, value, "assignment")) return match;
|
|
187
|
-
return `${prefix}${flag}${separator === "=" ? "= " : " "}${REDACTION}`;
|
|
188
|
-
});
|
|
189
|
-
|
|
190
|
-
for (const pattern of TOKEN_PATTERNS) text = text.replace(pattern, REDACTION);
|
|
191
|
-
|
|
192
|
-
text = text.replace(QUERY_SECRET_PATTERN, (match, prefix, key, separator, value) => {
|
|
193
|
-
if (!keyLooksSensitive(key) || !valueLooksSensitive(key, value, "assignment")) return match;
|
|
194
|
-
return `${prefix}${key}${separator}${REDACTION}`;
|
|
195
|
-
});
|
|
196
|
-
|
|
197
|
-
text = text.replace(DOUBLE_QUOTED_SECRET_ASSIGNMENT_PATTERN, (match, prefix, key, separator, value) => {
|
|
198
|
-
if (!keyLooksSensitive(key) || !valueLooksSensitive(key, value, "quoted")) return match;
|
|
199
|
-
return `${prefix}${key}${separator} "${REDACTION}"`;
|
|
200
|
-
});
|
|
201
|
-
|
|
202
|
-
text = text.replace(SINGLE_QUOTED_SECRET_ASSIGNMENT_PATTERN, (match, prefix, key, separator, value) => {
|
|
203
|
-
if (!keyLooksSensitive(key) || !valueLooksSensitive(key, value, "quoted")) return match;
|
|
204
|
-
return `${prefix}${key}${separator} '${REDACTION}'`;
|
|
205
|
-
});
|
|
206
|
-
|
|
207
|
-
text = text.replace(SECRET_ASSIGNMENT_PATTERN, (match, prefix, key, separator, value) => {
|
|
208
|
-
if (!keyLooksSensitive(key) || !valueLooksSensitive(key, value, assignmentSyntax(separator))) return match;
|
|
209
|
-
return `${prefix}${key}${separator} ${REDACTION}`;
|
|
210
|
-
});
|
|
211
|
-
|
|
212
|
-
text = text.replace(WHITESPACE_SECRET_ASSIGNMENT_PATTERN, (match, prefix, key, quote, value) => {
|
|
213
|
-
if (!valueLooksSensitive(key, value)) return match;
|
|
214
|
-
return `${prefix}${key}${quote}${REDACTION}`;
|
|
215
|
-
});
|
|
216
|
-
|
|
217
|
-
return { text, redacted: text !== input };
|
|
218
|
-
}
|
|
1
|
+
import {
|
|
2
|
+
REDACTION,
|
|
3
|
+
keyLooksSensitive,
|
|
4
|
+
redactSensitiveText,
|
|
5
|
+
valueLooksSensitive
|
|
6
|
+
} from "./sensitive-text.js";
|
|
7
|
+
|
|
8
|
+
export { redactSensitiveText } from "./sensitive-text.js";
|
|
9
|
+
export {
|
|
10
|
+
redactSensitiveShellSourceText,
|
|
11
|
+
redactSensitiveSourceText
|
|
12
|
+
} from "./sensitive-source-formats.js";
|
|
13
|
+
export { redactSensitiveProjectFileText } from "./sensitive-project-file.js";
|
|
219
14
|
|
|
220
15
|
export function containsSensitiveText(input) {
|
|
221
16
|
return typeof input === "string" && redactSensitiveText(input).redacted;
|
|
@@ -0,0 +1,42 @@
|
|
|
1
|
+
import { redactSensitiveText } from "./sensitive-text.js";
|
|
2
|
+
import {
|
|
3
|
+
redactSensitiveCssText,
|
|
4
|
+
redactSensitiveMarkupText,
|
|
5
|
+
redactSensitiveObjectiveCText,
|
|
6
|
+
redactSensitiveShellSourceText,
|
|
7
|
+
redactSensitiveSourceText,
|
|
8
|
+
redactSensitiveYamlText,
|
|
9
|
+
redactSourceText
|
|
10
|
+
} from "./sensitive-source-formats.js";
|
|
11
|
+
|
|
12
|
+
const PROJECT_SOURCE_CODE_PATH = /\.(?:[cm]?[jt]sx?|py|rb|php|java|kt|kts|swift|go|rs|c|cc|cpp|cxx|h|hh|hpp|hxx|cs|scala|lua|r|dart|ex|exs|erl|hrl|fs|fsx|vb|sql|vue|svelte)$/i;
|
|
13
|
+
const PROJECT_SHELL_SOURCE_PATH = /\.(?:sh|bash|zsh|fish)$/i;
|
|
14
|
+
const PROJECT_HASH_COMMENT_SOURCE_PATH = /\.(?:py|rb|php|r|ex|exs)$/i;
|
|
15
|
+
const PROJECT_YAML_PATH = /\.ya?ml$/i;
|
|
16
|
+
const PROJECT_MARKUP_PATH = /\.(?:html?|xml|svg)$/i;
|
|
17
|
+
const PROJECT_CSS_PATH = /\.(?:css|scss)$/i;
|
|
18
|
+
const PROJECT_OBJECTIVE_C_PATH = /\.mm?$/i;
|
|
19
|
+
|
|
20
|
+
export function redactSensitiveProjectFileText(filePath, input) {
|
|
21
|
+
const source = typeof input === "string" ? input : "";
|
|
22
|
+
const result = PROJECT_SHELL_SOURCE_PATH.test(String(filePath ?? ""))
|
|
23
|
+
? redactSensitiveShellSourceText(source)
|
|
24
|
+
: PROJECT_YAML_PATH.test(String(filePath ?? ""))
|
|
25
|
+
? redactSensitiveYamlText(source)
|
|
26
|
+
: PROJECT_MARKUP_PATH.test(String(filePath ?? ""))
|
|
27
|
+
? redactSensitiveMarkupText(source)
|
|
28
|
+
: PROJECT_CSS_PATH.test(String(filePath ?? ""))
|
|
29
|
+
? redactSensitiveCssText(source)
|
|
30
|
+
: PROJECT_OBJECTIVE_C_PATH.test(String(filePath ?? ""))
|
|
31
|
+
? redactSensitiveObjectiveCText(source)
|
|
32
|
+
: PROJECT_SOURCE_CODE_PATH.test(String(filePath ?? ""))
|
|
33
|
+
? redactSourceText(source, {
|
|
34
|
+
shellOnly: false,
|
|
35
|
+
hashComments: PROJECT_HASH_COMMENT_SOURCE_PATH.test(String(filePath ?? ""))
|
|
36
|
+
})
|
|
37
|
+
: redactSensitiveText(source);
|
|
38
|
+
return {
|
|
39
|
+
...result,
|
|
40
|
+
lineCountPreserved: (source.match(/\n/g) ?? []).length === (result.text.match(/\n/g) ?? []).length
|
|
41
|
+
};
|
|
42
|
+
}
|
|
@@ -0,0 +1,330 @@
|
|
|
1
|
+
import {
|
|
2
|
+
REDACTION,
|
|
3
|
+
SENSITIVE_KEY_TERMS,
|
|
4
|
+
SENSITIVE_PLURAL_KEYS,
|
|
5
|
+
keyLooksSensitive,
|
|
6
|
+
looksLikeKnownSecret,
|
|
7
|
+
normalizeSecretKey,
|
|
8
|
+
redactSensitiveTextInternal,
|
|
9
|
+
valueLooksPlaceholder
|
|
10
|
+
} from "./sensitive-text.js";
|
|
11
|
+
|
|
12
|
+
const SOURCE_ASSIGNMENT_START_PATTERN = /(^|[\s{[(;,])((?:(?:const|let|var)\s+)?(#?[$@%]?[A-Za-z_$][A-Za-z0-9_$]*)\s*(?:>>>=|<<=|>>=|\|\|=|\?\?=|&&=|\+=|-=|\*=|\/=|%=|&=|\|=|\^=|<-|:=|=)\s*)/g;
|
|
13
|
+
const SOURCE_TYPED_DECLARATION_START_PATTERN = /(^|[\s{[(;,])((?:const|var)\s+(#?[$@%]?[A-Za-z_$][A-Za-z0-9_$]*)\s+(?:\*|\[\])*[A-Za-z_$][A-Za-z0-9_$.[\]*]*\s*=\s*)/g;
|
|
14
|
+
const SOURCE_COMPOUND_ASSIGNMENT_START_PATTERN = /(^|[\s{[(;,])((#?[$@%]?[A-Za-z_$][A-Za-z0-9_$]*(?:(?:\?\.|\.|->|::)#?[A-Za-z_$][A-Za-z0-9_$]*|\[(?:"[^"\r\n]*"|'[^'\r\n]*'|[A-Za-z_$][A-Za-z0-9_$]*)\])+\s*(?:>>>=|<<=|>>=|\|\|=|\?\?=|&&=|\+=|-=|\*=|\/=|%=|&=|\|=|\^=|<-|:=|=)\s*))/g;
|
|
15
|
+
const SOURCE_BRACE_INITIALIZER_START_PATTERN = /(^|[\s{[(;,])((?:(?:const\s+)?[A-Za-z_$][A-Za-z0-9_$:<>,*&[\]]*\s+)+(#?[$@%]?[A-Za-z_$][A-Za-z0-9_$]*)\s*\{\s*)/g;
|
|
16
|
+
const SOURCE_PREPROCESSOR_DEFINE_START_PATTERN = /(^|[\r\n])([ \t]*#\s*define\s+([A-Za-z_][A-Za-z0-9_]*)\s+)/g;
|
|
17
|
+
const SOURCE_PROPERTY_START_PATTERN = /(^|[\s{[(;,])((?:["']?)([A-Za-z_$][A-Za-z0-9_$]*)(?:["']?)\s*:(?!=)\s*)/g;
|
|
18
|
+
const SOURCE_COMPUTED_PROPERTY_START_PATTERN = /(^|[\s{[(;,])(\[\s*["']([A-Za-z_$][A-Za-z0-9_$]*)["']\s*\]\s*:(?!=)\s*)/g;
|
|
19
|
+
const SOURCE_TEMPLATE_DOUBLE_BINDING_PATTERN = /((?::|v-bind:)([A-Za-z_$][A-Za-z0-9_$.-]*)\s*=\s*)"((?:\\.|[^"\\\r\n])*)"/g;
|
|
20
|
+
const SOURCE_TEMPLATE_SINGLE_BINDING_PATTERN = /((?::|v-bind:)([A-Za-z_$][A-Za-z0-9_$.-]*)\s*=\s*)'((?:\\.|[^'\\\r\n])*)'/g;
|
|
21
|
+
const SHELL_REFERENCE_PATTERN = /^\$(?:[A-Za-z_][A-Za-z0-9_]*|\{[A-Za-z_][A-Za-z0-9_]*\}|\([A-Za-z0-9_./:-]+(?:\s+\$[A-Za-z_][A-Za-z0-9_]*)*\))$/;
|
|
22
|
+
const SOURCE_CALL_START_PATTERN = /(^|[^A-Za-z0-9_$])([$@%]?[A-Za-z_$][A-Za-z0-9_$]*(?:(?:\.|->|::)[A-Za-z_$][A-Za-z0-9_$]*)*)\s*\(/g;
|
|
23
|
+
|
|
24
|
+
function balancedSourceExpression(value) {
|
|
25
|
+
const stack = [];
|
|
26
|
+
const pairs = { ")": "(", "]": "[", "}": "{" };
|
|
27
|
+
for (const character of value) {
|
|
28
|
+
if (character === "(" || character === "[" || character === "{") stack.push(character);
|
|
29
|
+
else if (character === ")" || character === "]" || character === "}") {
|
|
30
|
+
if (stack.pop() !== pairs[character]) return false;
|
|
31
|
+
}
|
|
32
|
+
}
|
|
33
|
+
return stack.length === 0;
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
function maskSourceLookupStrings(value) {
|
|
37
|
+
let valid = true;
|
|
38
|
+
const text = value.replace(/"((?:\\.|[^"\\\r\n])*)"|'((?:\\.|[^'\\\r\n])*)'/g, (match, doubleQuoted, singleQuoted, offset) => {
|
|
39
|
+
const literal = doubleQuoted ?? singleQuoted ?? "";
|
|
40
|
+
const normalized = normalizeSecretKey(literal);
|
|
41
|
+
const exactLookupKey = SENSITIVE_KEY_TERMS.includes(normalized) || SENSITIVE_PLURAL_KEYS.has(normalized);
|
|
42
|
+
const environmentLookupKey = /^[A-Z][A-Z0-9_]{1,79}$/.test(literal) && keyLooksSensitive(literal);
|
|
43
|
+
const before = value.slice(0, offset).trimEnd();
|
|
44
|
+
const after = value.slice(offset + match.length).trimStart();
|
|
45
|
+
const bracketLookup = /\[@?$/.test(before) && after.startsWith("]");
|
|
46
|
+
const accessorLookup = /(?:(?:\.|->|::)(?:get|lookup|valueForKey)|\b(?:getenv|lookup))\s*\($/i.test(before)
|
|
47
|
+
&& /^(?:,|\))/.test(after);
|
|
48
|
+
const objectiveAccessorLookup = /(?:objectForKey|valueForKey|forKey)\s*:\s*@?$/i.test(before)
|
|
49
|
+
&& /^(?:\]|\s)/.test(after);
|
|
50
|
+
if ((!exactLookupKey && !environmentLookupKey) || (!bracketLookup && !accessorLookup && !objectiveAccessorLookup)) valid = false;
|
|
51
|
+
return "PIAGENT_LOOKUP_KEY";
|
|
52
|
+
});
|
|
53
|
+
if (!valid || /["'`]/.test(text)) return undefined;
|
|
54
|
+
return text;
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
function hasStandaloneNumericSourceLiteral(value) {
|
|
58
|
+
const pattern = /(^|[^A-Za-z0-9_$])(?:0[xX][0-9A-Fa-f]+|0[bB][01]+|0[oO][0-7]+|(?:\d+(?:\.\d*)?|\.\d+)(?:[eE][+-]?\d+)?n?)(?=$|[^A-Za-z0-9_$])/g;
|
|
59
|
+
for (let match = pattern.exec(value); match; match = pattern.exec(value)) {
|
|
60
|
+
const literalStart = match.index + match[1].length;
|
|
61
|
+
const literalEnd = pattern.lastIndex;
|
|
62
|
+
const before = value.slice(0, literalStart).trimEnd();
|
|
63
|
+
const after = value.slice(literalEnd).trimStart();
|
|
64
|
+
const bracketIndex = before.endsWith("[") && after.startsWith("]")
|
|
65
|
+
&& /[A-Za-z0-9_$)\]]\s*\[$/.test(before);
|
|
66
|
+
if (!bracketIndex) return true;
|
|
67
|
+
}
|
|
68
|
+
return false;
|
|
69
|
+
}
|
|
70
|
+
|
|
71
|
+
function hasStandaloneKeywordSourceLiteral(value) {
|
|
72
|
+
const pattern = /(^|[^A-Za-z0-9_$])(?:null|undefined|true|false|nil|none|yes|no)(?=$|[^A-Za-z0-9_$])/gi;
|
|
73
|
+
for (let match = pattern.exec(value); match; match = pattern.exec(value)) {
|
|
74
|
+
const literalStart = match.index + match[1].length;
|
|
75
|
+
const before = value.slice(0, literalStart).trimEnd();
|
|
76
|
+
if (!before.endsWith(".") && !before.endsWith("->") && !before.endsWith("::")) return true;
|
|
77
|
+
}
|
|
78
|
+
return false;
|
|
79
|
+
}
|
|
80
|
+
|
|
81
|
+
function sourceReference(value, shellOnly) {
|
|
82
|
+
const clean = String(value ?? "").trim();
|
|
83
|
+
if (!clean) return false;
|
|
84
|
+
if (shellOnly) return !redactSensitiveTextInternal(clean).redacted && SHELL_REFERENCE_PATTERN.test(clean);
|
|
85
|
+
if (looksLikeKnownSecret(clean)) return false;
|
|
86
|
+
// Ruby percent literals and heredoc openers are literal syntax, not dynamic
|
|
87
|
+
// references. Never shield their contents from the source redactor.
|
|
88
|
+
if (/^%(?:q|Q|w|W|i|I|x|r|s)?[({[<]/.test(clean) || /^<<[-~]?/.test(clean)) return false;
|
|
89
|
+
const masked = maskSourceLookupStrings(clean);
|
|
90
|
+
if (!masked || !/^(?:await\s+)?[$@%]?[A-Za-z_]/.test(masked)
|
|
91
|
+
|| !/^[A-Za-z0-9_$@%?.()[\]{},!:\s|&+*/<>=-]+$/.test(masked)
|
|
92
|
+
|| !balancedSourceExpression(masked)
|
|
93
|
+
|| hasStandaloneNumericSourceLiteral(masked)
|
|
94
|
+
|| hasStandaloneKeywordSourceLiteral(masked)) return false;
|
|
95
|
+
return true;
|
|
96
|
+
}
|
|
97
|
+
|
|
98
|
+
function sourceExpressionEnd(segment, start) {
|
|
99
|
+
const stack = [];
|
|
100
|
+
const pairs = { ")": "(", "]": "[", "}": "{" };
|
|
101
|
+
let quote = "", escaped = false, sawValue = false;
|
|
102
|
+
for (let index = start; index < segment.length; index += 1) {
|
|
103
|
+
const character = segment[index];
|
|
104
|
+
if (quote) {
|
|
105
|
+
if (escaped) escaped = false;
|
|
106
|
+
else if (character === "\\") escaped = true;
|
|
107
|
+
else if (character === quote) quote = "";
|
|
108
|
+
continue;
|
|
109
|
+
}
|
|
110
|
+
if (character === "\"" || character === "'" || character === "`") {
|
|
111
|
+
quote = character;
|
|
112
|
+
sawValue = true;
|
|
113
|
+
continue;
|
|
114
|
+
}
|
|
115
|
+
if (character === "(" || character === "[" || character === "{") stack.push(character);
|
|
116
|
+
else if (character === ")" || character === "]" || character === "}") {
|
|
117
|
+
if (stack.length === 0) return index;
|
|
118
|
+
if (stack.at(-1) === pairs[character]) stack.pop();
|
|
119
|
+
} else if (stack.length === 0 && (character === ";" || character === ",")) {
|
|
120
|
+
return index;
|
|
121
|
+
} else if (stack.length === 0 && (character === "\r" || character === "\n")) {
|
|
122
|
+
if (character === "\r" && segment[index + 1] === "\n") continue;
|
|
123
|
+
const before = segment.slice(start, index).trimEnd();
|
|
124
|
+
const after = segment.slice(index + 1).trimStart();
|
|
125
|
+
const trailingContinuation = /(?:\b(?:await|return|yield|throw|new)|\?\.|\?\?|\|\||&&|[.?:+*/%&|=,<>{[(\\-])$/.test(before);
|
|
126
|
+
const leadingContinuation = /^(?:\?\.|\?\?|\|\||&&|[([.'"`?:+*/%&|,<>=-])/.test(after);
|
|
127
|
+
if (sawValue && !trailingContinuation && !leadingContinuation) return index;
|
|
128
|
+
} else if (!/\s/.test(character)) {
|
|
129
|
+
sawValue = true;
|
|
130
|
+
}
|
|
131
|
+
}
|
|
132
|
+
return segment.length;
|
|
133
|
+
}
|
|
134
|
+
|
|
135
|
+
function rewriteSensitiveStarts(segment, pattern, { allowDynamic, shellOnly, placeholder, acceptMatch, reference = sourceReference }) {
|
|
136
|
+
let output = "", cursor = 0;
|
|
137
|
+
pattern.lastIndex = 0;
|
|
138
|
+
for (let match = pattern.exec(segment); match; match = pattern.exec(segment)) {
|
|
139
|
+
if (match.index < cursor || !keyLooksSensitive(match[3]) || (acceptMatch && !acceptMatch(match))) continue;
|
|
140
|
+
const valueStart = pattern.lastIndex;
|
|
141
|
+
const valueEnd = sourceExpressionEnd(segment, valueStart);
|
|
142
|
+
const rawExpression = segment.slice(valueStart, valueEnd);
|
|
143
|
+
const expression = rawExpression.trim();
|
|
144
|
+
const complete = segment.slice(match.index, valueEnd);
|
|
145
|
+
output += segment.slice(cursor, match.index);
|
|
146
|
+
const trailingHorizontalWhitespace = rawExpression.match(/[ \t]+$/)?.[0] ?? "";
|
|
147
|
+
const lineEndings = (rawExpression.match(/\r\n|\n|\r/g) ?? []).join("");
|
|
148
|
+
output += allowDynamic && reference(expression, shellOnly)
|
|
149
|
+
? placeholder(complete)
|
|
150
|
+
: `${match[1]}${match[2]}${REDACTION}${lineEndings}${trailingHorizontalWhitespace}`;
|
|
151
|
+
cursor = valueEnd;
|
|
152
|
+
pattern.lastIndex = valueEnd;
|
|
153
|
+
}
|
|
154
|
+
return `${output}${segment.slice(cursor)}`;
|
|
155
|
+
}
|
|
156
|
+
|
|
157
|
+
function callCloseIndex(segment, openIndex) {
|
|
158
|
+
let depth = 0, quote = "", escaped = false;
|
|
159
|
+
for (let index = openIndex; index < segment.length; index += 1) {
|
|
160
|
+
const character = segment[index];
|
|
161
|
+
if (quote) {
|
|
162
|
+
if (escaped) escaped = false;
|
|
163
|
+
else if (character === "\\") escaped = true;
|
|
164
|
+
else if (character === quote) quote = "";
|
|
165
|
+
continue;
|
|
166
|
+
}
|
|
167
|
+
if (character === "\"" || character === "'" || character === "`") quote = character;
|
|
168
|
+
else if (character === "(") depth += 1;
|
|
169
|
+
else if (character === ")") {
|
|
170
|
+
depth -= 1;
|
|
171
|
+
if (depth === 0) return index;
|
|
172
|
+
}
|
|
173
|
+
}
|
|
174
|
+
return -1;
|
|
175
|
+
}
|
|
176
|
+
|
|
177
|
+
function callArgumentRanges(segment, start, end) {
|
|
178
|
+
const ranges = [];
|
|
179
|
+
let cursor = start, quote = "", escaped = false;
|
|
180
|
+
const stack = [];
|
|
181
|
+
const pairs = { ")": "(", "]": "[", "}": "{" };
|
|
182
|
+
for (let index = start; index <= end; index += 1) {
|
|
183
|
+
const character = index === end ? "," : segment[index];
|
|
184
|
+
if (quote) {
|
|
185
|
+
if (escaped) escaped = false;
|
|
186
|
+
else if (character === "\\") escaped = true;
|
|
187
|
+
else if (character === quote) quote = "";
|
|
188
|
+
continue;
|
|
189
|
+
}
|
|
190
|
+
if (character === "\"" || character === "'" || character === "`") quote = character;
|
|
191
|
+
else if (character === "(" || character === "[" || character === "{") stack.push(character);
|
|
192
|
+
else if (character === ")" || character === "]" || character === "}") {
|
|
193
|
+
if (stack.at(-1) === pairs[character]) stack.pop();
|
|
194
|
+
} else if (character === "," && stack.length === 0) {
|
|
195
|
+
ranges.push({ start: cursor, end: index });
|
|
196
|
+
cursor = index + 1;
|
|
197
|
+
}
|
|
198
|
+
}
|
|
199
|
+
return ranges;
|
|
200
|
+
}
|
|
201
|
+
|
|
202
|
+
function quotedSensitiveLookupKey(value) {
|
|
203
|
+
const match = /^\s*(["'])((?:\\.|(?!\1)[\s\S])*)\1\s*$/.exec(value);
|
|
204
|
+
if (!match) return false;
|
|
205
|
+
const normalized = normalizeSecretKey(match[2]);
|
|
206
|
+
return SENSITIVE_KEY_TERMS.includes(normalized) || SENSITIVE_PLURAL_KEYS.has(normalized)
|
|
207
|
+
|| (/^[A-Z][A-Z0-9_]{1,79}$/.test(match[2]) && keyLooksSensitive(match[2]));
|
|
208
|
+
}
|
|
209
|
+
|
|
210
|
+
function setterValueArgument(callee, argumentValues) {
|
|
211
|
+
const name = callee.split(/\.|->|::/).at(-1) ?? "";
|
|
212
|
+
const direct = /^(?:set|put|store|update|write|add|assign)(.+)$/i.exec(name);
|
|
213
|
+
if (direct && keyLooksSensitive(direct[1]) && argumentValues.length >= 1) return 0;
|
|
214
|
+
if (["set", "put", "store", "update", "write", "add", "assign"].includes(name.toLowerCase())
|
|
215
|
+
&& argumentValues.length >= 2 && quotedSensitiveLookupKey(argumentValues[0])) return 1;
|
|
216
|
+
return -1;
|
|
217
|
+
}
|
|
218
|
+
|
|
219
|
+
function couldSetSensitiveValue(callee, segment, openIndex) {
|
|
220
|
+
const name = callee.split(/\.|->|::/).at(-1) ?? "";
|
|
221
|
+
const direct = /^(?:set|put|store|update|write|add|assign)(.+)$/i.exec(name);
|
|
222
|
+
if (direct && keyLooksSensitive(direct[1])) return true;
|
|
223
|
+
if (!["set", "put", "store", "update", "write", "add", "assign"].includes(name.toLowerCase())) return false;
|
|
224
|
+
|
|
225
|
+
// Generic setters can only name a sensitive destination through their first
|
|
226
|
+
// quoted argument. Check that argument before looking for the matching close.
|
|
227
|
+
// This keeps deeply nested non-sensitive `set(...)` calls from each rescanning
|
|
228
|
+
// the entire suffix of the source file.
|
|
229
|
+
const firstArgument = /^\s*((?:"(?:\\.|[^"\\\r\n])*"|'(?:\\.|[^'\\\r\n])*'))\s*,/.exec(segment.slice(openIndex + 1));
|
|
230
|
+
return Boolean(firstArgument && quotedSensitiveLookupKey(firstArgument[1]));
|
|
231
|
+
}
|
|
232
|
+
|
|
233
|
+
function rewriteSensitiveCalls(segment, { allowDynamic, placeholder }) {
|
|
234
|
+
let output = "", cursor = 0;
|
|
235
|
+
SOURCE_CALL_START_PATTERN.lastIndex = 0;
|
|
236
|
+
for (let match = SOURCE_CALL_START_PATTERN.exec(segment); match; match = SOURCE_CALL_START_PATTERN.exec(segment)) {
|
|
237
|
+
if (match.index < cursor) continue;
|
|
238
|
+
const openIndex = SOURCE_CALL_START_PATTERN.lastIndex - 1;
|
|
239
|
+
if (!couldSetSensitiveValue(match[2], segment, openIndex)) {
|
|
240
|
+
// Resume on the opening delimiter so a nested setter in the first
|
|
241
|
+
// argument remains discoverable. Avoiding a full matching-close scan for
|
|
242
|
+
// every ordinary nested call keeps this pass linear for adversarially
|
|
243
|
+
// deep call expressions.
|
|
244
|
+
SOURCE_CALL_START_PATTERN.lastIndex = openIndex;
|
|
245
|
+
continue;
|
|
246
|
+
}
|
|
247
|
+
const closeIndex = callCloseIndex(segment, openIndex);
|
|
248
|
+
if (closeIndex < 0) continue;
|
|
249
|
+
const ranges = callArgumentRanges(segment, openIndex + 1, closeIndex);
|
|
250
|
+
const values = ranges.map((range) => segment.slice(range.start, range.end));
|
|
251
|
+
const valueIndex = setterValueArgument(match[2], values);
|
|
252
|
+
if (valueIndex < 0) {
|
|
253
|
+
// Restart on the opening delimiter so the regex can consume it as the
|
|
254
|
+
// prefix of a nested call in the first argument. Advancing past it skips
|
|
255
|
+
// `setToken(...)` in `wrapper(setToken(...), ...)`.
|
|
256
|
+
SOURCE_CALL_START_PATTERN.lastIndex = openIndex;
|
|
257
|
+
continue;
|
|
258
|
+
}
|
|
259
|
+
const range = ranges[valueIndex];
|
|
260
|
+
const expression = segment.slice(range.start, range.end);
|
|
261
|
+
if (allowDynamic && sourceReference(expression, false)) {
|
|
262
|
+
SOURCE_CALL_START_PATTERN.lastIndex = closeIndex + 1;
|
|
263
|
+
continue;
|
|
264
|
+
}
|
|
265
|
+
output += segment.slice(cursor, range.start);
|
|
266
|
+
output += `${REDACTION}${"\n".repeat((expression.match(/\n/g) ?? []).length)}`;
|
|
267
|
+
cursor = range.end;
|
|
268
|
+
SOURCE_CALL_START_PATTERN.lastIndex = closeIndex + 1;
|
|
269
|
+
}
|
|
270
|
+
return `${output}${segment.slice(cursor)}`;
|
|
271
|
+
}
|
|
272
|
+
|
|
273
|
+
function rewriteSensitiveValueDecorators(segment) {
|
|
274
|
+
const rewrite = (pattern, quote) => segment.replace(pattern, (match, prefix, literal, suffix) => {
|
|
275
|
+
const identifiers = suffix.match(/[$@%]?[A-Za-z_$][A-Za-z0-9_$]*/g) ?? [];
|
|
276
|
+
const field = identifiers.at(-1) ?? "";
|
|
277
|
+
const lookup = /^\$\{[A-Za-z_][A-Za-z0-9_.:-]*\}$/.test(literal);
|
|
278
|
+
if (!keyLooksSensitive(field) || lookup || valueLooksPlaceholder(literal)) return match;
|
|
279
|
+
return `${prefix}${quote}${REDACTION}${quote}${suffix}`;
|
|
280
|
+
});
|
|
281
|
+
let result = rewrite(/(@Value\s*\(\s*)"((?:\\.|[^"\\\r\n])*)"(\s*\)[^;\r\n]{0,160})/g, "\"");
|
|
282
|
+
result = result.replace(/(@Value\s*\(\s*)'((?:\\.|[^'\\\r\n])*)'(\s*\)[^;\r\n]{0,160})/g, (match, prefix, literal, suffix) => {
|
|
283
|
+
const identifiers = suffix.match(/[$@%]?[A-Za-z_$][A-Za-z0-9_$]*/g) ?? [];
|
|
284
|
+
const field = identifiers.at(-1) ?? "";
|
|
285
|
+
const lookup = /^\$\{[A-Za-z_][A-Za-z0-9_.:-]*\}$/.test(literal);
|
|
286
|
+
if (!keyLooksSensitive(field) || lookup || valueLooksPlaceholder(literal)) return match;
|
|
287
|
+
return `${prefix}'${REDACTION}'${suffix}`;
|
|
288
|
+
});
|
|
289
|
+
return result;
|
|
290
|
+
}
|
|
291
|
+
|
|
292
|
+
function rewriteSensitiveSegment(segment, { allowDynamic, shellOnly, placeholder }) {
|
|
293
|
+
let result = rewriteSensitiveStarts(segment, SOURCE_PREPROCESSOR_DEFINE_START_PATTERN, { allowDynamic, shellOnly, placeholder });
|
|
294
|
+
result = rewriteSensitiveStarts(result, SOURCE_COMPOUND_ASSIGNMENT_START_PATTERN, { allowDynamic, shellOnly, placeholder });
|
|
295
|
+
result = rewriteSensitiveStarts(result, SOURCE_TYPED_DECLARATION_START_PATTERN, { allowDynamic, shellOnly, placeholder });
|
|
296
|
+
result = rewriteSensitiveStarts(result, SOURCE_ASSIGNMENT_START_PATTERN, { allowDynamic, shellOnly, placeholder });
|
|
297
|
+
if (!shellOnly) {
|
|
298
|
+
result = rewriteSensitiveStarts(result, SOURCE_BRACE_INITIALIZER_START_PATTERN, {
|
|
299
|
+
allowDynamic,
|
|
300
|
+
shellOnly: false,
|
|
301
|
+
placeholder,
|
|
302
|
+
acceptMatch: (match) => !/^\s*(?:(?:export|public|private|protected|abstract|final|sealed|static|partial)\s+)*(?:class|struct|interface|enum|union|namespace|record)\b/.test(match[2])
|
|
303
|
+
});
|
|
304
|
+
result = rewriteSensitiveStarts(result, SOURCE_COMPUTED_PROPERTY_START_PATTERN, { allowDynamic, shellOnly: false, placeholder });
|
|
305
|
+
result = rewriteSensitiveStarts(result, SOURCE_PROPERTY_START_PATTERN, { allowDynamic, shellOnly: false, placeholder });
|
|
306
|
+
result = rewriteSensitiveCalls(result, { allowDynamic, placeholder });
|
|
307
|
+
result = rewriteSensitiveValueDecorators(result);
|
|
308
|
+
const rewriteTemplate = (pattern, quote) => {
|
|
309
|
+
result = result.replace(pattern, (match, prefix, key, value) => {
|
|
310
|
+
if (!keyLooksSensitive(key)) return match;
|
|
311
|
+
return allowDynamic && sourceReference(value, false)
|
|
312
|
+
? placeholder(match)
|
|
313
|
+
: `${prefix}${quote}${REDACTION}${quote}`;
|
|
314
|
+
});
|
|
315
|
+
};
|
|
316
|
+
rewriteTemplate(SOURCE_TEMPLATE_DOUBLE_BINDING_PATTERN, "\"");
|
|
317
|
+
rewriteTemplate(SOURCE_TEMPLATE_SINGLE_BINDING_PATTERN, "'");
|
|
318
|
+
}
|
|
319
|
+
return result;
|
|
320
|
+
}
|
|
321
|
+
|
|
322
|
+
export {
|
|
323
|
+
balancedSourceExpression,
|
|
324
|
+
hasStandaloneKeywordSourceLiteral,
|
|
325
|
+
hasStandaloneNumericSourceLiteral,
|
|
326
|
+
maskSourceLookupStrings,
|
|
327
|
+
rewriteSensitiveSegment,
|
|
328
|
+
rewriteSensitiveStarts,
|
|
329
|
+
sourceReference
|
|
330
|
+
};
|