1
0
mirror of https://github.com/bitwarden/browser synced 2026-01-28 23:33:27 +00:00

Merge branch 'main' into beeep/dev-container

This commit is contained in:
Conner Turnbull
2026-01-27 11:56:02 -05:00
committed by GitHub
9 changed files with 326 additions and 128 deletions

View File

@@ -158,7 +158,7 @@ describe("CollectAutofillContentService", () => {
type: "text",
value: "",
checked: false,
autoCompleteType: "",
autoCompleteType: null,
disabled: false,
readonly: false,
selectInfo: null,
@@ -346,7 +346,7 @@ describe("CollectAutofillContentService", () => {
type: "text",
value: "",
checked: false,
autoCompleteType: "",
autoCompleteType: null,
disabled: false,
readonly: false,
selectInfo: null,
@@ -379,7 +379,7 @@ describe("CollectAutofillContentService", () => {
type: "password",
value: "",
checked: false,
autoCompleteType: "",
autoCompleteType: null,
disabled: false,
readonly: false,
selectInfo: null,
@@ -588,7 +588,7 @@ describe("CollectAutofillContentService", () => {
"aria-disabled": false,
"aria-haspopup": false,
"aria-hidden": false,
autoCompleteType: "",
autoCompleteType: null,
checked: false,
"data-stripe": null,
disabled: false,
@@ -621,7 +621,7 @@ describe("CollectAutofillContentService", () => {
"aria-disabled": false,
"aria-haspopup": false,
"aria-hidden": false,
autoCompleteType: "",
autoCompleteType: null,
checked: false,
"data-stripe": null,
disabled: false,
@@ -2507,9 +2507,7 @@ describe("CollectAutofillContentService", () => {
"class",
"tabindex",
"title",
"value",
"rel",
"tagname",
"checked",
"disabled",
"readonly",

View File

@@ -1,5 +1,7 @@
// FIXME: Update this file to be type safe and remove this and next line
// @ts-strict-ignore
import { AUTOFILL_ATTRIBUTES } from "@bitwarden/common/autofill/constants";
import AutofillField from "../models/autofill-field";
import AutofillForm from "../models/autofill-form";
import AutofillPageDetails from "../models/autofill-page-details";
@@ -242,10 +244,10 @@ export class CollectAutofillContentService implements CollectAutofillContentServ
this._autofillFormElements.set(formElement, {
opid: formElement.opid,
htmlAction: this.getFormActionAttribute(formElement),
htmlName: this.getPropertyOrAttribute(formElement, "name"),
htmlClass: this.getPropertyOrAttribute(formElement, "class"),
htmlID: this.getPropertyOrAttribute(formElement, "id"),
htmlMethod: this.getPropertyOrAttribute(formElement, "method"),
htmlName: this.getPropertyOrAttribute(formElement, AUTOFILL_ATTRIBUTES.NAME),
htmlClass: this.getPropertyOrAttribute(formElement, AUTOFILL_ATTRIBUTES.CLASS),
htmlID: this.getPropertyOrAttribute(formElement, AUTOFILL_ATTRIBUTES.ID),
htmlMethod: this.getPropertyOrAttribute(formElement, AUTOFILL_ATTRIBUTES.METHOD),
});
}
@@ -260,7 +262,10 @@ export class CollectAutofillContentService implements CollectAutofillContentServ
* @private
*/
private getFormActionAttribute(element: ElementWithOpId<HTMLFormElement>): string {
return new URL(this.getPropertyOrAttribute(element, "action"), globalThis.location.href).href;
return new URL(
this.getPropertyOrAttribute(element, AUTOFILL_ATTRIBUTES.ACTION),
globalThis.location.href,
).href;
}
/**
@@ -335,7 +340,10 @@ export class CollectAutofillContentService implements CollectAutofillContentServ
return priorityFormFields;
}
const fieldType = this.getPropertyOrAttribute(element, "type")?.toLowerCase();
const fieldType = this.getPropertyOrAttribute(
element,
AUTOFILL_ATTRIBUTES.TYPE,
)?.toLowerCase();
if (unimportantFieldTypesSet.has(fieldType)) {
unimportantFormFields.push(element);
continue;
@@ -384,11 +392,11 @@ export class CollectAutofillContentService implements CollectAutofillContentServ
elementNumber: index,
maxLength: this.getAutofillFieldMaxLength(element),
viewable: await this.domElementVisibilityService.isElementViewable(element),
htmlID: this.getPropertyOrAttribute(element, "id"),
htmlName: this.getPropertyOrAttribute(element, "name"),
htmlClass: this.getPropertyOrAttribute(element, "class"),
tabindex: this.getPropertyOrAttribute(element, "tabindex"),
title: this.getPropertyOrAttribute(element, "title"),
htmlID: this.getPropertyOrAttribute(element, AUTOFILL_ATTRIBUTES.ID),
htmlName: this.getPropertyOrAttribute(element, AUTOFILL_ATTRIBUTES.NAME),
htmlClass: this.getPropertyOrAttribute(element, AUTOFILL_ATTRIBUTES.CLASS),
tabindex: this.getPropertyOrAttribute(element, AUTOFILL_ATTRIBUTES.TABINDEX),
title: this.getPropertyOrAttribute(element, AUTOFILL_ATTRIBUTES.TITLE),
tagName: this.getAttributeLowerCase(element, "tagName"),
dataSetValues: this.getDataSetValues(element),
};
@@ -404,16 +412,16 @@ export class CollectAutofillContentService implements CollectAutofillContentServ
}
let autofillFieldLabels = {};
const elementType = this.getAttributeLowerCase(element, "type");
const elementType = this.getAttributeLowerCase(element, AUTOFILL_ATTRIBUTES.TYPE);
if (elementType !== "hidden") {
autofillFieldLabels = {
"label-tag": this.createAutofillFieldLabelTag(element as FillableFormFieldElement),
"label-data": this.getPropertyOrAttribute(element, "data-label"),
"label-aria": this.getPropertyOrAttribute(element, "aria-label"),
"label-data": this.getPropertyOrAttribute(element, AUTOFILL_ATTRIBUTES.DATA_LABEL),
"label-aria": this.getPropertyOrAttribute(element, AUTOFILL_ATTRIBUTES.ARIA_LABEL),
"label-top": this.createAutofillFieldTopLabel(element),
"label-right": this.createAutofillFieldRightLabel(element),
"label-left": this.createAutofillFieldLeftLabel(element),
placeholder: this.getPropertyOrAttribute(element, "placeholder"),
placeholder: this.getPropertyOrAttribute(element, AUTOFILL_ATTRIBUTES.PLACEHOLDER),
};
}
@@ -421,21 +429,21 @@ export class CollectAutofillContentService implements CollectAutofillContentServ
const autofillField = {
...autofillFieldBase,
...autofillFieldLabels,
rel: this.getPropertyOrAttribute(element, "rel"),
rel: this.getPropertyOrAttribute(element, AUTOFILL_ATTRIBUTES.REL),
type: elementType,
value: this.getElementValue(element),
checked: this.getAttributeBoolean(element, "checked"),
checked: this.getAttributeBoolean(element, AUTOFILL_ATTRIBUTES.CHECKED),
autoCompleteType: this.getAutoCompleteAttribute(element),
disabled: this.getAttributeBoolean(element, "disabled"),
readonly: this.getAttributeBoolean(element, "readonly"),
disabled: this.getAttributeBoolean(element, AUTOFILL_ATTRIBUTES.DISABLED),
readonly: this.getAttributeBoolean(element, AUTOFILL_ATTRIBUTES.READONLY),
selectInfo: elementIsSelectElement(element)
? this.getSelectElementOptions(element as HTMLSelectElement)
: null,
form: fieldFormElement ? this.getPropertyOrAttribute(fieldFormElement, "opid") : null,
"aria-hidden": this.getAttributeBoolean(element, "aria-hidden", true),
"aria-disabled": this.getAttributeBoolean(element, "aria-disabled", true),
"aria-haspopup": this.getAttributeBoolean(element, "aria-haspopup", true),
"data-stripe": this.getPropertyOrAttribute(element, "data-stripe"),
"aria-hidden": this.getAttributeBoolean(element, AUTOFILL_ATTRIBUTES.ARIA_HIDDEN, true),
"aria-disabled": this.getAttributeBoolean(element, AUTOFILL_ATTRIBUTES.ARIA_DISABLED, true),
"aria-haspopup": this.getAttributeBoolean(element, AUTOFILL_ATTRIBUTES.ARIA_HASPOPUP, true),
"data-stripe": this.getPropertyOrAttribute(element, AUTOFILL_ATTRIBUTES.DATA_STRIPE),
};
this.cacheAutofillFieldElement(index, element, autofillField);
@@ -467,9 +475,9 @@ export class CollectAutofillContentService implements CollectAutofillContentServ
*/
private getAutoCompleteAttribute(element: ElementWithOpId<FormFieldElement>): string {
return (
this.getPropertyOrAttribute(element, "x-autocompletetype") ||
this.getPropertyOrAttribute(element, "autocompletetype") ||
this.getPropertyOrAttribute(element, "autocomplete")
this.getPropertyOrAttribute(element, AUTOFILL_ATTRIBUTES.AUTOCOMPLETE) ||
this.getPropertyOrAttribute(element, AUTOFILL_ATTRIBUTES.X_AUTOCOMPLETE_TYPE) ||
this.getPropertyOrAttribute(element, AUTOFILL_ATTRIBUTES.AUTOCOMPLETE_TYPE)
);
}
@@ -957,6 +965,8 @@ export class CollectAutofillContentService implements CollectAutofillContentServ
this.mutationObserver = new MutationObserver(this.handleMutationObserverMutation);
this.mutationObserver.observe(document.documentElement, {
attributes: true,
/** Mutations to node attributes NOT on this list will not be observed! */
attributeFilter: Object.values(AUTOFILL_ATTRIBUTES),
childList: true,
subtree: true,
});
@@ -1321,6 +1331,7 @@ export class CollectAutofillContentService implements CollectAutofillContentServ
action: () => (dataTarget.htmlAction = this.getFormActionAttribute(element)),
name: () => updateAttribute("htmlName"),
id: () => updateAttribute("htmlID"),
class: () => updateAttribute("htmlClass"),
method: () => updateAttribute("htmlMethod"),
};
@@ -1350,29 +1361,49 @@ export class CollectAutofillContentService implements CollectAutofillContentServ
this.updateAutofillDataAttribute({ element, attributeName, dataTarget, dataTargetKey });
};
const updateActions: Record<string, CallableFunction> = {
maxlength: () => (dataTarget.maxLength = this.getAutofillFieldMaxLength(element)),
id: () => updateAttribute("htmlID"),
name: () => updateAttribute("htmlName"),
class: () => updateAttribute("htmlClass"),
tabindex: () => updateAttribute("tabindex"),
title: () => updateAttribute("tabindex"),
rel: () => updateAttribute("rel"),
tagname: () => (dataTarget.tagName = this.getAttributeLowerCase(element, "tagName")),
type: () => (dataTarget.type = this.getAttributeLowerCase(element, "type")),
value: () => (dataTarget.value = this.getElementValue(element)),
checked: () => (dataTarget.checked = this.getAttributeBoolean(element, "checked")),
disabled: () => (dataTarget.disabled = this.getAttributeBoolean(element, "disabled")),
readonly: () => (dataTarget.readonly = this.getAttributeBoolean(element, "readonly")),
autocomplete: () => (dataTarget.autoCompleteType = this.getAutoCompleteAttribute(element)),
"data-label": () => updateAttribute("label-data"),
"aria-describedby": () => updateAttribute(AUTOFILL_ATTRIBUTES.ARIA_DESCRIBEDBY),
"aria-label": () => updateAttribute("label-aria"),
"aria-labelledby": () => updateAttribute(AUTOFILL_ATTRIBUTES.ARIA_LABELLEDBY),
"aria-hidden": () =>
(dataTarget["aria-hidden"] = this.getAttributeBoolean(element, "aria-hidden", true)),
(dataTarget["aria-hidden"] = this.getAttributeBoolean(
element,
AUTOFILL_ATTRIBUTES.ARIA_HIDDEN,
true,
)),
"aria-disabled": () =>
(dataTarget["aria-disabled"] = this.getAttributeBoolean(element, "aria-disabled", true)),
(dataTarget["aria-disabled"] = this.getAttributeBoolean(
element,
AUTOFILL_ATTRIBUTES.ARIA_DISABLED,
true,
)),
"aria-haspopup": () =>
(dataTarget["aria-haspopup"] = this.getAttributeBoolean(element, "aria-haspopup", true)),
"data-stripe": () => updateAttribute("data-stripe"),
(dataTarget["aria-haspopup"] = this.getAttributeBoolean(
element,
AUTOFILL_ATTRIBUTES.ARIA_HASPOPUP,
true,
)),
autocomplete: () => (dataTarget.autoCompleteType = this.getAutoCompleteAttribute(element)),
autocompletetype: () =>
(dataTarget.autoCompleteType = this.getAutoCompleteAttribute(element)),
"x-autocompletetype": () =>
(dataTarget.autoCompleteType = this.getAutoCompleteAttribute(element)),
class: () => updateAttribute("htmlClass"),
checked: () =>
(dataTarget.checked = this.getAttributeBoolean(element, AUTOFILL_ATTRIBUTES.CHECKED)),
"data-label": () => updateAttribute("label-data"),
"data-stripe": () => updateAttribute(AUTOFILL_ATTRIBUTES.DATA_STRIPE),
disabled: () =>
(dataTarget.disabled = this.getAttributeBoolean(element, AUTOFILL_ATTRIBUTES.DISABLED)),
id: () => updateAttribute("htmlID"),
maxlength: () => (dataTarget.maxLength = this.getAutofillFieldMaxLength(element)),
name: () => updateAttribute("htmlName"),
placeholder: () => updateAttribute(AUTOFILL_ATTRIBUTES.PLACEHOLDER),
readonly: () =>
(dataTarget.readonly = this.getAttributeBoolean(element, AUTOFILL_ATTRIBUTES.READONLY)),
rel: () => updateAttribute(AUTOFILL_ATTRIBUTES.REL),
tabindex: () => updateAttribute(AUTOFILL_ATTRIBUTES.TABINDEX),
title: () => updateAttribute(AUTOFILL_ATTRIBUTES.TITLE),
type: () => (dataTarget.type = this.getAttributeLowerCase(element, AUTOFILL_ATTRIBUTES.TYPE)),
};
if (!updateActions[attributeName]) {

View File

@@ -28,6 +28,41 @@ export const EVENTS = {
SUBMIT: "submit",
} as const;
/**
* HTML attributes observed by the MutationObserver for autofill form/field tracking.
* If you need to observe a new attribute, add it here.
*/
export const AUTOFILL_ATTRIBUTES = {
ACTION: "action",
ARIA_DESCRIBEDBY: "aria-describedby",
ARIA_DISABLED: "aria-disabled",
ARIA_HASPOPUP: "aria-haspopup",
ARIA_HIDDEN: "aria-hidden",
ARIA_LABEL: "aria-label",
ARIA_LABELLEDBY: "aria-labelledby",
AUTOCOMPLETE: "autocomplete",
AUTOCOMPLETE_TYPE: "autocompletetype",
X_AUTOCOMPLETE_TYPE: "x-autocompletetype",
CHECKED: "checked",
CLASS: "class",
DATA_LABEL: "data-label",
DATA_STRIPE: "data-stripe",
DISABLED: "disabled",
ID: "id",
MAXLENGTH: "maxlength",
METHOD: "method",
NAME: "name",
PLACEHOLDER: "placeholder",
POPOVER: "popover",
POPOVERTARGET: "popovertarget",
POPOVERTARGETACTION: "popovertargetaction",
READONLY: "readonly",
REL: "rel",
TABINDEX: "tabindex",
TITLE: "title",
TYPE: "type",
} as const;
export const ClearClipboardDelay = {
Never: null as null,
TenSeconds: 10,

View File

@@ -1,3 +1,4 @@
import { FieldType } from "@bitwarden/common/vault/enums";
import { FolderView } from "@bitwarden/common/vault/models/view/folder.view";
import { KeePass2XmlImporter } from "./keepass2-xml-importer";
@@ -5,6 +6,7 @@ import {
TestData,
TestData1,
TestData2,
TestDataWithProtectedFields,
} from "./spec-data/keepass2-xml/keepass2-xml-importer-testdata";
describe("KeePass2 Xml Importer", () => {
@@ -43,4 +45,73 @@ describe("KeePass2 Xml Importer", () => {
const result = await importer.parse(TestData2);
expect(result.success).toBe(false);
});
describe("protected fields handling", () => {
it("should import protected custom fields as hidden fields", async () => {
const importer = new KeePass2XmlImporter();
const result = await importer.parse(TestDataWithProtectedFields);
expect(result.success).toBe(true);
expect(result.ciphers.length).toBe(1);
const cipher = result.ciphers[0];
expect(cipher.name).toBe("Test Entry");
expect(cipher.login.username).toBe("testuser");
expect(cipher.login.password).toBe("testpass");
expect(cipher.notes).toContain("Regular notes");
// Check that protected custom field is imported as hidden field
const protectedField = cipher.fields.find((f) => f.name === "SAFE UN-LOCKING instructions");
expect(protectedField).toBeDefined();
expect(protectedField?.value).toBe("Secret instructions here");
expect(protectedField?.type).toBe(FieldType.Hidden);
// Check that regular custom field is imported as text field
const regularField = cipher.fields.find((f) => f.name === "CustomField");
expect(regularField).toBeDefined();
expect(regularField?.value).toBe("Custom value");
expect(regularField?.type).toBe(FieldType.Text);
});
it("should import long protected fields as hidden fields (not appended to notes)", async () => {
const importer = new KeePass2XmlImporter();
const result = await importer.parse(TestDataWithProtectedFields);
const cipher = result.ciphers[0];
// Long protected field should be imported as hidden field
const longField = cipher.fields.find((f) => f.name === "LongProtectedField");
expect(longField).toBeDefined();
expect(longField?.type).toBe(FieldType.Hidden);
expect(longField?.value).toContain("This is a very long protected field");
// Should not be appended to notes
expect(cipher.notes).not.toContain("LongProtectedField");
});
it("should import multiline protected fields as hidden fields (not appended to notes)", async () => {
const importer = new KeePass2XmlImporter();
const result = await importer.parse(TestDataWithProtectedFields);
const cipher = result.ciphers[0];
// Multiline protected field should be imported as hidden field
const multilineField = cipher.fields.find((f) => f.name === "MultilineProtectedField");
expect(multilineField).toBeDefined();
expect(multilineField?.type).toBe(FieldType.Hidden);
expect(multilineField?.value).toContain("Line 1");
// Should not be appended to notes
expect(cipher.notes).not.toContain("MultilineProtectedField");
});
it("should not append protected custom fields to notes", async () => {
const importer = new KeePass2XmlImporter();
const result = await importer.parse(TestDataWithProtectedFields);
const cipher = result.ciphers[0];
expect(cipher.notes).not.toContain("SAFE UN-LOCKING instructions");
expect(cipher.notes).not.toContain("Secret instructions here");
});
});
});

View File

@@ -1,6 +1,7 @@
// FIXME: Update this file to be type safe and remove this and next line
// @ts-strict-ignore
import { FieldType } from "@bitwarden/common/vault/enums";
import { FieldView } from "@bitwarden/common/vault/models/view/field.view";
import { FolderView } from "@bitwarden/common/vault/models/view/folder.view";
import { ImportResult } from "../models/import-result";
@@ -92,16 +93,26 @@ export class KeePass2XmlImporter extends BaseImporter implements Importer {
} else if (key === "Notes") {
cipher.notes += value + "\n";
} else {
let type = FieldType.Text;
const attrs = valueEl.attributes as any;
if (
const isProtected =
attrs.length > 0 &&
attrs.ProtectInMemory != null &&
attrs.ProtectInMemory.value === "True"
) {
type = FieldType.Hidden;
attrs.ProtectInMemory.value === "True";
if (isProtected) {
// Protected fields should always be imported as hidden fields,
// regardless of length or newlines (fixes #16897)
if (cipher.fields == null) {
cipher.fields = [];
}
const field = new FieldView();
field.type = FieldType.Hidden;
field.name = key;
field.value = value;
cipher.fields.push(field);
} else {
this.processKvp(cipher, key, value, FieldType.Text);
}
this.processKvp(cipher, key, value, type);
}
});

View File

@@ -29,8 +29,9 @@ export class RoboFormCsvImporter extends BaseImporter implements Importer {
cipher.notes = this.getValueOrDefault(value.Note);
cipher.name = this.getValueOrDefault(value.Name, "--");
cipher.login.username = this.getValueOrDefault(value.Login);
cipher.login.password = this.getValueOrDefault(value.Pwd);
cipher.login.uris = this.makeUriArray(value.Url);
cipher.login.password =
this.getValueOrDefault(value.Pwd) ?? this.getValueOrDefault(value.Password);
cipher.login.uris = this.makeUriArray(value.Url) ?? this.makeUriArray(value.URL);
if (!this.isNullOrWhitespace(value.Rf_fields)) {
this.parseRfFields(cipher, value);

View File

@@ -354,6 +354,57 @@ line2</Value>
</Group>
<DeletedObjects />
</KeePassFile>`;
export const TestDataWithProtectedFields = `<?xml version="1.0" encoding="utf-8" standalone="yes"?>
<KeePassFile>
<Root>
<Group>
<UUID>KvS57lVwl13AfGFLwkvq4Q==</UUID>
<Name>Root</Name>
<Entry>
<UUID>fAa543oYlgnJKkhKag5HLw==</UUID>
<String>
<Key>Title</Key>
<Value>Test Entry</Value>
</String>
<String>
<Key>UserName</Key>
<Value>testuser</Value>
</String>
<String>
<Key>Password</Key>
<Value ProtectInMemory="True">testpass</Value>
</String>
<String>
<Key>URL</Key>
<Value>https://example.com</Value>
</String>
<String>
<Key>Notes</Key>
<Value>Regular notes</Value>
</String>
<String>
<Key>SAFE UN-LOCKING instructions</Key>
<Value ProtectInMemory="True">Secret instructions here</Value>
</String>
<String>
<Key>CustomField</Key>
<Value>Custom value</Value>
</String>
<String>
<Key>LongProtectedField</Key>
<Value ProtectInMemory="True">This is a very long protected field value that exceeds 200 characters. It contains sensitive information that should be imported as a hidden field and not appended to the notes section. This text is long enough to trigger the old behavior.</Value>
</String>
<String>
<Key>MultilineProtectedField</Key>
<Value ProtectInMemory="True">Line 1
Line 2
Line 3</Value>
</String>
</Entry>
</Group>
</Root>
</KeePassFile>`;
export const TestData2 = `<?xml version="1.0" encoding="utf-8" standalone="yes"?>
<Meta>
<Generator>KeePass</Generator>

118
package-lock.json generated
View File

@@ -14,15 +14,15 @@
"libs/**/*"
],
"dependencies": {
"@angular/animations": "20.3.15",
"@angular/animations": "20.3.16",
"@angular/cdk": "20.2.14",
"@angular/common": "20.3.15",
"@angular/compiler": "20.3.15",
"@angular/core": "20.3.15",
"@angular/forms": "20.3.15",
"@angular/platform-browser": "20.3.15",
"@angular/platform-browser-dynamic": "20.3.15",
"@angular/router": "20.3.15",
"@angular/common": "20.3.16",
"@angular/compiler": "20.3.16",
"@angular/core": "20.3.16",
"@angular/forms": "20.3.16",
"@angular/platform-browser": "20.3.16",
"@angular/platform-browser-dynamic": "20.3.16",
"@angular/router": "20.3.16",
"@bitwarden/commercial-sdk-internal": "0.2.0-main.470",
"@bitwarden/sdk-internal": "0.2.0-main.470",
"@electron/fuses": "1.8.0",
@@ -74,7 +74,7 @@
"@angular-devkit/build-angular": "20.3.12",
"@angular-eslint/schematics": "20.7.0",
"@angular/cli": "20.3.12",
"@angular/compiler-cli": "20.3.15",
"@angular/compiler-cli": "20.3.16",
"@babel/core": "7.28.5",
"@babel/preset-env": "7.28.5",
"@compodoc/compodoc": "1.1.32",
@@ -2203,9 +2203,9 @@
}
},
"node_modules/@angular/animations": {
"version": "20.3.15",
"resolved": "https://registry.npmjs.org/@angular/animations/-/animations-20.3.15.tgz",
"integrity": "sha512-ikyKfhkxoqQA6JcBN0B9RaN6369sM1XYX81Id0lI58dmWCe7gYfrTp8ejqxxKftl514psQO3pkW8Gn1nJ131Gw==",
"version": "20.3.16",
"resolved": "https://registry.npmjs.org/@angular/animations/-/animations-20.3.16.tgz",
"integrity": "sha512-N83/GFY5lKNyWgPV3xHHy2rb3/eP1ZLzSVI+dmMVbf3jbqwY1YPQcMiAG8UDzaILY1Dkus91kWLF8Qdr3nHAzg==",
"license": "MIT",
"dependencies": {
"tslib": "^2.3.0"
@@ -2214,7 +2214,7 @@
"node": "^20.19.0 || ^22.12.0 || >=24.0.0"
},
"peerDependencies": {
"@angular/core": "20.3.15"
"@angular/core": "20.3.16"
}
},
"node_modules/@angular/build": {
@@ -2627,9 +2627,9 @@
}
},
"node_modules/@angular/common": {
"version": "20.3.15",
"resolved": "https://registry.npmjs.org/@angular/common/-/common-20.3.15.tgz",
"integrity": "sha512-k4mCXWRFiOHK3bUKfWkRQQ8KBPxW8TAJuKLYCsSHPCpMz6u0eA1F0VlrnOkZVKWPI792fOaEAWH2Y4PTaXlUHw==",
"version": "20.3.16",
"resolved": "https://registry.npmjs.org/@angular/common/-/common-20.3.16.tgz",
"integrity": "sha512-GRAziNlntwdnJy3F+8zCOvDdy7id0gITjDnM6P9+n2lXvtDuBLGJKU3DWBbvxcCjtD6JK/g/rEX5fbCxbUHkQQ==",
"license": "MIT",
"dependencies": {
"tslib": "^2.3.0"
@@ -2638,14 +2638,14 @@
"node": "^20.19.0 || ^22.12.0 || >=24.0.0"
},
"peerDependencies": {
"@angular/core": "20.3.15",
"@angular/core": "20.3.16",
"rxjs": "^6.5.3 || ^7.4.0"
}
},
"node_modules/@angular/compiler": {
"version": "20.3.15",
"resolved": "https://registry.npmjs.org/@angular/compiler/-/compiler-20.3.15.tgz",
"integrity": "sha512-lMicIAFAKZXa+BCZWs3soTjNQPZZXrF/WMVDinm8dQcggNarnDj4UmXgKSyXkkyqK5SLfnLsXVzrX6ndVT6z7A==",
"version": "20.3.16",
"resolved": "https://registry.npmjs.org/@angular/compiler/-/compiler-20.3.16.tgz",
"integrity": "sha512-Pt9Ms9GwTThgzdxWBwMfN8cH1JEtQ2DK5dc2yxYtPSaD+WKmG9AVL1PrzIYQEbaKcWk2jxASUHpEWSlNiwo8uw==",
"license": "MIT",
"dependencies": {
"tslib": "^2.3.0"
@@ -2655,9 +2655,9 @@
}
},
"node_modules/@angular/compiler-cli": {
"version": "20.3.15",
"resolved": "https://registry.npmjs.org/@angular/compiler-cli/-/compiler-cli-20.3.15.tgz",
"integrity": "sha512-8sJoxodxsfyZ8eJ5r6Bx7BCbazXYgsZ1+dE8t5u5rTQ6jNggwNtYEzkyReoD5xvP+MMtRkos3xpwq4rtFnpI6A==",
"version": "20.3.16",
"resolved": "https://registry.npmjs.org/@angular/compiler-cli/-/compiler-cli-20.3.16.tgz",
"integrity": "sha512-l3xF/fXfJAl/UrNnH9Ufkr79myjMgXdHq1mmmph2UnpeqilRB1b8lC9sLBV9MipQHVn3dwocxMIvtrcryfOaXw==",
"dev": true,
"license": "MIT",
"dependencies": {
@@ -2678,7 +2678,7 @@
"node": "^20.19.0 || ^22.12.0 || >=24.0.0"
},
"peerDependencies": {
"@angular/compiler": "20.3.15",
"@angular/compiler": "20.3.16",
"typescript": ">=5.8 <6.0"
},
"peerDependenciesMeta": {
@@ -2864,9 +2864,9 @@
}
},
"node_modules/@angular/core": {
"version": "20.3.15",
"resolved": "https://registry.npmjs.org/@angular/core/-/core-20.3.15.tgz",
"integrity": "sha512-NMbX71SlTZIY9+rh/SPhRYFJU0pMJYW7z/TBD4lqiO+b0DTOIg1k7Pg9ydJGqSjFO1Z4dQaA6TteNuF99TJCNw==",
"version": "20.3.16",
"resolved": "https://registry.npmjs.org/@angular/core/-/core-20.3.16.tgz",
"integrity": "sha512-KSFPKvOmWWLCJBbEO+CuRUXfecX2FRuO0jNi9c54ptXMOPHlK1lIojUnyXmMNzjdHgRug8ci9qDuftvC2B7MKg==",
"license": "MIT",
"dependencies": {
"tslib": "^2.3.0"
@@ -2875,7 +2875,7 @@
"node": "^20.19.0 || ^22.12.0 || >=24.0.0"
},
"peerDependencies": {
"@angular/compiler": "20.3.15",
"@angular/compiler": "20.3.16",
"rxjs": "^6.5.3 || ^7.4.0",
"zone.js": "~0.15.0"
},
@@ -2889,9 +2889,9 @@
}
},
"node_modules/@angular/forms": {
"version": "20.3.15",
"resolved": "https://registry.npmjs.org/@angular/forms/-/forms-20.3.15.tgz",
"integrity": "sha512-gS5hQkinq52pm/7mxz4yHPCzEcmRWjtUkOVddPH0V1BW/HMni/p4Y6k2KqKBeGb9p8S5EAp6PDxDVLOPukp3mg==",
"version": "20.3.16",
"resolved": "https://registry.npmjs.org/@angular/forms/-/forms-20.3.16.tgz",
"integrity": "sha512-1yzbXpExTqATpVcqA3wGrq4ACFIP3mRxA4pbso5KoJU+/4JfzNFwLsDaFXKpm5uxwchVnj8KM2vPaDOkvtp7NA==",
"license": "MIT",
"dependencies": {
"tslib": "^2.3.0"
@@ -2900,16 +2900,16 @@
"node": "^20.19.0 || ^22.12.0 || >=24.0.0"
},
"peerDependencies": {
"@angular/common": "20.3.15",
"@angular/core": "20.3.15",
"@angular/platform-browser": "20.3.15",
"@angular/common": "20.3.16",
"@angular/core": "20.3.16",
"@angular/platform-browser": "20.3.16",
"rxjs": "^6.5.3 || ^7.4.0"
}
},
"node_modules/@angular/platform-browser": {
"version": "20.3.15",
"resolved": "https://registry.npmjs.org/@angular/platform-browser/-/platform-browser-20.3.15.tgz",
"integrity": "sha512-TxRM/wTW/oGXv/3/Iohn58yWoiYXOaeEnxSasiGNS1qhbkcKtR70xzxW6NjChBUYAixz2ERkLURkpx3pI8Q6Dw==",
"version": "20.3.16",
"resolved": "https://registry.npmjs.org/@angular/platform-browser/-/platform-browser-20.3.16.tgz",
"integrity": "sha512-YsrLS6vyS77i4pVHg4gdSBW74qvzHjpQRTVQ5Lv/OxIjJdYYYkMmjNalCNgy1ZuyY6CaLIB11ccxhrNnxfKGOQ==",
"license": "MIT",
"dependencies": {
"tslib": "^2.3.0"
@@ -2918,9 +2918,9 @@
"node": "^20.19.0 || ^22.12.0 || >=24.0.0"
},
"peerDependencies": {
"@angular/animations": "20.3.15",
"@angular/common": "20.3.15",
"@angular/core": "20.3.15"
"@angular/animations": "20.3.16",
"@angular/common": "20.3.16",
"@angular/core": "20.3.16"
},
"peerDependenciesMeta": {
"@angular/animations": {
@@ -2929,9 +2929,9 @@
}
},
"node_modules/@angular/platform-browser-dynamic": {
"version": "20.3.15",
"resolved": "https://registry.npmjs.org/@angular/platform-browser-dynamic/-/platform-browser-dynamic-20.3.15.tgz",
"integrity": "sha512-RizuRdBt0d6ongQ2y8cr8YsXFyjF8f91vFfpSNw+cFj+oiEmRC1txcWUlH5bPLD9qSDied8qazUi0Tb8VPQDGw==",
"version": "20.3.16",
"resolved": "https://registry.npmjs.org/@angular/platform-browser-dynamic/-/platform-browser-dynamic-20.3.16.tgz",
"integrity": "sha512-5mECCV9YeKH6ue239GXRTGeDSd/eTbM1j8dDejhm5cGnPBhTxRw4o+GgSrWTYtb6VmIYdwUGBTC+wCBphiaQ2A==",
"license": "MIT",
"dependencies": {
"tslib": "^2.3.0"
@@ -2940,16 +2940,16 @@
"node": "^20.19.0 || ^22.12.0 || >=24.0.0"
},
"peerDependencies": {
"@angular/common": "20.3.15",
"@angular/compiler": "20.3.15",
"@angular/core": "20.3.15",
"@angular/platform-browser": "20.3.15"
"@angular/common": "20.3.16",
"@angular/compiler": "20.3.16",
"@angular/core": "20.3.16",
"@angular/platform-browser": "20.3.16"
}
},
"node_modules/@angular/router": {
"version": "20.3.15",
"resolved": "https://registry.npmjs.org/@angular/router/-/router-20.3.15.tgz",
"integrity": "sha512-6+qgk8swGSoAu7ISSY//GatAyCP36hEvvUgvjbZgkXLLH9yUQxdo77ij05aJ5s0OyB25q/JkqS8VTY0z1yE9NQ==",
"version": "20.3.16",
"resolved": "https://registry.npmjs.org/@angular/router/-/router-20.3.16.tgz",
"integrity": "sha512-e1LiQFZaajKqc00cY5FboIrWJZSMnZ64GDp5R0UejritYrqorQQQNOqP1W85BMuY2owibMmxVfX+dJg/Mc8PuQ==",
"license": "MIT",
"dependencies": {
"tslib": "^2.3.0"
@@ -2958,9 +2958,9 @@
"node": "^20.19.0 || ^22.12.0 || >=24.0.0"
},
"peerDependencies": {
"@angular/common": "20.3.15",
"@angular/core": "20.3.15",
"@angular/platform-browser": "20.3.15",
"@angular/common": "20.3.16",
"@angular/core": "20.3.16",
"@angular/platform-browser": "20.3.16",
"rxjs": "^6.5.3 || ^7.4.0"
}
},
@@ -32414,9 +32414,9 @@
"license": "MIT"
},
"node_modules/msgpackr": {
"version": "1.11.5",
"resolved": "https://registry.npmjs.org/msgpackr/-/msgpackr-1.11.5.tgz",
"integrity": "sha512-UjkUHN0yqp9RWKy0Lplhh+wlpdt9oQBYgULZOiFhV3VclSF1JnSQWZ5r9gORQlNYaUKQoR8itv7g7z1xDDuACA==",
"version": "1.11.8",
"resolved": "https://registry.npmjs.org/msgpackr/-/msgpackr-1.11.8.tgz",
"integrity": "sha512-bC4UGzHhVvgDNS7kn9tV8fAucIYUBuGojcaLiz7v+P63Lmtm0Xeji8B/8tYKddALXxJLpwIeBmUN3u64C4YkRA==",
"dev": true,
"license": "MIT",
"optional": true,
@@ -34690,9 +34690,9 @@
}
},
"node_modules/ordered-binary": {
"version": "1.6.0",
"resolved": "https://registry.npmjs.org/ordered-binary/-/ordered-binary-1.6.0.tgz",
"integrity": "sha512-IQh2aMfMIDbPjI/8a3Edr+PiOpcsB7yo8NdW7aHWVaoR/pcDldunMvnnwbk/auPGqmKeAdxtZl7MHX/QmPwhvQ==",
"version": "1.6.1",
"resolved": "https://registry.npmjs.org/ordered-binary/-/ordered-binary-1.6.1.tgz",
"integrity": "sha512-QkCdPooczexPLiXIrbVOPYkR3VO3T6v2OyKRkR1Xbhpy7/LAVXwahnRCgRp78Oe/Ehf0C/HATAxfSr6eA1oX+w==",
"dev": true,
"license": "MIT",
"optional": true

View File

@@ -41,7 +41,7 @@
"@angular-devkit/build-angular": "20.3.12",
"@angular-eslint/schematics": "20.7.0",
"@angular/cli": "20.3.12",
"@angular/compiler-cli": "20.3.15",
"@angular/compiler-cli": "20.3.16",
"@babel/core": "7.28.5",
"@babel/preset-env": "7.28.5",
"@compodoc/compodoc": "1.1.32",
@@ -153,15 +153,15 @@
"webpack-node-externals": "3.0.0"
},
"dependencies": {
"@angular/animations": "20.3.15",
"@angular/animations": "20.3.16",
"@angular/cdk": "20.2.14",
"@angular/common": "20.3.15",
"@angular/compiler": "20.3.15",
"@angular/core": "20.3.15",
"@angular/forms": "20.3.15",
"@angular/platform-browser": "20.3.15",
"@angular/platform-browser-dynamic": "20.3.15",
"@angular/router": "20.3.15",
"@angular/common": "20.3.16",
"@angular/compiler": "20.3.16",
"@angular/core": "20.3.16",
"@angular/forms": "20.3.16",
"@angular/platform-browser": "20.3.16",
"@angular/platform-browser-dynamic": "20.3.16",
"@angular/router": "20.3.16",
"@bitwarden/sdk-internal": "0.2.0-main.470",
"@bitwarden/commercial-sdk-internal": "0.2.0-main.470",
"@electron/fuses": "1.8.0",