Skip to content
Open
Show file tree
Hide file tree
Changes from 7 commits
Commits
Show all changes
30 commits
Select commit Hold shift + click to select a range
9f7d2de
chore: export dev_trie reserved keys
charmful0x Mar 12, 2026
24bcee2
fix: excluding reserved trie keys from address validation
charmful0x Mar 12, 2026
516a8f5
docs: document fns
charmful0x Mar 14, 2026
e584780
fix: align send_error/4 API and handle different Reason types
charmful0x Mar 16, 2026
861d572
fix: balance/3 return shape api
charmful0x Mar 16, 2026
25a9028
perf: unsupported actions fall through send_error/4 instead of no-op …
charmful0x Mar 17, 2026
9035d44
chore: upstream balance corruption hardening
charmful0x Mar 17, 2026
4bc6711
chore: enforce_set_authority/3 logic hardening
charmful0x Mar 17, 2026
ad5648e
chore: hardening validate_address/1 special char checks
charmful0x Mar 17, 2026
64744ec
fix: test regression
charmful0x Mar 17, 2026
a46ae54
fix: patch dev_mint_authority stale send ref
charmful0x Mar 17, 2026
94202e3
chore: add reserved recipient transfer test and validate balance/3 Ac…
charmful0x Mar 17, 2026
69aafc9
chore: harden enforce_mint_authority cse branches
charmful0x Mar 17, 2026
ea17733
fix: validate addresses
charmful0x Mar 17, 2026
dc0568d
chore: add not minter test
charmful0x Mar 17, 2026
cfaf7a1
chore: batch mint invalid recipient
charmful0x Mar 17, 2026
bad8def
Merge pull request #760 from permaweb/audit/mint-authority
charmful0x Mar 17, 2026
bc0780b
fix: validate/5 min N threshold lists dedup
charmful0x Mar 18, 2026
4b29884
chore: dup authority test + satisfies_constraints/6 fix
charmful0x Mar 18, 2026
25b94cd
fix: solidify Match threshold values
charmful0x Mar 18, 2026
3f580dc
Merge pull request #763 from permaweb/audit/security
charmful0x Mar 18, 2026
84729e4
perf: dynamic Valid authority threshold default
charmful0x Mar 18, 2026
c52c819
Merge pull request #764 from permaweb/audit/security
charmful0x Mar 18, 2026
5b95d92
fix: error propagation + edge case handling
charmful0x Mar 18, 2026
ec7fd3d
perf: hardeing Match checks logic
charmful0x Mar 19, 2026
4e661e7
chore: Default cleanup in Match path
charmful0x Mar 19, 2026
f8e6dc4
Merge pull request #769 from permaweb/audit/security
charmful0x Mar 19, 2026
e078052
fix: handle comma separated authorities
charmful0x Mar 19, 2026
038b33c
chore: add comma separated authority security test
charmful0x Mar 19, 2026
5a6df48
Merge pull request #770 from permaweb/audit/security
charmful0x Mar 19, 2026
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
72 changes: 46 additions & 26 deletions src/dev_token.erl
Original file line number Diff line number Diff line change
Expand Up @@ -94,7 +94,7 @@ balance(Base, Req, Opts) ->
hb_ao:set(Req, <<"subject">>, Account, Opts),
Opts
),
Balances =
Balance =
hb_ao:resolve_many(
[
NormBase,
Expand All @@ -107,11 +107,15 @@ balance(Base, Req, Opts) ->
debug_token,
{balance_after_mint_normalization,
{account, Account},
{balances, Balances}
{balance, Balance}
},
Opts
),
{ok, Balances}.
case Balance of
{ok, Balance} -> {ok, Balance};
{error, not_found} -> {ok, 0};
{error, Reason} -> {error, Reason}
end.

transfer(Base, Assignment, Opts) ->
maybe
Expand Down Expand Up @@ -144,8 +148,9 @@ transfer(Base, Assignment, Opts) ->
Opts
),
% Sanity check the transfer request.
true ?= (is_integer(SenderBalance) and is_integer(RecipientBalance))
orelse {error, <<"Invalid balance types.">>},
true ?= (is_integer(SenderBalance) and is_integer(RecipientBalance)
and (SenderBalance >= 0) and (RecipientBalance >= 0))
orelse {error, <<"Invalid balance values.">>},
true ?= (is_integer(Quantity) and (Quantity >= 0))
Copy link
Author

Choose a reason for hiding this comment

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

should we allow 0 transfers? or atleast in the case of $AO? this could be a spam entrypoint..?

