haechi 1.3.2 → 1.3.3
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/docs/current/operations-runbook.ko.md +11 -0
- package/docs/current/operations-runbook.md +17 -0
- package/docs/current/release-process.ko.md +1 -0
- package/docs/current/release-process.md +1 -0
- package/docs/current/reliability-hardening-track.ko.md +1 -1
- package/docs/current/reliability-hardening-track.md +1 -1
- package/docs/current/risk-register-release-gate.ko.md +3 -3
- package/docs/current/risk-register-release-gate.md +3 -3
- package/package.json +1 -1
- package/packages/filter/index.mjs +155 -7
|
@@ -21,6 +21,17 @@ docker compose up -d # 참조 스택 빌드 + 실행
|
|
|
21
21
|
docker compose logs -f haechi
|
|
22
22
|
```
|
|
23
23
|
|
|
24
|
+
**사전 빌드 이미지(GHCR).** 각 `v<semver>` 릴리스는 cosign 서명된 이미지를 `ghcr.io/<owner>/haechi`에 발행하며(태그 `<major>.<minor>.<patch>`, `<major>.<minor>`, `<major>`, `latest`), 실행 전에 검증하십시오 — 서명과 provenance가 이미지를 이 repo의 release workflow에 묶습니다:
|
|
25
|
+
|
|
26
|
+
```bash
|
|
27
|
+
cosign verify ghcr.io/<owner>/haechi:1.3.3 \
|
|
28
|
+
--certificate-identity-regexp '^https://github.com/<owner>/haechi/' \
|
|
29
|
+
--certificate-oidc-issuer https://token.actions.githubusercontent.com
|
|
30
|
+
gh attestation verify oci://ghcr.io/<owner>/haechi:1.3.3 --repo <owner>/haechi
|
|
31
|
+
```
|
|
32
|
+
|
|
33
|
+
이미지는 `proxy.trustForwardedProto: true`를 구워 넣으므로(TLS를 종단하는 리버스 프록시 뒤에서 `0.0.0.0`에 바인딩 — 아래 참조), Haechi는 보호되는 모든 요청에 `X-Forwarded-Proto: https`를 요구합니다. Haechi가 직접 TLS를 종단하게 하려면 `proxy.tls`가 설정된 본인의 설정을 마운트하십시오.
|
|
34
|
+
|
|
24
35
|
**TLS + 인증으로 앞단을 보호하십시오.** Haechi는 자체 TLS가 없습니다. 포트는 TLS를 종단하고 인증하는 리버스 프록시(nginx / Caddy / Traefik / API 게이트웨이)에만 공개하고, 원시 Haechi 포트를 공개 인터페이스에 절대 노출하지 마십시오. compose 예제는 바로 이 이유로 호스트 loopback(`127.0.0.1:11016`)에만 공개합니다.
|
|
25
36
|
|
|
26
37
|
**Loopback 너머 바인딩.** 컨테이너 내부에서는 매핑된 포트가 도달 가능하도록 Haechi가 `0.0.0.0`에 바인딩해야 하며, 이는 `--allow-remote-bind`를 요구합니다(참조 `CMD`가 전달합니다). 호스트에서는 기본 loopback 바인딩을 선호하고 리버스 프록시를 통해 Haechi에 접근하십시오. [Loopback 너머 바인딩](./configuration.ko.md)을 참고하십시오.
|
|
@@ -30,6 +30,23 @@ docker compose up -d # build + run the reference stack
|
|
|
30
30
|
docker compose logs -f haechi
|
|
31
31
|
```
|
|
32
32
|
|
|
33
|
+
**Pre-built image (GHCR).** Each `v<semver>` release publishes a cosign-signed
|
|
34
|
+
image to `ghcr.io/<owner>/haechi` (tags `<major>.<minor>.<patch>`, `<major>.<minor>`,
|
|
35
|
+
`<major>`, `latest`). Verify it before running — the signature and provenance bind
|
|
36
|
+
the image to this repo's release workflow:
|
|
37
|
+
|
|
38
|
+
```bash
|
|
39
|
+
cosign verify ghcr.io/<owner>/haechi:1.3.3 \
|
|
40
|
+
--certificate-identity-regexp '^https://github.com/<owner>/haechi/' \
|
|
41
|
+
--certificate-oidc-issuer https://token.actions.githubusercontent.com
|
|
42
|
+
gh attestation verify oci://ghcr.io/<owner>/haechi:1.3.3 --repo <owner>/haechi
|
|
43
|
+
```
|
|
44
|
+
|
|
45
|
+
The image bakes `proxy.trustForwardedProto: true` (it binds `0.0.0.0` behind a
|
|
46
|
+
TLS-terminating reverse proxy — see below), so Haechi requires `X-Forwarded-Proto:
|
|
47
|
+
https` on every protected request; mount your own config with `proxy.tls` set
|
|
48
|
+
instead if you want Haechi to terminate TLS itself.
|
|
49
|
+
|
|
33
50
|
**Front it with TLS + auth.** Haechi has no TLS of its own. Publish its port only
|
|
34
51
|
to a TLS-terminating, authenticating reverse proxy (nginx / Caddy / Traefik / an
|
|
35
52
|
API gateway); never expose the raw Haechi port on a public interface. The compose
|
|
@@ -68,6 +68,7 @@ npm audit signatures
|
|
|
68
68
|
|---|---|---|---|
|
|
69
69
|
| `.github/workflows/ci.yml` | — | 모든 push/PR | test, release preflight, SBOM artifact |
|
|
70
70
|
| `.github/workflows/npm-publish.yml` | `haechi` | `v<semver>` | npm provenance publish + 체크섬/증명 release 자산 |
|
|
71
|
+
| `.github/workflows/container-publish.yml` | `ghcr.io/<owner>/haechi` 이미지 | `v<semver>` | 루트 Dockerfile 빌드, GHCR로 push, digest 기준 keyless cosign 서명 + sigstore build-provenance 증명 |
|
|
71
72
|
| `.github/workflows/crypto-kms-publish.yml` | `haechi-crypto-kms` | `crypto-kms-v<semver>` | satellite publish, 동일한 서명 아티팩트 경로 |
|
|
72
73
|
| `.github/workflows/auth-jwt-publish.yml` | `haechi-auth-jwt` | `auth-jwt-v<semver>` | satellite publish, 동일한 서명 아티팩트 경로 |
|
|
73
74
|
| `.github/workflows/dashboard-publish.yml` | `haechi-dashboard` | `dashboard-v<semver>` | satellite publish, 동일한 서명 아티팩트 경로 |
|
|
@@ -68,6 +68,7 @@ npm audit signatures
|
|
|
68
68
|
|---|---|---|---|
|
|
69
69
|
| `.github/workflows/ci.yml` | — | any push/PR | Tests, release preflight, SBOM artifact |
|
|
70
70
|
| `.github/workflows/npm-publish.yml` | `haechi` | `v<semver>` | npm provenance publish + checksummed/attested release assets |
|
|
71
|
+
| `.github/workflows/container-publish.yml` | `ghcr.io/<owner>/haechi` image | `v<semver>` | Build the root Dockerfile, push to GHCR, keyless cosign sign by digest + sigstore build-provenance attestation |
|
|
71
72
|
| `.github/workflows/crypto-kms-publish.yml` | `haechi-crypto-kms` | `crypto-kms-v<semver>` | satellite publish, same signed-artifacts path |
|
|
72
73
|
| `.github/workflows/auth-jwt-publish.yml` | `haechi-auth-jwt` | `auth-jwt-v<semver>` | satellite publish, same signed-artifacts path |
|
|
73
74
|
| `.github/workflows/dashboard-publish.yml` | `haechi-dashboard` | `dashboard-v<semver>` | satellite publish, same signed-artifacts path |
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
# 신뢰성 하드닝 트랙 (Reliability Hardening Track)
|
|
2
2
|
|
|
3
|
-
- 상태:
|
|
3
|
+
- 상태: 출시 완료 — WS1–WS6 전부 core 1.2.0으로 전달·컷됨(릴리스 게이트 G7 Pass). 이 문서는 계획/감사 기록으로 보존합니다. (2026-06-12 확정; 1.1.1 코어에 대한 5-렌즈 읽기 전용 감사에 근거.)
|
|
4
4
|
- 대상 라인: 1.1.2(patch) → 1.2.0(minor); 신규 제품 표면 없음
|
|
5
5
|
- 목적: Haechi를 **상용 솔루션 수준의 신뢰성**으로 끌어올립니다 — 운영 AI 보안 게이트웨이에 기대되는 신뢰·운영성·탐지 품질의 밀도입니다. 이것은 품질 목표이지 상용화 계획이 아닙니다. 모든 항목은 **이미 존재하는 것을 조이거나, 측정하거나, 문서화**하며, 신규 기능을 추가하지 않습니다.
|
|
6
6
|
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
# Reliability Hardening Track
|
|
2
2
|
|
|
3
|
-
- Status:
|
|
3
|
+
- Status: Shipped — WS1–WS6 all delivered and cut in core 1.2.0 (release gate G7 Pass). This doc is retained as the planning/audit record. (Pinned 2026-06-12; grounded in a 5-lens read-only audit of the 1.1.1 core.)
|
|
4
4
|
- Target line: 1.1.2 (patch) → 1.2.0 (minor); no new product surface
|
|
5
5
|
- Purpose: raise Haechi to **commercial-solution-level reliability** — the trust, operability, and detection-quality density a production AI-security gateway is expected to have. This is a quality objective, not a commercialization plan. Every item **tightens, measures, or documents what already exists**; none adds a new feature.
|
|
6
6
|
|
|
@@ -14,9 +14,9 @@ Haechi는 `1.x` stable 라인을 출시했습니다. developer preview 게이트
|
|
|
14
14
|
| 구분 | 판단 | 이유 |
|
|
15
15
|
|---|---|---|
|
|
16
16
|
| GitHub public | 허용 | 보안 한계, threat model, shared responsibility가 문서화됨 |
|
|
17
|
-
| GitHub release/tag | 허용 (`v1.3.
|
|
18
|
-
| npm stable | `haechi@1.3.
|
|
19
|
-
| production use | 운영자 게이트; `1.3.
|
|
17
|
+
| GitHub release/tag | 허용 (`v1.3.3` 릴리스됨) | `v1.3.3`이 현재 릴리스(CR2 컷 `1.3.2` 위의 선제적 하드닝 패치); §5.7 및 §5.8(`CR2-001..008`) 항목은 모두 Resolved 유지, G9/G10은 Pass |
|
|
18
|
+
| npm stable | `haechi@1.3.3` publish됨 | `1.3.3`은 CR2-보완된 `1.3.2` 기준 위에 response-direction marker-skip 강화 + cosign 서명 GHCR 컨테이너 이미지를 더한 attested OIDC publish |
|
|
19
|
+
| production use | 운영자 게이트; `1.3.3`로 업그레이드 | 운영자 네트워크 통제, 인가/인증, key custody가 있을 때만 지원; 운영자는 민감한 제3자 업스트림 트래픽을 프록시로 라우팅하기 전에 최신 `haechi@1.3.3`(1.3.2의 CR2 수정 + marker-skip 하드닝 포함)을 실행해야 함 |
|
|
20
20
|
|
|
21
21
|
## 2. 릴리스 게이트
|
|
22
22
|
|
|
@@ -14,9 +14,9 @@ Haechi has shipped its `1.x` stable line. The developer-preview gate (G2, `haech
|
|
|
14
14
|
| Category | Judgment | Rationale |
|
|
15
15
|
|---|---|---|
|
|
16
16
|
| GitHub public | Allowed | Security limitations, threat model, and shared responsibility are documented |
|
|
17
|
-
| GitHub release/tag | Allowed (`v1.3.
|
|
18
|
-
| npm stable | `haechi@1.3.
|
|
19
|
-
| Production use | Operator-gated; upgrade to `1.3.
|
|
17
|
+
| GitHub release/tag | Allowed (`v1.3.3` released) | `v1.3.3` is the current release (a proactive-hardening patch over the CR2 cut `1.3.2`); all §5.7 and §5.8 (`CR2-001..008`) findings remain Resolved and G9/G10 are Pass |
|
|
18
|
+
| npm stable | `haechi@1.3.3` published | `1.3.3` is an attested OIDC publish adding the response-direction marker-skip tightening + a cosign-signed GHCR container image, over the CR2-remediated `1.3.2` baseline |
|
|
19
|
+
| Production use | Operator-gated; upgrade to `1.3.3` | Supported only with operator network controls, authz/authn, and key custody; operators should run the latest `haechi@1.3.3` (it carries the CR2 fixes from `1.3.2` plus the marker-skip hardening) before routing sensitive third-party upstream traffic through the proxy |
|
|
20
20
|
|
|
21
21
|
## 2. Release Gates
|
|
22
22
|
|
package/package.json
CHANGED
|
@@ -540,12 +540,15 @@ function scanEntry(entry, rules, context = {}) {
|
|
|
540
540
|
// own token. This is response-only on purpose: a REQUEST that contains a
|
|
541
541
|
// marker-shaped string is NOT Haechi output (Haechi hasn't transformed it yet),
|
|
542
542
|
// so it is scanned normally — otherwise an attacker could wrap a real secret in
|
|
543
|
-
// a fake `[TOKEN:…]` to evade request-side detection.
|
|
543
|
+
// a fake `[TOKEN:…]` to evade request-side detection. On the RESPONSE side the
|
|
544
|
+
// same wrap-a-secret risk is closed by haechiMarkerSpans recording a span only
|
|
545
|
+
// when the inner content matches a GENUINE emitted format — a fake marker
|
|
546
|
+
// wrapping a real secret stays in the scan and is detected/blocked.
|
|
544
547
|
// Markers are pure ASCII and NFKC-stable, so their spans are computed on the
|
|
545
548
|
// ORIGINAL value exactly as before — they line up with the same-length
|
|
546
549
|
// normalized scan (Case 2 below) and are irrelevant to the whole-leaf scan
|
|
547
550
|
// (Case 3).
|
|
548
|
-
const markerSpans = context?.direction === "response" ? haechiMarkerSpans(entry.value) : [];
|
|
551
|
+
const markerSpans = context?.direction === "response" ? haechiMarkerSpans(entry.value, rules, context) : [];
|
|
549
552
|
|
|
550
553
|
// WS2d — Unicode evasion via NFKC normalization. A client can defeat every
|
|
551
554
|
// regex rule by sending PII/secrets in a Unicode form that folds to ASCII
|
|
@@ -824,12 +827,157 @@ function isPositionStableNfkc(value, normalized) {
|
|
|
824
827
|
return rebuilt === normalized;
|
|
825
828
|
}
|
|
826
829
|
|
|
827
|
-
// Spans of Haechi's own transform markers in a string, so
|
|
828
|
-
// them: `[TOKEN:…]`, `[HAECHI_ENC:…]`, `[REDACTED:…]`.
|
|
829
|
-
|
|
830
|
+
// Spans of Haechi's own transform markers in a string, so RESPONSE-direction
|
|
831
|
+
// detection can skip them: `[TOKEN:…]`, `[HAECHI_ENC:…]`, `[REDACTED:…]`. A
|
|
832
|
+
// tokenized round-trip echoed by the model would otherwise be re-flagged as a
|
|
833
|
+
// secret (Haechi blocking its own output).
|
|
834
|
+
//
|
|
835
|
+
// CR-???: a span is recorded ONLY when its inner content matches a GENUINE
|
|
836
|
+
// format actually emitted by core's transform (packages/core/index.mjs
|
|
837
|
+
// replacementFor). Without this check the marker frame `[(?:TOKEN|…):[^\]]*]`
|
|
838
|
+
// would skip ANY inner content, so a hostile model could exfiltrate a real
|
|
839
|
+
// secret by wrapping it in a FAKE marker — `[TOKEN:sk-ant-api03-<secret>]`,
|
|
840
|
+
// `[HAECHI_ENC:<secret>]`, `[REDACTED:<secret>]` — and that span would be
|
|
841
|
+
// dropped from the scan. A marker-SHAPED string whose inner content is not
|
|
842
|
+
// genuine is left in the scan, so the wrapped secret is detected/blocked.
|
|
843
|
+
// Genuine inner formats:
|
|
844
|
+
// [REDACTED:<type>] <type> is a detection type name (lowercase
|
|
845
|
+
// identifier: [a-z][a-z0-9_]*).
|
|
846
|
+
// [TOKEN:<vaultTokenId>] vault id shape `tok_<type>_<hexhash>`
|
|
847
|
+
// (matches token-vault VAULT_TOKEN_SHAPE).
|
|
848
|
+
// [TOKEN:<type>:<shortHash>] non-vault deterministic token: type name + hex.
|
|
849
|
+
// [HAECHI_ENC:<base64url>] base64url that decodes to a VALID envelope
|
|
850
|
+
// JSON object (cryptoProvider.encrypt envelope:
|
|
851
|
+
// has `kid`+`aadHash`). A real secret string will
|
|
852
|
+
// not base64url-decode to such an object.
|
|
853
|
+
// Markers are pure ASCII / NFKC-stable and spans are computed on the ORIGINAL
|
|
854
|
+
// entry.value, so offset integrity is unchanged.
|
|
855
|
+
|
|
856
|
+
// Detection-type name shape (the `detection.type` written by core into REDACTED
|
|
857
|
+
// and the type segment of a non-vault TOKEN). Built-in rule types and custom
|
|
858
|
+
// rule types are lowercase identifiers; a real secret (hyphens, uppercase,
|
|
859
|
+
// length) does not match, so a wrapped secret stays in the scan.
|
|
860
|
+
const MARKER_TYPE_NAME = /^[a-z][a-z0-9_]*$/;
|
|
861
|
+
// Vault token id shape — mirrors token-vault VAULT_TOKEN_SHAPE
|
|
862
|
+
// (`tok_<type>_<hexhash>`, random: 16 hex, deterministic: 32 hex). Kept in sync
|
|
863
|
+
// with packages/token-vault/index.mjs (not exported from there).
|
|
864
|
+
const MARKER_VAULT_TOKEN = /^tok_[a-z0-9_]+_[a-f0-9]{16,}$/;
|
|
865
|
+
// Non-vault deterministic token: `<type>:<hex>` (core shortHash → 12 hex; allow
|
|
866
|
+
// any reasonable hex run so the check does not over-fit a single length).
|
|
867
|
+
const MARKER_NONVAULT_TOKEN = /^[a-z][a-z0-9_]*:[a-f0-9]{8,}$/;
|
|
868
|
+
// base64url alphabet only (core emits base64url with no padding).
|
|
869
|
+
const MARKER_BASE64URL = /^[A-Za-z0-9_-]+$/;
|
|
870
|
+
|
|
871
|
+
function isGenuineTokenInner(inner) {
|
|
872
|
+
return MARKER_VAULT_TOKEN.test(inner) || MARKER_NONVAULT_TOKEN.test(inner);
|
|
873
|
+
}
|
|
874
|
+
|
|
875
|
+
function isGenuineRedactedInner(inner) {
|
|
876
|
+
return MARKER_TYPE_NAME.test(inner);
|
|
877
|
+
}
|
|
878
|
+
|
|
879
|
+
// True only when `inner` base64url-decodes to a valid UTF-8 JSON object that
|
|
880
|
+
// carries the encrypt-envelope signature (`kid` + `aadHash` — the contract keys
|
|
881
|
+
// asserted by assertCryptoProviderConformance, present in the local AES-GCM
|
|
882
|
+
// envelope and any conformant external provider). Any decode/parse failure or a
|
|
883
|
+
// non-envelope shape → NOT a genuine marker (so a wrapped secret is scanned).
|
|
884
|
+
function isGenuineEncInner(inner) {
|
|
885
|
+
if (!MARKER_BASE64URL.test(inner)) {
|
|
886
|
+
return false;
|
|
887
|
+
}
|
|
888
|
+
try {
|
|
889
|
+
const bytes = Buffer.from(inner, "base64url");
|
|
890
|
+
// Reject inputs that do not round-trip through base64url (e.g. an invalid
|
|
891
|
+
// tail that Buffer silently truncates): a genuine marker always round-trips.
|
|
892
|
+
if (bytes.toString("base64url") !== inner) {
|
|
893
|
+
return false;
|
|
894
|
+
}
|
|
895
|
+
if (!isUtf8(bytes)) {
|
|
896
|
+
return false;
|
|
897
|
+
}
|
|
898
|
+
const parsed = JSON.parse(bytes.toString("utf8"));
|
|
899
|
+
return (
|
|
900
|
+
parsed !== null &&
|
|
901
|
+
typeof parsed === "object" &&
|
|
902
|
+
!Array.isArray(parsed) &&
|
|
903
|
+
typeof parsed.kid === "string" &&
|
|
904
|
+
typeof parsed.aadHash === "string"
|
|
905
|
+
);
|
|
906
|
+
} catch {
|
|
907
|
+
return false;
|
|
908
|
+
}
|
|
909
|
+
}
|
|
910
|
+
|
|
911
|
+
// Belt-and-suspenders for the genuine-marker shapes: even a correctly-SHAPED
|
|
912
|
+
// TOKEN/REDACTED inner must not itself carry a detectable secret. The lowercase-
|
|
913
|
+
// identifier classes (MARKER_TYPE_NAME, the type segments of the token shapes)
|
|
914
|
+
// overlap the body of real lowercase-bodied secrets (notably GitHub `gh[pousr]_`
|
|
915
|
+
// tokens), so a hostile model could smuggle such a secret as the `<type>` segment
|
|
916
|
+
// of an otherwise genuine-shaped marker. Re-scan the inner with the SAME rules and
|
|
917
|
+
// refuse to treat it as genuine if anything detectable is inside — this un-skips a
|
|
918
|
+
// marker exactly when skipping it would hide a leak.
|
|
919
|
+
function textHasDetection(text, rules, context) {
|
|
920
|
+
for (const rule of rules) {
|
|
921
|
+
if (rule.direction && rule.direction !== context?.direction) {
|
|
922
|
+
continue;
|
|
923
|
+
}
|
|
924
|
+
const regex = new RegExp(rule.pattern, rule.flags.includes("g") ? rule.flags : `${rule.flags}g`);
|
|
925
|
+
for (const match of text.matchAll(regex)) {
|
|
926
|
+
if (!rule.validate || rule.validate(match[0])) {
|
|
927
|
+
return true;
|
|
928
|
+
}
|
|
929
|
+
}
|
|
930
|
+
}
|
|
931
|
+
return false;
|
|
932
|
+
}
|
|
933
|
+
|
|
934
|
+
// The attacker-controllable segment(s) of a genuine-shaped marker inner — i.e. the
|
|
935
|
+
// `<type>` position(s) a hostile model could smuggle a secret into. For TOKEN we
|
|
936
|
+
// peel off the structural framing (`tok_<type>_<hex>` → `<type>`, `<type>:<hex>` →
|
|
937
|
+
// `<type>`) and scan the segment IN ISOLATION as well as the whole inner: a `\b`-
|
|
938
|
+
// anchored rule (e.g. GitHub `\bghp_…`) misses a token glued to the `tok_` prefix
|
|
939
|
+
// (no word boundary after `_`), but matches the segment scanned on its own.
|
|
940
|
+
function markerSecretSurfaces(kind, inner) {
|
|
941
|
+
const surfaces = [inner];
|
|
942
|
+
if (kind === "TOKEN") {
|
|
943
|
+
const vault = /^tok_(.+)_[a-f0-9]{16,}$/.exec(inner);
|
|
944
|
+
if (vault) {
|
|
945
|
+
surfaces.push(vault[1]);
|
|
946
|
+
}
|
|
947
|
+
const nonVault = /^(.+):[a-f0-9]{8,}$/.exec(inner);
|
|
948
|
+
if (nonVault) {
|
|
949
|
+
surfaces.push(nonVault[1]);
|
|
950
|
+
}
|
|
951
|
+
}
|
|
952
|
+
return surfaces;
|
|
953
|
+
}
|
|
954
|
+
|
|
955
|
+
function innerContainsDetection(kind, inner, rules, context) {
|
|
956
|
+
return markerSecretSurfaces(kind, inner).some((surface) => textHasDetection(surface, rules, context));
|
|
957
|
+
}
|
|
958
|
+
|
|
959
|
+
function haechiMarkerSpans(text, rules = [], context = {}) {
|
|
830
960
|
const spans = [];
|
|
831
|
-
for (const m of text.matchAll(/\[(
|
|
832
|
-
|
|
961
|
+
for (const m of text.matchAll(/\[(TOKEN|HAECHI_ENC|REDACTED):([^\]]*)\]/g)) {
|
|
962
|
+
const kind = m[1];
|
|
963
|
+
const inner = m[2];
|
|
964
|
+
let genuine = false;
|
|
965
|
+
if (kind === "TOKEN") {
|
|
966
|
+
genuine = isGenuineTokenInner(inner);
|
|
967
|
+
} else if (kind === "REDACTED") {
|
|
968
|
+
genuine = isGenuineRedactedInner(inner);
|
|
969
|
+
} else {
|
|
970
|
+
genuine = isGenuineEncInner(inner);
|
|
971
|
+
}
|
|
972
|
+
// HAECHI_ENC is exempt from the inner re-scan: its inner is an opaque base64url
|
|
973
|
+
// envelope validated by decode above (a raw secret cannot forge a valid
|
|
974
|
+
// envelope, and the envelope's base64url body is not a detectable leaf).
|
|
975
|
+
if (genuine && kind !== "HAECHI_ENC" && innerContainsDetection(kind, inner, rules, context)) {
|
|
976
|
+
genuine = false;
|
|
977
|
+
}
|
|
978
|
+
if (genuine) {
|
|
979
|
+
spans.push([m.index, m.index + m[0].length]);
|
|
980
|
+
}
|
|
833
981
|
}
|
|
834
982
|
return spans;
|
|
835
983
|
}
|