Skip to content

Export copy to clipboard functionality#50

Merged
weizman merged 16 commits intomainfrom
issue-46
Jan 15, 2025
Merged

Export copy to clipboard functionality#50
weizman merged 16 commits intomainfrom
issue-46

Conversation

@weizman
Copy link
Copy Markdown
Member

@weizman weizman commented Jul 12, 2024

close #46 by providing a new LavaDome method copy which copies the secret to clipboard without leaking it to any other entity.

@weizman weizman marked this pull request as draft July 12, 2024 23:01
@weizman weizman marked this pull request as ready for review July 16, 2024 11:38
@weizman weizman self-assigned this Jul 25, 2024
Comment thread packages/core/src/lavadome.mjs Outdated
@weizman weizman force-pushed the main branch 2 times, most recently from 7958a69 to fa4643f Compare September 18, 2024 08:04
@weizman weizman requested review from legobeat and naugtur September 18, 2024 08:28

function text(text) {
if (typeof text !== 'string') {
let secret = '';
Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

It doesn't feel great to have the secret hang out in memory in this long-lived scope... Not that I have a proposal on how it could be achieved differently, though...

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I see your point, but essentially, it's only natural that in order to provide service where the input is somehow manipulated we must have access to it

defineProperties(this, {text: {value: text}});
defineProperties(this, {
text: {value: text},
copy: {value: copy},
Copy link
Copy Markdown
Collaborator

@legobeat legobeat Oct 1, 2024

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Re https://github.com/LavaMoat/LavaDome/pull/50/files#r1782176796

Could setting the value of copy (including its declaration and instantiation) be moved to the inner scope of text? If so, maybe the need to move secret out of scope is no longer here?

Or would that conflict with the "make exported API tamper-proof" part? If so I think I'm still leaning towards that as the lesser bad. WDYT?

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

How would we export copy if it's inside text?

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

If we assign it inside the handler of text, is there a need to?

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

AFAIU, if you move the copy function into the text function, you closure the secret within the text scope instead of the LavaDome scope, which doesn't solve the problem, just shifts it elsewhere - the secret still must be scoped somewhere

Copy link
Copy Markdown
Collaborator

@legobeat legobeat Oct 1, 2024

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The secret is already part of the text scope (by virtue of being an argument).

However, moving/copying it to the outer scope widens the exposure (and lifetime, but not sure to what extent we can actually limit that if introducing the copy-to-clipboard functionality as intended).

For example, consider a scenario where text() is called a second time with invalid input on an existing LD instance:

ld.text('actualsecret');
// at some point later...
ld.text();

This will throw an Error where 'actualsecret' is still in scope. I don't have a PoC or anything but does seem like it could more easily leak to a malicious extension or devtool/debugger integration.

Vs if it's contained in the text function, separate text() invocations are not able to access each others' secrets, and subsequent calls would make new copy instances with isolated scopes wrt secret.

Copy link
Copy Markdown
Collaborator

@legobeat legobeat Oct 2, 2024

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

One way to at least prevent one aspect of scope-creep of secret itself without compromising on the tamper-proofing could be to freeze a property accessor and reassign copy itself instead:

// instead of `let secret`
let _copy = () => Promise.resolve();

async function copy() {
  await _copy();
}

// [...]

function text(input) {
  _copy = async () = {
    // `_copy` can now access `input` from `text` directly
  }
}

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

sold c8c9cd7

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I actually just removed c8c9cd7 because:

  1. it turns out the way i did it dropped a commit coming from main by accident which could have been a terrible mistake
  2. a younger comment made me realize the implementation isn't right, and that also I'm again not convinced this can be solved without storing the secret in some upper scope

For example, I'm not sure how #50 (comment) demonstrates this otherwise, how scoping a function that has access to the secret instead of the secret itself is far better?

A demonstrating PR here could very much help me understand 🙏

Comment thread packages/core/src/lavadome.mjs Outdated
if (typeof input !== 'string') {
throw new Error(
`LavaDomeCore: first argument must be a string, instead got ${stringify(text)}`);
`LavaDomeCore: first argument must be a string, instead got ${stringify(input)}`);
Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
`LavaDomeCore: first argument must be a string, instead got ${stringify(input)}`);
`LavaDomeCore: first argument must be a string, instead got ${typeof input}`);

Type should be enough, no need to template secret in error message

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

also 38ef179

Comment on lines +80 to +83
const type = 'text/plain';
const blob = new Blob([secret], {type});
const data = [new ClipboardItem({[type]: blob})];
await write(clipboard, data);
Copy link
Copy Markdown
Collaborator

@legobeat legobeat Oct 2, 2024

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Maybe swallow errors on write?

Suggested change
const type = 'text/plain';
const blob = new Blob([secret], {type});
const data = [new ClipboardItem({[type]: blob})];
await write(clipboard, data);
try {
const type = 'text/plain';
const blob = new Blob([secret], {type});
const data = [new ClipboardItem({[type]: blob})];
// do we also want to return the result of the await here?
await write(clipboard, data);
} finally {}

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

why?

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

To reduce the likelihood of an error containing a reference to the secret?

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

We shouldn't swallow errors that have nothing to do with us, the app should become aware of such errors being thrown. The error will never leak the secret, because if it did, it would have been a major info leak in the design of the native API, which should remain outside of LavaDome's jurisdiction.

@weizman weizman requested a review from legobeat October 7, 2024 13:51
boneskull
boneskull previously approved these changes Oct 7, 2024
Copy link
Copy Markdown
Member

@boneskull boneskull left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

mostly just questions. I'm not familiar with this (or React)

Comment thread packages/core/src/lavadome.mjs Outdated
}
}

export function LavaDome(host, opts) {
Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Is this a constructor? A React component? if the former, why not use class?

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

It's the former.
Because there's no way to make methods of a class (truly) private and inaccessible.

Copy link
Copy Markdown
Member

@boneskull boneskull Oct 8, 2024

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

not even with #private? but wait, why do we need to worry about that at all in this use case? I don't see any methods.

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

not even with #private
Screenshot 2024-10-09 at 11 31 55

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

text and the new copy are methods.
They're not assigned to the prototype as methods for the same privacy reason, but they are effectively methods of the instance.

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

for the sake of being through, I still don't understand why it needs to be a function and not a class with a constructor impl

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I have to admit I can't think of the reason I used to have when making this decision

Comment thread packages/core/src/native.mjs
Comment thread packages/react/src/lavadome.jsx
Comment thread packages/react/src/lavadome.jsx
Comment thread packages/react/src/lavadome.jsx
Comment thread packages/react/src/lavadome.jsx Outdated
Comment thread packages/core/src/lavadome.mjs Outdated
Comment thread packages/core/src/lavadome.mjs Outdated
Comment thread packages/core/src/lavadome.mjs Outdated
boneskull
boneskull previously approved these changes Jan 10, 2025
Copy link
Copy Markdown
Member

@boneskull boneskull left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I'm going to approve this because I don't know any better

@weizman weizman requested a review from boneskull January 13, 2025 15:04
@weizman weizman merged commit 46df348 into main Jan 15, 2025
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

Support copy to clipboard

3 participants