ETH and ERC-20 spec allows 0 transfers due to the existence of the associated gas cost

orelse {error, <<"Quantity must be a non-negative integer.">>},
true ?= (SenderBalance >= Quantity)
Expand Down Expand Up @@ -185,7 +190,7 @@ transfer(Base, Assignment, Opts) ->
},
Opts
),
{ok, send_error(Base, Assignment, Reason, Opts)}
send_error(Base, Assignment, Reason, Opts)
end.

transfer_notices(From, Recipient, Quantity, Req, Opts) ->
Expand Down Expand Up @@ -227,14 +232,15 @@ normalize_mint(Base, Assignment, Opts) ->
is_supported_mint_action(Action) ->
lists:member(Action, ?MINT_ACTIONS).

%% @doc Verify if the action is a supported path on the mint device interdface,
%% and if so, switch to the mint device and run it.
%% @doc Verify if the action is a supported path on the mint device interface,
%% and if so, switch to the mint device and run it. Unsupported actions fall through
%% send_error/4 codepath.
action_as_mint_device(Action, Base, Req, Opts) ->
case is_supported_mint_action(Action) of
true -> as_mint_device(Action, Base, Req, Opts);
false ->
?event(error, {unsupported_token_action, Action}, Opts),
{ok, Base}
send_error(Base, Req, <<"unsupported action: " /binary, Action>>, Opts)
end.

%% @doc Run a given `path' on the mint device.
Expand Down Expand Up @@ -285,36 +291,50 @@ enforce_set_authority(Base, Req, Opts) ->

%%% Helper functions.

%% @doc Validate address format for security
%% @doc Validate address format for security. the validation
%% allows binary addresses up to 128 bytes and prevent invalid
%% addresses such as dev_trie reserved keys.
validate_address(Address) when is_binary(Address) ->
case byte_size(Address) of
0 -> {error, <<"Recipient address cannot be empty.">>};
N when N > 128 -> {error, <<"Recipient address is too long.">>};
_ ->
% Check for path separators (security: prevent path traversal)
case binary:match(Address, [<<"/">>, <<"\\">>]) of
nomatch -> true;
_ ->
{
error,
<<"Recipient address cannot contain path separators.">>
}
maybe
true ?= (not dev_trie:is_reserved_key(Address))
orelse {error, <<"Recipient address uses a reserved internal key.">>},
% Check for path separators (security: prevent path traversal)
case binary:match(Address, [<<"/">>, <<"\\">>]) of
nomatch -> true;
_ -> {error, <<"Recipient address cannot contain path separators.">>}
end
end
end;
validate_address(_) ->
{error, <<"Recipient address must be a binary.">>}.

send_error(Base, Assignment, Reason, Opts) when is_atom(Reason) ->
send_error(Base, Assignment, atom_to_binary(Reason), Opts);
send_error(Base, Assignment, Reason, Opts) when not is_binary(Reason) ->
send_error(
Base,
Assignment,
iolist_to_binary(io_lib:format("~0p", [Reason])),
Opts
);
send_error(Base, Assignment, Reason, Opts) when is_binary(Reason) ->
case hb_ao:resolve(Assignment, <<"body/from">>, Opts) of
{error, Error} ->
?event(token_short, {skipping_error_report, Error}, Opts),
{ok, Base};
{ok, Target} ->
dev_process_outbox:send(
#{
<<"target">> => Target,
<<"reason">> => Reason
},
Base,
Opts
)
{ok,
dev_process_outbox:send(
#{
<<"target">> => Target,
<<"reason">> => Reason
},
Base,
Opts
)
}
end.
12 changes: 12 additions & 0 deletions src/dev_trie.erl
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,7 @@
%%% 4 children!)
-module(dev_trie).
-export([info/0, keys/2, set/3, get/3, get/4]).
-export([reserved_keys/0, is_reserved_key/1]).
-include_lib("eunit/include/eunit.hrl").
-include("include/hb.hrl").

Expand Down Expand Up @@ -304,6 +305,17 @@ edges(TrieNode, Opts) ->
Opts
),
hb_maps:keys(Filtered).
%% @doc Returns a list of the modules's edge labels for trie node.
reserved_keys() ->
[ <<"node-value">>,
<<"device">>,
<<"commitments">>,
<<"priv">>,
<<"hashpath">>
].
%% @doc Checks if the passed Key is a reserved dev_trie trie node label.
is_reserved_key(Key) ->
lists:member(Key, reserved_keys()).

%% @doc Compute the longest common binary prefix of A and B, comparing chunks of
%% N bits.
Expand Down