自动填充深色图案
已发表: 2022-03-10报纸注册表单有姓名、电子邮件和密码字段。 所以,我开始在名称字段上输入,自动填充建议了我的个人资料。 但是有一些时髦的东西。 自动完成建议包括我的邮寄地址。 不用说,令人费解的是:地址不是表单中的字段。 为什么它甚至被建议?
当这个问题开始在我脑海中形成时,我的大脑已经发出信号让我的手指点击这个建议,并且完成了。 接下来,我被带到第二个表单页面,该页面要求提供更多信息,例如地址、电话、出生日期等。 所有这些字段也已由自动填充功能预先填充。
我松了口气。 这是一个“多步骤”的形式,而不是网站的伎俩。 毕竟,那是一份有声望的报纸。 我从第二页删除了所有可选信息,完成了注册,然后继续前进。
这种(麻烦的)交互突出了使用自动完成功能的风险之一。
自动完成和自动填充
它们可能听起来很相似,但autocomplete
和自动填充不是一回事。 尽管它们密切相关:
- 自动填充是一种浏览器功能,允许人们保存信息(在浏览器或操作系统上)并在 Web 表单上使用它。
-
autocomplete
是一个 HTML 属性,它为浏览器提供有关如何(或不)自动填充 Web 表单中的字段的指南。
我们可以说自动填充是“什么”,而自动填充是“如何”,即autofill
存储数据并尝试在 Web 表单中匹配它(基于字段的name
、 type
或id
),并且autocomplete
引导浏览器关于如何做(每个字段中需要哪些信息)。
自动完成是一个强大的功能,有许多选项允许指定许多不同类型的值:
- 个人:姓名、地址、电话、出生日期;
- 财务:信用卡号、姓名、到期日;
- 人口统计:地点、年龄、性别、语言;
- 专业:公司和职位。
自动填充是一个广泛使用的功能,无论是有意还是无意:谁不接受让浏览器保存/使用 Web 表单信息,无论是有意还是无意? 这可能是一个问题——尤其是结合了对autocomplete
的不当使用(以及如今增加的网络钓鱼电子邮件和 SMS 消息的数量惊人。)
隐私风险
这两个功能都给用户带来(至少)两个主要风险,都与他们的个人数据及其隐私有关:
- 填充不可见字段(这与具有隐藏类型的字段不同);
- 即使在用户提交表单之前,也可以通过 JavaScript 读取自动完成的信息。
这意味着一旦用户选择自动填充信息,所有字段都将可供开发人员阅读。 同样,无论用户是否提交表单,用户都不知道实际填充了哪些字段。
最后一部分是相对的:知道填充了哪些字段将取决于浏览器。 Safari 和 Firefox 在这方面做得很好(我们很快就会在下面看到)。 另一方面,目前最流行的浏览器 Chrome 提供了糟糕的体验,甚至可能欺骗最有知识的用户分享他们的个人信息。
如果我们还考虑用户不小心选择填充字段的时间,这个问题就会变得更加相关。 让我们通过一个例子更详细地检查它。
一个小实验
我进行了一个小实验,创建了一个包含许多字段的表单并附加了具有不同值的autocomplete
属性。 然后,我玩了一下表单的结构:
- 我通过将大部分字段放在屏幕外的容器中来隐藏大部分字段(而不是使用
hidden
或type="hidden"
); - 我从标签顺序中删除了视觉上隐藏的字段(因此键盘用户会忽略隐藏的字段);
- 我尝试以不同的顺序对字段进行排序(令我惊讶的是,这影响了自动填充!)。
最后,表单的代码如下所示:
<form method="post" action="javascript:alertData()"> <label for="name">Full name</label><input name="name" autocomplete="name" /><br/> <label for="email">Email</label><input name="email"/><br/> <label for="postal-code">ZIP</label><input name="postal-code" autocomplete="postal-code"/> <div class="hide-this"> <!-- Hidden --> <label for="firstname">First name</label><input tabindex="-1" type="hidden" name="firstname" autocomplete="given-name" /><br/> <label for="lastname">Last name</label><input tabindex="-1" name="lastname" autocomplete="family-name" /><br/> <label for="honorific-prefix">honorific-prefix</label><input tabindex="-1" name="honorific-prefix" autocomplete="honorific-prefix"/><br/> <label for="organization">Organization</label><input tabindex="-1" name="organization" /><br/> <label for="phone">Phone</label><input tabindex="-1" name="phone" autocomplete="tel" /><br/> <label for="address">address</label><input tabindex="-1" name="address" autocomplete="street-address" /><br/> <label for="city">City</label><input tabindex="-1" name="city" autocomplete="address-level2" /><br/> <label for="state">State</label><input tabindex="-1" name="state" autocomplete="address-level1" /><br/> <label for="level3">Level3</label><input tabindex="-1" name="state" autocomplete="address-level3" /><br/> <label for="level4">Level4</label><input tabindex="-1" name="state" autocomplete="address-level4" /><br/> <label for="country">Country</label><input tabindex="-1" name="country" autocomplete="country" /><br/> <label for="birthday">Birthday</label><input tabindex="-1" name="birthday" autocomplete="bday" /><br/> <label for="language">Language</label><input tabindex="-1" name="language" autocomplete="language" /><br/> <label for="sex">Sex</label><input tabindex="-1" name="sex" autocomplete="sex" /><br/> <label for="url">URL</label><input tabindex="-1" name="url" autocomplete="url" /><br/> <label for="photo">Photo</label><input tabindex="-1" name="photo" autocomplete="photo" /><br/> <label for="impp">IMPP</label><input tabindex="-1" name="impp" autocomplete="impp" /><br/> <label for="username">Username</label><input tabindex="-1" name="username" autocomplete="username" /><br/> <label for="password">Password</label><input tabindex="-1" name="password" autocomplete="password" /><br/> <label for="new-password">Password New</label><input tabindex="-1" name="new-password" autocomplete="new-password" /><br/> <label for="current-password">Password Current</label><input tabindex="-1" name="current-password" autocomplete="current-password" /><br/> <label for="cc">CC#</label><input tabindex="-1" name="cc" autocomplete="cc-number" /><br/> <label for="cc-name">CC Name</label><input tabindex="-1" name="cc-name" autocomplete="cc-name" /><br/> <label for="cc-expiration">CC expiration</label><input tabindex="-1" name="cc-expiration" autocomplete="cc-expiration" /><br/> <label for="cc-zipcode">CC Zipcode</label><input tabindex="-1" name="cc-zipcode" autocomplete="cc-postalcode" /><br/> </div> <button>Submit</button> </form>
注意:我不久前创建了这个演示,标准是一个动态文档。 从那时起,一些自动完成名称发生了变化。 例如,现在我们可以为地址或信用卡指定new-password
和current-password
或更多以前不可用的详细信息。
该表单具有三个可见字段( name
、 email
和zipcode
)。 虽然这种形式在保险公司、有线电视和其他服务提供商中很常见,但它可能不会太普遍,所以我用一个电子邮件字段进一步减少了这种形式。 我们到处都可以注册网站、新闻通讯或更新。 你可以在这里看到一个正在运行的演示:
如果您使用自动完成来填写表格,那么您已经分享了比您想要的更多的信息(别担心,这些都是本地的,没有与我分享)。 在 Chrome 中,它甚至可能看起来像一个完全正常的订阅表单。
如果您没有/使用自动填充,请不要担心。 以下是在三种不同浏览器上的体验总结。
注意:所有这些测试都假设使用自动填充并且基于虚假配置文件!
苹果浏览器
当您单击表单控件时,Safari 将在字段右侧显示一个图标。 单击它将显示一个弹出窗口,其中包含浏览器将与表单共享的信息:
一件好事:它显示了将作为表单的一部分共享的所有数据。 不仅是可见字段的数据,而且是所有字段的数据。 此时,用户可能会怀疑有些事情不太对劲。 有什么可疑的。
当我将表单缩小为电子邮件字段时,Safari 做了一些有趣的事情。 自动填充弹出窗口不同:
它声明它将仅共享电子邮件(并且仅共享该信息)。 但下面的联系信息可能比较棘手。 当我们单击该按钮时,浏览器会显示配置文件的摘要及其共享数据。 但这在任何地方都没有明确说明。 它看起来就像一张普通的名片,带有一些“共享/不共享”选项。 单击“自动填充”按钮后,表单将填充所有数据。 不仅是电子邮件:
因此,有一种方法可以让用户无意中与表单共享信息。 考虑到它是两个可能选项中带有图标的“突出显示”,这很棘手,但并不太牵强。
有趣的是,浏览器将个人数据与信用卡数据分开,但 Safari 根据个人数据(姓名和 ZIP)填充了部分信用卡信息。
火狐
在 Firefox 中使用自动填充有点复杂。 它不像 Chrome 那样是自动的,也没有像 Safari 那样的图标。 用户必须开始输入或再次单击才能看到自动填充弹出窗口,其中将包含浏览器将填写的每个类别的注释,而不仅仅是可见字段:
使用仅限电子邮件的表单进行测试,Firefox 显示了相同的自动填充弹出窗口,说明它将填充哪些字段类别。 没有任何区别。
与其他浏览器类似,在自动填充运行后,我们可以使用 JavaScript 读取所有值。
Firefox 是三者中最好的:它清楚地说明了哪些信息将与表单共享,而与字段或其顺序无关。 它隐藏了自动填充功能,发生了第二次用户交互。
键盘用户可以在没有意识到的情况下选择自动填充,方法是进入弹出气泡并按 Tab 键。
铬合金
然后轮到 Chrome 了。 (这里我使用“Chrome”,但测试的几个基于 Chromium 的浏览器的结果相似。)我点击了该字段,没有任何进一步的交互,自动填充弹出窗口显示。 虽然 Firefox 和 Safari 有很多共同点,但 Chrome 完全不同:它只显示两个值,而且都是可见的。
这个显示是设计使然。 我特意选择了字段的顺序,以获得可见控件和自动完成建议的特定组合。 然而,看起来 Chrome 为第二个值赋予了一些自动完成属性更多的“权重”。 这使得弹出窗口根据表单中字段的顺序发生变化。
使用表单的第二个版本进行测试也好不到哪里去:
虽然弹出窗口显示了一个不可见的字段(名称),但尚不清楚弹出窗口中名称的用途。 有经验的用户可能知道会发生这种情况,因为名称是共享的,但普通用户(甚至是有经验的用户)可能会认为电子邮件与具有该名称的个人资料相关联。 浏览器将与表单共享的数据为零。
一旦用户点击自动填充按钮,开发人员就可以使用 JavaScript 读取数据:
Chrome 是最严重的违规者:它自动共享信息,不清楚涉及哪些数据,并且自动填充建议会根据控件的顺序和属性而变化。
前两个问题对所有/许多浏览器都很常见,以至于它甚至可以被视为一项功能。 然而,第三个问题是 Chromium 浏览器独有的,它促进了粗略的深色模式。
如果不是因为 Chrome 占据了在线浏览器(包括 Chrome 和基于 Chromium 的)相当大的市场份额,这种行为将更像是一个轶事而不是问题。
黑暗模式
您可能知道,深色模式是一种欺骗性的 UX 模式,它会诱使用户做他们可能并不真正想做的事情。
“当你使用网站和应用程序时,你不会阅读每一页上的每一个字——你会略读并做出假设。 如果一家公司想欺骗你做某事,他们可以利用这一点,让页面看起来像是在说一件事,而实际上它在说另一件事。”
— Harry Brignull,darkpatterns.org
前面几点描述的行为显然是一种欺骗性的用户体验。 没有经验的用户不会意识到他们正在共享他们的个人数据。 甚至更多精通技术的人可能会被它欺骗,因为 Chrome 使所选选项看起来像是属于个人资料,而不是清楚地说明正在共享的信息。
浏览器实现会导致这种行为,但它需要开发人员将其设置到位以利用它。 不幸的是,已经有公司愿意利用它,将其作为获得潜在客户的功能出售。
只要出现深色图案,它也可能是非法的。 这是因为它违反了欧洲通用数据保护条例 (GDPR) 第 5 条中规定的与个人数据处理相关的许多原则:
- 合法、公平、透明
这个过程几乎是透明的。 - 目的限制
数据的处理方式与最初的目的不符。 - 数据最小化
恰恰相反。 数据最大化:获取尽可能多的信息。
例如,如果您想订阅时事通讯或索取有关产品的信息,并且您提供了您的电子邮件,则网站没有合法权利获取您的姓名、地址、出生日期、电话号码或其他任何信息同意或知情。 即使您认为用户在点击自动填充时给予了许可,但获取数据的目的与表单的初衷并不相符。
可能的解决方案
为了避免这个问题,所有参与者都需要做出贡献并帮助解决这个问题:
- 用户
- 开发人员和设计师
- 浏览器
1. 用户
用户方面唯一要做的就是确保自动填充弹出窗口中显示的数据是正确的。
但我们需要记住,用户是这里的受害者。 我们可以责怪他们在点击自动填充时没有给予足够的关注,但这是不公平的。 另外,一个人可能会错误地单击按钮并意外共享其数据的原因有很多。 因此,即使是善意和精明的用户也可能会爱上它。
2. 开发人员和设计师
说实话。 虽然开发人员不是问题的根本原因,但他们在利用暗模式方面发挥了关键作用。 无论是意外还是出于恶意。
让我们负责任和诚实(这次是字面意义上的),因为这是开发人员和设计人员可以做的事情来建立信任并充分利用自动填充和自动完成功能:
- 仅自动完成您需要的数据。
- 明确说明将收集哪些数据。
- 不要隐藏稍后提交的表单字段。
- 不要误导或欺骗用户发送更多数据。
作为一种极端措施,可能会尽量避免自动完成某些字段。 但是,当然,这会带来其他问题,因为它会降低表单的可用性和可访问性。 所以找到平衡可能很棘手。
所有这一切都没有考虑可以利用暗模式的 XSS 漏洞的可能性。 当然,那将是一个完全不同的故事,也是一个更重要的问题。
3. 浏览器
大部分工作需要从浏览器端完成(尤其是在 Chromium 端)。 但是,让我首先说明 Web 浏览器处理自动填充/自动完成的方式并非都是坏事。 很多东西都是好的。 例如:
- 它们限制了可以共享的数据
浏览器有一个用于自动完成的字段列表,其中可能不包括 HTML 标准中描述的所有值。 - 它们封装和分组数据
浏览器将个人信息和财务信息分开,以保护信用卡等关键价值。 Safari 对此有一些问题,但问题不大。 - 他们警告将要共享的数据
有时这可能不完整(Chrome)或不清楚(Safari),但它们确实会提醒用户。
尽管如此,许多或所有网络浏览器都可以改进一些事情。
显示将自动完成的所有字段
浏览器应始终显示将在自动填充弹出窗口中自动完成的所有字段的列表(而不仅仅是部分列表。)此外,信息应明确标识为要共享的数据,而不是显示为常规联系人卡片可能会产生误导。
Firefox 在这一点上做得很好,Safari 总体上做得很好,与其他两个相比,Chrome 表现不佳。
不要在自动填充时触发onChange
事件
这将是一个有问题的请求,因为此行为是 HTML 标准中自动填充定义的一部分:
“自动完成机制必须由用户代理实现,就像用户修改了控件的数据一样 [...]。”
这意味着浏览器应该将自动完成的数据视为用户输入的数据,从而触发所有事件、显示值等。即使是在不可见的字段上也是如此。
防止在不可见元素上出现这种行为可以解决问题。 但是验证表单控件是否可见对浏览器来说可能代价高昂。 此外,这个解决方案只是部分的,因为即使没有输入触发事件,开发人员仍然可以读取这些值。
不允许开发人员在提交前阅读自动完成的字段
这也是有问题的,因为许多开发人员经常依赖于在提交之前读取字段值来验证值(例如,当用户离开输入时)。但这是有道理的:用户不想共享信息直到他们提交表单,所以浏览器也不应该。
另一种方法是在读取自动完成值时提供虚假数据。 Web 浏览器已经对访问的链接进行了类似的操作,为什么不对自动完成的表单字段做同样的事情呢? 提供乱七八糟的名称、与地方当局匹配的有效地址而不是用户地址、假电话号码? 这可以解决开发者验证需求,同时保护用户的个人信息。
显示浏览器将清楚地与表单共享的字段/值的完整列表将是向前迈出的一大步。 另外两个是理想的,但更多的是延伸目标。 尽管如此,它们仍然是可以大大改善隐私的举措。
自动填充深色模式仍然可以利用吗? 不幸的是,是的。 但这会复杂得多。 在这一点上,避免这种情况是用户的责任和开发者的责任。
结论
我们可以争辩说,自动完成不是一个巨大的安全问题(即使在 Chrome 上也不是),因为它需要用户交互来选择信息。 但是,我们也可以争辩说,潜在的数据丢失证明了适当的行动是合理的。 与保护关键个人信息相比,Chrome 对(相对)不太重要的安全/可用性问题(参见alert()
、 prompt()
和confirm()
进行了更多更改。
然后我们有深色图案的问题。 如果每个人都尽自己的一份力量,这是可以避免的:
- 用户应该小心他们自动填写的表格/数据;
- 开发人员应避免利用这些数据;
- 浏览器应该在保护人们的数据方面做得更好。
从根本上说,这种黑暗模式是一个浏览器问题(主要是 Chrome 问题),而不是一个小问题(隐私应该是在线的关键)。 但是有一个选择。 最后,是否利用暗模式取决于开发人员。 因此,让我们明智地选择并做正确的事。
Smashing 杂志的进一步阅读
- 更好的表单设计:每页一件事(案例研究),Adam Silver
- Web 表单中的常见问题和隐私,Vitaly Friedman
- 使用
accent-color
简化表单样式 , Michelle Barker - HTML5 输入类型:它们现在在哪里?,Drew McLellan
- Ionic React 中的表单和验证,Jerry Navi
- 移动表单设计的最佳实践,Nick Babich