PostgreSQL Common Table Expression: Ambiguous Column
While there is no real solution that matches your requirements, there are a few things to consider about the requirements.

First, let's see the hint that an error raised about a misnamed column produces:

WITH ts AS (
    SELECT * FROM t1 JOIN t2 USING (t_id)
) SELECT col2 FROM ts;

ERROR:  column "col2" does not exist
LINE 1: ...AS (SELECT * FROM t1 JOIN t2 USING (t_id)) SELECT col2 FROM ...
                                                             ^
HINT:  Perhaps you meant to reference the column "ts.col1" or the column "ts.col1".


Even the database suggests to choose from two indentically named columns - which, as you already guessed, impossible.  (Interestingly, you cannot do such a thing in a definition of a view, not to mention tables.)

So, about the requirements:

It is usually wise not to use * in the first place.  Apart from being faster to type, it does not bring too much.  When writing the main SELECT, we usually have an idea which columns we need - just add those inside the CTE.  This might be in some cases even beneficial for the performance of the query (rendering the size of the data set smaller).

When you want to make it simpler to type, you could try something like

SELECT f.*, s.data AS s_data 
FROM first AS f JOIN second AS s ON f.id = s.key


In my practice, it is even better to name columns with a prefix derived from the table name:

CREATE TABLE first(
    f_id serial PRIMARY KEY,
    f_data VARCHAR,
    f_date DATE
);

CREATE TABLE second(
    s_id serial PRIMARY KEY,
    s_first_id INT REFERENCES first(id), -- an exception, for making more clear 
                                         -- what the column refers to
    s_data VARCHAR,
    s_date DATE
);


This way, even in big databases, it is very rare to have columns with the same name.  And this completely removes the problem you are fighting with.