fix(federation/auth-guard): remediate CRIT-1/CRIT-2 + HIGH-1..4 review findings
All checks were successful
ci/woodpecker/pr/ci Pipeline was successful
ci/woodpecker/push/ci Pipeline was successful

- CRIT-1: Validate cert subjectUserId against grant.subjectUserId from DB;
  use authoritative DB value in FederationContext
- CRIT-2: Add @Inject(GrantsService) decorator (tsx/esbuild requirement)
- HIGH-1: Validate UTF8String TLV tag, length, and bounds in OID parser
- HIGH-2: Collapse all 403 wire messages to a generic string to prevent
  grant enumeration; keep internal logger detail
- HIGH-3: Assert federation wire envelope shape in all guard tests
- HIGH-4: Regression test for subjectUserId cert/DB mismatch

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
This commit is contained in:
Jarvis
2026-04-23 22:45:35 -05:00
parent b01c9b3bb0
commit 0af3e218a1
3 changed files with 146 additions and 32 deletions

View File

@@ -50,10 +50,40 @@ const decoder = new TextDecoder();
/**
* Decode an extension value encoded as ASN.1 UTF8String TLV
* (tag 0x0C + 1-byte length + UTF-8 bytes).
* Slices off the 2-byte TLV header and decodes the remainder as UTF-8.
* Validates tag, length byte, and buffer bounds before decoding.
* Throws a descriptive Error on malformed input; caller wraps in try/catch.
*/
function decodeUtf8StringTlv(value: ArrayBuffer): string {
const bytes = new Uint8Array(value);
// Need at least tag + length bytes
if (bytes.length < 2) {
throw new Error(`UTF8String TLV too short: expected at least 2 bytes, got ${bytes.length}`);
}
// Tag byte must be 0x0C (ASN.1 UTF8String)
if (bytes[0] !== 0x0c) {
throw new Error(
`UTF8String TLV tag mismatch: expected 0x0C, got 0x${bytes[0]!.toString(16).toUpperCase()}`,
);
}
// Only single-byte length form is supported (values 0127); long form not needed
// for OID strings of this length.
const declaredLength = bytes[1]!;
if (declaredLength > 127) {
throw new Error(
`UTF8String TLV uses long-form length (0x${declaredLength.toString(16).toUpperCase()}), which is not supported`,
);
}
// Declared length must match actual remaining bytes
if (declaredLength !== bytes.length - 2) {
throw new Error(
`UTF8String TLV length mismatch: declared ${declaredLength}, actual ${bytes.length - 2}`,
);
}
// Skip: tag (1 byte) + length (1 byte)
return decoder.decode(bytes.slice(2));
}